xref: /haiku/src/kits/interface/ColumnListView.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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 /
37 /	File:			ColumnListView.cpp
38 /
39 /   Description:    Experimental multi-column list view.
40 /
41 /	Copyright 2000+, Be Incorporated, All Rights Reserved
42 /					 By Jeff Bush
43 /
44 *******************************************************************************/
45 
46 #include "ColumnListView.h"
47 
48 #include <typeinfo>
49 
50 #include <algorithm>
51 #include <stdio.h>
52 #include <stdlib.h>
53 
54 #include <Application.h>
55 #include <Bitmap.h>
56 #include <ControlLook.h>
57 #include <Cursor.h>
58 #include <Debug.h>
59 #include <GraphicsDefs.h>
60 #include <LayoutUtils.h>
61 #include <MenuItem.h>
62 #include <PopUpMenu.h>
63 #include <Region.h>
64 #include <ScrollBar.h>
65 #include <String.h>
66 #include <SupportDefs.h>
67 #include <Window.h>
68 
69 #include <ObjectListPrivate.h>
70 
71 #include "ObjectList.h"
72 
73 
74 #define DOUBLE_BUFFERED_COLUMN_RESIZE 1
75 #define SMART_REDRAW 1
76 #define DRAG_TITLE_OUTLINE 1
77 #define CONSTRAIN_CLIPPING_REGION 1
78 #define LOWER_SCROLLBAR 0
79 
80 
81 namespace BPrivate {
82 
83 static const unsigned char kDownSortArrow8x8[] = {
84 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
85 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
86 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
87 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
88 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
89 	0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
90 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
91 	0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff
92 };
93 
94 static const unsigned char kUpSortArrow8x8[] = {
95 	0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
96 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
97 	0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
98 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
99 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
100 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
101 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
102 	0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff
103 };
104 
105 static const unsigned char kDownSortArrow8x8Invert[] = {
106 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
107 	0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
108 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
109 	0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
110 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
111 	0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
112 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
113 	0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff
114 };
115 
116 static const unsigned char kUpSortArrow8x8Invert[] = {
117 	0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff,
118 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
119 	0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
120 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
121 	0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
122 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
123 	0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
124 	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
125 };
126 
127 static const float kTintedLineTint = 1.04;
128 
129 static const float kMinTitleHeight = 16.0;
130 static const float kMinRowHeight = 16.0;
131 static const float kTitleSpacing = 1.4;
132 static const float kRowSpacing = 1.4;
133 static const float kLatchWidth = 15.0;
134 
135 static const int32 kMaxDepth = 1024;
136 static const float kLeftMargin = kLatchWidth;
137 static const float kRightMargin = 8;
138 static const float kOutlineLevelIndent = kLatchWidth;
139 static const float kColumnResizeAreaWidth = 10.0;
140 static const float kRowDragSensitivity = 5.0;
141 static const float kDoubleClickMoveSensitivity = 4.0;
142 static const float kSortIndicatorWidth = 9.0;
143 static const float kDropHighlightLineHeight = 2.0;
144 
145 static const uint32 kToggleColumn = 'BTCL';
146 
147 class BRowContainer : public BObjectList<BRow>
148 {
149 };
150 
151 class TitleView : public BView {
152 	typedef BView _inherited;
153 public:
154 								TitleView(BRect frame, OutlineView* outlineView,
155 									BList* visibleColumns, BList* sortColumns,
156 									BColumnListView* masterView,
157 									uint32 resizingMode);
158 	virtual						~TitleView();
159 
160 			void				ColumnAdded(BColumn* column);
161 			void				ColumnResized(BColumn* column, float oldWidth);
162 			void				SetColumnVisible(BColumn* column, bool visible);
163 
164 	virtual	void				Draw(BRect updateRect);
165 	virtual	void				ScrollTo(BPoint where);
166 	virtual	void				MessageReceived(BMessage* message);
167 	virtual	void				MouseDown(BPoint where);
168 	virtual	void				MouseMoved(BPoint where, uint32 transit,
169 									const BMessage* dragMessage);
170 	virtual	void				MouseUp(BPoint where);
171 	virtual	void				FrameResized(float width, float height);
172 
173 			void				MoveColumn(BColumn* column, int32 index);
174 			void				SetColumnFlags(column_flags flags);
175 
176 			void				SetEditMode(bool state)
177 									{ fEditMode = state; }
178 
179 			float				MarginWidth() const;
180 
181 private:
182 			void				GetTitleRect(BColumn* column, BRect* _rect);
183 			int32				FindColumn(BPoint where, float* _leftEdge);
184 			void				FixScrollBar(bool scrollToFit);
185 			void				DragSelectedColumn(BPoint where);
186 			void				ResizeSelectedColumn(BPoint where,
187 									bool preferred = false);
188 			void				ComputeDragBoundries(BColumn* column,
189 									BPoint where);
190 			void				DrawTitle(BView* view, BRect frame,
191 									BColumn* column, bool depressed);
192 
193 			float				_VirtualWidth() const;
194 
195 			OutlineView*		fOutlineView;
196 			BList*				fColumns;
197 			BList*				fSortColumns;
198 //			float				fColumnsWidth;
199 			BRect				fVisibleRect;
200 
201 #if DOUBLE_BUFFERED_COLUMN_RESIZE
202 			BBitmap*			fDrawBuffer;
203 			BView*				fDrawBufferView;
204 #endif
205 
206 			enum {
207 				INACTIVE,
208 				RESIZING_COLUMN,
209 				PRESSING_COLUMN,
210 				DRAG_COLUMN_INSIDE_TITLE,
211 				DRAG_COLUMN_OUTSIDE_TITLE
212 			}					fCurrentState;
213 
214 			BPopUpMenu*			fColumnPop;
215 			BColumnListView*	fMasterView;
216 			bool				fEditMode;
217 			int32				fColumnFlags;
218 
219 	// State information for resizing/dragging
220 			BColumn*			fSelectedColumn;
221 			BRect				fSelectedColumnRect;
222 			bool				fResizingFirstColumn;
223 			BPoint				fClickPoint; // offset within cell
224 			float				fLeftDragBoundry;
225 			float				fRightDragBoundry;
226 			BPoint				fCurrentDragPosition;
227 
228 
229 			BBitmap*			fUpSortArrow;
230 			BBitmap*			fDownSortArrow;
231 
232 			BCursor*			fResizeCursor;
233 			BCursor*			fMinResizeCursor;
234 			BCursor*			fMaxResizeCursor;
235 			BCursor*			fColumnMoveCursor;
236 };
237 
238 
239 class OutlineView : public BView {
240 	typedef BView _inherited;
241 public:
242 								OutlineView(BRect, BList* visibleColumns,
243 									BList* sortColumns,
244 									BColumnListView* listView);
245 	virtual						~OutlineView();
246 
247 	virtual void				Draw(BRect);
248 	const 	BRect&				VisibleRect() const;
249 
250 			void				RedrawColumn(BColumn* column, float leftEdge,
251 									bool isFirstColumn);
252 			void 				StartSorting();
253 			float				GetColumnPreferredWidth(BColumn* column);
254 
255 			void				AddRow(BRow*, int32 index, BRow* TheRow);
256 			BRow*				CurrentSelection(BRow* lastSelected) const;
257 			void 				ToggleFocusRowSelection(bool selectRange);
258 			void 				ToggleFocusRowOpen();
259 			void 				ChangeFocusRow(bool up, bool updateSelection,
260 									bool addToCurrentSelection);
261 			void 				MoveFocusToVisibleRect();
262 			void 				ExpandOrCollapse(BRow* parent, bool expand);
263 			void 				RemoveRow(BRow*);
264 			BRowContainer*		RowList();
265 			void				UpdateRow(BRow*);
266 			bool				FindParent(BRow* row, BRow** _parent,
267 									bool* _isVisible);
268 			int32				IndexOf(BRow* row);
269 			void				Deselect(BRow*);
270 			void				AddToSelection(BRow*);
271 			void				DeselectAll();
272 			BRow*				FocusRow() const;
273 			void				SetFocusRow(BRow* row, bool select);
274 			BRow*				FindRow(float ypos, int32* _indent,
275 									float* _top);
276 			bool				FindRect(const BRow* row, BRect* _rect);
277 			void				ScrollTo(const BRow* row);
278 
279 			void				Clear();
280 			void				SetSelectionMode(list_view_type type);
281 			list_view_type		SelectionMode() const;
282 			void				SetMouseTrackingEnabled(bool);
283 			void				FixScrollBar(bool scrollToFit);
284 			void				SetEditMode(bool state)
285 									{ fEditMode = state; }
286 
287 	virtual void				FrameResized(float width, float height);
288 	virtual void				ScrollTo(BPoint where);
289 	virtual void				MouseDown(BPoint where);
290 	virtual void				MouseMoved(BPoint where, uint32 transit,
291 									const BMessage* dragMessage);
292 	virtual void				MouseUp(BPoint where);
293 	virtual void				MessageReceived(BMessage* message);
294 
295 private:
296 			bool				SortList(BRowContainer* list, bool isVisible);
297 	static	int32				DeepSortThreadEntry(void* outlineView);
298 			void				DeepSort();
299 			void				SelectRange(BRow* start, BRow* end);
300 			int32				CompareRows(BRow* row1, BRow* row2);
301 			void				AddSorted(BRowContainer* list, BRow* row);
302 			void				RecursiveDeleteRows(BRowContainer* list,
303 									bool owner);
304 			void				InvalidateCachedPositions();
305 			bool				FindVisibleRect(BRow* row, BRect* _rect);
306 
307 			BList*				fColumns;
308 			BList*				fSortColumns;
309 			float				fItemsHeight;
310 			BRowContainer		fRows;
311 			BRect				fVisibleRect;
312 
313 #if DOUBLE_BUFFERED_COLUMN_RESIZE
314 			BBitmap*			fDrawBuffer;
315 			BView*				fDrawBufferView;
316 #endif
317 
318 			BRow*				fFocusRow;
319 			BRect				fFocusRowRect;
320 			BRow*				fRollOverRow;
321 
322 			BRow				fSelectionListDummyHead;
323 			BRow*				fLastSelectedItem;
324 			BRow*				fFirstSelectedItem;
325 
326 			thread_id			fSortThread;
327 			int32				fNumSorted;
328 			bool				fSortCancelled;
329 
330 			enum CurrentState {
331 				INACTIVE,
332 				LATCH_CLICKED,
333 				ROW_CLICKED,
334 				DRAGGING_ROWS
335 			};
336 
337 			CurrentState		fCurrentState;
338 
339 
340 			BColumnListView*	fMasterView;
341 			list_view_type		fSelectionMode;
342 			bool				fTrackMouse;
343 			BField*				fCurrentField;
344 			BRow*				fCurrentRow;
345 			BColumn*			fCurrentColumn;
346 			bool				fMouseDown;
347 			BRect				fFieldRect;
348 			int32				fCurrentCode;
349 			bool				fEditMode;
350 
351 	// State information for mouse/keyboard interaction
352 			BPoint				fClickPoint;
353 			bool				fDragging;
354 			int32				fClickCount;
355 			BRow*				fTargetRow;
356 			float				fTargetRowTop;
357 			BRect				fLatchRect;
358 			float				fDropHighlightY;
359 
360 	friend class RecursiveOutlineIterator;
361 };
362 
363 
364 class RecursiveOutlineIterator {
365 public:
366 								RecursiveOutlineIterator(
367 									BRowContainer* container,
368 									bool openBranchesOnly = true);
369 
370 			BRow*				CurrentRow() const;
371 			int32				CurrentLevel() const;
372 			void				GoToNext();
373 
374 private:
375 			struct {
376 				BRowContainer* fRowSet;
377 				int32 fIndex;
378 				int32 fDepth;
379 			}					fStack[kMaxDepth];
380 
381 			int32				fStackIndex;
382 			BRowContainer*		fCurrentList;
383 			int32				fCurrentListIndex;
384 			int32				fCurrentListDepth;
385 			bool				fOpenBranchesOnly;
386 };
387 
388 }	// namespace BPrivate
389 
390 
391 using namespace BPrivate;
392 
393 
394 BField::BField()
395 {
396 }
397 
398 
399 BField::~BField()
400 {
401 }
402 
403 
404 // #pragma mark -
405 
406 
407 void
408 BColumn::MouseMoved(BColumnListView* /*parent*/, BRow* /*row*/,
409 	BField* /*field*/, BRect /*field_rect*/, BPoint/*point*/,
410 	uint32 /*buttons*/, int32 /*code*/)
411 {
412 }
413 
414 
415 void
416 BColumn::MouseDown(BColumnListView* /*parent*/, BRow* /*row*/,
417 	BField* /*field*/, BRect /*field_rect*/, BPoint /*point*/,
418 	uint32 /*buttons*/)
419 {
420 }
421 
422 
423 void
424 BColumn::MouseUp(BColumnListView* /*parent*/, BRow* /*row*/, BField* /*field*/)
425 {
426 }
427 
428 
429 // #pragma mark -
430 
431 
432 BRow::BRow()
433 	:
434 	fChildList(NULL),
435 	fIsExpanded(false),
436 	fHeight(std::max(kMinRowHeight,
437 		ceilf(be_plain_font->Size() * kRowSpacing))),
438 	fNextSelected(NULL),
439 	fPrevSelected(NULL),
440 	fParent(NULL),
441 	fList(NULL)
442 {
443 }
444 
445 
446 BRow::BRow(float height)
447 	:
448 	fChildList(NULL),
449 	fIsExpanded(false),
450 	fHeight(height),
451 	fNextSelected(NULL),
452 	fPrevSelected(NULL),
453 	fParent(NULL),
454 	fList(NULL)
455 {
456 }
457 
458 
459 BRow::~BRow()
460 {
461 	while (true) {
462 		BField* field = (BField*) fFields.RemoveItem((int32)0);
463 		if (field == 0)
464 			break;
465 
466 		delete field;
467 	}
468 }
469 
470 
471 bool
472 BRow::HasLatch() const
473 {
474 	return fChildList != 0;
475 }
476 
477 
478 int32
479 BRow::CountFields() const
480 {
481 	return fFields.CountItems();
482 }
483 
484 
485 BField*
486 BRow::GetField(int32 index)
487 {
488 	return (BField*)fFields.ItemAt(index);
489 }
490 
491 
492 const BField*
493 BRow::GetField(int32 index) const
494 {
495 	return (const BField*)fFields.ItemAt(index);
496 }
497 
498 
499 void
500 BRow::SetField(BField* field, int32 logicalFieldIndex)
501 {
502 	if (fFields.ItemAt(logicalFieldIndex) != 0)
503 		delete (BField*)fFields.RemoveItem(logicalFieldIndex);
504 
505 	if (NULL != fList) {
506 		ValidateField(field, logicalFieldIndex);
507 		Invalidate();
508 	}
509 
510 	fFields.AddItem(field, logicalFieldIndex);
511 }
512 
513 
514 float
515 BRow::Height() const
516 {
517 	return fHeight;
518 }
519 
520 
521 bool
522 BRow::IsExpanded() const
523 {
524 	return fIsExpanded;
525 }
526 
527 
528 bool
529 BRow::IsSelected() const
530 {
531 	return fPrevSelected != NULL;
532 }
533 
534 
535 void
536 BRow::Invalidate()
537 {
538 	if (fList != NULL)
539 		fList->InvalidateRow(this);
540 }
541 
542 
543 void
544 BRow::ValidateFields() const
545 {
546 	for (int32 i = 0; i < CountFields(); i++)
547 		ValidateField(GetField(i), i);
548 }
549 
550 
551 void
552 BRow::ValidateField(const BField* field, int32 logicalFieldIndex) const
553 {
554 	// The Fields may be moved by the user, but the logicalFieldIndexes
555 	// do not change, so we need to map them over when checking the
556 	// Field types.
557 	BColumn* column = NULL;
558 	int32 items = fList->CountColumns();
559 	for (int32 i = 0 ; i < items; ++i) {
560 		column = fList->ColumnAt(i);
561 		if(column->LogicalFieldNum() == logicalFieldIndex )
562 			break;
563 	}
564 
565 	if (column == NULL) {
566 		BString dbmessage("\n\n\tThe parent BColumnListView does not have "
567 			"\n\ta BColumn at the logical field index ");
568 		dbmessage << logicalFieldIndex << ".\n";
569 		puts(dbmessage.String());
570 	} else {
571 		if (!column->AcceptsField(field)) {
572 			BString dbmessage("\n\n\tThe BColumn of type ");
573 			dbmessage << typeid(*column).name() << "\n\tat logical field index "
574 				<< logicalFieldIndex << "\n\tdoes not support the field type "
575 				<< typeid(*field).name() << ".\n\n";
576 			debugger(dbmessage.String());
577 		}
578 	}
579 }
580 
581 
582 // #pragma mark -
583 
584 
585 BColumn::BColumn(float width, float minWidth, float maxWidth, alignment align)
586 	:
587 	fWidth(width),
588 	fMinWidth(minWidth),
589 	fMaxWidth(maxWidth),
590 	fVisible(true),
591 	fList(0),
592 	fShowHeading(true),
593 	fAlignment(align)
594 {
595 }
596 
597 
598 BColumn::~BColumn()
599 {
600 }
601 
602 
603 float
604 BColumn::Width() const
605 {
606 	return fWidth;
607 }
608 
609 
610 void
611 BColumn::SetWidth(float width)
612 {
613 	fWidth = width;
614 }
615 
616 
617 float
618 BColumn::MinWidth() const
619 {
620 	return fMinWidth;
621 }
622 
623 
624 float
625 BColumn::MaxWidth() const
626 {
627 	return fMaxWidth;
628 }
629 
630 
631 void
632 BColumn::DrawTitle(BRect, BView*)
633 {
634 }
635 
636 
637 void
638 BColumn::DrawField(BField*, BRect, BView*)
639 {
640 }
641 
642 
643 int
644 BColumn::CompareFields(BField*, BField*)
645 {
646 	return 0;
647 }
648 
649 
650 void
651 BColumn::GetColumnName(BString* into) const
652 {
653 	*into = "(Unnamed)";
654 }
655 
656 
657 float
658 BColumn::GetPreferredWidth(BField* field, BView* parent) const
659 {
660 	return fWidth;
661 }
662 
663 
664 bool
665 BColumn::IsVisible() const
666 {
667 	return fVisible;
668 }
669 
670 
671 void
672 BColumn::SetVisible(bool visible)
673 {
674 	if (fList && (fVisible != visible))
675 		fList->SetColumnVisible(this, visible);
676 }
677 
678 
679 bool
680 BColumn::ShowHeading() const
681 {
682 	return fShowHeading;
683 }
684 
685 
686 void
687 BColumn::SetShowHeading(bool state)
688 {
689 	fShowHeading = state;
690 }
691 
692 
693 alignment
694 BColumn::Alignment() const
695 {
696 	return fAlignment;
697 }
698 
699 
700 void
701 BColumn::SetAlignment(alignment align)
702 {
703 	fAlignment = align;
704 }
705 
706 
707 bool
708 BColumn::WantsEvents() const
709 {
710 	return fWantsEvents;
711 }
712 
713 
714 void
715 BColumn::SetWantsEvents(bool state)
716 {
717 	fWantsEvents = state;
718 }
719 
720 
721 int32
722 BColumn::LogicalFieldNum() const
723 {
724 	return fFieldID;
725 }
726 
727 
728 bool
729 BColumn::AcceptsField(const BField*) const
730 {
731 	return true;
732 }
733 
734 
735 // #pragma mark -
736 
737 
738 BColumnListView::BColumnListView(BRect rect, const char* name,
739 	uint32 resizingMode, uint32 flags, border_style border,
740 	bool showHorizontalScrollbar)
741 	:
742 	BView(rect, name, resizingMode,
743 		flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
744 	fStatusView(NULL),
745 	fSelectionMessage(NULL),
746 	fSortingEnabled(true),
747 	fLatchWidth(kLatchWidth),
748 	fBorderStyle(border),
749 	fShowingHorizontalScrollBar(showHorizontalScrollbar)
750 {
751 	_Init();
752 }
753 
754 
755 BColumnListView::BColumnListView(const char* name, uint32 flags,
756 	border_style border, bool showHorizontalScrollbar)
757 	:
758 	BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
759 	fStatusView(NULL),
760 	fSelectionMessage(NULL),
761 	fSortingEnabled(true),
762 	fLatchWidth(kLatchWidth),
763 	fBorderStyle(border),
764 	fShowingHorizontalScrollBar(showHorizontalScrollbar)
765 {
766 	_Init();
767 }
768 
769 
770 BColumnListView::~BColumnListView()
771 {
772 	while (BColumn* column = (BColumn*)fColumns.RemoveItem((int32)0))
773 		delete column;
774 }
775 
776 
777 bool
778 BColumnListView::InitiateDrag(BPoint, bool)
779 {
780 	return false;
781 }
782 
783 
784 void
785 BColumnListView::MessageDropped(BMessage*, BPoint)
786 {
787 }
788 
789 
790 void
791 BColumnListView::ExpandOrCollapse(BRow* row, bool Open)
792 {
793 	fOutlineView->ExpandOrCollapse(row, Open);
794 }
795 
796 
797 status_t
798 BColumnListView::Invoke(BMessage* message)
799 {
800 	if (message == 0)
801 		message = Message();
802 
803 	return BInvoker::Invoke(message);
804 }
805 
806 
807 void
808 BColumnListView::ItemInvoked()
809 {
810 	Invoke();
811 }
812 
813 
814 void
815 BColumnListView::SetInvocationMessage(BMessage* message)
816 {
817 	SetMessage(message);
818 }
819 
820 
821 BMessage*
822 BColumnListView::InvocationMessage() const
823 {
824 	return Message();
825 }
826 
827 
828 uint32
829 BColumnListView::InvocationCommand() const
830 {
831 	return Command();
832 }
833 
834 
835 BRow*
836 BColumnListView::FocusRow() const
837 {
838 	return fOutlineView->FocusRow();
839 }
840 
841 
842 void
843 BColumnListView::SetFocusRow(int32 Index, bool Select)
844 {
845 	SetFocusRow(RowAt(Index), Select);
846 }
847 
848 
849 void
850 BColumnListView::SetFocusRow(BRow* row, bool Select)
851 {
852 	fOutlineView->SetFocusRow(row, Select);
853 }
854 
855 
856 void
857 BColumnListView::SetMouseTrackingEnabled(bool Enabled)
858 {
859 	fOutlineView->SetMouseTrackingEnabled(Enabled);
860 }
861 
862 
863 list_view_type
864 BColumnListView::SelectionMode() const
865 {
866 	return fOutlineView->SelectionMode();
867 }
868 
869 
870 void
871 BColumnListView::Deselect(BRow* row)
872 {
873 	fOutlineView->Deselect(row);
874 }
875 
876 
877 void
878 BColumnListView::AddToSelection(BRow* row)
879 {
880 	fOutlineView->AddToSelection(row);
881 }
882 
883 
884 void
885 BColumnListView::DeselectAll()
886 {
887 	fOutlineView->DeselectAll();
888 }
889 
890 
891 BRow*
892 BColumnListView::CurrentSelection(BRow* lastSelected) const
893 {
894 	return fOutlineView->CurrentSelection(lastSelected);
895 }
896 
897 
898 void
899 BColumnListView::SelectionChanged()
900 {
901 	if (fSelectionMessage)
902 		Invoke(fSelectionMessage);
903 }
904 
905 
906 void
907 BColumnListView::SetSelectionMessage(BMessage* message)
908 {
909 	if (fSelectionMessage == message)
910 		return;
911 
912 	delete fSelectionMessage;
913 	fSelectionMessage = message;
914 }
915 
916 
917 BMessage*
918 BColumnListView::SelectionMessage()
919 {
920 	return fSelectionMessage;
921 }
922 
923 
924 uint32
925 BColumnListView::SelectionCommand() const
926 {
927 	if (fSelectionMessage)
928 		return fSelectionMessage->what;
929 
930 	return 0;
931 }
932 
933 
934 void
935 BColumnListView::SetSelectionMode(list_view_type mode)
936 {
937 	fOutlineView->SetSelectionMode(mode);
938 }
939 
940 
941 void
942 BColumnListView::SetSortingEnabled(bool enabled)
943 {
944 	fSortingEnabled = enabled;
945 	fSortColumns.MakeEmpty();
946 	fTitleView->Invalidate();
947 		// erase sort indicators
948 }
949 
950 
951 bool
952 BColumnListView::SortingEnabled() const
953 {
954 	return fSortingEnabled;
955 }
956 
957 
958 void
959 BColumnListView::SetSortColumn(BColumn* column, bool add, bool ascending)
960 {
961 	if (!SortingEnabled())
962 		return;
963 
964 	if (!add)
965 		fSortColumns.MakeEmpty();
966 
967 	if (!fSortColumns.HasItem(column))
968 		fSortColumns.AddItem(column);
969 
970 	column->fSortAscending = ascending;
971 	fTitleView->Invalidate();
972 	fOutlineView->StartSorting();
973 }
974 
975 
976 void
977 BColumnListView::ClearSortColumns()
978 {
979 	fSortColumns.MakeEmpty();
980 	fTitleView->Invalidate();
981 		// erase sort indicators
982 }
983 
984 
985 void
986 BColumnListView::AddStatusView(BView* view)
987 {
988 	BRect bounds = Bounds();
989 	float width = view->Bounds().Width();
990 	if (width > bounds.Width() / 2)
991 		width = bounds.Width() / 2;
992 
993 	fStatusView = view;
994 
995 	Window()->BeginViewTransaction();
996 	fHorizontalScrollBar->ResizeBy(-(width + 1), 0);
997 	fHorizontalScrollBar->MoveBy((width + 1), 0);
998 	AddChild(view);
999 
1000 	BRect viewRect(bounds);
1001 	viewRect.right = width;
1002 	viewRect.top = viewRect.bottom - B_H_SCROLL_BAR_HEIGHT;
1003 	if (fBorderStyle == B_PLAIN_BORDER)
1004 		viewRect.OffsetBy(1, -1);
1005 	else if (fBorderStyle == B_FANCY_BORDER)
1006 		viewRect.OffsetBy(2, -2);
1007 
1008 	view->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
1009 	view->ResizeTo(viewRect.Width(), viewRect.Height());
1010 	view->MoveTo(viewRect.left, viewRect.top);
1011 	Window()->EndViewTransaction();
1012 }
1013 
1014 
1015 BView*
1016 BColumnListView::RemoveStatusView()
1017 {
1018 	if (fStatusView) {
1019 		float width = fStatusView->Bounds().Width();
1020 		Window()->BeginViewTransaction();
1021 		fStatusView->RemoveSelf();
1022 		fHorizontalScrollBar->MoveBy(-width, 0);
1023 		fHorizontalScrollBar->ResizeBy(width, 0);
1024 		Window()->EndViewTransaction();
1025 	}
1026 
1027 	BView* view = fStatusView;
1028 	fStatusView = 0;
1029 	return view;
1030 }
1031 
1032 
1033 void
1034 BColumnListView::AddColumn(BColumn* column, int32 logicalFieldIndex)
1035 {
1036 	ASSERT(column != NULL);
1037 
1038 	column->fList = this;
1039 	column->fFieldID = logicalFieldIndex;
1040 
1041 	// sanity check -- if there is already a field with this ID, remove it.
1042 	for (int32 index = 0; index < fColumns.CountItems(); index++) {
1043 		BColumn* existingColumn = (BColumn*) fColumns.ItemAt(index);
1044 		if (existingColumn && existingColumn->fFieldID == logicalFieldIndex) {
1045 			RemoveColumn(existingColumn);
1046 			break;
1047 		}
1048 	}
1049 
1050 	if (column->Width() < column->MinWidth())
1051 		column->SetWidth(column->MinWidth());
1052 	else if (column->Width() > column->MaxWidth())
1053 		column->SetWidth(column->MaxWidth());
1054 
1055 	fColumns.AddItem((void*) column);
1056 	fTitleView->ColumnAdded(column);
1057 }
1058 
1059 
1060 void
1061 BColumnListView::MoveColumn(BColumn* column, int32 index)
1062 {
1063 	ASSERT(column != NULL);
1064 	fTitleView->MoveColumn(column, index);
1065 }
1066 
1067 
1068 void
1069 BColumnListView::RemoveColumn(BColumn* column)
1070 {
1071 	if (fColumns.HasItem(column)) {
1072 		SetColumnVisible(column, false);
1073 		if (Window() != NULL)
1074 			Window()->UpdateIfNeeded();
1075 		fColumns.RemoveItem(column);
1076 	}
1077 }
1078 
1079 
1080 int32
1081 BColumnListView::CountColumns() const
1082 {
1083 	return fColumns.CountItems();
1084 }
1085 
1086 
1087 BColumn*
1088 BColumnListView::ColumnAt(int32 field) const
1089 {
1090 	return (BColumn*) fColumns.ItemAt(field);
1091 }
1092 
1093 
1094 BColumn*
1095 BColumnListView::ColumnAt(BPoint point) const
1096 {
1097 	float left = MAX(kLeftMargin, LatchWidth());
1098 
1099 	for (int i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1100 		if (column == NULL || !column->IsVisible())
1101 			continue;
1102 
1103 		float right = left + column->Width();
1104 		if (point.x >= left && point.x <= right)
1105 			return column;
1106 
1107 		left = right + 1;
1108 	}
1109 
1110 	return NULL;
1111 }
1112 
1113 
1114 void
1115 BColumnListView::SetColumnVisible(BColumn* column, bool visible)
1116 {
1117 	fTitleView->SetColumnVisible(column, visible);
1118 }
1119 
1120 
1121 void
1122 BColumnListView::SetColumnVisible(int32 index, bool isVisible)
1123 {
1124 	BColumn* column = ColumnAt(index);
1125 	if (column != NULL)
1126 		column->SetVisible(isVisible);
1127 }
1128 
1129 
1130 bool
1131 BColumnListView::IsColumnVisible(int32 index) const
1132 {
1133 	BColumn* column = ColumnAt(index);
1134 	if (column != NULL)
1135 		return column->IsVisible();
1136 
1137 	return false;
1138 }
1139 
1140 
1141 void
1142 BColumnListView::SetColumnFlags(column_flags flags)
1143 {
1144 	fTitleView->SetColumnFlags(flags);
1145 }
1146 
1147 
1148 void
1149 BColumnListView::ResizeColumnToPreferred(int32 index)
1150 {
1151 	BColumn* column = ColumnAt(index);
1152 	if (column == NULL)
1153 		return;
1154 
1155 	// get the preferred column width
1156 	float width = fOutlineView->GetColumnPreferredWidth(column);
1157 
1158 	// set it
1159 	float oldWidth = column->Width();
1160 	column->SetWidth(width);
1161 
1162 	fTitleView->ColumnResized(column, oldWidth);
1163 	fOutlineView->Invalidate();
1164 }
1165 
1166 
1167 void
1168 BColumnListView::ResizeAllColumnsToPreferred()
1169 {
1170 	int32 count = CountColumns();
1171 	for (int32 i = 0; i < count; i++)
1172 		ResizeColumnToPreferred(i);
1173 }
1174 
1175 
1176 const BRow*
1177 BColumnListView::RowAt(int32 Index, BRow* parentRow) const
1178 {
1179 	if (parentRow == 0)
1180 		return fOutlineView->RowList()->ItemAt(Index);
1181 
1182 	return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : NULL;
1183 }
1184 
1185 
1186 BRow*
1187 BColumnListView::RowAt(int32 Index, BRow* parentRow)
1188 {
1189 	if (parentRow == 0)
1190 		return fOutlineView->RowList()->ItemAt(Index);
1191 
1192 	return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : 0;
1193 }
1194 
1195 
1196 const BRow*
1197 BColumnListView::RowAt(BPoint point) const
1198 {
1199 	float top;
1200 	int32 indent;
1201 	return fOutlineView->FindRow(point.y, &indent, &top);
1202 }
1203 
1204 
1205 BRow*
1206 BColumnListView::RowAt(BPoint point)
1207 {
1208 	float top;
1209 	int32 indent;
1210 	return fOutlineView->FindRow(point.y, &indent, &top);
1211 }
1212 
1213 
1214 bool
1215 BColumnListView::GetRowRect(const BRow* row, BRect* outRect) const
1216 {
1217 	return fOutlineView->FindRect(row, outRect);
1218 }
1219 
1220 
1221 bool
1222 BColumnListView::FindParent(BRow* row, BRow** _parent, bool* _isVisible) const
1223 {
1224 	return fOutlineView->FindParent(row, _parent, _isVisible);
1225 }
1226 
1227 
1228 int32
1229 BColumnListView::IndexOf(BRow* row)
1230 {
1231 	return fOutlineView->IndexOf(row);
1232 }
1233 
1234 
1235 int32
1236 BColumnListView::CountRows(BRow* parentRow) const
1237 {
1238 	if (parentRow == 0)
1239 		return fOutlineView->RowList()->CountItems();
1240 	if (parentRow->fChildList)
1241 		return parentRow->fChildList->CountItems();
1242 	else
1243 		return 0;
1244 }
1245 
1246 
1247 void
1248 BColumnListView::AddRow(BRow* row, BRow* parentRow)
1249 {
1250 	AddRow(row, -1, parentRow);
1251 }
1252 
1253 
1254 void
1255 BColumnListView::AddRow(BRow* row, int32 index, BRow* parentRow)
1256 {
1257 	row->fChildList = 0;
1258 	row->fList = this;
1259 	row->ValidateFields();
1260 	fOutlineView->AddRow(row, index, parentRow);
1261 }
1262 
1263 
1264 void
1265 BColumnListView::RemoveRow(BRow* row)
1266 {
1267 	fOutlineView->RemoveRow(row);
1268 	row->fList = NULL;
1269 }
1270 
1271 
1272 void
1273 BColumnListView::UpdateRow(BRow* row)
1274 {
1275 	fOutlineView->UpdateRow(row);
1276 }
1277 
1278 
1279 bool
1280 BColumnListView::SwapRows(int32 index1, int32 index2, BRow* parentRow1,
1281 	BRow* parentRow2)
1282 {
1283 	BRow* row1 = NULL;
1284 	BRow* row2 = NULL;
1285 
1286 	BRowContainer* container1 = NULL;
1287 	BRowContainer* container2 = NULL;
1288 
1289 	if (parentRow1 == NULL)
1290 		container1 = fOutlineView->RowList();
1291 	else
1292 		container1 = parentRow1->fChildList;
1293 
1294 	if (container1 == NULL)
1295 		return false;
1296 
1297 	if (parentRow2 == NULL)
1298 		container2 = fOutlineView->RowList();
1299 	else
1300 		container2 = parentRow2->fChildList;
1301 
1302 	if (container2 == NULL)
1303 		return false;
1304 
1305 	row1 = container1->ItemAt(index1);
1306 
1307 	if (row1 == NULL)
1308 		return false;
1309 
1310 	row2 = container2->ItemAt(index2);
1311 
1312 	if (row2 == NULL)
1313 		return false;
1314 
1315 	container1->ReplaceItem(index2, row1);
1316 	container2->ReplaceItem(index1, row2);
1317 
1318 	BRect rect1;
1319 	BRect rect2;
1320 	BRect rect;
1321 
1322 	fOutlineView->FindRect(row1, &rect1);
1323 	fOutlineView->FindRect(row2, &rect2);
1324 
1325 	rect = rect1 | rect2;
1326 
1327 	fOutlineView->Invalidate(rect);
1328 
1329 	return true;
1330 }
1331 
1332 
1333 void
1334 BColumnListView::ScrollTo(const BRow* row)
1335 {
1336 	fOutlineView->ScrollTo(row);
1337 }
1338 
1339 
1340 void
1341 BColumnListView::ScrollTo(BPoint point)
1342 {
1343 	fOutlineView->ScrollTo(point);
1344 }
1345 
1346 
1347 void
1348 BColumnListView::Clear()
1349 {
1350 	fOutlineView->Clear();
1351 }
1352 
1353 
1354 void
1355 BColumnListView::InvalidateRow(BRow* row)
1356 {
1357 	BRect updateRect;
1358 	GetRowRect(row, &updateRect);
1359 	fOutlineView->Invalidate(updateRect);
1360 }
1361 
1362 
1363 // This method is deprecated.
1364 void
1365 BColumnListView::SetFont(const BFont* font, uint32 mask)
1366 {
1367 	fOutlineView->SetFont(font, mask);
1368 	fTitleView->SetFont(font, mask);
1369 }
1370 
1371 
1372 void
1373 BColumnListView::SetFont(ColumnListViewFont font_num, const BFont* font,
1374 	uint32 mask)
1375 {
1376 	switch (font_num) {
1377 		case B_FONT_ROW:
1378 			fOutlineView->SetFont(font, mask);
1379 			break;
1380 
1381 		case B_FONT_HEADER:
1382 			fTitleView->SetFont(font, mask);
1383 			break;
1384 
1385 		default:
1386 			ASSERT(false);
1387 			break;
1388 	}
1389 }
1390 
1391 
1392 void
1393 BColumnListView::GetFont(ColumnListViewFont font_num, BFont* font) const
1394 {
1395 	switch (font_num) {
1396 		case B_FONT_ROW:
1397 			fOutlineView->GetFont(font);
1398 			break;
1399 
1400 		case B_FONT_HEADER:
1401 			fTitleView->GetFont(font);
1402 			break;
1403 
1404 		default:
1405 			ASSERT(false);
1406 			break;
1407 	}
1408 }
1409 
1410 
1411 void
1412 BColumnListView::SetColor(ColumnListViewColor colorIndex, const rgb_color color)
1413 {
1414 	if ((int)colorIndex < 0) {
1415 		ASSERT(false);
1416 		colorIndex = (ColumnListViewColor)0;
1417 	}
1418 
1419 	if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
1420 		ASSERT(false);
1421 		colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
1422 	}
1423 
1424 	fColorList[colorIndex] = color;
1425 	fCustomColors = true;
1426 }
1427 
1428 
1429 void
1430 BColumnListView::ResetColors()
1431 {
1432 	fCustomColors = false;
1433 	_UpdateColors();
1434 	Invalidate();
1435 }
1436 
1437 
1438 rgb_color
1439 BColumnListView::Color(ColumnListViewColor colorIndex) const
1440 {
1441 	if ((int)colorIndex < 0) {
1442 		ASSERT(false);
1443 		colorIndex = (ColumnListViewColor)0;
1444 	}
1445 
1446 	if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
1447 		ASSERT(false);
1448 		colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
1449 	}
1450 
1451 	return fColorList[colorIndex];
1452 }
1453 
1454 
1455 void
1456 BColumnListView::SetHighColor(rgb_color color)
1457 {
1458 	BView::SetHighColor(color);
1459 //	fOutlineView->Invalidate();
1460 		// Redraw with the new color.
1461 		// Note that this will currently cause an infinite loop, refreshing
1462 		// over and over. A better solution is needed.
1463 }
1464 
1465 
1466 void
1467 BColumnListView::SetSelectionColor(rgb_color color)
1468 {
1469 	fColorList[B_COLOR_SELECTION] = color;
1470 	fCustomColors = true;
1471 }
1472 
1473 
1474 void
1475 BColumnListView::SetBackgroundColor(rgb_color color)
1476 {
1477 	fColorList[B_COLOR_BACKGROUND] = color;
1478 	fCustomColors = true;
1479 	fOutlineView->Invalidate();
1480 		// repaint with new color
1481 }
1482 
1483 
1484 void
1485 BColumnListView::SetEditColor(rgb_color color)
1486 {
1487 	fColorList[B_COLOR_EDIT_BACKGROUND] = color;
1488 	fCustomColors = true;
1489 }
1490 
1491 
1492 const rgb_color
1493 BColumnListView::SelectionColor() const
1494 {
1495 	return fColorList[B_COLOR_SELECTION];
1496 }
1497 
1498 
1499 const rgb_color
1500 BColumnListView::BackgroundColor() const
1501 {
1502 	return fColorList[B_COLOR_BACKGROUND];
1503 }
1504 
1505 
1506 const rgb_color
1507 BColumnListView::EditColor() const
1508 {
1509 	return fColorList[B_COLOR_EDIT_BACKGROUND];
1510 }
1511 
1512 
1513 BPoint
1514 BColumnListView::SuggestTextPosition(const BRow* row,
1515 	const BColumn* inColumn) const
1516 {
1517 	BRect rect(GetFieldRect(row, inColumn));
1518 
1519 	font_height fh;
1520 	fOutlineView->GetFontHeight(&fh);
1521 	float baseline = floor(rect.top + fh.ascent
1522 		+ (rect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
1523 	return BPoint(rect.left + 8, baseline);
1524 }
1525 
1526 
1527 BRect
1528 BColumnListView::GetFieldRect(const BRow* row, const BColumn* inColumn) const
1529 {
1530 	BRect rect;
1531 	GetRowRect(row, &rect);
1532 	if (inColumn != NULL) {
1533 		float leftEdge = MAX(kLeftMargin, LatchWidth());
1534 		for (int index = 0; index < fColumns.CountItems(); index++) {
1535 			BColumn* column = (BColumn*) fColumns.ItemAt(index);
1536 			if (column == NULL || !column->IsVisible())
1537 				continue;
1538 
1539 			if (column == inColumn) {
1540 				rect.left = leftEdge;
1541 				rect.right = rect.left + column->Width();
1542 				break;
1543 			}
1544 
1545 			leftEdge += column->Width() + 1;
1546 		}
1547 	}
1548 
1549 	return rect;
1550 }
1551 
1552 
1553 void
1554 BColumnListView::SetLatchWidth(float width)
1555 {
1556 	fLatchWidth = width;
1557 	Invalidate();
1558 }
1559 
1560 
1561 float
1562 BColumnListView::LatchWidth() const
1563 {
1564 	return fLatchWidth;
1565 }
1566 
1567 void
1568 BColumnListView::DrawLatch(BView* view, BRect rect, LatchType position, BRow*)
1569 {
1570 	const int32 rectInset = 4;
1571 
1572 	// make square
1573 	int32 sideLen = rect.IntegerWidth();
1574 	if (sideLen > rect.IntegerHeight())
1575 		sideLen = rect.IntegerHeight();
1576 
1577 	// make center
1578 	int32 halfWidth  = rect.IntegerWidth() / 2;
1579 	int32 halfHeight = rect.IntegerHeight() / 2;
1580 	int32 halfSide   = sideLen / 2;
1581 
1582 	float left = rect.left + halfWidth  - halfSide;
1583 	float top  = rect.top  + halfHeight - halfSide;
1584 
1585 	BRect itemRect(left, top, left + sideLen, top + sideLen);
1586 
1587 	// Why it is a pixel high? I don't know.
1588 	itemRect.OffsetBy(0, -1);
1589 
1590 	itemRect.InsetBy(rectInset, rectInset);
1591 
1592 	// make it an odd number of pixels wide, the latch looks better this way
1593 	if ((itemRect.IntegerWidth() % 2) == 1) {
1594 		itemRect.right += 1;
1595 		itemRect.bottom += 1;
1596 	}
1597 
1598 	rgb_color highColor = view->HighColor();
1599 	view->SetHighColor(0, 0, 0);
1600 
1601 	switch (position) {
1602 		case B_OPEN_LATCH:
1603 			view->StrokeRect(itemRect);
1604 			view->StrokeLine(
1605 				BPoint(itemRect.left + 2,
1606 					(itemRect.top + itemRect.bottom) / 2),
1607 				BPoint(itemRect.right - 2,
1608 					(itemRect.top + itemRect.bottom) / 2));
1609 			break;
1610 
1611 		case B_PRESSED_LATCH:
1612 			view->StrokeRect(itemRect);
1613 			view->StrokeLine(
1614 				BPoint(itemRect.left + 2,
1615 					(itemRect.top + itemRect.bottom) / 2),
1616 				BPoint(itemRect.right - 2,
1617 					(itemRect.top + itemRect.bottom) / 2));
1618 			view->StrokeLine(
1619 				BPoint((itemRect.left + itemRect.right) / 2,
1620 					itemRect.top +  2),
1621 				BPoint((itemRect.left + itemRect.right) / 2,
1622 					itemRect.bottom - 2));
1623 			view->InvertRect(itemRect);
1624 			break;
1625 
1626 		case B_CLOSED_LATCH:
1627 			view->StrokeRect(itemRect);
1628 			view->StrokeLine(
1629 				BPoint(itemRect.left + 2,
1630 					(itemRect.top + itemRect.bottom) / 2),
1631 				BPoint(itemRect.right - 2,
1632 					(itemRect.top + itemRect.bottom) / 2));
1633 			view->StrokeLine(
1634 				BPoint((itemRect.left + itemRect.right) / 2,
1635 					itemRect.top +  2),
1636 				BPoint((itemRect.left + itemRect.right) / 2,
1637 					itemRect.bottom - 2));
1638 			break;
1639 
1640 		case B_NO_LATCH:
1641 		default:
1642 			// No drawing
1643 			break;
1644 	}
1645 
1646 	view->SetHighColor(highColor);
1647 }
1648 
1649 
1650 void
1651 BColumnListView::MakeFocus(bool isFocus)
1652 {
1653 	if (fBorderStyle != B_NO_BORDER) {
1654 		// Redraw focus marks around view
1655 		Invalidate();
1656 		fHorizontalScrollBar->SetBorderHighlighted(isFocus);
1657 		fVerticalScrollBar->SetBorderHighlighted(isFocus);
1658 	}
1659 
1660 	BView::MakeFocus(isFocus);
1661 }
1662 
1663 
1664 void
1665 BColumnListView::MessageReceived(BMessage* message)
1666 {
1667 	// Propagate mouse wheel messages down to child, so that it can
1668 	// scroll.  Note we have done so, so we don't go into infinite
1669 	// recursion if this comes back up here.
1670 	if (message->what == B_MOUSE_WHEEL_CHANGED) {
1671 		bool handled;
1672 		if (message->FindBool("be:clvhandled", &handled) != B_OK) {
1673 			message->AddBool("be:clvhandled", true);
1674 			fOutlineView->MessageReceived(message);
1675 			return;
1676 		}
1677 	} else if (message->what == B_COLORS_UPDATED) {
1678 		// Todo: Is it worthwhile to optimize this?
1679 		_UpdateColors();
1680 	}
1681 
1682 	BView::MessageReceived(message);
1683 }
1684 
1685 
1686 void
1687 BColumnListView::KeyDown(const char* bytes, int32 numBytes)
1688 {
1689 	char key = bytes[0];
1690 	switch (key) {
1691 		case B_RIGHT_ARROW:
1692 		case B_LEFT_ARROW:
1693 		{
1694 			if ((modifiers() & B_SHIFT_KEY) != 0) {
1695 				float  minVal, maxVal;
1696 				fHorizontalScrollBar->GetRange(&minVal, &maxVal);
1697 				float smallStep, largeStep;
1698 				fHorizontalScrollBar->GetSteps(&smallStep, &largeStep);
1699 				float oldVal = fHorizontalScrollBar->Value();
1700 				float newVal = oldVal;
1701 
1702 				if (key == B_LEFT_ARROW)
1703 					newVal -= smallStep;
1704 				else if (key == B_RIGHT_ARROW)
1705 					newVal += smallStep;
1706 
1707 				if (newVal < minVal)
1708 					newVal = minVal;
1709 				else if (newVal > maxVal)
1710 					newVal = maxVal;
1711 
1712 				fHorizontalScrollBar->SetValue(newVal);
1713 			} else {
1714 				BRow* focusRow = fOutlineView->FocusRow();
1715 				if (focusRow == NULL)
1716 					break;
1717 
1718 				bool isExpanded = focusRow->HasLatch()
1719 					&& focusRow->IsExpanded();
1720 				switch (key) {
1721 					case B_LEFT_ARROW:
1722 						if (isExpanded)
1723 							fOutlineView->ToggleFocusRowOpen();
1724 						else if (focusRow->fParent != NULL) {
1725 							fOutlineView->DeselectAll();
1726 							fOutlineView->SetFocusRow(focusRow->fParent, true);
1727 							fOutlineView->ScrollTo(focusRow->fParent);
1728 						}
1729 						break;
1730 
1731 					case B_RIGHT_ARROW:
1732 						if (!isExpanded)
1733 							fOutlineView->ToggleFocusRowOpen();
1734 						else
1735 							fOutlineView->ChangeFocusRow(false, true, false);
1736 						break;
1737 				}
1738 			}
1739 			break;
1740 		}
1741 
1742 		case B_DOWN_ARROW:
1743 			fOutlineView->ChangeFocusRow(false,
1744 				(modifiers() & B_CONTROL_KEY) == 0,
1745 				(modifiers() & B_SHIFT_KEY) != 0);
1746 			break;
1747 
1748 		case B_UP_ARROW:
1749 			fOutlineView->ChangeFocusRow(true,
1750 				(modifiers() & B_CONTROL_KEY) == 0,
1751 				(modifiers() & B_SHIFT_KEY) != 0);
1752 			break;
1753 
1754 		case B_PAGE_UP:
1755 		case B_PAGE_DOWN:
1756 		{
1757 			float minValue, maxValue;
1758 			fVerticalScrollBar->GetRange(&minValue, &maxValue);
1759 			float smallStep, largeStep;
1760 			fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
1761 			float currentValue = fVerticalScrollBar->Value();
1762 			float newValue = currentValue;
1763 
1764 			if (key == B_PAGE_UP)
1765 				newValue -= largeStep;
1766 			else
1767 				newValue += largeStep;
1768 
1769 			if (newValue > maxValue)
1770 				newValue = maxValue;
1771 			else if (newValue < minValue)
1772 				newValue = minValue;
1773 
1774 			fVerticalScrollBar->SetValue(newValue);
1775 
1776 			// Option + pgup or pgdn scrolls and changes the selection.
1777 			if (modifiers() & B_OPTION_KEY)
1778 				fOutlineView->MoveFocusToVisibleRect();
1779 
1780 			break;
1781 		}
1782 
1783 		case B_ENTER:
1784 			Invoke();
1785 			break;
1786 
1787 		case B_SPACE:
1788 			fOutlineView->ToggleFocusRowSelection(
1789 				(modifiers() & B_SHIFT_KEY) != 0);
1790 			break;
1791 
1792 		case '+':
1793 			fOutlineView->ToggleFocusRowOpen();
1794 			break;
1795 
1796 		default:
1797 			BView::KeyDown(bytes, numBytes);
1798 	}
1799 }
1800 
1801 
1802 void
1803 BColumnListView::AttachedToWindow()
1804 {
1805 	if (!Messenger().IsValid())
1806 		SetTarget(Window());
1807 
1808 	if (SortingEnabled()) fOutlineView->StartSorting();
1809 }
1810 
1811 
1812 void
1813 BColumnListView::WindowActivated(bool active)
1814 {
1815 	fOutlineView->Invalidate();
1816 		// focus and selection appearance changes with focus
1817 
1818 	Invalidate();
1819 		// redraw focus marks around view
1820 	BView::WindowActivated(active);
1821 }
1822 
1823 
1824 void
1825 BColumnListView::Draw(BRect updateRect)
1826 {
1827 	BRect rect = Bounds();
1828 
1829 	uint32 flags = 0;
1830 	if (IsFocus() && Window()->IsActive())
1831 		flags |= BControlLook::B_FOCUSED;
1832 
1833 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1834 
1835 	BRect verticalScrollBarFrame;
1836 	if (!fVerticalScrollBar->IsHidden())
1837 		verticalScrollBarFrame = fVerticalScrollBar->Frame();
1838 
1839 	BRect horizontalScrollBarFrame;
1840 	if (!fHorizontalScrollBar->IsHidden())
1841 		horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
1842 
1843 	if (fBorderStyle == B_NO_BORDER) {
1844 		// We still draw the left/top border, but not focused.
1845 		// The scrollbars cannot be displayed without frame and
1846 		// it looks bad to have no frame only along the left/top
1847 		// side.
1848 		rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
1849 		SetHighColor(borderColor);
1850 		StrokeLine(BPoint(rect.left, rect.bottom),
1851 			BPoint(rect.left, rect.top));
1852 		StrokeLine(BPoint(rect.left + 1, rect.top),
1853 			BPoint(rect.right, rect.top));
1854 	}
1855 
1856 	be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1857 		verticalScrollBarFrame, horizontalScrollBarFrame,
1858 		base, fBorderStyle, flags);
1859 
1860 	if (fStatusView != NULL) {
1861 		rect = Bounds();
1862 		BRegion region(rect & fStatusView->Frame().InsetByCopy(-2, -2));
1863 		ConstrainClippingRegion(&region);
1864 		rect.bottom = fStatusView->Frame().top - 1;
1865 		be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1866 			BRect(), BRect(), base, fBorderStyle, flags);
1867 	}
1868 }
1869 
1870 
1871 void
1872 BColumnListView::SaveState(BMessage* message)
1873 {
1874 	message->MakeEmpty();
1875 
1876 	for (int32 i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1877 		message->AddInt32("ID", column->fFieldID);
1878 		message->AddFloat("width", column->fWidth);
1879 		message->AddBool("visible", column->fVisible);
1880 	}
1881 
1882 	message->AddBool("sortingenabled", fSortingEnabled);
1883 
1884 	if (fSortingEnabled) {
1885 		for (int32 i = 0; BColumn* column = (BColumn*)fSortColumns.ItemAt(i);
1886 				i++) {
1887 			message->AddInt32("sortID", column->fFieldID);
1888 			message->AddBool("sortascending", column->fSortAscending);
1889 		}
1890 	}
1891 }
1892 
1893 
1894 void
1895 BColumnListView::LoadState(BMessage* message)
1896 {
1897 	int32 id;
1898 	for (int i = 0; message->FindInt32("ID", i, &id) == B_OK; i++) {
1899 		for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
1900 			if (column->fFieldID == id) {
1901 				// move this column to position 'i' and set its attributes
1902 				MoveColumn(column, i);
1903 				float width;
1904 				if (message->FindFloat("width", i, &width) == B_OK)
1905 					column->SetWidth(width);
1906 				bool visible;
1907 				if (message->FindBool("visible", i, &visible) == B_OK)
1908 					column->SetVisible(visible);
1909 			}
1910 		}
1911 	}
1912 	bool b;
1913 	if (message->FindBool("sortingenabled", &b) == B_OK) {
1914 		SetSortingEnabled(b);
1915 		for (int k = 0; message->FindInt32("sortID", k, &id) == B_OK; k++) {
1916 			for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
1917 					j++) {
1918 				if (column->fFieldID == id) {
1919 					// add this column to the sort list
1920 					bool value;
1921 					if (message->FindBool("sortascending", k, &value) == B_OK)
1922 						SetSortColumn(column, true, value);
1923 				}
1924 			}
1925 		}
1926 	}
1927 }
1928 
1929 
1930 void
1931 BColumnListView::SetEditMode(bool state)
1932 {
1933 	fOutlineView->SetEditMode(state);
1934 	fTitleView->SetEditMode(state);
1935 }
1936 
1937 
1938 void
1939 BColumnListView::Refresh()
1940 {
1941 	if (LockLooper()) {
1942 		Invalidate();
1943 		fOutlineView->FixScrollBar (true);
1944 		fOutlineView->Invalidate();
1945 		Window()->UpdateIfNeeded();
1946 		UnlockLooper();
1947 	}
1948 }
1949 
1950 
1951 BSize
1952 BColumnListView::MinSize()
1953 {
1954 	BSize size;
1955 	size.width = 100;
1956 	size.height = std::max(kMinTitleHeight,
1957 		ceilf(be_plain_font->Size() * kTitleSpacing))
1958 		+ 4 * B_H_SCROLL_BAR_HEIGHT;
1959 	if (!fHorizontalScrollBar->IsHidden())
1960 		size.height += fHorizontalScrollBar->Frame().Height() + 1;
1961 	// TODO: Take border size into account
1962 
1963 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1964 }
1965 
1966 
1967 BSize
1968 BColumnListView::PreferredSize()
1969 {
1970 	BSize size = MinSize();
1971 	size.height += ceilf(be_plain_font->Size()) * 20;
1972 
1973 	// return MinSize().width if there are no columns.
1974 	int32 count = CountColumns();
1975 	if (count > 0) {
1976 		BRect titleRect;
1977 		BRect outlineRect;
1978 		BRect vScrollBarRect;
1979 		BRect hScrollBarRect;
1980 		_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1981 			hScrollBarRect);
1982 		// Start with the extra width for border and scrollbars etc.
1983 		size.width = titleRect.left - Bounds().left;
1984 		size.width += Bounds().right - titleRect.right;
1985 
1986 		// If we want all columns to be visible at their current width,
1987 		// we also need to add the extra margin width that the TitleView
1988 		// uses to compute its _VirtualWidth() for the horizontal scroll bar.
1989 		size.width += fTitleView->MarginWidth();
1990 		for (int32 i = 0; i < count; i++) {
1991 			BColumn* column = ColumnAt(i);
1992 			if (column != NULL)
1993 				size.width += column->Width();
1994 		}
1995 	}
1996 
1997 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1998 }
1999 
2000 
2001 BSize
2002 BColumnListView::MaxSize()
2003 {
2004 	BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
2005 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
2006 }
2007 
2008 
2009 void
2010 BColumnListView::LayoutInvalidated(bool descendants)
2011 {
2012 }
2013 
2014 
2015 void
2016 BColumnListView::DoLayout()
2017 {
2018 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
2019 		return;
2020 
2021 	BRect titleRect;
2022 	BRect outlineRect;
2023 	BRect vScrollBarRect;
2024 	BRect hScrollBarRect;
2025 	_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
2026 		hScrollBarRect);
2027 
2028 	fTitleView->MoveTo(titleRect.LeftTop());
2029 	fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
2030 
2031 	fOutlineView->MoveTo(outlineRect.LeftTop());
2032 	fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
2033 
2034 	fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
2035 	fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
2036 		vScrollBarRect.Height());
2037 
2038 	if (fStatusView != NULL) {
2039 		BSize size = fStatusView->MinSize();
2040 		if (size.height > B_H_SCROLL_BAR_HEIGHT)
2041 			size.height = B_H_SCROLL_BAR_HEIGHT;
2042 		if (size.width > Bounds().Width() / 2)
2043 			size.width = floorf(Bounds().Width() / 2);
2044 
2045 		BPoint offset(hScrollBarRect.LeftTop());
2046 
2047 		if (fBorderStyle == B_PLAIN_BORDER) {
2048 			offset += BPoint(0, 1);
2049 		} else if (fBorderStyle == B_FANCY_BORDER) {
2050 			offset += BPoint(-1, 2);
2051 			size.height -= 1;
2052 		}
2053 
2054 		fStatusView->MoveTo(offset);
2055 		fStatusView->ResizeTo(size.width, size.height);
2056 		hScrollBarRect.left = offset.x + size.width + 1;
2057 	}
2058 
2059 	fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
2060 	fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
2061 		hScrollBarRect.Height());
2062 
2063 	fOutlineView->FixScrollBar(true);
2064 }
2065 
2066 
2067 void
2068 BColumnListView::_Init()
2069 {
2070 	SetViewColor(B_TRANSPARENT_32_BIT);
2071 
2072 	BRect bounds(Bounds());
2073 	if (bounds.Width() <= 0)
2074 		bounds.right = 100;
2075 
2076 	if (bounds.Height() <= 0)
2077 		bounds.bottom = 100;
2078 
2079 	fCustomColors = false;
2080 	_UpdateColors();
2081 
2082 	BRect titleRect;
2083 	BRect outlineRect;
2084 	BRect vScrollBarRect;
2085 	BRect hScrollBarRect;
2086 	_GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
2087 		hScrollBarRect);
2088 
2089 	fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
2090 	AddChild(fOutlineView);
2091 
2092 
2093 	fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
2094 		&fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
2095 	AddChild(fTitleView);
2096 
2097 	fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
2098 		fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
2099 	AddChild(fVerticalScrollBar);
2100 
2101 	fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
2102 		"horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
2103 	AddChild(fHorizontalScrollBar);
2104 
2105 	if (!fShowingHorizontalScrollBar)
2106 		fHorizontalScrollBar->Hide();
2107 
2108 	fOutlineView->FixScrollBar(true);
2109 }
2110 
2111 
2112 void
2113 BColumnListView::_UpdateColors()
2114 {
2115 	if (fCustomColors)
2116 		return;
2117 
2118 	fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR);
2119 	fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2120 	fColorList[B_COLOR_ROW_DIVIDER] = tint_color(
2121 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT);
2122 	fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2123 	fColorList[B_COLOR_SELECTION_TEXT] =
2124 		ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2125 
2126 	// For non focus selection uses the selection color as BListView
2127 	fColorList[B_COLOR_NON_FOCUS_SELECTION] =
2128 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2129 
2130 	// edit mode doesn't work very well
2131 	fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color(
2132 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT);
2133 	fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180;
2134 
2135 	// Unused color
2136 	fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2137 
2138 	fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_PANEL_BACKGROUND_COLOR);
2139 	fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR);
2140 
2141 	// Unused colors
2142 	fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2143 	fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2144 }
2145 
2146 
2147 void
2148 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
2149 	BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect)
2150 {
2151 	const float vScrollBarWidth = be_control_look->GetScrollBarWidth(B_VERTICAL),
2152 		hScrollBarHeight = be_control_look->GetScrollBarWidth(B_HORIZONTAL);
2153 
2154 	titleRect = bounds;
2155 	titleRect.bottom = titleRect.top + std::max(kMinTitleHeight,
2156 		ceilf(be_plain_font->Size() * kTitleSpacing));
2157 #if !LOWER_SCROLLBAR
2158 	titleRect.right -= vScrollBarWidth;
2159 #endif
2160 
2161 	outlineRect = bounds;
2162 	outlineRect.top = titleRect.bottom + 1.0;
2163 	outlineRect.right -= vScrollBarWidth;
2164 	if (fShowingHorizontalScrollBar)
2165 		outlineRect.bottom -= hScrollBarHeight;
2166 
2167 	vScrollBarRect = bounds;
2168 #if LOWER_SCROLLBAR
2169 	vScrollBarRect.top += std::max(kMinTitleHeight,
2170 		ceilf(be_plain_font->Size() * kTitleSpacing));
2171 #endif
2172 
2173 	vScrollBarRect.left = vScrollBarRect.right - vScrollBarWidth;
2174 	if (fShowingHorizontalScrollBar)
2175 		vScrollBarRect.bottom -= hScrollBarHeight;
2176 
2177 	hScrollBarRect = bounds;
2178 	hScrollBarRect.top = hScrollBarRect.bottom - hScrollBarHeight;
2179 	hScrollBarRect.right -= vScrollBarWidth;
2180 
2181 	// Adjust stuff so the border will fit.
2182 	if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) {
2183 		titleRect.InsetBy(1, 0);
2184 		titleRect.OffsetBy(0, 1);
2185 		outlineRect.InsetBy(1, 1);
2186 	} else if (fBorderStyle == B_FANCY_BORDER) {
2187 		titleRect.InsetBy(2, 0);
2188 		titleRect.OffsetBy(0, 2);
2189 		outlineRect.InsetBy(2, 2);
2190 
2191 		vScrollBarRect.OffsetBy(-1, 0);
2192 #if LOWER_SCROLLBAR
2193 		vScrollBarRect.top += 2;
2194 		vScrollBarRect.bottom -= 1;
2195 #else
2196 		vScrollBarRect.InsetBy(0, 1);
2197 #endif
2198 		hScrollBarRect.OffsetBy(0, -1);
2199 		hScrollBarRect.InsetBy(1, 0);
2200 	}
2201 }
2202 
2203 
2204 // #pragma mark -
2205 
2206 
2207 TitleView::TitleView(BRect rect, OutlineView* horizontalSlave,
2208 	BList* visibleColumns, BList* sortColumns, BColumnListView* listView,
2209 	uint32 resizingMode)
2210 	:
2211 	BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS),
2212 	fOutlineView(horizontalSlave),
2213 	fColumns(visibleColumns),
2214 	fSortColumns(sortColumns),
2215 //	fColumnsWidth(0),
2216 	fVisibleRect(rect.OffsetToCopy(0, 0)),
2217 	fCurrentState(INACTIVE),
2218 	fColumnPop(NULL),
2219 	fMasterView(listView),
2220 	fEditMode(false),
2221 	fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE
2222 		| B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE)
2223 {
2224 	SetViewColor(B_TRANSPARENT_COLOR);
2225 
2226 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2227 	// xxx this needs to be smart about the size of the backbuffer.
2228 	BRect doubleBufferRect(0, 0, 600, 35);
2229 	fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
2230 	fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
2231 		B_FOLLOW_ALL_SIDES, 0);
2232 	fDrawBuffer->Lock();
2233 	fDrawBuffer->AddChild(fDrawBufferView);
2234 	fDrawBuffer->Unlock();
2235 #endif
2236 
2237 	fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2238 	fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2239 
2240 	fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
2241 	fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
2242 
2243 	fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST);
2244 	fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST);
2245 	fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST);
2246 	fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE);
2247 
2248 	FixScrollBar(true);
2249 }
2250 
2251 
2252 TitleView::~TitleView()
2253 {
2254 	delete fColumnPop;
2255 	fColumnPop = NULL;
2256 
2257 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2258 	delete fDrawBuffer;
2259 #endif
2260 	delete fUpSortArrow;
2261 	delete fDownSortArrow;
2262 
2263 	delete fResizeCursor;
2264 	delete fMaxResizeCursor;
2265 	delete fMinResizeCursor;
2266 	delete fColumnMoveCursor;
2267 }
2268 
2269 
2270 void
2271 TitleView::ColumnAdded(BColumn* column)
2272 {
2273 //	fColumnsWidth += column->Width();
2274 	FixScrollBar(false);
2275 	Invalidate();
2276 }
2277 
2278 
2279 void
2280 TitleView::ColumnResized(BColumn* column, float oldWidth)
2281 {
2282 //	fColumnsWidth += column->Width() - oldWidth;
2283 	FixScrollBar(false);
2284 	Invalidate();
2285 }
2286 
2287 
2288 void
2289 TitleView::SetColumnVisible(BColumn* column, bool visible)
2290 {
2291 	if (column->fVisible == visible)
2292 		return;
2293 
2294 	// If setting it visible, do this first so we can find its position
2295 	// to invalidate.  If hiding it, do it last.
2296 	if (visible)
2297 		column->fVisible = visible;
2298 
2299 	BRect titleInvalid;
2300 	GetTitleRect(column, &titleInvalid);
2301 
2302 	// Now really set the visibility
2303 	column->fVisible = visible;
2304 
2305 //	if (visible)
2306 //		fColumnsWidth += column->Width();
2307 //	else
2308 //		fColumnsWidth -= column->Width();
2309 
2310 	BRect outlineInvalid(fOutlineView->VisibleRect());
2311 	outlineInvalid.left = titleInvalid.left;
2312 	titleInvalid.right = outlineInvalid.right;
2313 
2314 	Invalidate(titleInvalid);
2315 	fOutlineView->Invalidate(outlineInvalid);
2316 
2317 	FixScrollBar(false);
2318 }
2319 
2320 
2321 void
2322 TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect)
2323 {
2324 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2325 	int32 numColumns = fColumns->CountItems();
2326 	for (int index = 0; index < numColumns; index++) {
2327 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2328 		if (!column->IsVisible())
2329 			continue;
2330 
2331 		if (column == findColumn) {
2332 			_rect->Set(leftEdge, 0, leftEdge + column->Width(),
2333 				fVisibleRect.bottom);
2334 			return;
2335 		}
2336 
2337 		leftEdge += column->Width() + 1;
2338 	}
2339 
2340 	TRESPASS();
2341 }
2342 
2343 
2344 int32
2345 TitleView::FindColumn(BPoint position, float* _leftEdge)
2346 {
2347 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2348 	int32 numColumns = fColumns->CountItems();
2349 	for (int index = 0; index < numColumns; index++) {
2350 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2351 		if (!column->IsVisible())
2352 			continue;
2353 
2354 		if (leftEdge > position.x)
2355 			break;
2356 
2357 		if (position.x >= leftEdge
2358 			&& position.x <= leftEdge + column->Width()) {
2359 			*_leftEdge = leftEdge;
2360 			return index;
2361 		}
2362 
2363 		leftEdge += column->Width() + 1;
2364 	}
2365 
2366 	return 0;
2367 }
2368 
2369 
2370 void
2371 TitleView::FixScrollBar(bool scrollToFit)
2372 {
2373 	BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2374 	if (hScrollBar == NULL)
2375 		return;
2376 
2377 	float virtualWidth = _VirtualWidth();
2378 
2379 	if (virtualWidth > fVisibleRect.Width()) {
2380 		hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
2381 
2382 		// Perform the little trick if the user is scrolled over too far.
2383 		// See OutlineView::FixScrollBar for a more in depth explanation
2384 		float maxScrollBarValue = virtualWidth - fVisibleRect.Width();
2385 		if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) {
2386 			hScrollBar->SetRange(0.0, maxScrollBarValue);
2387 			hScrollBar->SetSteps(50, fVisibleRect.Width());
2388 		}
2389 	} else if (hScrollBar->Value() == 0.0) {
2390 		// disable scroll bar.
2391 		hScrollBar->SetRange(0.0, 0.0);
2392 	}
2393 }
2394 
2395 
2396 void
2397 TitleView::DragSelectedColumn(BPoint position)
2398 {
2399 	float invalidLeft = fSelectedColumnRect.left;
2400 	float invalidRight = fSelectedColumnRect.right;
2401 
2402 	float leftEdge;
2403 	int32 columnIndex = FindColumn(position, &leftEdge);
2404 	fSelectedColumnRect.OffsetTo(leftEdge, 0);
2405 
2406 	MoveColumn(fSelectedColumn, columnIndex);
2407 
2408 	fSelectedColumn->fVisible = true;
2409 	ComputeDragBoundries(fSelectedColumn, position);
2410 
2411 	// Redraw the new column position
2412 	GetTitleRect(fSelectedColumn, &fSelectedColumnRect);
2413 	invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft);
2414 	invalidRight = MAX(fSelectedColumnRect.right, invalidRight);
2415 
2416 	Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom));
2417 	fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight,
2418 		fOutlineView->VisibleRect().bottom));
2419 
2420 	DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2421 }
2422 
2423 
2424 void
2425 TitleView::MoveColumn(BColumn* column, int32 index)
2426 {
2427 	fColumns->RemoveItem((void*) column);
2428 
2429 	if (-1 == index) {
2430 		// Re-add the column at the end of the list.
2431 		fColumns->AddItem((void*) column);
2432 	} else {
2433 		fColumns->AddItem((void*) column, index);
2434 	}
2435 }
2436 
2437 
2438 void
2439 TitleView::SetColumnFlags(column_flags flags)
2440 {
2441 	fColumnFlags = flags;
2442 }
2443 
2444 
2445 float
2446 TitleView::MarginWidth() const
2447 {
2448 	return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin;
2449 }
2450 
2451 
2452 void
2453 TitleView::ResizeSelectedColumn(BPoint position, bool preferred)
2454 {
2455 	float minWidth = fSelectedColumn->MinWidth();
2456 	float maxWidth = fSelectedColumn->MaxWidth();
2457 
2458 	float oldWidth = fSelectedColumn->Width();
2459 	float originalEdge = fSelectedColumnRect.left + oldWidth;
2460 	if (preferred) {
2461 		float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
2462 		fSelectedColumn->SetWidth(width);
2463 	} else if (position.x > fSelectedColumnRect.left + maxWidth)
2464 		fSelectedColumn->SetWidth(maxWidth);
2465 	else if (position.x < fSelectedColumnRect.left + minWidth)
2466 		fSelectedColumn->SetWidth(minWidth);
2467 	else
2468 		fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
2469 
2470 	float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
2471 		 - originalEdge;
2472 	if (dX != 0) {
2473 		float columnHeight = fVisibleRect.Height();
2474 		BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
2475 		BRect movedRect(originalRect);
2476 		movedRect.OffsetBy(dX, 0);
2477 
2478 		// Update the size of the title column
2479 		BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
2480 		BRect destRect(sourceRect);
2481 		destRect.OffsetBy(fSelectedColumnRect.left, 0);
2482 
2483 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2484 		fDrawBuffer->Lock();
2485 		DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
2486 		fDrawBufferView->Sync();
2487 		fDrawBuffer->Unlock();
2488 
2489 		CopyBits(originalRect, movedRect);
2490 		DrawBitmap(fDrawBuffer, sourceRect, destRect);
2491 #else
2492 		CopyBits(originalRect, movedRect);
2493 		DrawTitle(this, destRect, fSelectedColumn, false);
2494 #endif
2495 
2496 		// Update the body view
2497 		BRect slaveSize = fOutlineView->VisibleRect();
2498 		BRect slaveSource(originalRect);
2499 		slaveSource.bottom = slaveSize.bottom;
2500 		BRect slaveDest(movedRect);
2501 		slaveDest.bottom = slaveSize.bottom;
2502 		fOutlineView->CopyBits(slaveSource, slaveDest);
2503 		fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left,
2504 			fResizingFirstColumn);
2505 
2506 //		fColumnsWidth += dX;
2507 
2508 		// Update the cursor
2509 		if (fSelectedColumn->Width() == minWidth)
2510 			SetViewCursor(fMinResizeCursor, true);
2511 		else if (fSelectedColumn->Width() == maxWidth)
2512 			SetViewCursor(fMaxResizeCursor, true);
2513 		else
2514 			SetViewCursor(fResizeCursor, true);
2515 
2516 		ColumnResized(fSelectedColumn, oldWidth);
2517 	}
2518 }
2519 
2520 
2521 void
2522 TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
2523 {
2524 	float previousColumnLeftEdge = -1000000.0;
2525 	float nextColumnRightEdge = 1000000.0;
2526 
2527 	bool foundColumn = false;
2528 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2529 	int32 numColumns = fColumns->CountItems();
2530 	for (int index = 0; index < numColumns; index++) {
2531 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2532 		if (!column->IsVisible())
2533 			continue;
2534 
2535 		if (column == findColumn) {
2536 			foundColumn = true;
2537 			continue;
2538 		}
2539 
2540 		if (foundColumn) {
2541 			nextColumnRightEdge = leftEdge + column->Width();
2542 			break;
2543 		} else
2544 			previousColumnLeftEdge = leftEdge;
2545 
2546 		leftEdge += column->Width() + 1;
2547 	}
2548 
2549 	float rightEdge = leftEdge + findColumn->Width();
2550 
2551 	fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
2552 		leftEdge);
2553 	fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
2554 }
2555 
2556 
2557 void
2558 TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
2559 {
2560 	BRect drawRect;
2561 	drawRect = rect;
2562 
2563 	font_height fh;
2564 	GetFontHeight(&fh);
2565 
2566 	float baseline = floor(drawRect.top + fh.ascent
2567 		+ (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
2568 
2569 	BRect bgRect = rect;
2570 
2571 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
2572 	view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
2573 	view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
2574 
2575 	bgRect.bottom--;
2576 	bgRect.right--;
2577 
2578 	if (depressed)
2579 		base = tint_color(base, B_DARKEN_1_TINT);
2580 
2581 	be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
2582 		BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
2583 
2584 	view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
2585 		B_DARKEN_2_TINT));
2586 	view->StrokeLine(rect.RightTop(), rect.RightBottom());
2587 
2588 	// If no column given, nothing else to draw.
2589 	if (column == NULL)
2590 		return;
2591 
2592 	view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2593 
2594 	BFont font;
2595 	GetFont(&font);
2596 	view->SetFont(&font);
2597 
2598 	int sortIndex = fSortColumns->IndexOf(column);
2599 	if (sortIndex >= 0) {
2600 		// Draw sort notation.
2601 		BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline);
2602 
2603 		if (fSortColumns->CountItems() > 1) {
2604 			char str[256];
2605 			sprintf(str, "%d", sortIndex + 1);
2606 			const float w = view->StringWidth(str);
2607 			upperLeft.x -= w;
2608 
2609 			view->SetDrawingMode(B_OP_COPY);
2610 			view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth,
2611 				baseline));
2612 			view->DrawString(str);
2613 		}
2614 
2615 		float bmh = fDownSortArrow->Bounds().Height()+1;
2616 
2617 		view->SetDrawingMode(B_OP_OVER);
2618 
2619 		if (column->fSortAscending) {
2620 			BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2621 				- fDownSortArrow->Bounds().IntegerHeight()) / 2);
2622 			view->DrawBitmapAsync(fDownSortArrow, leftTop);
2623 		} else {
2624 			BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2625 				- fUpSortArrow->Bounds().IntegerHeight()) / 2);
2626 			view->DrawBitmapAsync(fUpSortArrow, leftTop);
2627 		}
2628 
2629 		upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2);
2630 		if (upperLeft.y < drawRect.top)
2631 			upperLeft.y = drawRect.top;
2632 
2633 		// Adjust title stuff for sort indicator
2634 		drawRect.right = upperLeft.x - 2;
2635 	}
2636 
2637 	if (drawRect.right > drawRect.left) {
2638 #if CONSTRAIN_CLIPPING_REGION
2639 		BRegion clipRegion(drawRect);
2640 		view->PushState();
2641 		view->ConstrainClippingRegion(&clipRegion);
2642 #endif
2643 		view->MovePenTo(BPoint(drawRect.left + 8, baseline));
2644 		view->SetDrawingMode(B_OP_OVER);
2645 		view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2646 		column->DrawTitle(drawRect, view);
2647 
2648 #if CONSTRAIN_CLIPPING_REGION
2649 		view->PopState();
2650 #endif
2651 	}
2652 }
2653 
2654 
2655 float
2656 TitleView::_VirtualWidth() const
2657 {
2658 	float width = MarginWidth();
2659 
2660 	int32 count = fColumns->CountItems();
2661 	for (int32 i = 0; i < count; i++) {
2662 		BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
2663 		if (column->IsVisible())
2664 			width += column->Width();
2665 	}
2666 
2667 	return width;
2668 }
2669 
2670 
2671 void
2672 TitleView::Draw(BRect invalidRect)
2673 {
2674 	float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2675 	for (int32 columnIndex = 0; columnIndex < fColumns->CountItems();
2676 		columnIndex++) {
2677 
2678 		BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
2679 		if (!column->IsVisible())
2680 			continue;
2681 
2682 		if (columnLeftEdge > invalidRect.right)
2683 			break;
2684 
2685 		if (columnLeftEdge + column->Width() >= invalidRect.left) {
2686 			BRect titleRect(columnLeftEdge, 0,
2687 				columnLeftEdge + column->Width(), fVisibleRect.Height());
2688 			DrawTitle(this, titleRect, column,
2689 				(fCurrentState == DRAG_COLUMN_INSIDE_TITLE
2690 				&& fSelectedColumn == column));
2691 		}
2692 
2693 		columnLeftEdge += column->Width() + 1;
2694 	}
2695 
2696 
2697 	// bevels for right title margin
2698 	if (columnLeftEdge <= invalidRect.right) {
2699 		BRect titleRect(columnLeftEdge, 0, Bounds().right + 2,
2700 			fVisibleRect.Height());
2701 		DrawTitle(this, titleRect, NULL, false);
2702 	}
2703 
2704 	// bevels for left title margin
2705 	if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) {
2706 		BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1,
2707 			fVisibleRect.Height());
2708 		DrawTitle(this, titleRect, NULL, false);
2709 	}
2710 
2711 #if DRAG_TITLE_OUTLINE
2712 	// (internal) column drag indicator
2713 	if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) {
2714 		BRect dragRect(fSelectedColumnRect);
2715 		dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2716 		if (dragRect.Intersects(invalidRect)) {
2717 			SetHighColor(0, 0, 255);
2718 			StrokeRect(dragRect);
2719 		}
2720 	}
2721 #endif
2722 }
2723 
2724 
2725 void
2726 TitleView::ScrollTo(BPoint position)
2727 {
2728 	fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0);
2729 	fVisibleRect.OffsetTo(position.x, position.y);
2730 
2731 	// Perform the little trick if the user is scrolled over too far.
2732 	// See OutlineView::ScrollTo for a more in depth explanation
2733 	float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width();
2734 	BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2735 	float min, max;
2736 	hScrollBar->GetRange(&min, &max);
2737 	if (max != maxScrollBarValue && position.x > maxScrollBarValue)
2738 		FixScrollBar(true);
2739 
2740 	_inherited::ScrollTo(position);
2741 }
2742 
2743 
2744 void
2745 TitleView::MessageReceived(BMessage* message)
2746 {
2747 	if (message->what == kToggleColumn) {
2748 		int32 num;
2749 		if (message->FindInt32("be:field_num", &num) == B_OK) {
2750 			for (int index = 0; index < fColumns->CountItems(); index++) {
2751 				BColumn* column = (BColumn*) fColumns->ItemAt(index);
2752 				if (column == NULL)
2753 					continue;
2754 
2755 				if (column->LogicalFieldNum() == num)
2756 					column->SetVisible(!column->IsVisible());
2757 			}
2758 		}
2759 		return;
2760 	}
2761 
2762 	BView::MessageReceived(message);
2763 }
2764 
2765 
2766 void
2767 TitleView::MouseDown(BPoint position)
2768 {
2769 	if (fEditMode)
2770 		return;
2771 
2772 	int32 buttons = 1;
2773 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2774 	if (buttons == B_SECONDARY_MOUSE_BUTTON
2775 		&& (fColumnFlags & B_ALLOW_COLUMN_POPUP)) {
2776 		// Right mouse button -- bring up menu to show/hide columns.
2777 		if (fColumnPop == NULL)
2778 			fColumnPop = new BPopUpMenu("Columns", false, false);
2779 
2780 		fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true);
2781 		BMessenger me(this);
2782 		for (int index = 0; index < fColumns->CountItems(); index++) {
2783 			BColumn* column = (BColumn*) fColumns->ItemAt(index);
2784 			if (column == NULL)
2785 				continue;
2786 
2787 			BString name;
2788 			column->GetColumnName(&name);
2789 			BMessage* message = new BMessage(kToggleColumn);
2790 			message->AddInt32("be:field_num", column->LogicalFieldNum());
2791 			BMenuItem* item = new BMenuItem(name.String(), message);
2792 			item->SetMarked(column->IsVisible());
2793 			item->SetTarget(me);
2794 			fColumnPop->AddItem(item);
2795 		}
2796 
2797 		BPoint screenPosition = ConvertToScreen(position);
2798 		BRect sticky(screenPosition, screenPosition);
2799 		sticky.InsetBy(-5, -5);
2800 		fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true);
2801 
2802 		return;
2803 	}
2804 
2805 	fResizingFirstColumn = true;
2806 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2807 	for (int index = 0; index < fColumns->CountItems(); index++) {
2808 		BColumn* column = (BColumn*)fColumns->ItemAt(index);
2809 		if (column == NULL || !column->IsVisible())
2810 			continue;
2811 
2812 		if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2813 			break;
2814 
2815 		// check for resizing a column
2816 		float rightEdge = leftEdge + column->Width();
2817 
2818 		if (column->ShowHeading()) {
2819 			if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2820 				&& position.x < rightEdge + kColumnResizeAreaWidth / 2
2821 				&& column->MaxWidth() > column->MinWidth()
2822 				&& (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) {
2823 
2824 				int32 clicks = 0;
2825 				fSelectedColumn = column;
2826 				fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2827 					fVisibleRect.Height());
2828 				Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2829 				if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) {
2830 					ResizeSelectedColumn(position, true);
2831 					fCurrentState = INACTIVE;
2832 					break;
2833 				}
2834 				fCurrentState = RESIZING_COLUMN;
2835 				fClickPoint = BPoint(position.x - rightEdge - 1,
2836 					position.y - fSelectedColumnRect.top);
2837 				SetMouseEventMask(B_POINTER_EVENTS,
2838 					B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2839 				break;
2840 			}
2841 
2842 			fResizingFirstColumn = false;
2843 
2844 			// check for clicking on a column
2845 			if (position.x > leftEdge && position.x < rightEdge) {
2846 				fCurrentState = PRESSING_COLUMN;
2847 				fSelectedColumn = column;
2848 				fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2849 					fVisibleRect.Height());
2850 				DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2851 				fClickPoint = BPoint(position.x - fSelectedColumnRect.left,
2852 					position.y - fSelectedColumnRect.top);
2853 				SetMouseEventMask(B_POINTER_EVENTS,
2854 					B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2855 				break;
2856 			}
2857 		}
2858 		leftEdge = rightEdge + 1;
2859 	}
2860 }
2861 
2862 
2863 void
2864 TitleView::MouseMoved(BPoint position, uint32 transit,
2865 	const BMessage* dragMessage)
2866 {
2867 	if (fEditMode)
2868 		return;
2869 
2870 	// Handle column manipulation
2871 	switch (fCurrentState) {
2872 		case RESIZING_COLUMN:
2873 			ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
2874 			break;
2875 
2876 		case PRESSING_COLUMN: {
2877 			if (abs((int32)(position.x - (fClickPoint.x
2878 					+ fSelectedColumnRect.left))) > kColumnResizeAreaWidth
2879 				|| abs((int32)(position.y - (fClickPoint.y
2880 					+ fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
2881 				// User has moved the mouse more than the tolerable amount,
2882 				// initiate a drag.
2883 				if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
2884 					if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
2885 						fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2886 						ComputeDragBoundries(fSelectedColumn, position);
2887 						SetViewCursor(fColumnMoveCursor, true);
2888 #if DRAG_TITLE_OUTLINE
2889 						BRect invalidRect(fSelectedColumnRect);
2890 						invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
2891 						fCurrentDragPosition = position;
2892 						Invalidate(invalidRect);
2893 #endif
2894 					}
2895 				} else {
2896 					if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
2897 						// Dragged outside view
2898 						fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2899 						fSelectedColumn->SetVisible(false);
2900 						BRect dragRect(fSelectedColumnRect);
2901 
2902 						// There is a race condition where the mouse may have
2903 						// moved by the time we get to handle this message.
2904 						// If the user drags a column very quickly, this
2905 						// results in the annoying bug where the cursor is
2906 						// outside of the rectangle that is being dragged
2907 						// around.  Call GetMouse with the checkQueue flag set
2908 						// to false so we can get the most recent position of
2909 						// the mouse.  This minimizes this problem (although
2910 						// it is currently not possible to completely eliminate
2911 						// it).
2912 						uint32 buttons;
2913 						GetMouse(&position, &buttons, false);
2914 						dragRect.OffsetTo(position.x - fClickPoint.x,
2915 							position.y - dragRect.Height() / 2);
2916 						BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2917 					}
2918 				}
2919 			}
2920 
2921 			break;
2922 		}
2923 
2924 		case DRAG_COLUMN_INSIDE_TITLE: {
2925 			if (transit == B_EXITED_VIEW
2926 				&& (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) {
2927 				// Dragged outside view
2928 				fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2929 				fSelectedColumn->SetVisible(false);
2930 				BRect dragRect(fSelectedColumnRect);
2931 
2932 				// See explanation above.
2933 				uint32 buttons;
2934 				GetMouse(&position, &buttons, false);
2935 
2936 				dragRect.OffsetTo(position.x - fClickPoint.x,
2937 					position.y - fClickPoint.y);
2938 				BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2939 			} else if (position.x < fLeftDragBoundry
2940 				|| position.x > fRightDragBoundry) {
2941 				DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2942 			}
2943 
2944 #if DRAG_TITLE_OUTLINE
2945 			// Set up the invalid rect to include the rect for the previous
2946 			// position of the drag rect, as well as the new one.
2947 			BRect invalidRect(fSelectedColumnRect);
2948 			invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2949 			if (position.x < fCurrentDragPosition.x)
2950 				invalidRect.left -= fCurrentDragPosition.x - position.x;
2951 			else
2952 				invalidRect.right += position.x - fCurrentDragPosition.x;
2953 
2954 			fCurrentDragPosition = position;
2955 			Invalidate(invalidRect);
2956 #endif
2957 			break;
2958 		}
2959 
2960 		case DRAG_COLUMN_OUTSIDE_TITLE:
2961 			if (transit == B_ENTERED_VIEW) {
2962 				// Drag back into view
2963 				EndRectTracking();
2964 				fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2965 				fSelectedColumn->SetVisible(true);
2966 				DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2967 			}
2968 
2969 			break;
2970 
2971 		case INACTIVE:
2972 			// Check for cursor changes if we are over the resize area for
2973 			// a column.
2974 			BColumn* resizeColumn = 0;
2975 			float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2976 			for (int index = 0; index < fColumns->CountItems(); index++) {
2977 				BColumn* column = (BColumn*) fColumns->ItemAt(index);
2978 				if (!column->IsVisible())
2979 					continue;
2980 
2981 				if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2982 					break;
2983 
2984 				float rightEdge = leftEdge + column->Width();
2985 				if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2986 					&& position.x < rightEdge + kColumnResizeAreaWidth / 2
2987 					&& column->MaxWidth() > column->MinWidth()) {
2988 					resizeColumn = column;
2989 					break;
2990 				}
2991 
2992 				leftEdge = rightEdge + 1;
2993 			}
2994 
2995 			// Update the cursor
2996 			if (resizeColumn) {
2997 				if (resizeColumn->Width() == resizeColumn->MinWidth())
2998 					SetViewCursor(fMinResizeCursor, true);
2999 				else if (resizeColumn->Width() == resizeColumn->MaxWidth())
3000 					SetViewCursor(fMaxResizeCursor, true);
3001 				else
3002 					SetViewCursor(fResizeCursor, true);
3003 			} else
3004 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3005 			break;
3006 	}
3007 }
3008 
3009 
3010 void
3011 TitleView::MouseUp(BPoint position)
3012 {
3013 	if (fEditMode)
3014 		return;
3015 
3016 	switch (fCurrentState) {
3017 		case RESIZING_COLUMN:
3018 			ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
3019 			fCurrentState = INACTIVE;
3020 			FixScrollBar(false);
3021 			break;
3022 
3023 		case PRESSING_COLUMN: {
3024 			if (fMasterView->SortingEnabled()) {
3025 				if (fSortColumns->HasItem(fSelectedColumn)) {
3026 					if ((modifiers() & B_CONTROL_KEY) == 0
3027 						&& fSortColumns->CountItems() > 1) {
3028 						fSortColumns->MakeEmpty();
3029 						fSortColumns->AddItem(fSelectedColumn);
3030 					}
3031 
3032 					fSelectedColumn->fSortAscending
3033 						= !fSelectedColumn->fSortAscending;
3034 				} else {
3035 					if ((modifiers() & B_CONTROL_KEY) == 0)
3036 						fSortColumns->MakeEmpty();
3037 
3038 					fSortColumns->AddItem(fSelectedColumn);
3039 					fSelectedColumn->fSortAscending = true;
3040 				}
3041 
3042 				fOutlineView->StartSorting();
3043 			}
3044 
3045 			fCurrentState = INACTIVE;
3046 			Invalidate();
3047 			break;
3048 		}
3049 
3050 		case DRAG_COLUMN_INSIDE_TITLE:
3051 			fCurrentState = INACTIVE;
3052 
3053 #if DRAG_TITLE_OUTLINE
3054 			Invalidate();	// xxx Can make this smaller
3055 #else
3056 			Invalidate(fSelectedColumnRect);
3057 #endif
3058 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3059 			break;
3060 
3061 		case DRAG_COLUMN_OUTSIDE_TITLE:
3062 			fCurrentState = INACTIVE;
3063 			EndRectTracking();
3064 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3065 			break;
3066 
3067 		default:
3068 			;
3069 	}
3070 }
3071 
3072 
3073 void
3074 TitleView::FrameResized(float width, float height)
3075 {
3076 	fVisibleRect.right = fVisibleRect.left + width;
3077 	fVisibleRect.bottom = fVisibleRect.top + height;
3078 	FixScrollBar(true);
3079 }
3080 
3081 
3082 // #pragma mark - OutlineView
3083 
3084 
3085 OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns,
3086 	BColumnListView* listView)
3087 	:
3088 	BView(rect, "outline_view", B_FOLLOW_ALL_SIDES,
3089 		B_WILL_DRAW | B_FRAME_EVENTS),
3090 	fColumns(visibleColumns),
3091 	fSortColumns(sortColumns),
3092 	fItemsHeight(0.0),
3093 	fVisibleRect(rect.OffsetToCopy(0, 0)),
3094 	fFocusRow(0),
3095 	fRollOverRow(0),
3096 	fLastSelectedItem(0),
3097 	fFirstSelectedItem(0),
3098 	fSortThread(B_BAD_THREAD_ID),
3099 	fCurrentState(INACTIVE),
3100 	fMasterView(listView),
3101 	fSelectionMode(B_MULTIPLE_SELECTION_LIST),
3102 	fTrackMouse(false),
3103 	fCurrentField(0),
3104 	fCurrentRow(0),
3105 	fCurrentColumn(0),
3106 	fMouseDown(false),
3107 	fCurrentCode(B_OUTSIDE_VIEW),
3108 	fEditMode(false),
3109 	fDragging(false),
3110 	fClickCount(0),
3111 	fDropHighlightY(-1)
3112 {
3113 	SetViewColor(B_TRANSPARENT_COLOR);
3114 
3115 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3116 	// TODO: This needs to be smart about the size of the buffer.
3117 	// Also, the buffer can be shared with the title's buffer.
3118 	BRect doubleBufferRect(0, 0, 600, 35);
3119 	fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
3120 	fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
3121 		B_FOLLOW_ALL_SIDES, 0);
3122 	fDrawBuffer->Lock();
3123 	fDrawBuffer->AddChild(fDrawBufferView);
3124 	fDrawBuffer->Unlock();
3125 #endif
3126 
3127 	FixScrollBar(true);
3128 	fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
3129 	fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
3130 }
3131 
3132 
3133 OutlineView::~OutlineView()
3134 {
3135 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3136 	delete fDrawBuffer;
3137 #endif
3138 
3139 	Clear();
3140 }
3141 
3142 
3143 void
3144 OutlineView::Clear()
3145 {
3146 	DeselectAll();
3147 		// Make sure selection list doesn't point to deleted rows!
3148 	RecursiveDeleteRows(&fRows, false);
3149 	fItemsHeight = 0.0;
3150 	FixScrollBar(true);
3151 	Invalidate();
3152 }
3153 
3154 
3155 void
3156 OutlineView::SetSelectionMode(list_view_type mode)
3157 {
3158 	DeselectAll();
3159 	fSelectionMode = mode;
3160 }
3161 
3162 
3163 list_view_type
3164 OutlineView::SelectionMode() const
3165 {
3166 	return fSelectionMode;
3167 }
3168 
3169 
3170 void
3171 OutlineView::Deselect(BRow* row)
3172 {
3173 	if (row == NULL)
3174 		return;
3175 
3176 	if (row->fNextSelected != 0) {
3177 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
3178 		row->fPrevSelected->fNextSelected = row->fNextSelected;
3179 		row->fNextSelected = 0;
3180 		row->fPrevSelected = 0;
3181 		Invalidate();
3182 	}
3183 }
3184 
3185 
3186 void
3187 OutlineView::AddToSelection(BRow* row)
3188 {
3189 	if (row == NULL)
3190 		return;
3191 
3192 	if (row->fNextSelected == 0) {
3193 		if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3194 			DeselectAll();
3195 
3196 		row->fNextSelected = fSelectionListDummyHead.fNextSelected;
3197 		row->fPrevSelected = &fSelectionListDummyHead;
3198 		row->fNextSelected->fPrevSelected = row;
3199 		row->fPrevSelected->fNextSelected = row;
3200 
3201 		BRect invalidRect;
3202 		if (FindVisibleRect(row, &invalidRect))
3203 			Invalidate(invalidRect);
3204 	}
3205 }
3206 
3207 
3208 void
3209 OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
3210 {
3211 	if (list == NULL)
3212 		return;
3213 
3214 	while (true) {
3215 		BRow* row = list->RemoveItemAt(0L);
3216 		if (row == 0)
3217 			break;
3218 
3219 		if (row->fChildList)
3220 			RecursiveDeleteRows(row->fChildList, true);
3221 
3222 		delete row;
3223 	}
3224 
3225 	if (isOwner)
3226 		delete list;
3227 }
3228 
3229 
3230 void
3231 OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
3232 {
3233 	// TODO: Remove code duplication (private function which takes a view
3234 	// pointer, pass "this" in non-double buffered mode)!
3235 	// Watch out for sourceRect versus destRect though!
3236 	if (!column)
3237 		return;
3238 
3239 	font_height fh;
3240 	GetFontHeight(&fh);
3241 	float line = 0.0;
3242 	bool tintedLine = true;
3243 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3244 		line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
3245 
3246 		BRow* row = iterator.CurrentRow();
3247 		float rowHeight = row->Height();
3248 		if (line > fVisibleRect.bottom)
3249 			break;
3250 		tintedLine = !tintedLine;
3251 
3252 		if (line + rowHeight >= fVisibleRect.top) {
3253 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3254 			BRect sourceRect(0, 0, column->Width(), rowHeight);
3255 #endif
3256 			BRect destRect(leftEdge, line, leftEdge + column->Width(),
3257 				line + rowHeight);
3258 
3259 			rgb_color highColor;
3260 			rgb_color lowColor;
3261 			if (row->fNextSelected != 0) {
3262 				if (fEditMode) {
3263 					highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3264 					lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3265 				} else {
3266 					highColor = fMasterView->Color(B_COLOR_SELECTION);
3267 					lowColor = fMasterView->Color(B_COLOR_SELECTION);
3268 				}
3269 			} else {
3270 				highColor = fMasterView->Color(B_COLOR_BACKGROUND);
3271 				lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3272 			}
3273 			if (tintedLine)
3274 				lowColor = tint_color(lowColor, kTintedLineTint);
3275 
3276 
3277 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3278 			fDrawBuffer->Lock();
3279 
3280 			fDrawBufferView->SetHighColor(highColor);
3281 			fDrawBufferView->SetLowColor(lowColor);
3282 
3283 			BFont font;
3284 			GetFont(&font);
3285 			fDrawBufferView->SetFont(&font);
3286 			fDrawBufferView->FillRect(sourceRect, B_SOLID_LOW);
3287 
3288 			if (isFirstColumn) {
3289 				// If this is the first column, double buffer drawing the latch
3290 				// too.
3291 				destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3292 					- fMasterView->LatchWidth();
3293 				sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3294 					- fMasterView->LatchWidth();
3295 
3296 				LatchType pos = B_NO_LATCH;
3297 				if (row->HasLatch())
3298 					pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH;
3299 
3300 				BRect latchRect(sourceRect);
3301 				latchRect.right = latchRect.left + fMasterView->LatchWidth();
3302 				fMasterView->DrawLatch(fDrawBufferView, latchRect, pos, row);
3303 			}
3304 
3305 			BField* field = row->GetField(column->fFieldID);
3306 			if (field) {
3307 				BRect fieldRect(sourceRect);
3308 				if (isFirstColumn)
3309 					fieldRect.left += fMasterView->LatchWidth();
3310 
3311 	#if CONSTRAIN_CLIPPING_REGION
3312 				BRegion clipRegion(fieldRect);
3313 				fDrawBufferView->PushState();
3314 				fDrawBufferView->ConstrainClippingRegion(&clipRegion);
3315 	#endif
3316 				fDrawBufferView->SetHighColor(fMasterView->Color(
3317 					row->fNextSelected ? B_COLOR_SELECTION_TEXT
3318 						: B_COLOR_TEXT));
3319 				float baseline = floor(fieldRect.top + fh.ascent
3320 					+ (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
3321 				fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
3322 				column->DrawField(field, fieldRect, fDrawBufferView);
3323 	#if CONSTRAIN_CLIPPING_REGION
3324 				fDrawBufferView->PopState();
3325 	#endif
3326 			}
3327 
3328 			if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3329 				&& Window()->IsActive()) {
3330 				fDrawBufferView->SetHighColor(fMasterView->Color(
3331 					B_COLOR_ROW_DIVIDER));
3332 				fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top,
3333 					10000.0, sourceRect.bottom));
3334 			}
3335 
3336 			fDrawBufferView->Sync();
3337 			fDrawBuffer->Unlock();
3338 			SetDrawingMode(B_OP_COPY);
3339 			DrawBitmap(fDrawBuffer, sourceRect, destRect);
3340 
3341 #else
3342 
3343 			SetHighColor(highColor);
3344 			SetLowColor(lowColor);
3345 			FillRect(destRect, B_SOLID_LOW);
3346 
3347 			BField* field = row->GetField(column->fFieldID);
3348 			if (field) {
3349 	#if CONSTRAIN_CLIPPING_REGION
3350 				BRegion clipRegion(destRect);
3351 				PushState();
3352 				ConstrainClippingRegion(&clipRegion);
3353 	#endif
3354 				SetHighColor(fMasterView->Color(row->fNextSelected
3355 					? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
3356 				float baseline = floor(destRect.top + fh.ascent
3357 					+ (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
3358 				MovePenTo(destRect.left + 8, baseline);
3359 				column->DrawField(field, destRect, this);
3360 	#if CONSTRAIN_CLIPPING_REGION
3361 				PopState();
3362 	#endif
3363 			}
3364 
3365 			if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3366 				&& Window()->IsActive()) {
3367 				SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3368 				StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
3369 			}
3370 #endif
3371 		}
3372 	}
3373 }
3374 
3375 
3376 void
3377 OutlineView::Draw(BRect invalidBounds)
3378 {
3379 #if SMART_REDRAW
3380 	BRegion invalidRegion;
3381 	GetClippingRegion(&invalidRegion);
3382 #endif
3383 
3384 	font_height fh;
3385 	GetFontHeight(&fh);
3386 
3387 	float line = 0.0;
3388 	bool tintedLine = true;
3389 	int32 numColumns = fColumns->CountItems();
3390 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3391 		iterator.GoToNext()) {
3392 		BRow* row = iterator.CurrentRow();
3393 		if (line > invalidBounds.bottom)
3394 			break;
3395 
3396 		tintedLine = !tintedLine;
3397 		float rowHeight = row->Height();
3398 
3399 		if (line >= invalidBounds.top - rowHeight) {
3400 			bool isFirstColumn = true;
3401 			float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
3402 
3403 			// setup background color
3404 			rgb_color lowColor;
3405 			if (row->fNextSelected != 0) {
3406 				if (Window()->IsActive()) {
3407 					if (fEditMode)
3408 						lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3409 					else
3410 						lowColor = fMasterView->Color(B_COLOR_SELECTION);
3411 				}
3412 				else
3413 					lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
3414 			} else
3415 				lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3416 			if (tintedLine)
3417 				lowColor = tint_color(lowColor, kTintedLineTint);
3418 
3419 			for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) {
3420 				BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
3421 				if (!column->IsVisible())
3422 					continue;
3423 
3424 				if (!isFirstColumn && fieldLeftEdge > invalidBounds.right)
3425 					break;
3426 
3427 				if (fieldLeftEdge + column->Width() >= invalidBounds.left) {
3428 					BRect fullRect(fieldLeftEdge, line,
3429 						fieldLeftEdge + column->Width(), line + rowHeight);
3430 
3431 					bool clippedFirstColumn = false;
3432 						// This happens when a column is indented past the
3433 						// beginning of the next column.
3434 
3435 					SetHighColor(lowColor);
3436 
3437 					BRect destRect(fullRect);
3438 					if (isFirstColumn) {
3439 						fullRect.left -= fMasterView->LatchWidth();
3440 						destRect.left += iterator.CurrentLevel()
3441 							* kOutlineLevelIndent;
3442 						if (destRect.left >= destRect.right) {
3443 							// clipped
3444 							FillRect(BRect(0, line, fieldLeftEdge
3445 								+ column->Width(), line + rowHeight));
3446 							clippedFirstColumn = true;
3447 						}
3448 
3449 						FillRect(BRect(0, line, MAX(kLeftMargin,
3450 							fMasterView->LatchWidth()), line + row->Height()));
3451 					}
3452 
3453 
3454 #if SMART_REDRAW
3455 					if (!clippedFirstColumn
3456 						&& invalidRegion.Intersects(fullRect)) {
3457 #else
3458 					if (!clippedFirstColumn) {
3459 #endif
3460 						FillRect(fullRect);	// Using color set above
3461 
3462 						// Draw the latch widget if it has one.
3463 						if (isFirstColumn) {
3464 							if (row == fTargetRow
3465 								&& fCurrentState == LATCH_CLICKED) {
3466 								// Note that this only occurs if the user is
3467 								// holding down a latch while items are added
3468 								// in the background.
3469 								BPoint pos;
3470 								uint32 buttons;
3471 								GetMouse(&pos, &buttons);
3472 								if (fLatchRect.Contains(pos)) {
3473 									fMasterView->DrawLatch(this, fLatchRect,
3474 										B_PRESSED_LATCH, fTargetRow);
3475 								} else {
3476 									fMasterView->DrawLatch(this, fLatchRect,
3477 										row->fIsExpanded ? B_OPEN_LATCH
3478 											: B_CLOSED_LATCH, fTargetRow);
3479 								}
3480 							} else {
3481 								LatchType pos = B_NO_LATCH;
3482 								if (row->HasLatch())
3483 									pos = row->fIsExpanded ? B_OPEN_LATCH
3484 										: B_CLOSED_LATCH;
3485 
3486 								fMasterView->DrawLatch(this,
3487 									BRect(destRect.left
3488 										- fMasterView->LatchWidth(),
3489 									destRect.top, destRect.left,
3490 									destRect.bottom), pos, row);
3491 							}
3492 						}
3493 
3494 						SetHighColor(fMasterView->HighColor());
3495 							// The master view just holds the high color for us.
3496 						SetLowColor(lowColor);
3497 
3498 						BField* field = row->GetField(column->fFieldID);
3499 						if (field) {
3500 #if CONSTRAIN_CLIPPING_REGION
3501 							BRegion clipRegion(destRect);
3502 							PushState();
3503 							ConstrainClippingRegion(&clipRegion);
3504 #endif
3505 							SetHighColor(fMasterView->Color(
3506 								row->fNextSelected ? B_COLOR_SELECTION_TEXT
3507 								: B_COLOR_TEXT));
3508 							float baseline = floor(destRect.top + fh.ascent
3509 								+ (destRect.Height() + 1
3510 								- (fh.ascent+fh.descent)) / 2);
3511 							MovePenTo(destRect.left + 8, baseline);
3512 							column->DrawField(field, destRect, this);
3513 #if CONSTRAIN_CLIPPING_REGION
3514 							PopState();
3515 #endif
3516 						}
3517 					}
3518 				}
3519 
3520 				isFirstColumn = false;
3521 				fieldLeftEdge += column->Width() + 1;
3522 			}
3523 
3524 			if (fieldLeftEdge <= invalidBounds.right) {
3525 				SetHighColor(lowColor);
3526 				FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
3527 					line + rowHeight));
3528 			}
3529 		}
3530 
3531 		// indicate the keyboard focus row
3532 		if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3533 			&& Window()->IsActive()) {
3534 			SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3535 			StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
3536 		}
3537 
3538 		line += rowHeight + 1;
3539 	}
3540 
3541 	if (line <= invalidBounds.bottom) {
3542 		// fill background below last item
3543 		SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3544 		FillRect(BRect(invalidBounds.left, line, invalidBounds.right,
3545 			invalidBounds.bottom));
3546 	}
3547 
3548 	// Draw the drop target line
3549 	if (fDropHighlightY != -1) {
3550 		InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3551 			1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3552 	}
3553 }
3554 
3555 
3556 BRow*
3557 OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top)
3558 {
3559 	if (_rowIndent && _top) {
3560 		float line = 0.0;
3561 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3562 			iterator.GoToNext()) {
3563 
3564 			BRow* row = iterator.CurrentRow();
3565 			if (line > ypos)
3566 				break;
3567 
3568 			float rowHeight = row->Height();
3569 			if (ypos <= line + rowHeight) {
3570 				*_top = line;
3571 				*_rowIndent = iterator.CurrentLevel();
3572 				return row;
3573 			}
3574 
3575 			line += rowHeight + 1;
3576 		}
3577 	}
3578 
3579 	return NULL;
3580 }
3581 
3582 void OutlineView::SetMouseTrackingEnabled(bool enabled)
3583 {
3584 	fTrackMouse = enabled;
3585 	if (!enabled && fDropHighlightY != -1) {
3586 		// Erase the old target line
3587 		InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3588 			1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3589 		fDropHighlightY = -1;
3590 	}
3591 }
3592 
3593 
3594 //
3595 // Note that this interaction is not totally safe.  If items are added to
3596 // the list in the background, the widget rect will be incorrect, possibly
3597 // resulting in drawing glitches.  The code that adds items needs to be a little smarter
3598 // about invalidating state.
3599 //
3600 void
3601 OutlineView::MouseDown(BPoint position)
3602 {
3603 	if (!fEditMode)
3604 		fMasterView->MakeFocus(true);
3605 
3606 	// Check to see if the user is clicking on a widget to open a section
3607 	// of the list.
3608 	bool reset_click_count = false;
3609 	int32 indent;
3610 	float rowTop;
3611 	BRow* row = FindRow(position.y, &indent, &rowTop);
3612 	if (row != NULL) {
3613 
3614 		// Update fCurrentField
3615 		bool handle_field = false;
3616 		BField* new_field = 0;
3617 		BRow* new_row = 0;
3618 		BColumn* new_column = 0;
3619 		BRect new_rect;
3620 
3621 		if (position.y >= 0) {
3622 			if (position.x >= 0) {
3623 				float x = 0;
3624 				for (int32 c = 0; c < fMasterView->CountColumns(); c++) {
3625 					new_column = fMasterView->ColumnAt(c);
3626 					if (!new_column->IsVisible())
3627 						continue;
3628 					if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x)
3629 						+ new_column->Width() >= position.x) {
3630 						if (new_column->WantsEvents()) {
3631 							new_field = row->GetField(c);
3632 							new_row = row;
3633 							FindRect(new_row,&new_rect);
3634 							new_rect.left = MAX(kLeftMargin,
3635 								fMasterView->LatchWidth()) + x;
3636 							new_rect.right = new_rect.left
3637 								+ new_column->Width() - 1;
3638 							handle_field = true;
3639 						}
3640 						break;
3641 					}
3642 					x += new_column->Width();
3643 				}
3644 			}
3645 		}
3646 
3647 		// Handle mouse down
3648 		if (handle_field) {
3649 			fMouseDown = true;
3650 			fFieldRect = new_rect;
3651 			fCurrentColumn = new_column;
3652 			fCurrentRow = new_row;
3653 			fCurrentField = new_field;
3654 			fCurrentCode = B_INSIDE_VIEW;
3655 			BMessage* message = Window()->CurrentMessage();
3656 			int32 buttons = 1;
3657 			message->FindInt32("buttons", &buttons);
3658 			fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
3659 				fCurrentField, fFieldRect, position, buttons);
3660 		}
3661 
3662 		if (!fEditMode) {
3663 
3664 			fTargetRow = row;
3665 			fTargetRowTop = rowTop;
3666 			FindVisibleRect(fFocusRow, &fFocusRowRect);
3667 
3668 			float leftWidgetBoundry = indent * kOutlineLevelIndent
3669 				+ MAX(kLeftMargin, fMasterView->LatchWidth())
3670 				- fMasterView->LatchWidth();
3671 			fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
3672 				+ fMasterView->LatchWidth(), rowTop + row->Height());
3673 			if (fLatchRect.Contains(position) && row->HasLatch()) {
3674 				fCurrentState = LATCH_CLICKED;
3675 				if (fTargetRow->fNextSelected != 0)
3676 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3677 				else
3678 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3679 
3680 				FillRect(fLatchRect);
3681 				if (fLatchRect.Contains(position)) {
3682 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3683 						row);
3684 				} else {
3685 					fMasterView->DrawLatch(this, fLatchRect,
3686 						fTargetRow->fIsExpanded ? B_OPEN_LATCH
3687 						: B_CLOSED_LATCH, row);
3688 				}
3689 			} else {
3690 				Invalidate(fFocusRowRect);
3691 				fFocusRow = fTargetRow;
3692 				FindVisibleRect(fFocusRow, &fFocusRowRect);
3693 
3694 				ASSERT(fTargetRow != 0);
3695 
3696 				if ((modifiers() & B_CONTROL_KEY) == 0)
3697 					DeselectAll();
3698 
3699 				if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
3700 					&& fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
3701 					SelectRange(fFirstSelectedItem, fTargetRow);
3702 				}
3703 				else {
3704 					if (fTargetRow->fNextSelected != 0) {
3705 						// Unselect row
3706 						fTargetRow->fNextSelected->fPrevSelected
3707 							= fTargetRow->fPrevSelected;
3708 						fTargetRow->fPrevSelected->fNextSelected
3709 							= fTargetRow->fNextSelected;
3710 						fTargetRow->fPrevSelected = 0;
3711 						fTargetRow->fNextSelected = 0;
3712 						fFirstSelectedItem = NULL;
3713 					} else {
3714 						// Select row
3715 						if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3716 							DeselectAll();
3717 
3718 						fTargetRow->fNextSelected
3719 							= fSelectionListDummyHead.fNextSelected;
3720 						fTargetRow->fPrevSelected
3721 							= &fSelectionListDummyHead;
3722 						fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
3723 						fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
3724 						fFirstSelectedItem = fTargetRow;
3725 					}
3726 
3727 					Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
3728 						fVisibleRect.right,
3729 						fTargetRowTop + fTargetRow->Height()));
3730 				}
3731 
3732 				fCurrentState = ROW_CLICKED;
3733 				if (fLastSelectedItem != fTargetRow)
3734 					reset_click_count = true;
3735 				fLastSelectedItem = fTargetRow;
3736 				fMasterView->SelectionChanged();
3737 
3738 			}
3739 		}
3740 
3741 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
3742 			B_NO_POINTER_HISTORY);
3743 
3744 	} else if (fFocusRow != 0) {
3745 		// User clicked in open space, unhighlight focus row.
3746 		FindVisibleRect(fFocusRow, &fFocusRowRect);
3747 		fFocusRow = 0;
3748 		Invalidate(fFocusRowRect);
3749 	}
3750 
3751 	// We stash the click counts here because the 'clicks' field
3752 	// is not in the CurrentMessage() when MouseUp is called... ;(
3753 	if (reset_click_count)
3754 		fClickCount = 1;
3755 	else
3756 		Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
3757 	fClickPoint = position;
3758 
3759 }
3760 
3761 
3762 void
3763 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
3764 	const BMessage* /*dragMessage*/)
3765 {
3766 	if (!fMouseDown) {
3767 		// Update fCurrentField
3768 		bool handle_field = false;
3769 		BField* new_field = 0;
3770 		BRow* new_row = 0;
3771 		BColumn* new_column = 0;
3772 		BRect new_rect(0,0,0,0);
3773 		if (position.y >=0 ) {
3774 			float top;
3775 			int32 indent;
3776 			BRow* row = FindRow(position.y, &indent, &top);
3777 			if (row && position.x >=0 ) {
3778 				float x=0;
3779 				for (int32 c=0;c<fMasterView->CountColumns();c++) {
3780 					new_column = fMasterView->ColumnAt(c);
3781 					if (!new_column->IsVisible())
3782 						continue;
3783 					if ((MAX(kLeftMargin,
3784 						fMasterView->LatchWidth()) + x) + new_column->Width()
3785 						> position.x) {
3786 
3787 						if(new_column->WantsEvents()) {
3788 							new_field = row->GetField(c);
3789 							new_row = row;
3790 							FindRect(new_row,&new_rect);
3791 							new_rect.left = MAX(kLeftMargin,
3792 								fMasterView->LatchWidth()) + x;
3793 							new_rect.right = new_rect.left
3794 								+ new_column->Width() - 1;
3795 							handle_field = true;
3796 						}
3797 						break;
3798 					}
3799 					x += new_column->Width();
3800 				}
3801 			}
3802 		}
3803 
3804 		// Handle mouse moved
3805 		if (handle_field) {
3806 			if (new_field != fCurrentField) {
3807 				if (fCurrentField) {
3808 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3809 						fCurrentField, fFieldRect, position, 0,
3810 						fCurrentCode = B_EXITED_VIEW);
3811 				}
3812 				fCurrentColumn = new_column;
3813 				fCurrentRow = new_row;
3814 				fCurrentField = new_field;
3815 				fFieldRect = new_rect;
3816 				if (fCurrentField) {
3817 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3818 						fCurrentField, fFieldRect, position, 0,
3819 						fCurrentCode = B_ENTERED_VIEW);
3820 				}
3821 			} else {
3822 				if (fCurrentField) {
3823 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3824 						fCurrentField, fFieldRect, position, 0,
3825 						fCurrentCode = B_INSIDE_VIEW);
3826 				}
3827 			}
3828 		} else {
3829 			if (fCurrentField) {
3830 				fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3831 					fCurrentField, fFieldRect, position, 0,
3832 					fCurrentCode = B_EXITED_VIEW);
3833 				fCurrentField = 0;
3834 				fCurrentColumn = 0;
3835 				fCurrentRow = 0;
3836 			}
3837 		}
3838 	} else {
3839 		if (fCurrentField) {
3840 			if (fFieldRect.Contains(position)) {
3841 				if (fCurrentCode == B_OUTSIDE_VIEW
3842 					|| fCurrentCode == B_EXITED_VIEW) {
3843 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3844 						fCurrentField, fFieldRect, position, 1,
3845 						fCurrentCode = B_ENTERED_VIEW);
3846 				} else {
3847 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3848 						fCurrentField, fFieldRect, position, 1,
3849 						fCurrentCode = B_INSIDE_VIEW);
3850 				}
3851 			} else {
3852 				if (fCurrentCode == B_INSIDE_VIEW
3853 					|| fCurrentCode == B_ENTERED_VIEW) {
3854 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3855 						fCurrentField, fFieldRect, position, 1,
3856 						fCurrentCode = B_EXITED_VIEW);
3857 				} else {
3858 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3859 						fCurrentField, fFieldRect, position, 1,
3860 						fCurrentCode = B_OUTSIDE_VIEW);
3861 				}
3862 			}
3863 		}
3864 	}
3865 
3866 	if (!fEditMode) {
3867 
3868 		switch (fCurrentState) {
3869 			case LATCH_CLICKED:
3870 				if (fTargetRow->fNextSelected != 0)
3871 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3872 				else
3873 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3874 
3875 				FillRect(fLatchRect);
3876 				if (fLatchRect.Contains(position)) {
3877 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3878 						fTargetRow);
3879 				} else {
3880 					fMasterView->DrawLatch(this, fLatchRect,
3881 						fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
3882 						fTargetRow);
3883 				}
3884 				break;
3885 
3886 			case ROW_CLICKED:
3887 				if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
3888 					|| abs((int)(position.y - fClickPoint.y))
3889 						> kRowDragSensitivity) {
3890 					fCurrentState = DRAGGING_ROWS;
3891 					fMasterView->InitiateDrag(fClickPoint,
3892 						fTargetRow->fNextSelected != 0);
3893 				}
3894 				break;
3895 
3896 			case DRAGGING_ROWS:
3897 #if 0
3898 				// falls through...
3899 #else
3900 				if (fTrackMouse /*&& message*/) {
3901 					if (fVisibleRect.Contains(position)) {
3902 						float top;
3903 						int32 indent;
3904 						BRow* target = FindRow(position.y, &indent, &top);
3905 						if (target)
3906 							SetFocusRow(target, true);
3907 					}
3908 				}
3909 				break;
3910 #endif
3911 
3912 			default: {
3913 
3914 				if (fTrackMouse /*&& message*/) {
3915 					// Draw a highlight line...
3916 					if (fVisibleRect.Contains(position)) {
3917 						float top;
3918 						int32 indent;
3919 						BRow* target = FindRow(position.y, &indent, &top);
3920 						if (target == fRollOverRow)
3921 							break;
3922 						if (fRollOverRow) {
3923 							BRect rect;
3924 							FindRect(fRollOverRow, &rect);
3925 							Invalidate(rect);
3926 						}
3927 						fRollOverRow = target;
3928 #if 0
3929 						SetFocusRow(fRollOverRow,false);
3930 #else
3931 						PushState();
3932 						SetDrawingMode(B_OP_BLEND);
3933 						SetHighColor(255, 255, 255, 255);
3934 						BRect rect;
3935 						FindRect(fRollOverRow, &rect);
3936 						rect.bottom -= 1.0;
3937 						FillRect(rect);
3938 						PopState();
3939 #endif
3940 					} else {
3941 						if (fRollOverRow) {
3942 							BRect rect;
3943 							FindRect(fRollOverRow, &rect);
3944 							Invalidate(rect);
3945 							fRollOverRow = NULL;
3946 						}
3947 					}
3948 				}
3949 			}
3950 		}
3951 	}
3952 }
3953 
3954 
3955 void
3956 OutlineView::MouseUp(BPoint position)
3957 {
3958 	if (fCurrentField) {
3959 		fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
3960 		fMouseDown = false;
3961 	}
3962 
3963 	if (fEditMode)
3964 		return;
3965 
3966 	switch (fCurrentState) {
3967 		case LATCH_CLICKED:
3968 			if (fLatchRect.Contains(position)) {
3969 				fMasterView->ExpandOrCollapse(fTargetRow,
3970 					!fTargetRow->fIsExpanded);
3971 			}
3972 
3973 			Invalidate(fLatchRect);
3974 			fCurrentState = INACTIVE;
3975 			break;
3976 
3977 		case ROW_CLICKED:
3978 			if (fClickCount > 1
3979 				&& abs((int)fClickPoint.x - (int)position.x)
3980 					< kDoubleClickMoveSensitivity
3981 				&& abs((int)fClickPoint.y - (int)position.y)
3982 					< kDoubleClickMoveSensitivity) {
3983 				fMasterView->ItemInvoked();
3984 			}
3985 			fCurrentState = INACTIVE;
3986 			break;
3987 
3988 		case DRAGGING_ROWS:
3989 			fCurrentState = INACTIVE;
3990 			// Falls through
3991 
3992 		default:
3993 			if (fDropHighlightY != -1) {
3994 				InvertRect(BRect(0,
3995 					fDropHighlightY - kDropHighlightLineHeight / 2,
3996 					1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3997 					// Erase the old target line
3998 				fDropHighlightY = -1;
3999 			}
4000 	}
4001 }
4002 
4003 
4004 void
4005 OutlineView::MessageReceived(BMessage* message)
4006 {
4007 	if (message->WasDropped()) {
4008 		fMasterView->MessageDropped(message,
4009 			ConvertFromScreen(message->DropPoint()));
4010 	} else {
4011 		BView::MessageReceived(message);
4012 	}
4013 }
4014 
4015 
4016 void
4017 OutlineView::ChangeFocusRow(bool up, bool updateSelection,
4018 	bool addToCurrentSelection)
4019 {
4020 	int32 indent;
4021 	float top;
4022 	float newRowPos = 0;
4023 	float verticalScroll = 0;
4024 
4025 	if (fFocusRow) {
4026 		// A row currently has the focus, get information about it
4027 		newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
4028 		if (newRowPos < fVisibleRect.top + 20)
4029 			verticalScroll = newRowPos - 20;
4030 		else if (newRowPos > fVisibleRect.bottom - 20)
4031 			verticalScroll = newRowPos - fVisibleRect.Height() + 20;
4032 	} else
4033 		newRowPos = fVisibleRect.top + 2;
4034 			// no row is currently focused, set this to the top of the window
4035 			// so we will select the first visible item in the list.
4036 
4037 	BRow* newRow = FindRow(newRowPos, &indent, &top);
4038 	if (newRow) {
4039 		if (fFocusRow) {
4040 			fFocusRowRect.right = 10000;
4041 			Invalidate(fFocusRowRect);
4042 		}
4043 		BRow* oldFocusRow = fFocusRow;
4044 		fFocusRow = newRow;
4045 		fFocusRowRect.top = top;
4046 		fFocusRowRect.left = 0;
4047 		fFocusRowRect.right = 10000;
4048 		fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
4049 		Invalidate(fFocusRowRect);
4050 
4051 		if (updateSelection) {
4052 			if (!addToCurrentSelection
4053 				|| fSelectionMode == B_SINGLE_SELECTION_LIST) {
4054 				DeselectAll();
4055 			}
4056 
4057 			// if the focus row isn't selected, add it to the selection
4058 			if (fFocusRow->fNextSelected == 0) {
4059 				fFocusRow->fNextSelected
4060 					= fSelectionListDummyHead.fNextSelected;
4061 				fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4062 				fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4063 				fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4064 			} else if (oldFocusRow != NULL
4065 				&& fSelectionListDummyHead.fNextSelected == oldFocusRow
4066 				&& (((IndexOf(oldFocusRow->fNextSelected)
4067 						< IndexOf(oldFocusRow)) == up)
4068 					|| fFocusRow == oldFocusRow->fNextSelected)) {
4069 					// if the focus row is selected, if:
4070 					// 1. the previous focus row is last in the selection
4071 					// 2a. the next selected row is now the focus row
4072 					// 2b. or the next selected row is beyond the focus row
4073 					//	   in the move direction
4074 					// then deselect the previous focus row
4075 				fSelectionListDummyHead.fNextSelected
4076 					= oldFocusRow->fNextSelected;
4077 				if (fSelectionListDummyHead.fNextSelected != NULL) {
4078 					fSelectionListDummyHead.fNextSelected->fPrevSelected
4079 						= &fSelectionListDummyHead;
4080 					oldFocusRow->fNextSelected = NULL;
4081 				}
4082 				oldFocusRow->fPrevSelected = NULL;
4083 			}
4084 
4085 			fLastSelectedItem = fFocusRow;
4086 		}
4087 	} else
4088 		Invalidate(fFocusRowRect);
4089 
4090 	if (verticalScroll != 0) {
4091 		BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4092 		float min, max;
4093 		vScrollBar->GetRange(&min, &max);
4094 		if (verticalScroll < min)
4095 			verticalScroll = min;
4096 		else if (verticalScroll > max)
4097 			verticalScroll = max;
4098 
4099 		vScrollBar->SetValue(verticalScroll);
4100 	}
4101 
4102 	if (newRow && updateSelection)
4103 		fMasterView->SelectionChanged();
4104 }
4105 
4106 
4107 void
4108 OutlineView::MoveFocusToVisibleRect()
4109 {
4110 	fFocusRow = 0;
4111 	ChangeFocusRow(true, true, false);
4112 }
4113 
4114 
4115 BRow*
4116 OutlineView::CurrentSelection(BRow* lastSelected) const
4117 {
4118 	BRow* row;
4119 	if (lastSelected == 0)
4120 		row = fSelectionListDummyHead.fNextSelected;
4121 	else
4122 		row = lastSelected->fNextSelected;
4123 
4124 
4125 	if (row == &fSelectionListDummyHead)
4126 		row = 0;
4127 
4128 	return row;
4129 }
4130 
4131 
4132 void
4133 OutlineView::ToggleFocusRowSelection(bool selectRange)
4134 {
4135 	if (fFocusRow == 0)
4136 		return;
4137 
4138 	if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
4139 		SelectRange(fLastSelectedItem, fFocusRow);
4140 	else {
4141 		if (fFocusRow->fNextSelected != 0) {
4142 			// Unselect row
4143 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
4144 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
4145 			fFocusRow->fPrevSelected = 0;
4146 			fFocusRow->fNextSelected = 0;
4147 		} else {
4148 			// Select row
4149 			if (fSelectionMode == B_SINGLE_SELECTION_LIST)
4150 				DeselectAll();
4151 
4152 			fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
4153 			fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4154 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4155 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4156 		}
4157 	}
4158 
4159 	fLastSelectedItem = fFocusRow;
4160 	fMasterView->SelectionChanged();
4161 	Invalidate(fFocusRowRect);
4162 }
4163 
4164 
4165 void
4166 OutlineView::ToggleFocusRowOpen()
4167 {
4168 	if (fFocusRow)
4169 		fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
4170 }
4171 
4172 
4173 void
4174 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
4175 {
4176 	// TODO: Could use CopyBits here to speed things up.
4177 
4178 	if (parentRow == NULL)
4179 		return;
4180 
4181 	if (parentRow->fIsExpanded == expand)
4182 		return;
4183 
4184 	parentRow->fIsExpanded = expand;
4185 
4186 	BRect parentRect;
4187 	if (FindRect(parentRow, &parentRect)) {
4188 		// Determine my new height
4189 		float subTreeHeight = 0.0;
4190 		if (parentRow->fIsExpanded)
4191 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4192 			     iterator.CurrentRow();
4193 			     iterator.GoToNext()
4194 			    )
4195 			{
4196 				subTreeHeight += iterator.CurrentRow()->Height()+1;
4197 			}
4198 		else
4199 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4200 			     iterator.CurrentRow();
4201 			     iterator.GoToNext()
4202 			    )
4203 			{
4204 				subTreeHeight -= iterator.CurrentRow()->Height()+1;
4205 			}
4206 		fItemsHeight += subTreeHeight;
4207 
4208 		// Adjust focus row if necessary.
4209 		if (FindRect(fFocusRow, &fFocusRowRect) == false) {
4210 			// focus row is in a subtree that has collapsed,
4211 			// move it up to the parent.
4212 			fFocusRow = parentRow;
4213 			FindRect(fFocusRow, &fFocusRowRect);
4214 		}
4215 
4216 		Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
4217 			fVisibleRect.bottom));
4218 		FixScrollBar(false);
4219 	}
4220 }
4221 
4222 void
4223 OutlineView::RemoveRow(BRow* row)
4224 {
4225 	if (row == NULL)
4226 		return;
4227 
4228 	BRow* parentRow = NULL;
4229 	bool parentIsVisible = false;
4230 	FindParent(row, &parentRow, &parentIsVisible);
4231 		// NOTE: This could be a root row without a parent, in which case
4232 		// it is always visible, though.
4233 
4234 	// Adjust height for the visible sub-tree that is going to be removed.
4235 	float subTreeHeight = 0.0f;
4236 	if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4237 		// The row itself is visible at least.
4238 		subTreeHeight = row->Height() + 1;
4239 		if (row->fIsExpanded) {
4240 			// Adjust for the height of visible sub-items as well.
4241 			// (By default, the iterator follows open branches only.)
4242 			for (RecursiveOutlineIterator iterator(row->fChildList);
4243 				iterator.CurrentRow(); iterator.GoToNext())
4244 				subTreeHeight += iterator.CurrentRow()->Height() + 1;
4245 		}
4246 		BRect invalid;
4247 		if (FindRect(row, &invalid)) {
4248 			invalid.bottom = Bounds().bottom;
4249 			if (invalid.IsValid())
4250 				Invalidate(invalid);
4251 		}
4252 	}
4253 
4254 	fItemsHeight -= subTreeHeight;
4255 
4256 	FixScrollBar(false);
4257 	int32 indent = 0;
4258 	float top = 0.0;
4259 	if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
4260 		// after removing this row, no rows are actually visible any more,
4261 		// force a scroll to make them visible again
4262 		if (fItemsHeight > fVisibleRect.Height())
4263 			ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
4264 		else
4265 			ScrollBy(0.0, -Bounds().top);
4266 	}
4267 	if (parentRow != NULL) {
4268 		parentRow->fChildList->RemoveItem(row);
4269 		if (parentRow->fChildList->CountItems() == 0) {
4270 			delete parentRow->fChildList;
4271 			parentRow->fChildList = 0;
4272 			// It was the last child row of the parent, which also means the
4273 			// latch disappears.
4274 			BRect parentRowRect;
4275 			if (parentIsVisible && FindRect(parentRow, &parentRowRect))
4276 				Invalidate(parentRowRect);
4277 		}
4278 	} else
4279 		fRows.RemoveItem(row);
4280 
4281 	// Adjust focus row if necessary.
4282 	if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
4283 		// focus row is in a subtree that is gone, move it up to the parent.
4284 		fFocusRow = parentRow;
4285 		if (fFocusRow)
4286 			FindRect(fFocusRow, &fFocusRowRect);
4287 	}
4288 
4289 	// Remove this from the selection if necessary
4290 	if (row->fNextSelected != 0) {
4291 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4292 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4293 		row->fPrevSelected = 0;
4294 		row->fNextSelected = 0;
4295 		fMasterView->SelectionChanged();
4296 	}
4297 
4298 	fCurrentColumn = 0;
4299 	fCurrentRow = 0;
4300 	fCurrentField = 0;
4301 }
4302 
4303 
4304 BRowContainer*
4305 OutlineView::RowList()
4306 {
4307 	return &fRows;
4308 }
4309 
4310 
4311 void
4312 OutlineView::UpdateRow(BRow* row)
4313 {
4314 	if (row) {
4315 		// Determine if this row has changed its sort order
4316 		BRow* parentRow = NULL;
4317 		bool parentIsVisible = false;
4318 		FindParent(row, &parentRow, &parentIsVisible);
4319 
4320 		BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
4321 
4322 		if(list) {
4323 			int32 rowIndex = list->IndexOf(row);
4324 			ASSERT(rowIndex >= 0);
4325 			ASSERT(list->ItemAt(rowIndex) == row);
4326 
4327 			bool rowMoved = false;
4328 			if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
4329 				rowMoved = true;
4330 
4331 			if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
4332 				row) < 0)
4333 				rowMoved = true;
4334 
4335 			if (rowMoved) {
4336 				// Sort location of this row has changed.
4337 				// Remove and re-add in the right spot
4338 				SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
4339 			} else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4340 				BRect invalidRect;
4341 				if (FindVisibleRect(row, &invalidRect))
4342 					Invalidate(invalidRect);
4343 			}
4344 		}
4345 	}
4346 }
4347 
4348 
4349 void
4350 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
4351 {
4352 	if (!row)
4353 		return;
4354 
4355 	row->fParent = parentRow;
4356 
4357 	if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) {
4358 		// Ignore index here.
4359 		if (parentRow) {
4360 			if (parentRow->fChildList == NULL)
4361 				parentRow->fChildList = new BRowContainer;
4362 
4363 			AddSorted(parentRow->fChildList, row);
4364 		} else
4365 			AddSorted(&fRows, row);
4366 	} else {
4367 		// Note, a -1 index implies add to end if sorting is not enabled
4368 		if (parentRow) {
4369 			if (parentRow->fChildList == 0)
4370 				parentRow->fChildList = new BRowContainer;
4371 
4372 			if (Index < 0 || Index > parentRow->fChildList->CountItems())
4373 				parentRow->fChildList->AddItem(row);
4374 			else
4375 				parentRow->fChildList->AddItem(row, Index);
4376 		} else {
4377 			if (Index < 0 || Index >= fRows.CountItems())
4378 				fRows.AddItem(row);
4379 			else
4380 				fRows.AddItem(row, Index);
4381 		}
4382 	}
4383 
4384 	if (parentRow == 0 || parentRow->fIsExpanded)
4385 		fItemsHeight += row->Height() + 1;
4386 
4387 	FixScrollBar(false);
4388 
4389 	BRect newRowRect;
4390 	const bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
4391 
4392 	if (newRowIsInOpenBranch) {
4393 		if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
4394 			// The focus row has moved.
4395 			Invalidate(fFocusRowRect);
4396 			FindRect(fFocusRow, &fFocusRowRect);
4397 			Invalidate(fFocusRowRect);
4398 		}
4399 
4400 		if (fCurrentState == INACTIVE) {
4401 			if (newRowRect.bottom < fVisibleRect.top) {
4402 				// The new row is totally above the current viewport, move
4403 				// everything down and redraw the first line.
4404 				BRect source(fVisibleRect);
4405 				BRect dest(fVisibleRect);
4406 				source.bottom -= row->Height() + 1;
4407 				dest.top += row->Height() + 1;
4408 				CopyBits(source, dest);
4409 				Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
4410 					fVisibleRect.top + newRowRect.Height()));
4411 			} else if (newRowRect.top < fVisibleRect.bottom) {
4412 				// New item is somewhere in the current region.  Scroll everything
4413 				// beneath it down and invalidate just the new row rect.
4414 				BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
4415 					fVisibleRect.bottom - newRowRect.Height());
4416 				BRect dest(source);
4417 				dest.OffsetBy(0, newRowRect.Height() + 1);
4418 				CopyBits(source, dest);
4419 				Invalidate(newRowRect);
4420 			} // otherwise, this is below the currently visible region
4421 		} else {
4422 			// Adding the item may have caused the item that the user is currently
4423 			// selected to move.  This would cause annoying drawing and interaction
4424 			// bugs, as the position of that item is cached.  If this happens, resize
4425 			// the scroll bar, then scroll back so the selected item is in view.
4426 			BRect targetRect;
4427 			if (FindRect(fTargetRow, &targetRect)) {
4428 				float delta = targetRect.top - fTargetRowTop;
4429 				if (delta != 0) {
4430 					// This causes a jump because ScrollBy will copy a chunk of the view.
4431 					// Since the actual contents of the view have been offset, we don't
4432 					// want this, we just want to change the virtual origin of the window.
4433 					// Constrain the clipping region so everything is clipped out so no
4434 					// copy occurs.
4435 					//
4436 					//	xxx this currently doesn't work if the scroll bars aren't enabled.
4437 					//  everything will still move anyway.  A minor annoyance.
4438 					BRegion emptyRegion;
4439 					ConstrainClippingRegion(&emptyRegion);
4440 					PushState();
4441 					ScrollBy(0, delta);
4442 					PopState();
4443 					ConstrainClippingRegion(NULL);
4444 
4445 					fTargetRowTop += delta;
4446 					fClickPoint.y += delta;
4447 					fLatchRect.OffsetBy(0, delta);
4448 				}
4449 			}
4450 		}
4451 	}
4452 
4453 	// If the parent was previously childless, it will need to have a latch
4454 	// drawn.
4455 	BRect parentRect;
4456 	if (parentRow && parentRow->fChildList->CountItems() == 1
4457 		&& FindVisibleRect(parentRow, &parentRect))
4458 		Invalidate(parentRect);
4459 }
4460 
4461 
4462 void
4463 OutlineView::FixScrollBar(bool scrollToFit)
4464 {
4465 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4466 	if (vScrollBar) {
4467 		if (fItemsHeight > fVisibleRect.Height()) {
4468 			float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4469 			vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
4470 
4471 			// If the user is scrolled down too far when making the range smaller, the list
4472 			// will jump suddenly, which is undesirable.  In this case, don't fix the scroll
4473 			// bar here. In ScrollTo, it checks to see if this has occured, and will
4474 			// fix the scroll bars sneakily if the user has scrolled up far enough.
4475 			if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
4476 				vScrollBar->SetRange(0.0, maxScrollBarValue);
4477 				vScrollBar->SetSteps(20.0, fVisibleRect.Height());
4478 			}
4479 		} else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0)
4480 			vScrollBar->SetRange(0.0, 0.0);		// disable scroll bar.
4481 	}
4482 }
4483 
4484 
4485 void
4486 OutlineView::AddSorted(BRowContainer* list, BRow* row)
4487 {
4488 	if (list && row) {
4489 		// Find general vicinity with binary search.
4490 		int32 lower = 0;
4491 		int32 upper = list->CountItems()-1;
4492 		while( lower < upper ) {
4493 			int32 middle = lower + (upper-lower+1)/2;
4494 			int32 cmp = CompareRows(row, list->ItemAt(middle));
4495 			if( cmp < 0 ) upper = middle-1;
4496 			else if( cmp > 0 ) lower = middle+1;
4497 			else lower = upper = middle;
4498 		}
4499 
4500 		// At this point, 'upper' and 'lower' at the last found item.
4501 		// Arbitrarily use 'upper' and determine the final insertion
4502 		// point -- either before or after this item.
4503 		if( upper < 0 ) upper = 0;
4504 		else if( upper < list->CountItems() ) {
4505 			if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
4506 		}
4507 
4508 		if (upper >= list->CountItems())
4509 			list->AddItem(row);				// Adding to end.
4510 		else
4511 			list->AddItem(row, upper);		// Insert
4512 	}
4513 }
4514 
4515 
4516 int32
4517 OutlineView::CompareRows(BRow* row1, BRow* row2)
4518 {
4519 	int32 itemCount (fSortColumns->CountItems());
4520 	if (row1 && row2) {
4521 		for (int32 index = 0; index < itemCount; index++) {
4522 			BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
4523 			int comp = 0;
4524 			BField* field1 = (BField*) row1->GetField(column->fFieldID);
4525 			BField* field2 = (BField*) row2->GetField(column->fFieldID);
4526 			if (field1 && field2)
4527 				comp = column->CompareFields(field1, field2);
4528 
4529 			if (!column->fSortAscending)
4530 				comp = -comp;
4531 
4532 			if (comp != 0)
4533 				return comp;
4534 		}
4535 	}
4536 	return 0;
4537 }
4538 
4539 
4540 void
4541 OutlineView::FrameResized(float width, float height)
4542 {
4543 	fVisibleRect.right = fVisibleRect.left + width;
4544 	fVisibleRect.bottom = fVisibleRect.top + height;
4545 	FixScrollBar(true);
4546 	_inherited::FrameResized(width, height);
4547 }
4548 
4549 
4550 void
4551 OutlineView::ScrollTo(BPoint position)
4552 {
4553 	fVisibleRect.OffsetTo(position.x, position.y);
4554 
4555 	// In FixScrollBar, we might not have been able to change the size of
4556 	// the scroll bar because the user was scrolled down too far.  Take
4557 	// this opportunity to sneak it in if we can.
4558 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4559 	float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4560 	float min, max;
4561 	vScrollBar->GetRange(&min, &max);
4562 	if (max != maxScrollBarValue && position.y > maxScrollBarValue)
4563 		FixScrollBar(true);
4564 
4565 	_inherited::ScrollTo(position);
4566 }
4567 
4568 
4569 const BRect&
4570 OutlineView::VisibleRect() const
4571 {
4572 	return fVisibleRect;
4573 }
4574 
4575 
4576 bool
4577 OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
4578 {
4579 	if (row && _rect) {
4580 		float line = 0.0;
4581 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4582 			iterator.GoToNext()) {
4583 
4584 			if (iterator.CurrentRow() == row) {
4585 				_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4586 					line + row->Height());
4587 				return line <= fVisibleRect.bottom;
4588 			}
4589 
4590 			line += iterator.CurrentRow()->Height() + 1;
4591 		}
4592 	}
4593 	return false;
4594 }
4595 
4596 
4597 bool
4598 OutlineView::FindRect(const BRow* row, BRect* _rect)
4599 {
4600 	float line = 0.0;
4601 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4602 		iterator.GoToNext()) {
4603 		if (iterator.CurrentRow() == row) {
4604 			_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4605 				line + row->Height());
4606 			return true;
4607 		}
4608 
4609 		line += iterator.CurrentRow()->Height() + 1;
4610 	}
4611 
4612 	return false;
4613 }
4614 
4615 
4616 void
4617 OutlineView::ScrollTo(const BRow* row)
4618 {
4619 	BRect rect;
4620 	if (FindRect(row, &rect)) {
4621 		BRect bounds = Bounds();
4622 		if (rect.top < bounds.top)
4623 			ScrollTo(BPoint(bounds.left, rect.top));
4624 		else if (rect.bottom > bounds.bottom)
4625 			ScrollBy(0, rect.bottom - bounds.bottom);
4626 	}
4627 }
4628 
4629 
4630 void
4631 OutlineView::DeselectAll()
4632 {
4633 	// Invalidate all selected rows
4634 	float line = 0.0;
4635 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4636 		iterator.GoToNext()) {
4637 		if (line > fVisibleRect.bottom)
4638 			break;
4639 
4640 		BRow* row = iterator.CurrentRow();
4641 		if (line + row->Height() > fVisibleRect.top) {
4642 			if (row->fNextSelected != 0)
4643 				Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
4644 					line + row->Height()));
4645 		}
4646 
4647 		line += row->Height() + 1;
4648 	}
4649 
4650 	// Set items not selected
4651 	while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
4652 		BRow* row = fSelectionListDummyHead.fNextSelected;
4653 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4654 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4655 		row->fNextSelected = 0;
4656 		row->fPrevSelected = 0;
4657 	}
4658 }
4659 
4660 
4661 BRow*
4662 OutlineView::FocusRow() const
4663 {
4664 	return fFocusRow;
4665 }
4666 
4667 
4668 void
4669 OutlineView::SetFocusRow(BRow* row, bool Select)
4670 {
4671 	if (row) {
4672 		if (Select)
4673 			AddToSelection(row);
4674 
4675 		if (fFocusRow == row)
4676 			return;
4677 
4678 		Invalidate(fFocusRowRect); // invalidate previous
4679 
4680 		fTargetRow = fFocusRow = row;
4681 
4682 		FindVisibleRect(fFocusRow, &fFocusRowRect);
4683 		Invalidate(fFocusRowRect); // invalidate current
4684 
4685 		fFocusRowRect.right = 10000;
4686 		fMasterView->SelectionChanged();
4687 	}
4688 }
4689 
4690 
4691 bool
4692 OutlineView::SortList(BRowContainer* list, bool isVisible)
4693 {
4694 	if (list) {
4695 		// Shellsort
4696 		BRow** items
4697 			= (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
4698 		int32 numItems = list->CountItems();
4699 		int h;
4700 		for (h = 1; h < numItems / 9; h = 3 * h + 1)
4701 			;
4702 
4703 		for (;h > 0; h /= 3) {
4704 			for (int step = h; step < numItems; step++) {
4705 				BRow* temp = items[step];
4706 				int i;
4707 				for (i = step - h; i >= 0; i -= h) {
4708 					if (CompareRows(temp, items[i]) < 0)
4709 						items[i + h] = items[i];
4710 					else
4711 						break;
4712 				}
4713 
4714 				items[i + h] = temp;
4715 			}
4716 		}
4717 
4718 		if (isVisible) {
4719 			Invalidate();
4720 
4721 			InvalidateCachedPositions();
4722 			int lockCount = Window()->CountLocks();
4723 			for (int i = 0; i < lockCount; i++)
4724 				Window()->Unlock();
4725 
4726 			while (lockCount--)
4727 				if (!Window()->Lock())
4728 					return false;	// Window is gone...
4729 		}
4730 	}
4731 	return true;
4732 }
4733 
4734 
4735 int32
4736 OutlineView::DeepSortThreadEntry(void* _outlineView)
4737 {
4738 	((OutlineView*) _outlineView)->DeepSort();
4739 	return 0;
4740 }
4741 
4742 
4743 void
4744 OutlineView::DeepSort()
4745 {
4746 	struct stack_entry {
4747 		bool isVisible;
4748 		BRowContainer* list;
4749 		int32 listIndex;
4750 	} stack[kMaxDepth];
4751 	int32 stackTop = 0;
4752 
4753 	stack[stackTop].list = &fRows;
4754 	stack[stackTop].isVisible = true;
4755 	stack[stackTop].listIndex = 0;
4756 	fNumSorted = 0;
4757 
4758 	if (Window()->Lock() == false)
4759 		return;
4760 
4761 	bool doneSorting = false;
4762 	while (!doneSorting && !fSortCancelled) {
4763 
4764 		stack_entry* currentEntry = &stack[stackTop];
4765 
4766 		// xxx Can make the invalidate area smaller by finding the rect for the
4767 		// parent item and using that as the top of the invalid rect.
4768 
4769 		bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
4770 		if (!haveLock)
4771 			return ;	// window is gone.
4772 
4773 		// Fix focus rect.
4774 		InvalidateCachedPositions();
4775 		if (fCurrentState != INACTIVE)
4776 			fCurrentState = INACTIVE;	// sorry...
4777 
4778 		// next list.
4779 		bool foundNextList = false;
4780 		while (!foundNextList && !fSortCancelled) {
4781 			for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
4782 				index++) {
4783 				BRow* parentRow = currentEntry->list->ItemAt(index);
4784 				BRowContainer* childList = parentRow->fChildList;
4785 				if (childList != 0) {
4786 					currentEntry->listIndex = index + 1;
4787 					stackTop++;
4788 					ASSERT(stackTop < kMaxDepth);
4789 					stack[stackTop].listIndex = 0;
4790 					stack[stackTop].list = childList;
4791 					stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
4792 					foundNextList = true;
4793 					break;
4794 				}
4795 			}
4796 
4797 			if (!foundNextList) {
4798 				// back up
4799 				if (--stackTop < 0) {
4800 					doneSorting = true;
4801 					break;
4802 				}
4803 
4804 				currentEntry = &stack[stackTop];
4805 			}
4806 		}
4807 	}
4808 
4809 	Window()->Unlock();
4810 }
4811 
4812 
4813 void
4814 OutlineView::StartSorting()
4815 {
4816 	// If this view is not yet attached to a window, don't start a sort thread!
4817 	if (Window() == NULL)
4818 		return;
4819 
4820 	if (fSortThread != B_BAD_THREAD_ID) {
4821 		thread_info tinfo;
4822 		if (get_thread_info(fSortThread, &tinfo) == B_OK) {
4823 			// Unlock window so this won't deadlock (sort thread is probably
4824 			// waiting to lock window).
4825 
4826 			int lockCount = Window()->CountLocks();
4827 			for (int i = 0; i < lockCount; i++)
4828 				Window()->Unlock();
4829 
4830 			fSortCancelled = true;
4831 			int32 status;
4832 			wait_for_thread(fSortThread, &status);
4833 
4834 			while (lockCount--)
4835 				if (!Window()->Lock())
4836 					return ;	// Window is gone...
4837 		}
4838 	}
4839 
4840 	fSortCancelled = false;
4841 	fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
4842 	resume_thread(fSortThread);
4843 }
4844 
4845 
4846 void
4847 OutlineView::SelectRange(BRow* start, BRow* end)
4848 {
4849 	if (!start || !end)
4850 		return;
4851 
4852 	if (start == end)	// start is always selected when this is called
4853 		return;
4854 
4855 	RecursiveOutlineIterator iterator(&fRows, false);
4856 	while (iterator.CurrentRow() != 0) {
4857 		if (iterator.CurrentRow() == end) {
4858 			// reverse selection, swap to fix special case
4859 			BRow* temp = start;
4860 			start = end;
4861 			end = temp;
4862 			break;
4863 		} else if (iterator.CurrentRow() == start)
4864 			break;
4865 
4866 		iterator.GoToNext();
4867 	}
4868 
4869 	while (true) {
4870 		BRow* row = iterator.CurrentRow();
4871 		if (row) {
4872 			if (row->fNextSelected == 0) {
4873 				row->fNextSelected = fSelectionListDummyHead.fNextSelected;
4874 				row->fPrevSelected = &fSelectionListDummyHead;
4875 				row->fNextSelected->fPrevSelected = row;
4876 				row->fPrevSelected->fNextSelected = row;
4877 			}
4878 		} else
4879 			break;
4880 
4881 		if (row == end)
4882 			break;
4883 
4884 		iterator.GoToNext();
4885 	}
4886 
4887 	Invalidate();  // xxx make invalidation smaller
4888 }
4889 
4890 
4891 bool
4892 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
4893 {
4894 	bool result = false;
4895 	if (row != NULL && outParent != NULL) {
4896 		*outParent = row->fParent;
4897 
4898 		if (outParentIsVisible != NULL) {
4899 			// Walk up the parent chain to determine if this row is visible
4900 			*outParentIsVisible = true;
4901 			for (BRow* currentRow = row->fParent; currentRow != NULL;
4902 				currentRow = currentRow->fParent) {
4903 				if (!currentRow->fIsExpanded) {
4904 					*outParentIsVisible = false;
4905 					break;
4906 				}
4907 			}
4908 		}
4909 
4910 		result = *outParent != NULL;
4911 	}
4912 
4913 	return result;
4914 }
4915 
4916 
4917 int32
4918 OutlineView::IndexOf(BRow* row)
4919 {
4920 	if (row) {
4921 		if (row->fParent == 0)
4922 			return fRows.IndexOf(row);
4923 
4924 		ASSERT(row->fParent->fChildList);
4925 		return row->fParent->fChildList->IndexOf(row);
4926 	}
4927 
4928 	return B_ERROR;
4929 }
4930 
4931 
4932 void
4933 OutlineView::InvalidateCachedPositions()
4934 {
4935 	if (fFocusRow)
4936 		FindRect(fFocusRow, &fFocusRowRect);
4937 }
4938 
4939 
4940 float
4941 OutlineView::GetColumnPreferredWidth(BColumn* column)
4942 {
4943 	float preferred = 0.0;
4944 	for (RecursiveOutlineIterator iterator(&fRows); BRow* row =
4945 		iterator.CurrentRow(); iterator.GoToNext()) {
4946 		BField* field = row->GetField(column->fFieldID);
4947 		if (field) {
4948 			float width = column->GetPreferredWidth(field, this)
4949 				+ iterator.CurrentLevel() * kOutlineLevelIndent;
4950 			preferred = max_c(preferred, width);
4951 		}
4952 	}
4953 
4954 	BString name;
4955 	column->GetColumnName(&name);
4956 	preferred = max_c(preferred, StringWidth(name));
4957 
4958 	// Constrain to preferred width. This makes the method do a little
4959 	// more than asked, but it's for convenience.
4960 	if (preferred < column->MinWidth())
4961 		preferred = column->MinWidth();
4962 	else if (preferred > column->MaxWidth())
4963 		preferred = column->MaxWidth();
4964 
4965 	return preferred;
4966 }
4967 
4968 
4969 // #pragma mark -
4970 
4971 
4972 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
4973 	bool openBranchesOnly)
4974 	:
4975 	fStackIndex(0),
4976 	fCurrentListIndex(0),
4977 	fCurrentListDepth(0),
4978 	fOpenBranchesOnly(openBranchesOnly)
4979 {
4980 	if (list == 0 || list->CountItems() == 0)
4981 		fCurrentList = 0;
4982 	else
4983 		fCurrentList = list;
4984 }
4985 
4986 
4987 BRow*
4988 RecursiveOutlineIterator::CurrentRow() const
4989 {
4990 	if (fCurrentList == 0)
4991 		return 0;
4992 
4993 	return fCurrentList->ItemAt(fCurrentListIndex);
4994 }
4995 
4996 
4997 void
4998 RecursiveOutlineIterator::GoToNext()
4999 {
5000 	if (fCurrentList == 0)
5001 		return;
5002 	if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
5003 		fCurrentList = 0;
5004 		return;
5005 	}
5006 
5007 	BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
5008 	if(currentRow) {
5009 		if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
5010 			&& currentRow->fChildList->CountItems() > 0) {
5011 			// Visit child.
5012 			// Put current list on the stack if it needs to be revisited.
5013 			if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
5014 				fStack[fStackIndex].fRowSet = fCurrentList;
5015 				fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
5016 				fStack[fStackIndex].fDepth = fCurrentListDepth;
5017 				fStackIndex++;
5018 			}
5019 
5020 			fCurrentList = currentRow->fChildList;
5021 			fCurrentListIndex = 0;
5022 			fCurrentListDepth++;
5023 		} else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
5024 			fCurrentListIndex++; // next item in current list
5025 		else if (--fStackIndex >= 0) {
5026 			fCurrentList = fStack[fStackIndex].fRowSet;
5027 			fCurrentListIndex = fStack[fStackIndex].fIndex;
5028 			fCurrentListDepth = fStack[fStackIndex].fDepth;
5029 		} else
5030 			fCurrentList = 0;
5031 	}
5032 }
5033 
5034 
5035 int32
5036 RecursiveOutlineIterator::CurrentLevel() const
5037 {
5038 	return fCurrentListDepth;
5039 }
5040 
5041 
5042