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