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