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