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