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