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