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