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