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