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