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