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