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