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