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