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