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