xref: /haiku/src/kits/tracker/TitleView.cpp (revision 9a6a20d4689307142a7ed26a1437ba47e244e73f)
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 		window->NewAttributesMenu(menu);
311 		window->AddMimeTypesToMenu(menu);
312 		window->MarkAttributesMenu(menu);
313 		menu->SetTargetForItems(window->PoseView());
314 		menu->Go(ConvertToScreen(where), true, false);
315 		return;
316 	}
317 
318 	bigtime_t doubleClickSpeed;
319 	get_click_speed(&doubleClickSpeed);
320 
321 	if (resizedTitle) {
322 		bool force = static_cast<bool>(buttons & B_TERTIARY_MOUSE_BUTTON);
323 		if (force || buttons & B_PRIMARY_MOUSE_BUTTON) {
324 			if (force || fPreviouslyClickedColumnTitle != 0) {
325 				if (force || system_time() - fPreviousLeftClickTime
326 						< doubleClickSpeed) {
327 					if (fPoseView->
328 							ResizeColumnToWidest(resizedTitle->Column())) {
329 						Invalidate();
330 						return;
331 					}
332 				}
333 			}
334 			fPreviousLeftClickTime = system_time();
335 			fPreviouslyClickedColumnTitle = resizedTitle;
336 		}
337 	} else if (!title)
338 		return;
339 
340 	SetMouseEventMask(B_POINTER_EVENTS,
341 		B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
342 
343 	// track the mouse
344 	if (resizedTitle) {
345 		fTrackingState = new ColumnResizeState(this, resizedTitle, where,
346 			system_time() + doubleClickSpeed);
347 	} else {
348 		fTrackingState = new ColumnDragState(this, title, where,
349 			system_time() + doubleClickSpeed);
350 	}
351 }
352 
353 
354 void
355 BTitleView::MouseUp(BPoint where)
356 {
357 	if (fTrackingState == NULL)
358 		return;
359 
360 	fTrackingState->MouseUp(where);
361 
362 	delete fTrackingState;
363 	fTrackingState = NULL;
364 }
365 
366 
367 void
368 BTitleView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
369 {
370 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
371 	if (window == NULL)
372 		return;
373 
374 	if (fTrackingState != NULL) {
375 		int32 buttons = 0;
376 		if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
377 			Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
378 		fTrackingState->MouseMoved(where, buttons);
379 		return;
380 	}
381 
382 	switch (code) {
383 		default:
384 			if (InColumnResizeArea(where) && window->IsActive())
385 				SetViewCursor(&fHorizontalResizeCursor);
386 			else
387 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
388 			break;
389 
390 		case B_EXITED_VIEW:
391 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
392 			break;
393 	}
394 
395 	_inherited::MouseMoved(where, code, dragMessage);
396 }
397 
398 
399 BColumnTitle*
400 BTitleView::InColumnResizeArea(BPoint where) const
401 {
402 	int32 count = fTitleList.CountItems();
403 	for (int32 index = 0; index < count; index++) {
404 		BColumnTitle* title = fTitleList.ItemAt(index);
405 		if (title->InColumnResizeArea(where))
406 			return title;
407 	}
408 
409 	return NULL;
410 }
411 
412 
413 BColumnTitle*
414 BTitleView::FindColumnTitle(BPoint where) const
415 {
416 	int32 count = fTitleList.CountItems();
417 	for (int32 index = 0; index < count; index++) {
418 		BColumnTitle* title = fTitleList.ItemAt(index);
419 		if (title->Bounds().Contains(where))
420 			return title;
421 	}
422 
423 	return NULL;
424 }
425 
426 
427 BColumnTitle*
428 BTitleView::FindColumnTitle(const BColumn* column) const
429 {
430 	int32 count = fTitleList.CountItems();
431 	for (int32 index = 0; index < count; index++) {
432 		BColumnTitle* title = fTitleList.ItemAt(index);
433 		if (title->Column() == column)
434 			return title;
435 	}
436 
437 	return NULL;
438 }
439 
440 
441 //	#pragma mark - BColumnTitle
442 
443 
444 BColumnTitle::BColumnTitle(BTitleView* view, BColumn* column)
445 	:
446 	fColumn(column),
447 	fParent(view)
448 {
449 }
450 
451 
452 bool
453 BColumnTitle::InColumnResizeArea(BPoint where) const
454 {
455 	BRect edge(Bounds());
456 	edge.left = edge.right - kEdgeSize;
457 	edge.right += kEdgeSize;
458 
459 	return edge.Contains(where);
460 }
461 
462 
463 BRect
464 BColumnTitle::Bounds() const
465 {
466 	BRect bounds(fColumn->Offset() - kTitleColumnLeftExtraMargin, 0, 0,
467 		fParent->Bounds().Height());
468 	bounds.right = bounds.left + fColumn->Width() + kTitleColumnExtraMargin;
469 
470 	return bounds;
471 }
472 
473 
474 void
475 BColumnTitle::Draw(BView* view, bool pressed)
476 {
477 	BRect bounds(Bounds());
478 
479 	font_height fontHeight;
480 	view->GetFontHeight(&fontHeight);
481 
482 	float baseline = floor(bounds.top + fontHeight.ascent
483 		+ (bounds.Height() + 1 - (fontHeight.ascent + fontHeight.descent)) / 2);
484 	BPoint titleLocation(0, baseline);
485 
486 	rgb_color baseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
487 
488 	if (pressed) {
489 		bounds.bottom--;
490 		BRect rect(bounds);
491 		rect.right--;
492 		baseColor = tint_color(baseColor, B_DARKEN_1_TINT);
493 
494 		be_control_look->DrawButtonBackground(view, rect, rect, baseColor, 0,
495 			BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
496 	}
497 
498 	BString titleString(fColumn->Title());
499 	view->TruncateString(&titleString, B_TRUNCATE_END,
500 		bounds.Width() - kTitleColumnExtraMargin);
501 	float resultingWidth = view->StringWidth(titleString.String());
502 
503 	switch (fColumn->Alignment()) {
504 		case B_ALIGN_LEFT:
505 		default:
506 			titleLocation.x = bounds.left + 1 + kTitleColumnLeftExtraMargin;
507 			break;
508 
509 		case B_ALIGN_CENTER:
510 			titleLocation.x = bounds.left + (bounds.Width() / 2)
511 				- (resultingWidth / 2);
512 			break;
513 
514 		case B_ALIGN_RIGHT:
515 			titleLocation.x = bounds.right - resultingWidth
516 				- kTitleColumnRightExtraMargin;
517 			break;
518 	}
519 
520 	view->SetHighUIColor(B_PANEL_TEXT_COLOR, pressed ? B_DARKEN_1_TINT : 1.0f);
521 	view->SetLowColor(baseColor);
522 	view->DrawString(titleString.String(), titleLocation);
523 
524 	// show sort columns
525 	bool secondary
526 		= (fColumn->AttrHash() == fParent->PoseView()->SecondarySort());
527 	if (secondary
528 		|| (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) {
529 
530 		BPoint center(titleLocation.x - 6,
531 			roundf((bounds.top + bounds.bottom) / 2.0));
532 		BPoint triangle[3];
533 		if (fParent->PoseView()->ReverseSort()) {
534 			triangle[0] = center + BPoint(-3.5, 1.5);
535 			triangle[1] = center + BPoint(3.5, 1.5);
536 			triangle[2] = center + BPoint(0.0, -2.0);
537 		} else {
538 			triangle[0] = center + BPoint(-3.5, -1.5);
539 			triangle[1] = center + BPoint(3.5, -1.5);
540 			triangle[2] = center + BPoint(0.0, 2.0);
541 		}
542 
543 		uint32 flags = view->Flags();
544 		view->SetFlags(flags | B_SUBPIXEL_PRECISE);
545 
546 		if (secondary) {
547 			view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.3);
548 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
549 		} else {
550 			view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.6);
551 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
552 		}
553 
554 		view->SetFlags(flags);
555 	}
556 
557 	view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
558 	view->StrokeLine(bounds.RightTop(), bounds.RightBottom());
559 }
560 
561 
562 //	#pragma mark - ColumnTrackState
563 
564 
565 ColumnTrackState::ColumnTrackState(BTitleView* view, BColumnTitle* title,
566 	BPoint where, bigtime_t pastClickTime)
567 	:
568 	fTitleView(view),
569 	fTitle(title),
570 	fFirstClickPoint(where),
571 	fPastClickTime(pastClickTime),
572 	fHasMoved(false)
573 {
574 }
575 
576 
577 void
578 ColumnTrackState::MouseUp(BPoint where)
579 {
580 	// if it is pressed shortly and not moved, it is a click
581 	// else it is a track
582 	if (system_time() <= fPastClickTime && !fHasMoved)
583 		Clicked(where);
584 	else
585 		Done(where);
586 }
587 
588 
589 void
590 ColumnTrackState::MouseMoved(BPoint where, uint32 buttons)
591 {
592 	if (!fHasMoved && system_time() < fPastClickTime) {
593 		BRect moveMargin(fFirstClickPoint, fFirstClickPoint);
594 		moveMargin.InsetBy(-1, -1);
595 
596 		if (moveMargin.Contains(where))
597 			return;
598 	}
599 
600 	Moved(where, buttons);
601 	fHasMoved = true;
602 }
603 
604 
605 //	#pragma mark - ColumnResizeState
606 
607 
608 ColumnResizeState::ColumnResizeState(BTitleView* view, BColumnTitle* title,
609 		BPoint where, bigtime_t pastClickTime)
610 	:
611 	ColumnTrackState(view, title, where, pastClickTime),
612 	fLastLineDrawPos(-1),
613 	fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width())
614 		- where.x)
615 {
616 	DrawLine();
617 }
618 
619 
620 bool
621 ColumnResizeState::ValueChanged(BPoint where)
622 {
623 	float newWidth = where.x + fInitialTrackOffset
624 		- fTitle->fColumn->Offset();
625 	if (newWidth < kMinColumnWidth)
626 		newWidth = kMinColumnWidth;
627 
628 	return newWidth != fTitle->fColumn->Width();
629 }
630 
631 
632 void
633 ColumnResizeState::Moved(BPoint where, uint32)
634 {
635 	float newWidth = where.x + fInitialTrackOffset
636 		- fTitle->fColumn->Offset();
637 	if (newWidth < kMinColumnWidth)
638 		newWidth = kMinColumnWidth;
639 
640 	BPoseView* poseView = fTitleView->PoseView();
641 
642 	//bool shrink = (newWidth < fTitle->fColumn->Width());
643 
644 	// resize the column
645 	poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos,
646 		_DrawLine, _UndrawLine);
647 
648 	BRect bounds(fTitleView->Bounds());
649 	bounds.left = fTitle->fColumn->Offset();
650 
651 	// force title redraw
652 	fTitleView->Draw(bounds, true, false);
653 }
654 
655 
656 void
657 ColumnResizeState::Done(BPoint /*where*/)
658 {
659 	UndrawLine();
660 }
661 
662 
663 void
664 ColumnResizeState::Clicked(BPoint /*where*/)
665 {
666 	UndrawLine();
667 }
668 
669 
670 void
671 ColumnResizeState::DrawLine()
672 {
673 	BPoseView* poseView = fTitleView->PoseView();
674 	ASSERT(!poseView->IsDesktopWindow());
675 
676 	BRect poseViewBounds(poseView->Bounds());
677 	// remember the line location
678 	poseViewBounds.left = fTitle->Bounds().right;
679 	fLastLineDrawPos = poseViewBounds.left;
680 
681 	// draw the line in the new location
682 	_DrawLine(poseView, poseViewBounds.LeftTop(),
683 		poseViewBounds.LeftBottom());
684 }
685 
686 
687 void
688 ColumnResizeState::UndrawLine()
689 {
690 	if (fLastLineDrawPos < 0)
691 		return;
692 
693 	BRect poseViewBounds(fTitleView->PoseView()->Bounds());
694 	poseViewBounds.left = fLastLineDrawPos;
695 
696 	_UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(),
697 		poseViewBounds.LeftBottom());
698 }
699 
700 
701 //	#pragma mark - ColumnDragState
702 
703 
704 ColumnDragState::ColumnDragState(BTitleView* view, BColumnTitle* columnTitle,
705 	BPoint where, bigtime_t pastClickTime)
706 	:
707 	ColumnTrackState(view, columnTitle, where, pastClickTime),
708 	fInitialMouseTrackOffset(where.x),
709 	fTrackingRemovedColumn(false)
710 {
711 	ASSERT(columnTitle);
712 	ASSERT(fTitle);
713 	ASSERT(fTitle->Column());
714 	DrawPressNoOutline();
715 }
716 
717 
718 // ToDo:
719 // Autoscroll when dragging column left/right
720 // fix dragging back a column before the first column (now adds as last)
721 // make column swaps/adds not invalidate/redraw columns to the left
722 void
723 ColumnDragState::Moved(BPoint where, uint32)
724 {
725 	// figure out where we are with the mouse
726 	BRect titleBounds(fTitleView->Bounds());
727 	bool overTitleView = titleBounds.Contains(where);
728 	BColumnTitle* overTitle = overTitleView
729 		? fTitleView->FindColumnTitle(where) : 0;
730 	BRect titleBoundsWithMargin(titleBounds);
731 	titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin);
732 	bool inMarginRect = overTitleView
733 		|| titleBoundsWithMargin.Contains(where);
734 
735 	bool drawOutline = false;
736 	bool undrawOutline = false;
737 
738 	if (fTrackingRemovedColumn) {
739 		if (overTitleView) {
740 			// tracked back with a removed title into the title bar, add it
741 			// back
742 			fTitleView->EndRectTracking();
743 			fColumnArchive.Seek(0, SEEK_SET);
744 			BColumn* column = BColumn::InstantiateFromStream(&fColumnArchive);
745 			ASSERT(column);
746 			const BColumn* after = NULL;
747 			if (overTitle)
748 				after = overTitle->Column();
749 
750 			fTitleView->PoseView()->AddColumn(column, after);
751 			fTrackingRemovedColumn = false;
752 			fTitle = fTitleView->FindColumnTitle(column);
753 			fInitialMouseTrackOffset += fTitle->Bounds().left;
754 			drawOutline = true;
755 		}
756 	} else {
757 		if (!inMarginRect) {
758 			// dragged a title out of the hysteresis margin around the
759 			// title bar - remove it and start dragging it as a dotted outline
760 
761 			BRect rect(fTitle->Bounds());
762 			rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5);
763 			fColumnArchive.Seek(0, SEEK_SET);
764 			fTitle->Column()->ArchiveToStream(&fColumnArchive);
765 			fInitialMouseTrackOffset -= fTitle->Bounds().left;
766 			if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(),
767 					false)) {
768 				fTitle = 0;
769 				fTitleView->BeginRectTracking(rect);
770 				fTrackingRemovedColumn = true;
771 				undrawOutline = true;
772 			}
773 		} else if (overTitle && overTitle != fTitle
774 					// over a different column
775 				&& (overTitle->Bounds().left >= fTitle->Bounds().right
776 						// over the one to the right
777 					|| where.x < overTitle->Bounds().left
778 							+ fTitle->Bounds().Width())) {
779 						// over the one to the left, far enough to not snap
780 						// right back
781 
782 			BColumn* column = fTitle->Column();
783 			fInitialMouseTrackOffset -= fTitle->Bounds().left;
784 			// swap the columns
785 			fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column());
786 			// re-grab the title object looking it up by the column
787 			fTitle = fTitleView->FindColumnTitle(column);
788 			// recalc initialMouseTrackOffset
789 			fInitialMouseTrackOffset += fTitle->Bounds().left;
790 			drawOutline = true;
791 		} else
792 			drawOutline = true;
793 	}
794 
795 	if (drawOutline)
796 		DrawOutline(where.x - fInitialMouseTrackOffset);
797 	else if (undrawOutline)
798 		UndrawOutline();
799 }
800 
801 
802 void
803 ColumnDragState::Done(BPoint /*where*/)
804 {
805 	if (fTrackingRemovedColumn)
806 		fTitleView->EndRectTracking();
807 	UndrawOutline();
808 }
809 
810 
811 void
812 ColumnDragState::Clicked(BPoint /*where*/)
813 {
814 	BPoseView* poseView = fTitleView->PoseView();
815 	uint32 hash = fTitle->Column()->AttrHash();
816 	uint32 primarySort = poseView->PrimarySort();
817 	uint32 secondarySort = poseView->SecondarySort();
818 	bool shift = (modifiers() & B_SHIFT_KEY) != 0;
819 
820 	// For now:
821 	// if we hit the primary sort field again
822 	// then if shift key was down, switch primary and secondary
823 	if (hash == primarySort) {
824 		if (shift && secondarySort) {
825 			poseView->SetPrimarySort(secondarySort);
826 			poseView->SetSecondarySort(primarySort);
827 		} else
828 			poseView->SetReverseSort(!poseView->ReverseSort());
829 	} else if (shift) {
830 		// hit secondary sort column with shift key, disable
831 		if (hash == secondarySort)
832 			poseView->SetSecondarySort(0);
833 		else
834 			poseView->SetSecondarySort(hash);
835 	} else {
836 		poseView->SetPrimarySort(hash);
837 		poseView->SetReverseSort(false);
838 	}
839 
840 	if (poseView->PrimarySort() == poseView->SecondarySort())
841 		poseView->SetSecondarySort(0);
842 
843 	UndrawOutline();
844 
845 	poseView->SortPoses();
846 	poseView->Invalidate();
847 }
848 
849 
850 bool
851 ColumnDragState::ValueChanged(BPoint)
852 {
853 	return true;
854 }
855 
856 
857 void
858 ColumnDragState::DrawPressNoOutline()
859 {
860 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle);
861 }
862 
863 
864 void
865 ColumnDragState::DrawOutline(float pos)
866 {
867 	BRect outline(fTitle->Bounds());
868 	outline.OffsetBy(pos, 0);
869 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline,
870 		outline);
871 }
872 
873 
874 void
875 ColumnDragState::UndrawOutline()
876 {
877 	fTitleView->Draw(fTitleView->Bounds(), true, false);
878 }
879 
880 
881 OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap;
882