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