xref: /haiku/src/kits/interface/ColumnListView.cpp (revision 1026b0a1a76dc88927bb8175c470f638dc5464ee)
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((int32)0);
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 	fShowingHorizontalScrollBar(showHorizontalScrollbar)
733 {
734 	_Init();
735 }
736 
737 
738 BColumnListView::BColumnListView(const char* name, uint32 flags,
739 	border_style border, bool showHorizontalScrollbar)
740 	:
741 	BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
742 	fStatusView(NULL),
743 	fSelectionMessage(NULL),
744 	fSortingEnabled(true),
745 	fLatchWidth(kLatchWidth),
746 	fBorderStyle(border),
747 	fShowingHorizontalScrollBar(showHorizontalScrollbar)
748 {
749 	_Init();
750 }
751 
752 
753 BColumnListView::~BColumnListView()
754 {
755 	while (BColumn* column = (BColumn*)fColumns.RemoveItem((int32)0))
756 		delete column;
757 }
758 
759 
760 bool
761 BColumnListView::InitiateDrag(BPoint, bool)
762 {
763 	return false;
764 }
765 
766 
767 void
768 BColumnListView::MessageDropped(BMessage*, BPoint)
769 {
770 }
771 
772 
773 void
774 BColumnListView::ExpandOrCollapse(BRow* row, bool Open)
775 {
776 	fOutlineView->ExpandOrCollapse(row, Open);
777 }
778 
779 
780 status_t
781 BColumnListView::Invoke(BMessage* message)
782 {
783 	if (message == 0)
784 		message = Message();
785 
786 	return BInvoker::Invoke(message);
787 }
788 
789 
790 void
791 BColumnListView::ItemInvoked()
792 {
793 	Invoke();
794 }
795 
796 
797 void
798 BColumnListView::SetInvocationMessage(BMessage* message)
799 {
800 	SetMessage(message);
801 }
802 
803 
804 BMessage*
805 BColumnListView::InvocationMessage() const
806 {
807 	return Message();
808 }
809 
810 
811 uint32
812 BColumnListView::InvocationCommand() const
813 {
814 	return Command();
815 }
816 
817 
818 BRow*
819 BColumnListView::FocusRow() const
820 {
821 	return fOutlineView->FocusRow();
822 }
823 
824 
825 void
826 BColumnListView::SetFocusRow(int32 Index, bool Select)
827 {
828 	SetFocusRow(RowAt(Index), Select);
829 }
830 
831 
832 void
833 BColumnListView::SetFocusRow(BRow* row, bool Select)
834 {
835 	fOutlineView->SetFocusRow(row, Select);
836 }
837 
838 
839 void
840 BColumnListView::SetMouseTrackingEnabled(bool Enabled)
841 {
842 	fOutlineView->SetMouseTrackingEnabled(Enabled);
843 }
844 
845 
846 list_view_type
847 BColumnListView::SelectionMode() const
848 {
849 	return fOutlineView->SelectionMode();
850 }
851 
852 
853 void
854 BColumnListView::Deselect(BRow* row)
855 {
856 	fOutlineView->Deselect(row);
857 }
858 
859 
860 void
861 BColumnListView::AddToSelection(BRow* row)
862 {
863 	fOutlineView->AddToSelection(row);
864 }
865 
866 
867 void
868 BColumnListView::DeselectAll()
869 {
870 	fOutlineView->DeselectAll();
871 }
872 
873 
874 BRow*
875 BColumnListView::CurrentSelection(BRow* lastSelected) const
876 {
877 	return fOutlineView->CurrentSelection(lastSelected);
878 }
879 
880 
881 void
882 BColumnListView::SelectionChanged()
883 {
884 	if (fSelectionMessage)
885 		Invoke(fSelectionMessage);
886 }
887 
888 
889 void
890 BColumnListView::SetSelectionMessage(BMessage* message)
891 {
892 	if (fSelectionMessage == message)
893 		return;
894 
895 	delete fSelectionMessage;
896 	fSelectionMessage = message;
897 }
898 
899 
900 BMessage*
901 BColumnListView::SelectionMessage()
902 {
903 	return fSelectionMessage;
904 }
905 
906 
907 uint32
908 BColumnListView::SelectionCommand() const
909 {
910 	if (fSelectionMessage)
911 		return fSelectionMessage->what;
912 
913 	return 0;
914 }
915 
916 
917 void
918 BColumnListView::SetSelectionMode(list_view_type mode)
919 {
920 	fOutlineView->SetSelectionMode(mode);
921 }
922 
923 
924 void
925 BColumnListView::SetSortingEnabled(bool enabled)
926 {
927 	fSortingEnabled = enabled;
928 	fSortColumns.MakeEmpty();
929 	fTitleView->Invalidate();	// Erase sort indicators
930 }
931 
932 
933 bool
934 BColumnListView::SortingEnabled() const
935 {
936 	return fSortingEnabled;
937 }
938 
939 
940 void
941 BColumnListView::SetSortColumn(BColumn* column, bool add, bool ascending)
942 {
943 	if (!SortingEnabled())
944 		return;
945 
946 	if (!add)
947 		fSortColumns.MakeEmpty();
948 
949 	if (!fSortColumns.HasItem(column))
950 		fSortColumns.AddItem(column);
951 
952 	column->fSortAscending = ascending;
953 	fTitleView->Invalidate();
954 	fOutlineView->StartSorting();
955 }
956 
957 
958 void
959 BColumnListView::ClearSortColumns()
960 {
961 	fSortColumns.MakeEmpty();
962 	fTitleView->Invalidate();	// Erase sort indicators
963 }
964 
965 
966 void
967 BColumnListView::AddStatusView(BView* view)
968 {
969 	BRect bounds = Bounds();
970 	float width = view->Bounds().Width();
971 	if (width > bounds.Width() / 2)
972 		width = bounds.Width() / 2;
973 
974 	fStatusView = view;
975 
976 	Window()->BeginViewTransaction();
977 	fHorizontalScrollBar->ResizeBy(-(width + 1), 0);
978 	fHorizontalScrollBar->MoveBy((width + 1), 0);
979 	AddChild(view);
980 
981 	BRect viewRect(bounds);
982 	viewRect.right = width;
983 	viewRect.top = viewRect.bottom - B_H_SCROLL_BAR_HEIGHT;
984 	if (fBorderStyle == B_PLAIN_BORDER)
985 		viewRect.OffsetBy(1, -1);
986 	else if (fBorderStyle == B_FANCY_BORDER)
987 		viewRect.OffsetBy(2, -2);
988 
989 	view->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
990 	view->ResizeTo(viewRect.Width(), viewRect.Height());
991 	view->MoveTo(viewRect.left, viewRect.top);
992 	Window()->EndViewTransaction();
993 }
994 
995 
996 BView*
997 BColumnListView::RemoveStatusView()
998 {
999 	if (fStatusView) {
1000 		float width = fStatusView->Bounds().Width();
1001 		Window()->BeginViewTransaction();
1002 		fStatusView->RemoveSelf();
1003 		fHorizontalScrollBar->MoveBy(-width, 0);
1004 		fHorizontalScrollBar->ResizeBy(width, 0);
1005 		Window()->EndViewTransaction();
1006 	}
1007 
1008 	BView* view = fStatusView;
1009 	fStatusView = 0;
1010 	return view;
1011 }
1012 
1013 
1014 void
1015 BColumnListView::AddColumn(BColumn* column, int32 logicalFieldIndex)
1016 {
1017 	ASSERT(column != NULL);
1018 
1019 	column->fList = this;
1020 	column->fFieldID = logicalFieldIndex;
1021 
1022 	// sanity check.  If there is already a field with this ID, remove it.
1023 	for (int32 index = 0; index < fColumns.CountItems(); index++) {
1024 		BColumn* existingColumn = (BColumn*) fColumns.ItemAt(index);
1025 		if (existingColumn && existingColumn->fFieldID == logicalFieldIndex) {
1026 			RemoveColumn(existingColumn);
1027 			break;
1028 		}
1029 	}
1030 
1031 	if (column->Width() < column->MinWidth())
1032 		column->SetWidth(column->MinWidth());
1033 	else if (column->Width() > column->MaxWidth())
1034 		column->SetWidth(column->MaxWidth());
1035 
1036 	fColumns.AddItem((void*) column);
1037 	fTitleView->ColumnAdded(column);
1038 }
1039 
1040 
1041 void
1042 BColumnListView::MoveColumn(BColumn* column, int32 index)
1043 {
1044 	ASSERT(column != NULL);
1045 	fTitleView->MoveColumn(column, index);
1046 }
1047 
1048 
1049 void
1050 BColumnListView::RemoveColumn(BColumn* column)
1051 {
1052 	if (fColumns.HasItem(column)) {
1053 		SetColumnVisible(column, false);
1054 		if (Window() != NULL)
1055 			Window()->UpdateIfNeeded();
1056 		fColumns.RemoveItem(column);
1057 	}
1058 }
1059 
1060 
1061 int32
1062 BColumnListView::CountColumns() const
1063 {
1064 	return fColumns.CountItems();
1065 }
1066 
1067 
1068 BColumn*
1069 BColumnListView::ColumnAt(int32 field) const
1070 {
1071 	return (BColumn*) fColumns.ItemAt(field);
1072 }
1073 
1074 
1075 BColumn*
1076 BColumnListView::ColumnAt(BPoint point) const
1077 {
1078 	float left = MAX(kLeftMargin, LatchWidth());
1079 
1080 	for (int i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1081 		if (!column->IsVisible())
1082 			continue;
1083 
1084 		float right = left + column->Width();
1085 		if (point.x >= left && point.x <= right)
1086 			return column;
1087 
1088 		left = right + 1;
1089 	}
1090 
1091 	return NULL;
1092 }
1093 
1094 
1095 void
1096 BColumnListView::SetColumnVisible(BColumn* column, bool visible)
1097 {
1098 	fTitleView->SetColumnVisible(column, visible);
1099 }
1100 
1101 
1102 void
1103 BColumnListView::SetColumnVisible(int32 index, bool isVisible)
1104 {
1105 	BColumn* column = ColumnAt(index);
1106 	if (column)
1107 		column->SetVisible(isVisible);
1108 }
1109 
1110 
1111 bool
1112 BColumnListView::IsColumnVisible(int32 index) const
1113 {
1114 	BColumn* column = ColumnAt(index);
1115 	if (column)
1116 		return column->IsVisible();
1117 
1118 	return false;
1119 }
1120 
1121 
1122 void
1123 BColumnListView::SetColumnFlags(column_flags flags)
1124 {
1125 	fTitleView->SetColumnFlags(flags);
1126 }
1127 
1128 
1129 void
1130 BColumnListView::ResizeColumnToPreferred(int32 index)
1131 {
1132 	BColumn* column = ColumnAt(index);
1133 	if (column == NULL)
1134 		return;
1135 
1136 	// get the preferred column width
1137 	float width = fOutlineView->GetColumnPreferredWidth(column);
1138 
1139 	// set it
1140 	float oldWidth = column->Width();
1141 	column->SetWidth(width);
1142 
1143 	fTitleView->ColumnResized(column, oldWidth);
1144 	fOutlineView->Invalidate();
1145 }
1146 
1147 
1148 void
1149 BColumnListView::ResizeAllColumnsToPreferred()
1150 {
1151 	int32 count = CountColumns();
1152 	for (int32 i = 0; i < count; i++)
1153 		ResizeColumnToPreferred(i);
1154 }
1155 
1156 
1157 const BRow*
1158 BColumnListView::RowAt(int32 Index, BRow* parentRow) const
1159 {
1160 	if (parentRow == 0)
1161 		return fOutlineView->RowList()->ItemAt(Index);
1162 
1163 	return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : NULL;
1164 }
1165 
1166 
1167 BRow*
1168 BColumnListView::RowAt(int32 Index, BRow* parentRow)
1169 {
1170 	if (parentRow == 0)
1171 		return fOutlineView->RowList()->ItemAt(Index);
1172 
1173 	return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : 0;
1174 }
1175 
1176 
1177 const BRow*
1178 BColumnListView::RowAt(BPoint point) const
1179 {
1180 	float top;
1181 	int32 indent;
1182 	return fOutlineView->FindRow(point.y, &indent, &top);
1183 }
1184 
1185 
1186 BRow*
1187 BColumnListView::RowAt(BPoint point)
1188 {
1189 	float top;
1190 	int32 indent;
1191 	return fOutlineView->FindRow(point.y, &indent, &top);
1192 }
1193 
1194 
1195 bool
1196 BColumnListView::GetRowRect(const BRow* row, BRect* outRect) const
1197 {
1198 	return fOutlineView->FindRect(row, outRect);
1199 }
1200 
1201 
1202 bool
1203 BColumnListView::FindParent(BRow* row, BRow** _parent, bool* _isVisible) const
1204 {
1205 	return fOutlineView->FindParent(row, _parent, _isVisible);
1206 }
1207 
1208 
1209 int32
1210 BColumnListView::IndexOf(BRow* row)
1211 {
1212 	return fOutlineView->IndexOf(row);
1213 }
1214 
1215 
1216 int32
1217 BColumnListView::CountRows(BRow* parentRow) const
1218 {
1219 	if (parentRow == 0)
1220 		return fOutlineView->RowList()->CountItems();
1221 	if (parentRow->fChildList)
1222 		return parentRow->fChildList->CountItems();
1223 	else
1224 		return 0;
1225 }
1226 
1227 
1228 void
1229 BColumnListView::AddRow(BRow* row, BRow* parentRow)
1230 {
1231 	AddRow(row, -1, parentRow);
1232 }
1233 
1234 
1235 void
1236 BColumnListView::AddRow(BRow* row, int32 index, BRow* parentRow)
1237 {
1238 	row->fChildList = 0;
1239 	row->fList = this;
1240 	row->ValidateFields();
1241 	fOutlineView->AddRow(row, index, parentRow);
1242 }
1243 
1244 
1245 void
1246 BColumnListView::RemoveRow(BRow* row)
1247 {
1248 	fOutlineView->RemoveRow(row);
1249 	row->fList = NULL;
1250 }
1251 
1252 
1253 void
1254 BColumnListView::UpdateRow(BRow* row)
1255 {
1256 	fOutlineView->UpdateRow(row);
1257 }
1258 
1259 
1260 void
1261 BColumnListView::ScrollTo(const BRow* row)
1262 {
1263 	fOutlineView->ScrollTo(row);
1264 }
1265 
1266 
1267 void
1268 BColumnListView::ScrollTo(BPoint point)
1269 {
1270 	fOutlineView->ScrollTo(point);
1271 }
1272 
1273 
1274 void
1275 BColumnListView::Clear()
1276 {
1277 	fOutlineView->Clear();
1278 }
1279 
1280 
1281 void
1282 BColumnListView::SetFont(const BFont* font, uint32 mask)
1283 {
1284 	// This method is deprecated.
1285 	fOutlineView->SetFont(font, mask);
1286 	fTitleView->SetFont(font, mask);
1287 }
1288 
1289 
1290 void
1291 BColumnListView::SetFont(ColumnListViewFont font_num, const BFont* font,
1292 	uint32 mask)
1293 {
1294 	switch (font_num) {
1295 		case B_FONT_ROW:
1296 			fOutlineView->SetFont(font, mask);
1297 			break;
1298 
1299 		case B_FONT_HEADER:
1300 			fTitleView->SetFont(font, mask);
1301 			break;
1302 
1303 		default:
1304 			ASSERT(false);
1305 			break;
1306 	}
1307 }
1308 
1309 
1310 void
1311 BColumnListView::GetFont(ColumnListViewFont font_num, BFont* font) const
1312 {
1313 	switch (font_num) {
1314 		case B_FONT_ROW:
1315 			fOutlineView->GetFont(font);
1316 			break;
1317 
1318 		case B_FONT_HEADER:
1319 			fTitleView->GetFont(font);
1320 			break;
1321 
1322 		default:
1323 			ASSERT(false);
1324 			break;
1325 	}
1326 }
1327 
1328 
1329 void
1330 BColumnListView::SetColor(ColumnListViewColor color_num, const rgb_color color)
1331 {
1332 	if ((int)color_num < 0) {
1333 		ASSERT(false);
1334 		color_num = (ColumnListViewColor) 0;
1335 	}
1336 
1337 	if ((int)color_num >= (int)B_COLOR_TOTAL) {
1338 		ASSERT(false);
1339 		color_num = (ColumnListViewColor) (B_COLOR_TOTAL - 1);
1340 	}
1341 
1342 	fColorList[color_num] = color;
1343 }
1344 
1345 
1346 rgb_color
1347 BColumnListView::Color(ColumnListViewColor color_num) const
1348 {
1349 	if ((int)color_num < 0) {
1350 		ASSERT(false);
1351 		color_num = (ColumnListViewColor) 0;
1352 	}
1353 
1354 	if ((int)color_num >= (int)B_COLOR_TOTAL) {
1355 		ASSERT(false);
1356 		color_num = (ColumnListViewColor) (B_COLOR_TOTAL - 1);
1357 	}
1358 
1359 	return fColorList[color_num];
1360 }
1361 
1362 
1363 void
1364 BColumnListView::SetHighColor(rgb_color color)
1365 {
1366 	BView::SetHighColor(color);
1367 //	fOutlineView->Invalidate();	// Redraw things with the new color
1368 								// Note that this will currently cause
1369 								// an infinite loop, refreshing over and over.
1370 								// A better solution is needed.
1371 }
1372 
1373 
1374 void
1375 BColumnListView::SetSelectionColor(rgb_color color)
1376 {
1377 	fColorList[B_COLOR_SELECTION] = color;
1378 }
1379 
1380 
1381 void
1382 BColumnListView::SetBackgroundColor(rgb_color color)
1383 {
1384 	fColorList[B_COLOR_BACKGROUND] = color;
1385 	fOutlineView->Invalidate();	// Repaint with new color
1386 }
1387 
1388 
1389 void
1390 BColumnListView::SetEditColor(rgb_color color)
1391 {
1392 	fColorList[B_COLOR_EDIT_BACKGROUND] = color;
1393 }
1394 
1395 
1396 const rgb_color
1397 BColumnListView::SelectionColor() const
1398 {
1399 	return fColorList[B_COLOR_SELECTION];
1400 }
1401 
1402 
1403 const rgb_color
1404 BColumnListView::BackgroundColor() const
1405 {
1406 	return fColorList[B_COLOR_BACKGROUND];
1407 }
1408 
1409 
1410 const rgb_color
1411 BColumnListView::EditColor() const
1412 {
1413 	return fColorList[B_COLOR_EDIT_BACKGROUND];
1414 }
1415 
1416 
1417 BPoint
1418 BColumnListView::SuggestTextPosition(const BRow* row,
1419 	const BColumn* inColumn) const
1420 {
1421 	BRect rect;
1422 	GetRowRect(row, &rect);
1423 	if (inColumn) {
1424 		float leftEdge = MAX(kLeftMargin, LatchWidth());
1425 		for (int index = 0; index < fColumns.CountItems(); index++) {
1426 			BColumn* column = (BColumn*) fColumns.ItemAt(index);
1427 			if (!column->IsVisible())
1428 				continue;
1429 
1430 			if (column == inColumn) {
1431 				rect.left = leftEdge;
1432 				rect.right = rect.left + column->Width();
1433 				break;
1434 			}
1435 
1436 			leftEdge += column->Width() + 1;
1437 		}
1438 	}
1439 
1440 	font_height fh;
1441 	fOutlineView->GetFontHeight(&fh);
1442 	float baseline = floor(rect.top + fh.ascent
1443 							+ (rect.Height()+1-(fh.ascent+fh.descent))/2);
1444 	return BPoint(rect.left + 8, baseline);
1445 }
1446 
1447 
1448 void
1449 BColumnListView::SetLatchWidth(float width)
1450 {
1451 	fLatchWidth = width;
1452 	Invalidate();
1453 }
1454 
1455 
1456 float
1457 BColumnListView::LatchWidth() const
1458 {
1459 	return fLatchWidth;
1460 }
1461 
1462 void
1463 BColumnListView::DrawLatch(BView* view, BRect rect, LatchType position, BRow*)
1464 {
1465 	const int32 rectInset = 4;
1466 
1467 	view->SetHighColor(0, 0, 0);
1468 
1469 	// Make Square
1470 	int32 sideLen = rect.IntegerWidth();
1471 	if (sideLen > rect.IntegerHeight())
1472 		sideLen = rect.IntegerHeight();
1473 
1474 	// Make Center
1475 	int32 halfWidth  = rect.IntegerWidth() / 2;
1476 	int32 halfHeight = rect.IntegerHeight() / 2;
1477 	int32 halfSide   = sideLen / 2;
1478 
1479 	float left = rect.left + halfWidth  - halfSide;
1480 	float top  = rect.top  + halfHeight - halfSide;
1481 
1482 	BRect itemRect(left, top, left + sideLen, top + sideLen);
1483 
1484 	// Why it is a pixel high? I don't know.
1485 	itemRect.OffsetBy(0, -1);
1486 
1487 	itemRect.InsetBy(rectInset, rectInset);
1488 
1489 	// Make it an odd number of pixels wide, the latch looks better this way
1490 	if ((itemRect.IntegerWidth() % 2) == 1) {
1491 		itemRect.right += 1;
1492 		itemRect.bottom += 1;
1493 	}
1494 
1495 	switch (position) {
1496 		case B_OPEN_LATCH:
1497 			view->StrokeRect(itemRect);
1498 			view->StrokeLine(
1499 				BPoint(itemRect.left + 2,
1500 					(itemRect.top + itemRect.bottom) / 2),
1501 				BPoint(itemRect.right - 2,
1502 					(itemRect.top + itemRect.bottom) / 2));
1503 			break;
1504 
1505 		case B_PRESSED_LATCH:
1506 			view->StrokeRect(itemRect);
1507 			view->StrokeLine(
1508 				BPoint(itemRect.left + 2,
1509 					(itemRect.top + itemRect.bottom) / 2),
1510 				BPoint(itemRect.right - 2,
1511 					(itemRect.top + itemRect.bottom) / 2));
1512 			view->StrokeLine(
1513 				BPoint((itemRect.left + itemRect.right) / 2,
1514 					itemRect.top +  2),
1515 				BPoint((itemRect.left + itemRect.right) / 2,
1516 					itemRect.bottom - 2));
1517 			view->InvertRect(itemRect);
1518 			break;
1519 
1520 		case B_CLOSED_LATCH:
1521 			view->StrokeRect(itemRect);
1522 			view->StrokeLine(
1523 				BPoint(itemRect.left + 2,
1524 					(itemRect.top + itemRect.bottom) / 2),
1525 				BPoint(itemRect.right - 2,
1526 					(itemRect.top + itemRect.bottom) / 2));
1527 			view->StrokeLine(
1528 				BPoint((itemRect.left + itemRect.right) / 2,
1529 					itemRect.top +  2),
1530 				BPoint((itemRect.left + itemRect.right) / 2,
1531 					itemRect.bottom - 2));
1532 			break;
1533 
1534 		case B_NO_LATCH:
1535 			// No drawing
1536 			break;
1537 	}
1538 }
1539 
1540 
1541 void
1542 BColumnListView::MakeFocus(bool isFocus)
1543 {
1544 	if (fBorderStyle != B_NO_BORDER) {
1545 		// Redraw focus marks around view
1546 		Invalidate();
1547 		fHorizontalScrollBar->SetBorderHighlighted(isFocus);
1548 		fVerticalScrollBar->SetBorderHighlighted(isFocus);
1549 	}
1550 
1551 	BView::MakeFocus(isFocus);
1552 }
1553 
1554 
1555 void
1556 BColumnListView::MessageReceived(BMessage* message)
1557 {
1558 	// Propagate mouse wheel messages down to child, so that it can
1559 	// scroll.  Note we have done so, so we don't go into infinite
1560 	// recursion if this comes back up here.
1561 	if (message->what == B_MOUSE_WHEEL_CHANGED) {
1562 		bool handled;
1563 		if (message->FindBool("be:clvhandled", &handled) != B_OK) {
1564 			message->AddBool("be:clvhandled", true);
1565 			fOutlineView->MessageReceived(message);
1566 			return;
1567 		}
1568 	}
1569 
1570 	BView::MessageReceived(message);
1571 }
1572 
1573 
1574 void
1575 BColumnListView::KeyDown(const char* bytes, int32 numBytes)
1576 {
1577 	char c = bytes[0];
1578 	switch (c) {
1579 		case B_RIGHT_ARROW:
1580 		case B_LEFT_ARROW:
1581 		{
1582 			float  minVal, maxVal;
1583 			fHorizontalScrollBar->GetRange(&minVal, &maxVal);
1584 			float smallStep, largeStep;
1585 			fHorizontalScrollBar->GetSteps(&smallStep, &largeStep);
1586 			float oldVal = fHorizontalScrollBar->Value();
1587 			float newVal = oldVal;
1588 
1589 			if (c == B_LEFT_ARROW)
1590 				newVal -= smallStep;
1591 			else if (c == B_RIGHT_ARROW)
1592 				newVal += smallStep;
1593 
1594 			if (newVal < minVal)
1595 				newVal = minVal;
1596 			else if (newVal > maxVal)
1597 				newVal = maxVal;
1598 
1599 			fHorizontalScrollBar->SetValue(newVal);
1600 			break;
1601 		}
1602 
1603 		case B_DOWN_ARROW:
1604 			fOutlineView->ChangeFocusRow(false,
1605 				(modifiers() & B_CONTROL_KEY) == 0,
1606 				(modifiers() & B_SHIFT_KEY) != 0);
1607 			break;
1608 
1609 		case B_UP_ARROW:
1610 			fOutlineView->ChangeFocusRow(true,
1611 				(modifiers() & B_CONTROL_KEY) == 0,
1612 				(modifiers() & B_SHIFT_KEY) != 0);
1613 			break;
1614 
1615 		case B_PAGE_UP:
1616 		case B_PAGE_DOWN:
1617 		{
1618 			float minValue, maxValue;
1619 			fVerticalScrollBar->GetRange(&minValue, &maxValue);
1620 			float smallStep, largeStep;
1621 			fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
1622 			float currentValue = fVerticalScrollBar->Value();
1623 			float newValue = currentValue;
1624 
1625 			if (c == B_PAGE_UP)
1626 				newValue -= largeStep;
1627 			else
1628 				newValue += largeStep;
1629 
1630 			if (newValue > maxValue)
1631 				newValue = maxValue;
1632 			else if (newValue < minValue)
1633 				newValue = minValue;
1634 
1635 			fVerticalScrollBar->SetValue(newValue);
1636 
1637 			// Option + pgup or pgdn scrolls and changes the selection.
1638 			if (modifiers() & B_OPTION_KEY)
1639 				fOutlineView->MoveFocusToVisibleRect();
1640 
1641 			break;
1642 		}
1643 
1644 		case B_ENTER:
1645 			Invoke();
1646 			break;
1647 
1648 		case B_SPACE:
1649 			fOutlineView->ToggleFocusRowSelection(
1650 				(modifiers() & B_SHIFT_KEY) != 0);
1651 			break;
1652 
1653 		case '+':
1654 			fOutlineView->ToggleFocusRowOpen();
1655 			break;
1656 
1657 		default:
1658 			BView::KeyDown(bytes, numBytes);
1659 	}
1660 }
1661 
1662 
1663 void
1664 BColumnListView::AttachedToWindow()
1665 {
1666 	if (!Messenger().IsValid())
1667 		SetTarget(Window());
1668 
1669 	if (SortingEnabled()) fOutlineView->StartSorting();
1670 }
1671 
1672 
1673 void
1674 BColumnListView::WindowActivated(bool active)
1675 {
1676 	fOutlineView->Invalidate();
1677 		// Focus and selection appearance changes with focus
1678 
1679 	Invalidate(); 	// Redraw focus marks around view
1680 	BView::WindowActivated(active);
1681 }
1682 
1683 
1684 void
1685 BColumnListView::Draw(BRect updateRect)
1686 {
1687 	BRect rect = Bounds();
1688 
1689 	if (be_control_look != NULL) {
1690 		uint32 flags = 0;
1691 		if (IsFocus() && Window()->IsActive())
1692 			flags |= BControlLook::B_FOCUSED;
1693 
1694 		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1695 
1696 		BRect verticalScrollBarFrame;
1697 		if (!fVerticalScrollBar->IsHidden())
1698 			verticalScrollBarFrame = fVerticalScrollBar->Frame();
1699 		BRect horizontalScrollBarFrame;
1700 		if (!fHorizontalScrollBar->IsHidden())
1701 			horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
1702 
1703 		if (fBorderStyle == B_NO_BORDER) {
1704 			// We still draw the left/top border, but not focused.
1705 			// The scrollbars cannot be displayed without frame and
1706 			// it looks bad to have no frame only along the left/top
1707 			// side.
1708 			rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
1709 			SetHighColor(borderColor);
1710 			StrokeLine(BPoint(rect.left, rect.bottom),
1711 				BPoint(rect.left, rect.top));
1712 			StrokeLine(BPoint(rect.left + 1, rect.top),
1713 				BPoint(rect.right, rect.top));
1714 		}
1715 
1716 		be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1717 			verticalScrollBarFrame, horizontalScrollBarFrame,
1718 			base, fBorderStyle, flags);
1719 
1720 		return;
1721 	}
1722 
1723 	BRect cornerRect(rect.right - B_V_SCROLL_BAR_WIDTH,
1724 		rect.bottom - B_H_SCROLL_BAR_HEIGHT, rect.right, rect.bottom);
1725 	if (fBorderStyle == B_PLAIN_BORDER) {
1726 		BView::SetHighColor(0, 0, 0);
1727 		StrokeRect(rect);
1728 		cornerRect.OffsetBy(-1, -1);
1729 	} else if (fBorderStyle == B_FANCY_BORDER) {
1730 		bool isFocus = IsFocus() && Window()->IsActive();
1731 
1732 		if (isFocus) {
1733 			// TODO: Need to find focus color programatically
1734 			BView::SetHighColor(0, 0, 190);
1735 		} else
1736 			BView::SetHighColor(255, 255, 255);
1737 
1738 		StrokeRect(rect);
1739 		if (!isFocus)
1740 			BView::SetHighColor(184, 184, 184);
1741 		else
1742 			BView::SetHighColor(152, 152, 152);
1743 
1744 		rect.InsetBy(1,1);
1745 		StrokeRect(rect);
1746 		cornerRect.OffsetBy(-2, -2);
1747 	}
1748 
1749 	BView::SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
1750 		// fills lower right rect between scroll bars
1751 	FillRect(cornerRect);
1752 }
1753 
1754 
1755 void
1756 BColumnListView::SaveState(BMessage* msg)
1757 {
1758 	msg->MakeEmpty();
1759 
1760 	for (int32 i = 0; BColumn* col = (BColumn*)fColumns.ItemAt(i); i++) {
1761 		msg->AddInt32("ID",col->fFieldID);
1762 		msg->AddFloat("width", col->fWidth);
1763 		msg->AddBool("visible", col->fVisible);
1764 	}
1765 
1766 	msg->AddBool("sortingenabled", fSortingEnabled);
1767 
1768 	if (fSortingEnabled) {
1769 		for (int32 i = 0; BColumn* col = (BColumn*)fSortColumns.ItemAt(i);
1770 				i++) {
1771 			msg->AddInt32("sortID", col->fFieldID);
1772 			msg->AddBool("sortascending", col->fSortAscending);
1773 		}
1774 	}
1775 }
1776 
1777 
1778 void
1779 BColumnListView::LoadState(BMessage* msg)
1780 {
1781 	int32 id;
1782 	for (int i = 0; msg->FindInt32("ID", i, &id) == B_OK; i++) {
1783 		for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
1784 			if (column->fFieldID == id) {
1785 				// move this column to position 'i' and set its attributes
1786 				MoveColumn(column, i);
1787 				float width;
1788 				if (msg->FindFloat("width", i, &width) == B_OK)
1789 					column->SetWidth(width);
1790 				bool visible;
1791 				if (msg->FindBool("visible", i, &visible) == B_OK)
1792 					column->SetVisible(visible);
1793 			}
1794 		}
1795 	}
1796 	bool b;
1797 	if (msg->FindBool("sortingenabled", &b) == B_OK) {
1798 		SetSortingEnabled(b);
1799 		for (int k = 0; msg->FindInt32("sortID", k, &id) == B_OK; k++) {
1800 			for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
1801 					j++) {
1802 				if (column->fFieldID == id) {
1803 					// add this column to the sort list
1804 					bool value;
1805 					if (msg->FindBool("sortascending", k, &value) == B_OK)
1806 						SetSortColumn(column, true, value);
1807 				}
1808 			}
1809 		}
1810 	}
1811 }
1812 
1813 
1814 void
1815 BColumnListView::SetEditMode(bool state)
1816 {
1817 	fOutlineView->SetEditMode(state);
1818 	fTitleView->SetEditMode(state);
1819 }
1820 
1821 
1822 void
1823 BColumnListView::Refresh()
1824 {
1825 	if (LockLooper()) {
1826 		Invalidate();
1827 		fOutlineView->FixScrollBar (true);
1828 		fOutlineView->Invalidate();
1829 		Window()->UpdateIfNeeded();
1830 		UnlockLooper();
1831 	}
1832 }
1833 
1834 
1835 BSize
1836 BColumnListView::MinSize()
1837 {
1838 	BSize size;
1839 	size.width = 100;
1840 	size.height = kTitleHeight + 4 * B_H_SCROLL_BAR_HEIGHT;
1841 	if (!fHorizontalScrollBar->IsHidden())
1842 		size.height += fHorizontalScrollBar->Frame().Height() + 1;
1843 	// TODO: Take border size into account
1844 
1845 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1846 }
1847 
1848 
1849 BSize
1850 BColumnListView::PreferredSize()
1851 {
1852 	BSize size = MinSize();
1853 	size.height += ceilf(be_plain_font->Size()) * 20;
1854 
1855 	// return MinSize().width if there are no columns.
1856 	int32 count = CountColumns();
1857 	if (count > 0) {
1858 		BRect titleRect;
1859 		BRect outlineRect;
1860 		BRect vScrollBarRect;
1861 		BRect hScrollBarRect;
1862 		_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1863 			hScrollBarRect);
1864 		// Start with the extra width for border and scrollbars etc.
1865 		size.width = titleRect.left - Bounds().left;
1866 		size.width += Bounds().right - titleRect.right;
1867 		// If we want all columns to be visible at their preferred width,
1868 		// we also need to add the extra margin width that the TitleView
1869 		// uses to compute its _VirtualWidth() for the horizontal scroll bar.
1870 		size.width += fTitleView->MarginWidth();
1871 		for (int32 i = 0; i < count; i++) {
1872 			BColumn* column = ColumnAt(i);
1873 			if (column != NULL)
1874 				size.width += fOutlineView->GetColumnPreferredWidth(column);
1875 		}
1876 	}
1877 
1878 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1879 }
1880 
1881 
1882 BSize
1883 BColumnListView::MaxSize()
1884 {
1885 	BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
1886 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1887 }
1888 
1889 
1890 void
1891 BColumnListView::LayoutInvalidated(bool descendants)
1892 {
1893 }
1894 
1895 
1896 void
1897 BColumnListView::DoLayout()
1898 {
1899 	if (!(Flags() & B_SUPPORTS_LAYOUT))
1900 		return;
1901 
1902 	BRect titleRect;
1903 	BRect outlineRect;
1904 	BRect vScrollBarRect;
1905 	BRect hScrollBarRect;
1906 	_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1907 		hScrollBarRect);
1908 
1909 	fTitleView->MoveTo(titleRect.LeftTop());
1910 	fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
1911 
1912 	fOutlineView->MoveTo(outlineRect.LeftTop());
1913 	fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
1914 
1915 	fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
1916 	fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
1917 		vScrollBarRect.Height());
1918 
1919 	fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
1920 	fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
1921 		hScrollBarRect.Height());
1922 
1923 	fOutlineView->FixScrollBar(true);
1924 }
1925 
1926 
1927 void
1928 BColumnListView::_Init()
1929 {
1930 	SetViewColor(B_TRANSPARENT_32_BIT);
1931 
1932 	BRect bounds(Bounds());
1933 	if (bounds.Width() <= 0)
1934 		bounds.right = 100;
1935 	if (bounds.Height() <= 0)
1936 		bounds.bottom = 100;
1937 
1938 	for (int i = 0; i < (int)B_COLOR_TOTAL; i++)
1939 		fColorList[i] = kColor[i];
1940 
1941 	BRect titleRect;
1942 	BRect outlineRect;
1943 	BRect vScrollBarRect;
1944 	BRect hScrollBarRect;
1945 	_GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
1946 		hScrollBarRect);
1947 
1948 	fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
1949 	AddChild(fOutlineView);
1950 
1951 
1952 	fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
1953 		&fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
1954 	AddChild(fTitleView);
1955 
1956 	fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
1957 		fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
1958 	AddChild(fVerticalScrollBar);
1959 
1960 	fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
1961 		"horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
1962 	AddChild(fHorizontalScrollBar);
1963 
1964 	if (!fShowingHorizontalScrollBar)
1965 		fHorizontalScrollBar->Hide();
1966 
1967 	fOutlineView->FixScrollBar(true);
1968 }
1969 
1970 
1971 void
1972 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
1973 	BRect& outlineRect, 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 (fShowingHorizontalScrollBar)
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 (fShowingHorizontalScrollBar)
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 			BMessage* message = Window()->CurrentMessage();
3517 			int32 buttons = 1;
3518 			message->FindInt32("buttons", &buttons);
3519 			fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
3520 				fCurrentField, fFieldRect, position, buttons);
3521 		}
3522 
3523 		if (!fEditMode) {
3524 
3525 			fTargetRow = row;
3526 			fTargetRowTop = rowTop;
3527 			FindVisibleRect(fFocusRow, &fFocusRowRect);
3528 
3529 			float leftWidgetBoundry = indent * kOutlineLevelIndent
3530 				+ MAX(kLeftMargin, fMasterView->LatchWidth())
3531 				- fMasterView->LatchWidth();
3532 			fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
3533 				+ fMasterView->LatchWidth(), rowTop + row->Height());
3534 			if (fLatchRect.Contains(position) && row->HasLatch()) {
3535 				fCurrentState = LATCH_CLICKED;
3536 				if (fTargetRow->fNextSelected != 0)
3537 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3538 				else
3539 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3540 
3541 				FillRect(fLatchRect);
3542 				if (fLatchRect.Contains(position)) {
3543 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3544 						row);
3545 				} else {
3546 					fMasterView->DrawLatch(this, fLatchRect,
3547 						fTargetRow->fIsExpanded ? B_OPEN_LATCH
3548 						: B_CLOSED_LATCH, row);
3549 				}
3550 			} else {
3551 				Invalidate(fFocusRowRect);
3552 				fFocusRow = fTargetRow;
3553 				FindVisibleRect(fFocusRow, &fFocusRowRect);
3554 
3555 				ASSERT(fTargetRow != 0);
3556 
3557 				if ((modifiers() & B_CONTROL_KEY) == 0)
3558 					DeselectAll();
3559 
3560 				if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
3561 					&& fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
3562 					SelectRange(fFirstSelectedItem, fTargetRow);
3563 				}
3564 				else {
3565 					if (fTargetRow->fNextSelected != 0) {
3566 						// Unselect row
3567 						fTargetRow->fNextSelected->fPrevSelected
3568 							= fTargetRow->fPrevSelected;
3569 						fTargetRow->fPrevSelected->fNextSelected
3570 							= fTargetRow->fNextSelected;
3571 						fTargetRow->fPrevSelected = 0;
3572 						fTargetRow->fNextSelected = 0;
3573 						fFirstSelectedItem = NULL;
3574 					} else {
3575 						// Select row
3576 						if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3577 							DeselectAll();
3578 
3579 						fTargetRow->fNextSelected
3580 							= fSelectionListDummyHead.fNextSelected;
3581 						fTargetRow->fPrevSelected
3582 							= &fSelectionListDummyHead;
3583 						fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
3584 						fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
3585 						fFirstSelectedItem = fTargetRow;
3586 					}
3587 
3588 					Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
3589 						fVisibleRect.right,
3590 						fTargetRowTop + fTargetRow->Height()));
3591 				}
3592 
3593 				fCurrentState = ROW_CLICKED;
3594 				if (fLastSelectedItem != fTargetRow)
3595 					reset_click_count = true;
3596 				fLastSelectedItem = fTargetRow;
3597 				fMasterView->SelectionChanged();
3598 
3599 			}
3600 		}
3601 
3602 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
3603 			B_NO_POINTER_HISTORY);
3604 
3605 	} else if (fFocusRow != 0) {
3606 		// User clicked in open space, unhighlight focus row.
3607 		FindVisibleRect(fFocusRow, &fFocusRowRect);
3608 		fFocusRow = 0;
3609 		Invalidate(fFocusRowRect);
3610 	}
3611 
3612 	// We stash the click counts here because the 'clicks' field
3613 	// is not in the CurrentMessage() when MouseUp is called... ;(
3614 	if (reset_click_count)
3615 		fClickCount = 1;
3616 	else
3617 		Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
3618 	fClickPoint = position;
3619 
3620 }
3621 
3622 
3623 void
3624 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
3625 	const BMessage* /*dragMessage*/)
3626 {
3627 	if (!fMouseDown) {
3628 		// Update fCurrentField
3629 		bool handle_field = false;
3630 		BField* new_field = 0;
3631 		BRow* new_row = 0;
3632 		BColumn* new_column = 0;
3633 		BRect new_rect(0,0,0,0);
3634 		if (position.y >=0 ) {
3635 			float top;
3636 			int32 indent;
3637 			BRow* row = FindRow(position.y, &indent, &top);
3638 			if (row && position.x >=0 ) {
3639 				float x=0;
3640 				for (int32 c=0;c<fMasterView->CountColumns();c++) {
3641 					new_column = fMasterView->ColumnAt(c);
3642 					if (!new_column->IsVisible())
3643 						continue;
3644 					if ((MAX(kLeftMargin,
3645 						fMasterView->LatchWidth()) + x) + new_column->Width()
3646 						> position.x) {
3647 
3648 						if(new_column->WantsEvents()) {
3649 							new_field = row->GetField(c);
3650 							new_row = row;
3651 							FindRect(new_row,&new_rect);
3652 							new_rect.left = MAX(kLeftMargin,
3653 								fMasterView->LatchWidth()) + x;
3654 							new_rect.right = new_rect.left
3655 								+ new_column->Width() - 1;
3656 							handle_field = true;
3657 						}
3658 						break;
3659 					}
3660 					x += new_column->Width();
3661 				}
3662 			}
3663 		}
3664 
3665 		// Handle mouse moved
3666 		if (handle_field) {
3667 			if (new_field != fCurrentField) {
3668 				if (fCurrentField) {
3669 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3670 						fCurrentField, fFieldRect, position, 0,
3671 						fCurrentCode = B_EXITED_VIEW);
3672 				}
3673 				fCurrentColumn = new_column;
3674 				fCurrentRow = new_row;
3675 				fCurrentField = new_field;
3676 				fFieldRect = new_rect;
3677 				if (fCurrentField) {
3678 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3679 						fCurrentField, fFieldRect, position, 0,
3680 						fCurrentCode = B_ENTERED_VIEW);
3681 				}
3682 			} else {
3683 				if (fCurrentField) {
3684 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3685 						fCurrentField, fFieldRect, position, 0,
3686 						fCurrentCode = B_INSIDE_VIEW);
3687 				}
3688 			}
3689 		} else {
3690 			if (fCurrentField) {
3691 				fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3692 					fCurrentField, fFieldRect, position, 0,
3693 					fCurrentCode = B_EXITED_VIEW);
3694 				fCurrentField = 0;
3695 				fCurrentColumn = 0;
3696 				fCurrentRow = 0;
3697 			}
3698 		}
3699 	} else {
3700 		if (fCurrentField) {
3701 			if (fFieldRect.Contains(position)) {
3702 				if (fCurrentCode == B_OUTSIDE_VIEW
3703 					|| fCurrentCode == B_EXITED_VIEW) {
3704 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3705 						fCurrentField, fFieldRect, position, 1,
3706 						fCurrentCode = B_ENTERED_VIEW);
3707 				} else {
3708 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3709 						fCurrentField, fFieldRect, position, 1,
3710 						fCurrentCode = B_INSIDE_VIEW);
3711 				}
3712 			} else {
3713 				if (fCurrentCode == B_INSIDE_VIEW
3714 					|| fCurrentCode == B_ENTERED_VIEW) {
3715 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3716 						fCurrentField, fFieldRect, position, 1,
3717 						fCurrentCode = B_EXITED_VIEW);
3718 				} else {
3719 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3720 						fCurrentField, fFieldRect, position, 1,
3721 						fCurrentCode = B_OUTSIDE_VIEW);
3722 				}
3723 			}
3724 		}
3725 	}
3726 
3727 	if (!fEditMode) {
3728 
3729 		switch (fCurrentState) {
3730 			case LATCH_CLICKED:
3731 				if (fTargetRow->fNextSelected != 0)
3732 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3733 				else
3734 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3735 
3736 				FillRect(fLatchRect);
3737 				if (fLatchRect.Contains(position)) {
3738 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3739 						fTargetRow);
3740 				} else {
3741 					fMasterView->DrawLatch(this, fLatchRect,
3742 						fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
3743 						fTargetRow);
3744 				}
3745 				break;
3746 
3747 			case ROW_CLICKED:
3748 				if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
3749 					|| abs((int)(position.y - fClickPoint.y))
3750 						> kRowDragSensitivity) {
3751 					fCurrentState = DRAGGING_ROWS;
3752 					fMasterView->InitiateDrag(fClickPoint,
3753 						fTargetRow->fNextSelected != 0);
3754 				}
3755 				break;
3756 
3757 			case DRAGGING_ROWS:
3758 #if 0
3759 				// falls through...
3760 #else
3761 				if (fTrackMouse /*&& message*/) {
3762 					if (fVisibleRect.Contains(position)) {
3763 						float top;
3764 						int32 indent;
3765 						BRow* target = FindRow(position.y, &indent, &top);
3766 						if (target)
3767 							SetFocusRow(target, true);
3768 					}
3769 				}
3770 				break;
3771 #endif
3772 
3773 			default: {
3774 
3775 				if (fTrackMouse /*&& message*/) {
3776 					// Draw a highlight line...
3777 					if (fVisibleRect.Contains(position)) {
3778 						float top;
3779 						int32 indent;
3780 						BRow* target = FindRow(position.y, &indent, &top);
3781 						if (target == fRollOverRow)
3782 							break;
3783 						if (fRollOverRow) {
3784 							BRect rect;
3785 							FindRect(fRollOverRow, &rect);
3786 							Invalidate(rect);
3787 						}
3788 						fRollOverRow = target;
3789 #if 0
3790 						SetFocusRow(fRollOverRow,false);
3791 #else
3792 						PushState();
3793 						SetDrawingMode(B_OP_BLEND);
3794 						SetHighColor(255, 255, 255, 255);
3795 						BRect rect;
3796 						FindRect(fRollOverRow, &rect);
3797 						rect.bottom -= 1.0;
3798 						FillRect(rect);
3799 						PopState();
3800 #endif
3801 					} else {
3802 						if (fRollOverRow) {
3803 							BRect rect;
3804 							FindRect(fRollOverRow, &rect);
3805 							Invalidate(rect);
3806 							fRollOverRow = NULL;
3807 						}
3808 					}
3809 				}
3810 			}
3811 		}
3812 	}
3813 }
3814 
3815 
3816 void
3817 OutlineView::MouseUp(BPoint position)
3818 {
3819 	if (fCurrentField) {
3820 		fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
3821 		fMouseDown = false;
3822 	}
3823 
3824 	if (fEditMode)
3825 		return;
3826 
3827 	switch (fCurrentState) {
3828 		case LATCH_CLICKED:
3829 			if (fLatchRect.Contains(position)) {
3830 				fMasterView->ExpandOrCollapse(fTargetRow,
3831 					!fTargetRow->fIsExpanded);
3832 			}
3833 
3834 			Invalidate(fLatchRect);
3835 			fCurrentState = INACTIVE;
3836 			break;
3837 
3838 		case ROW_CLICKED:
3839 			if (fClickCount > 1
3840 				&& abs((int)fClickPoint.x - (int)position.x)
3841 					< kDoubleClickMoveSensitivity
3842 				&& abs((int)fClickPoint.y - (int)position.y)
3843 					< kDoubleClickMoveSensitivity) {
3844 				fMasterView->ItemInvoked();
3845 			}
3846 			fCurrentState = INACTIVE;
3847 			break;
3848 
3849 		case DRAGGING_ROWS:
3850 			fCurrentState = INACTIVE;
3851 			// Falls through
3852 
3853 		default:
3854 			if (fDropHighlightY != -1) {
3855 				InvertRect(BRect(0,
3856 					fDropHighlightY - kDropHighlightLineHeight / 2,
3857 					1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3858 					// Erase the old target line
3859 				fDropHighlightY = -1;
3860 			}
3861 	}
3862 }
3863 
3864 
3865 void
3866 OutlineView::MessageReceived(BMessage* message)
3867 {
3868 	if (message->WasDropped()) {
3869 		fMasterView->MessageDropped(message,
3870 			ConvertFromScreen(message->DropPoint()));
3871 	} else {
3872 		BView::MessageReceived(message);
3873 	}
3874 }
3875 
3876 
3877 void
3878 OutlineView::ChangeFocusRow(bool up, bool updateSelection,
3879 	bool addToCurrentSelection)
3880 {
3881 	int32 indent;
3882 	float top;
3883 	float newRowPos = 0;
3884 	float verticalScroll = 0;
3885 
3886 	if (fFocusRow) {
3887 		// A row currently has the focus, get information about it
3888 		newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
3889 		if (newRowPos < fVisibleRect.top + 20)
3890 			verticalScroll = newRowPos - 20;
3891 		else if (newRowPos > fVisibleRect.bottom - 20)
3892 			verticalScroll = newRowPos - fVisibleRect.Height() + 20;
3893 	} else
3894 		newRowPos = fVisibleRect.top + 2;
3895 			// no row is currently focused, set this to the top of the window
3896 			// so we will select the first visible item in the list.
3897 
3898 	BRow* newRow = FindRow(newRowPos, &indent, &top);
3899 	if (newRow) {
3900 		if (fFocusRow) {
3901 			fFocusRowRect.right = 10000;
3902 			Invalidate(fFocusRowRect);
3903 		}
3904 		fFocusRow = newRow;
3905 		fFocusRowRect.top = top;
3906 		fFocusRowRect.left = 0;
3907 		fFocusRowRect.right = 10000;
3908 		fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
3909 		Invalidate(fFocusRowRect);
3910 
3911 		if (updateSelection) {
3912 			if (!addToCurrentSelection
3913 				|| fSelectionMode == B_SINGLE_SELECTION_LIST) {
3914 				DeselectAll();
3915 			}
3916 
3917 			if (fFocusRow->fNextSelected == 0) {
3918 				fFocusRow->fNextSelected
3919 					= fSelectionListDummyHead.fNextSelected;
3920 				fFocusRow->fPrevSelected = &fSelectionListDummyHead;
3921 				fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
3922 				fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
3923 			}
3924 
3925 			fLastSelectedItem = fFocusRow;
3926 		}
3927 	} else
3928 		Invalidate(fFocusRowRect);
3929 
3930 	if (verticalScroll != 0) {
3931 		BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
3932 		float min, max;
3933 		vScrollBar->GetRange(&min, &max);
3934 		if (verticalScroll < min)
3935 			verticalScroll = min;
3936 		else if (verticalScroll > max)
3937 			verticalScroll = max;
3938 
3939 		vScrollBar->SetValue(verticalScroll);
3940 	}
3941 
3942 	if (newRow && updateSelection)
3943 		fMasterView->SelectionChanged();
3944 }
3945 
3946 
3947 void
3948 OutlineView::MoveFocusToVisibleRect()
3949 {
3950 	fFocusRow = 0;
3951 	ChangeFocusRow(true, true, false);
3952 }
3953 
3954 
3955 BRow*
3956 OutlineView::CurrentSelection(BRow* lastSelected) const
3957 {
3958 	BRow* row;
3959 	if (lastSelected == 0)
3960 		row = fSelectionListDummyHead.fNextSelected;
3961 	else
3962 		row = lastSelected->fNextSelected;
3963 
3964 
3965 	if (row == &fSelectionListDummyHead)
3966 		row = 0;
3967 
3968 	return row;
3969 }
3970 
3971 
3972 void
3973 OutlineView::ToggleFocusRowSelection(bool selectRange)
3974 {
3975 	if (fFocusRow == 0)
3976 		return;
3977 
3978 	if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
3979 		SelectRange(fLastSelectedItem, fFocusRow);
3980 	else {
3981 		if (fFocusRow->fNextSelected != 0) {
3982 			// Unselect row
3983 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
3984 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
3985 			fFocusRow->fPrevSelected = 0;
3986 			fFocusRow->fNextSelected = 0;
3987 		} else {
3988 			// Select row
3989 			if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3990 				DeselectAll();
3991 
3992 			fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
3993 			fFocusRow->fPrevSelected = &fSelectionListDummyHead;
3994 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
3995 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
3996 		}
3997 	}
3998 
3999 	fLastSelectedItem = fFocusRow;
4000 	fMasterView->SelectionChanged();
4001 	Invalidate(fFocusRowRect);
4002 }
4003 
4004 
4005 void
4006 OutlineView::ToggleFocusRowOpen()
4007 {
4008 	if (fFocusRow)
4009 		fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
4010 }
4011 
4012 
4013 void
4014 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
4015 {
4016 	// TODO: Could use CopyBits here to speed things up.
4017 
4018 	if (parentRow == NULL)
4019 		return;
4020 
4021 	if (parentRow->fIsExpanded == expand)
4022 		return;
4023 
4024 	parentRow->fIsExpanded = expand;
4025 
4026 	BRect parentRect;
4027 	if (FindRect(parentRow, &parentRect)) {
4028 		// Determine my new height
4029 		float subTreeHeight = 0.0;
4030 		if (parentRow->fIsExpanded)
4031 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4032 			     iterator.CurrentRow();
4033 			     iterator.GoToNext()
4034 			    )
4035 			{
4036 				subTreeHeight += iterator.CurrentRow()->Height()+1;
4037 			}
4038 		else
4039 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4040 			     iterator.CurrentRow();
4041 			     iterator.GoToNext()
4042 			    )
4043 			{
4044 				subTreeHeight -= iterator.CurrentRow()->Height()+1;
4045 			}
4046 		fItemsHeight += subTreeHeight;
4047 
4048 		// Adjust focus row if necessary.
4049 		if (FindRect(fFocusRow, &fFocusRowRect) == false) {
4050 			// focus row is in a subtree that has collapsed,
4051 			// move it up to the parent.
4052 			fFocusRow = parentRow;
4053 			FindRect(fFocusRow, &fFocusRowRect);
4054 		}
4055 
4056 		Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
4057 			fVisibleRect.bottom));
4058 		FixScrollBar(false);
4059 	}
4060 }
4061 
4062 void
4063 OutlineView::RemoveRow(BRow* row)
4064 {
4065 	if (row == NULL)
4066 		return;
4067 
4068 	BRow* parentRow;
4069 	bool parentIsVisible;
4070 	FindParent(row, &parentRow, &parentIsVisible);
4071 		// NOTE: This could be a root row without a parent, in which case
4072 		// it is always visible, though.
4073 
4074 	// Adjust height for the visible sub-tree that is going to be removed.
4075 	float subTreeHeight = 0.0f;
4076 	if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4077 		// The row itself is visible at least.
4078 		subTreeHeight = row->Height() + 1;
4079 		if (row->fIsExpanded) {
4080 			// Adjust for the height of visible sub-items as well.
4081 			// (By default, the iterator follows open branches only.)
4082 			for (RecursiveOutlineIterator iterator(row->fChildList);
4083 				iterator.CurrentRow(); iterator.GoToNext())
4084 				subTreeHeight += iterator.CurrentRow()->Height() + 1;
4085 		}
4086 		BRect invalid;
4087 		if (FindRect(row, &invalid)) {
4088 			invalid.bottom = Bounds().bottom;
4089 			if (invalid.IsValid())
4090 				Invalidate(invalid);
4091 		}
4092 	}
4093 
4094 	fItemsHeight -= subTreeHeight;
4095 
4096 	FixScrollBar(false);
4097 	int32 indent = 0;
4098 	float top = 0.0;
4099 	if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
4100 		// after removing this row, no rows are actually visible any more,
4101 		// force a scroll to make them visible again
4102 		if (fItemsHeight > fVisibleRect.Height())
4103 			ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
4104 		else
4105 			ScrollBy(0.0, -Bounds().top);
4106 	}
4107 	if (parentRow != NULL) {
4108 		parentRow->fChildList->RemoveItem(row);
4109 		if (parentRow->fChildList->CountItems() == 0) {
4110 			delete parentRow->fChildList;
4111 			parentRow->fChildList = 0;
4112 			// It was the last child row of the parent, which also means the
4113 			// latch disappears.
4114 			BRect parentRowRect;
4115 			if (parentIsVisible && FindRect(parentRow, &parentRowRect))
4116 				Invalidate(parentRowRect);
4117 		}
4118 	} else
4119 		fRows.RemoveItem(row);
4120 
4121 	// Adjust focus row if necessary.
4122 	if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
4123 		// focus row is in a subtree that is gone, move it up to the parent.
4124 		fFocusRow = parentRow;
4125 		if (fFocusRow)
4126 			FindRect(fFocusRow, &fFocusRowRect);
4127 	}
4128 
4129 	// Remove this from the selection if necessary
4130 	if (row->fNextSelected != 0) {
4131 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4132 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4133 		row->fPrevSelected = 0;
4134 		row->fNextSelected = 0;
4135 		fMasterView->SelectionChanged();
4136 	}
4137 
4138 	fCurrentColumn = 0;
4139 	fCurrentRow = 0;
4140 	fCurrentField = 0;
4141 }
4142 
4143 
4144 BRowContainer*
4145 OutlineView::RowList()
4146 {
4147 	return &fRows;
4148 }
4149 
4150 
4151 void
4152 OutlineView::UpdateRow(BRow* row)
4153 {
4154 	if (row) {
4155 		// Determine if this row has changed its sort order
4156 		BRow* parentRow = NULL;
4157 		bool parentIsVisible = false;
4158 		FindParent(row, &parentRow, &parentIsVisible);
4159 
4160 		BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
4161 
4162 		if(list) {
4163 			int32 rowIndex = list->IndexOf(row);
4164 			ASSERT(rowIndex >= 0);
4165 			ASSERT(list->ItemAt(rowIndex) == row);
4166 
4167 			bool rowMoved = false;
4168 			if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
4169 				rowMoved = true;
4170 
4171 			if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
4172 				row) < 0)
4173 				rowMoved = true;
4174 
4175 			if (rowMoved) {
4176 				// Sort location of this row has changed.
4177 				// Remove and re-add in the right spot
4178 				SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
4179 			} else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4180 				BRect invalidRect;
4181 				if (FindVisibleRect(row, &invalidRect))
4182 					Invalidate(invalidRect);
4183 			}
4184 		}
4185 	}
4186 }
4187 
4188 
4189 void
4190 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
4191 {
4192 	if (!row)
4193 		return;
4194 
4195 	row->fParent = parentRow;
4196 
4197 	if (fMasterView->SortingEnabled()) {
4198 		// Ignore index here.
4199 		if (parentRow) {
4200 			if (parentRow->fChildList == NULL)
4201 				parentRow->fChildList = new BRowContainer;
4202 
4203 			AddSorted(parentRow->fChildList, row);
4204 		} else
4205 			AddSorted(&fRows, row);
4206 	} else {
4207 		// Note, a -1 index implies add to end if sorting is not enabled
4208 		if (parentRow) {
4209 			if (parentRow->fChildList == 0)
4210 				parentRow->fChildList = new BRowContainer;
4211 
4212 			if (Index < 0 || Index > parentRow->fChildList->CountItems())
4213 				parentRow->fChildList->AddItem(row);
4214 			else
4215 				parentRow->fChildList->AddItem(row, Index);
4216 		} else {
4217 			if (Index < 0 || Index >= fRows.CountItems())
4218 				fRows.AddItem(row);
4219 			else
4220 				fRows.AddItem(row, Index);
4221 		}
4222 	}
4223 
4224 	if (parentRow == 0 || parentRow->fIsExpanded)
4225 		fItemsHeight += row->Height() + 1;
4226 
4227 	FixScrollBar(false);
4228 
4229 	BRect newRowRect;
4230 	bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
4231 
4232 	if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
4233 		// The focus row has moved.
4234 		Invalidate(fFocusRowRect);
4235 		FindRect(fFocusRow, &fFocusRowRect);
4236 		Invalidate(fFocusRowRect);
4237 	}
4238 
4239 	if (newRowIsInOpenBranch) {
4240 		if (fCurrentState == INACTIVE) {
4241 			if (newRowRect.bottom < fVisibleRect.top) {
4242 				// The new row is totally above the current viewport, move
4243 				// everything down and redraw the first line.
4244 				BRect source(fVisibleRect);
4245 				BRect dest(fVisibleRect);
4246 				source.bottom -= row->Height() + 1;
4247 				dest.top += row->Height() + 1;
4248 				CopyBits(source, dest);
4249 				Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
4250 					fVisibleRect.top + newRowRect.Height()));
4251 			} else if (newRowRect.top < fVisibleRect.bottom) {
4252 				// New item is somewhere in the current region.  Scroll everything
4253 				// beneath it down and invalidate just the new row rect.
4254 				BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
4255 					fVisibleRect.bottom - newRowRect.Height());
4256 				BRect dest(source);
4257 				dest.OffsetBy(0, newRowRect.Height() + 1);
4258 				CopyBits(source, dest);
4259 				Invalidate(newRowRect);
4260 			} // otherwise, this is below the currently visible region
4261 		} else {
4262 			// Adding the item may have caused the item that the user is currently
4263 			// selected to move.  This would cause annoying drawing and interaction
4264 			// bugs, as the position of that item is cached.  If this happens, resize
4265 			// the scroll bar, then scroll back so the selected item is in view.
4266 			BRect targetRect;
4267 			if (FindRect(fTargetRow, &targetRect)) {
4268 				float delta = targetRect.top - fTargetRowTop;
4269 				if (delta != 0) {
4270 					// This causes a jump because ScrollBy will copy a chunk of the view.
4271 					// Since the actual contents of the view have been offset, we don't
4272 					// want this, we just want to change the virtual origin of the window.
4273 					// Constrain the clipping region so everything is clipped out so no
4274 					// copy occurs.
4275 					//
4276 					//	xxx this currently doesn't work if the scroll bars aren't enabled.
4277 					//  everything will still move anyway.  A minor annoyance.
4278 					BRegion emptyRegion;
4279 					ConstrainClippingRegion(&emptyRegion);
4280 					PushState();
4281 					ScrollBy(0, delta);
4282 					PopState();
4283 					ConstrainClippingRegion(NULL);
4284 
4285 					fTargetRowTop += delta;
4286 					fClickPoint.y += delta;
4287 					fLatchRect.OffsetBy(0, delta);
4288 				}
4289 			}
4290 		}
4291 	}
4292 
4293 	// If the parent was previously childless, it will need to have a latch
4294 	// drawn.
4295 	BRect parentRect;
4296 	if (parentRow && parentRow->fChildList->CountItems() == 1
4297 		&& FindVisibleRect(parentRow, &parentRect))
4298 		Invalidate(parentRect);
4299 }
4300 
4301 
4302 void
4303 OutlineView::FixScrollBar(bool scrollToFit)
4304 {
4305 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4306 	if (vScrollBar) {
4307 		if (fItemsHeight > fVisibleRect.Height()) {
4308 			float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4309 			vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
4310 
4311 			// If the user is scrolled down too far when making the range smaller, the list
4312 			// will jump suddenly, which is undesirable.  In this case, don't fix the scroll
4313 			// bar here. In ScrollTo, it checks to see if this has occured, and will
4314 			// fix the scroll bars sneakily if the user has scrolled up far enough.
4315 			if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
4316 				vScrollBar->SetRange(0.0, maxScrollBarValue);
4317 				vScrollBar->SetSteps(20.0, fVisibleRect.Height());
4318 			}
4319 		} else if (vScrollBar->Value() == 0.0)
4320 			vScrollBar->SetRange(0.0, 0.0);		// disable scroll bar.
4321 	}
4322 }
4323 
4324 
4325 void
4326 OutlineView::AddSorted(BRowContainer* list, BRow* row)
4327 {
4328 	if (list && row) {
4329 		// Find general vicinity with binary search.
4330 		int32 lower = 0;
4331 		int32 upper = list->CountItems()-1;
4332 		while( lower < upper ) {
4333 			int32 middle = lower + (upper-lower+1)/2;
4334 			int32 cmp = CompareRows(row, list->ItemAt(middle));
4335 			if( cmp < 0 ) upper = middle-1;
4336 			else if( cmp > 0 ) lower = middle+1;
4337 			else lower = upper = middle;
4338 		}
4339 
4340 		// At this point, 'upper' and 'lower' at the last found item.
4341 		// Arbitrarily use 'upper' and determine the final insertion
4342 		// point -- either before or after this item.
4343 		if( upper < 0 ) upper = 0;
4344 		else if( upper < list->CountItems() ) {
4345 			if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
4346 		}
4347 
4348 		if (upper >= list->CountItems())
4349 			list->AddItem(row);				// Adding to end.
4350 		else
4351 			list->AddItem(row, upper);		// Insert
4352 	}
4353 }
4354 
4355 
4356 int32
4357 OutlineView::CompareRows(BRow* row1, BRow* row2)
4358 {
4359 	int32 itemCount (fSortColumns->CountItems());
4360 	if (row1 && row2) {
4361 		for (int32 index = 0; index < itemCount; index++) {
4362 			BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
4363 			int comp = 0;
4364 			BField* field1 = (BField*) row1->GetField(column->fFieldID);
4365 			BField* field2 = (BField*) row2->GetField(column->fFieldID);
4366 			if (field1 && field2)
4367 				comp = column->CompareFields(field1, field2);
4368 
4369 			if (!column->fSortAscending)
4370 				comp = -comp;
4371 
4372 			if (comp != 0)
4373 				return comp;
4374 		}
4375 	}
4376 	return 0;
4377 }
4378 
4379 
4380 void
4381 OutlineView::FrameResized(float width, float height)
4382 {
4383 	fVisibleRect.right = fVisibleRect.left + width;
4384 	fVisibleRect.bottom = fVisibleRect.top + height;
4385 	FixScrollBar(true);
4386 	_inherited::FrameResized(width, height);
4387 }
4388 
4389 
4390 void
4391 OutlineView::ScrollTo(BPoint position)
4392 {
4393 	fVisibleRect.OffsetTo(position.x, position.y);
4394 
4395 	// In FixScrollBar, we might not have been able to change the size of
4396 	// the scroll bar because the user was scrolled down too far.  Take
4397 	// this opportunity to sneak it in if we can.
4398 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4399 	float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4400 	float min, max;
4401 	vScrollBar->GetRange(&min, &max);
4402 	if (max != maxScrollBarValue && position.y > maxScrollBarValue)
4403 		FixScrollBar(true);
4404 
4405 	_inherited::ScrollTo(position);
4406 }
4407 
4408 
4409 const BRect&
4410 OutlineView::VisibleRect() const
4411 {
4412 	return fVisibleRect;
4413 }
4414 
4415 
4416 bool
4417 OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
4418 {
4419 	if (row && _rect) {
4420 		float line = 0.0;
4421 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4422 			iterator.GoToNext()) {
4423 			if (line > fVisibleRect.bottom)
4424 				break;
4425 
4426 			if (iterator.CurrentRow() == row) {
4427 				_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4428 					line + row->Height());
4429 				return true;
4430 			}
4431 
4432 			line += iterator.CurrentRow()->Height() + 1;
4433 		}
4434 	}
4435 	return false;
4436 }
4437 
4438 
4439 bool
4440 OutlineView::FindRect(const BRow* row, BRect* _rect)
4441 {
4442 	float line = 0.0;
4443 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4444 		iterator.GoToNext()) {
4445 		if (iterator.CurrentRow() == row) {
4446 			_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4447 				line + row->Height());
4448 			return true;
4449 		}
4450 
4451 		line += iterator.CurrentRow()->Height() + 1;
4452 	}
4453 
4454 	return false;
4455 }
4456 
4457 
4458 void
4459 OutlineView::ScrollTo(const BRow* row)
4460 {
4461 	BRect rect;
4462 	if (FindRect(row, &rect)) {
4463 		BRect bounds = Bounds();
4464 		if (rect.top < bounds.top)
4465 			ScrollTo(BPoint(bounds.left, rect.top));
4466 		else if (rect.bottom > bounds.bottom)
4467 			ScrollBy(0, rect.bottom - bounds.bottom);
4468 	}
4469 }
4470 
4471 
4472 void
4473 OutlineView::DeselectAll()
4474 {
4475 	// Invalidate all selected rows
4476 	float line = 0.0;
4477 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4478 		iterator.GoToNext()) {
4479 		if (line > fVisibleRect.bottom)
4480 			break;
4481 
4482 		BRow* row = iterator.CurrentRow();
4483 		if (line + row->Height() > fVisibleRect.top) {
4484 			if (row->fNextSelected != 0)
4485 				Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
4486 					line + row->Height()));
4487 		}
4488 
4489 		line += row->Height() + 1;
4490 	}
4491 
4492 	// Set items not selected
4493 	while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
4494 		BRow* row = fSelectionListDummyHead.fNextSelected;
4495 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4496 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4497 		row->fNextSelected = 0;
4498 		row->fPrevSelected = 0;
4499 	}
4500 }
4501 
4502 
4503 BRow*
4504 OutlineView::FocusRow() const
4505 {
4506 	return fFocusRow;
4507 }
4508 
4509 
4510 void
4511 OutlineView::SetFocusRow(BRow* row, bool Select)
4512 {
4513 	if (row) {
4514 		if (Select)
4515 			AddToSelection(row);
4516 
4517 		if (fFocusRow == row)
4518 			return;
4519 
4520 		Invalidate(fFocusRowRect); // invalidate previous
4521 
4522 		fTargetRow = fFocusRow = row;
4523 
4524 		FindVisibleRect(fFocusRow, &fFocusRowRect);
4525 		Invalidate(fFocusRowRect); // invalidate current
4526 
4527 		fFocusRowRect.right = 10000;
4528 		fMasterView->SelectionChanged();
4529 	}
4530 }
4531 
4532 
4533 bool
4534 OutlineView::SortList(BRowContainer* list, bool isVisible)
4535 {
4536 	if (list) {
4537 		// Shellsort
4538 		BRow** items
4539 			= (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
4540 		int32 numItems = list->CountItems();
4541 		int h;
4542 		for (h = 1; h < numItems / 9; h = 3 * h + 1)
4543 			;
4544 
4545 		for (;h > 0; h /= 3) {
4546 			for (int step = h; step < numItems; step++) {
4547 				BRow* temp = items[step];
4548 				int i;
4549 				for (i = step - h; i >= 0; i -= h) {
4550 					if (CompareRows(temp, items[i]) < 0)
4551 						items[i + h] = items[i];
4552 					else
4553 						break;
4554 				}
4555 
4556 				items[i + h] = temp;
4557 			}
4558 		}
4559 
4560 		if (isVisible) {
4561 			Invalidate();
4562 
4563 			InvalidateCachedPositions();
4564 			int lockCount = Window()->CountLocks();
4565 			for (int i = 0; i < lockCount; i++)
4566 				Window()->Unlock();
4567 
4568 			while (lockCount--)
4569 				if (!Window()->Lock())
4570 					return false;	// Window is gone...
4571 		}
4572 	}
4573 	return true;
4574 }
4575 
4576 
4577 int32
4578 OutlineView::DeepSortThreadEntry(void* _outlineView)
4579 {
4580 	((OutlineView*) _outlineView)->DeepSort();
4581 	return 0;
4582 }
4583 
4584 
4585 void
4586 OutlineView::DeepSort()
4587 {
4588 	struct stack_entry {
4589 		bool isVisible;
4590 		BRowContainer* list;
4591 		int32 listIndex;
4592 	} stack[kMaxDepth];
4593 	int32 stackTop = 0;
4594 
4595 	stack[stackTop].list = &fRows;
4596 	stack[stackTop].isVisible = true;
4597 	stack[stackTop].listIndex = 0;
4598 	fNumSorted = 0;
4599 
4600 	if (Window()->Lock() == false)
4601 		return;
4602 
4603 	bool doneSorting = false;
4604 	while (!doneSorting && !fSortCancelled) {
4605 
4606 		stack_entry* currentEntry = &stack[stackTop];
4607 
4608 		// xxx Can make the invalidate area smaller by finding the rect for the
4609 		// parent item and using that as the top of the invalid rect.
4610 
4611 		bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
4612 		if (!haveLock)
4613 			return ;	// window is gone.
4614 
4615 		// Fix focus rect.
4616 		InvalidateCachedPositions();
4617 		if (fCurrentState != INACTIVE)
4618 			fCurrentState = INACTIVE;	// sorry...
4619 
4620 		// next list.
4621 		bool foundNextList = false;
4622 		while (!foundNextList && !fSortCancelled) {
4623 			for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
4624 				index++) {
4625 				BRow* parentRow = currentEntry->list->ItemAt(index);
4626 				BRowContainer* childList = parentRow->fChildList;
4627 				if (childList != 0) {
4628 					currentEntry->listIndex = index + 1;
4629 					stackTop++;
4630 					ASSERT(stackTop < kMaxDepth);
4631 					stack[stackTop].listIndex = 0;
4632 					stack[stackTop].list = childList;
4633 					stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
4634 					foundNextList = true;
4635 					break;
4636 				}
4637 			}
4638 
4639 			if (!foundNextList) {
4640 				// back up
4641 				if (--stackTop < 0) {
4642 					doneSorting = true;
4643 					break;
4644 				}
4645 
4646 				currentEntry = &stack[stackTop];
4647 			}
4648 		}
4649 	}
4650 
4651 	Window()->Unlock();
4652 }
4653 
4654 
4655 void
4656 OutlineView::StartSorting()
4657 {
4658 	// If this view is not yet attached to a window, don't start a sort thread!
4659 	if (Window() == NULL)
4660 		return;
4661 
4662 	if (fSortThread != B_BAD_THREAD_ID) {
4663 		thread_info tinfo;
4664 		if (get_thread_info(fSortThread, &tinfo) == B_OK) {
4665 			// Unlock window so this won't deadlock (sort thread is probably
4666 			// waiting to lock window).
4667 
4668 			int lockCount = Window()->CountLocks();
4669 			for (int i = 0; i < lockCount; i++)
4670 				Window()->Unlock();
4671 
4672 			fSortCancelled = true;
4673 			int32 status;
4674 			wait_for_thread(fSortThread, &status);
4675 
4676 			while (lockCount--)
4677 				if (!Window()->Lock())
4678 					return ;	// Window is gone...
4679 		}
4680 	}
4681 
4682 	fSortCancelled = false;
4683 	fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
4684 	resume_thread(fSortThread);
4685 }
4686 
4687 
4688 void
4689 OutlineView::SelectRange(BRow* start, BRow* end)
4690 {
4691 	if (!start || !end)
4692 		return;
4693 
4694 	if (start == end)	// start is always selected when this is called
4695 		return;
4696 
4697 	RecursiveOutlineIterator iterator(&fRows, false);
4698 	while (iterator.CurrentRow() != 0) {
4699 		if (iterator.CurrentRow() == end) {
4700 			// reverse selection, swap to fix special case
4701 			BRow* temp = start;
4702 			start = end;
4703 			end = temp;
4704 			break;
4705 		} else if (iterator.CurrentRow() == start)
4706 			break;
4707 
4708 		iterator.GoToNext();
4709 	}
4710 
4711 	while (true) {
4712 		BRow* row = iterator.CurrentRow();
4713 		if (row) {
4714 			if (row->fNextSelected == 0) {
4715 				row->fNextSelected = fSelectionListDummyHead.fNextSelected;
4716 				row->fPrevSelected = &fSelectionListDummyHead;
4717 				row->fNextSelected->fPrevSelected = row;
4718 				row->fPrevSelected->fNextSelected = row;
4719 			}
4720 		} else
4721 			break;
4722 
4723 		if (row == end)
4724 			break;
4725 
4726 		iterator.GoToNext();
4727 	}
4728 
4729 	Invalidate();  // xxx make invalidation smaller
4730 }
4731 
4732 
4733 bool
4734 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
4735 {
4736 	bool result = false;
4737 	if (row != NULL && outParent != NULL) {
4738 		*outParent = row->fParent;
4739 
4740 		if (outParentIsVisible != NULL) {
4741 			// Walk up the parent chain to determine if this row is visible
4742 			*outParentIsVisible = true;
4743 			for (BRow* currentRow = row->fParent; currentRow != NULL;
4744 				currentRow = currentRow->fParent) {
4745 				if (!currentRow->fIsExpanded) {
4746 					*outParentIsVisible = false;
4747 					break;
4748 				}
4749 			}
4750 		}
4751 
4752 		result = *outParent != NULL;
4753 	}
4754 
4755 	return result;
4756 }
4757 
4758 
4759 int32
4760 OutlineView::IndexOf(BRow* row)
4761 {
4762 	if (row) {
4763 		if (row->fParent == 0)
4764 			return fRows.IndexOf(row);
4765 
4766 		ASSERT(row->fParent->fChildList);
4767 		return row->fParent->fChildList->IndexOf(row);
4768 	}
4769 
4770 	return B_ERROR;
4771 }
4772 
4773 
4774 void
4775 OutlineView::InvalidateCachedPositions()
4776 {
4777 	if (fFocusRow)
4778 		FindRect(fFocusRow, &fFocusRowRect);
4779 }
4780 
4781 
4782 float
4783 OutlineView::GetColumnPreferredWidth(BColumn* column)
4784 {
4785 	float preferred = 0.0;
4786 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4787 		iterator.GoToNext()) {
4788 		BRow* row = iterator.CurrentRow();
4789 		BField* field = row->GetField(column->fFieldID);
4790 		if (field) {
4791 			float width = column->GetPreferredWidth(field, this);
4792 			if (preferred < width)
4793 				preferred = width;
4794 		}
4795 	}
4796 	// Constrain to preferred width. This makes the method do a little
4797 	// more than asked, but it's for convenience.
4798 	if (preferred < column->MinWidth())
4799 		preferred = column->MinWidth();
4800 	else if (preferred > column->MaxWidth())
4801 		preferred = column->MaxWidth();
4802 
4803 	return preferred;
4804 }
4805 
4806 
4807 // #pragma mark -
4808 
4809 
4810 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
4811 	bool openBranchesOnly)
4812 	:
4813 	fStackIndex(0),
4814 	fCurrentListIndex(0),
4815 	fCurrentListDepth(0),
4816 	fOpenBranchesOnly(openBranchesOnly)
4817 {
4818 	if (list == 0 || list->CountItems() == 0)
4819 		fCurrentList = 0;
4820 	else
4821 		fCurrentList = list;
4822 }
4823 
4824 
4825 BRow*
4826 RecursiveOutlineIterator::CurrentRow() const
4827 {
4828 	if (fCurrentList == 0)
4829 		return 0;
4830 
4831 	return fCurrentList->ItemAt(fCurrentListIndex);
4832 }
4833 
4834 
4835 void
4836 RecursiveOutlineIterator::GoToNext()
4837 {
4838 	if (fCurrentList == 0)
4839 		return;
4840 	if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
4841 		fCurrentList = 0;
4842 		return;
4843 	}
4844 
4845 	BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
4846 	if(currentRow) {
4847 		if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
4848 			&& currentRow->fChildList->CountItems() > 0) {
4849 			// Visit child.
4850 			// Put current list on the stack if it needs to be revisited.
4851 			if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
4852 				fStack[fStackIndex].fRowSet = fCurrentList;
4853 				fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
4854 				fStack[fStackIndex].fDepth = fCurrentListDepth;
4855 				fStackIndex++;
4856 			}
4857 
4858 			fCurrentList = currentRow->fChildList;
4859 			fCurrentListIndex = 0;
4860 			fCurrentListDepth++;
4861 		} else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
4862 			fCurrentListIndex++; // next item in current list
4863 		else if (--fStackIndex >= 0) {
4864 			fCurrentList = fStack[fStackIndex].fRowSet;
4865 			fCurrentListIndex = fStack[fStackIndex].fIndex;
4866 			fCurrentListDepth = fStack[fStackIndex].fDepth;
4867 		} else
4868 			fCurrentList = 0;
4869 	}
4870 }
4871 
4872 
4873 int32
4874 RecursiveOutlineIterator::CurrentLevel() const
4875 {
4876 	return fCurrentListDepth;
4877 }
4878 
4879 
4880