xref: /haiku/src/kits/tracker/TitleView.cpp (revision 1978089f7cec856677e46204e992c7273d70b9af)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 
36 //	ListView title drawing and mouse manipulation classes
37 
38 
39 #include "TitleView.h"
40 
41 #include <Alert.h>
42 #include <Application.h>
43 #include <ControlLook.h>
44 #include <Debug.h>
45 #include <PopUpMenu.h>
46 #include <Window.h>
47 
48 #include <algorithm>
49 
50 #include <stdio.h>
51 #include <string.h>
52 
53 #include "Commands.h"
54 #include "ContainerWindow.h"
55 #include "PoseView.h"
56 #include "Utilities.h"
57 
58 
59 #define APP_SERVER_CLEARS_BACKGROUND 1
60 
61 
62 static const float kMinFontSize = 8.0f;
63 static const float kMinTitleHeight = 13.0f;
64 static const float kTitleSpacing = 1.4f;
65 
66 
67 static void
68 _DrawLine(BPoseView* view, BPoint from, BPoint to)
69 {
70 	float tint = B_NO_TINT;
71 	color_which highColor = view->HighUIColor(&tint);
72 	view->SetHighUIColor(view->LowUIColor(), B_DARKEN_1_TINT);
73 	view->StrokeLine(from, to);
74 	view->SetHighUIColor(highColor, tint);
75 }
76 
77 
78 static void
79 _UndrawLine(BPoseView* view, BPoint from, BPoint to)
80 {
81 	view->StrokeLine(from, to, B_SOLID_LOW);
82 }
83 
84 
85 static void
86 _DrawOutline(BView* view, BRect where)
87 {
88 	where.right++;
89 	where.bottom--;
90 	float tint = B_NO_TINT;
91 	color_which highColor = view->HighUIColor(&tint);
92 	view->SetHighUIColor(B_CONTROL_HIGHLIGHT_COLOR);
93 	view->StrokeRect(where);
94 	view->SetHighUIColor(highColor, tint);
95 }
96 
97 
98 //	#pragma mark - BTitleView
99 
100 
101 BTitleView::BTitleView(BPoseView* view)
102 	:
103 	BView("TitleView", B_WILL_DRAW),
104 	fPoseView(view),
105 	fTitleList(10, true),
106 	fHorizontalResizeCursor(B_CURSOR_ID_RESIZE_EAST_WEST),
107 	fPreviouslyClickedColumnTitle(0),
108 	fPreviousLeftClickTime(0),
109 	fTrackingState(NULL)
110 {
111 	SetHighUIColor(B_PANEL_BACKGROUND_COLOR);
112 	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
113 #if APP_SERVER_CLEARS_BACKGROUND
114 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
115 #else
116 	SetViewColor(B_TRANSPARENT_COLOR);
117 #endif
118 
119 	float fontSize = std::max(kMinFontSize,
120 		floorf(be_plain_font->Size() * 0.75f));
121 
122 	BFont font(be_plain_font);
123 	font.SetSize(fontSize);
124 	SetFont(&font);
125 
126 	fPreferredHeight = std::max(kMinTitleHeight,
127 		ceilf(fontSize * kTitleSpacing));
128 
129 	Reset();
130 }
131 
132 
133 BTitleView::~BTitleView()
134 {
135 	delete fTrackingState;
136 }
137 
138 
139 void
140 BTitleView::Reset()
141 {
142 	fTitleList.MakeEmpty();
143 
144 	for (int32 index = 0; ; index++) {
145 		BColumn* column = fPoseView->ColumnAt(index);
146 		if (!column)
147 			break;
148 		fTitleList.AddItem(new BColumnTitle(this, column));
149 	}
150 	Invalidate();
151 }
152 
153 
154 void
155 BTitleView::AddTitle(BColumn* column, const BColumn* after)
156 {
157 	int32 count = fTitleList.CountItems();
158 	int32 index;
159 	if (after) {
160 		for (index = 0; index < count; index++) {
161 			BColumn* titleColumn = fTitleList.ItemAt(index)->Column();
162 
163 			if (after == titleColumn) {
164 				index++;
165 				break;
166 			}
167 		}
168 	} else
169 		index = count;
170 
171 	fTitleList.AddItem(new BColumnTitle(this, column), index);
172 	Invalidate();
173 }
174 
175 
176 void
177 BTitleView::RemoveTitle(BColumn* column)
178 {
179 	int32 count = fTitleList.CountItems();
180 	for (int32 index = 0; index < count; index++) {
181 		BColumnTitle* title = fTitleList.ItemAt(index);
182 		if (title->Column() == column) {
183 			fTitleList.RemoveItem(title);
184 			break;
185 		}
186 	}
187 
188 	Invalidate();
189 }
190 
191 
192 BSize
193 BTitleView::MinSize()
194 {
195 	return BSize(16, fPreferredHeight);
196 }
197 
198 
199 BSize
200 BTitleView::MaxSize()
201 {
202 	return BSize(B_SIZE_UNLIMITED, fPreferredHeight);
203 }
204 
205 
206 void
207 BTitleView::Draw(BRect rect)
208 {
209 	Draw(rect, false);
210 }
211 
212 
213 void
214 BTitleView::Draw(BRect /*updateRect*/, bool useOffscreen, bool updateOnly,
215 	const BColumnTitle* pressedColumn,
216 	void (*trackRectBlitter)(BView*, BRect), BRect passThru)
217 {
218 	BRect bounds(Bounds());
219 	BView* view;
220 
221 	if (useOffscreen) {
222 		ASSERT(sOffscreen);
223 		BRect frame(bounds);
224 		frame.right += frame.left;
225 			// ToDo: this is kind of messy way of avoiding being clipped
226 			// by the amount the title is scrolled to the left
227 		view = sOffscreen->BeginUsing(frame);
228 		view->SetOrigin(-bounds.left, 0);
229 		view->SetLowColor(LowColor());
230 		view->SetHighColor(HighColor());
231 		BFont font;
232 		GetFont(&font);
233 		view->SetFont(&font);
234 	} else
235 		view = this;
236 
237 	view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_2_TINT);
238 	view->StrokeLine(bounds.LeftBottom(), bounds.RightBottom());
239 	bounds.bottom--;
240 
241 	rgb_color baseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
242 	be_control_look->DrawButtonBackground(view, bounds, bounds, baseColor, 0,
243 		BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
244 
245 	int32 count = fTitleList.CountItems();
246 	float minx = bounds.right;
247 	float maxx = bounds.left;
248 	for (int32 index = 0; index < count; index++) {
249 		BColumnTitle* title = fTitleList.ItemAt(index);
250 		title->Draw(view, title == pressedColumn);
251 		BRect titleBounds(title->Bounds());
252 		if (titleBounds.left < minx)
253 			minx = titleBounds.left;
254 		if (titleBounds.right > maxx)
255 			maxx = titleBounds.right;
256 	}
257 
258 	bounds = Bounds();
259 	minx--;
260 	view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
261 	view->StrokeLine(BPoint(minx, bounds.top),
262 		BPoint(minx, bounds.bottom - 1));
263 
264 #if !(APP_SERVER_CLEARS_BACKGROUND)
265 	FillRect(BRect(bounds.left, bounds.top + 1, minx - 1, bounds.bottom - 1),
266 		B_SOLID_LOW);
267 	FillRect(BRect(maxx + 1, bounds.top + 1, bounds.right, bounds.bottom - 1),
268 		B_SOLID_LOW);
269 #endif
270 
271 	if (useOffscreen) {
272 		if (trackRectBlitter)
273 			(trackRectBlitter)(view, passThru);
274 
275 		view->Sync();
276 		DrawBitmap(sOffscreen->Bitmap());
277 		sOffscreen->DoneUsing();
278 	} else if (trackRectBlitter)
279 		(trackRectBlitter)(view, passThru);
280 }
281 
282 
283 void
284 BTitleView::MouseDown(BPoint where)
285 {
286 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
287 	if (window == NULL)
288 		return;
289 
290 	if (!window->IsActive()) {
291 		// window wasn't active, activate it and bail
292 		window->Activate();
293 		return;
294 	}
295 
296 	// finish any pending edits
297 	fPoseView->CommitActivePose();
298 
299 	BColumnTitle* title = FindColumnTitle(where);
300 	BColumnTitle* resizedTitle = InColumnResizeArea(where);
301 
302 	uint32 buttons;
303 	GetMouse(&where, &buttons);
304 
305 	// Check if the user clicked the secondary mouse button.
306 	// if so, display the attribute menu:
307 
308 	if (SecondaryMouseButtonDown(modifiers(), buttons)) {
309 		BPopUpMenu* menu = new BPopUpMenu("Attributes", false, false);
310 		menu->SetFont(be_plain_font);
311 		window->NewAttributesMenu(menu);
312 		window->AddMimeTypesToMenu(menu);
313 		window->MarkAttributesMenu(menu);
314 		menu->SetTargetForItems(window->PoseView());
315 		menu->Go(ConvertToScreen(where), true, false);
316 		return;
317 	}
318 
319 	bigtime_t doubleClickSpeed;
320 	get_click_speed(&doubleClickSpeed);
321 
322 	if (resizedTitle) {
323 		bool force = static_cast<bool>(buttons & B_TERTIARY_MOUSE_BUTTON);
324 		if (force || buttons & B_PRIMARY_MOUSE_BUTTON) {
325 			if (force || fPreviouslyClickedColumnTitle != 0) {
326 				if (force || system_time() - fPreviousLeftClickTime
327 						< doubleClickSpeed) {
328 					if (fPoseView->
329 							ResizeColumnToWidest(resizedTitle->Column())) {
330 						Invalidate();
331 						return;
332 					}
333 				}
334 			}
335 			fPreviousLeftClickTime = system_time();
336 			fPreviouslyClickedColumnTitle = resizedTitle;
337 		}
338 	} else if (!title)
339 		return;
340 
341 	SetMouseEventMask(B_POINTER_EVENTS,
342 		B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
343 
344 	// track the mouse
345 	if (resizedTitle) {
346 		fTrackingState = new ColumnResizeState(this, resizedTitle, where,
347 			system_time() + doubleClickSpeed);
348 	} else {
349 		fTrackingState = new ColumnDragState(this, title, where,
350 			system_time() + doubleClickSpeed);
351 	}
352 }
353 
354 
355 void
356 BTitleView::MouseUp(BPoint where)
357 {
358 	if (fTrackingState == NULL)
359 		return;
360 
361 	fTrackingState->MouseUp(where);
362 
363 	delete fTrackingState;
364 	fTrackingState = NULL;
365 }
366 
367 
368 void
369 BTitleView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
370 {
371 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
372 	if (window == NULL)
373 		return;
374 
375 	if (fTrackingState != NULL) {
376 		int32 buttons = 0;
377 		if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
378 			Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
379 		fTrackingState->MouseMoved(where, buttons);
380 		return;
381 	}
382 
383 	switch (code) {
384 		default:
385 			if (InColumnResizeArea(where) && window->IsActive())
386 				SetViewCursor(&fHorizontalResizeCursor);
387 			else
388 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
389 			break;
390 
391 		case B_EXITED_VIEW:
392 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
393 			break;
394 	}
395 
396 	_inherited::MouseMoved(where, code, dragMessage);
397 }
398 
399 
400 BColumnTitle*
401 BTitleView::InColumnResizeArea(BPoint where) const
402 {
403 	int32 count = fTitleList.CountItems();
404 	for (int32 index = 0; index < count; index++) {
405 		BColumnTitle* title = fTitleList.ItemAt(index);
406 		if (title->InColumnResizeArea(where))
407 			return title;
408 	}
409 
410 	return NULL;
411 }
412 
413 
414 BColumnTitle*
415 BTitleView::FindColumnTitle(BPoint where) const
416 {
417 	int32 count = fTitleList.CountItems();
418 	for (int32 index = 0; index < count; index++) {
419 		BColumnTitle* title = fTitleList.ItemAt(index);
420 		if (title->Bounds().Contains(where))
421 			return title;
422 	}
423 
424 	return NULL;
425 }
426 
427 
428 BColumnTitle*
429 BTitleView::FindColumnTitle(const BColumn* column) const
430 {
431 	int32 count = fTitleList.CountItems();
432 	for (int32 index = 0; index < count; index++) {
433 		BColumnTitle* title = fTitleList.ItemAt(index);
434 		if (title->Column() == column)
435 			return title;
436 	}
437 
438 	return NULL;
439 }
440 
441 
442 //	#pragma mark - BColumnTitle
443 
444 
445 BColumnTitle::BColumnTitle(BTitleView* view, BColumn* column)
446 	:
447 	fColumn(column),
448 	fParent(view)
449 {
450 }
451 
452 
453 bool
454 BColumnTitle::InColumnResizeArea(BPoint where) const
455 {
456 	BRect edge(Bounds());
457 	edge.left = edge.right - kEdgeSize;
458 	edge.right += kEdgeSize;
459 
460 	return edge.Contains(where);
461 }
462 
463 
464 BRect
465 BColumnTitle::Bounds() const
466 {
467 	BRect bounds(fColumn->Offset() - kTitleColumnLeftExtraMargin, 0, 0,
468 		fParent->Bounds().Height());
469 	bounds.right = bounds.left + fColumn->Width() + kTitleColumnExtraMargin;
470 
471 	return bounds;
472 }
473 
474 
475 void
476 BColumnTitle::Draw(BView* view, bool pressed)
477 {
478 	BRect bounds(Bounds());
479 
480 	font_height fontHeight;
481 	view->GetFontHeight(&fontHeight);
482 
483 	float baseline = floor(bounds.top + fontHeight.ascent
484 		+ (bounds.Height() + 1 - (fontHeight.ascent + fontHeight.descent)) / 2);
485 	BPoint titleLocation(0, baseline);
486 
487 	rgb_color baseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
488 
489 	if (pressed) {
490 		bounds.bottom--;
491 		BRect rect(bounds);
492 		rect.right--;
493 		baseColor = tint_color(baseColor, B_DARKEN_1_TINT);
494 
495 		be_control_look->DrawButtonBackground(view, rect, rect, baseColor, 0,
496 			BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
497 	}
498 
499 	BString titleString(fColumn->Title());
500 	view->TruncateString(&titleString, B_TRUNCATE_END,
501 		bounds.Width() - kTitleColumnExtraMargin);
502 	float resultingWidth = view->StringWidth(titleString.String());
503 
504 	switch (fColumn->Alignment()) {
505 		case B_ALIGN_LEFT:
506 		default:
507 			titleLocation.x = bounds.left + 1 + kTitleColumnLeftExtraMargin;
508 			break;
509 
510 		case B_ALIGN_CENTER:
511 			titleLocation.x = bounds.left + (bounds.Width() / 2)
512 				- (resultingWidth / 2);
513 			break;
514 
515 		case B_ALIGN_RIGHT:
516 			titleLocation.x = bounds.right - resultingWidth
517 				- kTitleColumnRightExtraMargin;
518 			break;
519 	}
520 
521 	view->SetHighUIColor(B_PANEL_TEXT_COLOR, pressed ? B_DARKEN_1_TINT : 1.0f);
522 	view->SetLowColor(baseColor);
523 	view->DrawString(titleString.String(), titleLocation);
524 
525 	// show sort columns
526 	bool secondary
527 		= (fColumn->AttrHash() == fParent->PoseView()->SecondarySort());
528 	if (secondary
529 		|| (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) {
530 
531 		BPoint center(titleLocation.x - 6,
532 			roundf((bounds.top + bounds.bottom) / 2.0));
533 		BPoint triangle[3];
534 		if (fParent->PoseView()->ReverseSort()) {
535 			triangle[0] = center + BPoint(-3.5, 1.5);
536 			triangle[1] = center + BPoint(3.5, 1.5);
537 			triangle[2] = center + BPoint(0.0, -2.0);
538 		} else {
539 			triangle[0] = center + BPoint(-3.5, -1.5);
540 			triangle[1] = center + BPoint(3.5, -1.5);
541 			triangle[2] = center + BPoint(0.0, 2.0);
542 		}
543 
544 		uint32 flags = view->Flags();
545 		view->SetFlags(flags | B_SUBPIXEL_PRECISE);
546 
547 		if (secondary) {
548 			view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.3);
549 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
550 		} else {
551 			view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.6);
552 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
553 		}
554 
555 		view->SetFlags(flags);
556 	}
557 
558 	view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
559 	view->StrokeLine(bounds.RightTop(), bounds.RightBottom());
560 }
561 
562 
563 //	#pragma mark - ColumnTrackState
564 
565 
566 ColumnTrackState::ColumnTrackState(BTitleView* view, BColumnTitle* title,
567 	BPoint where, bigtime_t pastClickTime)
568 	:
569 	fTitleView(view),
570 	fTitle(title),
571 	fFirstClickPoint(where),
572 	fPastClickTime(pastClickTime),
573 	fHasMoved(false)
574 {
575 }
576 
577 
578 void
579 ColumnTrackState::MouseUp(BPoint where)
580 {
581 	// if it is pressed shortly and not moved, it is a click
582 	// else it is a track
583 	if (system_time() <= fPastClickTime && !fHasMoved)
584 		Clicked(where);
585 	else
586 		Done(where);
587 }
588 
589 
590 void
591 ColumnTrackState::MouseMoved(BPoint where, uint32 buttons)
592 {
593 	if (!fHasMoved && system_time() < fPastClickTime) {
594 		BRect moveMargin(fFirstClickPoint, fFirstClickPoint);
595 		moveMargin.InsetBy(-1, -1);
596 
597 		if (moveMargin.Contains(where))
598 			return;
599 	}
600 
601 	Moved(where, buttons);
602 	fHasMoved = true;
603 }
604 
605 
606 //	#pragma mark - ColumnResizeState
607 
608 
609 ColumnResizeState::ColumnResizeState(BTitleView* view, BColumnTitle* title,
610 		BPoint where, bigtime_t pastClickTime)
611 	:
612 	ColumnTrackState(view, title, where, pastClickTime),
613 	fLastLineDrawPos(-1),
614 	fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width())
615 		- where.x)
616 {
617 	DrawLine();
618 }
619 
620 
621 bool
622 ColumnResizeState::ValueChanged(BPoint where)
623 {
624 	float newWidth = where.x + fInitialTrackOffset
625 		- fTitle->fColumn->Offset();
626 	if (newWidth < kMinColumnWidth)
627 		newWidth = kMinColumnWidth;
628 
629 	return newWidth != fTitle->fColumn->Width();
630 }
631 
632 
633 void
634 ColumnResizeState::Moved(BPoint where, uint32)
635 {
636 	float newWidth = where.x + fInitialTrackOffset
637 		- fTitle->fColumn->Offset();
638 	if (newWidth < kMinColumnWidth)
639 		newWidth = kMinColumnWidth;
640 
641 	BPoseView* poseView = fTitleView->PoseView();
642 
643 	//bool shrink = (newWidth < fTitle->fColumn->Width());
644 
645 	// resize the column
646 	poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos,
647 		_DrawLine, _UndrawLine);
648 
649 	BRect bounds(fTitleView->Bounds());
650 	bounds.left = fTitle->fColumn->Offset();
651 
652 	// force title redraw
653 	fTitleView->Draw(bounds, true, false);
654 }
655 
656 
657 void
658 ColumnResizeState::Done(BPoint /*where*/)
659 {
660 	UndrawLine();
661 }
662 
663 
664 void
665 ColumnResizeState::Clicked(BPoint /*where*/)
666 {
667 	UndrawLine();
668 }
669 
670 
671 void
672 ColumnResizeState::DrawLine()
673 {
674 	BPoseView* poseView = fTitleView->PoseView();
675 	ASSERT(!poseView->IsDesktopWindow());
676 
677 	BRect poseViewBounds(poseView->Bounds());
678 	// remember the line location
679 	poseViewBounds.left = fTitle->Bounds().right;
680 	fLastLineDrawPos = poseViewBounds.left;
681 
682 	// draw the line in the new location
683 	_DrawLine(poseView, poseViewBounds.LeftTop(),
684 		poseViewBounds.LeftBottom());
685 }
686 
687 
688 void
689 ColumnResizeState::UndrawLine()
690 {
691 	if (fLastLineDrawPos < 0)
692 		return;
693 
694 	BRect poseViewBounds(fTitleView->PoseView()->Bounds());
695 	poseViewBounds.left = fLastLineDrawPos;
696 
697 	_UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(),
698 		poseViewBounds.LeftBottom());
699 }
700 
701 
702 //	#pragma mark - ColumnDragState
703 
704 
705 ColumnDragState::ColumnDragState(BTitleView* view, BColumnTitle* columnTitle,
706 	BPoint where, bigtime_t pastClickTime)
707 	:
708 	ColumnTrackState(view, columnTitle, where, pastClickTime),
709 	fInitialMouseTrackOffset(where.x),
710 	fTrackingRemovedColumn(false)
711 {
712 	ASSERT(columnTitle);
713 	ASSERT(fTitle);
714 	ASSERT(fTitle->Column());
715 	DrawPressNoOutline();
716 }
717 
718 
719 // ToDo:
720 // Autoscroll when dragging column left/right
721 // fix dragging back a column before the first column (now adds as last)
722 // make column swaps/adds not invalidate/redraw columns to the left
723 void
724 ColumnDragState::Moved(BPoint where, uint32)
725 {
726 	// figure out where we are with the mouse
727 	BRect titleBounds(fTitleView->Bounds());
728 	bool overTitleView = titleBounds.Contains(where);
729 	BColumnTitle* overTitle = overTitleView
730 		? fTitleView->FindColumnTitle(where) : 0;
731 	BRect titleBoundsWithMargin(titleBounds);
732 	titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin);
733 	bool inMarginRect = overTitleView
734 		|| titleBoundsWithMargin.Contains(where);
735 
736 	bool drawOutline = false;
737 	bool undrawOutline = false;
738 
739 	if (fTrackingRemovedColumn) {
740 		if (overTitleView) {
741 			// tracked back with a removed title into the title bar, add it
742 			// back
743 			fTitleView->EndRectTracking();
744 			fColumnArchive.Seek(0, SEEK_SET);
745 			BColumn* column = BColumn::InstantiateFromStream(&fColumnArchive);
746 			ASSERT(column);
747 			const BColumn* after = NULL;
748 			if (overTitle)
749 				after = overTitle->Column();
750 
751 			fTitleView->PoseView()->AddColumn(column, after);
752 			fTrackingRemovedColumn = false;
753 			fTitle = fTitleView->FindColumnTitle(column);
754 			fInitialMouseTrackOffset += fTitle->Bounds().left;
755 			drawOutline = true;
756 		}
757 	} else {
758 		if (!inMarginRect) {
759 			// dragged a title out of the hysteresis margin around the
760 			// title bar - remove it and start dragging it as a dotted outline
761 
762 			BRect rect(fTitle->Bounds());
763 			rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5);
764 			fColumnArchive.Seek(0, SEEK_SET);
765 			fTitle->Column()->ArchiveToStream(&fColumnArchive);
766 			fInitialMouseTrackOffset -= fTitle->Bounds().left;
767 			if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(),
768 					false)) {
769 				fTitle = 0;
770 				fTitleView->BeginRectTracking(rect);
771 				fTrackingRemovedColumn = true;
772 				undrawOutline = true;
773 			}
774 		} else if (overTitle && overTitle != fTitle
775 					// over a different column
776 				&& (overTitle->Bounds().left >= fTitle->Bounds().right
777 						// over the one to the right
778 					|| where.x < overTitle->Bounds().left
779 							+ fTitle->Bounds().Width())) {
780 						// over the one to the left, far enough to not snap
781 						// right back
782 
783 			BColumn* column = fTitle->Column();
784 			fInitialMouseTrackOffset -= fTitle->Bounds().left;
785 			// swap the columns
786 			fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column());
787 			// re-grab the title object looking it up by the column
788 			fTitle = fTitleView->FindColumnTitle(column);
789 			// recalc initialMouseTrackOffset
790 			fInitialMouseTrackOffset += fTitle->Bounds().left;
791 			drawOutline = true;
792 		} else
793 			drawOutline = true;
794 	}
795 
796 	if (drawOutline)
797 		DrawOutline(where.x - fInitialMouseTrackOffset);
798 	else if (undrawOutline)
799 		UndrawOutline();
800 }
801 
802 
803 void
804 ColumnDragState::Done(BPoint /*where*/)
805 {
806 	if (fTrackingRemovedColumn)
807 		fTitleView->EndRectTracking();
808 	UndrawOutline();
809 }
810 
811 
812 void
813 ColumnDragState::Clicked(BPoint /*where*/)
814 {
815 	BPoseView* poseView = fTitleView->PoseView();
816 	uint32 hash = fTitle->Column()->AttrHash();
817 	uint32 primarySort = poseView->PrimarySort();
818 	uint32 secondarySort = poseView->SecondarySort();
819 	bool shift = (modifiers() & B_SHIFT_KEY) != 0;
820 
821 	// For now:
822 	// if we hit the primary sort field again
823 	// then if shift key was down, switch primary and secondary
824 	if (hash == primarySort) {
825 		if (shift && secondarySort) {
826 			poseView->SetPrimarySort(secondarySort);
827 			poseView->SetSecondarySort(primarySort);
828 		} else
829 			poseView->SetReverseSort(!poseView->ReverseSort());
830 	} else if (shift) {
831 		// hit secondary sort column with shift key, disable
832 		if (hash == secondarySort)
833 			poseView->SetSecondarySort(0);
834 		else
835 			poseView->SetSecondarySort(hash);
836 	} else {
837 		poseView->SetPrimarySort(hash);
838 		poseView->SetReverseSort(false);
839 	}
840 
841 	if (poseView->PrimarySort() == poseView->SecondarySort())
842 		poseView->SetSecondarySort(0);
843 
844 	UndrawOutline();
845 
846 	poseView->SortPoses();
847 	poseView->Invalidate();
848 }
849 
850 
851 bool
852 ColumnDragState::ValueChanged(BPoint)
853 {
854 	return true;
855 }
856 
857 
858 void
859 ColumnDragState::DrawPressNoOutline()
860 {
861 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle);
862 }
863 
864 
865 void
866 ColumnDragState::DrawOutline(float pos)
867 {
868 	BRect outline(fTitle->Bounds());
869 	outline.OffsetBy(pos, 0);
870 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline,
871 		outline);
872 }
873 
874 
875 void
876 ColumnDragState::UndrawOutline()
877 {
878 	fTitleView->Draw(fTitleView->Bounds(), true, false);
879 }
880 
881 
882 OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap;
883