xref: /haiku/src/kits/interface/ColumnListView.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
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";
569 		puts(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 key = bytes[0];
1690 	switch (key) {
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 (key == B_LEFT_ARROW)
1703 					newVal -= smallStep;
1704 				else if (key == 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 isExpanded = focusRow->HasLatch()
1719 					&& focusRow->IsExpanded();
1720 				switch (key) {
1721 					case B_LEFT_ARROW:
1722 						if (isExpanded)
1723 							fOutlineView->ToggleFocusRowOpen();
1724 						else if (focusRow->fParent != NULL) {
1725 							fOutlineView->DeselectAll();
1726 							fOutlineView->SetFocusRow(focusRow->fParent, true);
1727 							fOutlineView->ScrollTo(focusRow->fParent);
1728 						}
1729 						break;
1730 
1731 					case B_RIGHT_ARROW:
1732 						if (!isExpanded)
1733 							fOutlineView->ToggleFocusRowOpen();
1734 						else
1735 							fOutlineView->ChangeFocusRow(false, true, false);
1736 						break;
1737 				}
1738 			}
1739 			break;
1740 		}
1741 
1742 		case B_DOWN_ARROW:
1743 			fOutlineView->ChangeFocusRow(false,
1744 				(modifiers() & B_CONTROL_KEY) == 0,
1745 				(modifiers() & B_SHIFT_KEY) != 0);
1746 			break;
1747 
1748 		case B_UP_ARROW:
1749 			fOutlineView->ChangeFocusRow(true,
1750 				(modifiers() & B_CONTROL_KEY) == 0,
1751 				(modifiers() & B_SHIFT_KEY) != 0);
1752 			break;
1753 
1754 		case B_PAGE_UP:
1755 		case B_PAGE_DOWN:
1756 		{
1757 			float minValue, maxValue;
1758 			fVerticalScrollBar->GetRange(&minValue, &maxValue);
1759 			float smallStep, largeStep;
1760 			fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
1761 			float currentValue = fVerticalScrollBar->Value();
1762 			float newValue = currentValue;
1763 
1764 			if (key == B_PAGE_UP)
1765 				newValue -= largeStep;
1766 			else
1767 				newValue += largeStep;
1768 
1769 			if (newValue > maxValue)
1770 				newValue = maxValue;
1771 			else if (newValue < minValue)
1772 				newValue = minValue;
1773 
1774 			fVerticalScrollBar->SetValue(newValue);
1775 
1776 			// Option + pgup or pgdn scrolls and changes the selection.
1777 			if (modifiers() & B_OPTION_KEY)
1778 				fOutlineView->MoveFocusToVisibleRect();
1779 
1780 			break;
1781 		}
1782 
1783 		case B_ENTER:
1784 			Invoke();
1785 			break;
1786 
1787 		case B_SPACE:
1788 			fOutlineView->ToggleFocusRowSelection(
1789 				(modifiers() & B_SHIFT_KEY) != 0);
1790 			break;
1791 
1792 		case '+':
1793 			fOutlineView->ToggleFocusRowOpen();
1794 			break;
1795 
1796 		default:
1797 			BView::KeyDown(bytes, numBytes);
1798 	}
1799 }
1800 
1801 
1802 void
1803 BColumnListView::AttachedToWindow()
1804 {
1805 	if (!Messenger().IsValid())
1806 		SetTarget(Window());
1807 
1808 	if (SortingEnabled()) fOutlineView->StartSorting();
1809 }
1810 
1811 
1812 void
1813 BColumnListView::WindowActivated(bool active)
1814 {
1815 	fOutlineView->Invalidate();
1816 		// focus and selection appearance changes with focus
1817 
1818 	Invalidate();
1819 		// redraw focus marks around view
1820 	BView::WindowActivated(active);
1821 }
1822 
1823 
1824 void
1825 BColumnListView::Draw(BRect updateRect)
1826 {
1827 	BRect rect = Bounds();
1828 
1829 	uint32 flags = 0;
1830 	if (IsFocus() && Window()->IsActive())
1831 		flags |= BControlLook::B_FOCUSED;
1832 
1833 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1834 
1835 	BRect verticalScrollBarFrame;
1836 	if (!fVerticalScrollBar->IsHidden())
1837 		verticalScrollBarFrame = fVerticalScrollBar->Frame();
1838 
1839 	BRect horizontalScrollBarFrame;
1840 	if (!fHorizontalScrollBar->IsHidden())
1841 		horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
1842 
1843 	if (fBorderStyle == B_NO_BORDER) {
1844 		// We still draw the left/top border, but not focused.
1845 		// The scrollbars cannot be displayed without frame and
1846 		// it looks bad to have no frame only along the left/top
1847 		// side.
1848 		rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
1849 		SetHighColor(borderColor);
1850 		StrokeLine(BPoint(rect.left, rect.bottom),
1851 			BPoint(rect.left, rect.top));
1852 		StrokeLine(BPoint(rect.left + 1, rect.top),
1853 			BPoint(rect.right, rect.top));
1854 	}
1855 
1856 	be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1857 		verticalScrollBarFrame, horizontalScrollBarFrame,
1858 		base, fBorderStyle, flags);
1859 
1860 	if (fStatusView != NULL) {
1861 		rect = Bounds();
1862 		BRegion region(rect & fStatusView->Frame().InsetByCopy(-2, -2));
1863 		ConstrainClippingRegion(&region);
1864 		rect.bottom = fStatusView->Frame().top - 1;
1865 		be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1866 			BRect(), BRect(), base, fBorderStyle, flags);
1867 	}
1868 }
1869 
1870 
1871 void
1872 BColumnListView::SaveState(BMessage* message)
1873 {
1874 	message->MakeEmpty();
1875 
1876 	for (int32 i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1877 		message->AddInt32("ID", column->fFieldID);
1878 		message->AddFloat("width", column->fWidth);
1879 		message->AddBool("visible", column->fVisible);
1880 	}
1881 
1882 	message->AddBool("sortingenabled", fSortingEnabled);
1883 
1884 	if (fSortingEnabled) {
1885 		for (int32 i = 0; BColumn* column = (BColumn*)fSortColumns.ItemAt(i);
1886 				i++) {
1887 			message->AddInt32("sortID", column->fFieldID);
1888 			message->AddBool("sortascending", column->fSortAscending);
1889 		}
1890 	}
1891 }
1892 
1893 
1894 void
1895 BColumnListView::LoadState(BMessage* message)
1896 {
1897 	int32 id;
1898 	for (int i = 0; message->FindInt32("ID", i, &id) == B_OK; i++) {
1899 		for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
1900 			if (column->fFieldID == id) {
1901 				// move this column to position 'i' and set its attributes
1902 				MoveColumn(column, i);
1903 				float width;
1904 				if (message->FindFloat("width", i, &width) == B_OK)
1905 					column->SetWidth(width);
1906 				bool visible;
1907 				if (message->FindBool("visible", i, &visible) == B_OK)
1908 					column->SetVisible(visible);
1909 			}
1910 		}
1911 	}
1912 	bool b;
1913 	if (message->FindBool("sortingenabled", &b) == B_OK) {
1914 		SetSortingEnabled(b);
1915 		for (int k = 0; message->FindInt32("sortID", k, &id) == B_OK; k++) {
1916 			for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
1917 					j++) {
1918 				if (column->fFieldID == id) {
1919 					// add this column to the sort list
1920 					bool value;
1921 					if (message->FindBool("sortascending", k, &value) == B_OK)
1922 						SetSortColumn(column, true, value);
1923 				}
1924 			}
1925 		}
1926 	}
1927 }
1928 
1929 
1930 void
1931 BColumnListView::SetEditMode(bool state)
1932 {
1933 	fOutlineView->SetEditMode(state);
1934 	fTitleView->SetEditMode(state);
1935 }
1936 
1937 
1938 void
1939 BColumnListView::Refresh()
1940 {
1941 	if (LockLooper()) {
1942 		Invalidate();
1943 		fOutlineView->FixScrollBar (true);
1944 		fOutlineView->Invalidate();
1945 		Window()->UpdateIfNeeded();
1946 		UnlockLooper();
1947 	}
1948 }
1949 
1950 
1951 BSize
1952 BColumnListView::MinSize()
1953 {
1954 	BSize size;
1955 	size.width = 100;
1956 	size.height = std::max(kMinTitleHeight,
1957 		ceilf(be_plain_font->Size() * kTitleSpacing))
1958 		+ 4 * B_H_SCROLL_BAR_HEIGHT;
1959 	if (!fHorizontalScrollBar->IsHidden())
1960 		size.height += fHorizontalScrollBar->Frame().Height() + 1;
1961 	// TODO: Take border size into account
1962 
1963 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1964 }
1965 
1966 
1967 BSize
1968 BColumnListView::PreferredSize()
1969 {
1970 	BSize size = MinSize();
1971 	size.height += ceilf(be_plain_font->Size()) * 20;
1972 
1973 	// return MinSize().width if there are no columns.
1974 	int32 count = CountColumns();
1975 	if (count > 0) {
1976 		BRect titleRect;
1977 		BRect outlineRect;
1978 		BRect vScrollBarRect;
1979 		BRect hScrollBarRect;
1980 		_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1981 			hScrollBarRect);
1982 		// Start with the extra width for border and scrollbars etc.
1983 		size.width = titleRect.left - Bounds().left;
1984 		size.width += Bounds().right - titleRect.right;
1985 
1986 		// If we want all columns to be visible at their current width,
1987 		// we also need to add the extra margin width that the TitleView
1988 		// uses to compute its _VirtualWidth() for the horizontal scroll bar.
1989 		size.width += fTitleView->MarginWidth();
1990 		for (int32 i = 0; i < count; i++) {
1991 			BColumn* column = ColumnAt(i);
1992 			if (column != NULL)
1993 				size.width += column->Width();
1994 		}
1995 	}
1996 
1997 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1998 }
1999 
2000 
2001 BSize
2002 BColumnListView::MaxSize()
2003 {
2004 	BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
2005 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
2006 }
2007 
2008 
2009 void
2010 BColumnListView::LayoutInvalidated(bool descendants)
2011 {
2012 }
2013 
2014 
2015 void
2016 BColumnListView::DoLayout()
2017 {
2018 	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
2019 		return;
2020 
2021 	BRect titleRect;
2022 	BRect outlineRect;
2023 	BRect vScrollBarRect;
2024 	BRect hScrollBarRect;
2025 	_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
2026 		hScrollBarRect);
2027 
2028 	fTitleView->MoveTo(titleRect.LeftTop());
2029 	fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
2030 
2031 	fOutlineView->MoveTo(outlineRect.LeftTop());
2032 	fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
2033 
2034 	fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
2035 	fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
2036 		vScrollBarRect.Height());
2037 
2038 	if (fStatusView != NULL) {
2039 		BSize size = fStatusView->MinSize();
2040 		if (size.height > B_H_SCROLL_BAR_HEIGHT)
2041 			size.height = B_H_SCROLL_BAR_HEIGHT;
2042 		if (size.width > Bounds().Width() / 2)
2043 			size.width = floorf(Bounds().Width() / 2);
2044 
2045 		BPoint offset(hScrollBarRect.LeftTop());
2046 
2047 		if (fBorderStyle == B_PLAIN_BORDER) {
2048 			offset += BPoint(0, 1);
2049 		} else if (fBorderStyle == B_FANCY_BORDER) {
2050 			offset += BPoint(-1, 2);
2051 			size.height -= 1;
2052 		}
2053 
2054 		fStatusView->MoveTo(offset);
2055 		fStatusView->ResizeTo(size.width, size.height);
2056 		hScrollBarRect.left = offset.x + size.width + 1;
2057 	}
2058 
2059 	fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
2060 	fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
2061 		hScrollBarRect.Height());
2062 
2063 	fOutlineView->FixScrollBar(true);
2064 }
2065 
2066 
2067 void
2068 BColumnListView::_Init()
2069 {
2070 	SetViewColor(B_TRANSPARENT_32_BIT);
2071 
2072 	BRect bounds(Bounds());
2073 	if (bounds.Width() <= 0)
2074 		bounds.right = 100;
2075 
2076 	if (bounds.Height() <= 0)
2077 		bounds.bottom = 100;
2078 
2079 	fCustomColors = false;
2080 	_UpdateColors();
2081 
2082 	BRect titleRect;
2083 	BRect outlineRect;
2084 	BRect vScrollBarRect;
2085 	BRect hScrollBarRect;
2086 	_GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
2087 		hScrollBarRect);
2088 
2089 	fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
2090 	AddChild(fOutlineView);
2091 
2092 
2093 	fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
2094 		&fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
2095 	AddChild(fTitleView);
2096 
2097 	fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
2098 		fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
2099 	AddChild(fVerticalScrollBar);
2100 
2101 	fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
2102 		"horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
2103 	AddChild(fHorizontalScrollBar);
2104 
2105 	if (!fShowingHorizontalScrollBar)
2106 		fHorizontalScrollBar->Hide();
2107 
2108 	fOutlineView->FixScrollBar(true);
2109 }
2110 
2111 
2112 void
2113 BColumnListView::_UpdateColors()
2114 {
2115 	if (fCustomColors)
2116 		return;
2117 
2118 	fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR);
2119 	fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2120 	fColorList[B_COLOR_ROW_DIVIDER] = tint_color(
2121 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT);
2122 	fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2123 	fColorList[B_COLOR_SELECTION_TEXT] =
2124 		ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2125 
2126 	// For non focus selection uses the selection color as BListView
2127 	fColorList[B_COLOR_NON_FOCUS_SELECTION] =
2128 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2129 
2130 	// edit mode doesn't work very well
2131 	fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color(
2132 		ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT);
2133 	fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180;
2134 
2135 	// Unused color
2136 	fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2137 
2138 	fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_PANEL_BACKGROUND_COLOR);
2139 	fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR);
2140 
2141 	// Unused colors
2142 	fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2143 	fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2144 }
2145 
2146 
2147 void
2148 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
2149 	BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect)
2150 {
2151 	titleRect = bounds;
2152 	titleRect.bottom = titleRect.top + std::max(kMinTitleHeight,
2153 		ceilf(be_plain_font->Size() * kTitleSpacing));
2154 #if !LOWER_SCROLLBAR
2155 	titleRect.right -= B_V_SCROLL_BAR_WIDTH;
2156 #endif
2157 
2158 	outlineRect = bounds;
2159 	outlineRect.top = titleRect.bottom + 1.0;
2160 	outlineRect.right -= B_V_SCROLL_BAR_WIDTH;
2161 	if (fShowingHorizontalScrollBar)
2162 		outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2163 
2164 	vScrollBarRect = bounds;
2165 #if LOWER_SCROLLBAR
2166 	vScrollBarRect.top += std::max(kMinTitleHeight,
2167 		ceilf(be_plain_font->Size() * kTitleSpacing));
2168 #endif
2169 
2170 	vScrollBarRect.left = vScrollBarRect.right - B_V_SCROLL_BAR_WIDTH;
2171 	if (fShowingHorizontalScrollBar)
2172 		vScrollBarRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2173 
2174 	hScrollBarRect = bounds;
2175 	hScrollBarRect.top = hScrollBarRect.bottom - B_H_SCROLL_BAR_HEIGHT;
2176 	hScrollBarRect.right -= B_V_SCROLL_BAR_WIDTH;
2177 
2178 	// Adjust stuff so the border will fit.
2179 	if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) {
2180 		titleRect.InsetBy(1, 0);
2181 		titleRect.OffsetBy(0, 1);
2182 		outlineRect.InsetBy(1, 1);
2183 	} else if (fBorderStyle == B_FANCY_BORDER) {
2184 		titleRect.InsetBy(2, 0);
2185 		titleRect.OffsetBy(0, 2);
2186 		outlineRect.InsetBy(2, 2);
2187 
2188 		vScrollBarRect.OffsetBy(-1, 0);
2189 #if LOWER_SCROLLBAR
2190 		vScrollBarRect.top += 2;
2191 		vScrollBarRect.bottom -= 1;
2192 #else
2193 		vScrollBarRect.InsetBy(0, 1);
2194 #endif
2195 		hScrollBarRect.OffsetBy(0, -1);
2196 		hScrollBarRect.InsetBy(1, 0);
2197 	}
2198 }
2199 
2200 
2201 // #pragma mark -
2202 
2203 
2204 TitleView::TitleView(BRect rect, OutlineView* horizontalSlave,
2205 	BList* visibleColumns, BList* sortColumns, BColumnListView* listView,
2206 	uint32 resizingMode)
2207 	:
2208 	BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS),
2209 	fOutlineView(horizontalSlave),
2210 	fColumns(visibleColumns),
2211 	fSortColumns(sortColumns),
2212 //	fColumnsWidth(0),
2213 	fVisibleRect(rect.OffsetToCopy(0, 0)),
2214 	fCurrentState(INACTIVE),
2215 	fColumnPop(NULL),
2216 	fMasterView(listView),
2217 	fEditMode(false),
2218 	fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE
2219 		| B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE)
2220 {
2221 	SetViewColor(B_TRANSPARENT_COLOR);
2222 
2223 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2224 	// xxx this needs to be smart about the size of the backbuffer.
2225 	BRect doubleBufferRect(0, 0, 600, 35);
2226 	fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
2227 	fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
2228 		B_FOLLOW_ALL_SIDES, 0);
2229 	fDrawBuffer->Lock();
2230 	fDrawBuffer->AddChild(fDrawBufferView);
2231 	fDrawBuffer->Unlock();
2232 #endif
2233 
2234 	fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2235 	fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2236 
2237 	fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
2238 	fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
2239 
2240 	fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST);
2241 	fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST);
2242 	fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST);
2243 	fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE);
2244 
2245 	FixScrollBar(true);
2246 }
2247 
2248 
2249 TitleView::~TitleView()
2250 {
2251 	delete fColumnPop;
2252 	fColumnPop = NULL;
2253 
2254 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2255 	delete fDrawBuffer;
2256 #endif
2257 	delete fUpSortArrow;
2258 	delete fDownSortArrow;
2259 
2260 	delete fResizeCursor;
2261 	delete fMaxResizeCursor;
2262 	delete fMinResizeCursor;
2263 	delete fColumnMoveCursor;
2264 }
2265 
2266 
2267 void
2268 TitleView::ColumnAdded(BColumn* column)
2269 {
2270 //	fColumnsWidth += column->Width();
2271 	FixScrollBar(false);
2272 	Invalidate();
2273 }
2274 
2275 
2276 void
2277 TitleView::ColumnResized(BColumn* column, float oldWidth)
2278 {
2279 //	fColumnsWidth += column->Width() - oldWidth;
2280 	FixScrollBar(false);
2281 	Invalidate();
2282 }
2283 
2284 
2285 void
2286 TitleView::SetColumnVisible(BColumn* column, bool visible)
2287 {
2288 	if (column->fVisible == visible)
2289 		return;
2290 
2291 	// If setting it visible, do this first so we can find its position
2292 	// to invalidate.  If hiding it, do it last.
2293 	if (visible)
2294 		column->fVisible = visible;
2295 
2296 	BRect titleInvalid;
2297 	GetTitleRect(column, &titleInvalid);
2298 
2299 	// Now really set the visibility
2300 	column->fVisible = visible;
2301 
2302 //	if (visible)
2303 //		fColumnsWidth += column->Width();
2304 //	else
2305 //		fColumnsWidth -= column->Width();
2306 
2307 	BRect outlineInvalid(fOutlineView->VisibleRect());
2308 	outlineInvalid.left = titleInvalid.left;
2309 	titleInvalid.right = outlineInvalid.right;
2310 
2311 	Invalidate(titleInvalid);
2312 	fOutlineView->Invalidate(outlineInvalid);
2313 
2314 	FixScrollBar(false);
2315 }
2316 
2317 
2318 void
2319 TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect)
2320 {
2321 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2322 	int32 numColumns = fColumns->CountItems();
2323 	for (int index = 0; index < numColumns; index++) {
2324 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2325 		if (!column->IsVisible())
2326 			continue;
2327 
2328 		if (column == findColumn) {
2329 			_rect->Set(leftEdge, 0, leftEdge + column->Width(),
2330 				fVisibleRect.bottom);
2331 			return;
2332 		}
2333 
2334 		leftEdge += column->Width() + 1;
2335 	}
2336 
2337 	TRESPASS();
2338 }
2339 
2340 
2341 int32
2342 TitleView::FindColumn(BPoint position, float* _leftEdge)
2343 {
2344 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2345 	int32 numColumns = fColumns->CountItems();
2346 	for (int index = 0; index < numColumns; index++) {
2347 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2348 		if (!column->IsVisible())
2349 			continue;
2350 
2351 		if (leftEdge > position.x)
2352 			break;
2353 
2354 		if (position.x >= leftEdge
2355 			&& position.x <= leftEdge + column->Width()) {
2356 			*_leftEdge = leftEdge;
2357 			return index;
2358 		}
2359 
2360 		leftEdge += column->Width() + 1;
2361 	}
2362 
2363 	return 0;
2364 }
2365 
2366 
2367 void
2368 TitleView::FixScrollBar(bool scrollToFit)
2369 {
2370 	BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2371 	if (hScrollBar == NULL)
2372 		return;
2373 
2374 	float virtualWidth = _VirtualWidth();
2375 
2376 	if (virtualWidth > fVisibleRect.Width()) {
2377 		hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
2378 
2379 		// Perform the little trick if the user is scrolled over too far.
2380 		// See OutlineView::FixScrollBar for a more in depth explanation
2381 		float maxScrollBarValue = virtualWidth - fVisibleRect.Width();
2382 		if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) {
2383 			hScrollBar->SetRange(0.0, maxScrollBarValue);
2384 			hScrollBar->SetSteps(50, fVisibleRect.Width());
2385 		}
2386 	} else if (hScrollBar->Value() == 0.0) {
2387 		// disable scroll bar.
2388 		hScrollBar->SetRange(0.0, 0.0);
2389 	}
2390 }
2391 
2392 
2393 void
2394 TitleView::DragSelectedColumn(BPoint position)
2395 {
2396 	float invalidLeft = fSelectedColumnRect.left;
2397 	float invalidRight = fSelectedColumnRect.right;
2398 
2399 	float leftEdge;
2400 	int32 columnIndex = FindColumn(position, &leftEdge);
2401 	fSelectedColumnRect.OffsetTo(leftEdge, 0);
2402 
2403 	MoveColumn(fSelectedColumn, columnIndex);
2404 
2405 	fSelectedColumn->fVisible = true;
2406 	ComputeDragBoundries(fSelectedColumn, position);
2407 
2408 	// Redraw the new column position
2409 	GetTitleRect(fSelectedColumn, &fSelectedColumnRect);
2410 	invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft);
2411 	invalidRight = MAX(fSelectedColumnRect.right, invalidRight);
2412 
2413 	Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom));
2414 	fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight,
2415 		fOutlineView->VisibleRect().bottom));
2416 
2417 	DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2418 }
2419 
2420 
2421 void
2422 TitleView::MoveColumn(BColumn* column, int32 index)
2423 {
2424 	fColumns->RemoveItem((void*) column);
2425 
2426 	if (-1 == index) {
2427 		// Re-add the column at the end of the list.
2428 		fColumns->AddItem((void*) column);
2429 	} else {
2430 		fColumns->AddItem((void*) column, index);
2431 	}
2432 }
2433 
2434 
2435 void
2436 TitleView::SetColumnFlags(column_flags flags)
2437 {
2438 	fColumnFlags = flags;
2439 }
2440 
2441 
2442 float
2443 TitleView::MarginWidth() const
2444 {
2445 	return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin;
2446 }
2447 
2448 
2449 void
2450 TitleView::ResizeSelectedColumn(BPoint position, bool preferred)
2451 {
2452 	float minWidth = fSelectedColumn->MinWidth();
2453 	float maxWidth = fSelectedColumn->MaxWidth();
2454 
2455 	float oldWidth = fSelectedColumn->Width();
2456 	float originalEdge = fSelectedColumnRect.left + oldWidth;
2457 	if (preferred) {
2458 		float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
2459 		fSelectedColumn->SetWidth(width);
2460 	} else if (position.x > fSelectedColumnRect.left + maxWidth)
2461 		fSelectedColumn->SetWidth(maxWidth);
2462 	else if (position.x < fSelectedColumnRect.left + minWidth)
2463 		fSelectedColumn->SetWidth(minWidth);
2464 	else
2465 		fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
2466 
2467 	float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
2468 		 - originalEdge;
2469 	if (dX != 0) {
2470 		float columnHeight = fVisibleRect.Height();
2471 		BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
2472 		BRect movedRect(originalRect);
2473 		movedRect.OffsetBy(dX, 0);
2474 
2475 		// Update the size of the title column
2476 		BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
2477 		BRect destRect(sourceRect);
2478 		destRect.OffsetBy(fSelectedColumnRect.left, 0);
2479 
2480 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2481 		fDrawBuffer->Lock();
2482 		DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
2483 		fDrawBufferView->Sync();
2484 		fDrawBuffer->Unlock();
2485 
2486 		CopyBits(originalRect, movedRect);
2487 		DrawBitmap(fDrawBuffer, sourceRect, destRect);
2488 #else
2489 		CopyBits(originalRect, movedRect);
2490 		DrawTitle(this, destRect, fSelectedColumn, false);
2491 #endif
2492 
2493 		// Update the body view
2494 		BRect slaveSize = fOutlineView->VisibleRect();
2495 		BRect slaveSource(originalRect);
2496 		slaveSource.bottom = slaveSize.bottom;
2497 		BRect slaveDest(movedRect);
2498 		slaveDest.bottom = slaveSize.bottom;
2499 		fOutlineView->CopyBits(slaveSource, slaveDest);
2500 		fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left,
2501 			fResizingFirstColumn);
2502 
2503 //		fColumnsWidth += dX;
2504 
2505 		// Update the cursor
2506 		if (fSelectedColumn->Width() == minWidth)
2507 			SetViewCursor(fMinResizeCursor, true);
2508 		else if (fSelectedColumn->Width() == maxWidth)
2509 			SetViewCursor(fMaxResizeCursor, true);
2510 		else
2511 			SetViewCursor(fResizeCursor, true);
2512 
2513 		ColumnResized(fSelectedColumn, oldWidth);
2514 	}
2515 }
2516 
2517 
2518 void
2519 TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
2520 {
2521 	float previousColumnLeftEdge = -1000000.0;
2522 	float nextColumnRightEdge = 1000000.0;
2523 
2524 	bool foundColumn = false;
2525 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2526 	int32 numColumns = fColumns->CountItems();
2527 	for (int index = 0; index < numColumns; index++) {
2528 		BColumn* column = (BColumn*) fColumns->ItemAt(index);
2529 		if (!column->IsVisible())
2530 			continue;
2531 
2532 		if (column == findColumn) {
2533 			foundColumn = true;
2534 			continue;
2535 		}
2536 
2537 		if (foundColumn) {
2538 			nextColumnRightEdge = leftEdge + column->Width();
2539 			break;
2540 		} else
2541 			previousColumnLeftEdge = leftEdge;
2542 
2543 		leftEdge += column->Width() + 1;
2544 	}
2545 
2546 	float rightEdge = leftEdge + findColumn->Width();
2547 
2548 	fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
2549 		leftEdge);
2550 	fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
2551 }
2552 
2553 
2554 void
2555 TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
2556 {
2557 	BRect drawRect;
2558 	drawRect = rect;
2559 
2560 	font_height fh;
2561 	GetFontHeight(&fh);
2562 
2563 	float baseline = floor(drawRect.top + fh.ascent
2564 		+ (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
2565 
2566 	BRect bgRect = rect;
2567 
2568 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
2569 	view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
2570 	view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
2571 
2572 	bgRect.bottom--;
2573 	bgRect.right--;
2574 
2575 	if (depressed)
2576 		base = tint_color(base, B_DARKEN_1_TINT);
2577 
2578 	be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
2579 		BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
2580 
2581 	view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
2582 		B_DARKEN_2_TINT));
2583 	view->StrokeLine(rect.RightTop(), rect.RightBottom());
2584 
2585 	// If no column given, nothing else to draw.
2586 	if (column == NULL)
2587 		return;
2588 
2589 	view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2590 
2591 	BFont font;
2592 	GetFont(&font);
2593 	view->SetFont(&font);
2594 
2595 	int sortIndex = fSortColumns->IndexOf(column);
2596 	if (sortIndex >= 0) {
2597 		// Draw sort notation.
2598 		BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline);
2599 
2600 		if (fSortColumns->CountItems() > 1) {
2601 			char str[256];
2602 			sprintf(str, "%d", sortIndex + 1);
2603 			const float w = view->StringWidth(str);
2604 			upperLeft.x -= w;
2605 
2606 			view->SetDrawingMode(B_OP_COPY);
2607 			view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth,
2608 				baseline));
2609 			view->DrawString(str);
2610 		}
2611 
2612 		float bmh = fDownSortArrow->Bounds().Height()+1;
2613 
2614 		view->SetDrawingMode(B_OP_OVER);
2615 
2616 		if (column->fSortAscending) {
2617 			BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2618 				- fDownSortArrow->Bounds().IntegerHeight()) / 2);
2619 			view->DrawBitmapAsync(fDownSortArrow, leftTop);
2620 		} else {
2621 			BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2622 				- fUpSortArrow->Bounds().IntegerHeight()) / 2);
2623 			view->DrawBitmapAsync(fUpSortArrow, leftTop);
2624 		}
2625 
2626 		upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2);
2627 		if (upperLeft.y < drawRect.top)
2628 			upperLeft.y = drawRect.top;
2629 
2630 		// Adjust title stuff for sort indicator
2631 		drawRect.right = upperLeft.x - 2;
2632 	}
2633 
2634 	if (drawRect.right > drawRect.left) {
2635 #if CONSTRAIN_CLIPPING_REGION
2636 		BRegion clipRegion(drawRect);
2637 		view->PushState();
2638 		view->ConstrainClippingRegion(&clipRegion);
2639 #endif
2640 		view->MovePenTo(BPoint(drawRect.left + 8, baseline));
2641 		view->SetDrawingMode(B_OP_OVER);
2642 		view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2643 		column->DrawTitle(drawRect, view);
2644 
2645 #if CONSTRAIN_CLIPPING_REGION
2646 		view->PopState();
2647 #endif
2648 	}
2649 }
2650 
2651 
2652 float
2653 TitleView::_VirtualWidth() const
2654 {
2655 	float width = MarginWidth();
2656 
2657 	int32 count = fColumns->CountItems();
2658 	for (int32 i = 0; i < count; i++) {
2659 		BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
2660 		if (column->IsVisible())
2661 			width += column->Width();
2662 	}
2663 
2664 	return width;
2665 }
2666 
2667 
2668 void
2669 TitleView::Draw(BRect invalidRect)
2670 {
2671 	float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2672 	for (int32 columnIndex = 0; columnIndex < fColumns->CountItems();
2673 		columnIndex++) {
2674 
2675 		BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
2676 		if (!column->IsVisible())
2677 			continue;
2678 
2679 		if (columnLeftEdge > invalidRect.right)
2680 			break;
2681 
2682 		if (columnLeftEdge + column->Width() >= invalidRect.left) {
2683 			BRect titleRect(columnLeftEdge, 0,
2684 				columnLeftEdge + column->Width(), fVisibleRect.Height());
2685 			DrawTitle(this, titleRect, column,
2686 				(fCurrentState == DRAG_COLUMN_INSIDE_TITLE
2687 				&& fSelectedColumn == column));
2688 		}
2689 
2690 		columnLeftEdge += column->Width() + 1;
2691 	}
2692 
2693 
2694 	// bevels for right title margin
2695 	if (columnLeftEdge <= invalidRect.right) {
2696 		BRect titleRect(columnLeftEdge, 0, Bounds().right + 2,
2697 			fVisibleRect.Height());
2698 		DrawTitle(this, titleRect, NULL, false);
2699 	}
2700 
2701 	// bevels for left title margin
2702 	if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) {
2703 		BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1,
2704 			fVisibleRect.Height());
2705 		DrawTitle(this, titleRect, NULL, false);
2706 	}
2707 
2708 #if DRAG_TITLE_OUTLINE
2709 	// (internal) column drag indicator
2710 	if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) {
2711 		BRect dragRect(fSelectedColumnRect);
2712 		dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2713 		if (dragRect.Intersects(invalidRect)) {
2714 			SetHighColor(0, 0, 255);
2715 			StrokeRect(dragRect);
2716 		}
2717 	}
2718 #endif
2719 }
2720 
2721 
2722 void
2723 TitleView::ScrollTo(BPoint position)
2724 {
2725 	fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0);
2726 	fVisibleRect.OffsetTo(position.x, position.y);
2727 
2728 	// Perform the little trick if the user is scrolled over too far.
2729 	// See OutlineView::ScrollTo for a more in depth explanation
2730 	float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width();
2731 	BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2732 	float min, max;
2733 	hScrollBar->GetRange(&min, &max);
2734 	if (max != maxScrollBarValue && position.x > maxScrollBarValue)
2735 		FixScrollBar(true);
2736 
2737 	_inherited::ScrollTo(position);
2738 }
2739 
2740 
2741 void
2742 TitleView::MessageReceived(BMessage* message)
2743 {
2744 	if (message->what == kToggleColumn) {
2745 		int32 num;
2746 		if (message->FindInt32("be:field_num", &num) == B_OK) {
2747 			for (int index = 0; index < fColumns->CountItems(); index++) {
2748 				BColumn* column = (BColumn*) fColumns->ItemAt(index);
2749 				if (column == NULL)
2750 					continue;
2751 
2752 				if (column->LogicalFieldNum() == num)
2753 					column->SetVisible(!column->IsVisible());
2754 			}
2755 		}
2756 		return;
2757 	}
2758 
2759 	BView::MessageReceived(message);
2760 }
2761 
2762 
2763 void
2764 TitleView::MouseDown(BPoint position)
2765 {
2766 	if (fEditMode)
2767 		return;
2768 
2769 	int32 buttons = 1;
2770 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2771 	if (buttons == B_SECONDARY_MOUSE_BUTTON
2772 		&& (fColumnFlags & B_ALLOW_COLUMN_POPUP)) {
2773 		// Right mouse button -- bring up menu to show/hide columns.
2774 		if (fColumnPop == NULL)
2775 			fColumnPop = new BPopUpMenu("Columns", false, false);
2776 
2777 		fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true);
2778 		BMessenger me(this);
2779 		for (int index = 0; index < fColumns->CountItems(); index++) {
2780 			BColumn* column = (BColumn*) fColumns->ItemAt(index);
2781 			if (column == NULL)
2782 				continue;
2783 
2784 			BString name;
2785 			column->GetColumnName(&name);
2786 			BMessage* message = new BMessage(kToggleColumn);
2787 			message->AddInt32("be:field_num", column->LogicalFieldNum());
2788 			BMenuItem* item = new BMenuItem(name.String(), message);
2789 			item->SetMarked(column->IsVisible());
2790 			item->SetTarget(me);
2791 			fColumnPop->AddItem(item);
2792 		}
2793 
2794 		BPoint screenPosition = ConvertToScreen(position);
2795 		BRect sticky(screenPosition, screenPosition);
2796 		sticky.InsetBy(-5, -5);
2797 		fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true);
2798 
2799 		return;
2800 	}
2801 
2802 	fResizingFirstColumn = true;
2803 	float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2804 	for (int index = 0; index < fColumns->CountItems(); index++) {
2805 		BColumn* column = (BColumn*)fColumns->ItemAt(index);
2806 		if (column == NULL || !column->IsVisible())
2807 			continue;
2808 
2809 		if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2810 			break;
2811 
2812 		// check for resizing a column
2813 		float rightEdge = leftEdge + column->Width();
2814 
2815 		if (column->ShowHeading()) {
2816 			if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2817 				&& position.x < rightEdge + kColumnResizeAreaWidth / 2
2818 				&& column->MaxWidth() > column->MinWidth()
2819 				&& (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) {
2820 
2821 				int32 clicks = 0;
2822 				fSelectedColumn = column;
2823 				fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2824 					fVisibleRect.Height());
2825 				Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2826 				if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) {
2827 					ResizeSelectedColumn(position, true);
2828 					fCurrentState = INACTIVE;
2829 					break;
2830 				}
2831 				fCurrentState = RESIZING_COLUMN;
2832 				fClickPoint = BPoint(position.x - rightEdge - 1,
2833 					position.y - fSelectedColumnRect.top);
2834 				SetMouseEventMask(B_POINTER_EVENTS,
2835 					B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2836 				break;
2837 			}
2838 
2839 			fResizingFirstColumn = false;
2840 
2841 			// check for clicking on a column
2842 			if (position.x > leftEdge && position.x < rightEdge) {
2843 				fCurrentState = PRESSING_COLUMN;
2844 				fSelectedColumn = column;
2845 				fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2846 					fVisibleRect.Height());
2847 				DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2848 				fClickPoint = BPoint(position.x - fSelectedColumnRect.left,
2849 					position.y - fSelectedColumnRect.top);
2850 				SetMouseEventMask(B_POINTER_EVENTS,
2851 					B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2852 				break;
2853 			}
2854 		}
2855 		leftEdge = rightEdge + 1;
2856 	}
2857 }
2858 
2859 
2860 void
2861 TitleView::MouseMoved(BPoint position, uint32 transit,
2862 	const BMessage* dragMessage)
2863 {
2864 	if (fEditMode)
2865 		return;
2866 
2867 	// Handle column manipulation
2868 	switch (fCurrentState) {
2869 		case RESIZING_COLUMN:
2870 			ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
2871 			break;
2872 
2873 		case PRESSING_COLUMN: {
2874 			if (abs((int32)(position.x - (fClickPoint.x
2875 					+ fSelectedColumnRect.left))) > kColumnResizeAreaWidth
2876 				|| abs((int32)(position.y - (fClickPoint.y
2877 					+ fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
2878 				// User has moved the mouse more than the tolerable amount,
2879 				// initiate a drag.
2880 				if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
2881 					if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
2882 						fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2883 						ComputeDragBoundries(fSelectedColumn, position);
2884 						SetViewCursor(fColumnMoveCursor, true);
2885 #if DRAG_TITLE_OUTLINE
2886 						BRect invalidRect(fSelectedColumnRect);
2887 						invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
2888 						fCurrentDragPosition = position;
2889 						Invalidate(invalidRect);
2890 #endif
2891 					}
2892 				} else {
2893 					if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
2894 						// Dragged outside view
2895 						fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2896 						fSelectedColumn->SetVisible(false);
2897 						BRect dragRect(fSelectedColumnRect);
2898 
2899 						// There is a race condition where the mouse may have
2900 						// moved by the time we get to handle this message.
2901 						// If the user drags a column very quickly, this
2902 						// results in the annoying bug where the cursor is
2903 						// outside of the rectangle that is being dragged
2904 						// around.  Call GetMouse with the checkQueue flag set
2905 						// to false so we can get the most recent position of
2906 						// the mouse.  This minimizes this problem (although
2907 						// it is currently not possible to completely eliminate
2908 						// it).
2909 						uint32 buttons;
2910 						GetMouse(&position, &buttons, false);
2911 						dragRect.OffsetTo(position.x - fClickPoint.x,
2912 							position.y - dragRect.Height() / 2);
2913 						BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2914 					}
2915 				}
2916 			}
2917 
2918 			break;
2919 		}
2920 
2921 		case DRAG_COLUMN_INSIDE_TITLE: {
2922 			if (transit == B_EXITED_VIEW
2923 				&& (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) {
2924 				// Dragged outside view
2925 				fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2926 				fSelectedColumn->SetVisible(false);
2927 				BRect dragRect(fSelectedColumnRect);
2928 
2929 				// See explanation above.
2930 				uint32 buttons;
2931 				GetMouse(&position, &buttons, false);
2932 
2933 				dragRect.OffsetTo(position.x - fClickPoint.x,
2934 					position.y - fClickPoint.y);
2935 				BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2936 			} else if (position.x < fLeftDragBoundry
2937 				|| position.x > fRightDragBoundry) {
2938 				DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2939 			}
2940 
2941 #if DRAG_TITLE_OUTLINE
2942 			// Set up the invalid rect to include the rect for the previous
2943 			// position of the drag rect, as well as the new one.
2944 			BRect invalidRect(fSelectedColumnRect);
2945 			invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2946 			if (position.x < fCurrentDragPosition.x)
2947 				invalidRect.left -= fCurrentDragPosition.x - position.x;
2948 			else
2949 				invalidRect.right += position.x - fCurrentDragPosition.x;
2950 
2951 			fCurrentDragPosition = position;
2952 			Invalidate(invalidRect);
2953 #endif
2954 			break;
2955 		}
2956 
2957 		case DRAG_COLUMN_OUTSIDE_TITLE:
2958 			if (transit == B_ENTERED_VIEW) {
2959 				// Drag back into view
2960 				EndRectTracking();
2961 				fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2962 				fSelectedColumn->SetVisible(true);
2963 				DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2964 			}
2965 
2966 			break;
2967 
2968 		case INACTIVE:
2969 			// Check for cursor changes if we are over the resize area for
2970 			// a column.
2971 			BColumn* resizeColumn = 0;
2972 			float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2973 			for (int index = 0; index < fColumns->CountItems(); index++) {
2974 				BColumn* column = (BColumn*) fColumns->ItemAt(index);
2975 				if (!column->IsVisible())
2976 					continue;
2977 
2978 				if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2979 					break;
2980 
2981 				float rightEdge = leftEdge + column->Width();
2982 				if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2983 					&& position.x < rightEdge + kColumnResizeAreaWidth / 2
2984 					&& column->MaxWidth() > column->MinWidth()) {
2985 					resizeColumn = column;
2986 					break;
2987 				}
2988 
2989 				leftEdge = rightEdge + 1;
2990 			}
2991 
2992 			// Update the cursor
2993 			if (resizeColumn) {
2994 				if (resizeColumn->Width() == resizeColumn->MinWidth())
2995 					SetViewCursor(fMinResizeCursor, true);
2996 				else if (resizeColumn->Width() == resizeColumn->MaxWidth())
2997 					SetViewCursor(fMaxResizeCursor, true);
2998 				else
2999 					SetViewCursor(fResizeCursor, true);
3000 			} else
3001 				SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3002 			break;
3003 	}
3004 }
3005 
3006 
3007 void
3008 TitleView::MouseUp(BPoint position)
3009 {
3010 	if (fEditMode)
3011 		return;
3012 
3013 	switch (fCurrentState) {
3014 		case RESIZING_COLUMN:
3015 			ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
3016 			fCurrentState = INACTIVE;
3017 			FixScrollBar(false);
3018 			break;
3019 
3020 		case PRESSING_COLUMN: {
3021 			if (fMasterView->SortingEnabled()) {
3022 				if (fSortColumns->HasItem(fSelectedColumn)) {
3023 					if ((modifiers() & B_CONTROL_KEY) == 0
3024 						&& fSortColumns->CountItems() > 1) {
3025 						fSortColumns->MakeEmpty();
3026 						fSortColumns->AddItem(fSelectedColumn);
3027 					}
3028 
3029 					fSelectedColumn->fSortAscending
3030 						= !fSelectedColumn->fSortAscending;
3031 				} else {
3032 					if ((modifiers() & B_CONTROL_KEY) == 0)
3033 						fSortColumns->MakeEmpty();
3034 
3035 					fSortColumns->AddItem(fSelectedColumn);
3036 					fSelectedColumn->fSortAscending = true;
3037 				}
3038 
3039 				fOutlineView->StartSorting();
3040 			}
3041 
3042 			fCurrentState = INACTIVE;
3043 			Invalidate();
3044 			break;
3045 		}
3046 
3047 		case DRAG_COLUMN_INSIDE_TITLE:
3048 			fCurrentState = INACTIVE;
3049 
3050 #if DRAG_TITLE_OUTLINE
3051 			Invalidate();	// xxx Can make this smaller
3052 #else
3053 			Invalidate(fSelectedColumnRect);
3054 #endif
3055 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3056 			break;
3057 
3058 		case DRAG_COLUMN_OUTSIDE_TITLE:
3059 			fCurrentState = INACTIVE;
3060 			EndRectTracking();
3061 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3062 			break;
3063 
3064 		default:
3065 			;
3066 	}
3067 }
3068 
3069 
3070 void
3071 TitleView::FrameResized(float width, float height)
3072 {
3073 	fVisibleRect.right = fVisibleRect.left + width;
3074 	fVisibleRect.bottom = fVisibleRect.top + height;
3075 	FixScrollBar(true);
3076 }
3077 
3078 
3079 // #pragma mark - OutlineView
3080 
3081 
3082 OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns,
3083 	BColumnListView* listView)
3084 	:
3085 	BView(rect, "outline_view", B_FOLLOW_ALL_SIDES,
3086 		B_WILL_DRAW | B_FRAME_EVENTS),
3087 	fColumns(visibleColumns),
3088 	fSortColumns(sortColumns),
3089 	fItemsHeight(0.0),
3090 	fVisibleRect(rect.OffsetToCopy(0, 0)),
3091 	fFocusRow(0),
3092 	fRollOverRow(0),
3093 	fLastSelectedItem(0),
3094 	fFirstSelectedItem(0),
3095 	fSortThread(B_BAD_THREAD_ID),
3096 	fCurrentState(INACTIVE),
3097 	fMasterView(listView),
3098 	fSelectionMode(B_MULTIPLE_SELECTION_LIST),
3099 	fTrackMouse(false),
3100 	fCurrentField(0),
3101 	fCurrentRow(0),
3102 	fCurrentColumn(0),
3103 	fMouseDown(false),
3104 	fCurrentCode(B_OUTSIDE_VIEW),
3105 	fEditMode(false),
3106 	fDragging(false),
3107 	fClickCount(0),
3108 	fDropHighlightY(-1)
3109 {
3110 	SetViewColor(B_TRANSPARENT_COLOR);
3111 
3112 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3113 	// TODO: This needs to be smart about the size of the buffer.
3114 	// Also, the buffer can be shared with the title's buffer.
3115 	BRect doubleBufferRect(0, 0, 600, 35);
3116 	fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
3117 	fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
3118 		B_FOLLOW_ALL_SIDES, 0);
3119 	fDrawBuffer->Lock();
3120 	fDrawBuffer->AddChild(fDrawBufferView);
3121 	fDrawBuffer->Unlock();
3122 #endif
3123 
3124 	FixScrollBar(true);
3125 	fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
3126 	fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
3127 }
3128 
3129 
3130 OutlineView::~OutlineView()
3131 {
3132 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3133 	delete fDrawBuffer;
3134 #endif
3135 
3136 	Clear();
3137 }
3138 
3139 
3140 void
3141 OutlineView::Clear()
3142 {
3143 	DeselectAll();
3144 		// Make sure selection list doesn't point to deleted rows!
3145 	RecursiveDeleteRows(&fRows, false);
3146 	fItemsHeight = 0.0;
3147 	FixScrollBar(true);
3148 	Invalidate();
3149 }
3150 
3151 
3152 void
3153 OutlineView::SetSelectionMode(list_view_type mode)
3154 {
3155 	DeselectAll();
3156 	fSelectionMode = mode;
3157 }
3158 
3159 
3160 list_view_type
3161 OutlineView::SelectionMode() const
3162 {
3163 	return fSelectionMode;
3164 }
3165 
3166 
3167 void
3168 OutlineView::Deselect(BRow* row)
3169 {
3170 	if (row == NULL)
3171 		return;
3172 
3173 	if (row->fNextSelected != 0) {
3174 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
3175 		row->fPrevSelected->fNextSelected = row->fNextSelected;
3176 		row->fNextSelected = 0;
3177 		row->fPrevSelected = 0;
3178 		Invalidate();
3179 	}
3180 }
3181 
3182 
3183 void
3184 OutlineView::AddToSelection(BRow* row)
3185 {
3186 	if (row == NULL)
3187 		return;
3188 
3189 	if (row->fNextSelected == 0) {
3190 		if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3191 			DeselectAll();
3192 
3193 		row->fNextSelected = fSelectionListDummyHead.fNextSelected;
3194 		row->fPrevSelected = &fSelectionListDummyHead;
3195 		row->fNextSelected->fPrevSelected = row;
3196 		row->fPrevSelected->fNextSelected = row;
3197 
3198 		BRect invalidRect;
3199 		if (FindVisibleRect(row, &invalidRect))
3200 			Invalidate(invalidRect);
3201 	}
3202 }
3203 
3204 
3205 void
3206 OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
3207 {
3208 	if (list == NULL)
3209 		return;
3210 
3211 	while (true) {
3212 		BRow* row = list->RemoveItemAt(0L);
3213 		if (row == 0)
3214 			break;
3215 
3216 		if (row->fChildList)
3217 			RecursiveDeleteRows(row->fChildList, true);
3218 
3219 		delete row;
3220 	}
3221 
3222 	if (isOwner)
3223 		delete list;
3224 }
3225 
3226 
3227 void
3228 OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
3229 {
3230 	// TODO: Remove code duplication (private function which takes a view
3231 	// pointer, pass "this" in non-double buffered mode)!
3232 	// Watch out for sourceRect versus destRect though!
3233 	if (!column)
3234 		return;
3235 
3236 	font_height fh;
3237 	GetFontHeight(&fh);
3238 	float line = 0.0;
3239 	bool tintedLine = true;
3240 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3241 		line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
3242 
3243 		BRow* row = iterator.CurrentRow();
3244 		float rowHeight = row->Height();
3245 		if (line > fVisibleRect.bottom)
3246 			break;
3247 		tintedLine = !tintedLine;
3248 
3249 		if (line + rowHeight >= fVisibleRect.top) {
3250 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3251 			BRect sourceRect(0, 0, column->Width(), rowHeight);
3252 #endif
3253 			BRect destRect(leftEdge, line, leftEdge + column->Width(),
3254 				line + rowHeight);
3255 
3256 			rgb_color highColor;
3257 			rgb_color lowColor;
3258 			if (row->fNextSelected != 0) {
3259 				if (fEditMode) {
3260 					highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3261 					lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3262 				} else {
3263 					highColor = fMasterView->Color(B_COLOR_SELECTION);
3264 					lowColor = fMasterView->Color(B_COLOR_SELECTION);
3265 				}
3266 			} else {
3267 				highColor = fMasterView->Color(B_COLOR_BACKGROUND);
3268 				lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3269 			}
3270 			if (tintedLine)
3271 				lowColor = tint_color(lowColor, kTintedLineTint);
3272 
3273 
3274 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3275 			fDrawBuffer->Lock();
3276 
3277 			fDrawBufferView->SetHighColor(highColor);
3278 			fDrawBufferView->SetLowColor(lowColor);
3279 
3280 			BFont font;
3281 			GetFont(&font);
3282 			fDrawBufferView->SetFont(&font);
3283 			fDrawBufferView->FillRect(sourceRect, B_SOLID_LOW);
3284 
3285 			if (isFirstColumn) {
3286 				// If this is the first column, double buffer drawing the latch
3287 				// too.
3288 				destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3289 					- fMasterView->LatchWidth();
3290 				sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3291 					- fMasterView->LatchWidth();
3292 
3293 				LatchType pos = B_NO_LATCH;
3294 				if (row->HasLatch())
3295 					pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH;
3296 
3297 				BRect latchRect(sourceRect);
3298 				latchRect.right = latchRect.left + fMasterView->LatchWidth();
3299 				fMasterView->DrawLatch(fDrawBufferView, latchRect, pos, row);
3300 			}
3301 
3302 			BField* field = row->GetField(column->fFieldID);
3303 			if (field) {
3304 				BRect fieldRect(sourceRect);
3305 				if (isFirstColumn)
3306 					fieldRect.left += fMasterView->LatchWidth();
3307 
3308 	#if CONSTRAIN_CLIPPING_REGION
3309 				BRegion clipRegion(fieldRect);
3310 				fDrawBufferView->PushState();
3311 				fDrawBufferView->ConstrainClippingRegion(&clipRegion);
3312 	#endif
3313 				fDrawBufferView->SetHighColor(fMasterView->Color(
3314 					row->fNextSelected ? B_COLOR_SELECTION_TEXT
3315 						: B_COLOR_TEXT));
3316 				float baseline = floor(fieldRect.top + fh.ascent
3317 					+ (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
3318 				fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
3319 				column->DrawField(field, fieldRect, fDrawBufferView);
3320 	#if CONSTRAIN_CLIPPING_REGION
3321 				fDrawBufferView->PopState();
3322 	#endif
3323 			}
3324 
3325 			if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3326 				&& Window()->IsActive()) {
3327 				fDrawBufferView->SetHighColor(fMasterView->Color(
3328 					B_COLOR_ROW_DIVIDER));
3329 				fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top,
3330 					10000.0, sourceRect.bottom));
3331 			}
3332 
3333 			fDrawBufferView->Sync();
3334 			fDrawBuffer->Unlock();
3335 			SetDrawingMode(B_OP_COPY);
3336 			DrawBitmap(fDrawBuffer, sourceRect, destRect);
3337 
3338 #else
3339 
3340 			SetHighColor(highColor);
3341 			SetLowColor(lowColor);
3342 			FillRect(destRect, B_SOLID_LOW);
3343 
3344 			BField* field = row->GetField(column->fFieldID);
3345 			if (field) {
3346 	#if CONSTRAIN_CLIPPING_REGION
3347 				BRegion clipRegion(destRect);
3348 				PushState();
3349 				ConstrainClippingRegion(&clipRegion);
3350 	#endif
3351 				SetHighColor(fMasterView->Color(row->fNextSelected
3352 					? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
3353 				float baseline = floor(destRect.top + fh.ascent
3354 					+ (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
3355 				MovePenTo(destRect.left + 8, baseline);
3356 				column->DrawField(field, destRect, this);
3357 	#if CONSTRAIN_CLIPPING_REGION
3358 				PopState();
3359 	#endif
3360 			}
3361 
3362 			if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3363 				&& Window()->IsActive()) {
3364 				SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3365 				StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
3366 			}
3367 #endif
3368 		}
3369 	}
3370 }
3371 
3372 
3373 void
3374 OutlineView::Draw(BRect invalidBounds)
3375 {
3376 #if SMART_REDRAW
3377 	BRegion invalidRegion;
3378 	GetClippingRegion(&invalidRegion);
3379 #endif
3380 
3381 	font_height fh;
3382 	GetFontHeight(&fh);
3383 
3384 	float line = 0.0;
3385 	bool tintedLine = true;
3386 	int32 numColumns = fColumns->CountItems();
3387 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3388 		iterator.GoToNext()) {
3389 		BRow* row = iterator.CurrentRow();
3390 		if (line > invalidBounds.bottom)
3391 			break;
3392 
3393 		tintedLine = !tintedLine;
3394 		float rowHeight = row->Height();
3395 
3396 		if (line >= invalidBounds.top - rowHeight) {
3397 			bool isFirstColumn = true;
3398 			float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
3399 
3400 			// setup background color
3401 			rgb_color lowColor;
3402 			if (row->fNextSelected != 0) {
3403 				if (Window()->IsActive()) {
3404 					if (fEditMode)
3405 						lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3406 					else
3407 						lowColor = fMasterView->Color(B_COLOR_SELECTION);
3408 				}
3409 				else
3410 					lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
3411 			} else
3412 				lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3413 			if (tintedLine)
3414 				lowColor = tint_color(lowColor, kTintedLineTint);
3415 
3416 			for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) {
3417 				BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
3418 				if (!column->IsVisible())
3419 					continue;
3420 
3421 				if (!isFirstColumn && fieldLeftEdge > invalidBounds.right)
3422 					break;
3423 
3424 				if (fieldLeftEdge + column->Width() >= invalidBounds.left) {
3425 					BRect fullRect(fieldLeftEdge, line,
3426 						fieldLeftEdge + column->Width(), line + rowHeight);
3427 
3428 					bool clippedFirstColumn = false;
3429 						// This happens when a column is indented past the
3430 						// beginning of the next column.
3431 
3432 					SetHighColor(lowColor);
3433 
3434 					BRect destRect(fullRect);
3435 					if (isFirstColumn) {
3436 						fullRect.left -= fMasterView->LatchWidth();
3437 						destRect.left += iterator.CurrentLevel()
3438 							* kOutlineLevelIndent;
3439 						if (destRect.left >= destRect.right) {
3440 							// clipped
3441 							FillRect(BRect(0, line, fieldLeftEdge
3442 								+ column->Width(), line + rowHeight));
3443 							clippedFirstColumn = true;
3444 						}
3445 
3446 						FillRect(BRect(0, line, MAX(kLeftMargin,
3447 							fMasterView->LatchWidth()), line + row->Height()));
3448 					}
3449 
3450 
3451 #if SMART_REDRAW
3452 					if (!clippedFirstColumn
3453 						&& invalidRegion.Intersects(fullRect)) {
3454 #else
3455 					if (!clippedFirstColumn) {
3456 #endif
3457 						FillRect(fullRect);	// Using color set above
3458 
3459 						// Draw the latch widget if it has one.
3460 						if (isFirstColumn) {
3461 							if (row == fTargetRow
3462 								&& fCurrentState == LATCH_CLICKED) {
3463 								// Note that this only occurs if the user is
3464 								// holding down a latch while items are added
3465 								// in the background.
3466 								BPoint pos;
3467 								uint32 buttons;
3468 								GetMouse(&pos, &buttons);
3469 								if (fLatchRect.Contains(pos)) {
3470 									fMasterView->DrawLatch(this, fLatchRect,
3471 										B_PRESSED_LATCH, fTargetRow);
3472 								} else {
3473 									fMasterView->DrawLatch(this, fLatchRect,
3474 										row->fIsExpanded ? B_OPEN_LATCH
3475 											: B_CLOSED_LATCH, fTargetRow);
3476 								}
3477 							} else {
3478 								LatchType pos = B_NO_LATCH;
3479 								if (row->HasLatch())
3480 									pos = row->fIsExpanded ? B_OPEN_LATCH
3481 										: B_CLOSED_LATCH;
3482 
3483 								fMasterView->DrawLatch(this,
3484 									BRect(destRect.left
3485 										- fMasterView->LatchWidth(),
3486 									destRect.top, destRect.left,
3487 									destRect.bottom), pos, row);
3488 							}
3489 						}
3490 
3491 						SetHighColor(fMasterView->HighColor());
3492 							// The master view just holds the high color for us.
3493 						SetLowColor(lowColor);
3494 
3495 						BField* field = row->GetField(column->fFieldID);
3496 						if (field) {
3497 #if CONSTRAIN_CLIPPING_REGION
3498 							BRegion clipRegion(destRect);
3499 							PushState();
3500 							ConstrainClippingRegion(&clipRegion);
3501 #endif
3502 							SetHighColor(fMasterView->Color(
3503 								row->fNextSelected ? B_COLOR_SELECTION_TEXT
3504 								: B_COLOR_TEXT));
3505 							float baseline = floor(destRect.top + fh.ascent
3506 								+ (destRect.Height() + 1
3507 								- (fh.ascent+fh.descent)) / 2);
3508 							MovePenTo(destRect.left + 8, baseline);
3509 							column->DrawField(field, destRect, this);
3510 #if CONSTRAIN_CLIPPING_REGION
3511 							PopState();
3512 #endif
3513 						}
3514 					}
3515 				}
3516 
3517 				isFirstColumn = false;
3518 				fieldLeftEdge += column->Width() + 1;
3519 			}
3520 
3521 			if (fieldLeftEdge <= invalidBounds.right) {
3522 				SetHighColor(lowColor);
3523 				FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
3524 					line + rowHeight));
3525 			}
3526 		}
3527 
3528 		// indicate the keyboard focus row
3529 		if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3530 			&& Window()->IsActive()) {
3531 			SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3532 			StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
3533 		}
3534 
3535 		line += rowHeight + 1;
3536 	}
3537 
3538 	if (line <= invalidBounds.bottom) {
3539 		// fill background below last item
3540 		SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3541 		FillRect(BRect(invalidBounds.left, line, invalidBounds.right,
3542 			invalidBounds.bottom));
3543 	}
3544 
3545 	// Draw the drop target line
3546 	if (fDropHighlightY != -1) {
3547 		InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3548 			1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3549 	}
3550 }
3551 
3552 
3553 BRow*
3554 OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top)
3555 {
3556 	if (_rowIndent && _top) {
3557 		float line = 0.0;
3558 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3559 			iterator.GoToNext()) {
3560 
3561 			BRow* row = iterator.CurrentRow();
3562 			if (line > ypos)
3563 				break;
3564 
3565 			float rowHeight = row->Height();
3566 			if (ypos <= line + rowHeight) {
3567 				*_top = line;
3568 				*_rowIndent = iterator.CurrentLevel();
3569 				return row;
3570 			}
3571 
3572 			line += rowHeight + 1;
3573 		}
3574 	}
3575 
3576 	return NULL;
3577 }
3578 
3579 void OutlineView::SetMouseTrackingEnabled(bool enabled)
3580 {
3581 	fTrackMouse = enabled;
3582 	if (!enabled && fDropHighlightY != -1) {
3583 		// Erase the old target line
3584 		InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3585 			1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3586 		fDropHighlightY = -1;
3587 	}
3588 }
3589 
3590 
3591 //
3592 // Note that this interaction is not totally safe.  If items are added to
3593 // the list in the background, the widget rect will be incorrect, possibly
3594 // resulting in drawing glitches.  The code that adds items needs to be a little smarter
3595 // about invalidating state.
3596 //
3597 void
3598 OutlineView::MouseDown(BPoint position)
3599 {
3600 	if (!fEditMode)
3601 		fMasterView->MakeFocus(true);
3602 
3603 	// Check to see if the user is clicking on a widget to open a section
3604 	// of the list.
3605 	bool reset_click_count = false;
3606 	int32 indent;
3607 	float rowTop;
3608 	BRow* row = FindRow(position.y, &indent, &rowTop);
3609 	if (row != NULL) {
3610 
3611 		// Update fCurrentField
3612 		bool handle_field = false;
3613 		BField* new_field = 0;
3614 		BRow* new_row = 0;
3615 		BColumn* new_column = 0;
3616 		BRect new_rect;
3617 
3618 		if (position.y >= 0) {
3619 			if (position.x >= 0) {
3620 				float x = 0;
3621 				for (int32 c = 0; c < fMasterView->CountColumns(); c++) {
3622 					new_column = fMasterView->ColumnAt(c);
3623 					if (!new_column->IsVisible())
3624 						continue;
3625 					if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x)
3626 						+ new_column->Width() >= position.x) {
3627 						if (new_column->WantsEvents()) {
3628 							new_field = row->GetField(c);
3629 							new_row = row;
3630 							FindRect(new_row,&new_rect);
3631 							new_rect.left = MAX(kLeftMargin,
3632 								fMasterView->LatchWidth()) + x;
3633 							new_rect.right = new_rect.left
3634 								+ new_column->Width() - 1;
3635 							handle_field = true;
3636 						}
3637 						break;
3638 					}
3639 					x += new_column->Width();
3640 				}
3641 			}
3642 		}
3643 
3644 		// Handle mouse down
3645 		if (handle_field) {
3646 			fMouseDown = true;
3647 			fFieldRect = new_rect;
3648 			fCurrentColumn = new_column;
3649 			fCurrentRow = new_row;
3650 			fCurrentField = new_field;
3651 			fCurrentCode = B_INSIDE_VIEW;
3652 			BMessage* message = Window()->CurrentMessage();
3653 			int32 buttons = 1;
3654 			message->FindInt32("buttons", &buttons);
3655 			fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
3656 				fCurrentField, fFieldRect, position, buttons);
3657 		}
3658 
3659 		if (!fEditMode) {
3660 
3661 			fTargetRow = row;
3662 			fTargetRowTop = rowTop;
3663 			FindVisibleRect(fFocusRow, &fFocusRowRect);
3664 
3665 			float leftWidgetBoundry = indent * kOutlineLevelIndent
3666 				+ MAX(kLeftMargin, fMasterView->LatchWidth())
3667 				- fMasterView->LatchWidth();
3668 			fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
3669 				+ fMasterView->LatchWidth(), rowTop + row->Height());
3670 			if (fLatchRect.Contains(position) && row->HasLatch()) {
3671 				fCurrentState = LATCH_CLICKED;
3672 				if (fTargetRow->fNextSelected != 0)
3673 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3674 				else
3675 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3676 
3677 				FillRect(fLatchRect);
3678 				if (fLatchRect.Contains(position)) {
3679 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3680 						row);
3681 				} else {
3682 					fMasterView->DrawLatch(this, fLatchRect,
3683 						fTargetRow->fIsExpanded ? B_OPEN_LATCH
3684 						: B_CLOSED_LATCH, row);
3685 				}
3686 			} else {
3687 				Invalidate(fFocusRowRect);
3688 				fFocusRow = fTargetRow;
3689 				FindVisibleRect(fFocusRow, &fFocusRowRect);
3690 
3691 				ASSERT(fTargetRow != 0);
3692 
3693 				if ((modifiers() & B_CONTROL_KEY) == 0)
3694 					DeselectAll();
3695 
3696 				if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
3697 					&& fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
3698 					SelectRange(fFirstSelectedItem, fTargetRow);
3699 				}
3700 				else {
3701 					if (fTargetRow->fNextSelected != 0) {
3702 						// Unselect row
3703 						fTargetRow->fNextSelected->fPrevSelected
3704 							= fTargetRow->fPrevSelected;
3705 						fTargetRow->fPrevSelected->fNextSelected
3706 							= fTargetRow->fNextSelected;
3707 						fTargetRow->fPrevSelected = 0;
3708 						fTargetRow->fNextSelected = 0;
3709 						fFirstSelectedItem = NULL;
3710 					} else {
3711 						// Select row
3712 						if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3713 							DeselectAll();
3714 
3715 						fTargetRow->fNextSelected
3716 							= fSelectionListDummyHead.fNextSelected;
3717 						fTargetRow->fPrevSelected
3718 							= &fSelectionListDummyHead;
3719 						fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
3720 						fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
3721 						fFirstSelectedItem = fTargetRow;
3722 					}
3723 
3724 					Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
3725 						fVisibleRect.right,
3726 						fTargetRowTop + fTargetRow->Height()));
3727 				}
3728 
3729 				fCurrentState = ROW_CLICKED;
3730 				if (fLastSelectedItem != fTargetRow)
3731 					reset_click_count = true;
3732 				fLastSelectedItem = fTargetRow;
3733 				fMasterView->SelectionChanged();
3734 
3735 			}
3736 		}
3737 
3738 		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
3739 			B_NO_POINTER_HISTORY);
3740 
3741 	} else if (fFocusRow != 0) {
3742 		// User clicked in open space, unhighlight focus row.
3743 		FindVisibleRect(fFocusRow, &fFocusRowRect);
3744 		fFocusRow = 0;
3745 		Invalidate(fFocusRowRect);
3746 	}
3747 
3748 	// We stash the click counts here because the 'clicks' field
3749 	// is not in the CurrentMessage() when MouseUp is called... ;(
3750 	if (reset_click_count)
3751 		fClickCount = 1;
3752 	else
3753 		Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
3754 	fClickPoint = position;
3755 
3756 }
3757 
3758 
3759 void
3760 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
3761 	const BMessage* /*dragMessage*/)
3762 {
3763 	if (!fMouseDown) {
3764 		// Update fCurrentField
3765 		bool handle_field = false;
3766 		BField* new_field = 0;
3767 		BRow* new_row = 0;
3768 		BColumn* new_column = 0;
3769 		BRect new_rect(0,0,0,0);
3770 		if (position.y >=0 ) {
3771 			float top;
3772 			int32 indent;
3773 			BRow* row = FindRow(position.y, &indent, &top);
3774 			if (row && position.x >=0 ) {
3775 				float x=0;
3776 				for (int32 c=0;c<fMasterView->CountColumns();c++) {
3777 					new_column = fMasterView->ColumnAt(c);
3778 					if (!new_column->IsVisible())
3779 						continue;
3780 					if ((MAX(kLeftMargin,
3781 						fMasterView->LatchWidth()) + x) + new_column->Width()
3782 						> position.x) {
3783 
3784 						if(new_column->WantsEvents()) {
3785 							new_field = row->GetField(c);
3786 							new_row = row;
3787 							FindRect(new_row,&new_rect);
3788 							new_rect.left = MAX(kLeftMargin,
3789 								fMasterView->LatchWidth()) + x;
3790 							new_rect.right = new_rect.left
3791 								+ new_column->Width() - 1;
3792 							handle_field = true;
3793 						}
3794 						break;
3795 					}
3796 					x += new_column->Width();
3797 				}
3798 			}
3799 		}
3800 
3801 		// Handle mouse moved
3802 		if (handle_field) {
3803 			if (new_field != fCurrentField) {
3804 				if (fCurrentField) {
3805 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3806 						fCurrentField, fFieldRect, position, 0,
3807 						fCurrentCode = B_EXITED_VIEW);
3808 				}
3809 				fCurrentColumn = new_column;
3810 				fCurrentRow = new_row;
3811 				fCurrentField = new_field;
3812 				fFieldRect = new_rect;
3813 				if (fCurrentField) {
3814 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3815 						fCurrentField, fFieldRect, position, 0,
3816 						fCurrentCode = B_ENTERED_VIEW);
3817 				}
3818 			} else {
3819 				if (fCurrentField) {
3820 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3821 						fCurrentField, fFieldRect, position, 0,
3822 						fCurrentCode = B_INSIDE_VIEW);
3823 				}
3824 			}
3825 		} else {
3826 			if (fCurrentField) {
3827 				fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3828 					fCurrentField, fFieldRect, position, 0,
3829 					fCurrentCode = B_EXITED_VIEW);
3830 				fCurrentField = 0;
3831 				fCurrentColumn = 0;
3832 				fCurrentRow = 0;
3833 			}
3834 		}
3835 	} else {
3836 		if (fCurrentField) {
3837 			if (fFieldRect.Contains(position)) {
3838 				if (fCurrentCode == B_OUTSIDE_VIEW
3839 					|| fCurrentCode == B_EXITED_VIEW) {
3840 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3841 						fCurrentField, fFieldRect, position, 1,
3842 						fCurrentCode = B_ENTERED_VIEW);
3843 				} else {
3844 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3845 						fCurrentField, fFieldRect, position, 1,
3846 						fCurrentCode = B_INSIDE_VIEW);
3847 				}
3848 			} else {
3849 				if (fCurrentCode == B_INSIDE_VIEW
3850 					|| fCurrentCode == B_ENTERED_VIEW) {
3851 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3852 						fCurrentField, fFieldRect, position, 1,
3853 						fCurrentCode = B_EXITED_VIEW);
3854 				} else {
3855 					fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3856 						fCurrentField, fFieldRect, position, 1,
3857 						fCurrentCode = B_OUTSIDE_VIEW);
3858 				}
3859 			}
3860 		}
3861 	}
3862 
3863 	if (!fEditMode) {
3864 
3865 		switch (fCurrentState) {
3866 			case LATCH_CLICKED:
3867 				if (fTargetRow->fNextSelected != 0)
3868 					SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3869 				else
3870 					SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3871 
3872 				FillRect(fLatchRect);
3873 				if (fLatchRect.Contains(position)) {
3874 					fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3875 						fTargetRow);
3876 				} else {
3877 					fMasterView->DrawLatch(this, fLatchRect,
3878 						fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
3879 						fTargetRow);
3880 				}
3881 				break;
3882 
3883 			case ROW_CLICKED:
3884 				if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
3885 					|| abs((int)(position.y - fClickPoint.y))
3886 						> kRowDragSensitivity) {
3887 					fCurrentState = DRAGGING_ROWS;
3888 					fMasterView->InitiateDrag(fClickPoint,
3889 						fTargetRow->fNextSelected != 0);
3890 				}
3891 				break;
3892 
3893 			case DRAGGING_ROWS:
3894 #if 0
3895 				// falls through...
3896 #else
3897 				if (fTrackMouse /*&& message*/) {
3898 					if (fVisibleRect.Contains(position)) {
3899 						float top;
3900 						int32 indent;
3901 						BRow* target = FindRow(position.y, &indent, &top);
3902 						if (target)
3903 							SetFocusRow(target, true);
3904 					}
3905 				}
3906 				break;
3907 #endif
3908 
3909 			default: {
3910 
3911 				if (fTrackMouse /*&& message*/) {
3912 					// Draw a highlight line...
3913 					if (fVisibleRect.Contains(position)) {
3914 						float top;
3915 						int32 indent;
3916 						BRow* target = FindRow(position.y, &indent, &top);
3917 						if (target == fRollOverRow)
3918 							break;
3919 						if (fRollOverRow) {
3920 							BRect rect;
3921 							FindRect(fRollOverRow, &rect);
3922 							Invalidate(rect);
3923 						}
3924 						fRollOverRow = target;
3925 #if 0
3926 						SetFocusRow(fRollOverRow,false);
3927 #else
3928 						PushState();
3929 						SetDrawingMode(B_OP_BLEND);
3930 						SetHighColor(255, 255, 255, 255);
3931 						BRect rect;
3932 						FindRect(fRollOverRow, &rect);
3933 						rect.bottom -= 1.0;
3934 						FillRect(rect);
3935 						PopState();
3936 #endif
3937 					} else {
3938 						if (fRollOverRow) {
3939 							BRect rect;
3940 							FindRect(fRollOverRow, &rect);
3941 							Invalidate(rect);
3942 							fRollOverRow = NULL;
3943 						}
3944 					}
3945 				}
3946 			}
3947 		}
3948 	}
3949 }
3950 
3951 
3952 void
3953 OutlineView::MouseUp(BPoint position)
3954 {
3955 	if (fCurrentField) {
3956 		fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
3957 		fMouseDown = false;
3958 	}
3959 
3960 	if (fEditMode)
3961 		return;
3962 
3963 	switch (fCurrentState) {
3964 		case LATCH_CLICKED:
3965 			if (fLatchRect.Contains(position)) {
3966 				fMasterView->ExpandOrCollapse(fTargetRow,
3967 					!fTargetRow->fIsExpanded);
3968 			}
3969 
3970 			Invalidate(fLatchRect);
3971 			fCurrentState = INACTIVE;
3972 			break;
3973 
3974 		case ROW_CLICKED:
3975 			if (fClickCount > 1
3976 				&& abs((int)fClickPoint.x - (int)position.x)
3977 					< kDoubleClickMoveSensitivity
3978 				&& abs((int)fClickPoint.y - (int)position.y)
3979 					< kDoubleClickMoveSensitivity) {
3980 				fMasterView->ItemInvoked();
3981 			}
3982 			fCurrentState = INACTIVE;
3983 			break;
3984 
3985 		case DRAGGING_ROWS:
3986 			fCurrentState = INACTIVE;
3987 			// Falls through
3988 
3989 		default:
3990 			if (fDropHighlightY != -1) {
3991 				InvertRect(BRect(0,
3992 					fDropHighlightY - kDropHighlightLineHeight / 2,
3993 					1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3994 					// Erase the old target line
3995 				fDropHighlightY = -1;
3996 			}
3997 	}
3998 }
3999 
4000 
4001 void
4002 OutlineView::MessageReceived(BMessage* message)
4003 {
4004 	if (message->WasDropped()) {
4005 		fMasterView->MessageDropped(message,
4006 			ConvertFromScreen(message->DropPoint()));
4007 	} else {
4008 		BView::MessageReceived(message);
4009 	}
4010 }
4011 
4012 
4013 void
4014 OutlineView::ChangeFocusRow(bool up, bool updateSelection,
4015 	bool addToCurrentSelection)
4016 {
4017 	int32 indent;
4018 	float top;
4019 	float newRowPos = 0;
4020 	float verticalScroll = 0;
4021 
4022 	if (fFocusRow) {
4023 		// A row currently has the focus, get information about it
4024 		newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
4025 		if (newRowPos < fVisibleRect.top + 20)
4026 			verticalScroll = newRowPos - 20;
4027 		else if (newRowPos > fVisibleRect.bottom - 20)
4028 			verticalScroll = newRowPos - fVisibleRect.Height() + 20;
4029 	} else
4030 		newRowPos = fVisibleRect.top + 2;
4031 			// no row is currently focused, set this to the top of the window
4032 			// so we will select the first visible item in the list.
4033 
4034 	BRow* newRow = FindRow(newRowPos, &indent, &top);
4035 	if (newRow) {
4036 		if (fFocusRow) {
4037 			fFocusRowRect.right = 10000;
4038 			Invalidate(fFocusRowRect);
4039 		}
4040 		BRow* oldFocusRow = fFocusRow;
4041 		fFocusRow = newRow;
4042 		fFocusRowRect.top = top;
4043 		fFocusRowRect.left = 0;
4044 		fFocusRowRect.right = 10000;
4045 		fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
4046 		Invalidate(fFocusRowRect);
4047 
4048 		if (updateSelection) {
4049 			if (!addToCurrentSelection
4050 				|| fSelectionMode == B_SINGLE_SELECTION_LIST) {
4051 				DeselectAll();
4052 			}
4053 
4054 			// if the focus row isn't selected, add it to the selection
4055 			if (fFocusRow->fNextSelected == 0) {
4056 				fFocusRow->fNextSelected
4057 					= fSelectionListDummyHead.fNextSelected;
4058 				fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4059 				fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4060 				fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4061 			} else if (oldFocusRow != NULL
4062 				&& fSelectionListDummyHead.fNextSelected == oldFocusRow
4063 				&& (((IndexOf(oldFocusRow->fNextSelected)
4064 						< IndexOf(oldFocusRow)) == up)
4065 					|| fFocusRow == oldFocusRow->fNextSelected)) {
4066 					// if the focus row is selected, if:
4067 					// 1. the previous focus row is last in the selection
4068 					// 2a. the next selected row is now the focus row
4069 					// 2b. or the next selected row is beyond the focus row
4070 					//	   in the move direction
4071 					// then deselect the previous focus row
4072 				fSelectionListDummyHead.fNextSelected
4073 					= oldFocusRow->fNextSelected;
4074 				if (fSelectionListDummyHead.fNextSelected != NULL) {
4075 					fSelectionListDummyHead.fNextSelected->fPrevSelected
4076 						= &fSelectionListDummyHead;
4077 					oldFocusRow->fNextSelected = NULL;
4078 				}
4079 				oldFocusRow->fPrevSelected = NULL;
4080 			}
4081 
4082 			fLastSelectedItem = fFocusRow;
4083 		}
4084 	} else
4085 		Invalidate(fFocusRowRect);
4086 
4087 	if (verticalScroll != 0) {
4088 		BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4089 		float min, max;
4090 		vScrollBar->GetRange(&min, &max);
4091 		if (verticalScroll < min)
4092 			verticalScroll = min;
4093 		else if (verticalScroll > max)
4094 			verticalScroll = max;
4095 
4096 		vScrollBar->SetValue(verticalScroll);
4097 	}
4098 
4099 	if (newRow && updateSelection)
4100 		fMasterView->SelectionChanged();
4101 }
4102 
4103 
4104 void
4105 OutlineView::MoveFocusToVisibleRect()
4106 {
4107 	fFocusRow = 0;
4108 	ChangeFocusRow(true, true, false);
4109 }
4110 
4111 
4112 BRow*
4113 OutlineView::CurrentSelection(BRow* lastSelected) const
4114 {
4115 	BRow* row;
4116 	if (lastSelected == 0)
4117 		row = fSelectionListDummyHead.fNextSelected;
4118 	else
4119 		row = lastSelected->fNextSelected;
4120 
4121 
4122 	if (row == &fSelectionListDummyHead)
4123 		row = 0;
4124 
4125 	return row;
4126 }
4127 
4128 
4129 void
4130 OutlineView::ToggleFocusRowSelection(bool selectRange)
4131 {
4132 	if (fFocusRow == 0)
4133 		return;
4134 
4135 	if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
4136 		SelectRange(fLastSelectedItem, fFocusRow);
4137 	else {
4138 		if (fFocusRow->fNextSelected != 0) {
4139 			// Unselect row
4140 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
4141 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
4142 			fFocusRow->fPrevSelected = 0;
4143 			fFocusRow->fNextSelected = 0;
4144 		} else {
4145 			// Select row
4146 			if (fSelectionMode == B_SINGLE_SELECTION_LIST)
4147 				DeselectAll();
4148 
4149 			fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
4150 			fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4151 			fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4152 			fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4153 		}
4154 	}
4155 
4156 	fLastSelectedItem = fFocusRow;
4157 	fMasterView->SelectionChanged();
4158 	Invalidate(fFocusRowRect);
4159 }
4160 
4161 
4162 void
4163 OutlineView::ToggleFocusRowOpen()
4164 {
4165 	if (fFocusRow)
4166 		fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
4167 }
4168 
4169 
4170 void
4171 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
4172 {
4173 	// TODO: Could use CopyBits here to speed things up.
4174 
4175 	if (parentRow == NULL)
4176 		return;
4177 
4178 	if (parentRow->fIsExpanded == expand)
4179 		return;
4180 
4181 	parentRow->fIsExpanded = expand;
4182 
4183 	BRect parentRect;
4184 	if (FindRect(parentRow, &parentRect)) {
4185 		// Determine my new height
4186 		float subTreeHeight = 0.0;
4187 		if (parentRow->fIsExpanded)
4188 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4189 			     iterator.CurrentRow();
4190 			     iterator.GoToNext()
4191 			    )
4192 			{
4193 				subTreeHeight += iterator.CurrentRow()->Height()+1;
4194 			}
4195 		else
4196 			for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4197 			     iterator.CurrentRow();
4198 			     iterator.GoToNext()
4199 			    )
4200 			{
4201 				subTreeHeight -= iterator.CurrentRow()->Height()+1;
4202 			}
4203 		fItemsHeight += subTreeHeight;
4204 
4205 		// Adjust focus row if necessary.
4206 		if (FindRect(fFocusRow, &fFocusRowRect) == false) {
4207 			// focus row is in a subtree that has collapsed,
4208 			// move it up to the parent.
4209 			fFocusRow = parentRow;
4210 			FindRect(fFocusRow, &fFocusRowRect);
4211 		}
4212 
4213 		Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
4214 			fVisibleRect.bottom));
4215 		FixScrollBar(false);
4216 	}
4217 }
4218 
4219 void
4220 OutlineView::RemoveRow(BRow* row)
4221 {
4222 	if (row == NULL)
4223 		return;
4224 
4225 	BRow* parentRow = NULL;
4226 	bool parentIsVisible = false;
4227 	FindParent(row, &parentRow, &parentIsVisible);
4228 		// NOTE: This could be a root row without a parent, in which case
4229 		// it is always visible, though.
4230 
4231 	// Adjust height for the visible sub-tree that is going to be removed.
4232 	float subTreeHeight = 0.0f;
4233 	if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4234 		// The row itself is visible at least.
4235 		subTreeHeight = row->Height() + 1;
4236 		if (row->fIsExpanded) {
4237 			// Adjust for the height of visible sub-items as well.
4238 			// (By default, the iterator follows open branches only.)
4239 			for (RecursiveOutlineIterator iterator(row->fChildList);
4240 				iterator.CurrentRow(); iterator.GoToNext())
4241 				subTreeHeight += iterator.CurrentRow()->Height() + 1;
4242 		}
4243 		BRect invalid;
4244 		if (FindRect(row, &invalid)) {
4245 			invalid.bottom = Bounds().bottom;
4246 			if (invalid.IsValid())
4247 				Invalidate(invalid);
4248 		}
4249 	}
4250 
4251 	fItemsHeight -= subTreeHeight;
4252 
4253 	FixScrollBar(false);
4254 	int32 indent = 0;
4255 	float top = 0.0;
4256 	if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
4257 		// after removing this row, no rows are actually visible any more,
4258 		// force a scroll to make them visible again
4259 		if (fItemsHeight > fVisibleRect.Height())
4260 			ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
4261 		else
4262 			ScrollBy(0.0, -Bounds().top);
4263 	}
4264 	if (parentRow != NULL) {
4265 		parentRow->fChildList->RemoveItem(row);
4266 		if (parentRow->fChildList->CountItems() == 0) {
4267 			delete parentRow->fChildList;
4268 			parentRow->fChildList = 0;
4269 			// It was the last child row of the parent, which also means the
4270 			// latch disappears.
4271 			BRect parentRowRect;
4272 			if (parentIsVisible && FindRect(parentRow, &parentRowRect))
4273 				Invalidate(parentRowRect);
4274 		}
4275 	} else
4276 		fRows.RemoveItem(row);
4277 
4278 	// Adjust focus row if necessary.
4279 	if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
4280 		// focus row is in a subtree that is gone, move it up to the parent.
4281 		fFocusRow = parentRow;
4282 		if (fFocusRow)
4283 			FindRect(fFocusRow, &fFocusRowRect);
4284 	}
4285 
4286 	// Remove this from the selection if necessary
4287 	if (row->fNextSelected != 0) {
4288 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4289 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4290 		row->fPrevSelected = 0;
4291 		row->fNextSelected = 0;
4292 		fMasterView->SelectionChanged();
4293 	}
4294 
4295 	fCurrentColumn = 0;
4296 	fCurrentRow = 0;
4297 	fCurrentField = 0;
4298 }
4299 
4300 
4301 BRowContainer*
4302 OutlineView::RowList()
4303 {
4304 	return &fRows;
4305 }
4306 
4307 
4308 void
4309 OutlineView::UpdateRow(BRow* row)
4310 {
4311 	if (row) {
4312 		// Determine if this row has changed its sort order
4313 		BRow* parentRow = NULL;
4314 		bool parentIsVisible = false;
4315 		FindParent(row, &parentRow, &parentIsVisible);
4316 
4317 		BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
4318 
4319 		if(list) {
4320 			int32 rowIndex = list->IndexOf(row);
4321 			ASSERT(rowIndex >= 0);
4322 			ASSERT(list->ItemAt(rowIndex) == row);
4323 
4324 			bool rowMoved = false;
4325 			if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
4326 				rowMoved = true;
4327 
4328 			if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
4329 				row) < 0)
4330 				rowMoved = true;
4331 
4332 			if (rowMoved) {
4333 				// Sort location of this row has changed.
4334 				// Remove and re-add in the right spot
4335 				SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
4336 			} else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4337 				BRect invalidRect;
4338 				if (FindVisibleRect(row, &invalidRect))
4339 					Invalidate(invalidRect);
4340 			}
4341 		}
4342 	}
4343 }
4344 
4345 
4346 void
4347 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
4348 {
4349 	if (!row)
4350 		return;
4351 
4352 	row->fParent = parentRow;
4353 
4354 	if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) {
4355 		// Ignore index here.
4356 		if (parentRow) {
4357 			if (parentRow->fChildList == NULL)
4358 				parentRow->fChildList = new BRowContainer;
4359 
4360 			AddSorted(parentRow->fChildList, row);
4361 		} else
4362 			AddSorted(&fRows, row);
4363 	} else {
4364 		// Note, a -1 index implies add to end if sorting is not enabled
4365 		if (parentRow) {
4366 			if (parentRow->fChildList == 0)
4367 				parentRow->fChildList = new BRowContainer;
4368 
4369 			if (Index < 0 || Index > parentRow->fChildList->CountItems())
4370 				parentRow->fChildList->AddItem(row);
4371 			else
4372 				parentRow->fChildList->AddItem(row, Index);
4373 		} else {
4374 			if (Index < 0 || Index >= fRows.CountItems())
4375 				fRows.AddItem(row);
4376 			else
4377 				fRows.AddItem(row, Index);
4378 		}
4379 	}
4380 
4381 	if (parentRow == 0 || parentRow->fIsExpanded)
4382 		fItemsHeight += row->Height() + 1;
4383 
4384 	FixScrollBar(false);
4385 
4386 	BRect newRowRect;
4387 	bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
4388 
4389 	if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
4390 		// The focus row has moved.
4391 		Invalidate(fFocusRowRect);
4392 		FindRect(fFocusRow, &fFocusRowRect);
4393 		Invalidate(fFocusRowRect);
4394 	}
4395 
4396 	if (newRowIsInOpenBranch) {
4397 		if (fCurrentState == INACTIVE) {
4398 			if (newRowRect.bottom < fVisibleRect.top) {
4399 				// The new row is totally above the current viewport, move
4400 				// everything down and redraw the first line.
4401 				BRect source(fVisibleRect);
4402 				BRect dest(fVisibleRect);
4403 				source.bottom -= row->Height() + 1;
4404 				dest.top += row->Height() + 1;
4405 				CopyBits(source, dest);
4406 				Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
4407 					fVisibleRect.top + newRowRect.Height()));
4408 			} else if (newRowRect.top < fVisibleRect.bottom) {
4409 				// New item is somewhere in the current region.  Scroll everything
4410 				// beneath it down and invalidate just the new row rect.
4411 				BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
4412 					fVisibleRect.bottom - newRowRect.Height());
4413 				BRect dest(source);
4414 				dest.OffsetBy(0, newRowRect.Height() + 1);
4415 				CopyBits(source, dest);
4416 				Invalidate(newRowRect);
4417 			} // otherwise, this is below the currently visible region
4418 		} else {
4419 			// Adding the item may have caused the item that the user is currently
4420 			// selected to move.  This would cause annoying drawing and interaction
4421 			// bugs, as the position of that item is cached.  If this happens, resize
4422 			// the scroll bar, then scroll back so the selected item is in view.
4423 			BRect targetRect;
4424 			if (FindRect(fTargetRow, &targetRect)) {
4425 				float delta = targetRect.top - fTargetRowTop;
4426 				if (delta != 0) {
4427 					// This causes a jump because ScrollBy will copy a chunk of the view.
4428 					// Since the actual contents of the view have been offset, we don't
4429 					// want this, we just want to change the virtual origin of the window.
4430 					// Constrain the clipping region so everything is clipped out so no
4431 					// copy occurs.
4432 					//
4433 					//	xxx this currently doesn't work if the scroll bars aren't enabled.
4434 					//  everything will still move anyway.  A minor annoyance.
4435 					BRegion emptyRegion;
4436 					ConstrainClippingRegion(&emptyRegion);
4437 					PushState();
4438 					ScrollBy(0, delta);
4439 					PopState();
4440 					ConstrainClippingRegion(NULL);
4441 
4442 					fTargetRowTop += delta;
4443 					fClickPoint.y += delta;
4444 					fLatchRect.OffsetBy(0, delta);
4445 				}
4446 			}
4447 		}
4448 	}
4449 
4450 	// If the parent was previously childless, it will need to have a latch
4451 	// drawn.
4452 	BRect parentRect;
4453 	if (parentRow && parentRow->fChildList->CountItems() == 1
4454 		&& FindVisibleRect(parentRow, &parentRect))
4455 		Invalidate(parentRect);
4456 }
4457 
4458 
4459 void
4460 OutlineView::FixScrollBar(bool scrollToFit)
4461 {
4462 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4463 	if (vScrollBar) {
4464 		if (fItemsHeight > fVisibleRect.Height()) {
4465 			float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4466 			vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
4467 
4468 			// If the user is scrolled down too far when making the range smaller, the list
4469 			// will jump suddenly, which is undesirable.  In this case, don't fix the scroll
4470 			// bar here. In ScrollTo, it checks to see if this has occured, and will
4471 			// fix the scroll bars sneakily if the user has scrolled up far enough.
4472 			if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
4473 				vScrollBar->SetRange(0.0, maxScrollBarValue);
4474 				vScrollBar->SetSteps(20.0, fVisibleRect.Height());
4475 			}
4476 		} else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0)
4477 			vScrollBar->SetRange(0.0, 0.0);		// disable scroll bar.
4478 	}
4479 }
4480 
4481 
4482 void
4483 OutlineView::AddSorted(BRowContainer* list, BRow* row)
4484 {
4485 	if (list && row) {
4486 		// Find general vicinity with binary search.
4487 		int32 lower = 0;
4488 		int32 upper = list->CountItems()-1;
4489 		while( lower < upper ) {
4490 			int32 middle = lower + (upper-lower+1)/2;
4491 			int32 cmp = CompareRows(row, list->ItemAt(middle));
4492 			if( cmp < 0 ) upper = middle-1;
4493 			else if( cmp > 0 ) lower = middle+1;
4494 			else lower = upper = middle;
4495 		}
4496 
4497 		// At this point, 'upper' and 'lower' at the last found item.
4498 		// Arbitrarily use 'upper' and determine the final insertion
4499 		// point -- either before or after this item.
4500 		if( upper < 0 ) upper = 0;
4501 		else if( upper < list->CountItems() ) {
4502 			if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
4503 		}
4504 
4505 		if (upper >= list->CountItems())
4506 			list->AddItem(row);				// Adding to end.
4507 		else
4508 			list->AddItem(row, upper);		// Insert
4509 	}
4510 }
4511 
4512 
4513 int32
4514 OutlineView::CompareRows(BRow* row1, BRow* row2)
4515 {
4516 	int32 itemCount (fSortColumns->CountItems());
4517 	if (row1 && row2) {
4518 		for (int32 index = 0; index < itemCount; index++) {
4519 			BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
4520 			int comp = 0;
4521 			BField* field1 = (BField*) row1->GetField(column->fFieldID);
4522 			BField* field2 = (BField*) row2->GetField(column->fFieldID);
4523 			if (field1 && field2)
4524 				comp = column->CompareFields(field1, field2);
4525 
4526 			if (!column->fSortAscending)
4527 				comp = -comp;
4528 
4529 			if (comp != 0)
4530 				return comp;
4531 		}
4532 	}
4533 	return 0;
4534 }
4535 
4536 
4537 void
4538 OutlineView::FrameResized(float width, float height)
4539 {
4540 	fVisibleRect.right = fVisibleRect.left + width;
4541 	fVisibleRect.bottom = fVisibleRect.top + height;
4542 	FixScrollBar(true);
4543 	_inherited::FrameResized(width, height);
4544 }
4545 
4546 
4547 void
4548 OutlineView::ScrollTo(BPoint position)
4549 {
4550 	fVisibleRect.OffsetTo(position.x, position.y);
4551 
4552 	// In FixScrollBar, we might not have been able to change the size of
4553 	// the scroll bar because the user was scrolled down too far.  Take
4554 	// this opportunity to sneak it in if we can.
4555 	BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4556 	float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4557 	float min, max;
4558 	vScrollBar->GetRange(&min, &max);
4559 	if (max != maxScrollBarValue && position.y > maxScrollBarValue)
4560 		FixScrollBar(true);
4561 
4562 	_inherited::ScrollTo(position);
4563 }
4564 
4565 
4566 const BRect&
4567 OutlineView::VisibleRect() const
4568 {
4569 	return fVisibleRect;
4570 }
4571 
4572 
4573 bool
4574 OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
4575 {
4576 	if (row && _rect) {
4577 		float line = 0.0;
4578 		for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4579 			iterator.GoToNext()) {
4580 
4581 			if (iterator.CurrentRow() == row) {
4582 				_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4583 					line + row->Height());
4584 				return line <= fVisibleRect.bottom;
4585 			}
4586 
4587 			line += iterator.CurrentRow()->Height() + 1;
4588 		}
4589 	}
4590 	return false;
4591 }
4592 
4593 
4594 bool
4595 OutlineView::FindRect(const BRow* row, BRect* _rect)
4596 {
4597 	float line = 0.0;
4598 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4599 		iterator.GoToNext()) {
4600 		if (iterator.CurrentRow() == row) {
4601 			_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4602 				line + row->Height());
4603 			return true;
4604 		}
4605 
4606 		line += iterator.CurrentRow()->Height() + 1;
4607 	}
4608 
4609 	return false;
4610 }
4611 
4612 
4613 void
4614 OutlineView::ScrollTo(const BRow* row)
4615 {
4616 	BRect rect;
4617 	if (FindRect(row, &rect)) {
4618 		BRect bounds = Bounds();
4619 		if (rect.top < bounds.top)
4620 			ScrollTo(BPoint(bounds.left, rect.top));
4621 		else if (rect.bottom > bounds.bottom)
4622 			ScrollBy(0, rect.bottom - bounds.bottom);
4623 	}
4624 }
4625 
4626 
4627 void
4628 OutlineView::DeselectAll()
4629 {
4630 	// Invalidate all selected rows
4631 	float line = 0.0;
4632 	for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4633 		iterator.GoToNext()) {
4634 		if (line > fVisibleRect.bottom)
4635 			break;
4636 
4637 		BRow* row = iterator.CurrentRow();
4638 		if (line + row->Height() > fVisibleRect.top) {
4639 			if (row->fNextSelected != 0)
4640 				Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
4641 					line + row->Height()));
4642 		}
4643 
4644 		line += row->Height() + 1;
4645 	}
4646 
4647 	// Set items not selected
4648 	while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
4649 		BRow* row = fSelectionListDummyHead.fNextSelected;
4650 		row->fNextSelected->fPrevSelected = row->fPrevSelected;
4651 		row->fPrevSelected->fNextSelected = row->fNextSelected;
4652 		row->fNextSelected = 0;
4653 		row->fPrevSelected = 0;
4654 	}
4655 }
4656 
4657 
4658 BRow*
4659 OutlineView::FocusRow() const
4660 {
4661 	return fFocusRow;
4662 }
4663 
4664 
4665 void
4666 OutlineView::SetFocusRow(BRow* row, bool Select)
4667 {
4668 	if (row) {
4669 		if (Select)
4670 			AddToSelection(row);
4671 
4672 		if (fFocusRow == row)
4673 			return;
4674 
4675 		Invalidate(fFocusRowRect); // invalidate previous
4676 
4677 		fTargetRow = fFocusRow = row;
4678 
4679 		FindVisibleRect(fFocusRow, &fFocusRowRect);
4680 		Invalidate(fFocusRowRect); // invalidate current
4681 
4682 		fFocusRowRect.right = 10000;
4683 		fMasterView->SelectionChanged();
4684 	}
4685 }
4686 
4687 
4688 bool
4689 OutlineView::SortList(BRowContainer* list, bool isVisible)
4690 {
4691 	if (list) {
4692 		// Shellsort
4693 		BRow** items
4694 			= (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
4695 		int32 numItems = list->CountItems();
4696 		int h;
4697 		for (h = 1; h < numItems / 9; h = 3 * h + 1)
4698 			;
4699 
4700 		for (;h > 0; h /= 3) {
4701 			for (int step = h; step < numItems; step++) {
4702 				BRow* temp = items[step];
4703 				int i;
4704 				for (i = step - h; i >= 0; i -= h) {
4705 					if (CompareRows(temp, items[i]) < 0)
4706 						items[i + h] = items[i];
4707 					else
4708 						break;
4709 				}
4710 
4711 				items[i + h] = temp;
4712 			}
4713 		}
4714 
4715 		if (isVisible) {
4716 			Invalidate();
4717 
4718 			InvalidateCachedPositions();
4719 			int lockCount = Window()->CountLocks();
4720 			for (int i = 0; i < lockCount; i++)
4721 				Window()->Unlock();
4722 
4723 			while (lockCount--)
4724 				if (!Window()->Lock())
4725 					return false;	// Window is gone...
4726 		}
4727 	}
4728 	return true;
4729 }
4730 
4731 
4732 int32
4733 OutlineView::DeepSortThreadEntry(void* _outlineView)
4734 {
4735 	((OutlineView*) _outlineView)->DeepSort();
4736 	return 0;
4737 }
4738 
4739 
4740 void
4741 OutlineView::DeepSort()
4742 {
4743 	struct stack_entry {
4744 		bool isVisible;
4745 		BRowContainer* list;
4746 		int32 listIndex;
4747 	} stack[kMaxDepth];
4748 	int32 stackTop = 0;
4749 
4750 	stack[stackTop].list = &fRows;
4751 	stack[stackTop].isVisible = true;
4752 	stack[stackTop].listIndex = 0;
4753 	fNumSorted = 0;
4754 
4755 	if (Window()->Lock() == false)
4756 		return;
4757 
4758 	bool doneSorting = false;
4759 	while (!doneSorting && !fSortCancelled) {
4760 
4761 		stack_entry* currentEntry = &stack[stackTop];
4762 
4763 		// xxx Can make the invalidate area smaller by finding the rect for the
4764 		// parent item and using that as the top of the invalid rect.
4765 
4766 		bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
4767 		if (!haveLock)
4768 			return ;	// window is gone.
4769 
4770 		// Fix focus rect.
4771 		InvalidateCachedPositions();
4772 		if (fCurrentState != INACTIVE)
4773 			fCurrentState = INACTIVE;	// sorry...
4774 
4775 		// next list.
4776 		bool foundNextList = false;
4777 		while (!foundNextList && !fSortCancelled) {
4778 			for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
4779 				index++) {
4780 				BRow* parentRow = currentEntry->list->ItemAt(index);
4781 				BRowContainer* childList = parentRow->fChildList;
4782 				if (childList != 0) {
4783 					currentEntry->listIndex = index + 1;
4784 					stackTop++;
4785 					ASSERT(stackTop < kMaxDepth);
4786 					stack[stackTop].listIndex = 0;
4787 					stack[stackTop].list = childList;
4788 					stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
4789 					foundNextList = true;
4790 					break;
4791 				}
4792 			}
4793 
4794 			if (!foundNextList) {
4795 				// back up
4796 				if (--stackTop < 0) {
4797 					doneSorting = true;
4798 					break;
4799 				}
4800 
4801 				currentEntry = &stack[stackTop];
4802 			}
4803 		}
4804 	}
4805 
4806 	Window()->Unlock();
4807 }
4808 
4809 
4810 void
4811 OutlineView::StartSorting()
4812 {
4813 	// If this view is not yet attached to a window, don't start a sort thread!
4814 	if (Window() == NULL)
4815 		return;
4816 
4817 	if (fSortThread != B_BAD_THREAD_ID) {
4818 		thread_info tinfo;
4819 		if (get_thread_info(fSortThread, &tinfo) == B_OK) {
4820 			// Unlock window so this won't deadlock (sort thread is probably
4821 			// waiting to lock window).
4822 
4823 			int lockCount = Window()->CountLocks();
4824 			for (int i = 0; i < lockCount; i++)
4825 				Window()->Unlock();
4826 
4827 			fSortCancelled = true;
4828 			int32 status;
4829 			wait_for_thread(fSortThread, &status);
4830 
4831 			while (lockCount--)
4832 				if (!Window()->Lock())
4833 					return ;	// Window is gone...
4834 		}
4835 	}
4836 
4837 	fSortCancelled = false;
4838 	fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
4839 	resume_thread(fSortThread);
4840 }
4841 
4842 
4843 void
4844 OutlineView::SelectRange(BRow* start, BRow* end)
4845 {
4846 	if (!start || !end)
4847 		return;
4848 
4849 	if (start == end)	// start is always selected when this is called
4850 		return;
4851 
4852 	RecursiveOutlineIterator iterator(&fRows, false);
4853 	while (iterator.CurrentRow() != 0) {
4854 		if (iterator.CurrentRow() == end) {
4855 			// reverse selection, swap to fix special case
4856 			BRow* temp = start;
4857 			start = end;
4858 			end = temp;
4859 			break;
4860 		} else if (iterator.CurrentRow() == start)
4861 			break;
4862 
4863 		iterator.GoToNext();
4864 	}
4865 
4866 	while (true) {
4867 		BRow* row = iterator.CurrentRow();
4868 		if (row) {
4869 			if (row->fNextSelected == 0) {
4870 				row->fNextSelected = fSelectionListDummyHead.fNextSelected;
4871 				row->fPrevSelected = &fSelectionListDummyHead;
4872 				row->fNextSelected->fPrevSelected = row;
4873 				row->fPrevSelected->fNextSelected = row;
4874 			}
4875 		} else
4876 			break;
4877 
4878 		if (row == end)
4879 			break;
4880 
4881 		iterator.GoToNext();
4882 	}
4883 
4884 	Invalidate();  // xxx make invalidation smaller
4885 }
4886 
4887 
4888 bool
4889 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
4890 {
4891 	bool result = false;
4892 	if (row != NULL && outParent != NULL) {
4893 		*outParent = row->fParent;
4894 
4895 		if (outParentIsVisible != NULL) {
4896 			// Walk up the parent chain to determine if this row is visible
4897 			*outParentIsVisible = true;
4898 			for (BRow* currentRow = row->fParent; currentRow != NULL;
4899 				currentRow = currentRow->fParent) {
4900 				if (!currentRow->fIsExpanded) {
4901 					*outParentIsVisible = false;
4902 					break;
4903 				}
4904 			}
4905 		}
4906 
4907 		result = *outParent != NULL;
4908 	}
4909 
4910 	return result;
4911 }
4912 
4913 
4914 int32
4915 OutlineView::IndexOf(BRow* row)
4916 {
4917 	if (row) {
4918 		if (row->fParent == 0)
4919 			return fRows.IndexOf(row);
4920 
4921 		ASSERT(row->fParent->fChildList);
4922 		return row->fParent->fChildList->IndexOf(row);
4923 	}
4924 
4925 	return B_ERROR;
4926 }
4927 
4928 
4929 void
4930 OutlineView::InvalidateCachedPositions()
4931 {
4932 	if (fFocusRow)
4933 		FindRect(fFocusRow, &fFocusRowRect);
4934 }
4935 
4936 
4937 float
4938 OutlineView::GetColumnPreferredWidth(BColumn* column)
4939 {
4940 	float preferred = 0.0;
4941 	for (RecursiveOutlineIterator iterator(&fRows); BRow* row =
4942 		iterator.CurrentRow(); iterator.GoToNext()) {
4943 		BField* field = row->GetField(column->fFieldID);
4944 		if (field) {
4945 			float width = column->GetPreferredWidth(field, this)
4946 				+ iterator.CurrentLevel() * kOutlineLevelIndent;
4947 			preferred = max_c(preferred, width);
4948 		}
4949 	}
4950 
4951 	BString name;
4952 	column->GetColumnName(&name);
4953 	preferred = max_c(preferred, StringWidth(name));
4954 
4955 	// Constrain to preferred width. This makes the method do a little
4956 	// more than asked, but it's for convenience.
4957 	if (preferred < column->MinWidth())
4958 		preferred = column->MinWidth();
4959 	else if (preferred > column->MaxWidth())
4960 		preferred = column->MaxWidth();
4961 
4962 	return preferred;
4963 }
4964 
4965 
4966 // #pragma mark -
4967 
4968 
4969 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
4970 	bool openBranchesOnly)
4971 	:
4972 	fStackIndex(0),
4973 	fCurrentListIndex(0),
4974 	fCurrentListDepth(0),
4975 	fOpenBranchesOnly(openBranchesOnly)
4976 {
4977 	if (list == 0 || list->CountItems() == 0)
4978 		fCurrentList = 0;
4979 	else
4980 		fCurrentList = list;
4981 }
4982 
4983 
4984 BRow*
4985 RecursiveOutlineIterator::CurrentRow() const
4986 {
4987 	if (fCurrentList == 0)
4988 		return 0;
4989 
4990 	return fCurrentList->ItemAt(fCurrentListIndex);
4991 }
4992 
4993 
4994 void
4995 RecursiveOutlineIterator::GoToNext()
4996 {
4997 	if (fCurrentList == 0)
4998 		return;
4999 	if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
5000 		fCurrentList = 0;
5001 		return;
5002 	}
5003 
5004 	BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
5005 	if(currentRow) {
5006 		if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
5007 			&& currentRow->fChildList->CountItems() > 0) {
5008 			// Visit child.
5009 			// Put current list on the stack if it needs to be revisited.
5010 			if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
5011 				fStack[fStackIndex].fRowSet = fCurrentList;
5012 				fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
5013 				fStack[fStackIndex].fDepth = fCurrentListDepth;
5014 				fStackIndex++;
5015 			}
5016 
5017 			fCurrentList = currentRow->fChildList;
5018 			fCurrentListIndex = 0;
5019 			fCurrentListDepth++;
5020 		} else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
5021 			fCurrentListIndex++; // next item in current list
5022 		else if (--fStackIndex >= 0) {
5023 			fCurrentList = fStack[fStackIndex].fRowSet;
5024 			fCurrentListIndex = fStack[fStackIndex].fIndex;
5025 			fCurrentListDepth = fStack[fStackIndex].fDepth;
5026 		} else
5027 			fCurrentList = 0;
5028 	}
5029 }
5030 
5031 
5032 int32
5033 RecursiveOutlineIterator::CurrentLevel() const
5034 {
5035 	return fCurrentListDepth;
5036 }
5037 
5038 
5039