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