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