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