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