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