xref: /haiku/src/kits/tracker/TitleView.cpp (revision d0ac609964842f8cdb6d54b3c539c6c15293e172)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 
36 //	ListView title drawing and mouse manipulation classes
37 
38 
39 #include "TitleView.h"
40 
41 #include <Alert.h>
42 #include <Application.h>
43 #include <ControlLook.h>
44 #include <Debug.h>
45 #include <PopUpMenu.h>
46 #include <Window.h>
47 
48 #include <stdio.h>
49 #include <string.h>
50 
51 #include "Commands.h"
52 #include "ContainerWindow.h"
53 #include "PoseView.h"
54 #include "Utilities.h"
55 
56 
57 #define APP_SERVER_CLEARS_BACKGROUND 1
58 
59 
60 static rgb_color sTitleBackground;
61 static rgb_color sDarkTitleBackground;
62 static rgb_color sShineColor;
63 static rgb_color sLightShadowColor;
64 static rgb_color sShadowColor;
65 static rgb_color sDarkShadowColor;
66 
67 
68 static void
69 _DrawLine(BPoseView* view, BPoint from, BPoint to)
70 {
71 	rgb_color highColor = view->HighColor();
72 	view->SetHighColor(tint_color(view->LowColor(), B_DARKEN_1_TINT));
73 	view->StrokeLine(from, to);
74 	view->SetHighColor(highColor);
75 }
76 
77 
78 static void
79 _UndrawLine(BPoseView* view, BPoint from, BPoint to)
80 {
81 	view->StrokeLine(from, to, B_SOLID_LOW);
82 }
83 
84 
85 static void
86 _DrawOutline(BView* view, BRect where)
87 {
88 	where.right++;
89 	where.bottom--;
90 	rgb_color highColor = view->HighColor();
91 	view->SetHighColor(ui_color(B_CONTROL_HIGHLIGHT_COLOR));
92 	view->StrokeRect(where);
93 	view->SetHighColor(highColor);
94 }
95 
96 
97 //	#pragma mark - BTitleView
98 
99 
100 BTitleView::BTitleView(BPoseView* view)
101 	:
102 	BView("BTitleView", B_WILL_DRAW),
103 	fPoseView(view),
104 	fTitleList(10, true),
105 	fHorizontalResizeCursor(B_CURSOR_ID_RESIZE_EAST_WEST),
106 	fPreviouslyClickedColumnTitle(0),
107 	fPreviousLeftClickTime(0),
108 	fTrackingState(NULL)
109 {
110 	sTitleBackground = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 0.88f);
111 		// 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(floorf(be_plain_font->Size() * 0.75f));
128 	SetFont(&font);
129 
130 	font_height height;
131 	GetFontHeight(&height);
132 	fPreferredHeight = ceilf(height.ascent + height.descent) + 2;
133 
134 	Reset();
135 }
136 
137 
138 BTitleView::~BTitleView()
139 {
140 	delete fTrackingState;
141 }
142 
143 
144 void
145 BTitleView::Reset()
146 {
147 	fTitleList.MakeEmpty();
148 
149 	for (int32 index = 0; ; index++) {
150 		BColumn* column = fPoseView->ColumnAt(index);
151 		if (!column)
152 			break;
153 		fTitleList.AddItem(new BColumnTitle(this, column));
154 	}
155 	Invalidate();
156 }
157 
158 
159 void
160 BTitleView::AddTitle(BColumn* column, const BColumn* after)
161 {
162 	int32 count = fTitleList.CountItems();
163 	int32 index;
164 	if (after) {
165 		for (index = 0; index < count; index++) {
166 			BColumn* titleColumn = fTitleList.ItemAt(index)->Column();
167 
168 			if (after == titleColumn) {
169 				index++;
170 				break;
171 			}
172 		}
173 	} else
174 		index = count;
175 
176 	fTitleList.AddItem(new BColumnTitle(this, column), index);
177 	Invalidate();
178 }
179 
180 
181 void
182 BTitleView::RemoveTitle(BColumn* column)
183 {
184 	int32 count = fTitleList.CountItems();
185 	for (int32 index = 0; index < count; index++) {
186 		BColumnTitle* title = fTitleList.ItemAt(index);
187 		if (title->Column() == column) {
188 			fTitleList.RemoveItem(title);
189 			break;
190 		}
191 	}
192 
193 	Invalidate();
194 }
195 
196 
197 BSize
198 BTitleView::MinSize()
199 {
200 	return BSize(16, fPreferredHeight);
201 }
202 
203 
204 BSize
205 BTitleView::MaxSize()
206 {
207 	return BSize(B_SIZE_UNLIMITED, fPreferredHeight);
208 }
209 
210 
211 void
212 BTitleView::Draw(BRect rect)
213 {
214 	Draw(rect, false);
215 }
216 
217 
218 void
219 BTitleView::Draw(BRect /*updateRect*/, bool useOffscreen, bool updateOnly,
220 	const BColumnTitle* pressedColumn,
221 	void (*trackRectBlitter)(BView*, BRect), BRect passThru)
222 {
223 	BRect bounds(Bounds());
224 	BView* view;
225 
226 	if (useOffscreen) {
227 		ASSERT(sOffscreen);
228 		BRect frame(bounds);
229 		frame.right += frame.left;
230 			// ToDo: this is kind of messy way of avoiding being clipped
231 			// by the amount the title is scrolled to the left
232 		view = sOffscreen->BeginUsing(frame);
233 		view->SetOrigin(-bounds.left, 0);
234 		view->SetLowColor(LowColor());
235 		view->SetHighColor(HighColor());
236 		BFont font;
237 		GetFont(&font);
238 		view->SetFont(&font);
239 	} else
240 		view = this;
241 
242 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
243 	view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
244 	view->StrokeLine(bounds.LeftBottom(), bounds.RightBottom());
245 	bounds.bottom--;
246 
247 	be_control_look->DrawButtonBackground(view, bounds, bounds, base, 0,
248 		BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
249 
250 	int32 count = fTitleList.CountItems();
251 	float minx = bounds.right;
252 	float maxx = bounds.left;
253 	for (int32 index = 0; index < count; index++) {
254 		BColumnTitle* title = fTitleList.ItemAt(index);
255 		title->Draw(view, title == pressedColumn);
256 		BRect titleBounds(title->Bounds());
257 		if (titleBounds.left < minx)
258 			minx = titleBounds.left;
259 		if (titleBounds.right > maxx)
260 			maxx = titleBounds.right;
261 	}
262 
263 	bounds = Bounds();
264 	minx--;
265 	view->SetHighColor(sLightShadowColor);
266 	view->StrokeLine(BPoint(minx, bounds.top),
267 		BPoint(minx, bounds.bottom - 1));
268 
269 #if !(APP_SERVER_CLEARS_BACKGROUND)
270 	FillRect(BRect(bounds.left, bounds.top + 1, minx - 1, bounds.bottom - 1),
271 		B_SOLID_LOW);
272 	FillRect(BRect(maxx + 1, bounds.top + 1, bounds.right, bounds.bottom - 1),
273 		B_SOLID_LOW);
274 #endif
275 
276 	if (useOffscreen) {
277 		if (trackRectBlitter)
278 			(trackRectBlitter)(view, passThru);
279 
280 		view->Sync();
281 		DrawBitmap(sOffscreen->Bitmap());
282 		sOffscreen->DoneUsing();
283 	} else if (trackRectBlitter)
284 		(trackRectBlitter)(view, passThru);
285 }
286 
287 
288 void
289 BTitleView::MouseDown(BPoint where)
290 {
291 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
292 	if (window == NULL)
293 		return;
294 
295 	if (!window->IsActive()) {
296 		// window wasn't active, activate it and bail
297 		window->Activate();
298 		return;
299 	}
300 
301 	// finish any pending edits
302 	fPoseView->CommitActivePose();
303 
304 	BColumnTitle* title = FindColumnTitle(where);
305 	BColumnTitle* resizedTitle = InColumnResizeArea(where);
306 
307 	uint32 buttons;
308 	GetMouse(&where, &buttons);
309 
310 	// Check if the user clicked the secondary mouse button.
311 	// if so, display the attribute menu:
312 
313 	if (SecondaryMouseButtonDown(modifiers(), buttons)) {
314 		BPopUpMenu* menu = new BPopUpMenu("Attributes", false, false);
315 		menu->SetFont(be_plain_font);
316 		window->NewAttributeMenu(menu);
317 		window->AddMimeTypesToMenu(menu);
318 		window->MarkAttributeMenu(menu);
319 		menu->SetTargetForItems(window->PoseView());
320 		menu->Go(ConvertToScreen(where), true, false);
321 		return;
322 	}
323 
324 	bigtime_t doubleClickSpeed;
325 	get_click_speed(&doubleClickSpeed);
326 
327 	if (resizedTitle) {
328 		bool force = static_cast<bool>(buttons & B_TERTIARY_MOUSE_BUTTON);
329 		if (force || buttons & B_PRIMARY_MOUSE_BUTTON) {
330 			if (force || fPreviouslyClickedColumnTitle != 0) {
331 				if (force || system_time() - fPreviousLeftClickTime
332 						< doubleClickSpeed) {
333 					if (fPoseView->
334 							ResizeColumnToWidest(resizedTitle->Column())) {
335 						Invalidate();
336 						return;
337 					}
338 				}
339 			}
340 			fPreviousLeftClickTime = system_time();
341 			fPreviouslyClickedColumnTitle = resizedTitle;
342 		}
343 	} else if (!title)
344 		return;
345 
346 	SetMouseEventMask(B_POINTER_EVENTS,
347 		B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
348 
349 	// track the mouse
350 	if (resizedTitle) {
351 		fTrackingState = new ColumnResizeState(this, resizedTitle, where,
352 			system_time() + doubleClickSpeed);
353 	} else {
354 		fTrackingState = new ColumnDragState(this, title, where,
355 			system_time() + doubleClickSpeed);
356 	}
357 }
358 
359 
360 void
361 BTitleView::MouseUp(BPoint where)
362 {
363 	if (fTrackingState == NULL)
364 		return;
365 
366 	fTrackingState->MouseUp(where);
367 
368 	delete fTrackingState;
369 	fTrackingState = NULL;
370 }
371 
372 
373 void
374 BTitleView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
375 {
376 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
377 	if (window == NULL)
378 		return;
379 
380 	if (fTrackingState != NULL) {
381 		int32 buttons = 0;
382 		if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
383 			Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
384 		fTrackingState->MouseMoved(where, buttons);
385 		return;
386 	}
387 
388 	switch (code) {
389 		default:
390 			if (InColumnResizeArea(where) && window->IsActive())
391 				SetViewCursor(&fHorizontalResizeCursor);
392 			else
393 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
394 			break;
395 
396 		case B_EXITED_VIEW:
397 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
398 			break;
399 	}
400 
401 	_inherited::MouseMoved(where, code, dragMessage);
402 }
403 
404 
405 BColumnTitle*
406 BTitleView::InColumnResizeArea(BPoint where) const
407 {
408 	int32 count = fTitleList.CountItems();
409 	for (int32 index = 0; index < count; index++) {
410 		BColumnTitle* title = fTitleList.ItemAt(index);
411 		if (title->InColumnResizeArea(where))
412 			return title;
413 	}
414 
415 	return NULL;
416 }
417 
418 
419 BColumnTitle*
420 BTitleView::FindColumnTitle(BPoint where) const
421 {
422 	int32 count = fTitleList.CountItems();
423 	for (int32 index = 0; index < count; index++) {
424 		BColumnTitle* title = fTitleList.ItemAt(index);
425 		if (title->Bounds().Contains(where))
426 			return title;
427 	}
428 
429 	return NULL;
430 }
431 
432 
433 BColumnTitle*
434 BTitleView::FindColumnTitle(const BColumn* column) const
435 {
436 	int32 count = fTitleList.CountItems();
437 	for (int32 index = 0; index < count; index++) {
438 		BColumnTitle* title = fTitleList.ItemAt(index);
439 		if (title->Column() == column)
440 			return title;
441 	}
442 
443 	return NULL;
444 }
445 
446 
447 //	#pragma mark - BColumnTitle
448 
449 
450 BColumnTitle::BColumnTitle(BTitleView* view, BColumn* column)
451 	:
452 	fColumn(column),
453 	fParent(view)
454 {
455 }
456 
457 
458 bool
459 BColumnTitle::InColumnResizeArea(BPoint where) const
460 {
461 	BRect edge(Bounds());
462 	edge.left = edge.right - kEdgeSize;
463 	edge.right += kEdgeSize;
464 
465 	return edge.Contains(where);
466 }
467 
468 
469 BRect
470 BColumnTitle::Bounds() const
471 {
472 	BRect bounds(fColumn->Offset() - kTitleColumnLeftExtraMargin, 0, 0,
473 		fParent->Bounds().Height());
474 	bounds.right = bounds.left + fColumn->Width() + kTitleColumnExtraMargin;
475 
476 	return bounds;
477 }
478 
479 
480 void
481 BColumnTitle::Draw(BView* view, bool pressed)
482 {
483 	BRect bounds(Bounds());
484 
485 	font_height height;
486 	view->GetFontHeight(&height);
487 	BPoint loc(0, bounds.top + ceilf(height.ascent) + 2);
488 
489 	if (pressed) {
490 		bounds.bottom--;
491 		BRect rect(bounds);
492 		rect.right--;
493 		rgb_color base = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
494 			B_DARKEN_1_TINT);
495 
496 		be_control_look->DrawButtonBackground(view, rect, rect, base, 0,
497 			BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
498 	}
499 
500 	BString titleString(fColumn->Title());
501 	view->TruncateString(&titleString, B_TRUNCATE_END,
502 		bounds.Width() - kTitleColumnExtraMargin);
503 	float resultingWidth = view->StringWidth(titleString.String());
504 
505 	switch (fColumn->Alignment()) {
506 		case B_ALIGN_LEFT:
507 		default:
508 			loc.x = bounds.left + 1 + kTitleColumnLeftExtraMargin;
509 			break;
510 
511 		case B_ALIGN_CENTER:
512 			loc.x = bounds.left + (bounds.Width() / 2) - (resultingWidth / 2);
513 			break;
514 
515 		case B_ALIGN_RIGHT:
516 			loc.x = bounds.right - resultingWidth
517 				- kTitleColumnRightExtraMargin;
518 			break;
519 	}
520 
521 	view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1.75));
522 	view->DrawString(titleString.String(), loc);
523 
524 	// show sort columns
525 	bool secondary
526 		= (fColumn->AttrHash() == fParent->PoseView()->SecondarySort());
527 	if (secondary
528 		|| (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) {
529 
530 		BPoint center(loc.x - 6, roundf((bounds.top + bounds.bottom) / 2.0));
531 		BPoint triangle[3];
532 		if (fParent->PoseView()->ReverseSort()) {
533 			triangle[0] = center + BPoint(-3.5, 1.5);
534 			triangle[1] = center + BPoint(3.5, 1.5);
535 			triangle[2] = center + BPoint(0.0, -2.0);
536 		} else {
537 			triangle[0] = center + BPoint(-3.5, -1.5);
538 			triangle[1] = center + BPoint(3.5, -1.5);
539 			triangle[2] = center + BPoint(0.0, 2.0);
540 		}
541 
542 		uint32 flags = view->Flags();
543 		view->SetFlags(flags | B_SUBPIXEL_PRECISE);
544 
545 		if (secondary) {
546 			view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
547 				1.3));
548 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
549 		} else {
550 			view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
551 				1.6));
552 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
553 		}
554 
555 		view->SetFlags(flags);
556 	}
557 
558 	view->SetHighColor(sLightShadowColor);
559 	view->StrokeLine(bounds.RightTop(), bounds.RightBottom());
560 }
561 
562 
563 //	#pragma mark - ColumnTrackState
564 
565 
566 ColumnTrackState::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 
578 void
579 ColumnTrackState::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 
590 void
591 ColumnTrackState::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 
609 ColumnResizeState::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 
621 bool
622 ColumnResizeState::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 
633 void
634 ColumnResizeState::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 
657 void
658 ColumnResizeState::Done(BPoint /*where*/)
659 {
660 	UndrawLine();
661 }
662 
663 
664 void
665 ColumnResizeState::Clicked(BPoint /*where*/)
666 {
667 	UndrawLine();
668 }
669 
670 
671 void
672 ColumnResizeState::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 
688 void
689 ColumnResizeState::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 
705 ColumnDragState::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
723 void
724 ColumnDragState::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 
803 void
804 ColumnDragState::Done(BPoint /*where*/)
805 {
806 	if (fTrackingRemovedColumn)
807 		fTitleView->EndRectTracking();
808 	UndrawOutline();
809 }
810 
811 
812 void
813 ColumnDragState::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 
851 bool
852 ColumnDragState::ValueChanged(BPoint)
853 {
854 	return true;
855 }
856 
857 
858 void
859 ColumnDragState::DrawPressNoOutline()
860 {
861 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle);
862 }
863 
864 
865 void
866 ColumnDragState::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 
875 void
876 ColumnDragState::UndrawOutline()
877 {
878 	fTitleView->Draw(fTitleView->Bounds(), true, false);
879 }
880 
881 
882 OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap;
883