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