xref: /haiku/src/kits/tracker/TitleView.cpp (revision d231c2a7a54fd2f3d003a5d2cb8717614fc17626)
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->SetLowColor(pressed ? sDarkTitleBackground : sTitleBackground);
522 	view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1.75));
523 	view->DrawString(titleString.String(), loc);
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(loc.x - 6, roundf((bounds.top + bounds.bottom) / 2.0));
532 		BPoint triangle[3];
533 		if (fParent->PoseView()->ReverseSort()) {
534 			triangle[0] = center + BPoint(-3.5, 1.5);
535 			triangle[1] = center + BPoint(3.5, 1.5);
536 			triangle[2] = center + BPoint(0.0, -2.0);
537 		} else {
538 			triangle[0] = center + BPoint(-3.5, -1.5);
539 			triangle[1] = center + BPoint(3.5, -1.5);
540 			triangle[2] = center + BPoint(0.0, 2.0);
541 		}
542 
543 		uint32 flags = view->Flags();
544 		view->SetFlags(flags | B_SUBPIXEL_PRECISE);
545 
546 		if (secondary) {
547 			view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
548 				1.3));
549 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
550 		} else {
551 			view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
552 				1.6));
553 			view->FillTriangle(triangle[0], triangle[1], triangle[2]);
554 		}
555 
556 		view->SetFlags(flags);
557 	}
558 
559 	view->SetHighColor(sLightShadowColor);
560 	view->StrokeLine(bounds.RightTop(), bounds.RightBottom());
561 }
562 
563 
564 //	#pragma mark - ColumnTrackState
565 
566 
567 ColumnTrackState::ColumnTrackState(BTitleView* view, BColumnTitle* title,
568 	BPoint where, bigtime_t pastClickTime)
569 	:
570 	fTitleView(view),
571 	fTitle(title),
572 	fFirstClickPoint(where),
573 	fPastClickTime(pastClickTime),
574 	fHasMoved(false)
575 {
576 }
577 
578 
579 void
580 ColumnTrackState::MouseUp(BPoint where)
581 {
582 	// if it is pressed shortly and not moved, it is a click
583 	// else it is a track
584 	if (system_time() <= fPastClickTime && !fHasMoved)
585 		Clicked(where);
586 	else
587 		Done(where);
588 }
589 
590 
591 void
592 ColumnTrackState::MouseMoved(BPoint where, uint32 buttons)
593 {
594 	if (!fHasMoved && system_time() < fPastClickTime) {
595 		BRect moveMargin(fFirstClickPoint, fFirstClickPoint);
596 		moveMargin.InsetBy(-1, -1);
597 
598 		if (moveMargin.Contains(where))
599 			return;
600 	}
601 
602 	Moved(where, buttons);
603 	fHasMoved = true;
604 }
605 
606 
607 //	#pragma mark - ColumnResizeState
608 
609 
610 ColumnResizeState::ColumnResizeState(BTitleView* view, BColumnTitle* title,
611 		BPoint where, bigtime_t pastClickTime)
612 	:
613 	ColumnTrackState(view, title, where, pastClickTime),
614 	fLastLineDrawPos(-1),
615 	fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width())
616 		- where.x)
617 {
618 	DrawLine();
619 }
620 
621 
622 bool
623 ColumnResizeState::ValueChanged(BPoint where)
624 {
625 	float newWidth = where.x + fInitialTrackOffset
626 		- fTitle->fColumn->Offset();
627 	if (newWidth < kMinColumnWidth)
628 		newWidth = kMinColumnWidth;
629 
630 	return newWidth != fTitle->fColumn->Width();
631 }
632 
633 
634 void
635 ColumnResizeState::Moved(BPoint where, uint32)
636 {
637 	float newWidth = where.x + fInitialTrackOffset
638 		- fTitle->fColumn->Offset();
639 	if (newWidth < kMinColumnWidth)
640 		newWidth = kMinColumnWidth;
641 
642 	BPoseView* poseView = fTitleView->PoseView();
643 
644 	//bool shrink = (newWidth < fTitle->fColumn->Width());
645 
646 	// resize the column
647 	poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos,
648 		_DrawLine, _UndrawLine);
649 
650 	BRect bounds(fTitleView->Bounds());
651 	bounds.left = fTitle->fColumn->Offset();
652 
653 	// force title redraw
654 	fTitleView->Draw(bounds, true, false);
655 }
656 
657 
658 void
659 ColumnResizeState::Done(BPoint /*where*/)
660 {
661 	UndrawLine();
662 }
663 
664 
665 void
666 ColumnResizeState::Clicked(BPoint /*where*/)
667 {
668 	UndrawLine();
669 }
670 
671 
672 void
673 ColumnResizeState::DrawLine()
674 {
675 	BPoseView* poseView = fTitleView->PoseView();
676 	ASSERT(!poseView->IsDesktopWindow());
677 
678 	BRect poseViewBounds(poseView->Bounds());
679 	// remember the line location
680 	poseViewBounds.left = fTitle->Bounds().right;
681 	fLastLineDrawPos = poseViewBounds.left;
682 
683 	// draw the line in the new location
684 	_DrawLine(poseView, poseViewBounds.LeftTop(),
685 		poseViewBounds.LeftBottom());
686 }
687 
688 
689 void
690 ColumnResizeState::UndrawLine()
691 {
692 	if (fLastLineDrawPos < 0)
693 		return;
694 
695 	BRect poseViewBounds(fTitleView->PoseView()->Bounds());
696 	poseViewBounds.left = fLastLineDrawPos;
697 
698 	_UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(),
699 		poseViewBounds.LeftBottom());
700 }
701 
702 
703 //	#pragma mark - ColumnDragState
704 
705 
706 ColumnDragState::ColumnDragState(BTitleView* view, BColumnTitle* columnTitle,
707 	BPoint where, bigtime_t pastClickTime)
708 	:
709 	ColumnTrackState(view, columnTitle, where, pastClickTime),
710 	fInitialMouseTrackOffset(where.x),
711 	fTrackingRemovedColumn(false)
712 {
713 	ASSERT(columnTitle);
714 	ASSERT(fTitle);
715 	ASSERT(fTitle->Column());
716 	DrawPressNoOutline();
717 }
718 
719 
720 // ToDo:
721 // Autoscroll when dragging column left/right
722 // fix dragging back a column before the first column (now adds as last)
723 // make column swaps/adds not invalidate/redraw columns to the left
724 void
725 ColumnDragState::Moved(BPoint where, uint32)
726 {
727 	// figure out where we are with the mouse
728 	BRect titleBounds(fTitleView->Bounds());
729 	bool overTitleView = titleBounds.Contains(where);
730 	BColumnTitle* overTitle = overTitleView
731 		? fTitleView->FindColumnTitle(where) : 0;
732 	BRect titleBoundsWithMargin(titleBounds);
733 	titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin);
734 	bool inMarginRect = overTitleView
735 		|| titleBoundsWithMargin.Contains(where);
736 
737 	bool drawOutline = false;
738 	bool undrawOutline = false;
739 
740 	if (fTrackingRemovedColumn) {
741 		if (overTitleView) {
742 			// tracked back with a removed title into the title bar, add it
743 			// back
744 			fTitleView->EndRectTracking();
745 			fColumnArchive.Seek(0, SEEK_SET);
746 			BColumn* column = BColumn::InstantiateFromStream(&fColumnArchive);
747 			ASSERT(column);
748 			const BColumn* after = NULL;
749 			if (overTitle)
750 				after = overTitle->Column();
751 
752 			fTitleView->PoseView()->AddColumn(column, after);
753 			fTrackingRemovedColumn = false;
754 			fTitle = fTitleView->FindColumnTitle(column);
755 			fInitialMouseTrackOffset += fTitle->Bounds().left;
756 			drawOutline = true;
757 		}
758 	} else {
759 		if (!inMarginRect) {
760 			// dragged a title out of the hysteresis margin around the
761 			// title bar - remove it and start dragging it as a dotted outline
762 
763 			BRect rect(fTitle->Bounds());
764 			rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5);
765 			fColumnArchive.Seek(0, SEEK_SET);
766 			fTitle->Column()->ArchiveToStream(&fColumnArchive);
767 			fInitialMouseTrackOffset -= fTitle->Bounds().left;
768 			if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(),
769 					false)) {
770 				fTitle = 0;
771 				fTitleView->BeginRectTracking(rect);
772 				fTrackingRemovedColumn = true;
773 				undrawOutline = true;
774 			}
775 		} else if (overTitle && overTitle != fTitle
776 					// over a different column
777 				&& (overTitle->Bounds().left >= fTitle->Bounds().right
778 						// over the one to the right
779 					|| where.x < overTitle->Bounds().left
780 							+ fTitle->Bounds().Width())) {
781 						// over the one to the left, far enough to not snap
782 						// right back
783 
784 			BColumn* column = fTitle->Column();
785 			fInitialMouseTrackOffset -= fTitle->Bounds().left;
786 			// swap the columns
787 			fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column());
788 			// re-grab the title object looking it up by the column
789 			fTitle = fTitleView->FindColumnTitle(column);
790 			// recalc initialMouseTrackOffset
791 			fInitialMouseTrackOffset += fTitle->Bounds().left;
792 			drawOutline = true;
793 		} else
794 			drawOutline = true;
795 	}
796 
797 	if (drawOutline)
798 		DrawOutline(where.x - fInitialMouseTrackOffset);
799 	else if (undrawOutline)
800 		UndrawOutline();
801 }
802 
803 
804 void
805 ColumnDragState::Done(BPoint /*where*/)
806 {
807 	if (fTrackingRemovedColumn)
808 		fTitleView->EndRectTracking();
809 	UndrawOutline();
810 }
811 
812 
813 void
814 ColumnDragState::Clicked(BPoint /*where*/)
815 {
816 	BPoseView* poseView = fTitleView->PoseView();
817 	uint32 hash = fTitle->Column()->AttrHash();
818 	uint32 primarySort = poseView->PrimarySort();
819 	uint32 secondarySort = poseView->SecondarySort();
820 	bool shift = (modifiers() & B_SHIFT_KEY) != 0;
821 
822 	// For now:
823 	// if we hit the primary sort field again
824 	// then if shift key was down, switch primary and secondary
825 	if (hash == primarySort) {
826 		if (shift && secondarySort) {
827 			poseView->SetPrimarySort(secondarySort);
828 			poseView->SetSecondarySort(primarySort);
829 		} else
830 			poseView->SetReverseSort(!poseView->ReverseSort());
831 	} else if (shift) {
832 		// hit secondary sort column with shift key, disable
833 		if (hash == secondarySort)
834 			poseView->SetSecondarySort(0);
835 		else
836 			poseView->SetSecondarySort(hash);
837 	} else {
838 		poseView->SetPrimarySort(hash);
839 		poseView->SetReverseSort(false);
840 	}
841 
842 	if (poseView->PrimarySort() == poseView->SecondarySort())
843 		poseView->SetSecondarySort(0);
844 
845 	UndrawOutline();
846 
847 	poseView->SortPoses();
848 	poseView->Invalidate();
849 }
850 
851 
852 bool
853 ColumnDragState::ValueChanged(BPoint)
854 {
855 	return true;
856 }
857 
858 
859 void
860 ColumnDragState::DrawPressNoOutline()
861 {
862 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle);
863 }
864 
865 
866 void
867 ColumnDragState::DrawOutline(float pos)
868 {
869 	BRect outline(fTitle->Bounds());
870 	outline.OffsetBy(pos, 0);
871 	fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline,
872 		outline);
873 }
874 
875 
876 void
877 ColumnDragState::UndrawOutline()
878 {
879 	fTitleView->Draw(fTitleView->Bounds(), true, false);
880 }
881 
882 
883 OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap;
884