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