xref: /haiku/src/kits/interface/ColumnListView.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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 c = bytes[0];
1690 	switch (c) {
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 (c == B_LEFT_ARROW)
1703 					newVal -= smallStep;
1704 				else if (c == 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 expanded = focusRow->IsExpanded();
1719 				if ((c == B_RIGHT_ARROW && !expanded)
1720 					|| (c == B_LEFT_ARROW && expanded)) {
1721 					fOutlineView->ToggleFocusRowOpen();
1722 				}
1723 			}
1724 			break;
1725 		}
1726 
1727 		case B_DOWN_ARROW:
1728 			fOutlineView->ChangeFocusRow(false,
1729 				(modifiers() & B_CONTROL_KEY) == 0,
1730 				(modifiers() & B_SHIFT_KEY) != 0);
1731 			break;
1732 
1733 		case B_UP_ARROW:
1734 			fOutlineView->ChangeFocusRow(true,
1735 				(modifiers() & B_CONTROL_KEY) == 0,
1736 				(modifiers() & B_SHIFT_KEY) != 0);
1737 			break;
1738 
1739 		case B_PAGE_UP:
1740 		case B_PAGE_DOWN:
1741 		{
1742 			float minValue, maxValue;
1743 			fVerticalScrollBar->GetRange(&minValue, &maxValue);
1744 			float smallStep, largeStep;
1745 			fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
1746 			float currentValue = fVerticalScrollBar->Value();
1747 			float newValue = currentValue;
1748 
1749 			if (c == B_PAGE_UP)
1750 				newValue -= largeStep;
1751 			else
1752 				newValue += largeStep;
1753 
1754 			if (newValue > maxValue)
1755 				newValue = maxValue;
1756 			else if (newValue < minValue)
1757 				newValue = minValue;
1758 
1759 			fVerticalScrollBar->SetValue(newValue);
1760 
1761 			// Option + pgup or pgdn scrolls and changes the selection.
1762 			if (modifiers() & B_OPTION_KEY)
1763 				fOutlineView->MoveFocusToVisibleRect();
1764 
1765 			break;
1766 		}
1767 
1768 		case B_ENTER:
1769 			Invoke();
1770 			break;
1771 
1772 		case B_SPACE:
1773 			fOutlineView->ToggleFocusRowSelection(
1774 				(modifiers() & B_SHIFT_KEY) != 0);
1775 			break;
1776 
1777 		case '+':
1778 			fOutlineView->ToggleFocusRowOpen();
1779 			break;
1780 
1781 		default:
1782 			BView::KeyDown(bytes, numBytes);
1783 	}
1784 }
1785 
1786 
1787 void
1788 BColumnListView::AttachedToWindow()
1789 {
1790 	if (!Messenger().IsValid())
1791 		SetTarget(Window());
1792 
1793 	if (SortingEnabled()) fOutlineView->StartSorting();
1794 }
1795 
1796 
1797 void
1798 BColumnListView::WindowActivated(bool active)
1799 {
1800 	fOutlineView->Invalidate();
1801 		// focus and selection appearance changes with focus
1802 
1803 	Invalidate();
1804 		// redraw focus marks around view
1805 	BView::WindowActivated(active);
1806 }
1807 
1808 
1809 void
1810 BColumnListView::Draw(BRect updateRect)
1811 {
1812 	BRect rect = Bounds();
1813 
1814 	uint32 flags = 0;
1815 	if (IsFocus() && Window()->IsActive())
1816 		flags |= BControlLook::B_FOCUSED;
1817 
1818 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1819 
1820 	BRect verticalScrollBarFrame;
1821 	if (!fVerticalScrollBar->IsHidden())
1822 		verticalScrollBarFrame = fVerticalScrollBar->Frame();
1823 
1824 	BRect horizontalScrollBarFrame;
1825 	if (!fHorizontalScrollBar->IsHidden())
1826 		horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
1827 
1828 	if (fBorderStyle == B_NO_BORDER) {
1829 		// We still draw the left/top border, but not focused.
1830 		// The scrollbars cannot be displayed without frame and
1831 		// it looks bad to have no frame only along the left/top
1832 		// side.
1833 		rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
1834 		SetHighColor(borderColor);
1835 		StrokeLine(BPoint(rect.left, rect.bottom),
1836 			BPoint(rect.left, rect.top));
1837 		StrokeLine(BPoint(rect.left + 1, rect.top),
1838 			BPoint(rect.right, rect.top));
1839 	}
1840 
1841 	be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1842 		verticalScrollBarFrame, horizontalScrollBarFrame,
1843 		base, fBorderStyle, flags);
1844 
1845 	if (fStatusView != NULL) {
1846 		rect = Bounds();
1847 		BRegion region(rect & fStatusView->Frame().InsetByCopy(-2, -2));
1848 		ConstrainClippingRegion(&region);
1849 		rect.bottom = fStatusView->Frame().top - 1;
1850 		be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1851 			BRect(), BRect(), base, fBorderStyle, flags);
1852 	}
1853 }
1854 
1855 
1856 void
1857 BColumnListView::SaveState(BMessage* message)
1858 {
1859 	message->MakeEmpty();
1860 
1861 	for (int32 i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1862 		message->AddInt32("ID", column->fFieldID);
1863 		message->AddFloat("width", column->fWidth);
1864 		message->AddBool("visible", column->fVisible);
1865 	}
1866 
1867 	message->AddBool("sortingenabled", fSortingEnabled);
1868 
1869 	if (fSortingEnabled) {
1870 		for (int32 i = 0; BColumn* column = (BColumn*)fSortColumns.ItemAt(i);
1871 				i++) {
1872 			message->AddInt32("sortID", column->fFieldID);
1873 			message->AddBool("sortascending", column->fSortAscending);
1874 		}
1875 	}
1876 }
1877 
1878 
1879 void
1880 BColumnListView::LoadState(BMessage* message)
1881 {
1882 	int32 id;
1883 	for (int i = 0; message->FindInt32("ID", i, &id) == B_OK; i++) {
1884 		for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
1885 			if (column->fFieldID == id) {
1886 				// move this column to position 'i' and set its attributes
1887 				MoveColumn(column, i);
1888 				float width;
1889 				if (message->FindFloat("width", i, &width) == B_OK)
1890 					column->SetWidth(width);
1891 				bool visible;
1892 				if (message->FindBool("visible", i, &visible) == B_OK)
1893 					column->SetVisible(visible);
1894 			}
1895 		}
1896 	}
1897 	bool b;
1898 	if (message->FindBool("sortingenabled", &b) == B_OK) {
1899 		SetSortingEnabled(b);
1900 		for (int k = 0; message->FindInt32("sortID", k, &id) == B_OK; k++) {
1901 			for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
1902 					j++) {
1903 				if (column->fFieldID == id) {
1904 					// add this column to the sort list
1905 					bool value;
1906 					if (message->FindBool("sortascending", k, &value) == B_OK)
1907 						SetSortColumn(column, true, value);
1908 				}
1909 			}
1910 		}
1911 	}
1912 }
1913 
1914 
1915 void
1916 BColumnListView::SetEditMode(bool state)
1917 {
1918 	fOutlineView->SetEditMode(state);
1919 	fTitleView->SetEditMode(state);
1920 }
1921 
1922 
1923 void
1924 BColumnListView::Refresh()
1925 {
1926 	if (LockLooper()) {
1927 		Invalidate();
1928 		fOutlineView->FixScrollBar (true);
1929 		fOutlineView->Invalidate();
1930 		Window()->UpdateIfNeeded();
1931 		UnlockLooper();
1932 	}
1933 }
1934 
1935 
1936 BSize
1937 BColumnListView::MinSize()
1938 {
1939 	BSize size;
1940 	size.width = 100;
1941 	size.height = std::max(kMinTitleHeight,
1942 		ceilf(be_plain_font->Size() * kTitleSpacing))
1943 		+ 4 * B_H_SCROLL_BAR_HEIGHT;
1944 	if (!fHorizontalScrollBar->IsHidden())
1945 		size.height += fHorizontalScrollBar->Frame().Height() + 1;
1946 	// TODO: Take border size into account
1947 
1948 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1949 }
1950 
1951 
1952 BSize
1953 BColumnListView::PreferredSize()
1954 {
1955 	BSize size = MinSize();
1956 	size.height += ceilf(be_plain_font->Size()) * 20;
1957 
1958 	// return MinSize().width if there are no columns.
1959 	int32 count = CountColumns();
1960 	if (count > 0) {
1961 		BRect titleRect;
1962 		BRect outlineRect;
1963 		BRect vScrollBarRect;
1964 		BRect hScrollBarRect;
1965 		_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1966 			hScrollBarRect);
1967 		// Start with the extra width for border and scrollbars etc.
1968 		size.width = titleRect.left - Bounds().left;
1969 		size.width += Bounds().right - titleRect.right;
1970 
1971 		// If we want all columns to be visible at their current width,
1972 		// we also need to add the extra margin width that the TitleView
1973 		// uses to compute its _VirtualWidth() for the horizontal scroll bar.
1974 		size.width += fTitleView->MarginWidth();
1975 		for (int32 i = 0; i < count; i++) {
1976 			BColumn* column = ColumnAt(i);
1977 			if (column != NULL)
1978 				size.width += column->Width();
1979 		}
1980 	}
1981 
1982 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1983 }
1984 
1985 
1986 BSize
1987 BColumnListView::MaxSize()
1988 {
1989 	BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
1990 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1991 }
1992 
1993 
1994 void
1995 BColumnListView::LayoutInvalidated(bool descendants)
1996 {
1997 }
1998 
1999 
2000 void
2001 BColumnListView::DoLayout()
2002 {
2003 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
2004 		return;
2005 
2006 	BRect titleRect;
2007 	BRect outlineRect;
2008 	BRect vScrollBarRect;
2009 	BRect hScrollBarRect;
2010 	_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
2011 		hScrollBarRect);
2012 
2013 	fTitleView->MoveTo(titleRect.LeftTop());
2014 	fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
2015 
2016 	fOutlineView->MoveTo(outlineRect.LeftTop());
2017 	fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
2018 
2019 	fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
2020 	fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
2021 		vScrollBarRect.Height());
2022 
2023 	if (fStatusView != NULL) {
2024 		BSize size = fStatusView->MinSize();
2025 		if (size.height > B_H_SCROLL_BAR_HEIGHT)
2026 			size.height = B_H_SCROLL_BAR_HEIGHT;
2027 		if (size.width > Bounds().Width() / 2)
2028 			size.width = floorf(Bounds().Width() / 2);
2029 
2030 		BPoint offset(hScrollBarRect.LeftTop());
2031 
2032 		if (fBorderStyle == B_PLAIN_BORDER) {
2033 			offset += BPoint(0, 1);
2034 		} else if (fBorderStyle == B_FANCY_BORDER) {
2035 			offset += BPoint(-1, 2);
2036 			size.height -= 1;
2037 		}
2038 
2039 		fStatusView->MoveTo(offset);
2040 		fStatusView->ResizeTo(size.width, size.height);
2041 		hScrollBarRect.left = offset.x + size.width + 1;
2042 	}
2043 
2044 	fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
2045 	fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
2046 		hScrollBarRect.Height());
2047 
2048 	fOutlineView->FixScrollBar(true);
2049 }
2050 
2051 
2052 void
2053 BColumnListView::_Init()
2054 {
2055 	SetViewColor(B_TRANSPARENT_32_BIT);
2056 
2057 	BRect bounds(Bounds());
2058 	if (bounds.Width() <= 0)
2059 		bounds.right = 100;
2060 
2061 	if (bounds.Height() <= 0)
2062 		bounds.bottom = 100;
2063 
2064 	fCustomColors = false;
2065 	_UpdateColors();
2066 
2067 	BRect titleRect;
2068 	BRect outlineRect;
2069 	BRect vScrollBarRect;
2070 	BRect hScrollBarRect;
2071 	_GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
2072 		hScrollBarRect);
2073 
2074 	fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
2075 	AddChild(fOutlineView);
2076 
2077 
2078 	fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
2079 		&fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
2080 	AddChild(fTitleView);
2081 
2082 	fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
2083 		fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
2084 	AddChild(fVerticalScrollBar);
2085 
2086 	fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
2087 		"horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
2088 	AddChild(fHorizontalScrollBar);
2089 
2090 	if (!fShowingHorizontalScrollBar)
2091 		fHorizontalScrollBar->Hide();
2092 
2093 	fOutlineView->FixScrollBar(true);
2094 }
2095 
2096 
2097 void
2098 BColumnListView::_UpdateColors()
2099 {
2100 	if (fCustomColors)
2101 		return;
2102 
2103 	fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR);
2104 	fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2105 	fColorList[B_COLOR_ROW_DIVIDER] = tint_color(
2106 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT);
2107 	fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2108 	fColorList[B_COLOR_SELECTION_TEXT] =
2109 		ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2110 
2111 	// For non focus selection uses the selection color as BListView
2112 	fColorList[B_COLOR_NON_FOCUS_SELECTION] =
2113 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2114 
2115 	// edit mode doesn't work very well
2116 	fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color(
2117 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT);
2118 	fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180;
2119 
2120 	// Unused color
2121 	fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2122 
2123 	fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_PANEL_BACKGROUND_COLOR);
2124 	fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR);
2125 
2126 	// Unused colors
2127 	fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2128 	fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2129 }
2130 
2131 
2132 void
2133 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
2134 	BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect)
2135 {
2136 	titleRect = bounds;
2137 	titleRect.bottom = titleRect.top + std::max(kMinTitleHeight,
2138 		ceilf(be_plain_font->Size() * kTitleSpacing));
2139 #if !LOWER_SCROLLBAR
2140 	titleRect.right -= B_V_SCROLL_BAR_WIDTH;
2141 #endif
2142 
2143 	outlineRect = bounds;
2144 	outlineRect.top = titleRect.bottom + 1.0;
2145 	outlineRect.right -= B_V_SCROLL_BAR_WIDTH;
2146 	if (fShowingHorizontalScrollBar)
2147 		outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2148 
2149 	vScrollBarRect = bounds;
2150 #if LOWER_SCROLLBAR
2151 	vScrollBarRect.top += std::max(kMinTitleHeight,
2152 		ceilf(be_plain_font->Size() * kTitleSpacing));
2153 #endif
2154 
2155 	vScrollBarRect.left = vScrollBarRect.right - B_V_SCROLL_BAR_WIDTH;
2156 	if (fShowingHorizontalScrollBar)
2157 		vScrollBarRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2158 
2159 	hScrollBarRect = bounds;
2160 	hScrollBarRect.top = hScrollBarRect.bottom - B_H_SCROLL_BAR_HEIGHT;
2161 	hScrollBarRect.right -= B_V_SCROLL_BAR_WIDTH;
2162 
2163 	// Adjust stuff so the border will fit.
2164 	if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) {
2165 		titleRect.InsetBy(1, 0);
2166 		titleRect.OffsetBy(0, 1);
2167 		outlineRect.InsetBy(1, 1);
2168 	} else if (fBorderStyle == B_FANCY_BORDER) {
2169 		titleRect.InsetBy(2, 0);
2170 		titleRect.OffsetBy(0, 2);
2171 		outlineRect.InsetBy(2, 2);
2172 
2173 		vScrollBarRect.OffsetBy(-1, 0);
2174 #if LOWER_SCROLLBAR
2175 		vScrollBarRect.top += 2;
2176 		vScrollBarRect.bottom -= 1;
2177 #else
2178 		vScrollBarRect.InsetBy(0, 1);
2179 #endif
2180 		hScrollBarRect.OffsetBy(0, -1);
2181 		hScrollBarRect.InsetBy(1, 0);
2182 	}
2183 }
2184 
2185 
2186 // #pragma mark -
2187 
2188 
2189 TitleView::TitleView(BRect rect, OutlineView* horizontalSlave,
2190 	BList* visibleColumns, BList* sortColumns, BColumnListView* listView,
2191 	uint32 resizingMode)
2192 	:
2193 	BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS),
2194 	fOutlineView(horizontalSlave),
2195 	fColumns(visibleColumns),
2196 	fSortColumns(sortColumns),
2197 //	fColumnsWidth(0),
2198 	fVisibleRect(rect.OffsetToCopy(0, 0)),
2199 	fCurrentState(INACTIVE),
2200 	fColumnPop(NULL),
2201 	fMasterView(listView),
2202 	fEditMode(false),
2203 	fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE
2204 		| B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE)
2205 {
2206 	SetViewColor(B_TRANSPARENT_COLOR);
2207 
2208 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2209 	// xxx this needs to be smart about the size of the backbuffer.
2210 	BRect doubleBufferRect(0, 0, 600, 35);
2211 	fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
2212 	fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
2213 		B_FOLLOW_ALL_SIDES, 0);
2214 	fDrawBuffer->Lock();
2215 	fDrawBuffer->AddChild(fDrawBufferView);
2216 	fDrawBuffer->Unlock();
2217 #endif
2218 
2219 	fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2220 	fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2221 
2222 	fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
2223 	fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
2224 
2225 	fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST);
2226 	fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST);
2227 	fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST);
2228 	fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE);
2229 
2230 	FixScrollBar(true);
2231 }
2232 
2233 
2234 TitleView::~TitleView()
2235 {
2236 	delete fColumnPop;
2237 	fColumnPop = NULL;
2238 
2239 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2240 	delete fDrawBuffer;
2241 #endif
2242 	delete fUpSortArrow;
2243 	delete fDownSortArrow;
2244 
2245 	delete fResizeCursor;
2246 	delete fMaxResizeCursor;
2247 	delete fMinResizeCursor;
2248 	delete fColumnMoveCursor;
2249 }
2250 
2251 
2252 void
2253 TitleView::ColumnAdded(BColumn* column)
2254 {
2255 //	fColumnsWidth += column->Width();
2256 	FixScrollBar(false);
2257 	Invalidate();
2258 }
2259 
2260 
2261 void
2262 TitleView::ColumnResized(BColumn* column, float oldWidth)
2263 {
2264 //	fColumnsWidth += column->Width() - oldWidth;
2265 	FixScrollBar(false);
2266 	Invalidate();
2267 }
2268 
2269 
2270 void
2271 TitleView::SetColumnVisible(BColumn* column, bool visible)
2272 {
2273 	if (column->fVisible == visible)
2274 		return;
2275 
2276 	// If setting it visible, do this first so we can find its position
2277 	// to invalidate.  If hiding it, do it last.
2278 	if (visible)
2279 		column->fVisible = visible;
2280 
2281 	BRect titleInvalid;
2282 	GetTitleRect(column, &titleInvalid);
2283 
2284 	// Now really set the visibility
2285 	column->fVisible = visible;
2286 
2287 //	if (visible)
2288 //		fColumnsWidth += column->Width();
2289 //	else
2290 //		fColumnsWidth -= column->Width();
2291 
2292 	BRect outlineInvalid(fOutlineView->VisibleRect());
2293 	outlineInvalid.left = titleInvalid.left;
2294 	titleInvalid.right = outlineInvalid.right;
2295 
2296 	Invalidate(titleInvalid);
2297 	fOutlineView->Invalidate(outlineInvalid);
2298 
2299 	FixScrollBar(false);
2300 }
2301 
2302 
2303 void
2304 TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect)
2305 {
2306 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2307 	int32 numColumns = fColumns->CountItems();
2308 	for (int index = 0; index < numColumns; index++) {
2309 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2310 		if (!column->IsVisible())
2311 			continue;
2312 
2313 		if (column == findColumn) {
2314 			_rect->Set(leftEdge, 0, leftEdge + column->Width(),
2315 				fVisibleRect.bottom);
2316 			return;
2317 		}
2318 
2319 		leftEdge += column->Width() + 1;
2320 	}
2321 
2322 	TRESPASS();
2323 }
2324 
2325 
2326 int32
2327 TitleView::FindColumn(BPoint position, float* _leftEdge)
2328 {
2329 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2330 	int32 numColumns = fColumns->CountItems();
2331 	for (int index = 0; index < numColumns; index++) {
2332 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2333 		if (!column->IsVisible())
2334 			continue;
2335 
2336 		if (leftEdge > position.x)
2337 			break;
2338 
2339 		if (position.x >= leftEdge
2340 			&& position.x <= leftEdge + column->Width()) {
2341 			*_leftEdge = leftEdge;
2342 			return index;
2343 		}
2344 
2345 		leftEdge += column->Width() + 1;
2346 	}
2347 
2348 	return 0;
2349 }
2350 
2351 
2352 void
2353 TitleView::FixScrollBar(bool scrollToFit)
2354 {
2355 	BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2356 	if (hScrollBar == NULL)
2357 		return;
2358 
2359 	float virtualWidth = _VirtualWidth();
2360 
2361 	if (virtualWidth > fVisibleRect.Width()) {
2362 		hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
2363 
2364 		// Perform the little trick if the user is scrolled over too far.
2365 		// See OutlineView::FixScrollBar for a more in depth explanation
2366 		float maxScrollBarValue = virtualWidth - fVisibleRect.Width();
2367 		if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) {
2368 			hScrollBar->SetRange(0.0, maxScrollBarValue);
2369 			hScrollBar->SetSteps(50, fVisibleRect.Width());
2370 		}
2371 	} else if (hScrollBar->Value() == 0.0) {
2372 		// disable scroll bar.
2373 		hScrollBar->SetRange(0.0, 0.0);
2374 	}
2375 }
2376 
2377 
2378 void
2379 TitleView::DragSelectedColumn(BPoint position)
2380 {
2381 	float invalidLeft = fSelectedColumnRect.left;
2382 	float invalidRight = fSelectedColumnRect.right;
2383 
2384 	float leftEdge;
2385 	int32 columnIndex = FindColumn(position, &leftEdge);
2386 	fSelectedColumnRect.OffsetTo(leftEdge, 0);
2387 
2388 	MoveColumn(fSelectedColumn, columnIndex);
2389 
2390 	fSelectedColumn->fVisible = true;
2391 	ComputeDragBoundries(fSelectedColumn, position);
2392 
2393 	// Redraw the new column position
2394 	GetTitleRect(fSelectedColumn, &fSelectedColumnRect);
2395 	invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft);
2396 	invalidRight = MAX(fSelectedColumnRect.right, invalidRight);
2397 
2398 	Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom));
2399 	fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight,
2400 		fOutlineView->VisibleRect().bottom));
2401 
2402 	DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2403 }
2404 
2405 
2406 void
2407 TitleView::MoveColumn(BColumn* column, int32 index)
2408 {
2409 	fColumns->RemoveItem((void*) column);
2410 
2411 	if (-1 == index) {
2412 		// Re-add the column at the end of the list.
2413 		fColumns->AddItem((void*) column);
2414 	} else {
2415 		fColumns->AddItem((void*) column, index);
2416 	}
2417 }
2418 
2419 
2420 void
2421 TitleView::SetColumnFlags(column_flags flags)
2422 {
2423 	fColumnFlags = flags;
2424 }
2425 
2426 
2427 float
2428 TitleView::MarginWidth() const
2429 {
2430 	return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin;
2431 }
2432 
2433 
2434 void
2435 TitleView::ResizeSelectedColumn(BPoint position, bool preferred)
2436 {
2437 	float minWidth = fSelectedColumn->MinWidth();
2438 	float maxWidth = fSelectedColumn->MaxWidth();
2439 
2440 	float oldWidth = fSelectedColumn->Width();
2441 	float originalEdge = fSelectedColumnRect.left + oldWidth;
2442 	if (preferred) {
2443 		float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
2444 		fSelectedColumn->SetWidth(width);
2445 	} else if (position.x > fSelectedColumnRect.left + maxWidth)
2446 		fSelectedColumn->SetWidth(maxWidth);
2447 	else if (position.x < fSelectedColumnRect.left + minWidth)
2448 		fSelectedColumn->SetWidth(minWidth);
2449 	else
2450 		fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
2451 
2452 	float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
2453 		 - originalEdge;
2454 	if (dX != 0) {
2455 		float columnHeight = fVisibleRect.Height();
2456 		BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
2457 		BRect movedRect(originalRect);
2458 		movedRect.OffsetBy(dX, 0);
2459 
2460 		// Update the size of the title column
2461 		BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
2462 		BRect destRect(sourceRect);
2463 		destRect.OffsetBy(fSelectedColumnRect.left, 0);
2464 
2465 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2466 		fDrawBuffer->Lock();
2467 		DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
2468 		fDrawBufferView->Sync();
2469 		fDrawBuffer->Unlock();
2470 
2471 		CopyBits(originalRect, movedRect);
2472 		DrawBitmap(fDrawBuffer, sourceRect, destRect);
2473 #else
2474 		CopyBits(originalRect, movedRect);
2475 		DrawTitle(this, destRect, fSelectedColumn, false);
2476 #endif
2477 
2478 		// Update the body view
2479 		BRect slaveSize = fOutlineView->VisibleRect();
2480 		BRect slaveSource(originalRect);
2481 		slaveSource.bottom = slaveSize.bottom;
2482 		BRect slaveDest(movedRect);
2483 		slaveDest.bottom = slaveSize.bottom;
2484 		fOutlineView->CopyBits(slaveSource, slaveDest);
2485 		fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left,
2486 			fResizingFirstColumn);
2487 
2488 //		fColumnsWidth += dX;
2489 
2490 		// Update the cursor
2491 		if (fSelectedColumn->Width() == minWidth)
2492 			SetViewCursor(fMinResizeCursor, true);
2493 		else if (fSelectedColumn->Width() == maxWidth)
2494 			SetViewCursor(fMaxResizeCursor, true);
2495 		else
2496 			SetViewCursor(fResizeCursor, true);
2497 
2498 		ColumnResized(fSelectedColumn, oldWidth);
2499 	}
2500 }
2501 
2502 
2503 void
2504 TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
2505 {
2506 	float previousColumnLeftEdge = -1000000.0;
2507 	float nextColumnRightEdge = 1000000.0;
2508 
2509 	bool foundColumn = false;
2510 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2511 	int32 numColumns = fColumns->CountItems();
2512 	for (int index = 0; index < numColumns; index++) {
2513 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2514 		if (!column->IsVisible())
2515 			continue;
2516 
2517 		if (column == findColumn) {
2518 			foundColumn = true;
2519 			continue;
2520 		}
2521 
2522 		if (foundColumn) {
2523 			nextColumnRightEdge = leftEdge + column->Width();
2524 			break;
2525 		} else
2526 			previousColumnLeftEdge = leftEdge;
2527 
2528 		leftEdge += column->Width() + 1;
2529 	}
2530 
2531 	float rightEdge = leftEdge + findColumn->Width();
2532 
2533 	fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
2534 		leftEdge);
2535 	fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
2536 }
2537 
2538 
2539 void
2540 TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
2541 {
2542 	BRect drawRect;
2543 	drawRect = rect;
2544 
2545 	font_height fh;
2546 	GetFontHeight(&fh);
2547 
2548 	float baseline = floor(drawRect.top + fh.ascent
2549 		+ (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
2550 
2551 	BRect bgRect = rect;
2552 
2553 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
2554 	view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
2555 	view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
2556 
2557 	bgRect.bottom--;
2558 	bgRect.right--;
2559 
2560 	if (depressed)
2561 		base = tint_color(base, B_DARKEN_1_TINT);
2562 
2563 	be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
2564 		BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
2565 
2566 	view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
2567 		B_DARKEN_2_TINT));
2568 	view->StrokeLine(rect.RightTop(), rect.RightBottom());
2569 
2570 	// If no column given, nothing else to draw.
2571 	if (column == NULL)
2572 		return;
2573 
2574 	view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2575 
2576 	BFont font;
2577 	GetFont(&font);
2578 	view->SetFont(&font);
2579 
2580 	int sortIndex = fSortColumns->IndexOf(column);
2581 	if (sortIndex >= 0) {
2582 		// Draw sort notation.
2583 		BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline);
2584 
2585 		if (fSortColumns->CountItems() > 1) {
2586 			char str[256];
2587 			sprintf(str, "%d", sortIndex + 1);
2588 			const float w = view->StringWidth(str);
2589 			upperLeft.x -= w;
2590 
2591 			view->SetDrawingMode(B_OP_COPY);
2592 			view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth,
2593 				baseline));
2594 			view->DrawString(str);
2595 		}
2596 
2597 		float bmh = fDownSortArrow->Bounds().Height()+1;
2598 
2599 		view->SetDrawingMode(B_OP_OVER);
2600 
2601 		if (column->fSortAscending) {
2602 			BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2603 				- fDownSortArrow->Bounds().IntegerHeight()) / 2);
2604 			view->DrawBitmapAsync(fDownSortArrow, leftTop);
2605 		} else {
2606 			BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2607 				- fUpSortArrow->Bounds().IntegerHeight()) / 2);
2608 			view->DrawBitmapAsync(fUpSortArrow, leftTop);
2609 		}
2610 
2611 		upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2);
2612 		if (upperLeft.y < drawRect.top)
2613 			upperLeft.y = drawRect.top;
2614 
2615 		// Adjust title stuff for sort indicator
2616 		drawRect.right = upperLeft.x - 2;
2617 	}
2618 
2619 	if (drawRect.right > drawRect.left) {
2620 #if CONSTRAIN_CLIPPING_REGION
2621 		BRegion clipRegion(drawRect);
2622 		view->PushState();
2623 		view->ConstrainClippingRegion(&clipRegion);
2624 #endif
2625 		view->MovePenTo(BPoint(drawRect.left + 8, baseline));
2626 		view->SetDrawingMode(B_OP_OVER);
2627 		view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2628 		column->DrawTitle(drawRect, view);
2629 
2630 #if CONSTRAIN_CLIPPING_REGION
2631 		view->PopState();
2632 #endif
2633 	}
2634 }
2635 
2636 
2637 float
2638 TitleView::_VirtualWidth() const
2639 {
2640 	float width = MarginWidth();
2641 
2642 	int32 count = fColumns->CountItems();
2643 	for (int32 i = 0; i < count; i++) {
2644 		BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
2645 		if (column->IsVisible())
2646 			width += column->Width();
2647 	}
2648 
2649 	return width;
2650 }
2651 
2652 
2653 void
2654 TitleView::Draw(BRect invalidRect)
2655 {
2656 	float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2657 	for (int32 columnIndex = 0; columnIndex < fColumns->CountItems();
2658 		columnIndex++) {
2659 
2660 		BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
2661 		if (!column->IsVisible())
2662 			continue;
2663 
2664 		if (columnLeftEdge > invalidRect.right)
2665 			break;
2666 
2667 		if (columnLeftEdge + column->Width() >= invalidRect.left) {
2668 			BRect titleRect(columnLeftEdge, 0,
2669 				columnLeftEdge + column->Width(), fVisibleRect.Height());
2670 			DrawTitle(this, titleRect, column,
2671 				(fCurrentState == DRAG_COLUMN_INSIDE_TITLE
2672 				&& fSelectedColumn == column));
2673 		}
2674 
2675 		columnLeftEdge += column->Width() + 1;
2676 	}
2677 
2678 
2679 	// bevels for right title margin
2680 	if (columnLeftEdge <= invalidRect.right) {
2681 		BRect titleRect(columnLeftEdge, 0, Bounds().right + 2,
2682 			fVisibleRect.Height());
2683 		DrawTitle(this, titleRect, NULL, false);
2684 	}
2685 
2686 	// bevels for left title margin
2687 	if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) {
2688 		BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1,
2689 			fVisibleRect.Height());
2690 		DrawTitle(this, titleRect, NULL, false);
2691 	}
2692 
2693 #if DRAG_TITLE_OUTLINE
2694 	// (internal) column drag indicator
2695 	if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) {
2696 		BRect dragRect(fSelectedColumnRect);
2697 		dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2698 		if (dragRect.Intersects(invalidRect)) {
2699 			SetHighColor(0, 0, 255);
2700 			StrokeRect(dragRect);
2701 		}
2702 	}
2703 #endif
2704 }
2705 
2706 
2707 void
2708 TitleView::ScrollTo(BPoint position)
2709 {
2710 	fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0);
2711 	fVisibleRect.OffsetTo(position.x, position.y);
2712 
2713 	// Perform the little trick if the user is scrolled over too far.
2714 	// See OutlineView::ScrollTo for a more in depth explanation
2715 	float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width();
2716 	BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2717 	float min, max;
2718 	hScrollBar->GetRange(&min, &max);
2719 	if (max != maxScrollBarValue && position.x > maxScrollBarValue)
2720 		FixScrollBar(true);
2721 
2722 	_inherited::ScrollTo(position);
2723 }
2724 
2725 
2726 void
2727 TitleView::MessageReceived(BMessage* message)
2728 {
2729 	if (message->what == kToggleColumn) {
2730 		int32 num;
2731 		if (message->FindInt32("be:field_num", &num) == B_OK) {
2732 			for (int index = 0; index < fColumns->CountItems(); index++) {
2733 				BColumn* column = (BColumn*) fColumns->ItemAt(index);
2734 				if (column == NULL)
2735 					continue;
2736 
2737 				if (column->LogicalFieldNum() == num)
2738 					column->SetVisible(!column->IsVisible());
2739 			}
2740 		}
2741 		return;
2742 	}
2743 
2744 	BView::MessageReceived(message);
2745 }
2746 
2747 
2748 void
2749 TitleView::MouseDown(BPoint position)
2750 {
2751 	if (fEditMode)
2752 		return;
2753 
2754 	int32 buttons = 1;
2755 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2756 	if (buttons == B_SECONDARY_MOUSE_BUTTON
2757 		&& (fColumnFlags & B_ALLOW_COLUMN_POPUP)) {
2758 		// Right mouse button -- bring up menu to show/hide columns.
2759 		if (fColumnPop == NULL)
2760 			fColumnPop = new BPopUpMenu("Columns", false, false);
2761 
2762 		fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true);
2763 		BMessenger me(this);
2764 		for (int index = 0; index < fColumns->CountItems(); index++) {
2765 			BColumn* column = (BColumn*) fColumns->ItemAt(index);
2766 			if (column == NULL)
2767 				continue;
2768 
2769 			BString name;
2770 			column->GetColumnName(&name);
2771 			BMessage* message = new BMessage(kToggleColumn);
2772 			message->AddInt32("be:field_num", column->LogicalFieldNum());
2773 			BMenuItem* item = new BMenuItem(name.String(), message);
2774 			item->SetMarked(column->IsVisible());
2775 			item->SetTarget(me);
2776 			fColumnPop->AddItem(item);
2777 		}
2778 
2779 		BPoint screenPosition = ConvertToScreen(position);
2780 		BRect sticky(screenPosition, screenPosition);
2781 		sticky.InsetBy(-5, -5);
2782 		fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true);
2783 
2784 		return;
2785 	}
2786 
2787 	fResizingFirstColumn = true;
2788 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2789 	for (int index = 0; index < fColumns->CountItems(); index++) {
2790 		BColumn* column = (BColumn*)fColumns->ItemAt(index);
2791 		if (column == NULL || !column->IsVisible())
2792 			continue;
2793 
2794 		if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2795 			break;
2796 
2797 		// check for resizing a column
2798 		float rightEdge = leftEdge + column->Width();
2799 
2800 		if (column->ShowHeading()) {
2801 			if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2802 				&& position.x < rightEdge + kColumnResizeAreaWidth / 2
2803 				&& column->MaxWidth() > column->MinWidth()
2804 				&& (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) {
2805 
2806 				int32 clicks = 0;
2807 				fSelectedColumn = column;
2808 				fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2809 					fVisibleRect.Height());
2810 				Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2811 				if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) {
2812 					ResizeSelectedColumn(position, true);
2813 					fCurrentState = INACTIVE;
2814 					break;
2815 				}
2816 				fCurrentState = RESIZING_COLUMN;
2817 				fClickPoint = BPoint(position.x - rightEdge - 1,
2818 					position.y - fSelectedColumnRect.top);
2819 				SetMouseEventMask(B_POINTER_EVENTS,
2820 					B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2821 				break;
2822 			}
2823 
2824 			fResizingFirstColumn = false;
2825 
2826 			// check for clicking on a column
2827 			if (position.x > leftEdge && position.x < rightEdge) {
2828 				fCurrentState = PRESSING_COLUMN;
2829 				fSelectedColumn = column;
2830 				fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2831 					fVisibleRect.Height());
2832 				DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2833 				fClickPoint = BPoint(position.x - fSelectedColumnRect.left,
2834 					position.y - fSelectedColumnRect.top);
2835 				SetMouseEventMask(B_POINTER_EVENTS,
2836 					B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2837 				break;
2838 			}
2839 		}
2840 		leftEdge = rightEdge + 1;
2841 	}
2842 }
2843 
2844 
2845 void
2846 TitleView::MouseMoved(BPoint position, uint32 transit,
2847 	const BMessage* dragMessage)
2848 {
2849 	if (fEditMode)
2850 		return;
2851 
2852 	// Handle column manipulation
2853 	switch (fCurrentState) {
2854 		case RESIZING_COLUMN:
2855 			ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
2856 			break;
2857 
2858 		case PRESSING_COLUMN: {
2859 			if (abs((int32)(position.x - (fClickPoint.x
2860 					+ fSelectedColumnRect.left))) > kColumnResizeAreaWidth
2861 				|| abs((int32)(position.y - (fClickPoint.y
2862 					+ fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
2863 				// User has moved the mouse more than the tolerable amount,
2864 				// initiate a drag.
2865 				if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
2866 					if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
2867 						fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2868 						ComputeDragBoundries(fSelectedColumn, position);
2869 						SetViewCursor(fColumnMoveCursor, true);
2870 #if DRAG_TITLE_OUTLINE
2871 						BRect invalidRect(fSelectedColumnRect);
2872 						invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
2873 						fCurrentDragPosition = position;
2874 						Invalidate(invalidRect);
2875 #endif
2876 					}
2877 				} else {
2878 					if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
2879 						// Dragged outside view
2880 						fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2881 						fSelectedColumn->SetVisible(false);
2882 						BRect dragRect(fSelectedColumnRect);
2883 
2884 						// There is a race condition where the mouse may have
2885 						// moved by the time we get to handle this message.
2886 						// If the user drags a column very quickly, this
2887 						// results in the annoying bug where the cursor is
2888 						// outside of the rectangle that is being dragged
2889 						// around.  Call GetMouse with the checkQueue flag set
2890 						// to false so we can get the most recent position of
2891 						// the mouse.  This minimizes this problem (although
2892 						// it is currently not possible to completely eliminate
2893 						// it).
2894 						uint32 buttons;
2895 						GetMouse(&position, &buttons, false);
2896 						dragRect.OffsetTo(position.x - fClickPoint.x,
2897 							position.y - dragRect.Height() / 2);
2898 						BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2899 					}
2900 				}
2901 			}
2902 
2903 			break;
2904 		}
2905 
2906 		case DRAG_COLUMN_INSIDE_TITLE: {
2907 			if (transit == B_EXITED_VIEW
2908 				&& (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) {
2909 				// Dragged outside view
2910 				fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2911 				fSelectedColumn->SetVisible(false);
2912 				BRect dragRect(fSelectedColumnRect);
2913 
2914 				// See explanation above.
2915 				uint32 buttons;
2916 				GetMouse(&position, &buttons, false);
2917 
2918 				dragRect.OffsetTo(position.x - fClickPoint.x,
2919 					position.y - fClickPoint.y);
2920 				BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2921 			} else if (position.x < fLeftDragBoundry
2922 				|| position.x > fRightDragBoundry) {
2923 				DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2924 			}
2925 
2926 #if DRAG_TITLE_OUTLINE
2927 			// Set up the invalid rect to include the rect for the previous
2928 			// position of the drag rect, as well as the new one.
2929 			BRect invalidRect(fSelectedColumnRect);
2930 			invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2931 			if (position.x < fCurrentDragPosition.x)
2932 				invalidRect.left -= fCurrentDragPosition.x - position.x;
2933 			else
2934 				invalidRect.right += position.x - fCurrentDragPosition.x;
2935 
2936 			fCurrentDragPosition = position;
2937 			Invalidate(invalidRect);
2938 #endif
2939 			break;
2940 		}
2941 
2942 		case DRAG_COLUMN_OUTSIDE_TITLE:
2943 			if (transit == B_ENTERED_VIEW) {
2944 				// Drag back into view
2945 				EndRectTracking();
2946 				fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2947 				fSelectedColumn->SetVisible(true);
2948 				DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2949 			}
2950 
2951 			break;
2952 
2953 		case INACTIVE:
2954 			// Check for cursor changes if we are over the resize area for
2955 			// a column.
2956 			BColumn* resizeColumn = 0;
2957 			float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2958 			for (int index = 0; index < fColumns->CountItems(); index++) {
2959 				BColumn* column = (BColumn*) fColumns->ItemAt(index);
2960 				if (!column->IsVisible())
2961 					continue;
2962 
2963 				if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2964 					break;
2965 
2966 				float rightEdge = leftEdge + column->Width();
2967 				if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2968 					&& position.x < rightEdge + kColumnResizeAreaWidth / 2
2969 					&& column->MaxWidth() > column->MinWidth()) {
2970 					resizeColumn = column;
2971 					break;
2972 				}
2973 
2974 				leftEdge = rightEdge + 1;
2975 			}
2976 
2977 			// Update the cursor
2978 			if (resizeColumn) {
2979 				if (resizeColumn->Width() == resizeColumn->MinWidth())
2980 					SetViewCursor(fMinResizeCursor, true);
2981 				else if (resizeColumn->Width() == resizeColumn->MaxWidth())
2982 					SetViewCursor(fMaxResizeCursor, true);
2983 				else
2984 					SetViewCursor(fResizeCursor, true);
2985 			} else
2986 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
2987 			break;
2988 	}
2989 }
2990 
2991 
2992 void
2993 TitleView::MouseUp(BPoint position)
2994 {
2995 	if (fEditMode)
2996 		return;
2997 
2998 	switch (fCurrentState) {
2999 		case RESIZING_COLUMN:
3000 			ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
3001 			fCurrentState = INACTIVE;
3002 			FixScrollBar(false);
3003 			break;
3004 
3005 		case PRESSING_COLUMN: {
3006 			if (fMasterView->SortingEnabled()) {
3007 				if (fSortColumns->HasItem(fSelectedColumn)) {
3008 					if ((modifiers() & B_CONTROL_KEY) == 0
3009 						&& fSortColumns->CountItems() > 1) {
3010 						fSortColumns->MakeEmpty();
3011 						fSortColumns->AddItem(fSelectedColumn);
3012 					}
3013 
3014 					fSelectedColumn->fSortAscending
3015 						= !fSelectedColumn->fSortAscending;
3016 				} else {
3017 					if ((modifiers() & B_CONTROL_KEY) == 0)
3018 						fSortColumns->MakeEmpty();
3019 
3020 					fSortColumns->AddItem(fSelectedColumn);
3021 					fSelectedColumn->fSortAscending = true;
3022 				}
3023 
3024 				fOutlineView->StartSorting();
3025 			}
3026 
3027 			fCurrentState = INACTIVE;
3028 			Invalidate();
3029 			break;
3030 		}
3031 
3032 		case DRAG_COLUMN_INSIDE_TITLE:
3033 			fCurrentState = INACTIVE;
3034 
3035 #if DRAG_TITLE_OUTLINE
3036 			Invalidate();	// xxx Can make this smaller
3037 #else
3038 			Invalidate(fSelectedColumnRect);
3039 #endif
3040 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3041 			break;
3042 
3043 		case DRAG_COLUMN_OUTSIDE_TITLE:
3044 			fCurrentState = INACTIVE;
3045 			EndRectTracking();
3046 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3047 			break;
3048 
3049 		default:
3050 			;
3051 	}
3052 }
3053 
3054 
3055 void
3056 TitleView::FrameResized(float width, float height)
3057 {
3058 	fVisibleRect.right = fVisibleRect.left + width;
3059 	fVisibleRect.bottom = fVisibleRect.top + height;
3060 	FixScrollBar(true);
3061 }
3062 
3063 
3064 // #pragma mark - OutlineView
3065 
3066 
3067 OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns,
3068 	BColumnListView* listView)
3069 	:
3070 	BView(rect, "outline_view", B_FOLLOW_ALL_SIDES,
3071 		B_WILL_DRAW | B_FRAME_EVENTS),
3072 	fColumns(visibleColumns),
3073 	fSortColumns(sortColumns),
3074 	fItemsHeight(0.0),
3075 	fVisibleRect(rect.OffsetToCopy(0, 0)),
3076 	fFocusRow(0),
3077 	fRollOverRow(0),
3078 	fLastSelectedItem(0),
3079 	fFirstSelectedItem(0),
3080 	fSortThread(B_BAD_THREAD_ID),
3081 	fCurrentState(INACTIVE),
3082 	fMasterView(listView),
3083 	fSelectionMode(B_MULTIPLE_SELECTION_LIST),
3084 	fTrackMouse(false),
3085 	fCurrentField(0),
3086 	fCurrentRow(0),
3087 	fCurrentColumn(0),
3088 	fMouseDown(false),
3089 	fCurrentCode(B_OUTSIDE_VIEW),
3090 	fEditMode(false),
3091 	fDragging(false),
3092 	fClickCount(0),
3093 	fDropHighlightY(-1)
3094 {
3095 	SetViewColor(B_TRANSPARENT_COLOR);
3096 
3097 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3098 	// TODO: This needs to be smart about the size of the buffer.
3099 	// Also, the buffer can be shared with the title's buffer.
3100 	BRect doubleBufferRect(0, 0, 600, 35);
3101 	fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
3102 	fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
3103 		B_FOLLOW_ALL_SIDES, 0);
3104 	fDrawBuffer->Lock();
3105 	fDrawBuffer->AddChild(fDrawBufferView);
3106 	fDrawBuffer->Unlock();
3107 #endif
3108 
3109 	FixScrollBar(true);
3110 	fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
3111 	fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
3112 }
3113 
3114 
3115 OutlineView::~OutlineView()
3116 {
3117 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3118 	delete fDrawBuffer;
3119 #endif
3120 
3121 	Clear();
3122 }
3123 
3124 
3125 void
3126 OutlineView::Clear()
3127 {
3128 	DeselectAll();
3129 		// Make sure selection list doesn't point to deleted rows!
3130 	RecursiveDeleteRows(&fRows, false);
3131 	fItemsHeight = 0.0;
3132 	FixScrollBar(true);
3133 	Invalidate();
3134 }
3135 
3136 
3137 void
3138 OutlineView::SetSelectionMode(list_view_type mode)
3139 {
3140 	DeselectAll();
3141 	fSelectionMode = mode;
3142 }
3143 
3144 
3145 list_view_type
3146 OutlineView::SelectionMode() const
3147 {
3148 	return fSelectionMode;
3149 }
3150 
3151 
3152 void
3153 OutlineView::Deselect(BRow* row)
3154 {
3155 	if (row == NULL)
3156 		return;
3157 
3158 	if (row->fNextSelected != 0) {
3159 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
3160 		row->fPrevSelected->fNextSelected = row->fNextSelected;
3161 		row->fNextSelected = 0;
3162 		row->fPrevSelected = 0;
3163 		Invalidate();
3164 	}
3165 }
3166 
3167 
3168 void
3169 OutlineView::AddToSelection(BRow* row)
3170 {
3171 	if (row == NULL)
3172 		return;
3173 
3174 	if (row->fNextSelected == 0) {
3175 		if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3176 			DeselectAll();
3177 
3178 		row->fNextSelected = fSelectionListDummyHead.fNextSelected;
3179 		row->fPrevSelected = &fSelectionListDummyHead;
3180 		row->fNextSelected->fPrevSelected = row;
3181 		row->fPrevSelected->fNextSelected = row;
3182 
3183 		BRect invalidRect;
3184 		if (FindVisibleRect(row, &invalidRect))
3185 			Invalidate(invalidRect);
3186 	}
3187 }
3188 
3189 
3190 void
3191 OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
3192 {
3193 	if (list == NULL)
3194 		return;
3195 
3196 	while (true) {
3197 		BRow* row = list->RemoveItemAt(0L);
3198 		if (row == 0)
3199 			break;
3200 
3201 		if (row->fChildList)
3202 			RecursiveDeleteRows(row->fChildList, true);
3203 
3204 		delete row;
3205 	}
3206 
3207 	if (isOwner)
3208 		delete list;
3209 }
3210 
3211 
3212 void
3213 OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
3214 {
3215 	// TODO: Remove code duplication (private function which takes a view
3216 	// pointer, pass "this" in non-double buffered mode)!
3217 	// Watch out for sourceRect versus destRect though!
3218 	if (!column)
3219 		return;
3220 
3221 	font_height fh;
3222 	GetFontHeight(&fh);
3223 	float line = 0.0;
3224 	bool tintedLine = true;
3225 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3226 		line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
3227 
3228 		BRow* row = iterator.CurrentRow();
3229 		float rowHeight = row->Height();
3230 		if (line > fVisibleRect.bottom)
3231 			break;
3232 		tintedLine = !tintedLine;
3233 
3234 		if (line + rowHeight >= fVisibleRect.top) {
3235 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3236 			BRect sourceRect(0, 0, column->Width(), rowHeight);
3237 #endif
3238 			BRect destRect(leftEdge, line, leftEdge + column->Width(),
3239 				line + rowHeight);
3240 
3241 			rgb_color highColor;
3242 			rgb_color lowColor;
3243 			if (row->fNextSelected != 0) {
3244 				if (fEditMode) {
3245 					highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3246 					lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3247 				} else {
3248 					highColor = fMasterView->Color(B_COLOR_SELECTION);
3249 					lowColor = fMasterView->Color(B_COLOR_SELECTION);
3250 				}
3251 			} else {
3252 				highColor = fMasterView->Color(B_COLOR_BACKGROUND);
3253 				lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3254 			}
3255 			if (tintedLine)
3256 				lowColor = tint_color(lowColor, kTintedLineTint);
3257 
3258 
3259 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3260 			fDrawBuffer->Lock();
3261 
3262 			fDrawBufferView->SetHighColor(highColor);
3263 			fDrawBufferView->SetLowColor(lowColor);
3264 
3265 			BFont font;
3266 			GetFont(&font);
3267 			fDrawBufferView->SetFont(&font);
3268 			fDrawBufferView->FillRect(sourceRect, B_SOLID_LOW);
3269 
3270 			if (isFirstColumn) {
3271 				// If this is the first column, double buffer drawing the latch
3272 				// too.
3273 				destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3274 					- fMasterView->LatchWidth();
3275 				sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3276 					- fMasterView->LatchWidth();
3277 
3278 				LatchType pos = B_NO_LATCH;
3279 				if (row->HasLatch())
3280 					pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH;
3281 
3282 				BRect latchRect(sourceRect);
3283 				latchRect.right = latchRect.left + fMasterView->LatchWidth();
3284 				fMasterView->DrawLatch(fDrawBufferView, latchRect, pos, row);
3285 			}
3286 
3287 			BField* field = row->GetField(column->fFieldID);
3288 			if (field) {
3289 				BRect fieldRect(sourceRect);
3290 				if (isFirstColumn)
3291 					fieldRect.left += fMasterView->LatchWidth();
3292 
3293 	#if CONSTRAIN_CLIPPING_REGION
3294 				BRegion clipRegion(fieldRect);
3295 				fDrawBufferView->PushState();
3296 				fDrawBufferView->ConstrainClippingRegion(&clipRegion);
3297 	#endif
3298 				fDrawBufferView->SetHighColor(fMasterView->Color(
3299 					row->fNextSelected ? B_COLOR_SELECTION_TEXT
3300 						: B_COLOR_TEXT));
3301 				float baseline = floor(fieldRect.top + fh.ascent
3302 					+ (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
3303 				fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
3304 				column->DrawField(field, fieldRect, fDrawBufferView);
3305 	#if CONSTRAIN_CLIPPING_REGION
3306 				fDrawBufferView->PopState();
3307 	#endif
3308 			}
3309 
3310 			if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3311 				&& Window()->IsActive()) {
3312 				fDrawBufferView->SetHighColor(fMasterView->Color(
3313 					B_COLOR_ROW_DIVIDER));
3314 				fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top,
3315 					10000.0, sourceRect.bottom));
3316 			}
3317 
3318 			fDrawBufferView->Sync();
3319 			fDrawBuffer->Unlock();
3320 			SetDrawingMode(B_OP_COPY);
3321 			DrawBitmap(fDrawBuffer, sourceRect, destRect);
3322 
3323 #else
3324 
3325 			SetHighColor(highColor);
3326 			SetLowColor(lowColor);
3327 			FillRect(destRect, B_SOLID_LOW);
3328 
3329 			BField* field = row->GetField(column->fFieldID);
3330 			if (field) {
3331 	#if CONSTRAIN_CLIPPING_REGION
3332 				BRegion clipRegion(destRect);
3333 				PushState();
3334 				ConstrainClippingRegion(&clipRegion);
3335 	#endif
3336 				SetHighColor(fMasterView->Color(row->fNextSelected
3337 					? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
3338 				float baseline = floor(destRect.top + fh.ascent
3339 					+ (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
3340 				MovePenTo(destRect.left + 8, baseline);
3341 				column->DrawField(field, destRect, this);
3342 	#if CONSTRAIN_CLIPPING_REGION
3343 				PopState();
3344 	#endif
3345 			}
3346 
3347 			if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3348 				&& Window()->IsActive()) {
3349 				SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3350 				StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
3351 			}
3352 #endif
3353 		}
3354 	}
3355 }
3356 
3357 
3358 void
3359 OutlineView::Draw(BRect invalidBounds)
3360 {
3361 #if SMART_REDRAW
3362 	BRegion invalidRegion;
3363 	GetClippingRegion(&invalidRegion);
3364 #endif
3365 
3366 	font_height fh;
3367 	GetFontHeight(&fh);
3368 
3369 	float line = 0.0;
3370 	bool tintedLine = true;
3371 	int32 numColumns = fColumns->CountItems();
3372 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3373 		iterator.GoToNext()) {
3374 		BRow* row = iterator.CurrentRow();
3375 		if (line > invalidBounds.bottom)
3376 			break;
3377 
3378 		tintedLine = !tintedLine;
3379 		float rowHeight = row->Height();
3380 
3381 		if (line >= invalidBounds.top - rowHeight) {
3382 			bool isFirstColumn = true;
3383 			float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
3384 
3385 			// setup background color
3386 			rgb_color lowColor;
3387 			if (row->fNextSelected != 0) {
3388 				if (Window()->IsActive()) {
3389 					if (fEditMode)
3390 						lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3391 					else
3392 						lowColor = fMasterView->Color(B_COLOR_SELECTION);
3393 				}
3394 				else
3395 					lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
3396 			} else
3397 				lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3398 			if (tintedLine)
3399 				lowColor = tint_color(lowColor, kTintedLineTint);
3400 
3401 			for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) {
3402 				BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
3403 				if (!column->IsVisible())
3404 					continue;
3405 
3406 				if (!isFirstColumn && fieldLeftEdge > invalidBounds.right)
3407 					break;
3408 
3409 				if (fieldLeftEdge + column->Width() >= invalidBounds.left) {
3410 					BRect fullRect(fieldLeftEdge, line,
3411 						fieldLeftEdge + column->Width(), line + rowHeight);
3412 
3413 					bool clippedFirstColumn = false;
3414 						// This happens when a column is indented past the
3415 						// beginning of the next column.
3416 
3417 					SetHighColor(lowColor);
3418 
3419 					BRect destRect(fullRect);
3420 					if (isFirstColumn) {
3421 						fullRect.left -= fMasterView->LatchWidth();
3422 						destRect.left += iterator.CurrentLevel()
3423 							* kOutlineLevelIndent;
3424 						if (destRect.left >= destRect.right) {
3425 							// clipped
3426 							FillRect(BRect(0, line, fieldLeftEdge
3427 								+ column->Width(), line + rowHeight));
3428 							clippedFirstColumn = true;
3429 						}
3430 
3431 						FillRect(BRect(0, line, MAX(kLeftMargin,
3432 							fMasterView->LatchWidth()), line + row->Height()));
3433 					}
3434 
3435 
3436 #if SMART_REDRAW
3437 					if (!clippedFirstColumn
3438 						&& invalidRegion.Intersects(fullRect)) {
3439 #else
3440 					if (!clippedFirstColumn) {
3441 #endif
3442 						FillRect(fullRect);	// Using color set above
3443 
3444 						// Draw the latch widget if it has one.
3445 						if (isFirstColumn) {
3446 							if (row == fTargetRow
3447 								&& fCurrentState == LATCH_CLICKED) {
3448 								// Note that this only occurs if the user is
3449 								// holding down a latch while items are added
3450 								// in the background.
3451 								BPoint pos;
3452 								uint32 buttons;
3453 								GetMouse(&pos, &buttons);
3454 								if (fLatchRect.Contains(pos)) {
3455 									fMasterView->DrawLatch(this, fLatchRect,
3456 										B_PRESSED_LATCH, fTargetRow);
3457 								} else {
3458 									fMasterView->DrawLatch(this, fLatchRect,
3459 										row->fIsExpanded ? B_OPEN_LATCH
3460 											: B_CLOSED_LATCH, fTargetRow);
3461 								}
3462 							} else {
3463 								LatchType pos = B_NO_LATCH;
3464 								if (row->HasLatch())
3465 									pos = row->fIsExpanded ? B_OPEN_LATCH
3466 										: B_CLOSED_LATCH;
3467 
3468 								fMasterView->DrawLatch(this,
3469 									BRect(destRect.left
3470 										- fMasterView->LatchWidth(),
3471 									destRect.top, destRect.left,
3472 									destRect.bottom), pos, row);
3473 							}
3474 						}
3475 
3476 						SetHighColor(fMasterView->HighColor());
3477 							// The master view just holds the high color for us.
3478 						SetLowColor(lowColor);
3479 
3480 						BField* field = row->GetField(column->fFieldID);
3481 						if (field) {
3482 #if CONSTRAIN_CLIPPING_REGION
3483 							BRegion clipRegion(destRect);
3484 							PushState();
3485 							ConstrainClippingRegion(&clipRegion);
3486 #endif
3487 							SetHighColor(fMasterView->Color(
3488 								row->fNextSelected ? B_COLOR_SELECTION_TEXT
3489 								: B_COLOR_TEXT));
3490 							float baseline = floor(destRect.top + fh.ascent
3491 								+ (destRect.Height() + 1
3492 								- (fh.ascent+fh.descent)) / 2);
3493 							MovePenTo(destRect.left + 8, baseline);
3494 							column->DrawField(field, destRect, this);
3495 #if CONSTRAIN_CLIPPING_REGION
3496 							PopState();
3497 #endif
3498 						}
3499 					}
3500 				}
3501 
3502 				isFirstColumn = false;
3503 				fieldLeftEdge += column->Width() + 1;
3504 			}
3505 
3506 			if (fieldLeftEdge <= invalidBounds.right) {
3507 				SetHighColor(lowColor);
3508 				FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
3509 					line + rowHeight));
3510 			}
3511 		}
3512 
3513 		// indicate the keyboard focus row
3514 		if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3515 			&& Window()->IsActive()) {
3516 			SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3517 			StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
3518 		}
3519 
3520 		line += rowHeight + 1;
3521 	}
3522 
3523 	if (line <= invalidBounds.bottom) {
3524 		// fill background below last item
3525 		SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3526 		FillRect(BRect(invalidBounds.left, line, invalidBounds.right,
3527 			invalidBounds.bottom));
3528 	}
3529 
3530 	// Draw the drop target line
3531 	if (fDropHighlightY != -1) {
3532 		InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3533 			1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3534 	}
3535 }
3536 
3537 
3538 BRow*
3539 OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top)
3540 {
3541 	if (_rowIndent && _top) {
3542 		float line = 0.0;
3543 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3544 			iterator.GoToNext()) {
3545 
3546 			BRow* row = iterator.CurrentRow();
3547 			if (line > ypos)
3548 				break;
3549 
3550 			float rowHeight = row->Height();
3551 			if (ypos <= line + rowHeight) {
3552 				*_top = line;
3553 				*_rowIndent = iterator.CurrentLevel();
3554 				return row;
3555 			}
3556 
3557 			line += rowHeight + 1;
3558 		}
3559 	}
3560 
3561 	return NULL;
3562 }
3563 
3564 void OutlineView::SetMouseTrackingEnabled(bool enabled)
3565 {
3566 	fTrackMouse = enabled;
3567 	if (!enabled && fDropHighlightY != -1) {
3568 		// Erase the old target line
3569 		InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3570 			1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3571 		fDropHighlightY = -1;
3572 	}
3573 }
3574 
3575 
3576 //
3577 // Note that this interaction is not totally safe.  If items are added to
3578 // the list in the background, the widget rect will be incorrect, possibly
3579 // resulting in drawing glitches.  The code that adds items needs to be a little smarter
3580 // about invalidating state.
3581 //
3582 void
3583 OutlineView::MouseDown(BPoint position)
3584 {
3585 	if (!fEditMode)
3586 		fMasterView->MakeFocus(true);
3587 
3588 	// Check to see if the user is clicking on a widget to open a section
3589 	// of the list.
3590 	bool reset_click_count = false;
3591 	int32 indent;
3592 	float rowTop;
3593 	BRow* row = FindRow(position.y, &indent, &rowTop);
3594 	if (row != NULL) {
3595 
3596 		// Update fCurrentField
3597 		bool handle_field = false;
3598 		BField* new_field = 0;
3599 		BRow* new_row = 0;
3600 		BColumn* new_column = 0;
3601 		BRect new_rect;
3602 
3603 		if (position.y >= 0) {
3604 			if (position.x >= 0) {
3605 				float x = 0;
3606 				for (int32 c = 0; c < fMasterView->CountColumns(); c++) {
3607 					new_column = fMasterView->ColumnAt(c);
3608 					if (!new_column->IsVisible())
3609 						continue;
3610 					if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x)
3611 						+ new_column->Width() >= position.x) {
3612 						if (new_column->WantsEvents()) {
3613 							new_field = row->GetField(c);
3614 							new_row = row;
3615 							FindRect(new_row,&new_rect);
3616 							new_rect.left = MAX(kLeftMargin,
3617 								fMasterView->LatchWidth()) + x;
3618 							new_rect.right = new_rect.left
3619 								+ new_column->Width() - 1;
3620 							handle_field = true;
3621 						}
3622 						break;
3623 					}
3624 					x += new_column->Width();
3625 				}
3626 			}
3627 		}
3628 
3629 		// Handle mouse down
3630 		if (handle_field) {
3631 			fMouseDown = true;
3632 			fFieldRect = new_rect;
3633 			fCurrentColumn = new_column;
3634 			fCurrentRow = new_row;
3635 			fCurrentField = new_field;
3636 			fCurrentCode = B_INSIDE_VIEW;
3637 			BMessage* message = Window()->CurrentMessage();
3638 			int32 buttons = 1;
3639 			message->FindInt32("buttons", &buttons);
3640 			fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
3641 				fCurrentField, fFieldRect, position, buttons);
3642 		}
3643 
3644 		if (!fEditMode) {
3645 
3646 			fTargetRow = row;
3647 			fTargetRowTop = rowTop;
3648 			FindVisibleRect(fFocusRow, &fFocusRowRect);
3649 
3650 			float leftWidgetBoundry = indent * kOutlineLevelIndent
3651 				+ MAX(kLeftMargin, fMasterView->LatchWidth())
3652 				- fMasterView->LatchWidth();
3653 			fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
3654 				+ fMasterView->LatchWidth(), rowTop + row->Height());
3655 			if (fLatchRect.Contains(position) && row->HasLatch()) {
3656 				fCurrentState = LATCH_CLICKED;
3657 				if (fTargetRow->fNextSelected != 0)
3658 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3659 				else
3660 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3661 
3662 				FillRect(fLatchRect);
3663 				if (fLatchRect.Contains(position)) {
3664 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3665 						row);
3666 				} else {
3667 					fMasterView->DrawLatch(this, fLatchRect,
3668 						fTargetRow->fIsExpanded ? B_OPEN_LATCH
3669 						: B_CLOSED_LATCH, row);
3670 				}
3671 			} else {
3672 				Invalidate(fFocusRowRect);
3673 				fFocusRow = fTargetRow;
3674 				FindVisibleRect(fFocusRow, &fFocusRowRect);
3675 
3676 				ASSERT(fTargetRow != 0);
3677 
3678 				if ((modifiers() & B_CONTROL_KEY) == 0)
3679 					DeselectAll();
3680 
3681 				if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
3682 					&& fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
3683 					SelectRange(fFirstSelectedItem, fTargetRow);
3684 				}
3685 				else {
3686 					if (fTargetRow->fNextSelected != 0) {
3687 						// Unselect row
3688 						fTargetRow->fNextSelected->fPrevSelected
3689 							= fTargetRow->fPrevSelected;
3690 						fTargetRow->fPrevSelected->fNextSelected
3691 							= fTargetRow->fNextSelected;
3692 						fTargetRow->fPrevSelected = 0;
3693 						fTargetRow->fNextSelected = 0;
3694 						fFirstSelectedItem = NULL;
3695 					} else {
3696 						// Select row
3697 						if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3698 							DeselectAll();
3699 
3700 						fTargetRow->fNextSelected
3701 							= fSelectionListDummyHead.fNextSelected;
3702 						fTargetRow->fPrevSelected
3703 							= &fSelectionListDummyHead;
3704 						fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
3705 						fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
3706 						fFirstSelectedItem = fTargetRow;
3707 					}
3708 
3709 					Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
3710 						fVisibleRect.right,
3711 						fTargetRowTop + fTargetRow->Height()));
3712 				}
3713 
3714 				fCurrentState = ROW_CLICKED;
3715 				if (fLastSelectedItem != fTargetRow)
3716 					reset_click_count = true;
3717 				fLastSelectedItem = fTargetRow;
3718 				fMasterView->SelectionChanged();
3719 
3720 			}
3721 		}
3722 
3723 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
3724 			B_NO_POINTER_HISTORY);
3725 
3726 	} else if (fFocusRow != 0) {
3727 		// User clicked in open space, unhighlight focus row.
3728 		FindVisibleRect(fFocusRow, &fFocusRowRect);
3729 		fFocusRow = 0;
3730 		Invalidate(fFocusRowRect);
3731 	}
3732 
3733 	// We stash the click counts here because the 'clicks' field
3734 	// is not in the CurrentMessage() when MouseUp is called... ;(
3735 	if (reset_click_count)
3736 		fClickCount = 1;
3737 	else
3738 		Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
3739 	fClickPoint = position;
3740 
3741 }
3742 
3743 
3744 void
3745 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
3746 	const BMessage* /*dragMessage*/)
3747 {
3748 	if (!fMouseDown) {
3749 		// Update fCurrentField
3750 		bool handle_field = false;
3751 		BField* new_field = 0;
3752 		BRow* new_row = 0;
3753 		BColumn* new_column = 0;
3754 		BRect new_rect(0,0,0,0);
3755 		if (position.y >=0 ) {
3756 			float top;
3757 			int32 indent;
3758 			BRow* row = FindRow(position.y, &indent, &top);
3759 			if (row && position.x >=0 ) {
3760 				float x=0;
3761 				for (int32 c=0;c<fMasterView->CountColumns();c++) {
3762 					new_column = fMasterView->ColumnAt(c);
3763 					if (!new_column->IsVisible())
3764 						continue;
3765 					if ((MAX(kLeftMargin,
3766 						fMasterView->LatchWidth()) + x) + new_column->Width()
3767 						> position.x) {
3768 
3769 						if(new_column->WantsEvents()) {
3770 							new_field = row->GetField(c);
3771 							new_row = row;
3772 							FindRect(new_row,&new_rect);
3773 							new_rect.left = MAX(kLeftMargin,
3774 								fMasterView->LatchWidth()) + x;
3775 							new_rect.right = new_rect.left
3776 								+ new_column->Width() - 1;
3777 							handle_field = true;
3778 						}
3779 						break;
3780 					}
3781 					x += new_column->Width();
3782 				}
3783 			}
3784 		}
3785 
3786 		// Handle mouse moved
3787 		if (handle_field) {
3788 			if (new_field != fCurrentField) {
3789 				if (fCurrentField) {
3790 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3791 						fCurrentField, fFieldRect, position, 0,
3792 						fCurrentCode = B_EXITED_VIEW);
3793 				}
3794 				fCurrentColumn = new_column;
3795 				fCurrentRow = new_row;
3796 				fCurrentField = new_field;
3797 				fFieldRect = new_rect;
3798 				if (fCurrentField) {
3799 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3800 						fCurrentField, fFieldRect, position, 0,
3801 						fCurrentCode = B_ENTERED_VIEW);
3802 				}
3803 			} else {
3804 				if (fCurrentField) {
3805 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3806 						fCurrentField, fFieldRect, position, 0,
3807 						fCurrentCode = B_INSIDE_VIEW);
3808 				}
3809 			}
3810 		} else {
3811 			if (fCurrentField) {
3812 				fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3813 					fCurrentField, fFieldRect, position, 0,
3814 					fCurrentCode = B_EXITED_VIEW);
3815 				fCurrentField = 0;
3816 				fCurrentColumn = 0;
3817 				fCurrentRow = 0;
3818 			}
3819 		}
3820 	} else {
3821 		if (fCurrentField) {
3822 			if (fFieldRect.Contains(position)) {
3823 				if (fCurrentCode == B_OUTSIDE_VIEW
3824 					|| fCurrentCode == B_EXITED_VIEW) {
3825 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3826 						fCurrentField, fFieldRect, position, 1,
3827 						fCurrentCode = B_ENTERED_VIEW);
3828 				} else {
3829 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3830 						fCurrentField, fFieldRect, position, 1,
3831 						fCurrentCode = B_INSIDE_VIEW);
3832 				}
3833 			} else {
3834 				if (fCurrentCode == B_INSIDE_VIEW
3835 					|| fCurrentCode == B_ENTERED_VIEW) {
3836 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3837 						fCurrentField, fFieldRect, position, 1,
3838 						fCurrentCode = B_EXITED_VIEW);
3839 				} else {
3840 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3841 						fCurrentField, fFieldRect, position, 1,
3842 						fCurrentCode = B_OUTSIDE_VIEW);
3843 				}
3844 			}
3845 		}
3846 	}
3847 
3848 	if (!fEditMode) {
3849 
3850 		switch (fCurrentState) {
3851 			case LATCH_CLICKED:
3852 				if (fTargetRow->fNextSelected != 0)
3853 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3854 				else
3855 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3856 
3857 				FillRect(fLatchRect);
3858 				if (fLatchRect.Contains(position)) {
3859 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3860 						fTargetRow);
3861 				} else {
3862 					fMasterView->DrawLatch(this, fLatchRect,
3863 						fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
3864 						fTargetRow);
3865 				}
3866 				break;
3867 
3868 			case ROW_CLICKED:
3869 				if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
3870 					|| abs((int)(position.y - fClickPoint.y))
3871 						> kRowDragSensitivity) {
3872 					fCurrentState = DRAGGING_ROWS;
3873 					fMasterView->InitiateDrag(fClickPoint,
3874 						fTargetRow->fNextSelected != 0);
3875 				}
3876 				break;
3877 
3878 			case DRAGGING_ROWS:
3879 #if 0
3880 				// falls through...
3881 #else
3882 				if (fTrackMouse /*&& message*/) {
3883 					if (fVisibleRect.Contains(position)) {
3884 						float top;
3885 						int32 indent;
3886 						BRow* target = FindRow(position.y, &indent, &top);
3887 						if (target)
3888 							SetFocusRow(target, true);
3889 					}
3890 				}
3891 				break;
3892 #endif
3893 
3894 			default: {
3895 
3896 				if (fTrackMouse /*&& message*/) {
3897 					// Draw a highlight line...
3898 					if (fVisibleRect.Contains(position)) {
3899 						float top;
3900 						int32 indent;
3901 						BRow* target = FindRow(position.y, &indent, &top);
3902 						if (target == fRollOverRow)
3903 							break;
3904 						if (fRollOverRow) {
3905 							BRect rect;
3906 							FindRect(fRollOverRow, &rect);
3907 							Invalidate(rect);
3908 						}
3909 						fRollOverRow = target;
3910 #if 0
3911 						SetFocusRow(fRollOverRow,false);
3912 #else
3913 						PushState();
3914 						SetDrawingMode(B_OP_BLEND);
3915 						SetHighColor(255, 255, 255, 255);
3916 						BRect rect;
3917 						FindRect(fRollOverRow, &rect);
3918 						rect.bottom -= 1.0;
3919 						FillRect(rect);
3920 						PopState();
3921 #endif
3922 					} else {
3923 						if (fRollOverRow) {
3924 							BRect rect;
3925 							FindRect(fRollOverRow, &rect);
3926 							Invalidate(rect);
3927 							fRollOverRow = NULL;
3928 						}
3929 					}
3930 				}
3931 			}
3932 		}
3933 	}
3934 }
3935 
3936 
3937 void
3938 OutlineView::MouseUp(BPoint position)
3939 {
3940 	if (fCurrentField) {
3941 		fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
3942 		fMouseDown = false;
3943 	}
3944 
3945 	if (fEditMode)
3946 		return;
3947 
3948 	switch (fCurrentState) {
3949 		case LATCH_CLICKED:
3950 			if (fLatchRect.Contains(position)) {
3951 				fMasterView->ExpandOrCollapse(fTargetRow,
3952 					!fTargetRow->fIsExpanded);
3953 			}
3954 
3955 			Invalidate(fLatchRect);
3956 			fCurrentState = INACTIVE;
3957 			break;
3958 
3959 		case ROW_CLICKED:
3960 			if (fClickCount > 1
3961 				&& abs((int)fClickPoint.x - (int)position.x)
3962 					< kDoubleClickMoveSensitivity
3963 				&& abs((int)fClickPoint.y - (int)position.y)
3964 					< kDoubleClickMoveSensitivity) {
3965 				fMasterView->ItemInvoked();
3966 			}
3967 			fCurrentState = INACTIVE;
3968 			break;
3969 
3970 		case DRAGGING_ROWS:
3971 			fCurrentState = INACTIVE;
3972 			// Falls through
3973 
3974 		default:
3975 			if (fDropHighlightY != -1) {
3976 				InvertRect(BRect(0,
3977 					fDropHighlightY - kDropHighlightLineHeight / 2,
3978 					1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3979 					// Erase the old target line
3980 				fDropHighlightY = -1;
3981 			}
3982 	}
3983 }
3984 
3985 
3986 void
3987 OutlineView::MessageReceived(BMessage* message)
3988 {
3989 	if (message->WasDropped()) {
3990 		fMasterView->MessageDropped(message,
3991 			ConvertFromScreen(message->DropPoint()));
3992 	} else {
3993 		BView::MessageReceived(message);
3994 	}
3995 }
3996 
3997 
3998 void
3999 OutlineView::ChangeFocusRow(bool up, bool updateSelection,
4000 	bool addToCurrentSelection)
4001 {
4002 	int32 indent;
4003 	float top;
4004 	float newRowPos = 0;
4005 	float verticalScroll = 0;
4006 
4007 	if (fFocusRow) {
4008 		// A row currently has the focus, get information about it
4009 		newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
4010 		if (newRowPos < fVisibleRect.top + 20)
4011 			verticalScroll = newRowPos - 20;
4012 		else if (newRowPos > fVisibleRect.bottom - 20)
4013 			verticalScroll = newRowPos - fVisibleRect.Height() + 20;
4014 	} else
4015 		newRowPos = fVisibleRect.top + 2;
4016 			// no row is currently focused, set this to the top of the window
4017 			// so we will select the first visible item in the list.
4018 
4019 	BRow* newRow = FindRow(newRowPos, &indent, &top);
4020 	if (newRow) {
4021 		if (fFocusRow) {
4022 			fFocusRowRect.right = 10000;
4023 			Invalidate(fFocusRowRect);
4024 		}
4025 		BRow* oldFocusRow = fFocusRow;
4026 		fFocusRow = newRow;
4027 		fFocusRowRect.top = top;
4028 		fFocusRowRect.left = 0;
4029 		fFocusRowRect.right = 10000;
4030 		fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
4031 		Invalidate(fFocusRowRect);
4032 
4033 		if (updateSelection) {
4034 			if (!addToCurrentSelection
4035 				|| fSelectionMode == B_SINGLE_SELECTION_LIST) {
4036 				DeselectAll();
4037 			}
4038 
4039 			// if the focus row isn't selected, add it to the selection
4040 			if (fFocusRow->fNextSelected == 0) {
4041 				fFocusRow->fNextSelected
4042 					= fSelectionListDummyHead.fNextSelected;
4043 				fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4044 				fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4045 				fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4046 			} else if (oldFocusRow != NULL
4047 				&& fSelectionListDummyHead.fNextSelected == oldFocusRow
4048 				&& (((IndexOf(oldFocusRow->fNextSelected)
4049 						< IndexOf(oldFocusRow)) == up)
4050 					|| fFocusRow == oldFocusRow->fNextSelected)) {
4051 					// if the focus row is selected, if:
4052 					// 1. the previous focus row is last in the selection
4053 					// 2a. the next selected row is now the focus row
4054 					// 2b. or the next selected row is beyond the focus row
4055 					//	   in the move direction
4056 					// then deselect the previous focus row
4057 				fSelectionListDummyHead.fNextSelected
4058 					= oldFocusRow->fNextSelected;
4059 				if (fSelectionListDummyHead.fNextSelected != NULL) {
4060 					fSelectionListDummyHead.fNextSelected->fPrevSelected
4061 						= &fSelectionListDummyHead;
4062 					oldFocusRow->fNextSelected = NULL;
4063 				}
4064 				oldFocusRow->fPrevSelected = NULL;
4065 			}
4066 
4067 			fLastSelectedItem = fFocusRow;
4068 		}
4069 	} else
4070 		Invalidate(fFocusRowRect);
4071 
4072 	if (verticalScroll != 0) {
4073 		BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4074 		float min, max;
4075 		vScrollBar->GetRange(&min, &max);
4076 		if (verticalScroll < min)
4077 			verticalScroll = min;
4078 		else if (verticalScroll > max)
4079 			verticalScroll = max;
4080 
4081 		vScrollBar->SetValue(verticalScroll);
4082 	}
4083 
4084 	if (newRow && updateSelection)
4085 		fMasterView->SelectionChanged();
4086 }
4087 
4088 
4089 void
4090 OutlineView::MoveFocusToVisibleRect()
4091 {
4092 	fFocusRow = 0;
4093 	ChangeFocusRow(true, true, false);
4094 }
4095 
4096 
4097 BRow*
4098 OutlineView::CurrentSelection(BRow* lastSelected) const
4099 {
4100 	BRow* row;
4101 	if (lastSelected == 0)
4102 		row = fSelectionListDummyHead.fNextSelected;
4103 	else
4104 		row = lastSelected->fNextSelected;
4105 
4106 
4107 	if (row == &fSelectionListDummyHead)
4108 		row = 0;
4109 
4110 	return row;
4111 }
4112 
4113 
4114 void
4115 OutlineView::ToggleFocusRowSelection(bool selectRange)
4116 {
4117 	if (fFocusRow == 0)
4118 		return;
4119 
4120 	if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
4121 		SelectRange(fLastSelectedItem, fFocusRow);
4122 	else {
4123 		if (fFocusRow->fNextSelected != 0) {
4124 			// Unselect row
4125 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
4126 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
4127 			fFocusRow->fPrevSelected = 0;
4128 			fFocusRow->fNextSelected = 0;
4129 		} else {
4130 			// Select row
4131 			if (fSelectionMode == B_SINGLE_SELECTION_LIST)
4132 				DeselectAll();
4133 
4134 			fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
4135 			fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4136 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4137 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4138 		}
4139 	}
4140 
4141 	fLastSelectedItem = fFocusRow;
4142 	fMasterView->SelectionChanged();
4143 	Invalidate(fFocusRowRect);
4144 }
4145 
4146 
4147 void
4148 OutlineView::ToggleFocusRowOpen()
4149 {
4150 	if (fFocusRow)
4151 		fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
4152 }
4153 
4154 
4155 void
4156 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
4157 {
4158 	// TODO: Could use CopyBits here to speed things up.
4159 
4160 	if (parentRow == NULL)
4161 		return;
4162 
4163 	if (parentRow->fIsExpanded == expand)
4164 		return;
4165 
4166 	parentRow->fIsExpanded = expand;
4167 
4168 	BRect parentRect;
4169 	if (FindRect(parentRow, &parentRect)) {
4170 		// Determine my new height
4171 		float subTreeHeight = 0.0;
4172 		if (parentRow->fIsExpanded)
4173 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4174 			     iterator.CurrentRow();
4175 			     iterator.GoToNext()
4176 			    )
4177 			{
4178 				subTreeHeight += iterator.CurrentRow()->Height()+1;
4179 			}
4180 		else
4181 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4182 			     iterator.CurrentRow();
4183 			     iterator.GoToNext()
4184 			    )
4185 			{
4186 				subTreeHeight -= iterator.CurrentRow()->Height()+1;
4187 			}
4188 		fItemsHeight += subTreeHeight;
4189 
4190 		// Adjust focus row if necessary.
4191 		if (FindRect(fFocusRow, &fFocusRowRect) == false) {
4192 			// focus row is in a subtree that has collapsed,
4193 			// move it up to the parent.
4194 			fFocusRow = parentRow;
4195 			FindRect(fFocusRow, &fFocusRowRect);
4196 		}
4197 
4198 		Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
4199 			fVisibleRect.bottom));
4200 		FixScrollBar(false);
4201 	}
4202 }
4203 
4204 void
4205 OutlineView::RemoveRow(BRow* row)
4206 {
4207 	if (row == NULL)
4208 		return;
4209 
4210 	BRow* parentRow;
4211 	bool parentIsVisible;
4212 	FindParent(row, &parentRow, &parentIsVisible);
4213 		// NOTE: This could be a root row without a parent, in which case
4214 		// it is always visible, though.
4215 
4216 	// Adjust height for the visible sub-tree that is going to be removed.
4217 	float subTreeHeight = 0.0f;
4218 	if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4219 		// The row itself is visible at least.
4220 		subTreeHeight = row->Height() + 1;
4221 		if (row->fIsExpanded) {
4222 			// Adjust for the height of visible sub-items as well.
4223 			// (By default, the iterator follows open branches only.)
4224 			for (RecursiveOutlineIterator iterator(row->fChildList);
4225 				iterator.CurrentRow(); iterator.GoToNext())
4226 				subTreeHeight += iterator.CurrentRow()->Height() + 1;
4227 		}
4228 		BRect invalid;
4229 		if (FindRect(row, &invalid)) {
4230 			invalid.bottom = Bounds().bottom;
4231 			if (invalid.IsValid())
4232 				Invalidate(invalid);
4233 		}
4234 	}
4235 
4236 	fItemsHeight -= subTreeHeight;
4237 
4238 	FixScrollBar(false);
4239 	int32 indent = 0;
4240 	float top = 0.0;
4241 	if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
4242 		// after removing this row, no rows are actually visible any more,
4243 		// force a scroll to make them visible again
4244 		if (fItemsHeight > fVisibleRect.Height())
4245 			ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
4246 		else
4247 			ScrollBy(0.0, -Bounds().top);
4248 	}
4249 	if (parentRow != NULL) {
4250 		parentRow->fChildList->RemoveItem(row);
4251 		if (parentRow->fChildList->CountItems() == 0) {
4252 			delete parentRow->fChildList;
4253 			parentRow->fChildList = 0;
4254 			// It was the last child row of the parent, which also means the
4255 			// latch disappears.
4256 			BRect parentRowRect;
4257 			if (parentIsVisible && FindRect(parentRow, &parentRowRect))
4258 				Invalidate(parentRowRect);
4259 		}
4260 	} else
4261 		fRows.RemoveItem(row);
4262 
4263 	// Adjust focus row if necessary.
4264 	if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
4265 		// focus row is in a subtree that is gone, move it up to the parent.
4266 		fFocusRow = parentRow;
4267 		if (fFocusRow)
4268 			FindRect(fFocusRow, &fFocusRowRect);
4269 	}
4270 
4271 	// Remove this from the selection if necessary
4272 	if (row->fNextSelected != 0) {
4273 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4274 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4275 		row->fPrevSelected = 0;
4276 		row->fNextSelected = 0;
4277 		fMasterView->SelectionChanged();
4278 	}
4279 
4280 	fCurrentColumn = 0;
4281 	fCurrentRow = 0;
4282 	fCurrentField = 0;
4283 }
4284 
4285 
4286 BRowContainer*
4287 OutlineView::RowList()
4288 {
4289 	return &fRows;
4290 }
4291 
4292 
4293 void
4294 OutlineView::UpdateRow(BRow* row)
4295 {
4296 	if (row) {
4297 		// Determine if this row has changed its sort order
4298 		BRow* parentRow = NULL;
4299 		bool parentIsVisible = false;
4300 		FindParent(row, &parentRow, &parentIsVisible);
4301 
4302 		BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
4303 
4304 		if(list) {
4305 			int32 rowIndex = list->IndexOf(row);
4306 			ASSERT(rowIndex >= 0);
4307 			ASSERT(list->ItemAt(rowIndex) == row);
4308 
4309 			bool rowMoved = false;
4310 			if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
4311 				rowMoved = true;
4312 
4313 			if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
4314 				row) < 0)
4315 				rowMoved = true;
4316 
4317 			if (rowMoved) {
4318 				// Sort location of this row has changed.
4319 				// Remove and re-add in the right spot
4320 				SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
4321 			} else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4322 				BRect invalidRect;
4323 				if (FindVisibleRect(row, &invalidRect))
4324 					Invalidate(invalidRect);
4325 			}
4326 		}
4327 	}
4328 }
4329 
4330 
4331 void
4332 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
4333 {
4334 	if (!row)
4335 		return;
4336 
4337 	row->fParent = parentRow;
4338 
4339 	if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) {
4340 		// Ignore index here.
4341 		if (parentRow) {
4342 			if (parentRow->fChildList == NULL)
4343 				parentRow->fChildList = new BRowContainer;
4344 
4345 			AddSorted(parentRow->fChildList, row);
4346 		} else
4347 			AddSorted(&fRows, row);
4348 	} else {
4349 		// Note, a -1 index implies add to end if sorting is not enabled
4350 		if (parentRow) {
4351 			if (parentRow->fChildList == 0)
4352 				parentRow->fChildList = new BRowContainer;
4353 
4354 			if (Index < 0 || Index > parentRow->fChildList->CountItems())
4355 				parentRow->fChildList->AddItem(row);
4356 			else
4357 				parentRow->fChildList->AddItem(row, Index);
4358 		} else {
4359 			if (Index < 0 || Index >= fRows.CountItems())
4360 				fRows.AddItem(row);
4361 			else
4362 				fRows.AddItem(row, Index);
4363 		}
4364 	}
4365 
4366 	if (parentRow == 0 || parentRow->fIsExpanded)
4367 		fItemsHeight += row->Height() + 1;
4368 
4369 	FixScrollBar(false);
4370 
4371 	BRect newRowRect;
4372 	bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
4373 
4374 	if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
4375 		// The focus row has moved.
4376 		Invalidate(fFocusRowRect);
4377 		FindRect(fFocusRow, &fFocusRowRect);
4378 		Invalidate(fFocusRowRect);
4379 	}
4380 
4381 	if (newRowIsInOpenBranch) {
4382 		if (fCurrentState == INACTIVE) {
4383 			if (newRowRect.bottom < fVisibleRect.top) {
4384 				// The new row is totally above the current viewport, move
4385 				// everything down and redraw the first line.
4386 				BRect source(fVisibleRect);
4387 				BRect dest(fVisibleRect);
4388 				source.bottom -= row->Height() + 1;
4389 				dest.top += row->Height() + 1;
4390 				CopyBits(source, dest);
4391 				Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
4392 					fVisibleRect.top + newRowRect.Height()));
4393 			} else if (newRowRect.top < fVisibleRect.bottom) {
4394 				// New item is somewhere in the current region.  Scroll everything
4395 				// beneath it down and invalidate just the new row rect.
4396 				BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
4397 					fVisibleRect.bottom - newRowRect.Height());
4398 				BRect dest(source);
4399 				dest.OffsetBy(0, newRowRect.Height() + 1);
4400 				CopyBits(source, dest);
4401 				Invalidate(newRowRect);
4402 			} // otherwise, this is below the currently visible region
4403 		} else {
4404 			// Adding the item may have caused the item that the user is currently
4405 			// selected to move.  This would cause annoying drawing and interaction
4406 			// bugs, as the position of that item is cached.  If this happens, resize
4407 			// the scroll bar, then scroll back so the selected item is in view.
4408 			BRect targetRect;
4409 			if (FindRect(fTargetRow, &targetRect)) {
4410 				float delta = targetRect.top - fTargetRowTop;
4411 				if (delta != 0) {
4412 					// This causes a jump because ScrollBy will copy a chunk of the view.
4413 					// Since the actual contents of the view have been offset, we don't
4414 					// want this, we just want to change the virtual origin of the window.
4415 					// Constrain the clipping region so everything is clipped out so no
4416 					// copy occurs.
4417 					//
4418 					//	xxx this currently doesn't work if the scroll bars aren't enabled.
4419 					//  everything will still move anyway.  A minor annoyance.
4420 					BRegion emptyRegion;
4421 					ConstrainClippingRegion(&emptyRegion);
4422 					PushState();
4423 					ScrollBy(0, delta);
4424 					PopState();
4425 					ConstrainClippingRegion(NULL);
4426 
4427 					fTargetRowTop += delta;
4428 					fClickPoint.y += delta;
4429 					fLatchRect.OffsetBy(0, delta);
4430 				}
4431 			}
4432 		}
4433 	}
4434 
4435 	// If the parent was previously childless, it will need to have a latch
4436 	// drawn.
4437 	BRect parentRect;
4438 	if (parentRow && parentRow->fChildList->CountItems() == 1
4439 		&& FindVisibleRect(parentRow, &parentRect))
4440 		Invalidate(parentRect);
4441 }
4442 
4443 
4444 void
4445 OutlineView::FixScrollBar(bool scrollToFit)
4446 {
4447 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4448 	if (vScrollBar) {
4449 		if (fItemsHeight > fVisibleRect.Height()) {
4450 			float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4451 			vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
4452 
4453 			// If the user is scrolled down too far when making the range smaller, the list
4454 			// will jump suddenly, which is undesirable.  In this case, don't fix the scroll
4455 			// bar here. In ScrollTo, it checks to see if this has occured, and will
4456 			// fix the scroll bars sneakily if the user has scrolled up far enough.
4457 			if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
4458 				vScrollBar->SetRange(0.0, maxScrollBarValue);
4459 				vScrollBar->SetSteps(20.0, fVisibleRect.Height());
4460 			}
4461 		} else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0)
4462 			vScrollBar->SetRange(0.0, 0.0);		// disable scroll bar.
4463 	}
4464 }
4465 
4466 
4467 void
4468 OutlineView::AddSorted(BRowContainer* list, BRow* row)
4469 {
4470 	if (list && row) {
4471 		// Find general vicinity with binary search.
4472 		int32 lower = 0;
4473 		int32 upper = list->CountItems()-1;
4474 		while( lower < upper ) {
4475 			int32 middle = lower + (upper-lower+1)/2;
4476 			int32 cmp = CompareRows(row, list->ItemAt(middle));
4477 			if( cmp < 0 ) upper = middle-1;
4478 			else if( cmp > 0 ) lower = middle+1;
4479 			else lower = upper = middle;
4480 		}
4481 
4482 		// At this point, 'upper' and 'lower' at the last found item.
4483 		// Arbitrarily use 'upper' and determine the final insertion
4484 		// point -- either before or after this item.
4485 		if( upper < 0 ) upper = 0;
4486 		else if( upper < list->CountItems() ) {
4487 			if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
4488 		}
4489 
4490 		if (upper >= list->CountItems())
4491 			list->AddItem(row);				// Adding to end.
4492 		else
4493 			list->AddItem(row, upper);		// Insert
4494 	}
4495 }
4496 
4497 
4498 int32
4499 OutlineView::CompareRows(BRow* row1, BRow* row2)
4500 {
4501 	int32 itemCount (fSortColumns->CountItems());
4502 	if (row1 && row2) {
4503 		for (int32 index = 0; index < itemCount; index++) {
4504 			BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
4505 			int comp = 0;
4506 			BField* field1 = (BField*) row1->GetField(column->fFieldID);
4507 			BField* field2 = (BField*) row2->GetField(column->fFieldID);
4508 			if (field1 && field2)
4509 				comp = column->CompareFields(field1, field2);
4510 
4511 			if (!column->fSortAscending)
4512 				comp = -comp;
4513 
4514 			if (comp != 0)
4515 				return comp;
4516 		}
4517 	}
4518 	return 0;
4519 }
4520 
4521 
4522 void
4523 OutlineView::FrameResized(float width, float height)
4524 {
4525 	fVisibleRect.right = fVisibleRect.left + width;
4526 	fVisibleRect.bottom = fVisibleRect.top + height;
4527 	FixScrollBar(true);
4528 	_inherited::FrameResized(width, height);
4529 }
4530 
4531 
4532 void
4533 OutlineView::ScrollTo(BPoint position)
4534 {
4535 	fVisibleRect.OffsetTo(position.x, position.y);
4536 
4537 	// In FixScrollBar, we might not have been able to change the size of
4538 	// the scroll bar because the user was scrolled down too far.  Take
4539 	// this opportunity to sneak it in if we can.
4540 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4541 	float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4542 	float min, max;
4543 	vScrollBar->GetRange(&min, &max);
4544 	if (max != maxScrollBarValue && position.y > maxScrollBarValue)
4545 		FixScrollBar(true);
4546 
4547 	_inherited::ScrollTo(position);
4548 }
4549 
4550 
4551 const BRect&
4552 OutlineView::VisibleRect() const
4553 {
4554 	return fVisibleRect;
4555 }
4556 
4557 
4558 bool
4559 OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
4560 {
4561 	if (row && _rect) {
4562 		float line = 0.0;
4563 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4564 			iterator.GoToNext()) {
4565 
4566 			if (iterator.CurrentRow() == row) {
4567 				_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4568 					line + row->Height());
4569 				return line <= fVisibleRect.bottom;
4570 			}
4571 
4572 			line += iterator.CurrentRow()->Height() + 1;
4573 		}
4574 	}
4575 	return false;
4576 }
4577 
4578 
4579 bool
4580 OutlineView::FindRect(const BRow* row, BRect* _rect)
4581 {
4582 	float line = 0.0;
4583 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4584 		iterator.GoToNext()) {
4585 		if (iterator.CurrentRow() == row) {
4586 			_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4587 				line + row->Height());
4588 			return true;
4589 		}
4590 
4591 		line += iterator.CurrentRow()->Height() + 1;
4592 	}
4593 
4594 	return false;
4595 }
4596 
4597 
4598 void
4599 OutlineView::ScrollTo(const BRow* row)
4600 {
4601 	BRect rect;
4602 	if (FindRect(row, &rect)) {
4603 		BRect bounds = Bounds();
4604 		if (rect.top < bounds.top)
4605 			ScrollTo(BPoint(bounds.left, rect.top));
4606 		else if (rect.bottom > bounds.bottom)
4607 			ScrollBy(0, rect.bottom - bounds.bottom);
4608 	}
4609 }
4610 
4611 
4612 void
4613 OutlineView::DeselectAll()
4614 {
4615 	// Invalidate all selected rows
4616 	float line = 0.0;
4617 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4618 		iterator.GoToNext()) {
4619 		if (line > fVisibleRect.bottom)
4620 			break;
4621 
4622 		BRow* row = iterator.CurrentRow();
4623 		if (line + row->Height() > fVisibleRect.top) {
4624 			if (row->fNextSelected != 0)
4625 				Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
4626 					line + row->Height()));
4627 		}
4628 
4629 		line += row->Height() + 1;
4630 	}
4631 
4632 	// Set items not selected
4633 	while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
4634 		BRow* row = fSelectionListDummyHead.fNextSelected;
4635 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4636 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4637 		row->fNextSelected = 0;
4638 		row->fPrevSelected = 0;
4639 	}
4640 }
4641 
4642 
4643 BRow*
4644 OutlineView::FocusRow() const
4645 {
4646 	return fFocusRow;
4647 }
4648 
4649 
4650 void
4651 OutlineView::SetFocusRow(BRow* row, bool Select)
4652 {
4653 	if (row) {
4654 		if (Select)
4655 			AddToSelection(row);
4656 
4657 		if (fFocusRow == row)
4658 			return;
4659 
4660 		Invalidate(fFocusRowRect); // invalidate previous
4661 
4662 		fTargetRow = fFocusRow = row;
4663 
4664 		FindVisibleRect(fFocusRow, &fFocusRowRect);
4665 		Invalidate(fFocusRowRect); // invalidate current
4666 
4667 		fFocusRowRect.right = 10000;
4668 		fMasterView->SelectionChanged();
4669 	}
4670 }
4671 
4672 
4673 bool
4674 OutlineView::SortList(BRowContainer* list, bool isVisible)
4675 {
4676 	if (list) {
4677 		// Shellsort
4678 		BRow** items
4679 			= (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
4680 		int32 numItems = list->CountItems();
4681 		int h;
4682 		for (h = 1; h < numItems / 9; h = 3 * h + 1)
4683 			;
4684 
4685 		for (;h > 0; h /= 3) {
4686 			for (int step = h; step < numItems; step++) {
4687 				BRow* temp = items[step];
4688 				int i;
4689 				for (i = step - h; i >= 0; i -= h) {
4690 					if (CompareRows(temp, items[i]) < 0)
4691 						items[i + h] = items[i];
4692 					else
4693 						break;
4694 				}
4695 
4696 				items[i + h] = temp;
4697 			}
4698 		}
4699 
4700 		if (isVisible) {
4701 			Invalidate();
4702 
4703 			InvalidateCachedPositions();
4704 			int lockCount = Window()->CountLocks();
4705 			for (int i = 0; i < lockCount; i++)
4706 				Window()->Unlock();
4707 
4708 			while (lockCount--)
4709 				if (!Window()->Lock())
4710 					return false;	// Window is gone...
4711 		}
4712 	}
4713 	return true;
4714 }
4715 
4716 
4717 int32
4718 OutlineView::DeepSortThreadEntry(void* _outlineView)
4719 {
4720 	((OutlineView*) _outlineView)->DeepSort();
4721 	return 0;
4722 }
4723 
4724 
4725 void
4726 OutlineView::DeepSort()
4727 {
4728 	struct stack_entry {
4729 		bool isVisible;
4730 		BRowContainer* list;
4731 		int32 listIndex;
4732 	} stack[kMaxDepth];
4733 	int32 stackTop = 0;
4734 
4735 	stack[stackTop].list = &fRows;
4736 	stack[stackTop].isVisible = true;
4737 	stack[stackTop].listIndex = 0;
4738 	fNumSorted = 0;
4739 
4740 	if (Window()->Lock() == false)
4741 		return;
4742 
4743 	bool doneSorting = false;
4744 	while (!doneSorting && !fSortCancelled) {
4745 
4746 		stack_entry* currentEntry = &stack[stackTop];
4747 
4748 		// xxx Can make the invalidate area smaller by finding the rect for the
4749 		// parent item and using that as the top of the invalid rect.
4750 
4751 		bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
4752 		if (!haveLock)
4753 			return ;	// window is gone.
4754 
4755 		// Fix focus rect.
4756 		InvalidateCachedPositions();
4757 		if (fCurrentState != INACTIVE)
4758 			fCurrentState = INACTIVE;	// sorry...
4759 
4760 		// next list.
4761 		bool foundNextList = false;
4762 		while (!foundNextList && !fSortCancelled) {
4763 			for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
4764 				index++) {
4765 				BRow* parentRow = currentEntry->list->ItemAt(index);
4766 				BRowContainer* childList = parentRow->fChildList;
4767 				if (childList != 0) {
4768 					currentEntry->listIndex = index + 1;
4769 					stackTop++;
4770 					ASSERT(stackTop < kMaxDepth);
4771 					stack[stackTop].listIndex = 0;
4772 					stack[stackTop].list = childList;
4773 					stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
4774 					foundNextList = true;
4775 					break;
4776 				}
4777 			}
4778 
4779 			if (!foundNextList) {
4780 				// back up
4781 				if (--stackTop < 0) {
4782 					doneSorting = true;
4783 					break;
4784 				}
4785 
4786 				currentEntry = &stack[stackTop];
4787 			}
4788 		}
4789 	}
4790 
4791 	Window()->Unlock();
4792 }
4793 
4794 
4795 void
4796 OutlineView::StartSorting()
4797 {
4798 	// If this view is not yet attached to a window, don't start a sort thread!
4799 	if (Window() == NULL)
4800 		return;
4801 
4802 	if (fSortThread != B_BAD_THREAD_ID) {
4803 		thread_info tinfo;
4804 		if (get_thread_info(fSortThread, &tinfo) == B_OK) {
4805 			// Unlock window so this won't deadlock (sort thread is probably
4806 			// waiting to lock window).
4807 
4808 			int lockCount = Window()->CountLocks();
4809 			for (int i = 0; i < lockCount; i++)
4810 				Window()->Unlock();
4811 
4812 			fSortCancelled = true;
4813 			int32 status;
4814 			wait_for_thread(fSortThread, &status);
4815 
4816 			while (lockCount--)
4817 				if (!Window()->Lock())
4818 					return ;	// Window is gone...
4819 		}
4820 	}
4821 
4822 	fSortCancelled = false;
4823 	fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
4824 	resume_thread(fSortThread);
4825 }
4826 
4827 
4828 void
4829 OutlineView::SelectRange(BRow* start, BRow* end)
4830 {
4831 	if (!start || !end)
4832 		return;
4833 
4834 	if (start == end)	// start is always selected when this is called
4835 		return;
4836 
4837 	RecursiveOutlineIterator iterator(&fRows, false);
4838 	while (iterator.CurrentRow() != 0) {
4839 		if (iterator.CurrentRow() == end) {
4840 			// reverse selection, swap to fix special case
4841 			BRow* temp = start;
4842 			start = end;
4843 			end = temp;
4844 			break;
4845 		} else if (iterator.CurrentRow() == start)
4846 			break;
4847 
4848 		iterator.GoToNext();
4849 	}
4850 
4851 	while (true) {
4852 		BRow* row = iterator.CurrentRow();
4853 		if (row) {
4854 			if (row->fNextSelected == 0) {
4855 				row->fNextSelected = fSelectionListDummyHead.fNextSelected;
4856 				row->fPrevSelected = &fSelectionListDummyHead;
4857 				row->fNextSelected->fPrevSelected = row;
4858 				row->fPrevSelected->fNextSelected = row;
4859 			}
4860 		} else
4861 			break;
4862 
4863 		if (row == end)
4864 			break;
4865 
4866 		iterator.GoToNext();
4867 	}
4868 
4869 	Invalidate();  // xxx make invalidation smaller
4870 }
4871 
4872 
4873 bool
4874 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
4875 {
4876 	bool result = false;
4877 	if (row != NULL && outParent != NULL) {
4878 		*outParent = row->fParent;
4879 
4880 		if (outParentIsVisible != NULL) {
4881 			// Walk up the parent chain to determine if this row is visible
4882 			*outParentIsVisible = true;
4883 			for (BRow* currentRow = row->fParent; currentRow != NULL;
4884 				currentRow = currentRow->fParent) {
4885 				if (!currentRow->fIsExpanded) {
4886 					*outParentIsVisible = false;
4887 					break;
4888 				}
4889 			}
4890 		}
4891 
4892 		result = *outParent != NULL;
4893 	}
4894 
4895 	return result;
4896 }
4897 
4898 
4899 int32
4900 OutlineView::IndexOf(BRow* row)
4901 {
4902 	if (row) {
4903 		if (row->fParent == 0)
4904 			return fRows.IndexOf(row);
4905 
4906 		ASSERT(row->fParent->fChildList);
4907 		return row->fParent->fChildList->IndexOf(row);
4908 	}
4909 
4910 	return B_ERROR;
4911 }
4912 
4913 
4914 void
4915 OutlineView::InvalidateCachedPositions()
4916 {
4917 	if (fFocusRow)
4918 		FindRect(fFocusRow, &fFocusRowRect);
4919 }
4920 
4921 
4922 float
4923 OutlineView::GetColumnPreferredWidth(BColumn* column)
4924 {
4925 	float preferred = 0.0;
4926 	for (RecursiveOutlineIterator iterator(&fRows); BRow* row =
4927 		iterator.CurrentRow(); iterator.GoToNext()) {
4928 		BField* field = row->GetField(column->fFieldID);
4929 		if (field) {
4930 			float width = column->GetPreferredWidth(field, this)
4931 				+ iterator.CurrentLevel() * kOutlineLevelIndent;
4932 			preferred = max_c(preferred, width);
4933 		}
4934 	}
4935 
4936 	BString name;
4937 	column->GetColumnName(&name);
4938 	preferred = max_c(preferred, StringWidth(name));
4939 
4940 	// Constrain to preferred width. This makes the method do a little
4941 	// more than asked, but it's for convenience.
4942 	if (preferred < column->MinWidth())
4943 		preferred = column->MinWidth();
4944 	else if (preferred > column->MaxWidth())
4945 		preferred = column->MaxWidth();
4946 
4947 	return preferred;
4948 }
4949 
4950 
4951 // #pragma mark -
4952 
4953 
4954 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
4955 	bool openBranchesOnly)
4956 	:
4957 	fStackIndex(0),
4958 	fCurrentListIndex(0),
4959 	fCurrentListDepth(0),
4960 	fOpenBranchesOnly(openBranchesOnly)
4961 {
4962 	if (list == 0 || list->CountItems() == 0)
4963 		fCurrentList = 0;
4964 	else
4965 		fCurrentList = list;
4966 }
4967 
4968 
4969 BRow*
4970 RecursiveOutlineIterator::CurrentRow() const
4971 {
4972 	if (fCurrentList == 0)
4973 		return 0;
4974 
4975 	return fCurrentList->ItemAt(fCurrentListIndex);
4976 }
4977 
4978 
4979 void
4980 RecursiveOutlineIterator::GoToNext()
4981 {
4982 	if (fCurrentList == 0)
4983 		return;
4984 	if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
4985 		fCurrentList = 0;
4986 		return;
4987 	}
4988 
4989 	BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
4990 	if(currentRow) {
4991 		if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
4992 			&& currentRow->fChildList->CountItems() > 0) {
4993 			// Visit child.
4994 			// Put current list on the stack if it needs to be revisited.
4995 			if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
4996 				fStack[fStackIndex].fRowSet = fCurrentList;
4997 				fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
4998 				fStack[fStackIndex].fDepth = fCurrentListDepth;
4999 				fStackIndex++;
5000 			}
5001 
5002 			fCurrentList = currentRow->fChildList;
5003 			fCurrentListIndex = 0;
5004 			fCurrentListDepth++;
5005 		} else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
5006 			fCurrentListIndex++; // next item in current list
5007 		else if (--fStackIndex >= 0) {
5008 			fCurrentList = fStack[fStackIndex].fRowSet;
5009 			fCurrentListIndex = fStack[fStackIndex].fIndex;
5010 			fCurrentListDepth = fStack[fStackIndex].fDepth;
5011 		} else
5012 			fCurrentList = 0;
5013 	}
5014 }
5015 
5016 
5017 int32
5018 RecursiveOutlineIterator::CurrentLevel() const
5019 {
5020 	return fCurrentListDepth;
5021 }
5022 
5023 
5024