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