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