HaikuDepot: notify work status from main window
[haiku.git] / src / kits / interface / ColumnListView.cpp
blob2c0ed08e2a0e9fb0b6ba26e1f26ad99da996db5a
1 /*
2 Open Tracker License
4 Terms and Conditions
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
35 /*******************************************************************************
37 / File: ColumnListView.cpp
39 / Description: Experimental multi-column list view.
41 / Copyright 2000+, Be Incorporated, All Rights Reserved
42 / By Jeff Bush
44 *******************************************************************************/
46 #include "ColumnListView.h"
48 #include <typeinfo>
50 #include <algorithm>
51 #include <stdio.h>
52 #include <stdlib.h>
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>
69 #include <ObjectListPrivate.h>
71 #include "ObjectList.h"
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
81 namespace BPrivate {
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
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
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
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
127 static const float kTintedLineTint = 1.04;
129 static const float kMinTitleHeight = 16.0;
130 static const float kMinRowHeight = 16.0;
131 static const float kTitleSpacing = 1.4;
132 static const float kRowSpacing = 1.4;
133 static const float kLatchWidth = 15.0;
135 static const int32 kMaxDepth = 1024;
136 static const float kLeftMargin = kLatchWidth;
137 static const float kRightMargin = 8;
138 static const float kOutlineLevelIndent = kLatchWidth;
139 static const float kColumnResizeAreaWidth = 10.0;
140 static const float kRowDragSensitivity = 5.0;
141 static const float kDoubleClickMoveSensitivity = 4.0;
142 static const float kSortIndicatorWidth = 9.0;
143 static const float kDropHighlightLineHeight = 2.0;
145 static const uint32 kToggleColumn = 'BTCL';
147 class BRowContainer : public BObjectList<BRow>
151 class TitleView : public BView {
152 typedef BView _inherited;
153 public:
154 TitleView(BRect frame, OutlineView* outlineView,
155 BList* visibleColumns, BList* sortColumns,
156 BColumnListView* masterView,
157 uint32 resizingMode);
158 virtual ~TitleView();
160 void ColumnAdded(BColumn* column);
161 void ColumnResized(BColumn* column, float oldWidth);
162 void SetColumnVisible(BColumn* column, bool visible);
164 virtual void Draw(BRect updateRect);
165 virtual void ScrollTo(BPoint where);
166 virtual void MessageReceived(BMessage* message);
167 virtual void MouseDown(BPoint where);
168 virtual void MouseMoved(BPoint where, uint32 transit,
169 const BMessage* dragMessage);
170 virtual void MouseUp(BPoint where);
171 virtual void FrameResized(float width, float height);
173 void MoveColumn(BColumn* column, int32 index);
174 void SetColumnFlags(column_flags flags);
176 void SetEditMode(bool state)
177 { fEditMode = state; }
179 float MarginWidth() const;
181 private:
182 void GetTitleRect(BColumn* column, BRect* _rect);
183 int32 FindColumn(BPoint where, float* _leftEdge);
184 void FixScrollBar(bool scrollToFit);
185 void DragSelectedColumn(BPoint where);
186 void ResizeSelectedColumn(BPoint where,
187 bool preferred = false);
188 void ComputeDragBoundries(BColumn* column,
189 BPoint where);
190 void DrawTitle(BView* view, BRect frame,
191 BColumn* column, bool depressed);
193 float _VirtualWidth() const;
195 OutlineView* fOutlineView;
196 BList* fColumns;
197 BList* fSortColumns;
198 // float fColumnsWidth;
199 BRect fVisibleRect;
201 #if DOUBLE_BUFFERED_COLUMN_RESIZE
202 BBitmap* fDrawBuffer;
203 BView* fDrawBufferView;
204 #endif
206 enum {
207 INACTIVE,
208 RESIZING_COLUMN,
209 PRESSING_COLUMN,
210 DRAG_COLUMN_INSIDE_TITLE,
211 DRAG_COLUMN_OUTSIDE_TITLE
212 } fCurrentState;
214 BPopUpMenu* fColumnPop;
215 BColumnListView* fMasterView;
216 bool fEditMode;
217 int32 fColumnFlags;
219 // State information for resizing/dragging
220 BColumn* fSelectedColumn;
221 BRect fSelectedColumnRect;
222 bool fResizingFirstColumn;
223 BPoint fClickPoint; // offset within cell
224 float fLeftDragBoundry;
225 float fRightDragBoundry;
226 BPoint fCurrentDragPosition;
229 BBitmap* fUpSortArrow;
230 BBitmap* fDownSortArrow;
232 BCursor* fResizeCursor;
233 BCursor* fMinResizeCursor;
234 BCursor* fMaxResizeCursor;
235 BCursor* fColumnMoveCursor;
239 class OutlineView : public BView {
240 typedef BView _inherited;
241 public:
242 OutlineView(BRect, BList* visibleColumns,
243 BList* sortColumns,
244 BColumnListView* listView);
245 virtual ~OutlineView();
247 virtual void Draw(BRect);
248 const BRect& VisibleRect() const;
250 void RedrawColumn(BColumn* column, float leftEdge,
251 bool isFirstColumn);
252 void StartSorting();
253 float GetColumnPreferredWidth(BColumn* column);
255 void AddRow(BRow*, int32 index, BRow* TheRow);
256 BRow* CurrentSelection(BRow* lastSelected) const;
257 void ToggleFocusRowSelection(bool selectRange);
258 void ToggleFocusRowOpen();
259 void ChangeFocusRow(bool up, bool updateSelection,
260 bool addToCurrentSelection);
261 void MoveFocusToVisibleRect();
262 void ExpandOrCollapse(BRow* parent, bool expand);
263 void RemoveRow(BRow*);
264 BRowContainer* RowList();
265 void UpdateRow(BRow*);
266 bool FindParent(BRow* row, BRow** _parent,
267 bool* _isVisible);
268 int32 IndexOf(BRow* row);
269 void Deselect(BRow*);
270 void AddToSelection(BRow*);
271 void DeselectAll();
272 BRow* FocusRow() const;
273 void SetFocusRow(BRow* row, bool select);
274 BRow* FindRow(float ypos, int32* _indent,
275 float* _top);
276 bool FindRect(const BRow* row, BRect* _rect);
277 void ScrollTo(const BRow* row);
279 void Clear();
280 void SetSelectionMode(list_view_type type);
281 list_view_type SelectionMode() const;
282 void SetMouseTrackingEnabled(bool);
283 void FixScrollBar(bool scrollToFit);
284 void SetEditMode(bool state)
285 { fEditMode = state; }
287 virtual void FrameResized(float width, float height);
288 virtual void ScrollTo(BPoint where);
289 virtual void MouseDown(BPoint where);
290 virtual void MouseMoved(BPoint where, uint32 transit,
291 const BMessage* dragMessage);
292 virtual void MouseUp(BPoint where);
293 virtual void MessageReceived(BMessage* message);
295 private:
296 bool SortList(BRowContainer* list, bool isVisible);
297 static int32 DeepSortThreadEntry(void* outlineView);
298 void DeepSort();
299 void SelectRange(BRow* start, BRow* end);
300 int32 CompareRows(BRow* row1, BRow* row2);
301 void AddSorted(BRowContainer* list, BRow* row);
302 void RecursiveDeleteRows(BRowContainer* list,
303 bool owner);
304 void InvalidateCachedPositions();
305 bool FindVisibleRect(BRow* row, BRect* _rect);
307 BList* fColumns;
308 BList* fSortColumns;
309 float fItemsHeight;
310 BRowContainer fRows;
311 BRect fVisibleRect;
313 #if DOUBLE_BUFFERED_COLUMN_RESIZE
314 BBitmap* fDrawBuffer;
315 BView* fDrawBufferView;
316 #endif
318 BRow* fFocusRow;
319 BRect fFocusRowRect;
320 BRow* fRollOverRow;
322 BRow fSelectionListDummyHead;
323 BRow* fLastSelectedItem;
324 BRow* fFirstSelectedItem;
326 thread_id fSortThread;
327 int32 fNumSorted;
328 bool fSortCancelled;
330 enum CurrentState {
331 INACTIVE,
332 LATCH_CLICKED,
333 ROW_CLICKED,
334 DRAGGING_ROWS
337 CurrentState fCurrentState;
340 BColumnListView* fMasterView;
341 list_view_type fSelectionMode;
342 bool fTrackMouse;
343 BField* fCurrentField;
344 BRow* fCurrentRow;
345 BColumn* fCurrentColumn;
346 bool fMouseDown;
347 BRect fFieldRect;
348 int32 fCurrentCode;
349 bool fEditMode;
351 // State information for mouse/keyboard interaction
352 BPoint fClickPoint;
353 bool fDragging;
354 int32 fClickCount;
355 BRow* fTargetRow;
356 float fTargetRowTop;
357 BRect fLatchRect;
358 float fDropHighlightY;
360 friend class RecursiveOutlineIterator;
364 class RecursiveOutlineIterator {
365 public:
366 RecursiveOutlineIterator(
367 BRowContainer* container,
368 bool openBranchesOnly = true);
370 BRow* CurrentRow() const;
371 int32 CurrentLevel() const;
372 void GoToNext();
374 private:
375 struct {
376 BRowContainer* fRowSet;
377 int32 fIndex;
378 int32 fDepth;
379 } fStack[kMaxDepth];
381 int32 fStackIndex;
382 BRowContainer* fCurrentList;
383 int32 fCurrentListIndex;
384 int32 fCurrentListDepth;
385 bool fOpenBranchesOnly;
388 } // namespace BPrivate
391 using namespace BPrivate;
394 BField::BField()
399 BField::~BField()
404 // #pragma mark -
407 void
408 BColumn::MouseMoved(BColumnListView* /*parent*/, BRow* /*row*/,
409 BField* /*field*/, BRect /*field_rect*/, BPoint/*point*/,
410 uint32 /*buttons*/, int32 /*code*/)
415 void
416 BColumn::MouseDown(BColumnListView* /*parent*/, BRow* /*row*/,
417 BField* /*field*/, BRect /*field_rect*/, BPoint /*point*/,
418 uint32 /*buttons*/)
423 void
424 BColumn::MouseUp(BColumnListView* /*parent*/, BRow* /*row*/, BField* /*field*/)
429 // #pragma mark -
432 BRow::BRow()
434 fChildList(NULL),
435 fIsExpanded(false),
436 fHeight(std::max(kMinRowHeight,
437 ceilf(be_plain_font->Size() * kRowSpacing))),
438 fNextSelected(NULL),
439 fPrevSelected(NULL),
440 fParent(NULL),
441 fList(NULL)
446 BRow::BRow(float height)
448 fChildList(NULL),
449 fIsExpanded(false),
450 fHeight(height),
451 fNextSelected(NULL),
452 fPrevSelected(NULL),
453 fParent(NULL),
454 fList(NULL)
459 BRow::~BRow()
461 while (true) {
462 BField* field = (BField*) fFields.RemoveItem((int32)0);
463 if (field == 0)
464 break;
466 delete field;
471 bool
472 BRow::HasLatch() const
474 return fChildList != 0;
478 int32
479 BRow::CountFields() const
481 return fFields.CountItems();
485 BField*
486 BRow::GetField(int32 index)
488 return (BField*)fFields.ItemAt(index);
492 const BField*
493 BRow::GetField(int32 index) const
495 return (const BField*)fFields.ItemAt(index);
499 void
500 BRow::SetField(BField* field, int32 logicalFieldIndex)
502 if (fFields.ItemAt(logicalFieldIndex) != 0)
503 delete (BField*)fFields.RemoveItem(logicalFieldIndex);
505 if (NULL != fList) {
506 ValidateField(field, logicalFieldIndex);
507 Invalidate();
510 fFields.AddItem(field, logicalFieldIndex);
514 float
515 BRow::Height() const
517 return fHeight;
521 bool
522 BRow::IsExpanded() const
524 return fIsExpanded;
528 bool
529 BRow::IsSelected() const
531 return fPrevSelected != NULL;
535 void
536 BRow::Invalidate()
538 if (fList != NULL)
539 fList->InvalidateRow(this);
543 void
544 BRow::ValidateFields() const
546 for (int32 i = 0; i < CountFields(); i++)
547 ValidateField(GetField(i), i);
551 void
552 BRow::ValidateField(const BField* field, int32 logicalFieldIndex) const
554 // The Fields may be moved by the user, but the logicalFieldIndexes
555 // do not change, so we need to map them over when checking the
556 // Field types.
557 BColumn* column = NULL;
558 int32 items = fList->CountColumns();
559 for (int32 i = 0 ; i < items; ++i) {
560 column = fList->ColumnAt(i);
561 if(column->LogicalFieldNum() == logicalFieldIndex )
562 break;
565 if (column == NULL) {
566 BString dbmessage("\n\n\tThe parent BColumnListView does not have "
567 "\n\ta BColumn at the logical field index ");
568 dbmessage << logicalFieldIndex << ".\n\n";
569 printf(dbmessage.String());
570 } else {
571 if (!column->AcceptsField(field)) {
572 BString dbmessage("\n\n\tThe BColumn of type ");
573 dbmessage << typeid(*column).name() << "\n\tat logical field index "
574 << logicalFieldIndex << "\n\tdoes not support the field type "
575 << typeid(*field).name() << ".\n\n";
576 debugger(dbmessage.String());
582 // #pragma mark -
585 BColumn::BColumn(float width, float minWidth, float maxWidth, alignment align)
587 fWidth(width),
588 fMinWidth(minWidth),
589 fMaxWidth(maxWidth),
590 fVisible(true),
591 fList(0),
592 fShowHeading(true),
593 fAlignment(align)
598 BColumn::~BColumn()
603 float
604 BColumn::Width() const
606 return fWidth;
610 void
611 BColumn::SetWidth(float width)
613 fWidth = width;
617 float
618 BColumn::MinWidth() const
620 return fMinWidth;
624 float
625 BColumn::MaxWidth() const
627 return fMaxWidth;
631 void
632 BColumn::DrawTitle(BRect, BView*)
637 void
638 BColumn::DrawField(BField*, BRect, BView*)
644 BColumn::CompareFields(BField*, BField*)
646 return 0;
650 void
651 BColumn::GetColumnName(BString* into) const
653 *into = "(Unnamed)";
657 float
658 BColumn::GetPreferredWidth(BField* field, BView* parent) const
660 return fWidth;
664 bool
665 BColumn::IsVisible() const
667 return fVisible;
671 void
672 BColumn::SetVisible(bool visible)
674 if (fList && (fVisible != visible))
675 fList->SetColumnVisible(this, visible);
679 bool
680 BColumn::ShowHeading() const
682 return fShowHeading;
686 void
687 BColumn::SetShowHeading(bool state)
689 fShowHeading = state;
693 alignment
694 BColumn::Alignment() const
696 return fAlignment;
700 void
701 BColumn::SetAlignment(alignment align)
703 fAlignment = align;
707 bool
708 BColumn::WantsEvents() const
710 return fWantsEvents;
714 void
715 BColumn::SetWantsEvents(bool state)
717 fWantsEvents = state;
721 int32
722 BColumn::LogicalFieldNum() const
724 return fFieldID;
728 bool
729 BColumn::AcceptsField(const BField*) const
731 return true;
735 // #pragma mark -
738 BColumnListView::BColumnListView(BRect rect, const char* name,
739 uint32 resizingMode, uint32 flags, border_style border,
740 bool showHorizontalScrollbar)
742 BView(rect, name, resizingMode,
743 flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
744 fStatusView(NULL),
745 fSelectionMessage(NULL),
746 fSortingEnabled(true),
747 fLatchWidth(kLatchWidth),
748 fBorderStyle(border),
749 fShowingHorizontalScrollBar(showHorizontalScrollbar)
751 _Init();
755 BColumnListView::BColumnListView(const char* name, uint32 flags,
756 border_style border, bool showHorizontalScrollbar)
758 BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
759 fStatusView(NULL),
760 fSelectionMessage(NULL),
761 fSortingEnabled(true),
762 fLatchWidth(kLatchWidth),
763 fBorderStyle(border),
764 fShowingHorizontalScrollBar(showHorizontalScrollbar)
766 _Init();
770 BColumnListView::~BColumnListView()
772 while (BColumn* column = (BColumn*)fColumns.RemoveItem((int32)0))
773 delete column;
777 bool
778 BColumnListView::InitiateDrag(BPoint, bool)
780 return false;
784 void
785 BColumnListView::MessageDropped(BMessage*, BPoint)
790 void
791 BColumnListView::ExpandOrCollapse(BRow* row, bool Open)
793 fOutlineView->ExpandOrCollapse(row, Open);
797 status_t
798 BColumnListView::Invoke(BMessage* message)
800 if (message == 0)
801 message = Message();
803 return BInvoker::Invoke(message);
807 void
808 BColumnListView::ItemInvoked()
810 Invoke();
814 void
815 BColumnListView::SetInvocationMessage(BMessage* message)
817 SetMessage(message);
821 BMessage*
822 BColumnListView::InvocationMessage() const
824 return Message();
828 uint32
829 BColumnListView::InvocationCommand() const
831 return Command();
835 BRow*
836 BColumnListView::FocusRow() const
838 return fOutlineView->FocusRow();
842 void
843 BColumnListView::SetFocusRow(int32 Index, bool Select)
845 SetFocusRow(RowAt(Index), Select);
849 void
850 BColumnListView::SetFocusRow(BRow* row, bool Select)
852 fOutlineView->SetFocusRow(row, Select);
856 void
857 BColumnListView::SetMouseTrackingEnabled(bool Enabled)
859 fOutlineView->SetMouseTrackingEnabled(Enabled);
863 list_view_type
864 BColumnListView::SelectionMode() const
866 return fOutlineView->SelectionMode();
870 void
871 BColumnListView::Deselect(BRow* row)
873 fOutlineView->Deselect(row);
877 void
878 BColumnListView::AddToSelection(BRow* row)
880 fOutlineView->AddToSelection(row);
884 void
885 BColumnListView::DeselectAll()
887 fOutlineView->DeselectAll();
891 BRow*
892 BColumnListView::CurrentSelection(BRow* lastSelected) const
894 return fOutlineView->CurrentSelection(lastSelected);
898 void
899 BColumnListView::SelectionChanged()
901 if (fSelectionMessage)
902 Invoke(fSelectionMessage);
906 void
907 BColumnListView::SetSelectionMessage(BMessage* message)
909 if (fSelectionMessage == message)
910 return;
912 delete fSelectionMessage;
913 fSelectionMessage = message;
917 BMessage*
918 BColumnListView::SelectionMessage()
920 return fSelectionMessage;
924 uint32
925 BColumnListView::SelectionCommand() const
927 if (fSelectionMessage)
928 return fSelectionMessage->what;
930 return 0;
934 void
935 BColumnListView::SetSelectionMode(list_view_type mode)
937 fOutlineView->SetSelectionMode(mode);
941 void
942 BColumnListView::SetSortingEnabled(bool enabled)
944 fSortingEnabled = enabled;
945 fSortColumns.MakeEmpty();
946 fTitleView->Invalidate();
947 // erase sort indicators
951 bool
952 BColumnListView::SortingEnabled() const
954 return fSortingEnabled;
958 void
959 BColumnListView::SetSortColumn(BColumn* column, bool add, bool ascending)
961 if (!SortingEnabled())
962 return;
964 if (!add)
965 fSortColumns.MakeEmpty();
967 if (!fSortColumns.HasItem(column))
968 fSortColumns.AddItem(column);
970 column->fSortAscending = ascending;
971 fTitleView->Invalidate();
972 fOutlineView->StartSorting();
976 void
977 BColumnListView::ClearSortColumns()
979 fSortColumns.MakeEmpty();
980 fTitleView->Invalidate();
981 // erase sort indicators
985 void
986 BColumnListView::AddStatusView(BView* view)
988 BRect bounds = Bounds();
989 float width = view->Bounds().Width();
990 if (width > bounds.Width() / 2)
991 width = bounds.Width() / 2;
993 fStatusView = view;
995 Window()->BeginViewTransaction();
996 fHorizontalScrollBar->ResizeBy(-(width + 1), 0);
997 fHorizontalScrollBar->MoveBy((width + 1), 0);
998 AddChild(view);
1000 BRect viewRect(bounds);
1001 viewRect.right = width;
1002 viewRect.top = viewRect.bottom - B_H_SCROLL_BAR_HEIGHT;
1003 if (fBorderStyle == B_PLAIN_BORDER)
1004 viewRect.OffsetBy(1, -1);
1005 else if (fBorderStyle == B_FANCY_BORDER)
1006 viewRect.OffsetBy(2, -2);
1008 view->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
1009 view->ResizeTo(viewRect.Width(), viewRect.Height());
1010 view->MoveTo(viewRect.left, viewRect.top);
1011 Window()->EndViewTransaction();
1015 BView*
1016 BColumnListView::RemoveStatusView()
1018 if (fStatusView) {
1019 float width = fStatusView->Bounds().Width();
1020 Window()->BeginViewTransaction();
1021 fStatusView->RemoveSelf();
1022 fHorizontalScrollBar->MoveBy(-width, 0);
1023 fHorizontalScrollBar->ResizeBy(width, 0);
1024 Window()->EndViewTransaction();
1027 BView* view = fStatusView;
1028 fStatusView = 0;
1029 return view;
1033 void
1034 BColumnListView::AddColumn(BColumn* column, int32 logicalFieldIndex)
1036 ASSERT(column != NULL);
1038 column->fList = this;
1039 column->fFieldID = logicalFieldIndex;
1041 // sanity check -- if there is already a field with this ID, remove it.
1042 for (int32 index = 0; index < fColumns.CountItems(); index++) {
1043 BColumn* existingColumn = (BColumn*) fColumns.ItemAt(index);
1044 if (existingColumn && existingColumn->fFieldID == logicalFieldIndex) {
1045 RemoveColumn(existingColumn);
1046 break;
1050 if (column->Width() < column->MinWidth())
1051 column->SetWidth(column->MinWidth());
1052 else if (column->Width() > column->MaxWidth())
1053 column->SetWidth(column->MaxWidth());
1055 fColumns.AddItem((void*) column);
1056 fTitleView->ColumnAdded(column);
1060 void
1061 BColumnListView::MoveColumn(BColumn* column, int32 index)
1063 ASSERT(column != NULL);
1064 fTitleView->MoveColumn(column, index);
1068 void
1069 BColumnListView::RemoveColumn(BColumn* column)
1071 if (fColumns.HasItem(column)) {
1072 SetColumnVisible(column, false);
1073 if (Window() != NULL)
1074 Window()->UpdateIfNeeded();
1075 fColumns.RemoveItem(column);
1080 int32
1081 BColumnListView::CountColumns() const
1083 return fColumns.CountItems();
1087 BColumn*
1088 BColumnListView::ColumnAt(int32 field) const
1090 return (BColumn*) fColumns.ItemAt(field);
1094 BColumn*
1095 BColumnListView::ColumnAt(BPoint point) const
1097 float left = MAX(kLeftMargin, LatchWidth());
1099 for (int i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1100 if (column == NULL || !column->IsVisible())
1101 continue;
1103 float right = left + column->Width();
1104 if (point.x >= left && point.x <= right)
1105 return column;
1107 left = right + 1;
1110 return NULL;
1114 void
1115 BColumnListView::SetColumnVisible(BColumn* column, bool visible)
1117 fTitleView->SetColumnVisible(column, visible);
1121 void
1122 BColumnListView::SetColumnVisible(int32 index, bool isVisible)
1124 BColumn* column = ColumnAt(index);
1125 if (column != NULL)
1126 column->SetVisible(isVisible);
1130 bool
1131 BColumnListView::IsColumnVisible(int32 index) const
1133 BColumn* column = ColumnAt(index);
1134 if (column != NULL)
1135 return column->IsVisible();
1137 return false;
1141 void
1142 BColumnListView::SetColumnFlags(column_flags flags)
1144 fTitleView->SetColumnFlags(flags);
1148 void
1149 BColumnListView::ResizeColumnToPreferred(int32 index)
1151 BColumn* column = ColumnAt(index);
1152 if (column == NULL)
1153 return;
1155 // get the preferred column width
1156 float width = fOutlineView->GetColumnPreferredWidth(column);
1158 // set it
1159 float oldWidth = column->Width();
1160 column->SetWidth(width);
1162 fTitleView->ColumnResized(column, oldWidth);
1163 fOutlineView->Invalidate();
1167 void
1168 BColumnListView::ResizeAllColumnsToPreferred()
1170 int32 count = CountColumns();
1171 for (int32 i = 0; i < count; i++)
1172 ResizeColumnToPreferred(i);
1176 const BRow*
1177 BColumnListView::RowAt(int32 Index, BRow* parentRow) const
1179 if (parentRow == 0)
1180 return fOutlineView->RowList()->ItemAt(Index);
1182 return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : NULL;
1186 BRow*
1187 BColumnListView::RowAt(int32 Index, BRow* parentRow)
1189 if (parentRow == 0)
1190 return fOutlineView->RowList()->ItemAt(Index);
1192 return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : 0;
1196 const BRow*
1197 BColumnListView::RowAt(BPoint point) const
1199 float top;
1200 int32 indent;
1201 return fOutlineView->FindRow(point.y, &indent, &top);
1205 BRow*
1206 BColumnListView::RowAt(BPoint point)
1208 float top;
1209 int32 indent;
1210 return fOutlineView->FindRow(point.y, &indent, &top);
1214 bool
1215 BColumnListView::GetRowRect(const BRow* row, BRect* outRect) const
1217 return fOutlineView->FindRect(row, outRect);
1221 bool
1222 BColumnListView::FindParent(BRow* row, BRow** _parent, bool* _isVisible) const
1224 return fOutlineView->FindParent(row, _parent, _isVisible);
1228 int32
1229 BColumnListView::IndexOf(BRow* row)
1231 return fOutlineView->IndexOf(row);
1235 int32
1236 BColumnListView::CountRows(BRow* parentRow) const
1238 if (parentRow == 0)
1239 return fOutlineView->RowList()->CountItems();
1240 if (parentRow->fChildList)
1241 return parentRow->fChildList->CountItems();
1242 else
1243 return 0;
1247 void
1248 BColumnListView::AddRow(BRow* row, BRow* parentRow)
1250 AddRow(row, -1, parentRow);
1254 void
1255 BColumnListView::AddRow(BRow* row, int32 index, BRow* parentRow)
1257 row->fChildList = 0;
1258 row->fList = this;
1259 row->ValidateFields();
1260 fOutlineView->AddRow(row, index, parentRow);
1264 void
1265 BColumnListView::RemoveRow(BRow* row)
1267 fOutlineView->RemoveRow(row);
1268 row->fList = NULL;
1272 void
1273 BColumnListView::UpdateRow(BRow* row)
1275 fOutlineView->UpdateRow(row);
1279 bool
1280 BColumnListView::SwapRows(int32 index1, int32 index2, BRow* parentRow1,
1281 BRow* parentRow2)
1283 BRow* row1 = NULL;
1284 BRow* row2 = NULL;
1286 BRowContainer* container1 = NULL;
1287 BRowContainer* container2 = NULL;
1289 if (parentRow1 == NULL)
1290 container1 = fOutlineView->RowList();
1291 else
1292 container1 = parentRow1->fChildList;
1294 if (container1 == NULL)
1295 return false;
1297 if (parentRow2 == NULL)
1298 container2 = fOutlineView->RowList();
1299 else
1300 container2 = parentRow2->fChildList;
1302 if (container2 == NULL)
1303 return false;
1305 row1 = container1->ItemAt(index1);
1307 if (row1 == NULL)
1308 return false;
1310 row2 = container2->ItemAt(index2);
1312 if (row2 == NULL)
1313 return false;
1315 container1->ReplaceItem(index2, row1);
1316 container2->ReplaceItem(index1, row2);
1318 BRect rect1;
1319 BRect rect2;
1320 BRect rect;
1322 fOutlineView->FindRect(row1, &rect1);
1323 fOutlineView->FindRect(row2, &rect2);
1325 rect = rect1 | rect2;
1327 fOutlineView->Invalidate(rect);
1329 return true;
1333 void
1334 BColumnListView::ScrollTo(const BRow* row)
1336 fOutlineView->ScrollTo(row);
1340 void
1341 BColumnListView::ScrollTo(BPoint point)
1343 fOutlineView->ScrollTo(point);
1347 void
1348 BColumnListView::Clear()
1350 fOutlineView->Clear();
1354 void
1355 BColumnListView::InvalidateRow(BRow* row)
1357 BRect updateRect;
1358 GetRowRect(row, &updateRect);
1359 fOutlineView->Invalidate(updateRect);
1363 // This method is deprecated.
1364 void
1365 BColumnListView::SetFont(const BFont* font, uint32 mask)
1367 fOutlineView->SetFont(font, mask);
1368 fTitleView->SetFont(font, mask);
1372 void
1373 BColumnListView::SetFont(ColumnListViewFont font_num, const BFont* font,
1374 uint32 mask)
1376 switch (font_num) {
1377 case B_FONT_ROW:
1378 fOutlineView->SetFont(font, mask);
1379 break;
1381 case B_FONT_HEADER:
1382 fTitleView->SetFont(font, mask);
1383 break;
1385 default:
1386 ASSERT(false);
1387 break;
1392 void
1393 BColumnListView::GetFont(ColumnListViewFont font_num, BFont* font) const
1395 switch (font_num) {
1396 case B_FONT_ROW:
1397 fOutlineView->GetFont(font);
1398 break;
1400 case B_FONT_HEADER:
1401 fTitleView->GetFont(font);
1402 break;
1404 default:
1405 ASSERT(false);
1406 break;
1411 void
1412 BColumnListView::SetColor(ColumnListViewColor colorIndex, const rgb_color color)
1414 if ((int)colorIndex < 0) {
1415 ASSERT(false);
1416 colorIndex = (ColumnListViewColor)0;
1419 if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
1420 ASSERT(false);
1421 colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
1424 fColorList[colorIndex] = color;
1425 fCustomColors = true;
1429 void
1430 BColumnListView::ResetColors()
1432 fCustomColors = false;
1433 _UpdateColors();
1434 Invalidate();
1438 rgb_color
1439 BColumnListView::Color(ColumnListViewColor colorIndex) const
1441 if ((int)colorIndex < 0) {
1442 ASSERT(false);
1443 colorIndex = (ColumnListViewColor)0;
1446 if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
1447 ASSERT(false);
1448 colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
1451 return fColorList[colorIndex];
1455 void
1456 BColumnListView::SetHighColor(rgb_color color)
1458 BView::SetHighColor(color);
1459 // fOutlineView->Invalidate();
1460 // Redraw with the new color.
1461 // Note that this will currently cause an infinite loop, refreshing
1462 // over and over. A better solution is needed.
1466 void
1467 BColumnListView::SetSelectionColor(rgb_color color)
1469 fColorList[B_COLOR_SELECTION] = color;
1470 fCustomColors = true;
1474 void
1475 BColumnListView::SetBackgroundColor(rgb_color color)
1477 fColorList[B_COLOR_BACKGROUND] = color;
1478 fCustomColors = true;
1479 fOutlineView->Invalidate();
1480 // repaint with new color
1484 void
1485 BColumnListView::SetEditColor(rgb_color color)
1487 fColorList[B_COLOR_EDIT_BACKGROUND] = color;
1488 fCustomColors = true;
1492 const rgb_color
1493 BColumnListView::SelectionColor() const
1495 return fColorList[B_COLOR_SELECTION];
1499 const rgb_color
1500 BColumnListView::BackgroundColor() const
1502 return fColorList[B_COLOR_BACKGROUND];
1506 const rgb_color
1507 BColumnListView::EditColor() const
1509 return fColorList[B_COLOR_EDIT_BACKGROUND];
1513 BPoint
1514 BColumnListView::SuggestTextPosition(const BRow* row,
1515 const BColumn* inColumn) const
1517 BRect rect(GetFieldRect(row, inColumn));
1519 font_height fh;
1520 fOutlineView->GetFontHeight(&fh);
1521 float baseline = floor(rect.top + fh.ascent
1522 + (rect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
1523 return BPoint(rect.left + 8, baseline);
1527 BRect
1528 BColumnListView::GetFieldRect(const BRow* row, const BColumn* inColumn) const
1530 BRect rect;
1531 GetRowRect(row, &rect);
1532 if (inColumn != NULL) {
1533 float leftEdge = MAX(kLeftMargin, LatchWidth());
1534 for (int index = 0; index < fColumns.CountItems(); index++) {
1535 BColumn* column = (BColumn*) fColumns.ItemAt(index);
1536 if (column == NULL || !column->IsVisible())
1537 continue;
1539 if (column == inColumn) {
1540 rect.left = leftEdge;
1541 rect.right = rect.left + column->Width();
1542 break;
1545 leftEdge += column->Width() + 1;
1549 return rect;
1553 void
1554 BColumnListView::SetLatchWidth(float width)
1556 fLatchWidth = width;
1557 Invalidate();
1561 float
1562 BColumnListView::LatchWidth() const
1564 return fLatchWidth;
1567 void
1568 BColumnListView::DrawLatch(BView* view, BRect rect, LatchType position, BRow*)
1570 const int32 rectInset = 4;
1572 // make square
1573 int32 sideLen = rect.IntegerWidth();
1574 if (sideLen > rect.IntegerHeight())
1575 sideLen = rect.IntegerHeight();
1577 // make center
1578 int32 halfWidth = rect.IntegerWidth() / 2;
1579 int32 halfHeight = rect.IntegerHeight() / 2;
1580 int32 halfSide = sideLen / 2;
1582 float left = rect.left + halfWidth - halfSide;
1583 float top = rect.top + halfHeight - halfSide;
1585 BRect itemRect(left, top, left + sideLen, top + sideLen);
1587 // Why it is a pixel high? I don't know.
1588 itemRect.OffsetBy(0, -1);
1590 itemRect.InsetBy(rectInset, rectInset);
1592 // make it an odd number of pixels wide, the latch looks better this way
1593 if ((itemRect.IntegerWidth() % 2) == 1) {
1594 itemRect.right += 1;
1595 itemRect.bottom += 1;
1598 rgb_color highColor = view->HighColor();
1599 view->SetHighColor(0, 0, 0);
1601 switch (position) {
1602 case B_OPEN_LATCH:
1603 view->StrokeRect(itemRect);
1604 view->StrokeLine(
1605 BPoint(itemRect.left + 2,
1606 (itemRect.top + itemRect.bottom) / 2),
1607 BPoint(itemRect.right - 2,
1608 (itemRect.top + itemRect.bottom) / 2));
1609 break;
1611 case B_PRESSED_LATCH:
1612 view->StrokeRect(itemRect);
1613 view->StrokeLine(
1614 BPoint(itemRect.left + 2,
1615 (itemRect.top + itemRect.bottom) / 2),
1616 BPoint(itemRect.right - 2,
1617 (itemRect.top + itemRect.bottom) / 2));
1618 view->StrokeLine(
1619 BPoint((itemRect.left + itemRect.right) / 2,
1620 itemRect.top + 2),
1621 BPoint((itemRect.left + itemRect.right) / 2,
1622 itemRect.bottom - 2));
1623 view->InvertRect(itemRect);
1624 break;
1626 case B_CLOSED_LATCH:
1627 view->StrokeRect(itemRect);
1628 view->StrokeLine(
1629 BPoint(itemRect.left + 2,
1630 (itemRect.top + itemRect.bottom) / 2),
1631 BPoint(itemRect.right - 2,
1632 (itemRect.top + itemRect.bottom) / 2));
1633 view->StrokeLine(
1634 BPoint((itemRect.left + itemRect.right) / 2,
1635 itemRect.top + 2),
1636 BPoint((itemRect.left + itemRect.right) / 2,
1637 itemRect.bottom - 2));
1638 break;
1640 case B_NO_LATCH:
1641 default:
1642 // No drawing
1643 break;
1646 view->SetHighColor(highColor);
1650 void
1651 BColumnListView::MakeFocus(bool isFocus)
1653 if (fBorderStyle != B_NO_BORDER) {
1654 // Redraw focus marks around view
1655 Invalidate();
1656 fHorizontalScrollBar->SetBorderHighlighted(isFocus);
1657 fVerticalScrollBar->SetBorderHighlighted(isFocus);
1660 BView::MakeFocus(isFocus);
1664 void
1665 BColumnListView::MessageReceived(BMessage* message)
1667 // Propagate mouse wheel messages down to child, so that it can
1668 // scroll. Note we have done so, so we don't go into infinite
1669 // recursion if this comes back up here.
1670 if (message->what == B_MOUSE_WHEEL_CHANGED) {
1671 bool handled;
1672 if (message->FindBool("be:clvhandled", &handled) != B_OK) {
1673 message->AddBool("be:clvhandled", true);
1674 fOutlineView->MessageReceived(message);
1675 return;
1677 } else if (message->what == B_COLORS_UPDATED) {
1678 // Todo: Is it worthwhile to optimize this?
1679 _UpdateColors();
1682 BView::MessageReceived(message);
1686 void
1687 BColumnListView::KeyDown(const char* bytes, int32 numBytes)
1689 char c = bytes[0];
1690 switch (c) {
1691 case B_RIGHT_ARROW:
1692 case B_LEFT_ARROW:
1694 if ((modifiers() & B_SHIFT_KEY) != 0) {
1695 float minVal, maxVal;
1696 fHorizontalScrollBar->GetRange(&minVal, &maxVal);
1697 float smallStep, largeStep;
1698 fHorizontalScrollBar->GetSteps(&smallStep, &largeStep);
1699 float oldVal = fHorizontalScrollBar->Value();
1700 float newVal = oldVal;
1702 if (c == B_LEFT_ARROW)
1703 newVal -= smallStep;
1704 else if (c == B_RIGHT_ARROW)
1705 newVal += smallStep;
1707 if (newVal < minVal)
1708 newVal = minVal;
1709 else if (newVal > maxVal)
1710 newVal = maxVal;
1712 fHorizontalScrollBar->SetValue(newVal);
1713 } else {
1714 BRow* focusRow = fOutlineView->FocusRow();
1715 if (focusRow == NULL)
1716 break;
1718 bool expanded = focusRow->IsExpanded();
1719 if ((c == B_RIGHT_ARROW && !expanded)
1720 || (c == B_LEFT_ARROW && expanded)) {
1721 fOutlineView->ToggleFocusRowOpen();
1724 break;
1727 case B_DOWN_ARROW:
1728 fOutlineView->ChangeFocusRow(false,
1729 (modifiers() & B_CONTROL_KEY) == 0,
1730 (modifiers() & B_SHIFT_KEY) != 0);
1731 break;
1733 case B_UP_ARROW:
1734 fOutlineView->ChangeFocusRow(true,
1735 (modifiers() & B_CONTROL_KEY) == 0,
1736 (modifiers() & B_SHIFT_KEY) != 0);
1737 break;
1739 case B_PAGE_UP:
1740 case B_PAGE_DOWN:
1742 float minValue, maxValue;
1743 fVerticalScrollBar->GetRange(&minValue, &maxValue);
1744 float smallStep, largeStep;
1745 fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
1746 float currentValue = fVerticalScrollBar->Value();
1747 float newValue = currentValue;
1749 if (c == B_PAGE_UP)
1750 newValue -= largeStep;
1751 else
1752 newValue += largeStep;
1754 if (newValue > maxValue)
1755 newValue = maxValue;
1756 else if (newValue < minValue)
1757 newValue = minValue;
1759 fVerticalScrollBar->SetValue(newValue);
1761 // Option + pgup or pgdn scrolls and changes the selection.
1762 if (modifiers() & B_OPTION_KEY)
1763 fOutlineView->MoveFocusToVisibleRect();
1765 break;
1768 case B_ENTER:
1769 Invoke();
1770 break;
1772 case B_SPACE:
1773 fOutlineView->ToggleFocusRowSelection(
1774 (modifiers() & B_SHIFT_KEY) != 0);
1775 break;
1777 case '+':
1778 fOutlineView->ToggleFocusRowOpen();
1779 break;
1781 default:
1782 BView::KeyDown(bytes, numBytes);
1787 void
1788 BColumnListView::AttachedToWindow()
1790 if (!Messenger().IsValid())
1791 SetTarget(Window());
1793 if (SortingEnabled()) fOutlineView->StartSorting();
1797 void
1798 BColumnListView::WindowActivated(bool active)
1800 fOutlineView->Invalidate();
1801 // focus and selection appearance changes with focus
1803 Invalidate();
1804 // redraw focus marks around view
1805 BView::WindowActivated(active);
1809 void
1810 BColumnListView::Draw(BRect updateRect)
1812 BRect rect = Bounds();
1814 uint32 flags = 0;
1815 if (IsFocus() && Window()->IsActive())
1816 flags |= BControlLook::B_FOCUSED;
1818 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1820 BRect verticalScrollBarFrame;
1821 if (!fVerticalScrollBar->IsHidden())
1822 verticalScrollBarFrame = fVerticalScrollBar->Frame();
1824 BRect horizontalScrollBarFrame;
1825 if (!fHorizontalScrollBar->IsHidden())
1826 horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
1828 if (fBorderStyle == B_NO_BORDER) {
1829 // We still draw the left/top border, but not focused.
1830 // The scrollbars cannot be displayed without frame and
1831 // it looks bad to have no frame only along the left/top
1832 // side.
1833 rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
1834 SetHighColor(borderColor);
1835 StrokeLine(BPoint(rect.left, rect.bottom),
1836 BPoint(rect.left, rect.top));
1837 StrokeLine(BPoint(rect.left + 1, rect.top),
1838 BPoint(rect.right, rect.top));
1841 be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1842 verticalScrollBarFrame, horizontalScrollBarFrame,
1843 base, fBorderStyle, flags);
1845 if (fStatusView != NULL) {
1846 rect = Bounds();
1847 BRegion region(rect & fStatusView->Frame().InsetByCopy(-2, -2));
1848 ConstrainClippingRegion(&region);
1849 rect.bottom = fStatusView->Frame().top - 1;
1850 be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1851 BRect(), BRect(), base, fBorderStyle, flags);
1856 void
1857 BColumnListView::SaveState(BMessage* message)
1859 message->MakeEmpty();
1861 for (int32 i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1862 message->AddInt32("ID", column->fFieldID);
1863 message->AddFloat("width", column->fWidth);
1864 message->AddBool("visible", column->fVisible);
1867 message->AddBool("sortingenabled", fSortingEnabled);
1869 if (fSortingEnabled) {
1870 for (int32 i = 0; BColumn* column = (BColumn*)fSortColumns.ItemAt(i);
1871 i++) {
1872 message->AddInt32("sortID", column->fFieldID);
1873 message->AddBool("sortascending", column->fSortAscending);
1879 void
1880 BColumnListView::LoadState(BMessage* message)
1882 int32 id;
1883 for (int i = 0; message->FindInt32("ID", i, &id) == B_OK; i++) {
1884 for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
1885 if (column->fFieldID == id) {
1886 // move this column to position 'i' and set its attributes
1887 MoveColumn(column, i);
1888 float width;
1889 if (message->FindFloat("width", i, &width) == B_OK)
1890 column->SetWidth(width);
1891 bool visible;
1892 if (message->FindBool("visible", i, &visible) == B_OK)
1893 column->SetVisible(visible);
1897 bool b;
1898 if (message->FindBool("sortingenabled", &b) == B_OK) {
1899 SetSortingEnabled(b);
1900 for (int k = 0; message->FindInt32("sortID", k, &id) == B_OK; k++) {
1901 for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
1902 j++) {
1903 if (column->fFieldID == id) {
1904 // add this column to the sort list
1905 bool value;
1906 if (message->FindBool("sortascending", k, &value) == B_OK)
1907 SetSortColumn(column, true, value);
1915 void
1916 BColumnListView::SetEditMode(bool state)
1918 fOutlineView->SetEditMode(state);
1919 fTitleView->SetEditMode(state);
1923 void
1924 BColumnListView::Refresh()
1926 if (LockLooper()) {
1927 Invalidate();
1928 fOutlineView->FixScrollBar (true);
1929 fOutlineView->Invalidate();
1930 Window()->UpdateIfNeeded();
1931 UnlockLooper();
1936 BSize
1937 BColumnListView::MinSize()
1939 BSize size;
1940 size.width = 100;
1941 size.height = std::max(kMinTitleHeight,
1942 ceilf(be_plain_font->Size() * kTitleSpacing))
1943 + 4 * B_H_SCROLL_BAR_HEIGHT;
1944 if (!fHorizontalScrollBar->IsHidden())
1945 size.height += fHorizontalScrollBar->Frame().Height() + 1;
1946 // TODO: Take border size into account
1948 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1952 BSize
1953 BColumnListView::PreferredSize()
1955 BSize size = MinSize();
1956 size.height += ceilf(be_plain_font->Size()) * 20;
1958 // return MinSize().width if there are no columns.
1959 int32 count = CountColumns();
1960 if (count > 0) {
1961 BRect titleRect;
1962 BRect outlineRect;
1963 BRect vScrollBarRect;
1964 BRect hScrollBarRect;
1965 _GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1966 hScrollBarRect);
1967 // Start with the extra width for border and scrollbars etc.
1968 size.width = titleRect.left - Bounds().left;
1969 size.width += Bounds().right - titleRect.right;
1970 // If we want all columns to be visible at their preferred width,
1971 // we also need to add the extra margin width that the TitleView
1972 // uses to compute its _VirtualWidth() for the horizontal scroll bar.
1973 size.width += fTitleView->MarginWidth();
1974 for (int32 i = 0; i < count; i++) {
1975 BColumn* column = ColumnAt(i);
1976 if (column != NULL)
1977 size.width += fOutlineView->GetColumnPreferredWidth(column);
1981 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1985 BSize
1986 BColumnListView::MaxSize()
1988 BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
1989 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1993 void
1994 BColumnListView::LayoutInvalidated(bool descendants)
1999 void
2000 BColumnListView::DoLayout()
2002 if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
2003 return;
2005 BRect titleRect;
2006 BRect outlineRect;
2007 BRect vScrollBarRect;
2008 BRect hScrollBarRect;
2009 _GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
2010 hScrollBarRect);
2012 fTitleView->MoveTo(titleRect.LeftTop());
2013 fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
2015 fOutlineView->MoveTo(outlineRect.LeftTop());
2016 fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
2018 fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
2019 fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
2020 vScrollBarRect.Height());
2022 if (fStatusView != NULL) {
2023 BSize size = fStatusView->MinSize();
2024 if (size.height > B_H_SCROLL_BAR_HEIGHT)
2025 size.height = B_H_SCROLL_BAR_HEIGHT;
2026 if (size.width > Bounds().Width() / 2)
2027 size.width = floorf(Bounds().Width() / 2);
2029 BPoint offset(hScrollBarRect.LeftTop());
2031 if (fBorderStyle == B_PLAIN_BORDER) {
2032 offset += BPoint(0, 1);
2033 } else if (fBorderStyle == B_FANCY_BORDER) {
2034 offset += BPoint(-1, 2);
2035 size.height -= 1;
2038 fStatusView->MoveTo(offset);
2039 fStatusView->ResizeTo(size.width, size.height);
2040 hScrollBarRect.left = offset.x + size.width + 1;
2043 fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
2044 fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
2045 hScrollBarRect.Height());
2047 fOutlineView->FixScrollBar(true);
2051 void
2052 BColumnListView::_Init()
2054 SetViewColor(B_TRANSPARENT_32_BIT);
2056 BRect bounds(Bounds());
2057 if (bounds.Width() <= 0)
2058 bounds.right = 100;
2060 if (bounds.Height() <= 0)
2061 bounds.bottom = 100;
2063 fCustomColors = false;
2064 _UpdateColors();
2066 BRect titleRect;
2067 BRect outlineRect;
2068 BRect vScrollBarRect;
2069 BRect hScrollBarRect;
2070 _GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
2071 hScrollBarRect);
2073 fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
2074 AddChild(fOutlineView);
2077 fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
2078 &fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
2079 AddChild(fTitleView);
2081 fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
2082 fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
2083 AddChild(fVerticalScrollBar);
2085 fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
2086 "horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
2087 AddChild(fHorizontalScrollBar);
2089 if (!fShowingHorizontalScrollBar)
2090 fHorizontalScrollBar->Hide();
2092 fOutlineView->FixScrollBar(true);
2096 void
2097 BColumnListView::_UpdateColors()
2099 if (fCustomColors)
2100 return;
2102 fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR);
2103 fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2104 fColorList[B_COLOR_ROW_DIVIDER] = tint_color(
2105 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT);
2106 fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2107 fColorList[B_COLOR_SELECTION_TEXT] =
2108 ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2110 // For non focus selection uses the selection color as BListView
2111 fColorList[B_COLOR_NON_FOCUS_SELECTION] =
2112 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2114 // edit mode doesn't work very well
2115 fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color(
2116 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT);
2117 fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180;
2119 // Unused color
2120 fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2122 fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_PANEL_BACKGROUND_COLOR);
2123 fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR);
2125 // Unused colors
2126 fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2127 fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2131 void
2132 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
2133 BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect)
2135 titleRect = bounds;
2136 titleRect.bottom = titleRect.top + std::max(kMinTitleHeight,
2137 ceilf(be_plain_font->Size() * kTitleSpacing));
2138 #if !LOWER_SCROLLBAR
2139 titleRect.right -= B_V_SCROLL_BAR_WIDTH;
2140 #endif
2142 outlineRect = bounds;
2143 outlineRect.top = titleRect.bottom + 1.0;
2144 outlineRect.right -= B_V_SCROLL_BAR_WIDTH;
2145 if (fShowingHorizontalScrollBar)
2146 outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2148 vScrollBarRect = bounds;
2149 #if LOWER_SCROLLBAR
2150 vScrollBarRect.top += std::max(kMinTitleHeight,
2151 ceilf(be_plain_font->Size() * kTitleSpacing));
2152 #endif
2154 vScrollBarRect.left = vScrollBarRect.right - B_V_SCROLL_BAR_WIDTH;
2155 if (fShowingHorizontalScrollBar)
2156 vScrollBarRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2158 hScrollBarRect = bounds;
2159 hScrollBarRect.top = hScrollBarRect.bottom - B_H_SCROLL_BAR_HEIGHT;
2160 hScrollBarRect.right -= B_V_SCROLL_BAR_WIDTH;
2162 // Adjust stuff so the border will fit.
2163 if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) {
2164 titleRect.InsetBy(1, 0);
2165 titleRect.OffsetBy(0, 1);
2166 outlineRect.InsetBy(1, 1);
2167 } else if (fBorderStyle == B_FANCY_BORDER) {
2168 titleRect.InsetBy(2, 0);
2169 titleRect.OffsetBy(0, 2);
2170 outlineRect.InsetBy(2, 2);
2172 vScrollBarRect.OffsetBy(-1, 0);
2173 #if LOWER_SCROLLBAR
2174 vScrollBarRect.top += 2;
2175 vScrollBarRect.bottom -= 1;
2176 #else
2177 vScrollBarRect.InsetBy(0, 1);
2178 #endif
2179 hScrollBarRect.OffsetBy(0, -1);
2180 hScrollBarRect.InsetBy(1, 0);
2185 // #pragma mark -
2188 TitleView::TitleView(BRect rect, OutlineView* horizontalSlave,
2189 BList* visibleColumns, BList* sortColumns, BColumnListView* listView,
2190 uint32 resizingMode)
2192 BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS),
2193 fOutlineView(horizontalSlave),
2194 fColumns(visibleColumns),
2195 fSortColumns(sortColumns),
2196 // fColumnsWidth(0),
2197 fVisibleRect(rect.OffsetToCopy(0, 0)),
2198 fCurrentState(INACTIVE),
2199 fColumnPop(NULL),
2200 fMasterView(listView),
2201 fEditMode(false),
2202 fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE
2203 | B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE)
2205 SetViewColor(B_TRANSPARENT_COLOR);
2207 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2208 // xxx this needs to be smart about the size of the backbuffer.
2209 BRect doubleBufferRect(0, 0, 600, 35);
2210 fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
2211 fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
2212 B_FOLLOW_ALL_SIDES, 0);
2213 fDrawBuffer->Lock();
2214 fDrawBuffer->AddChild(fDrawBufferView);
2215 fDrawBuffer->Unlock();
2216 #endif
2218 fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2219 fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2221 fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
2222 fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
2224 fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST);
2225 fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST);
2226 fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST);
2227 fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE);
2229 FixScrollBar(true);
2233 TitleView::~TitleView()
2235 delete fColumnPop;
2236 fColumnPop = NULL;
2238 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2239 delete fDrawBuffer;
2240 #endif
2241 delete fUpSortArrow;
2242 delete fDownSortArrow;
2244 delete fResizeCursor;
2245 delete fMaxResizeCursor;
2246 delete fMinResizeCursor;
2247 delete fColumnMoveCursor;
2251 void
2252 TitleView::ColumnAdded(BColumn* column)
2254 // fColumnsWidth += column->Width();
2255 FixScrollBar(false);
2256 Invalidate();
2260 void
2261 TitleView::ColumnResized(BColumn* column, float oldWidth)
2263 // fColumnsWidth += column->Width() - oldWidth;
2264 FixScrollBar(false);
2265 Invalidate();
2269 void
2270 TitleView::SetColumnVisible(BColumn* column, bool visible)
2272 if (column->fVisible == visible)
2273 return;
2275 // If setting it visible, do this first so we can find its position
2276 // to invalidate. If hiding it, do it last.
2277 if (visible)
2278 column->fVisible = visible;
2280 BRect titleInvalid;
2281 GetTitleRect(column, &titleInvalid);
2283 // Now really set the visibility
2284 column->fVisible = visible;
2286 // if (visible)
2287 // fColumnsWidth += column->Width();
2288 // else
2289 // fColumnsWidth -= column->Width();
2291 BRect outlineInvalid(fOutlineView->VisibleRect());
2292 outlineInvalid.left = titleInvalid.left;
2293 titleInvalid.right = outlineInvalid.right;
2295 Invalidate(titleInvalid);
2296 fOutlineView->Invalidate(outlineInvalid);
2300 void
2301 TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect)
2303 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2304 int32 numColumns = fColumns->CountItems();
2305 for (int index = 0; index < numColumns; index++) {
2306 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2307 if (!column->IsVisible())
2308 continue;
2310 if (column == findColumn) {
2311 _rect->Set(leftEdge, 0, leftEdge + column->Width(),
2312 fVisibleRect.bottom);
2313 return;
2316 leftEdge += column->Width() + 1;
2319 TRESPASS();
2323 int32
2324 TitleView::FindColumn(BPoint position, float* _leftEdge)
2326 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2327 int32 numColumns = fColumns->CountItems();
2328 for (int index = 0; index < numColumns; index++) {
2329 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2330 if (!column->IsVisible())
2331 continue;
2333 if (leftEdge > position.x)
2334 break;
2336 if (position.x >= leftEdge
2337 && position.x <= leftEdge + column->Width()) {
2338 *_leftEdge = leftEdge;
2339 return index;
2342 leftEdge += column->Width() + 1;
2345 return 0;
2349 void
2350 TitleView::FixScrollBar(bool scrollToFit)
2352 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2353 if (hScrollBar == NULL)
2354 return;
2356 float virtualWidth = _VirtualWidth();
2358 if (virtualWidth > fVisibleRect.Width()) {
2359 hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
2361 // Perform the little trick if the user is scrolled over too far.
2362 // See OutlineView::FixScrollBar for a more in depth explanation
2363 float maxScrollBarValue = virtualWidth - fVisibleRect.Width();
2364 if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) {
2365 hScrollBar->SetRange(0.0, maxScrollBarValue);
2366 hScrollBar->SetSteps(50, fVisibleRect.Width());
2368 } else if (hScrollBar->Value() == 0.0) {
2369 // disable scroll bar.
2370 hScrollBar->SetRange(0.0, 0.0);
2375 void
2376 TitleView::DragSelectedColumn(BPoint position)
2378 float invalidLeft = fSelectedColumnRect.left;
2379 float invalidRight = fSelectedColumnRect.right;
2381 float leftEdge;
2382 int32 columnIndex = FindColumn(position, &leftEdge);
2383 fSelectedColumnRect.OffsetTo(leftEdge, 0);
2385 MoveColumn(fSelectedColumn, columnIndex);
2387 fSelectedColumn->fVisible = true;
2388 ComputeDragBoundries(fSelectedColumn, position);
2390 // Redraw the new column position
2391 GetTitleRect(fSelectedColumn, &fSelectedColumnRect);
2392 invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft);
2393 invalidRight = MAX(fSelectedColumnRect.right, invalidRight);
2395 Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom));
2396 fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight,
2397 fOutlineView->VisibleRect().bottom));
2399 DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2403 void
2404 TitleView::MoveColumn(BColumn* column, int32 index)
2406 fColumns->RemoveItem((void*) column);
2408 if (-1 == index) {
2409 // Re-add the column at the end of the list.
2410 fColumns->AddItem((void*) column);
2411 } else {
2412 fColumns->AddItem((void*) column, index);
2417 void
2418 TitleView::SetColumnFlags(column_flags flags)
2420 fColumnFlags = flags;
2424 float
2425 TitleView::MarginWidth() const
2427 return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin;
2431 void
2432 TitleView::ResizeSelectedColumn(BPoint position, bool preferred)
2434 float minWidth = fSelectedColumn->MinWidth();
2435 float maxWidth = fSelectedColumn->MaxWidth();
2437 float oldWidth = fSelectedColumn->Width();
2438 float originalEdge = fSelectedColumnRect.left + oldWidth;
2439 if (preferred) {
2440 float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
2441 fSelectedColumn->SetWidth(width);
2442 } else if (position.x > fSelectedColumnRect.left + maxWidth)
2443 fSelectedColumn->SetWidth(maxWidth);
2444 else if (position.x < fSelectedColumnRect.left + minWidth)
2445 fSelectedColumn->SetWidth(minWidth);
2446 else
2447 fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
2449 float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
2450 - originalEdge;
2451 if (dX != 0) {
2452 float columnHeight = fVisibleRect.Height();
2453 BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
2454 BRect movedRect(originalRect);
2455 movedRect.OffsetBy(dX, 0);
2457 // Update the size of the title column
2458 BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
2459 BRect destRect(sourceRect);
2460 destRect.OffsetBy(fSelectedColumnRect.left, 0);
2462 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2463 fDrawBuffer->Lock();
2464 DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
2465 fDrawBufferView->Sync();
2466 fDrawBuffer->Unlock();
2468 CopyBits(originalRect, movedRect);
2469 DrawBitmap(fDrawBuffer, sourceRect, destRect);
2470 #else
2471 CopyBits(originalRect, movedRect);
2472 DrawTitle(this, destRect, fSelectedColumn, false);
2473 #endif
2475 // Update the body view
2476 BRect slaveSize = fOutlineView->VisibleRect();
2477 BRect slaveSource(originalRect);
2478 slaveSource.bottom = slaveSize.bottom;
2479 BRect slaveDest(movedRect);
2480 slaveDest.bottom = slaveSize.bottom;
2481 fOutlineView->CopyBits(slaveSource, slaveDest);
2482 fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left,
2483 fResizingFirstColumn);
2485 // fColumnsWidth += dX;
2487 // Update the cursor
2488 if (fSelectedColumn->Width() == minWidth)
2489 SetViewCursor(fMinResizeCursor, true);
2490 else if (fSelectedColumn->Width() == maxWidth)
2491 SetViewCursor(fMaxResizeCursor, true);
2492 else
2493 SetViewCursor(fResizeCursor, true);
2495 ColumnResized(fSelectedColumn, oldWidth);
2500 void
2501 TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
2503 float previousColumnLeftEdge = -1000000.0;
2504 float nextColumnRightEdge = 1000000.0;
2506 bool foundColumn = false;
2507 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2508 int32 numColumns = fColumns->CountItems();
2509 for (int index = 0; index < numColumns; index++) {
2510 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2511 if (!column->IsVisible())
2512 continue;
2514 if (column == findColumn) {
2515 foundColumn = true;
2516 continue;
2519 if (foundColumn) {
2520 nextColumnRightEdge = leftEdge + column->Width();
2521 break;
2522 } else
2523 previousColumnLeftEdge = leftEdge;
2525 leftEdge += column->Width() + 1;
2528 float rightEdge = leftEdge + findColumn->Width();
2530 fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
2531 leftEdge);
2532 fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
2536 void
2537 TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
2539 BRect drawRect;
2540 rgb_color borderColor = mix_color(
2541 fMasterView->Color(B_COLOR_HEADER_BACKGROUND),
2542 make_color(0, 0, 0), 128);
2543 drawRect = rect;
2545 font_height fh;
2546 GetFontHeight(&fh);
2548 float baseline = floor(drawRect.top + fh.ascent
2549 + (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
2551 BRect bgRect = rect;
2553 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
2554 view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
2555 view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
2557 bgRect.bottom--;
2558 bgRect.right--;
2560 if (depressed)
2561 base = tint_color(base, B_DARKEN_1_TINT);
2563 be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
2564 BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
2566 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
2567 B_DARKEN_2_TINT));
2568 view->StrokeLine(rect.RightTop(), rect.RightBottom());
2570 // If no column given, nothing else to draw.
2571 if (column == NULL)
2572 return;
2574 view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2576 BFont font;
2577 GetFont(&font);
2578 view->SetFont(&font);
2580 int sortIndex = fSortColumns->IndexOf(column);
2581 if (sortIndex >= 0) {
2582 // Draw sort notation.
2583 BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline);
2585 if (fSortColumns->CountItems() > 1) {
2586 char str[256];
2587 sprintf(str, "%d", sortIndex + 1);
2588 const float w = view->StringWidth(str);
2589 upperLeft.x -= w;
2591 view->SetDrawingMode(B_OP_COPY);
2592 view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth,
2593 baseline));
2594 view->DrawString(str);
2597 float bmh = fDownSortArrow->Bounds().Height()+1;
2599 view->SetDrawingMode(B_OP_OVER);
2601 if (column->fSortAscending) {
2602 BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2603 - fDownSortArrow->Bounds().IntegerHeight()) / 2);
2604 view->DrawBitmapAsync(fDownSortArrow, leftTop);
2605 } else {
2606 BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2607 - fUpSortArrow->Bounds().IntegerHeight()) / 2);
2608 view->DrawBitmapAsync(fUpSortArrow, leftTop);
2611 upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2);
2612 if (upperLeft.y < drawRect.top)
2613 upperLeft.y = drawRect.top;
2615 // Adjust title stuff for sort indicator
2616 drawRect.right = upperLeft.x - 2;
2619 if (drawRect.right > drawRect.left) {
2620 #if CONSTRAIN_CLIPPING_REGION
2621 BRegion clipRegion(drawRect);
2622 view->PushState();
2623 view->ConstrainClippingRegion(&clipRegion);
2624 #endif
2625 view->MovePenTo(BPoint(drawRect.left + 8, baseline));
2626 view->SetDrawingMode(B_OP_OVER);
2627 view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2628 column->DrawTitle(drawRect, view);
2630 #if CONSTRAIN_CLIPPING_REGION
2631 view->PopState();
2632 #endif
2637 float
2638 TitleView::_VirtualWidth() const
2640 float width = MarginWidth();
2642 int32 count = fColumns->CountItems();
2643 for (int32 i = 0; i < count; i++) {
2644 BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
2645 width += column->Width();
2648 return width;
2652 void
2653 TitleView::Draw(BRect invalidRect)
2655 float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2656 for (int32 columnIndex = 0; columnIndex < fColumns->CountItems();
2657 columnIndex++) {
2659 BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
2660 if (!column->IsVisible())
2661 continue;
2663 if (columnLeftEdge > invalidRect.right)
2664 break;
2666 if (columnLeftEdge + column->Width() >= invalidRect.left) {
2667 BRect titleRect(columnLeftEdge, 0,
2668 columnLeftEdge + column->Width(), fVisibleRect.Height());
2669 DrawTitle(this, titleRect, column,
2670 (fCurrentState == DRAG_COLUMN_INSIDE_TITLE
2671 && fSelectedColumn == column));
2674 columnLeftEdge += column->Width() + 1;
2678 // bevels for right title margin
2679 if (columnLeftEdge <= invalidRect.right) {
2680 BRect titleRect(columnLeftEdge, 0, Bounds().right + 2,
2681 fVisibleRect.Height());
2682 DrawTitle(this, titleRect, NULL, false);
2685 // bevels for left title margin
2686 if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) {
2687 BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1,
2688 fVisibleRect.Height());
2689 DrawTitle(this, titleRect, NULL, false);
2692 #if DRAG_TITLE_OUTLINE
2693 // (internal) column drag indicator
2694 if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) {
2695 BRect dragRect(fSelectedColumnRect);
2696 dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2697 if (dragRect.Intersects(invalidRect)) {
2698 SetHighColor(0, 0, 255);
2699 StrokeRect(dragRect);
2702 #endif
2706 void
2707 TitleView::ScrollTo(BPoint position)
2709 fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0);
2710 fVisibleRect.OffsetTo(position.x, position.y);
2712 // Perform the little trick if the user is scrolled over too far.
2713 // See OutlineView::ScrollTo for a more in depth explanation
2714 float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width();
2715 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2716 float min, max;
2717 hScrollBar->GetRange(&min, &max);
2718 if (max != maxScrollBarValue && position.x > maxScrollBarValue)
2719 FixScrollBar(true);
2721 _inherited::ScrollTo(position);
2725 void
2726 TitleView::MessageReceived(BMessage* message)
2728 if (message->what == kToggleColumn) {
2729 int32 num;
2730 if (message->FindInt32("be:field_num", &num) == B_OK) {
2731 for (int index = 0; index < fColumns->CountItems(); index++) {
2732 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2733 if (column == NULL)
2734 continue;
2736 if (column->LogicalFieldNum() == num)
2737 column->SetVisible(!column->IsVisible());
2740 return;
2743 BView::MessageReceived(message);
2747 void
2748 TitleView::MouseDown(BPoint position)
2750 if (fEditMode)
2751 return;
2753 int32 buttons = 1;
2754 Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2755 if (buttons == B_SECONDARY_MOUSE_BUTTON
2756 && (fColumnFlags & B_ALLOW_COLUMN_POPUP)) {
2757 // Right mouse button -- bring up menu to show/hide columns.
2758 if (fColumnPop == NULL)
2759 fColumnPop = new BPopUpMenu("Columns", false, false);
2761 fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true);
2762 BMessenger me(this);
2763 for (int index = 0; index < fColumns->CountItems(); index++) {
2764 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2765 if (column == NULL)
2766 continue;
2768 BString name;
2769 column->GetColumnName(&name);
2770 BMessage* message = new BMessage(kToggleColumn);
2771 message->AddInt32("be:field_num", column->LogicalFieldNum());
2772 BMenuItem* item = new BMenuItem(name.String(), message);
2773 item->SetMarked(column->IsVisible());
2774 item->SetTarget(me);
2775 fColumnPop->AddItem(item);
2778 BPoint screenPosition = ConvertToScreen(position);
2779 BRect sticky(screenPosition, screenPosition);
2780 sticky.InsetBy(-5, -5);
2781 fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true);
2783 return;
2786 fResizingFirstColumn = true;
2787 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2788 for (int index = 0; index < fColumns->CountItems(); index++) {
2789 BColumn* column = (BColumn*)fColumns->ItemAt(index);
2790 if (column == NULL || !column->IsVisible())
2791 continue;
2793 if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2794 break;
2796 // check for resizing a column
2797 float rightEdge = leftEdge + column->Width();
2799 if (column->ShowHeading()) {
2800 if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2801 && position.x < rightEdge + kColumnResizeAreaWidth / 2
2802 && column->MaxWidth() > column->MinWidth()
2803 && (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) {
2805 int32 clicks = 0;
2806 fSelectedColumn = column;
2807 fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2808 fVisibleRect.Height());
2809 Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2810 if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) {
2811 ResizeSelectedColumn(position, true);
2812 fCurrentState = INACTIVE;
2813 break;
2815 fCurrentState = RESIZING_COLUMN;
2816 fClickPoint = BPoint(position.x - rightEdge - 1,
2817 position.y - fSelectedColumnRect.top);
2818 SetMouseEventMask(B_POINTER_EVENTS,
2819 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2820 break;
2823 fResizingFirstColumn = false;
2825 // check for clicking on a column
2826 if (position.x > leftEdge && position.x < rightEdge) {
2827 fCurrentState = PRESSING_COLUMN;
2828 fSelectedColumn = column;
2829 fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2830 fVisibleRect.Height());
2831 DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2832 fClickPoint = BPoint(position.x - fSelectedColumnRect.left,
2833 position.y - fSelectedColumnRect.top);
2834 SetMouseEventMask(B_POINTER_EVENTS,
2835 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2836 break;
2839 leftEdge = rightEdge + 1;
2844 void
2845 TitleView::MouseMoved(BPoint position, uint32 transit,
2846 const BMessage* dragMessage)
2848 if (fEditMode)
2849 return;
2851 // Handle column manipulation
2852 switch (fCurrentState) {
2853 case RESIZING_COLUMN:
2854 ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
2855 break;
2857 case PRESSING_COLUMN: {
2858 if (abs((int32)(position.x - (fClickPoint.x
2859 + fSelectedColumnRect.left))) > kColumnResizeAreaWidth
2860 || abs((int32)(position.y - (fClickPoint.y
2861 + fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
2862 // User has moved the mouse more than the tolerable amount,
2863 // initiate a drag.
2864 if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
2865 if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
2866 fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2867 ComputeDragBoundries(fSelectedColumn, position);
2868 SetViewCursor(fColumnMoveCursor, true);
2869 #if DRAG_TITLE_OUTLINE
2870 BRect invalidRect(fSelectedColumnRect);
2871 invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
2872 fCurrentDragPosition = position;
2873 Invalidate(invalidRect);
2874 #endif
2876 } else {
2877 if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
2878 // Dragged outside view
2879 fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2880 fSelectedColumn->SetVisible(false);
2881 BRect dragRect(fSelectedColumnRect);
2883 // There is a race condition where the mouse may have
2884 // moved by the time we get to handle this message.
2885 // If the user drags a column very quickly, this
2886 // results in the annoying bug where the cursor is
2887 // outside of the rectangle that is being dragged
2888 // around. Call GetMouse with the checkQueue flag set
2889 // to false so we can get the most recent position of
2890 // the mouse. This minimizes this problem (although
2891 // it is currently not possible to completely eliminate
2892 // it).
2893 uint32 buttons;
2894 GetMouse(&position, &buttons, false);
2895 dragRect.OffsetTo(position.x - fClickPoint.x,
2896 position.y - dragRect.Height() / 2);
2897 BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2902 break;
2905 case DRAG_COLUMN_INSIDE_TITLE: {
2906 if (transit == B_EXITED_VIEW
2907 && (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) {
2908 // Dragged outside view
2909 fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2910 fSelectedColumn->SetVisible(false);
2911 BRect dragRect(fSelectedColumnRect);
2913 // See explanation above.
2914 uint32 buttons;
2915 GetMouse(&position, &buttons, false);
2917 dragRect.OffsetTo(position.x - fClickPoint.x,
2918 position.y - fClickPoint.y);
2919 BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2920 } else if (position.x < fLeftDragBoundry
2921 || position.x > fRightDragBoundry) {
2922 DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2925 #if DRAG_TITLE_OUTLINE
2926 // Set up the invalid rect to include the rect for the previous
2927 // position of the drag rect, as well as the new one.
2928 BRect invalidRect(fSelectedColumnRect);
2929 invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2930 if (position.x < fCurrentDragPosition.x)
2931 invalidRect.left -= fCurrentDragPosition.x - position.x;
2932 else
2933 invalidRect.right += position.x - fCurrentDragPosition.x;
2935 fCurrentDragPosition = position;
2936 Invalidate(invalidRect);
2937 #endif
2938 break;
2941 case DRAG_COLUMN_OUTSIDE_TITLE:
2942 if (transit == B_ENTERED_VIEW) {
2943 // Drag back into view
2944 EndRectTracking();
2945 fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2946 fSelectedColumn->SetVisible(true);
2947 DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2950 break;
2952 case INACTIVE:
2953 // Check for cursor changes if we are over the resize area for
2954 // a column.
2955 BColumn* resizeColumn = 0;
2956 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2957 for (int index = 0; index < fColumns->CountItems(); index++) {
2958 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2959 if (!column->IsVisible())
2960 continue;
2962 if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2963 break;
2965 float rightEdge = leftEdge + column->Width();
2966 if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2967 && position.x < rightEdge + kColumnResizeAreaWidth / 2
2968 && column->MaxWidth() > column->MinWidth()) {
2969 resizeColumn = column;
2970 break;
2973 leftEdge = rightEdge + 1;
2976 // Update the cursor
2977 if (resizeColumn) {
2978 if (resizeColumn->Width() == resizeColumn->MinWidth())
2979 SetViewCursor(fMinResizeCursor, true);
2980 else if (resizeColumn->Width() == resizeColumn->MaxWidth())
2981 SetViewCursor(fMaxResizeCursor, true);
2982 else
2983 SetViewCursor(fResizeCursor, true);
2984 } else
2985 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
2986 break;
2991 void
2992 TitleView::MouseUp(BPoint position)
2994 if (fEditMode)
2995 return;
2997 switch (fCurrentState) {
2998 case RESIZING_COLUMN:
2999 ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
3000 fCurrentState = INACTIVE;
3001 FixScrollBar(false);
3002 break;
3004 case PRESSING_COLUMN: {
3005 if (fMasterView->SortingEnabled()) {
3006 if (fSortColumns->HasItem(fSelectedColumn)) {
3007 if ((modifiers() & B_CONTROL_KEY) == 0
3008 && fSortColumns->CountItems() > 1) {
3009 fSortColumns->MakeEmpty();
3010 fSortColumns->AddItem(fSelectedColumn);
3013 fSelectedColumn->fSortAscending
3014 = !fSelectedColumn->fSortAscending;
3015 } else {
3016 if ((modifiers() & B_CONTROL_KEY) == 0)
3017 fSortColumns->MakeEmpty();
3019 fSortColumns->AddItem(fSelectedColumn);
3020 fSelectedColumn->fSortAscending = true;
3023 fOutlineView->StartSorting();
3026 fCurrentState = INACTIVE;
3027 Invalidate();
3028 break;
3031 case DRAG_COLUMN_INSIDE_TITLE:
3032 fCurrentState = INACTIVE;
3034 #if DRAG_TITLE_OUTLINE
3035 Invalidate(); // xxx Can make this smaller
3036 #else
3037 Invalidate(fSelectedColumnRect);
3038 #endif
3039 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3040 break;
3042 case DRAG_COLUMN_OUTSIDE_TITLE:
3043 fCurrentState = INACTIVE;
3044 EndRectTracking();
3045 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3046 break;
3048 default:
3054 void
3055 TitleView::FrameResized(float width, float height)
3057 fVisibleRect.right = fVisibleRect.left + width;
3058 fVisibleRect.bottom = fVisibleRect.top + height;
3059 FixScrollBar(true);
3063 // #pragma mark - OutlineView
3066 OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns,
3067 BColumnListView* listView)
3069 BView(rect, "outline_view", B_FOLLOW_ALL_SIDES,
3070 B_WILL_DRAW | B_FRAME_EVENTS),
3071 fColumns(visibleColumns),
3072 fSortColumns(sortColumns),
3073 fItemsHeight(0.0),
3074 fVisibleRect(rect.OffsetToCopy(0, 0)),
3075 fFocusRow(0),
3076 fRollOverRow(0),
3077 fLastSelectedItem(0),
3078 fFirstSelectedItem(0),
3079 fSortThread(B_BAD_THREAD_ID),
3080 fCurrentState(INACTIVE),
3081 fMasterView(listView),
3082 fSelectionMode(B_MULTIPLE_SELECTION_LIST),
3083 fTrackMouse(false),
3084 fCurrentField(0),
3085 fCurrentRow(0),
3086 fCurrentColumn(0),
3087 fMouseDown(false),
3088 fCurrentCode(B_OUTSIDE_VIEW),
3089 fEditMode(false),
3090 fDragging(false),
3091 fClickCount(0),
3092 fDropHighlightY(-1)
3094 SetViewColor(B_TRANSPARENT_COLOR);
3096 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3097 // TODO: This needs to be smart about the size of the buffer.
3098 // Also, the buffer can be shared with the title's buffer.
3099 BRect doubleBufferRect(0, 0, 600, 35);
3100 fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
3101 fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
3102 B_FOLLOW_ALL_SIDES, 0);
3103 fDrawBuffer->Lock();
3104 fDrawBuffer->AddChild(fDrawBufferView);
3105 fDrawBuffer->Unlock();
3106 #endif
3108 FixScrollBar(true);
3109 fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
3110 fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
3114 OutlineView::~OutlineView()
3116 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3117 delete fDrawBuffer;
3118 #endif
3120 Clear();
3124 void
3125 OutlineView::Clear()
3127 DeselectAll();
3128 // Make sure selection list doesn't point to deleted rows!
3129 RecursiveDeleteRows(&fRows, false);
3130 fItemsHeight = 0.0;
3131 FixScrollBar(true);
3132 Invalidate();
3136 void
3137 OutlineView::SetSelectionMode(list_view_type mode)
3139 DeselectAll();
3140 fSelectionMode = mode;
3144 list_view_type
3145 OutlineView::SelectionMode() const
3147 return fSelectionMode;
3151 void
3152 OutlineView::Deselect(BRow* row)
3154 if (row == NULL)
3155 return;
3157 if (row->fNextSelected != 0) {
3158 row->fNextSelected->fPrevSelected = row->fPrevSelected;
3159 row->fPrevSelected->fNextSelected = row->fNextSelected;
3160 row->fNextSelected = 0;
3161 row->fPrevSelected = 0;
3162 Invalidate();
3167 void
3168 OutlineView::AddToSelection(BRow* row)
3170 if (row == NULL)
3171 return;
3173 if (row->fNextSelected == 0) {
3174 if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3175 DeselectAll();
3177 row->fNextSelected = fSelectionListDummyHead.fNextSelected;
3178 row->fPrevSelected = &fSelectionListDummyHead;
3179 row->fNextSelected->fPrevSelected = row;
3180 row->fPrevSelected->fNextSelected = row;
3182 BRect invalidRect;
3183 if (FindVisibleRect(row, &invalidRect))
3184 Invalidate(invalidRect);
3189 void
3190 OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
3192 if (list == NULL)
3193 return;
3195 while (true) {
3196 BRow* row = list->RemoveItemAt(0L);
3197 if (row == 0)
3198 break;
3200 if (row->fChildList)
3201 RecursiveDeleteRows(row->fChildList, true);
3203 delete row;
3206 if (isOwner)
3207 delete list;
3211 void
3212 OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
3214 // TODO: Remove code duplication (private function which takes a view
3215 // pointer, pass "this" in non-double buffered mode)!
3216 // Watch out for sourceRect versus destRect though!
3217 if (!column)
3218 return;
3220 font_height fh;
3221 GetFontHeight(&fh);
3222 float line = 0.0;
3223 bool tintedLine = true;
3224 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3225 line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
3227 BRow* row = iterator.CurrentRow();
3228 float rowHeight = row->Height();
3229 if (line > fVisibleRect.bottom)
3230 break;
3231 tintedLine = !tintedLine;
3233 if (line + rowHeight >= fVisibleRect.top) {
3234 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3235 BRect sourceRect(0, 0, column->Width(), rowHeight);
3236 #endif
3237 BRect destRect(leftEdge, line, leftEdge + column->Width(),
3238 line + rowHeight);
3240 rgb_color highColor;
3241 rgb_color lowColor;
3242 if (row->fNextSelected != 0) {
3243 if (fEditMode) {
3244 highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3245 lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3246 } else {
3247 highColor = fMasterView->Color(B_COLOR_SELECTION);
3248 lowColor = fMasterView->Color(B_COLOR_SELECTION);
3250 } else {
3251 highColor = fMasterView->Color(B_COLOR_BACKGROUND);
3252 lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3254 if (tintedLine)
3255 lowColor = tint_color(lowColor, kTintedLineTint);
3258 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3259 fDrawBuffer->Lock();
3261 fDrawBufferView->SetHighColor(highColor);
3262 fDrawBufferView->SetLowColor(lowColor);
3264 BFont font;
3265 GetFont(&font);
3266 fDrawBufferView->SetFont(&font);
3267 fDrawBufferView->FillRect(sourceRect, B_SOLID_LOW);
3269 if (isFirstColumn) {
3270 // If this is the first column, double buffer drawing the latch
3271 // too.
3272 destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3273 - fMasterView->LatchWidth();
3274 sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3275 - fMasterView->LatchWidth();
3277 LatchType pos = B_NO_LATCH;
3278 if (row->HasLatch())
3279 pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH;
3281 BRect latchRect(sourceRect);
3282 latchRect.right = latchRect.left + fMasterView->LatchWidth();
3283 fMasterView->DrawLatch(fDrawBufferView, latchRect, pos, row);
3286 BField* field = row->GetField(column->fFieldID);
3287 if (field) {
3288 BRect fieldRect(sourceRect);
3289 if (isFirstColumn)
3290 fieldRect.left += fMasterView->LatchWidth();
3292 #if CONSTRAIN_CLIPPING_REGION
3293 BRegion clipRegion(fieldRect);
3294 fDrawBufferView->PushState();
3295 fDrawBufferView->ConstrainClippingRegion(&clipRegion);
3296 #endif
3297 fDrawBufferView->SetHighColor(fMasterView->Color(
3298 row->fNextSelected ? B_COLOR_SELECTION_TEXT
3299 : B_COLOR_TEXT));
3300 float baseline = floor(fieldRect.top + fh.ascent
3301 + (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
3302 fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
3303 column->DrawField(field, fieldRect, fDrawBufferView);
3304 #if CONSTRAIN_CLIPPING_REGION
3305 fDrawBufferView->PopState();
3306 #endif
3309 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3310 && Window()->IsActive()) {
3311 fDrawBufferView->SetHighColor(fMasterView->Color(
3312 B_COLOR_ROW_DIVIDER));
3313 fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top,
3314 10000.0, sourceRect.bottom));
3317 fDrawBufferView->Sync();
3318 fDrawBuffer->Unlock();
3319 SetDrawingMode(B_OP_COPY);
3320 DrawBitmap(fDrawBuffer, sourceRect, destRect);
3322 #else
3324 SetHighColor(highColor);
3325 SetLowColor(lowColor);
3326 FillRect(destRect, B_SOLID_LOW);
3328 BField* field = row->GetField(column->fFieldID);
3329 if (field) {
3330 #if CONSTRAIN_CLIPPING_REGION
3331 BRegion clipRegion(destRect);
3332 PushState();
3333 ConstrainClippingRegion(&clipRegion);
3334 #endif
3335 SetHighColor(fMasterView->Color(row->fNextSelected
3336 ? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
3337 float baseline = floor(destRect.top + fh.ascent
3338 + (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
3339 MovePenTo(destRect.left + 8, baseline);
3340 column->DrawField(field, destRect, this);
3341 #if CONSTRAIN_CLIPPING_REGION
3342 PopState();
3343 #endif
3346 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3347 && Window()->IsActive()) {
3348 SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3349 StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
3351 #endif
3357 void
3358 OutlineView::Draw(BRect invalidBounds)
3360 #if SMART_REDRAW
3361 BRegion invalidRegion;
3362 GetClippingRegion(&invalidRegion);
3363 #endif
3365 font_height fh;
3366 GetFontHeight(&fh);
3368 float line = 0.0;
3369 bool tintedLine = true;
3370 int32 numColumns = fColumns->CountItems();
3371 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3372 iterator.GoToNext()) {
3373 BRow* row = iterator.CurrentRow();
3374 if (line > invalidBounds.bottom)
3375 break;
3377 tintedLine = !tintedLine;
3378 float rowHeight = row->Height();
3380 if (line >= invalidBounds.top - rowHeight) {
3381 bool isFirstColumn = true;
3382 float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
3384 // setup background color
3385 rgb_color lowColor;
3386 if (row->fNextSelected != 0) {
3387 if (Window()->IsActive()) {
3388 if (fEditMode)
3389 lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3390 else
3391 lowColor = fMasterView->Color(B_COLOR_SELECTION);
3393 else
3394 lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
3395 } else
3396 lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3397 if (tintedLine)
3398 lowColor = tint_color(lowColor, kTintedLineTint);
3400 for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) {
3401 BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
3402 if (!column->IsVisible())
3403 continue;
3405 if (!isFirstColumn && fieldLeftEdge > invalidBounds.right)
3406 break;
3408 if (fieldLeftEdge + column->Width() >= invalidBounds.left) {
3409 BRect fullRect(fieldLeftEdge, line,
3410 fieldLeftEdge + column->Width(), line + rowHeight);
3412 bool clippedFirstColumn = false;
3413 // This happens when a column is indented past the
3414 // beginning of the next column.
3416 SetHighColor(lowColor);
3418 BRect destRect(fullRect);
3419 if (isFirstColumn) {
3420 fullRect.left -= fMasterView->LatchWidth();
3421 destRect.left += iterator.CurrentLevel()
3422 * kOutlineLevelIndent;
3423 if (destRect.left >= destRect.right) {
3424 // clipped
3425 FillRect(BRect(0, line, fieldLeftEdge
3426 + column->Width(), line + rowHeight));
3427 clippedFirstColumn = true;
3430 FillRect(BRect(0, line, MAX(kLeftMargin,
3431 fMasterView->LatchWidth()), line + row->Height()));
3435 #if SMART_REDRAW
3436 if (!clippedFirstColumn
3437 && invalidRegion.Intersects(fullRect)) {
3438 #else
3439 if (!clippedFirstColumn) {
3440 #endif
3441 FillRect(fullRect); // Using color set above
3443 // Draw the latch widget if it has one.
3444 if (isFirstColumn) {
3445 if (row == fTargetRow
3446 && fCurrentState == LATCH_CLICKED) {
3447 // Note that this only occurs if the user is
3448 // holding down a latch while items are added
3449 // in the background.
3450 BPoint pos;
3451 uint32 buttons;
3452 GetMouse(&pos, &buttons);
3453 if (fLatchRect.Contains(pos)) {
3454 fMasterView->DrawLatch(this, fLatchRect,
3455 B_PRESSED_LATCH, fTargetRow);
3456 } else {
3457 fMasterView->DrawLatch(this, fLatchRect,
3458 row->fIsExpanded ? B_OPEN_LATCH
3459 : B_CLOSED_LATCH, fTargetRow);
3461 } else {
3462 LatchType pos = B_NO_LATCH;
3463 if (row->HasLatch())
3464 pos = row->fIsExpanded ? B_OPEN_LATCH
3465 : B_CLOSED_LATCH;
3467 fMasterView->DrawLatch(this,
3468 BRect(destRect.left
3469 - fMasterView->LatchWidth(),
3470 destRect.top, destRect.left,
3471 destRect.bottom), pos, row);
3475 SetHighColor(fMasterView->HighColor());
3476 // The master view just holds the high color for us.
3477 SetLowColor(lowColor);
3479 BField* field = row->GetField(column->fFieldID);
3480 if (field) {
3481 #if CONSTRAIN_CLIPPING_REGION
3482 BRegion clipRegion(destRect);
3483 PushState();
3484 ConstrainClippingRegion(&clipRegion);
3485 #endif
3486 SetHighColor(fMasterView->Color(
3487 row->fNextSelected ? B_COLOR_SELECTION_TEXT
3488 : B_COLOR_TEXT));
3489 float baseline = floor(destRect.top + fh.ascent
3490 + (destRect.Height() + 1
3491 - (fh.ascent+fh.descent)) / 2);
3492 MovePenTo(destRect.left + 8, baseline);
3493 column->DrawField(field, destRect, this);
3494 #if CONSTRAIN_CLIPPING_REGION
3495 PopState();
3496 #endif
3501 isFirstColumn = false;
3502 fieldLeftEdge += column->Width() + 1;
3505 if (fieldLeftEdge <= invalidBounds.right) {
3506 SetHighColor(lowColor);
3507 FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
3508 line + rowHeight));
3512 // indicate the keyboard focus row
3513 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3514 && Window()->IsActive()) {
3515 SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3516 StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
3519 line += rowHeight + 1;
3522 if (line <= invalidBounds.bottom) {
3523 // fill background below last item
3524 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3525 FillRect(BRect(invalidBounds.left, line, invalidBounds.right,
3526 invalidBounds.bottom));
3529 // Draw the drop target line
3530 if (fDropHighlightY != -1) {
3531 InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3532 1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3537 BRow*
3538 OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top)
3540 if (_rowIndent && _top) {
3541 float line = 0.0;
3542 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3543 iterator.GoToNext()) {
3545 BRow* row = iterator.CurrentRow();
3546 if (line > ypos)
3547 break;
3549 float rowHeight = row->Height();
3550 if (ypos <= line + rowHeight) {
3551 *_top = line;
3552 *_rowIndent = iterator.CurrentLevel();
3553 return row;
3556 line += rowHeight + 1;
3560 return NULL;
3563 void OutlineView::SetMouseTrackingEnabled(bool enabled)
3565 fTrackMouse = enabled;
3566 if (!enabled && fDropHighlightY != -1) {
3567 // Erase the old target line
3568 InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3569 1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3570 fDropHighlightY = -1;
3576 // Note that this interaction is not totally safe. If items are added to
3577 // the list in the background, the widget rect will be incorrect, possibly
3578 // resulting in drawing glitches. The code that adds items needs to be a little smarter
3579 // about invalidating state.
3581 void
3582 OutlineView::MouseDown(BPoint position)
3584 if (!fEditMode)
3585 fMasterView->MakeFocus(true);
3587 // Check to see if the user is clicking on a widget to open a section
3588 // of the list.
3589 bool reset_click_count = false;
3590 int32 indent;
3591 float rowTop;
3592 BRow* row = FindRow(position.y, &indent, &rowTop);
3593 if (row != NULL) {
3595 // Update fCurrentField
3596 bool handle_field = false;
3597 BField* new_field = 0;
3598 BRow* new_row = 0;
3599 BColumn* new_column = 0;
3600 BRect new_rect;
3602 if (position.y >= 0) {
3603 if (position.x >= 0) {
3604 float x = 0;
3605 for (int32 c = 0; c < fMasterView->CountColumns(); c++) {
3606 new_column = fMasterView->ColumnAt(c);
3607 if (!new_column->IsVisible())
3608 continue;
3609 if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x)
3610 + new_column->Width() >= position.x) {
3611 if (new_column->WantsEvents()) {
3612 new_field = row->GetField(c);
3613 new_row = row;
3614 FindRect(new_row,&new_rect);
3615 new_rect.left = MAX(kLeftMargin,
3616 fMasterView->LatchWidth()) + x;
3617 new_rect.right = new_rect.left
3618 + new_column->Width() - 1;
3619 handle_field = true;
3621 break;
3623 x += new_column->Width();
3628 // Handle mouse down
3629 if (handle_field) {
3630 fMouseDown = true;
3631 fFieldRect = new_rect;
3632 fCurrentColumn = new_column;
3633 fCurrentRow = new_row;
3634 fCurrentField = new_field;
3635 fCurrentCode = B_INSIDE_VIEW;
3636 BMessage* message = Window()->CurrentMessage();
3637 int32 buttons = 1;
3638 message->FindInt32("buttons", &buttons);
3639 fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
3640 fCurrentField, fFieldRect, position, buttons);
3643 if (!fEditMode) {
3645 fTargetRow = row;
3646 fTargetRowTop = rowTop;
3647 FindVisibleRect(fFocusRow, &fFocusRowRect);
3649 float leftWidgetBoundry = indent * kOutlineLevelIndent
3650 + MAX(kLeftMargin, fMasterView->LatchWidth())
3651 - fMasterView->LatchWidth();
3652 fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
3653 + fMasterView->LatchWidth(), rowTop + row->Height());
3654 if (fLatchRect.Contains(position) && row->HasLatch()) {
3655 fCurrentState = LATCH_CLICKED;
3656 if (fTargetRow->fNextSelected != 0)
3657 SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3658 else
3659 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3661 FillRect(fLatchRect);
3662 if (fLatchRect.Contains(position)) {
3663 fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3664 row);
3665 } else {
3666 fMasterView->DrawLatch(this, fLatchRect,
3667 fTargetRow->fIsExpanded ? B_OPEN_LATCH
3668 : B_CLOSED_LATCH, row);
3670 } else {
3671 Invalidate(fFocusRowRect);
3672 fFocusRow = fTargetRow;
3673 FindVisibleRect(fFocusRow, &fFocusRowRect);
3675 ASSERT(fTargetRow != 0);
3677 if ((modifiers() & B_CONTROL_KEY) == 0)
3678 DeselectAll();
3680 if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
3681 && fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
3682 SelectRange(fFirstSelectedItem, fTargetRow);
3684 else {
3685 if (fTargetRow->fNextSelected != 0) {
3686 // Unselect row
3687 fTargetRow->fNextSelected->fPrevSelected
3688 = fTargetRow->fPrevSelected;
3689 fTargetRow->fPrevSelected->fNextSelected
3690 = fTargetRow->fNextSelected;
3691 fTargetRow->fPrevSelected = 0;
3692 fTargetRow->fNextSelected = 0;
3693 fFirstSelectedItem = NULL;
3694 } else {
3695 // Select row
3696 if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3697 DeselectAll();
3699 fTargetRow->fNextSelected
3700 = fSelectionListDummyHead.fNextSelected;
3701 fTargetRow->fPrevSelected
3702 = &fSelectionListDummyHead;
3703 fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
3704 fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
3705 fFirstSelectedItem = fTargetRow;
3708 Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
3709 fVisibleRect.right,
3710 fTargetRowTop + fTargetRow->Height()));
3713 fCurrentState = ROW_CLICKED;
3714 if (fLastSelectedItem != fTargetRow)
3715 reset_click_count = true;
3716 fLastSelectedItem = fTargetRow;
3717 fMasterView->SelectionChanged();
3722 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
3723 B_NO_POINTER_HISTORY);
3725 } else if (fFocusRow != 0) {
3726 // User clicked in open space, unhighlight focus row.
3727 FindVisibleRect(fFocusRow, &fFocusRowRect);
3728 fFocusRow = 0;
3729 Invalidate(fFocusRowRect);
3732 // We stash the click counts here because the 'clicks' field
3733 // is not in the CurrentMessage() when MouseUp is called... ;(
3734 if (reset_click_count)
3735 fClickCount = 1;
3736 else
3737 Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
3738 fClickPoint = position;
3743 void
3744 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
3745 const BMessage* /*dragMessage*/)
3747 if (!fMouseDown) {
3748 // Update fCurrentField
3749 bool handle_field = false;
3750 BField* new_field = 0;
3751 BRow* new_row = 0;
3752 BColumn* new_column = 0;
3753 BRect new_rect(0,0,0,0);
3754 if (position.y >=0 ) {
3755 float top;
3756 int32 indent;
3757 BRow* row = FindRow(position.y, &indent, &top);
3758 if (row && position.x >=0 ) {
3759 float x=0;
3760 for (int32 c=0;c<fMasterView->CountColumns();c++) {
3761 new_column = fMasterView->ColumnAt(c);
3762 if (!new_column->IsVisible())
3763 continue;
3764 if ((MAX(kLeftMargin,
3765 fMasterView->LatchWidth()) + x) + new_column->Width()
3766 > position.x) {
3768 if(new_column->WantsEvents()) {
3769 new_field = row->GetField(c);
3770 new_row = row;
3771 FindRect(new_row,&new_rect);
3772 new_rect.left = MAX(kLeftMargin,
3773 fMasterView->LatchWidth()) + x;
3774 new_rect.right = new_rect.left
3775 + new_column->Width() - 1;
3776 handle_field = true;
3778 break;
3780 x += new_column->Width();
3785 // Handle mouse moved
3786 if (handle_field) {
3787 if (new_field != fCurrentField) {
3788 if (fCurrentField) {
3789 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3790 fCurrentField, fFieldRect, position, 0,
3791 fCurrentCode = B_EXITED_VIEW);
3793 fCurrentColumn = new_column;
3794 fCurrentRow = new_row;
3795 fCurrentField = new_field;
3796 fFieldRect = new_rect;
3797 if (fCurrentField) {
3798 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3799 fCurrentField, fFieldRect, position, 0,
3800 fCurrentCode = B_ENTERED_VIEW);
3802 } else {
3803 if (fCurrentField) {
3804 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3805 fCurrentField, fFieldRect, position, 0,
3806 fCurrentCode = B_INSIDE_VIEW);
3809 } else {
3810 if (fCurrentField) {
3811 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3812 fCurrentField, fFieldRect, position, 0,
3813 fCurrentCode = B_EXITED_VIEW);
3814 fCurrentField = 0;
3815 fCurrentColumn = 0;
3816 fCurrentRow = 0;
3819 } else {
3820 if (fCurrentField) {
3821 if (fFieldRect.Contains(position)) {
3822 if (fCurrentCode == B_OUTSIDE_VIEW
3823 || fCurrentCode == B_EXITED_VIEW) {
3824 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3825 fCurrentField, fFieldRect, position, 1,
3826 fCurrentCode = B_ENTERED_VIEW);
3827 } else {
3828 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3829 fCurrentField, fFieldRect, position, 1,
3830 fCurrentCode = B_INSIDE_VIEW);
3832 } else {
3833 if (fCurrentCode == B_INSIDE_VIEW
3834 || fCurrentCode == B_ENTERED_VIEW) {
3835 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3836 fCurrentField, fFieldRect, position, 1,
3837 fCurrentCode = B_EXITED_VIEW);
3838 } else {
3839 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3840 fCurrentField, fFieldRect, position, 1,
3841 fCurrentCode = B_OUTSIDE_VIEW);
3847 if (!fEditMode) {
3849 switch (fCurrentState) {
3850 case LATCH_CLICKED:
3851 if (fTargetRow->fNextSelected != 0)
3852 SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3853 else
3854 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3856 FillRect(fLatchRect);
3857 if (fLatchRect.Contains(position)) {
3858 fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3859 fTargetRow);
3860 } else {
3861 fMasterView->DrawLatch(this, fLatchRect,
3862 fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
3863 fTargetRow);
3865 break;
3867 case ROW_CLICKED:
3868 if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
3869 || abs((int)(position.y - fClickPoint.y))
3870 > kRowDragSensitivity) {
3871 fCurrentState = DRAGGING_ROWS;
3872 fMasterView->InitiateDrag(fClickPoint,
3873 fTargetRow->fNextSelected != 0);
3875 break;
3877 case DRAGGING_ROWS:
3878 #if 0
3879 // falls through...
3880 #else
3881 if (fTrackMouse /*&& message*/) {
3882 if (fVisibleRect.Contains(position)) {
3883 float top;
3884 int32 indent;
3885 BRow* target = FindRow(position.y, &indent, &top);
3886 if (target)
3887 SetFocusRow(target, true);
3890 break;
3891 #endif
3893 default: {
3895 if (fTrackMouse /*&& message*/) {
3896 // Draw a highlight line...
3897 if (fVisibleRect.Contains(position)) {
3898 float top;
3899 int32 indent;
3900 BRow* target = FindRow(position.y, &indent, &top);
3901 if (target == fRollOverRow)
3902 break;
3903 if (fRollOverRow) {
3904 BRect rect;
3905 FindRect(fRollOverRow, &rect);
3906 Invalidate(rect);
3908 fRollOverRow = target;
3909 #if 0
3910 SetFocusRow(fRollOverRow,false);
3911 #else
3912 PushState();
3913 SetDrawingMode(B_OP_BLEND);
3914 SetHighColor(255, 255, 255, 255);
3915 BRect rect;
3916 FindRect(fRollOverRow, &rect);
3917 rect.bottom -= 1.0;
3918 FillRect(rect);
3919 PopState();
3920 #endif
3921 } else {
3922 if (fRollOverRow) {
3923 BRect rect;
3924 FindRect(fRollOverRow, &rect);
3925 Invalidate(rect);
3926 fRollOverRow = NULL;
3936 void
3937 OutlineView::MouseUp(BPoint position)
3939 if (fCurrentField) {
3940 fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
3941 fMouseDown = false;
3944 if (fEditMode)
3945 return;
3947 switch (fCurrentState) {
3948 case LATCH_CLICKED:
3949 if (fLatchRect.Contains(position)) {
3950 fMasterView->ExpandOrCollapse(fTargetRow,
3951 !fTargetRow->fIsExpanded);
3954 Invalidate(fLatchRect);
3955 fCurrentState = INACTIVE;
3956 break;
3958 case ROW_CLICKED:
3959 if (fClickCount > 1
3960 && abs((int)fClickPoint.x - (int)position.x)
3961 < kDoubleClickMoveSensitivity
3962 && abs((int)fClickPoint.y - (int)position.y)
3963 < kDoubleClickMoveSensitivity) {
3964 fMasterView->ItemInvoked();
3966 fCurrentState = INACTIVE;
3967 break;
3969 case DRAGGING_ROWS:
3970 fCurrentState = INACTIVE;
3971 // Falls through
3973 default:
3974 if (fDropHighlightY != -1) {
3975 InvertRect(BRect(0,
3976 fDropHighlightY - kDropHighlightLineHeight / 2,
3977 1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3978 // Erase the old target line
3979 fDropHighlightY = -1;
3985 void
3986 OutlineView::MessageReceived(BMessage* message)
3988 if (message->WasDropped()) {
3989 fMasterView->MessageDropped(message,
3990 ConvertFromScreen(message->DropPoint()));
3991 } else {
3992 BView::MessageReceived(message);
3997 void
3998 OutlineView::ChangeFocusRow(bool up, bool updateSelection,
3999 bool addToCurrentSelection)
4001 int32 indent;
4002 float top;
4003 float newRowPos = 0;
4004 float verticalScroll = 0;
4006 if (fFocusRow) {
4007 // A row currently has the focus, get information about it
4008 newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
4009 if (newRowPos < fVisibleRect.top + 20)
4010 verticalScroll = newRowPos - 20;
4011 else if (newRowPos > fVisibleRect.bottom - 20)
4012 verticalScroll = newRowPos - fVisibleRect.Height() + 20;
4013 } else
4014 newRowPos = fVisibleRect.top + 2;
4015 // no row is currently focused, set this to the top of the window
4016 // so we will select the first visible item in the list.
4018 BRow* newRow = FindRow(newRowPos, &indent, &top);
4019 if (newRow) {
4020 if (fFocusRow) {
4021 fFocusRowRect.right = 10000;
4022 Invalidate(fFocusRowRect);
4024 BRow* oldFocusRow = fFocusRow;
4025 fFocusRow = newRow;
4026 fFocusRowRect.top = top;
4027 fFocusRowRect.left = 0;
4028 fFocusRowRect.right = 10000;
4029 fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
4030 Invalidate(fFocusRowRect);
4032 if (updateSelection) {
4033 if (!addToCurrentSelection
4034 || fSelectionMode == B_SINGLE_SELECTION_LIST) {
4035 DeselectAll();
4038 // if the focus row isn't selected, add it to the selection
4039 if (fFocusRow->fNextSelected == 0) {
4040 fFocusRow->fNextSelected
4041 = fSelectionListDummyHead.fNextSelected;
4042 fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4043 fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4044 fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4045 } else if (oldFocusRow != NULL
4046 && fSelectionListDummyHead.fNextSelected == oldFocusRow
4047 && (((IndexOf(oldFocusRow->fNextSelected)
4048 < IndexOf(oldFocusRow)) == up)
4049 || fFocusRow == oldFocusRow->fNextSelected)) {
4050 // if the focus row is selected, if:
4051 // 1. the previous focus row is last in the selection
4052 // 2a. the next selected row is now the focus row
4053 // 2b. or the next selected row is beyond the focus row
4054 // in the move direction
4055 // then deselect the previous focus row
4056 fSelectionListDummyHead.fNextSelected
4057 = oldFocusRow->fNextSelected;
4058 if (fSelectionListDummyHead.fNextSelected != NULL) {
4059 fSelectionListDummyHead.fNextSelected->fPrevSelected
4060 = &fSelectionListDummyHead;
4061 oldFocusRow->fNextSelected = NULL;
4063 oldFocusRow->fPrevSelected = NULL;
4066 fLastSelectedItem = fFocusRow;
4068 } else
4069 Invalidate(fFocusRowRect);
4071 if (verticalScroll != 0) {
4072 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4073 float min, max;
4074 vScrollBar->GetRange(&min, &max);
4075 if (verticalScroll < min)
4076 verticalScroll = min;
4077 else if (verticalScroll > max)
4078 verticalScroll = max;
4080 vScrollBar->SetValue(verticalScroll);
4083 if (newRow && updateSelection)
4084 fMasterView->SelectionChanged();
4088 void
4089 OutlineView::MoveFocusToVisibleRect()
4091 fFocusRow = 0;
4092 ChangeFocusRow(true, true, false);
4096 BRow*
4097 OutlineView::CurrentSelection(BRow* lastSelected) const
4099 BRow* row;
4100 if (lastSelected == 0)
4101 row = fSelectionListDummyHead.fNextSelected;
4102 else
4103 row = lastSelected->fNextSelected;
4106 if (row == &fSelectionListDummyHead)
4107 row = 0;
4109 return row;
4113 void
4114 OutlineView::ToggleFocusRowSelection(bool selectRange)
4116 if (fFocusRow == 0)
4117 return;
4119 if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
4120 SelectRange(fLastSelectedItem, fFocusRow);
4121 else {
4122 if (fFocusRow->fNextSelected != 0) {
4123 // Unselect row
4124 fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
4125 fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
4126 fFocusRow->fPrevSelected = 0;
4127 fFocusRow->fNextSelected = 0;
4128 } else {
4129 // Select row
4130 if (fSelectionMode == B_SINGLE_SELECTION_LIST)
4131 DeselectAll();
4133 fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
4134 fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4135 fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4136 fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4140 fLastSelectedItem = fFocusRow;
4141 fMasterView->SelectionChanged();
4142 Invalidate(fFocusRowRect);
4146 void
4147 OutlineView::ToggleFocusRowOpen()
4149 if (fFocusRow)
4150 fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
4154 void
4155 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
4157 // TODO: Could use CopyBits here to speed things up.
4159 if (parentRow == NULL)
4160 return;
4162 if (parentRow->fIsExpanded == expand)
4163 return;
4165 parentRow->fIsExpanded = expand;
4167 BRect parentRect;
4168 if (FindRect(parentRow, &parentRect)) {
4169 // Determine my new height
4170 float subTreeHeight = 0.0;
4171 if (parentRow->fIsExpanded)
4172 for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4173 iterator.CurrentRow();
4174 iterator.GoToNext()
4177 subTreeHeight += iterator.CurrentRow()->Height()+1;
4179 else
4180 for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4181 iterator.CurrentRow();
4182 iterator.GoToNext()
4185 subTreeHeight -= iterator.CurrentRow()->Height()+1;
4187 fItemsHeight += subTreeHeight;
4189 // Adjust focus row if necessary.
4190 if (FindRect(fFocusRow, &fFocusRowRect) == false) {
4191 // focus row is in a subtree that has collapsed,
4192 // move it up to the parent.
4193 fFocusRow = parentRow;
4194 FindRect(fFocusRow, &fFocusRowRect);
4197 Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
4198 fVisibleRect.bottom));
4199 FixScrollBar(false);
4203 void
4204 OutlineView::RemoveRow(BRow* row)
4206 if (row == NULL)
4207 return;
4209 BRow* parentRow;
4210 bool parentIsVisible;
4211 FindParent(row, &parentRow, &parentIsVisible);
4212 // NOTE: This could be a root row without a parent, in which case
4213 // it is always visible, though.
4215 // Adjust height for the visible sub-tree that is going to be removed.
4216 float subTreeHeight = 0.0f;
4217 if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4218 // The row itself is visible at least.
4219 subTreeHeight = row->Height() + 1;
4220 if (row->fIsExpanded) {
4221 // Adjust for the height of visible sub-items as well.
4222 // (By default, the iterator follows open branches only.)
4223 for (RecursiveOutlineIterator iterator(row->fChildList);
4224 iterator.CurrentRow(); iterator.GoToNext())
4225 subTreeHeight += iterator.CurrentRow()->Height() + 1;
4227 BRect invalid;
4228 if (FindRect(row, &invalid)) {
4229 invalid.bottom = Bounds().bottom;
4230 if (invalid.IsValid())
4231 Invalidate(invalid);
4235 fItemsHeight -= subTreeHeight;
4237 FixScrollBar(false);
4238 int32 indent = 0;
4239 float top = 0.0;
4240 if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
4241 // after removing this row, no rows are actually visible any more,
4242 // force a scroll to make them visible again
4243 if (fItemsHeight > fVisibleRect.Height())
4244 ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
4245 else
4246 ScrollBy(0.0, -Bounds().top);
4248 if (parentRow != NULL) {
4249 parentRow->fChildList->RemoveItem(row);
4250 if (parentRow->fChildList->CountItems() == 0) {
4251 delete parentRow->fChildList;
4252 parentRow->fChildList = 0;
4253 // It was the last child row of the parent, which also means the
4254 // latch disappears.
4255 BRect parentRowRect;
4256 if (parentIsVisible && FindRect(parentRow, &parentRowRect))
4257 Invalidate(parentRowRect);
4259 } else
4260 fRows.RemoveItem(row);
4262 // Adjust focus row if necessary.
4263 if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
4264 // focus row is in a subtree that is gone, move it up to the parent.
4265 fFocusRow = parentRow;
4266 if (fFocusRow)
4267 FindRect(fFocusRow, &fFocusRowRect);
4270 // Remove this from the selection if necessary
4271 if (row->fNextSelected != 0) {
4272 row->fNextSelected->fPrevSelected = row->fPrevSelected;
4273 row->fPrevSelected->fNextSelected = row->fNextSelected;
4274 row->fPrevSelected = 0;
4275 row->fNextSelected = 0;
4276 fMasterView->SelectionChanged();
4279 fCurrentColumn = 0;
4280 fCurrentRow = 0;
4281 fCurrentField = 0;
4285 BRowContainer*
4286 OutlineView::RowList()
4288 return &fRows;
4292 void
4293 OutlineView::UpdateRow(BRow* row)
4295 if (row) {
4296 // Determine if this row has changed its sort order
4297 BRow* parentRow = NULL;
4298 bool parentIsVisible = false;
4299 FindParent(row, &parentRow, &parentIsVisible);
4301 BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
4303 if(list) {
4304 int32 rowIndex = list->IndexOf(row);
4305 ASSERT(rowIndex >= 0);
4306 ASSERT(list->ItemAt(rowIndex) == row);
4308 bool rowMoved = false;
4309 if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
4310 rowMoved = true;
4312 if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
4313 row) < 0)
4314 rowMoved = true;
4316 if (rowMoved) {
4317 // Sort location of this row has changed.
4318 // Remove and re-add in the right spot
4319 SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
4320 } else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4321 BRect invalidRect;
4322 if (FindVisibleRect(row, &invalidRect))
4323 Invalidate(invalidRect);
4330 void
4331 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
4333 if (!row)
4334 return;
4336 row->fParent = parentRow;
4338 if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) {
4339 // Ignore index here.
4340 if (parentRow) {
4341 if (parentRow->fChildList == NULL)
4342 parentRow->fChildList = new BRowContainer;
4344 AddSorted(parentRow->fChildList, row);
4345 } else
4346 AddSorted(&fRows, row);
4347 } else {
4348 // Note, a -1 index implies add to end if sorting is not enabled
4349 if (parentRow) {
4350 if (parentRow->fChildList == 0)
4351 parentRow->fChildList = new BRowContainer;
4353 if (Index < 0 || Index > parentRow->fChildList->CountItems())
4354 parentRow->fChildList->AddItem(row);
4355 else
4356 parentRow->fChildList->AddItem(row, Index);
4357 } else {
4358 if (Index < 0 || Index >= fRows.CountItems())
4359 fRows.AddItem(row);
4360 else
4361 fRows.AddItem(row, Index);
4365 if (parentRow == 0 || parentRow->fIsExpanded)
4366 fItemsHeight += row->Height() + 1;
4368 FixScrollBar(false);
4370 BRect newRowRect;
4371 bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
4373 if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
4374 // The focus row has moved.
4375 Invalidate(fFocusRowRect);
4376 FindRect(fFocusRow, &fFocusRowRect);
4377 Invalidate(fFocusRowRect);
4380 if (newRowIsInOpenBranch) {
4381 if (fCurrentState == INACTIVE) {
4382 if (newRowRect.bottom < fVisibleRect.top) {
4383 // The new row is totally above the current viewport, move
4384 // everything down and redraw the first line.
4385 BRect source(fVisibleRect);
4386 BRect dest(fVisibleRect);
4387 source.bottom -= row->Height() + 1;
4388 dest.top += row->Height() + 1;
4389 CopyBits(source, dest);
4390 Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
4391 fVisibleRect.top + newRowRect.Height()));
4392 } else if (newRowRect.top < fVisibleRect.bottom) {
4393 // New item is somewhere in the current region. Scroll everything
4394 // beneath it down and invalidate just the new row rect.
4395 BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
4396 fVisibleRect.bottom - newRowRect.Height());
4397 BRect dest(source);
4398 dest.OffsetBy(0, newRowRect.Height() + 1);
4399 CopyBits(source, dest);
4400 Invalidate(newRowRect);
4401 } // otherwise, this is below the currently visible region
4402 } else {
4403 // Adding the item may have caused the item that the user is currently
4404 // selected to move. This would cause annoying drawing and interaction
4405 // bugs, as the position of that item is cached. If this happens, resize
4406 // the scroll bar, then scroll back so the selected item is in view.
4407 BRect targetRect;
4408 if (FindRect(fTargetRow, &targetRect)) {
4409 float delta = targetRect.top - fTargetRowTop;
4410 if (delta != 0) {
4411 // This causes a jump because ScrollBy will copy a chunk of the view.
4412 // Since the actual contents of the view have been offset, we don't
4413 // want this, we just want to change the virtual origin of the window.
4414 // Constrain the clipping region so everything is clipped out so no
4415 // copy occurs.
4417 // xxx this currently doesn't work if the scroll bars aren't enabled.
4418 // everything will still move anyway. A minor annoyance.
4419 BRegion emptyRegion;
4420 ConstrainClippingRegion(&emptyRegion);
4421 PushState();
4422 ScrollBy(0, delta);
4423 PopState();
4424 ConstrainClippingRegion(NULL);
4426 fTargetRowTop += delta;
4427 fClickPoint.y += delta;
4428 fLatchRect.OffsetBy(0, delta);
4434 // If the parent was previously childless, it will need to have a latch
4435 // drawn.
4436 BRect parentRect;
4437 if (parentRow && parentRow->fChildList->CountItems() == 1
4438 && FindVisibleRect(parentRow, &parentRect))
4439 Invalidate(parentRect);
4443 void
4444 OutlineView::FixScrollBar(bool scrollToFit)
4446 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4447 if (vScrollBar) {
4448 if (fItemsHeight > fVisibleRect.Height()) {
4449 float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4450 vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
4452 // If the user is scrolled down too far when making the range smaller, the list
4453 // will jump suddenly, which is undesirable. In this case, don't fix the scroll
4454 // bar here. In ScrollTo, it checks to see if this has occured, and will
4455 // fix the scroll bars sneakily if the user has scrolled up far enough.
4456 if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
4457 vScrollBar->SetRange(0.0, maxScrollBarValue);
4458 vScrollBar->SetSteps(20.0, fVisibleRect.Height());
4460 } else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0)
4461 vScrollBar->SetRange(0.0, 0.0); // disable scroll bar.
4466 void
4467 OutlineView::AddSorted(BRowContainer* list, BRow* row)
4469 if (list && row) {
4470 // Find general vicinity with binary search.
4471 int32 lower = 0;
4472 int32 upper = list->CountItems()-1;
4473 while( lower < upper ) {
4474 int32 middle = lower + (upper-lower+1)/2;
4475 int32 cmp = CompareRows(row, list->ItemAt(middle));
4476 if( cmp < 0 ) upper = middle-1;
4477 else if( cmp > 0 ) lower = middle+1;
4478 else lower = upper = middle;
4481 // At this point, 'upper' and 'lower' at the last found item.
4482 // Arbitrarily use 'upper' and determine the final insertion
4483 // point -- either before or after this item.
4484 if( upper < 0 ) upper = 0;
4485 else if( upper < list->CountItems() ) {
4486 if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
4489 if (upper >= list->CountItems())
4490 list->AddItem(row); // Adding to end.
4491 else
4492 list->AddItem(row, upper); // Insert
4497 int32
4498 OutlineView::CompareRows(BRow* row1, BRow* row2)
4500 int32 itemCount (fSortColumns->CountItems());
4501 if (row1 && row2) {
4502 for (int32 index = 0; index < itemCount; index++) {
4503 BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
4504 int comp = 0;
4505 BField* field1 = (BField*) row1->GetField(column->fFieldID);
4506 BField* field2 = (BField*) row2->GetField(column->fFieldID);
4507 if (field1 && field2)
4508 comp = column->CompareFields(field1, field2);
4510 if (!column->fSortAscending)
4511 comp = -comp;
4513 if (comp != 0)
4514 return comp;
4517 return 0;
4521 void
4522 OutlineView::FrameResized(float width, float height)
4524 fVisibleRect.right = fVisibleRect.left + width;
4525 fVisibleRect.bottom = fVisibleRect.top + height;
4526 FixScrollBar(true);
4527 _inherited::FrameResized(width, height);
4531 void
4532 OutlineView::ScrollTo(BPoint position)
4534 fVisibleRect.OffsetTo(position.x, position.y);
4536 // In FixScrollBar, we might not have been able to change the size of
4537 // the scroll bar because the user was scrolled down too far. Take
4538 // this opportunity to sneak it in if we can.
4539 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4540 float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4541 float min, max;
4542 vScrollBar->GetRange(&min, &max);
4543 if (max != maxScrollBarValue && position.y > maxScrollBarValue)
4544 FixScrollBar(true);
4546 _inherited::ScrollTo(position);
4550 const BRect&
4551 OutlineView::VisibleRect() const
4553 return fVisibleRect;
4557 bool
4558 OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
4560 if (row && _rect) {
4561 float line = 0.0;
4562 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4563 iterator.GoToNext()) {
4564 if (line > fVisibleRect.bottom)
4565 break;
4567 if (iterator.CurrentRow() == row) {
4568 _rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4569 line + row->Height());
4570 return true;
4573 line += iterator.CurrentRow()->Height() + 1;
4576 return false;
4580 bool
4581 OutlineView::FindRect(const BRow* row, BRect* _rect)
4583 float line = 0.0;
4584 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4585 iterator.GoToNext()) {
4586 if (iterator.CurrentRow() == row) {
4587 _rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4588 line + row->Height());
4589 return true;
4592 line += iterator.CurrentRow()->Height() + 1;
4595 return false;
4599 void
4600 OutlineView::ScrollTo(const BRow* row)
4602 BRect rect;
4603 if (FindRect(row, &rect)) {
4604 BRect bounds = Bounds();
4605 if (rect.top < bounds.top)
4606 ScrollTo(BPoint(bounds.left, rect.top));
4607 else if (rect.bottom > bounds.bottom)
4608 ScrollBy(0, rect.bottom - bounds.bottom);
4613 void
4614 OutlineView::DeselectAll()
4616 // Invalidate all selected rows
4617 float line = 0.0;
4618 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4619 iterator.GoToNext()) {
4620 if (line > fVisibleRect.bottom)
4621 break;
4623 BRow* row = iterator.CurrentRow();
4624 if (line + row->Height() > fVisibleRect.top) {
4625 if (row->fNextSelected != 0)
4626 Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
4627 line + row->Height()));
4630 line += row->Height() + 1;
4633 // Set items not selected
4634 while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
4635 BRow* row = fSelectionListDummyHead.fNextSelected;
4636 row->fNextSelected->fPrevSelected = row->fPrevSelected;
4637 row->fPrevSelected->fNextSelected = row->fNextSelected;
4638 row->fNextSelected = 0;
4639 row->fPrevSelected = 0;
4644 BRow*
4645 OutlineView::FocusRow() const
4647 return fFocusRow;
4651 void
4652 OutlineView::SetFocusRow(BRow* row, bool Select)
4654 if (row) {
4655 if (Select)
4656 AddToSelection(row);
4658 if (fFocusRow == row)
4659 return;
4661 Invalidate(fFocusRowRect); // invalidate previous
4663 fTargetRow = fFocusRow = row;
4665 FindVisibleRect(fFocusRow, &fFocusRowRect);
4666 Invalidate(fFocusRowRect); // invalidate current
4668 fFocusRowRect.right = 10000;
4669 fMasterView->SelectionChanged();
4674 bool
4675 OutlineView::SortList(BRowContainer* list, bool isVisible)
4677 if (list) {
4678 // Shellsort
4679 BRow** items
4680 = (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
4681 int32 numItems = list->CountItems();
4682 int h;
4683 for (h = 1; h < numItems / 9; h = 3 * h + 1)
4686 for (;h > 0; h /= 3) {
4687 for (int step = h; step < numItems; step++) {
4688 BRow* temp = items[step];
4689 int i;
4690 for (i = step - h; i >= 0; i -= h) {
4691 if (CompareRows(temp, items[i]) < 0)
4692 items[i + h] = items[i];
4693 else
4694 break;
4697 items[i + h] = temp;
4701 if (isVisible) {
4702 Invalidate();
4704 InvalidateCachedPositions();
4705 int lockCount = Window()->CountLocks();
4706 for (int i = 0; i < lockCount; i++)
4707 Window()->Unlock();
4709 while (lockCount--)
4710 if (!Window()->Lock())
4711 return false; // Window is gone...
4714 return true;
4718 int32
4719 OutlineView::DeepSortThreadEntry(void* _outlineView)
4721 ((OutlineView*) _outlineView)->DeepSort();
4722 return 0;
4726 void
4727 OutlineView::DeepSort()
4729 struct stack_entry {
4730 bool isVisible;
4731 BRowContainer* list;
4732 int32 listIndex;
4733 } stack[kMaxDepth];
4734 int32 stackTop = 0;
4736 stack[stackTop].list = &fRows;
4737 stack[stackTop].isVisible = true;
4738 stack[stackTop].listIndex = 0;
4739 fNumSorted = 0;
4741 if (Window()->Lock() == false)
4742 return;
4744 bool doneSorting = false;
4745 while (!doneSorting && !fSortCancelled) {
4747 stack_entry* currentEntry = &stack[stackTop];
4749 // xxx Can make the invalidate area smaller by finding the rect for the
4750 // parent item and using that as the top of the invalid rect.
4752 bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
4753 if (!haveLock)
4754 return ; // window is gone.
4756 // Fix focus rect.
4757 InvalidateCachedPositions();
4758 if (fCurrentState != INACTIVE)
4759 fCurrentState = INACTIVE; // sorry...
4761 // next list.
4762 bool foundNextList = false;
4763 while (!foundNextList && !fSortCancelled) {
4764 for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
4765 index++) {
4766 BRow* parentRow = currentEntry->list->ItemAt(index);
4767 BRowContainer* childList = parentRow->fChildList;
4768 if (childList != 0) {
4769 currentEntry->listIndex = index + 1;
4770 stackTop++;
4771 ASSERT(stackTop < kMaxDepth);
4772 stack[stackTop].listIndex = 0;
4773 stack[stackTop].list = childList;
4774 stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
4775 foundNextList = true;
4776 break;
4780 if (!foundNextList) {
4781 // back up
4782 if (--stackTop < 0) {
4783 doneSorting = true;
4784 break;
4787 currentEntry = &stack[stackTop];
4792 Window()->Unlock();
4796 void
4797 OutlineView::StartSorting()
4799 // If this view is not yet attached to a window, don't start a sort thread!
4800 if (Window() == NULL)
4801 return;
4803 if (fSortThread != B_BAD_THREAD_ID) {
4804 thread_info tinfo;
4805 if (get_thread_info(fSortThread, &tinfo) == B_OK) {
4806 // Unlock window so this won't deadlock (sort thread is probably
4807 // waiting to lock window).
4809 int lockCount = Window()->CountLocks();
4810 for (int i = 0; i < lockCount; i++)
4811 Window()->Unlock();
4813 fSortCancelled = true;
4814 int32 status;
4815 wait_for_thread(fSortThread, &status);
4817 while (lockCount--)
4818 if (!Window()->Lock())
4819 return ; // Window is gone...
4823 fSortCancelled = false;
4824 fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
4825 resume_thread(fSortThread);
4829 void
4830 OutlineView::SelectRange(BRow* start, BRow* end)
4832 if (!start || !end)
4833 return;
4835 if (start == end) // start is always selected when this is called
4836 return;
4838 RecursiveOutlineIterator iterator(&fRows, false);
4839 while (iterator.CurrentRow() != 0) {
4840 if (iterator.CurrentRow() == end) {
4841 // reverse selection, swap to fix special case
4842 BRow* temp = start;
4843 start = end;
4844 end = temp;
4845 break;
4846 } else if (iterator.CurrentRow() == start)
4847 break;
4849 iterator.GoToNext();
4852 while (true) {
4853 BRow* row = iterator.CurrentRow();
4854 if (row) {
4855 if (row->fNextSelected == 0) {
4856 row->fNextSelected = fSelectionListDummyHead.fNextSelected;
4857 row->fPrevSelected = &fSelectionListDummyHead;
4858 row->fNextSelected->fPrevSelected = row;
4859 row->fPrevSelected->fNextSelected = row;
4861 } else
4862 break;
4864 if (row == end)
4865 break;
4867 iterator.GoToNext();
4870 Invalidate(); // xxx make invalidation smaller
4874 bool
4875 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
4877 bool result = false;
4878 if (row != NULL && outParent != NULL) {
4879 *outParent = row->fParent;
4881 if (outParentIsVisible != NULL) {
4882 // Walk up the parent chain to determine if this row is visible
4883 *outParentIsVisible = true;
4884 for (BRow* currentRow = row->fParent; currentRow != NULL;
4885 currentRow = currentRow->fParent) {
4886 if (!currentRow->fIsExpanded) {
4887 *outParentIsVisible = false;
4888 break;
4893 result = *outParent != NULL;
4896 return result;
4900 int32
4901 OutlineView::IndexOf(BRow* row)
4903 if (row) {
4904 if (row->fParent == 0)
4905 return fRows.IndexOf(row);
4907 ASSERT(row->fParent->fChildList);
4908 return row->fParent->fChildList->IndexOf(row);
4911 return B_ERROR;
4915 void
4916 OutlineView::InvalidateCachedPositions()
4918 if (fFocusRow)
4919 FindRect(fFocusRow, &fFocusRowRect);
4923 float
4924 OutlineView::GetColumnPreferredWidth(BColumn* column)
4926 float preferred = 0.0;
4927 for (RecursiveOutlineIterator iterator(&fRows); BRow* row =
4928 iterator.CurrentRow(); iterator.GoToNext()) {
4929 BField* field = row->GetField(column->fFieldID);
4930 if (field) {
4931 float width = column->GetPreferredWidth(field, this)
4932 + iterator.CurrentLevel() * kOutlineLevelIndent;
4933 preferred = max_c(preferred, width);
4937 BString name;
4938 column->GetColumnName(&name);
4939 preferred = max_c(preferred, StringWidth(name));
4941 // Constrain to preferred width. This makes the method do a little
4942 // more than asked, but it's for convenience.
4943 if (preferred < column->MinWidth())
4944 preferred = column->MinWidth();
4945 else if (preferred > column->MaxWidth())
4946 preferred = column->MaxWidth();
4948 return preferred;
4952 // #pragma mark -
4955 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
4956 bool openBranchesOnly)
4958 fStackIndex(0),
4959 fCurrentListIndex(0),
4960 fCurrentListDepth(0),
4961 fOpenBranchesOnly(openBranchesOnly)
4963 if (list == 0 || list->CountItems() == 0)
4964 fCurrentList = 0;
4965 else
4966 fCurrentList = list;
4970 BRow*
4971 RecursiveOutlineIterator::CurrentRow() const
4973 if (fCurrentList == 0)
4974 return 0;
4976 return fCurrentList->ItemAt(fCurrentListIndex);
4980 void
4981 RecursiveOutlineIterator::GoToNext()
4983 if (fCurrentList == 0)
4984 return;
4985 if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
4986 fCurrentList = 0;
4987 return;
4990 BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
4991 if(currentRow) {
4992 if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
4993 && currentRow->fChildList->CountItems() > 0) {
4994 // Visit child.
4995 // Put current list on the stack if it needs to be revisited.
4996 if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
4997 fStack[fStackIndex].fRowSet = fCurrentList;
4998 fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
4999 fStack[fStackIndex].fDepth = fCurrentListDepth;
5000 fStackIndex++;
5003 fCurrentList = currentRow->fChildList;
5004 fCurrentListIndex = 0;
5005 fCurrentListDepth++;
5006 } else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
5007 fCurrentListIndex++; // next item in current list
5008 else if (--fStackIndex >= 0) {
5009 fCurrentList = fStack[fStackIndex].fRowSet;
5010 fCurrentListIndex = fStack[fStackIndex].fIndex;
5011 fCurrentListDepth = fStack[fStackIndex].fDepth;
5012 } else
5013 fCurrentList = 0;
5018 int32
5019 RecursiveOutlineIterator::CurrentLevel() const
5021 return fCurrentListDepth;