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