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