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