tcp: Fix 64 bit build with debugging features enabled.
[haiku.git] / src / kits / interface / ColumnListView.cpp
blob10f2d58996e4ef9030426e6e8af75b3f60f25897
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 "ColorTools.h"
72 #include "ObjectList.h"
75 #define DOUBLE_BUFFERED_COLUMN_RESIZE 1
76 #define SMART_REDRAW 1
77 #define DRAG_TITLE_OUTLINE 1
78 #define CONSTRAIN_CLIPPING_REGION 1
79 #define LOWER_SCROLLBAR 0
82 namespace BPrivate {
84 static const unsigned char kDownSortArrow8x8[] = {
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 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
89 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
90 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
91 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
92 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff
95 static const unsigned char kUpSortArrow8x8[] = {
96 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
97 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
98 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
99 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
100 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
101 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
102 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
103 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff
106 static const unsigned char kDownSortArrow8x8Invert[] = {
107 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
108 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
109 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
110 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
111 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
112 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
113 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
114 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff
117 static const unsigned char kUpSortArrow8x8Invert[] = {
118 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff,
119 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
120 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
121 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
122 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
123 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
124 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
125 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
128 static const float kTintedLineTint = 1.04;
130 static const float kMinTitleHeight = 16.0;
131 static const float kMinRowHeight = 16.0;
132 static const float kTitleSpacing = 1.4;
133 static const float kRowSpacing = 1.4;
134 static const float kLatchWidth = 15.0;
136 static const int32 kMaxDepth = 1024;
137 static const float kLeftMargin = kLatchWidth;
138 static const float kRightMargin = 8;
139 static const float kOutlineLevelIndent = kLatchWidth;
140 static const float kColumnResizeAreaWidth = 10.0;
141 static const float kRowDragSensitivity = 5.0;
142 static const float kDoubleClickMoveSensitivity = 4.0;
143 static const float kSortIndicatorWidth = 9.0;
144 static const float kDropHighlightLineHeight = 2.0;
146 static const uint32 kToggleColumn = 'BTCL';
148 class BRowContainer : public BObjectList<BRow>
152 class TitleView : public BView {
153 typedef BView _inherited;
154 public:
155 TitleView(BRect frame, OutlineView* outlineView,
156 BList* visibleColumns, BList* sortColumns,
157 BColumnListView* masterView,
158 uint32 resizingMode);
159 virtual ~TitleView();
161 void ColumnAdded(BColumn* column);
162 void ColumnResized(BColumn* column, float oldWidth);
163 void SetColumnVisible(BColumn* column, bool visible);
165 virtual void Draw(BRect updateRect);
166 virtual void ScrollTo(BPoint where);
167 virtual void MessageReceived(BMessage* message);
168 virtual void MouseDown(BPoint where);
169 virtual void MouseMoved(BPoint where, uint32 transit,
170 const BMessage* dragMessage);
171 virtual void MouseUp(BPoint where);
172 virtual void FrameResized(float width, float height);
174 void MoveColumn(BColumn* column, int32 index);
175 void SetColumnFlags(column_flags flags);
177 void SetEditMode(bool state)
178 { fEditMode = state; }
180 float MarginWidth() const;
182 private:
183 void GetTitleRect(BColumn* column, BRect* _rect);
184 int32 FindColumn(BPoint where, float* _leftEdge);
185 void FixScrollBar(bool scrollToFit);
186 void DragSelectedColumn(BPoint where);
187 void ResizeSelectedColumn(BPoint where,
188 bool preferred = false);
189 void ComputeDragBoundries(BColumn* column,
190 BPoint where);
191 void DrawTitle(BView* view, BRect frame,
192 BColumn* column, bool depressed);
194 float _VirtualWidth() const;
196 OutlineView* fOutlineView;
197 BList* fColumns;
198 BList* fSortColumns;
199 // float fColumnsWidth;
200 BRect fVisibleRect;
202 #if DOUBLE_BUFFERED_COLUMN_RESIZE
203 BBitmap* fDrawBuffer;
204 BView* fDrawBufferView;
205 #endif
207 enum {
208 INACTIVE,
209 RESIZING_COLUMN,
210 PRESSING_COLUMN,
211 DRAG_COLUMN_INSIDE_TITLE,
212 DRAG_COLUMN_OUTSIDE_TITLE
213 } fCurrentState;
215 BPopUpMenu* fColumnPop;
216 BColumnListView* fMasterView;
217 bool fEditMode;
218 int32 fColumnFlags;
220 // State information for resizing/dragging
221 BColumn* fSelectedColumn;
222 BRect fSelectedColumnRect;
223 bool fResizingFirstColumn;
224 BPoint fClickPoint; // offset within cell
225 float fLeftDragBoundry;
226 float fRightDragBoundry;
227 BPoint fCurrentDragPosition;
230 BBitmap* fUpSortArrow;
231 BBitmap* fDownSortArrow;
233 BCursor* fResizeCursor;
234 BCursor* fMinResizeCursor;
235 BCursor* fMaxResizeCursor;
236 BCursor* fColumnMoveCursor;
240 class OutlineView : public BView {
241 typedef BView _inherited;
242 public:
243 OutlineView(BRect, BList* visibleColumns,
244 BList* sortColumns,
245 BColumnListView* listView);
246 virtual ~OutlineView();
248 virtual void Draw(BRect);
249 const BRect& VisibleRect() const;
251 void RedrawColumn(BColumn* column, float leftEdge,
252 bool isFirstColumn);
253 void StartSorting();
254 float GetColumnPreferredWidth(BColumn* column);
256 void AddRow(BRow*, int32 index, BRow* TheRow);
257 BRow* CurrentSelection(BRow* lastSelected) const;
258 void ToggleFocusRowSelection(bool selectRange);
259 void ToggleFocusRowOpen();
260 void ChangeFocusRow(bool up, bool updateSelection,
261 bool addToCurrentSelection);
262 void MoveFocusToVisibleRect();
263 void ExpandOrCollapse(BRow* parent, bool expand);
264 void RemoveRow(BRow*);
265 BRowContainer* RowList();
266 void UpdateRow(BRow*);
267 bool FindParent(BRow* row, BRow** _parent,
268 bool* _isVisible);
269 int32 IndexOf(BRow* row);
270 void Deselect(BRow*);
271 void AddToSelection(BRow*);
272 void DeselectAll();
273 BRow* FocusRow() const;
274 void SetFocusRow(BRow* row, bool select);
275 BRow* FindRow(float ypos, int32* _indent,
276 float* _top);
277 bool FindRect(const BRow* row, BRect* _rect);
278 void ScrollTo(const BRow* row);
280 void Clear();
281 void SetSelectionMode(list_view_type type);
282 list_view_type SelectionMode() const;
283 void SetMouseTrackingEnabled(bool);
284 void FixScrollBar(bool scrollToFit);
285 void SetEditMode(bool state)
286 { fEditMode = state; }
288 virtual void FrameResized(float width, float height);
289 virtual void ScrollTo(BPoint where);
290 virtual void MouseDown(BPoint where);
291 virtual void MouseMoved(BPoint where, uint32 transit,
292 const BMessage* dragMessage);
293 virtual void MouseUp(BPoint where);
294 virtual void MessageReceived(BMessage* message);
296 private:
297 bool SortList(BRowContainer* list, bool isVisible);
298 static int32 DeepSortThreadEntry(void* outlineView);
299 void DeepSort();
300 void SelectRange(BRow* start, BRow* end);
301 int32 CompareRows(BRow* row1, BRow* row2);
302 void AddSorted(BRowContainer* list, BRow* row);
303 void RecursiveDeleteRows(BRowContainer* list,
304 bool owner);
305 void InvalidateCachedPositions();
306 bool FindVisibleRect(BRow* row, BRect* _rect);
308 BList* fColumns;
309 BList* fSortColumns;
310 float fItemsHeight;
311 BRowContainer fRows;
312 BRect fVisibleRect;
314 #if DOUBLE_BUFFERED_COLUMN_RESIZE
315 BBitmap* fDrawBuffer;
316 BView* fDrawBufferView;
317 #endif
319 BRow* fFocusRow;
320 BRect fFocusRowRect;
321 BRow* fRollOverRow;
323 BRow fSelectionListDummyHead;
324 BRow* fLastSelectedItem;
325 BRow* fFirstSelectedItem;
327 thread_id fSortThread;
328 int32 fNumSorted;
329 bool fSortCancelled;
331 enum CurrentState {
332 INACTIVE,
333 LATCH_CLICKED,
334 ROW_CLICKED,
335 DRAGGING_ROWS
338 CurrentState fCurrentState;
341 BColumnListView* fMasterView;
342 list_view_type fSelectionMode;
343 bool fTrackMouse;
344 BField* fCurrentField;
345 BRow* fCurrentRow;
346 BColumn* fCurrentColumn;
347 bool fMouseDown;
348 BRect fFieldRect;
349 int32 fCurrentCode;
350 bool fEditMode;
352 // State information for mouse/keyboard interaction
353 BPoint fClickPoint;
354 bool fDragging;
355 int32 fClickCount;
356 BRow* fTargetRow;
357 float fTargetRowTop;
358 BRect fLatchRect;
359 float fDropHighlightY;
361 friend class RecursiveOutlineIterator;
365 class RecursiveOutlineIterator {
366 public:
367 RecursiveOutlineIterator(
368 BRowContainer* container,
369 bool openBranchesOnly = true);
371 BRow* CurrentRow() const;
372 int32 CurrentLevel() const;
373 void GoToNext();
375 private:
376 struct {
377 BRowContainer* fRowSet;
378 int32 fIndex;
379 int32 fDepth;
380 } fStack[kMaxDepth];
382 int32 fStackIndex;
383 BRowContainer* fCurrentList;
384 int32 fCurrentListIndex;
385 int32 fCurrentListDepth;
386 bool fOpenBranchesOnly;
389 } // namespace BPrivate
392 using namespace BPrivate;
395 BField::BField()
400 BField::~BField()
405 // #pragma mark -
408 void
409 BColumn::MouseMoved(BColumnListView* /*parent*/, BRow* /*row*/,
410 BField* /*field*/, BRect /*field_rect*/, BPoint/*point*/,
411 uint32 /*buttons*/, int32 /*code*/)
416 void
417 BColumn::MouseDown(BColumnListView* /*parent*/, BRow* /*row*/,
418 BField* /*field*/, BRect /*field_rect*/, BPoint /*point*/,
419 uint32 /*buttons*/)
424 void
425 BColumn::MouseUp(BColumnListView* /*parent*/, BRow* /*row*/, BField* /*field*/)
430 // #pragma mark -
433 BRow::BRow()
435 fChildList(NULL),
436 fIsExpanded(false),
437 fHeight(std::max(kMinRowHeight,
438 ceilf(be_plain_font->Size() * kRowSpacing))),
439 fNextSelected(NULL),
440 fPrevSelected(NULL),
441 fParent(NULL),
442 fList(NULL)
447 BRow::BRow(float height)
449 fChildList(NULL),
450 fIsExpanded(false),
451 fHeight(height),
452 fNextSelected(NULL),
453 fPrevSelected(NULL),
454 fParent(NULL),
455 fList(NULL)
460 BRow::~BRow()
462 while (true) {
463 BField* field = (BField*) fFields.RemoveItem((int32)0);
464 if (field == 0)
465 break;
467 delete field;
472 bool
473 BRow::HasLatch() const
475 return fChildList != 0;
479 int32
480 BRow::CountFields() const
482 return fFields.CountItems();
486 BField*
487 BRow::GetField(int32 index)
489 return (BField*)fFields.ItemAt(index);
493 const BField*
494 BRow::GetField(int32 index) const
496 return (const BField*)fFields.ItemAt(index);
500 void
501 BRow::SetField(BField* field, int32 logicalFieldIndex)
503 if (fFields.ItemAt(logicalFieldIndex) != 0)
504 delete (BField*)fFields.RemoveItem(logicalFieldIndex);
506 if (NULL != fList) {
507 ValidateField(field, logicalFieldIndex);
508 Invalidate();
511 fFields.AddItem(field, logicalFieldIndex);
515 float
516 BRow::Height() const
518 return fHeight;
522 bool
523 BRow::IsExpanded() const
525 return fIsExpanded;
529 bool
530 BRow::IsSelected() const
532 return fPrevSelected != NULL;
536 void
537 BRow::Invalidate()
539 if (fList != NULL)
540 fList->InvalidateRow(this);
544 void
545 BRow::ValidateFields() const
547 for (int32 i = 0; i < CountFields(); i++)
548 ValidateField(GetField(i), i);
552 void
553 BRow::ValidateField(const BField* field, int32 logicalFieldIndex) const
555 // The Fields may be moved by the user, but the logicalFieldIndexes
556 // do not change, so we need to map them over when checking the
557 // Field types.
558 BColumn* column = NULL;
559 int32 items = fList->CountColumns();
560 for (int32 i = 0 ; i < items; ++i) {
561 column = fList->ColumnAt(i);
562 if(column->LogicalFieldNum() == logicalFieldIndex )
563 break;
566 if (column == NULL) {
567 BString dbmessage("\n\n\tThe parent BColumnListView does not have "
568 "\n\ta BColumn at the logical field index ");
569 dbmessage << logicalFieldIndex << ".\n\n";
570 printf(dbmessage.String());
571 } else {
572 if (!column->AcceptsField(field)) {
573 BString dbmessage("\n\n\tThe BColumn of type ");
574 dbmessage << typeid(*column).name() << "\n\tat logical field index "
575 << logicalFieldIndex << "\n\tdoes not support the field type "
576 << typeid(*field).name() << ".\n\n";
577 debugger(dbmessage.String());
583 // #pragma mark -
586 BColumn::BColumn(float width, float minWidth, float maxWidth, alignment align)
588 fWidth(width),
589 fMinWidth(minWidth),
590 fMaxWidth(maxWidth),
591 fVisible(true),
592 fList(0),
593 fShowHeading(true),
594 fAlignment(align)
599 BColumn::~BColumn()
604 float
605 BColumn::Width() const
607 return fWidth;
611 void
612 BColumn::SetWidth(float width)
614 fWidth = width;
618 float
619 BColumn::MinWidth() const
621 return fMinWidth;
625 float
626 BColumn::MaxWidth() const
628 return fMaxWidth;
632 void
633 BColumn::DrawTitle(BRect, BView*)
638 void
639 BColumn::DrawField(BField*, BRect, BView*)
645 BColumn::CompareFields(BField*, BField*)
647 return 0;
651 void
652 BColumn::GetColumnName(BString* into) const
654 *into = "(Unnamed)";
658 float
659 BColumn::GetPreferredWidth(BField* field, BView* parent) const
661 return fWidth;
665 bool
666 BColumn::IsVisible() const
668 return fVisible;
672 void
673 BColumn::SetVisible(bool visible)
675 if (fList && (fVisible != visible))
676 fList->SetColumnVisible(this, visible);
680 bool
681 BColumn::ShowHeading() const
683 return fShowHeading;
687 void
688 BColumn::SetShowHeading(bool state)
690 fShowHeading = state;
694 alignment
695 BColumn::Alignment() const
697 return fAlignment;
701 void
702 BColumn::SetAlignment(alignment align)
704 fAlignment = align;
708 bool
709 BColumn::WantsEvents() const
711 return fWantsEvents;
715 void
716 BColumn::SetWantsEvents(bool state)
718 fWantsEvents = state;
722 int32
723 BColumn::LogicalFieldNum() const
725 return fFieldID;
729 bool
730 BColumn::AcceptsField(const BField*) const
732 return true;
736 // #pragma mark -
739 BColumnListView::BColumnListView(BRect rect, const char* name,
740 uint32 resizingMode, uint32 flags, border_style border,
741 bool showHorizontalScrollbar)
743 BView(rect, name, resizingMode,
744 flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
745 fStatusView(NULL),
746 fSelectionMessage(NULL),
747 fSortingEnabled(true),
748 fLatchWidth(kLatchWidth),
749 fBorderStyle(border),
750 fShowingHorizontalScrollBar(showHorizontalScrollbar)
752 _Init();
756 BColumnListView::BColumnListView(const char* name, uint32 flags,
757 border_style border, bool showHorizontalScrollbar)
759 BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
760 fStatusView(NULL),
761 fSelectionMessage(NULL),
762 fSortingEnabled(true),
763 fLatchWidth(kLatchWidth),
764 fBorderStyle(border),
765 fShowingHorizontalScrollBar(showHorizontalScrollbar)
767 _Init();
771 BColumnListView::~BColumnListView()
773 while (BColumn* column = (BColumn*)fColumns.RemoveItem((int32)0))
774 delete column;
778 bool
779 BColumnListView::InitiateDrag(BPoint, bool)
781 return false;
785 void
786 BColumnListView::MessageDropped(BMessage*, BPoint)
791 void
792 BColumnListView::ExpandOrCollapse(BRow* row, bool Open)
794 fOutlineView->ExpandOrCollapse(row, Open);
798 status_t
799 BColumnListView::Invoke(BMessage* message)
801 if (message == 0)
802 message = Message();
804 return BInvoker::Invoke(message);
808 void
809 BColumnListView::ItemInvoked()
811 Invoke();
815 void
816 BColumnListView::SetInvocationMessage(BMessage* message)
818 SetMessage(message);
822 BMessage*
823 BColumnListView::InvocationMessage() const
825 return Message();
829 uint32
830 BColumnListView::InvocationCommand() const
832 return Command();
836 BRow*
837 BColumnListView::FocusRow() const
839 return fOutlineView->FocusRow();
843 void
844 BColumnListView::SetFocusRow(int32 Index, bool Select)
846 SetFocusRow(RowAt(Index), Select);
850 void
851 BColumnListView::SetFocusRow(BRow* row, bool Select)
853 fOutlineView->SetFocusRow(row, Select);
857 void
858 BColumnListView::SetMouseTrackingEnabled(bool Enabled)
860 fOutlineView->SetMouseTrackingEnabled(Enabled);
864 list_view_type
865 BColumnListView::SelectionMode() const
867 return fOutlineView->SelectionMode();
871 void
872 BColumnListView::Deselect(BRow* row)
874 fOutlineView->Deselect(row);
878 void
879 BColumnListView::AddToSelection(BRow* row)
881 fOutlineView->AddToSelection(row);
885 void
886 BColumnListView::DeselectAll()
888 fOutlineView->DeselectAll();
892 BRow*
893 BColumnListView::CurrentSelection(BRow* lastSelected) const
895 return fOutlineView->CurrentSelection(lastSelected);
899 void
900 BColumnListView::SelectionChanged()
902 if (fSelectionMessage)
903 Invoke(fSelectionMessage);
907 void
908 BColumnListView::SetSelectionMessage(BMessage* message)
910 if (fSelectionMessage == message)
911 return;
913 delete fSelectionMessage;
914 fSelectionMessage = message;
918 BMessage*
919 BColumnListView::SelectionMessage()
921 return fSelectionMessage;
925 uint32
926 BColumnListView::SelectionCommand() const
928 if (fSelectionMessage)
929 return fSelectionMessage->what;
931 return 0;
935 void
936 BColumnListView::SetSelectionMode(list_view_type mode)
938 fOutlineView->SetSelectionMode(mode);
942 void
943 BColumnListView::SetSortingEnabled(bool enabled)
945 fSortingEnabled = enabled;
946 fSortColumns.MakeEmpty();
947 fTitleView->Invalidate();
948 // erase sort indicators
952 bool
953 BColumnListView::SortingEnabled() const
955 return fSortingEnabled;
959 void
960 BColumnListView::SetSortColumn(BColumn* column, bool add, bool ascending)
962 if (!SortingEnabled())
963 return;
965 if (!add)
966 fSortColumns.MakeEmpty();
968 if (!fSortColumns.HasItem(column))
969 fSortColumns.AddItem(column);
971 column->fSortAscending = ascending;
972 fTitleView->Invalidate();
973 fOutlineView->StartSorting();
977 void
978 BColumnListView::ClearSortColumns()
980 fSortColumns.MakeEmpty();
981 fTitleView->Invalidate();
982 // erase sort indicators
986 void
987 BColumnListView::AddStatusView(BView* view)
989 BRect bounds = Bounds();
990 float width = view->Bounds().Width();
991 if (width > bounds.Width() / 2)
992 width = bounds.Width() / 2;
994 fStatusView = view;
996 Window()->BeginViewTransaction();
997 fHorizontalScrollBar->ResizeBy(-(width + 1), 0);
998 fHorizontalScrollBar->MoveBy((width + 1), 0);
999 AddChild(view);
1001 BRect viewRect(bounds);
1002 viewRect.right = width;
1003 viewRect.top = viewRect.bottom - B_H_SCROLL_BAR_HEIGHT;
1004 if (fBorderStyle == B_PLAIN_BORDER)
1005 viewRect.OffsetBy(1, -1);
1006 else if (fBorderStyle == B_FANCY_BORDER)
1007 viewRect.OffsetBy(2, -2);
1009 view->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
1010 view->ResizeTo(viewRect.Width(), viewRect.Height());
1011 view->MoveTo(viewRect.left, viewRect.top);
1012 Window()->EndViewTransaction();
1016 BView*
1017 BColumnListView::RemoveStatusView()
1019 if (fStatusView) {
1020 float width = fStatusView->Bounds().Width();
1021 Window()->BeginViewTransaction();
1022 fStatusView->RemoveSelf();
1023 fHorizontalScrollBar->MoveBy(-width, 0);
1024 fHorizontalScrollBar->ResizeBy(width, 0);
1025 Window()->EndViewTransaction();
1028 BView* view = fStatusView;
1029 fStatusView = 0;
1030 return view;
1034 void
1035 BColumnListView::AddColumn(BColumn* column, int32 logicalFieldIndex)
1037 ASSERT(column != NULL);
1039 column->fList = this;
1040 column->fFieldID = logicalFieldIndex;
1042 // sanity check -- if there is already a field with this ID, remove it.
1043 for (int32 index = 0; index < fColumns.CountItems(); index++) {
1044 BColumn* existingColumn = (BColumn*) fColumns.ItemAt(index);
1045 if (existingColumn && existingColumn->fFieldID == logicalFieldIndex) {
1046 RemoveColumn(existingColumn);
1047 break;
1051 if (column->Width() < column->MinWidth())
1052 column->SetWidth(column->MinWidth());
1053 else if (column->Width() > column->MaxWidth())
1054 column->SetWidth(column->MaxWidth());
1056 fColumns.AddItem((void*) column);
1057 fTitleView->ColumnAdded(column);
1061 void
1062 BColumnListView::MoveColumn(BColumn* column, int32 index)
1064 ASSERT(column != NULL);
1065 fTitleView->MoveColumn(column, index);
1069 void
1070 BColumnListView::RemoveColumn(BColumn* column)
1072 if (fColumns.HasItem(column)) {
1073 SetColumnVisible(column, false);
1074 if (Window() != NULL)
1075 Window()->UpdateIfNeeded();
1076 fColumns.RemoveItem(column);
1081 int32
1082 BColumnListView::CountColumns() const
1084 return fColumns.CountItems();
1088 BColumn*
1089 BColumnListView::ColumnAt(int32 field) const
1091 return (BColumn*) fColumns.ItemAt(field);
1095 BColumn*
1096 BColumnListView::ColumnAt(BPoint point) const
1098 float left = MAX(kLeftMargin, LatchWidth());
1100 for (int i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1101 if (column == NULL || !column->IsVisible())
1102 continue;
1104 float right = left + column->Width();
1105 if (point.x >= left && point.x <= right)
1106 return column;
1108 left = right + 1;
1111 return NULL;
1115 void
1116 BColumnListView::SetColumnVisible(BColumn* column, bool visible)
1118 fTitleView->SetColumnVisible(column, visible);
1122 void
1123 BColumnListView::SetColumnVisible(int32 index, bool isVisible)
1125 BColumn* column = ColumnAt(index);
1126 if (column != NULL)
1127 column->SetVisible(isVisible);
1131 bool
1132 BColumnListView::IsColumnVisible(int32 index) const
1134 BColumn* column = ColumnAt(index);
1135 if (column != NULL)
1136 return column->IsVisible();
1138 return false;
1142 void
1143 BColumnListView::SetColumnFlags(column_flags flags)
1145 fTitleView->SetColumnFlags(flags);
1149 void
1150 BColumnListView::ResizeColumnToPreferred(int32 index)
1152 BColumn* column = ColumnAt(index);
1153 if (column == NULL)
1154 return;
1156 // get the preferred column width
1157 float width = fOutlineView->GetColumnPreferredWidth(column);
1159 // set it
1160 float oldWidth = column->Width();
1161 column->SetWidth(width);
1163 fTitleView->ColumnResized(column, oldWidth);
1164 fOutlineView->Invalidate();
1168 void
1169 BColumnListView::ResizeAllColumnsToPreferred()
1171 int32 count = CountColumns();
1172 for (int32 i = 0; i < count; i++)
1173 ResizeColumnToPreferred(i);
1177 const BRow*
1178 BColumnListView::RowAt(int32 Index, BRow* parentRow) const
1180 if (parentRow == 0)
1181 return fOutlineView->RowList()->ItemAt(Index);
1183 return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : NULL;
1187 BRow*
1188 BColumnListView::RowAt(int32 Index, BRow* parentRow)
1190 if (parentRow == 0)
1191 return fOutlineView->RowList()->ItemAt(Index);
1193 return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : 0;
1197 const BRow*
1198 BColumnListView::RowAt(BPoint point) const
1200 float top;
1201 int32 indent;
1202 return fOutlineView->FindRow(point.y, &indent, &top);
1206 BRow*
1207 BColumnListView::RowAt(BPoint point)
1209 float top;
1210 int32 indent;
1211 return fOutlineView->FindRow(point.y, &indent, &top);
1215 bool
1216 BColumnListView::GetRowRect(const BRow* row, BRect* outRect) const
1218 return fOutlineView->FindRect(row, outRect);
1222 bool
1223 BColumnListView::FindParent(BRow* row, BRow** _parent, bool* _isVisible) const
1225 return fOutlineView->FindParent(row, _parent, _isVisible);
1229 int32
1230 BColumnListView::IndexOf(BRow* row)
1232 return fOutlineView->IndexOf(row);
1236 int32
1237 BColumnListView::CountRows(BRow* parentRow) const
1239 if (parentRow == 0)
1240 return fOutlineView->RowList()->CountItems();
1241 if (parentRow->fChildList)
1242 return parentRow->fChildList->CountItems();
1243 else
1244 return 0;
1248 void
1249 BColumnListView::AddRow(BRow* row, BRow* parentRow)
1251 AddRow(row, -1, parentRow);
1255 void
1256 BColumnListView::AddRow(BRow* row, int32 index, BRow* parentRow)
1258 row->fChildList = 0;
1259 row->fList = this;
1260 row->ValidateFields();
1261 fOutlineView->AddRow(row, index, parentRow);
1265 void
1266 BColumnListView::RemoveRow(BRow* row)
1268 fOutlineView->RemoveRow(row);
1269 row->fList = NULL;
1273 void
1274 BColumnListView::UpdateRow(BRow* row)
1276 fOutlineView->UpdateRow(row);
1280 bool
1281 BColumnListView::SwapRows(int32 index1, int32 index2, BRow* parentRow1,
1282 BRow* parentRow2)
1284 BRow* row1 = NULL;
1285 BRow* row2 = NULL;
1287 BRowContainer* container1 = NULL;
1288 BRowContainer* container2 = NULL;
1290 if (parentRow1 == NULL)
1291 container1 = fOutlineView->RowList();
1292 else
1293 container1 = parentRow1->fChildList;
1295 if (container1 == NULL)
1296 return false;
1298 if (parentRow2 == NULL)
1299 container2 = fOutlineView->RowList();
1300 else
1301 container2 = parentRow2->fChildList;
1303 if (container2 == NULL)
1304 return false;
1306 row1 = container1->ItemAt(index1);
1308 if (row1 == NULL)
1309 return false;
1311 row2 = container2->ItemAt(index2);
1313 if (row2 == NULL)
1314 return false;
1316 container1->ReplaceItem(index2, row1);
1317 container2->ReplaceItem(index1, row2);
1319 BRect rect1;
1320 BRect rect2;
1321 BRect rect;
1323 fOutlineView->FindRect(row1, &rect1);
1324 fOutlineView->FindRect(row2, &rect2);
1326 rect = rect1 | rect2;
1328 fOutlineView->Invalidate(rect);
1330 return true;
1334 void
1335 BColumnListView::ScrollTo(const BRow* row)
1337 fOutlineView->ScrollTo(row);
1341 void
1342 BColumnListView::ScrollTo(BPoint point)
1344 fOutlineView->ScrollTo(point);
1348 void
1349 BColumnListView::Clear()
1351 fOutlineView->Clear();
1355 void
1356 BColumnListView::InvalidateRow(BRow* row)
1358 BRect updateRect;
1359 GetRowRect(row, &updateRect);
1360 fOutlineView->Invalidate(updateRect);
1364 // This method is deprecated.
1365 void
1366 BColumnListView::SetFont(const BFont* font, uint32 mask)
1368 fOutlineView->SetFont(font, mask);
1369 fTitleView->SetFont(font, mask);
1373 void
1374 BColumnListView::SetFont(ColumnListViewFont font_num, const BFont* font,
1375 uint32 mask)
1377 switch (font_num) {
1378 case B_FONT_ROW:
1379 fOutlineView->SetFont(font, mask);
1380 break;
1382 case B_FONT_HEADER:
1383 fTitleView->SetFont(font, mask);
1384 break;
1386 default:
1387 ASSERT(false);
1388 break;
1393 void
1394 BColumnListView::GetFont(ColumnListViewFont font_num, BFont* font) const
1396 switch (font_num) {
1397 case B_FONT_ROW:
1398 fOutlineView->GetFont(font);
1399 break;
1401 case B_FONT_HEADER:
1402 fTitleView->GetFont(font);
1403 break;
1405 default:
1406 ASSERT(false);
1407 break;
1412 void
1413 BColumnListView::SetColor(ColumnListViewColor colorIndex, const rgb_color color)
1415 if ((int)colorIndex < 0) {
1416 ASSERT(false);
1417 colorIndex = (ColumnListViewColor)0;
1420 if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
1421 ASSERT(false);
1422 colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
1425 fColorList[colorIndex] = color;
1429 rgb_color
1430 BColumnListView::Color(ColumnListViewColor colorIndex) const
1432 if ((int)colorIndex < 0) {
1433 ASSERT(false);
1434 colorIndex = (ColumnListViewColor)0;
1437 if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
1438 ASSERT(false);
1439 colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
1442 return fColorList[colorIndex];
1446 void
1447 BColumnListView::SetHighColor(rgb_color color)
1449 BView::SetHighColor(color);
1450 // fOutlineView->Invalidate();
1451 // Redraw with the new color.
1452 // Note that this will currently cause an infinite loop, refreshing
1453 // over and over. A better solution is needed.
1457 void
1458 BColumnListView::SetSelectionColor(rgb_color color)
1460 fColorList[B_COLOR_SELECTION] = color;
1464 void
1465 BColumnListView::SetBackgroundColor(rgb_color color)
1467 fColorList[B_COLOR_BACKGROUND] = color;
1468 fOutlineView->Invalidate();
1469 // repaint with new color
1473 void
1474 BColumnListView::SetEditColor(rgb_color color)
1476 fColorList[B_COLOR_EDIT_BACKGROUND] = color;
1480 const rgb_color
1481 BColumnListView::SelectionColor() const
1483 return fColorList[B_COLOR_SELECTION];
1487 const rgb_color
1488 BColumnListView::BackgroundColor() const
1490 return fColorList[B_COLOR_BACKGROUND];
1494 const rgb_color
1495 BColumnListView::EditColor() const
1497 return fColorList[B_COLOR_EDIT_BACKGROUND];
1501 BPoint
1502 BColumnListView::SuggestTextPosition(const BRow* row,
1503 const BColumn* inColumn) const
1505 BRect rect(GetFieldRect(row, inColumn));
1507 font_height fh;
1508 fOutlineView->GetFontHeight(&fh);
1509 float baseline = floor(rect.top + fh.ascent
1510 + (rect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
1511 return BPoint(rect.left + 8, baseline);
1515 BRect
1516 BColumnListView::GetFieldRect(const BRow* row, const BColumn* inColumn) const
1518 BRect rect;
1519 GetRowRect(row, &rect);
1520 if (inColumn != NULL) {
1521 float leftEdge = MAX(kLeftMargin, LatchWidth());
1522 for (int index = 0; index < fColumns.CountItems(); index++) {
1523 BColumn* column = (BColumn*) fColumns.ItemAt(index);
1524 if (column == NULL || !column->IsVisible())
1525 continue;
1527 if (column == inColumn) {
1528 rect.left = leftEdge;
1529 rect.right = rect.left + column->Width();
1530 break;
1533 leftEdge += column->Width() + 1;
1537 return rect;
1541 void
1542 BColumnListView::SetLatchWidth(float width)
1544 fLatchWidth = width;
1545 Invalidate();
1549 float
1550 BColumnListView::LatchWidth() const
1552 return fLatchWidth;
1555 void
1556 BColumnListView::DrawLatch(BView* view, BRect rect, LatchType position, BRow*)
1558 const int32 rectInset = 4;
1560 // make square
1561 int32 sideLen = rect.IntegerWidth();
1562 if (sideLen > rect.IntegerHeight())
1563 sideLen = rect.IntegerHeight();
1565 // make center
1566 int32 halfWidth = rect.IntegerWidth() / 2;
1567 int32 halfHeight = rect.IntegerHeight() / 2;
1568 int32 halfSide = sideLen / 2;
1570 float left = rect.left + halfWidth - halfSide;
1571 float top = rect.top + halfHeight - halfSide;
1573 BRect itemRect(left, top, left + sideLen, top + sideLen);
1575 // Why it is a pixel high? I don't know.
1576 itemRect.OffsetBy(0, -1);
1578 itemRect.InsetBy(rectInset, rectInset);
1580 // make it an odd number of pixels wide, the latch looks better this way
1581 if ((itemRect.IntegerWidth() % 2) == 1) {
1582 itemRect.right += 1;
1583 itemRect.bottom += 1;
1586 rgb_color highColor = view->HighColor();
1587 view->SetHighColor(0, 0, 0);
1589 switch (position) {
1590 case B_OPEN_LATCH:
1591 view->StrokeRect(itemRect);
1592 view->StrokeLine(
1593 BPoint(itemRect.left + 2,
1594 (itemRect.top + itemRect.bottom) / 2),
1595 BPoint(itemRect.right - 2,
1596 (itemRect.top + itemRect.bottom) / 2));
1597 break;
1599 case B_PRESSED_LATCH:
1600 view->StrokeRect(itemRect);
1601 view->StrokeLine(
1602 BPoint(itemRect.left + 2,
1603 (itemRect.top + itemRect.bottom) / 2),
1604 BPoint(itemRect.right - 2,
1605 (itemRect.top + itemRect.bottom) / 2));
1606 view->StrokeLine(
1607 BPoint((itemRect.left + itemRect.right) / 2,
1608 itemRect.top + 2),
1609 BPoint((itemRect.left + itemRect.right) / 2,
1610 itemRect.bottom - 2));
1611 view->InvertRect(itemRect);
1612 break;
1614 case B_CLOSED_LATCH:
1615 view->StrokeRect(itemRect);
1616 view->StrokeLine(
1617 BPoint(itemRect.left + 2,
1618 (itemRect.top + itemRect.bottom) / 2),
1619 BPoint(itemRect.right - 2,
1620 (itemRect.top + itemRect.bottom) / 2));
1621 view->StrokeLine(
1622 BPoint((itemRect.left + itemRect.right) / 2,
1623 itemRect.top + 2),
1624 BPoint((itemRect.left + itemRect.right) / 2,
1625 itemRect.bottom - 2));
1626 break;
1628 case B_NO_LATCH:
1629 default:
1630 // No drawing
1631 break;
1634 view->SetHighColor(highColor);
1638 void
1639 BColumnListView::MakeFocus(bool isFocus)
1641 if (fBorderStyle != B_NO_BORDER) {
1642 // Redraw focus marks around view
1643 Invalidate();
1644 fHorizontalScrollBar->SetBorderHighlighted(isFocus);
1645 fVerticalScrollBar->SetBorderHighlighted(isFocus);
1648 BView::MakeFocus(isFocus);
1652 void
1653 BColumnListView::MessageReceived(BMessage* message)
1655 // Propagate mouse wheel messages down to child, so that it can
1656 // scroll. Note we have done so, so we don't go into infinite
1657 // recursion if this comes back up here.
1658 if (message->what == B_MOUSE_WHEEL_CHANGED) {
1659 bool handled;
1660 if (message->FindBool("be:clvhandled", &handled) != B_OK) {
1661 message->AddBool("be:clvhandled", true);
1662 fOutlineView->MessageReceived(message);
1663 return;
1667 BView::MessageReceived(message);
1671 void
1672 BColumnListView::KeyDown(const char* bytes, int32 numBytes)
1674 char c = bytes[0];
1675 switch (c) {
1676 case B_RIGHT_ARROW:
1677 case B_LEFT_ARROW:
1679 if ((modifiers() & B_SHIFT_KEY) != 0) {
1680 float minVal, maxVal;
1681 fHorizontalScrollBar->GetRange(&minVal, &maxVal);
1682 float smallStep, largeStep;
1683 fHorizontalScrollBar->GetSteps(&smallStep, &largeStep);
1684 float oldVal = fHorizontalScrollBar->Value();
1685 float newVal = oldVal;
1687 if (c == B_LEFT_ARROW)
1688 newVal -= smallStep;
1689 else if (c == B_RIGHT_ARROW)
1690 newVal += smallStep;
1692 if (newVal < minVal)
1693 newVal = minVal;
1694 else if (newVal > maxVal)
1695 newVal = maxVal;
1697 fHorizontalScrollBar->SetValue(newVal);
1698 } else {
1699 BRow* focusRow = fOutlineView->FocusRow();
1700 if (focusRow == NULL)
1701 break;
1703 bool expanded = focusRow->IsExpanded();
1704 if ((c == B_RIGHT_ARROW && !expanded)
1705 || (c == B_LEFT_ARROW && expanded)) {
1706 fOutlineView->ToggleFocusRowOpen();
1709 break;
1712 case B_DOWN_ARROW:
1713 fOutlineView->ChangeFocusRow(false,
1714 (modifiers() & B_CONTROL_KEY) == 0,
1715 (modifiers() & B_SHIFT_KEY) != 0);
1716 break;
1718 case B_UP_ARROW:
1719 fOutlineView->ChangeFocusRow(true,
1720 (modifiers() & B_CONTROL_KEY) == 0,
1721 (modifiers() & B_SHIFT_KEY) != 0);
1722 break;
1724 case B_PAGE_UP:
1725 case B_PAGE_DOWN:
1727 float minValue, maxValue;
1728 fVerticalScrollBar->GetRange(&minValue, &maxValue);
1729 float smallStep, largeStep;
1730 fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
1731 float currentValue = fVerticalScrollBar->Value();
1732 float newValue = currentValue;
1734 if (c == B_PAGE_UP)
1735 newValue -= largeStep;
1736 else
1737 newValue += largeStep;
1739 if (newValue > maxValue)
1740 newValue = maxValue;
1741 else if (newValue < minValue)
1742 newValue = minValue;
1744 fVerticalScrollBar->SetValue(newValue);
1746 // Option + pgup or pgdn scrolls and changes the selection.
1747 if (modifiers() & B_OPTION_KEY)
1748 fOutlineView->MoveFocusToVisibleRect();
1750 break;
1753 case B_ENTER:
1754 Invoke();
1755 break;
1757 case B_SPACE:
1758 fOutlineView->ToggleFocusRowSelection(
1759 (modifiers() & B_SHIFT_KEY) != 0);
1760 break;
1762 case '+':
1763 fOutlineView->ToggleFocusRowOpen();
1764 break;
1766 default:
1767 BView::KeyDown(bytes, numBytes);
1772 void
1773 BColumnListView::AttachedToWindow()
1775 if (!Messenger().IsValid())
1776 SetTarget(Window());
1778 if (SortingEnabled()) fOutlineView->StartSorting();
1782 void
1783 BColumnListView::WindowActivated(bool active)
1785 fOutlineView->Invalidate();
1786 // focus and selection appearance changes with focus
1788 Invalidate();
1789 // redraw focus marks around view
1790 BView::WindowActivated(active);
1794 void
1795 BColumnListView::Draw(BRect updateRect)
1797 BRect rect = Bounds();
1799 uint32 flags = 0;
1800 if (IsFocus() && Window()->IsActive())
1801 flags |= BControlLook::B_FOCUSED;
1803 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
1805 BRect verticalScrollBarFrame;
1806 if (!fVerticalScrollBar->IsHidden())
1807 verticalScrollBarFrame = fVerticalScrollBar->Frame();
1809 BRect horizontalScrollBarFrame;
1810 if (!fHorizontalScrollBar->IsHidden())
1811 horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
1813 if (fBorderStyle == B_NO_BORDER) {
1814 // We still draw the left/top border, but not focused.
1815 // The scrollbars cannot be displayed without frame and
1816 // it looks bad to have no frame only along the left/top
1817 // side.
1818 rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
1819 SetHighColor(borderColor);
1820 StrokeLine(BPoint(rect.left, rect.bottom),
1821 BPoint(rect.left, rect.top));
1822 StrokeLine(BPoint(rect.left + 1, rect.top),
1823 BPoint(rect.right, rect.top));
1826 be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1827 verticalScrollBarFrame, horizontalScrollBarFrame,
1828 base, fBorderStyle, flags);
1830 if (fStatusView != NULL) {
1831 rect = Bounds();
1832 BRegion region(rect & fStatusView->Frame().InsetByCopy(-2, -2));
1833 ConstrainClippingRegion(&region);
1834 rect.bottom = fStatusView->Frame().top - 1;
1835 be_control_look->DrawScrollViewFrame(this, rect, updateRect,
1836 BRect(), BRect(), base, fBorderStyle, flags);
1841 void
1842 BColumnListView::SaveState(BMessage* message)
1844 message->MakeEmpty();
1846 for (int32 i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
1847 message->AddInt32("ID", column->fFieldID);
1848 message->AddFloat("width", column->fWidth);
1849 message->AddBool("visible", column->fVisible);
1852 message->AddBool("sortingenabled", fSortingEnabled);
1854 if (fSortingEnabled) {
1855 for (int32 i = 0; BColumn* column = (BColumn*)fSortColumns.ItemAt(i);
1856 i++) {
1857 message->AddInt32("sortID", column->fFieldID);
1858 message->AddBool("sortascending", column->fSortAscending);
1864 void
1865 BColumnListView::LoadState(BMessage* message)
1867 int32 id;
1868 for (int i = 0; message->FindInt32("ID", i, &id) == B_OK; i++) {
1869 for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
1870 if (column->fFieldID == id) {
1871 // move this column to position 'i' and set its attributes
1872 MoveColumn(column, i);
1873 float width;
1874 if (message->FindFloat("width", i, &width) == B_OK)
1875 column->SetWidth(width);
1876 bool visible;
1877 if (message->FindBool("visible", i, &visible) == B_OK)
1878 column->SetVisible(visible);
1882 bool b;
1883 if (message->FindBool("sortingenabled", &b) == B_OK) {
1884 SetSortingEnabled(b);
1885 for (int k = 0; message->FindInt32("sortID", k, &id) == B_OK; k++) {
1886 for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
1887 j++) {
1888 if (column->fFieldID == id) {
1889 // add this column to the sort list
1890 bool value;
1891 if (message->FindBool("sortascending", k, &value) == B_OK)
1892 SetSortColumn(column, true, value);
1900 void
1901 BColumnListView::SetEditMode(bool state)
1903 fOutlineView->SetEditMode(state);
1904 fTitleView->SetEditMode(state);
1908 void
1909 BColumnListView::Refresh()
1911 if (LockLooper()) {
1912 Invalidate();
1913 fOutlineView->FixScrollBar (true);
1914 fOutlineView->Invalidate();
1915 Window()->UpdateIfNeeded();
1916 UnlockLooper();
1921 BSize
1922 BColumnListView::MinSize()
1924 BSize size;
1925 size.width = 100;
1926 size.height = std::max(kMinTitleHeight,
1927 ceilf(be_plain_font->Size() * kTitleSpacing))
1928 + 4 * B_H_SCROLL_BAR_HEIGHT;
1929 if (!fHorizontalScrollBar->IsHidden())
1930 size.height += fHorizontalScrollBar->Frame().Height() + 1;
1931 // TODO: Take border size into account
1933 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1937 BSize
1938 BColumnListView::PreferredSize()
1940 BSize size = MinSize();
1941 size.height += ceilf(be_plain_font->Size()) * 20;
1943 // return MinSize().width if there are no columns.
1944 int32 count = CountColumns();
1945 if (count > 0) {
1946 BRect titleRect;
1947 BRect outlineRect;
1948 BRect vScrollBarRect;
1949 BRect hScrollBarRect;
1950 _GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1951 hScrollBarRect);
1952 // Start with the extra width for border and scrollbars etc.
1953 size.width = titleRect.left - Bounds().left;
1954 size.width += Bounds().right - titleRect.right;
1955 // If we want all columns to be visible at their preferred width,
1956 // we also need to add the extra margin width that the TitleView
1957 // uses to compute its _VirtualWidth() for the horizontal scroll bar.
1958 size.width += fTitleView->MarginWidth();
1959 for (int32 i = 0; i < count; i++) {
1960 BColumn* column = ColumnAt(i);
1961 if (column != NULL)
1962 size.width += fOutlineView->GetColumnPreferredWidth(column);
1966 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1970 BSize
1971 BColumnListView::MaxSize()
1973 BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
1974 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1978 void
1979 BColumnListView::LayoutInvalidated(bool descendants)
1984 void
1985 BColumnListView::DoLayout()
1987 if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
1988 return;
1990 BRect titleRect;
1991 BRect outlineRect;
1992 BRect vScrollBarRect;
1993 BRect hScrollBarRect;
1994 _GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
1995 hScrollBarRect);
1997 fTitleView->MoveTo(titleRect.LeftTop());
1998 fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
2000 fOutlineView->MoveTo(outlineRect.LeftTop());
2001 fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
2003 fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
2004 fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
2005 vScrollBarRect.Height());
2007 if (fStatusView != NULL) {
2008 BSize size = fStatusView->MinSize();
2009 if (size.height > B_H_SCROLL_BAR_HEIGHT)
2010 size.height = B_H_SCROLL_BAR_HEIGHT;
2011 if (size.width > Bounds().Width() / 2)
2012 size.width = floorf(Bounds().Width() / 2);
2014 BPoint offset(hScrollBarRect.LeftTop());
2016 if (fBorderStyle == B_PLAIN_BORDER) {
2017 offset += BPoint(0, 1);
2018 } else if (fBorderStyle == B_FANCY_BORDER) {
2019 offset += BPoint(-1, 2);
2020 size.height -= 1;
2023 fStatusView->MoveTo(offset);
2024 fStatusView->ResizeTo(size.width, size.height);
2025 hScrollBarRect.left = offset.x + size.width + 1;
2028 fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
2029 fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
2030 hScrollBarRect.Height());
2032 fOutlineView->FixScrollBar(true);
2036 void
2037 BColumnListView::_Init()
2039 SetViewColor(B_TRANSPARENT_32_BIT);
2041 BRect bounds(Bounds());
2042 if (bounds.Width() <= 0)
2043 bounds.right = 100;
2045 if (bounds.Height() <= 0)
2046 bounds.bottom = 100;
2048 fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR);
2049 fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2050 fColorList[B_COLOR_ROW_DIVIDER] = tint_color(
2051 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT);
2052 fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2053 fColorList[B_COLOR_SELECTION_TEXT] =
2054 ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2056 // For non focus selection uses the selection color as BListView
2057 fColorList[B_COLOR_NON_FOCUS_SELECTION] =
2058 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
2060 // edit mode doesn't work very well
2061 fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color(
2062 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT);
2063 fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180;
2065 // Unused color
2066 fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
2068 fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_PANEL_BACKGROUND_COLOR);
2069 fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR);
2071 // Unused colors
2072 fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2073 fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR);
2075 BRect titleRect;
2076 BRect outlineRect;
2077 BRect vScrollBarRect;
2078 BRect hScrollBarRect;
2079 _GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
2080 hScrollBarRect);
2082 fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
2083 AddChild(fOutlineView);
2086 fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
2087 &fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
2088 AddChild(fTitleView);
2090 fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
2091 fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
2092 AddChild(fVerticalScrollBar);
2094 fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
2095 "horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
2096 AddChild(fHorizontalScrollBar);
2098 if (!fShowingHorizontalScrollBar)
2099 fHorizontalScrollBar->Hide();
2101 fOutlineView->FixScrollBar(true);
2105 void
2106 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
2107 BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect)
2109 titleRect = bounds;
2110 titleRect.bottom = titleRect.top + std::max(kMinTitleHeight,
2111 ceilf(be_plain_font->Size() * kTitleSpacing));
2112 #if !LOWER_SCROLLBAR
2113 titleRect.right -= B_V_SCROLL_BAR_WIDTH;
2114 #endif
2116 outlineRect = bounds;
2117 outlineRect.top = titleRect.bottom + 1.0;
2118 outlineRect.right -= B_V_SCROLL_BAR_WIDTH;
2119 if (fShowingHorizontalScrollBar)
2120 outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2122 vScrollBarRect = bounds;
2123 #if LOWER_SCROLLBAR
2124 vScrollBarRect.top += std::max(kMinTitleHeight,
2125 ceilf(be_plain_font->Size() * kTitleSpacing));
2126 #endif
2128 vScrollBarRect.left = vScrollBarRect.right - B_V_SCROLL_BAR_WIDTH;
2129 if (fShowingHorizontalScrollBar)
2130 vScrollBarRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
2132 hScrollBarRect = bounds;
2133 hScrollBarRect.top = hScrollBarRect.bottom - B_H_SCROLL_BAR_HEIGHT;
2134 hScrollBarRect.right -= B_V_SCROLL_BAR_WIDTH;
2136 // Adjust stuff so the border will fit.
2137 if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) {
2138 titleRect.InsetBy(1, 0);
2139 titleRect.OffsetBy(0, 1);
2140 outlineRect.InsetBy(1, 1);
2141 } else if (fBorderStyle == B_FANCY_BORDER) {
2142 titleRect.InsetBy(2, 0);
2143 titleRect.OffsetBy(0, 2);
2144 outlineRect.InsetBy(2, 2);
2146 vScrollBarRect.OffsetBy(-1, 0);
2147 #if LOWER_SCROLLBAR
2148 vScrollBarRect.top += 2;
2149 vScrollBarRect.bottom -= 1;
2150 #else
2151 vScrollBarRect.InsetBy(0, 1);
2152 #endif
2153 hScrollBarRect.OffsetBy(0, -1);
2154 hScrollBarRect.InsetBy(1, 0);
2159 // #pragma mark -
2162 TitleView::TitleView(BRect rect, OutlineView* horizontalSlave,
2163 BList* visibleColumns, BList* sortColumns, BColumnListView* listView,
2164 uint32 resizingMode)
2166 BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS),
2167 fOutlineView(horizontalSlave),
2168 fColumns(visibleColumns),
2169 fSortColumns(sortColumns),
2170 // fColumnsWidth(0),
2171 fVisibleRect(rect.OffsetToCopy(0, 0)),
2172 fCurrentState(INACTIVE),
2173 fColumnPop(NULL),
2174 fMasterView(listView),
2175 fEditMode(false),
2176 fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE
2177 | B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE)
2179 SetViewColor(B_TRANSPARENT_COLOR);
2181 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2182 // xxx this needs to be smart about the size of the backbuffer.
2183 BRect doubleBufferRect(0, 0, 600, 35);
2184 fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
2185 fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
2186 B_FOLLOW_ALL_SIDES, 0);
2187 fDrawBuffer->Lock();
2188 fDrawBuffer->AddChild(fDrawBufferView);
2189 fDrawBuffer->Unlock();
2190 #endif
2192 fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2193 fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
2195 fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
2196 fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
2198 fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST);
2199 fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST);
2200 fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST);
2201 fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE);
2203 FixScrollBar(true);
2207 TitleView::~TitleView()
2209 delete fColumnPop;
2210 fColumnPop = NULL;
2212 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2213 delete fDrawBuffer;
2214 #endif
2215 delete fUpSortArrow;
2216 delete fDownSortArrow;
2218 delete fResizeCursor;
2219 delete fMaxResizeCursor;
2220 delete fMinResizeCursor;
2221 delete fColumnMoveCursor;
2225 void
2226 TitleView::ColumnAdded(BColumn* column)
2228 // fColumnsWidth += column->Width();
2229 FixScrollBar(false);
2230 Invalidate();
2234 void
2235 TitleView::ColumnResized(BColumn* column, float oldWidth)
2237 // fColumnsWidth += column->Width() - oldWidth;
2238 FixScrollBar(false);
2239 Invalidate();
2243 void
2244 TitleView::SetColumnVisible(BColumn* column, bool visible)
2246 if (column->fVisible == visible)
2247 return;
2249 // If setting it visible, do this first so we can find its position
2250 // to invalidate. If hiding it, do it last.
2251 if (visible)
2252 column->fVisible = visible;
2254 BRect titleInvalid;
2255 GetTitleRect(column, &titleInvalid);
2257 // Now really set the visibility
2258 column->fVisible = visible;
2260 // if (visible)
2261 // fColumnsWidth += column->Width();
2262 // else
2263 // fColumnsWidth -= column->Width();
2265 BRect outlineInvalid(fOutlineView->VisibleRect());
2266 outlineInvalid.left = titleInvalid.left;
2267 titleInvalid.right = outlineInvalid.right;
2269 Invalidate(titleInvalid);
2270 fOutlineView->Invalidate(outlineInvalid);
2274 void
2275 TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect)
2277 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2278 int32 numColumns = fColumns->CountItems();
2279 for (int index = 0; index < numColumns; index++) {
2280 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2281 if (!column->IsVisible())
2282 continue;
2284 if (column == findColumn) {
2285 _rect->Set(leftEdge, 0, leftEdge + column->Width(),
2286 fVisibleRect.bottom);
2287 return;
2290 leftEdge += column->Width() + 1;
2293 TRESPASS();
2297 int32
2298 TitleView::FindColumn(BPoint position, float* _leftEdge)
2300 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2301 int32 numColumns = fColumns->CountItems();
2302 for (int index = 0; index < numColumns; index++) {
2303 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2304 if (!column->IsVisible())
2305 continue;
2307 if (leftEdge > position.x)
2308 break;
2310 if (position.x >= leftEdge
2311 && position.x <= leftEdge + column->Width()) {
2312 *_leftEdge = leftEdge;
2313 return index;
2316 leftEdge += column->Width() + 1;
2319 return 0;
2323 void
2324 TitleView::FixScrollBar(bool scrollToFit)
2326 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2327 if (hScrollBar == NULL)
2328 return;
2330 float virtualWidth = _VirtualWidth();
2332 if (virtualWidth > fVisibleRect.Width()) {
2333 hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
2335 // Perform the little trick if the user is scrolled over too far.
2336 // See OutlineView::FixScrollBar for a more in depth explanation
2337 float maxScrollBarValue = virtualWidth - fVisibleRect.Width();
2338 if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) {
2339 hScrollBar->SetRange(0.0, maxScrollBarValue);
2340 hScrollBar->SetSteps(50, fVisibleRect.Width());
2342 } else if (hScrollBar->Value() == 0.0) {
2343 // disable scroll bar.
2344 hScrollBar->SetRange(0.0, 0.0);
2349 void
2350 TitleView::DragSelectedColumn(BPoint position)
2352 float invalidLeft = fSelectedColumnRect.left;
2353 float invalidRight = fSelectedColumnRect.right;
2355 float leftEdge;
2356 int32 columnIndex = FindColumn(position, &leftEdge);
2357 fSelectedColumnRect.OffsetTo(leftEdge, 0);
2359 MoveColumn(fSelectedColumn, columnIndex);
2361 fSelectedColumn->fVisible = true;
2362 ComputeDragBoundries(fSelectedColumn, position);
2364 // Redraw the new column position
2365 GetTitleRect(fSelectedColumn, &fSelectedColumnRect);
2366 invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft);
2367 invalidRight = MAX(fSelectedColumnRect.right, invalidRight);
2369 Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom));
2370 fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight,
2371 fOutlineView->VisibleRect().bottom));
2373 DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2377 void
2378 TitleView::MoveColumn(BColumn* column, int32 index)
2380 fColumns->RemoveItem((void*) column);
2382 if (-1 == index) {
2383 // Re-add the column at the end of the list.
2384 fColumns->AddItem((void*) column);
2385 } else {
2386 fColumns->AddItem((void*) column, index);
2391 void
2392 TitleView::SetColumnFlags(column_flags flags)
2394 fColumnFlags = flags;
2398 float
2399 TitleView::MarginWidth() const
2401 return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin;
2405 void
2406 TitleView::ResizeSelectedColumn(BPoint position, bool preferred)
2408 float minWidth = fSelectedColumn->MinWidth();
2409 float maxWidth = fSelectedColumn->MaxWidth();
2411 float oldWidth = fSelectedColumn->Width();
2412 float originalEdge = fSelectedColumnRect.left + oldWidth;
2413 if (preferred) {
2414 float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
2415 fSelectedColumn->SetWidth(width);
2416 } else if (position.x > fSelectedColumnRect.left + maxWidth)
2417 fSelectedColumn->SetWidth(maxWidth);
2418 else if (position.x < fSelectedColumnRect.left + minWidth)
2419 fSelectedColumn->SetWidth(minWidth);
2420 else
2421 fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
2423 float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
2424 - originalEdge;
2425 if (dX != 0) {
2426 float columnHeight = fVisibleRect.Height();
2427 BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
2428 BRect movedRect(originalRect);
2429 movedRect.OffsetBy(dX, 0);
2431 // Update the size of the title column
2432 BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
2433 BRect destRect(sourceRect);
2434 destRect.OffsetBy(fSelectedColumnRect.left, 0);
2436 #if DOUBLE_BUFFERED_COLUMN_RESIZE
2437 fDrawBuffer->Lock();
2438 DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
2439 fDrawBufferView->Sync();
2440 fDrawBuffer->Unlock();
2442 CopyBits(originalRect, movedRect);
2443 DrawBitmap(fDrawBuffer, sourceRect, destRect);
2444 #else
2445 CopyBits(originalRect, movedRect);
2446 DrawTitle(this, destRect, fSelectedColumn, false);
2447 #endif
2449 // Update the body view
2450 BRect slaveSize = fOutlineView->VisibleRect();
2451 BRect slaveSource(originalRect);
2452 slaveSource.bottom = slaveSize.bottom;
2453 BRect slaveDest(movedRect);
2454 slaveDest.bottom = slaveSize.bottom;
2455 fOutlineView->CopyBits(slaveSource, slaveDest);
2456 fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left,
2457 fResizingFirstColumn);
2459 // fColumnsWidth += dX;
2461 // Update the cursor
2462 if (fSelectedColumn->Width() == minWidth)
2463 SetViewCursor(fMinResizeCursor, true);
2464 else if (fSelectedColumn->Width() == maxWidth)
2465 SetViewCursor(fMaxResizeCursor, true);
2466 else
2467 SetViewCursor(fResizeCursor, true);
2469 ColumnResized(fSelectedColumn, oldWidth);
2474 void
2475 TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
2477 float previousColumnLeftEdge = -1000000.0;
2478 float nextColumnRightEdge = 1000000.0;
2480 bool foundColumn = false;
2481 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2482 int32 numColumns = fColumns->CountItems();
2483 for (int index = 0; index < numColumns; index++) {
2484 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2485 if (!column->IsVisible())
2486 continue;
2488 if (column == findColumn) {
2489 foundColumn = true;
2490 continue;
2493 if (foundColumn) {
2494 nextColumnRightEdge = leftEdge + column->Width();
2495 break;
2496 } else
2497 previousColumnLeftEdge = leftEdge;
2499 leftEdge += column->Width() + 1;
2502 float rightEdge = leftEdge + findColumn->Width();
2504 fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
2505 leftEdge);
2506 fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
2510 void
2511 TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
2513 BRect drawRect;
2514 rgb_color borderColor = mix_color(
2515 fMasterView->Color(B_COLOR_HEADER_BACKGROUND),
2516 make_color(0, 0, 0), 128);
2517 drawRect = rect;
2519 font_height fh;
2520 GetFontHeight(&fh);
2522 float baseline = floor(drawRect.top + fh.ascent
2523 + (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
2525 BRect bgRect = rect;
2527 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
2528 view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
2529 view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
2531 bgRect.bottom--;
2532 bgRect.right--;
2534 if (depressed)
2535 base = tint_color(base, B_DARKEN_1_TINT);
2537 be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
2538 BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
2540 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
2541 B_DARKEN_2_TINT));
2542 view->StrokeLine(rect.RightTop(), rect.RightBottom());
2544 // If no column given, nothing else to draw.
2545 if (column == NULL)
2546 return;
2548 view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2550 BFont font;
2551 GetFont(&font);
2552 view->SetFont(&font);
2554 int sortIndex = fSortColumns->IndexOf(column);
2555 if (sortIndex >= 0) {
2556 // Draw sort notation.
2557 BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline);
2559 if (fSortColumns->CountItems() > 1) {
2560 char str[256];
2561 sprintf(str, "%d", sortIndex + 1);
2562 const float w = view->StringWidth(str);
2563 upperLeft.x -= w;
2565 view->SetDrawingMode(B_OP_COPY);
2566 view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth,
2567 baseline));
2568 view->DrawString(str);
2571 float bmh = fDownSortArrow->Bounds().Height()+1;
2573 view->SetDrawingMode(B_OP_OVER);
2575 if (column->fSortAscending) {
2576 BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2577 - fDownSortArrow->Bounds().IntegerHeight()) / 2);
2578 view->DrawBitmapAsync(fDownSortArrow, leftTop);
2579 } else {
2580 BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
2581 - fUpSortArrow->Bounds().IntegerHeight()) / 2);
2582 view->DrawBitmapAsync(fUpSortArrow, leftTop);
2585 upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2);
2586 if (upperLeft.y < drawRect.top)
2587 upperLeft.y = drawRect.top;
2589 // Adjust title stuff for sort indicator
2590 drawRect.right = upperLeft.x - 2;
2593 if (drawRect.right > drawRect.left) {
2594 #if CONSTRAIN_CLIPPING_REGION
2595 BRegion clipRegion(drawRect);
2596 view->PushState();
2597 view->ConstrainClippingRegion(&clipRegion);
2598 #endif
2599 view->MovePenTo(BPoint(drawRect.left + 8, baseline));
2600 view->SetDrawingMode(B_OP_OVER);
2601 view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
2602 column->DrawTitle(drawRect, view);
2604 #if CONSTRAIN_CLIPPING_REGION
2605 view->PopState();
2606 #endif
2611 float
2612 TitleView::_VirtualWidth() const
2614 float width = MarginWidth();
2616 int32 count = fColumns->CountItems();
2617 for (int32 i = 0; i < count; i++) {
2618 BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
2619 width += column->Width();
2622 return width;
2626 void
2627 TitleView::Draw(BRect invalidRect)
2629 float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2630 for (int32 columnIndex = 0; columnIndex < fColumns->CountItems();
2631 columnIndex++) {
2633 BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
2634 if (!column->IsVisible())
2635 continue;
2637 if (columnLeftEdge > invalidRect.right)
2638 break;
2640 if (columnLeftEdge + column->Width() >= invalidRect.left) {
2641 BRect titleRect(columnLeftEdge, 0,
2642 columnLeftEdge + column->Width(), fVisibleRect.Height());
2643 DrawTitle(this, titleRect, column,
2644 (fCurrentState == DRAG_COLUMN_INSIDE_TITLE
2645 && fSelectedColumn == column));
2648 columnLeftEdge += column->Width() + 1;
2652 // bevels for right title margin
2653 if (columnLeftEdge <= invalidRect.right) {
2654 BRect titleRect(columnLeftEdge, 0, Bounds().right + 2,
2655 fVisibleRect.Height());
2656 DrawTitle(this, titleRect, NULL, false);
2659 // bevels for left title margin
2660 if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) {
2661 BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1,
2662 fVisibleRect.Height());
2663 DrawTitle(this, titleRect, NULL, false);
2666 #if DRAG_TITLE_OUTLINE
2667 // (internal) column drag indicator
2668 if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) {
2669 BRect dragRect(fSelectedColumnRect);
2670 dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2671 if (dragRect.Intersects(invalidRect)) {
2672 SetHighColor(0, 0, 255);
2673 StrokeRect(dragRect);
2676 #endif
2680 void
2681 TitleView::ScrollTo(BPoint position)
2683 fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0);
2684 fVisibleRect.OffsetTo(position.x, position.y);
2686 // Perform the little trick if the user is scrolled over too far.
2687 // See OutlineView::ScrollTo for a more in depth explanation
2688 float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width();
2689 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
2690 float min, max;
2691 hScrollBar->GetRange(&min, &max);
2692 if (max != maxScrollBarValue && position.x > maxScrollBarValue)
2693 FixScrollBar(true);
2695 _inherited::ScrollTo(position);
2699 void
2700 TitleView::MessageReceived(BMessage* message)
2702 if (message->what == kToggleColumn) {
2703 int32 num;
2704 if (message->FindInt32("be:field_num", &num) == B_OK) {
2705 for (int index = 0; index < fColumns->CountItems(); index++) {
2706 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2707 if (column == NULL)
2708 continue;
2710 if (column->LogicalFieldNum() == num)
2711 column->SetVisible(!column->IsVisible());
2714 return;
2717 BView::MessageReceived(message);
2721 void
2722 TitleView::MouseDown(BPoint position)
2724 if (fEditMode)
2725 return;
2727 int32 buttons = 1;
2728 Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2729 if (buttons == B_SECONDARY_MOUSE_BUTTON
2730 && (fColumnFlags & B_ALLOW_COLUMN_POPUP)) {
2731 // Right mouse button -- bring up menu to show/hide columns.
2732 if (fColumnPop == NULL)
2733 fColumnPop = new BPopUpMenu("Columns", false, false);
2735 fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true);
2736 BMessenger me(this);
2737 for (int index = 0; index < fColumns->CountItems(); index++) {
2738 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2739 if (column == NULL)
2740 continue;
2742 BString name;
2743 column->GetColumnName(&name);
2744 BMessage* message = new BMessage(kToggleColumn);
2745 message->AddInt32("be:field_num", column->LogicalFieldNum());
2746 BMenuItem* item = new BMenuItem(name.String(), message);
2747 item->SetMarked(column->IsVisible());
2748 item->SetTarget(me);
2749 fColumnPop->AddItem(item);
2752 BPoint screenPosition = ConvertToScreen(position);
2753 BRect sticky(screenPosition, screenPosition);
2754 sticky.InsetBy(-5, -5);
2755 fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true);
2757 return;
2760 fResizingFirstColumn = true;
2761 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2762 for (int index = 0; index < fColumns->CountItems(); index++) {
2763 BColumn* column = (BColumn*)fColumns->ItemAt(index);
2764 if (column == NULL || !column->IsVisible())
2765 continue;
2767 if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2768 break;
2770 // check for resizing a column
2771 float rightEdge = leftEdge + column->Width();
2773 if (column->ShowHeading()) {
2774 if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2775 && position.x < rightEdge + kColumnResizeAreaWidth / 2
2776 && column->MaxWidth() > column->MinWidth()
2777 && (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) {
2779 int32 clicks = 0;
2780 fSelectedColumn = column;
2781 fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2782 fVisibleRect.Height());
2783 Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2784 if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) {
2785 ResizeSelectedColumn(position, true);
2786 fCurrentState = INACTIVE;
2787 break;
2789 fCurrentState = RESIZING_COLUMN;
2790 fClickPoint = BPoint(position.x - rightEdge - 1,
2791 position.y - fSelectedColumnRect.top);
2792 SetMouseEventMask(B_POINTER_EVENTS,
2793 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2794 break;
2797 fResizingFirstColumn = false;
2799 // check for clicking on a column
2800 if (position.x > leftEdge && position.x < rightEdge) {
2801 fCurrentState = PRESSING_COLUMN;
2802 fSelectedColumn = column;
2803 fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
2804 fVisibleRect.Height());
2805 DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
2806 fClickPoint = BPoint(position.x - fSelectedColumnRect.left,
2807 position.y - fSelectedColumnRect.top);
2808 SetMouseEventMask(B_POINTER_EVENTS,
2809 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
2810 break;
2813 leftEdge = rightEdge + 1;
2818 void
2819 TitleView::MouseMoved(BPoint position, uint32 transit,
2820 const BMessage* dragMessage)
2822 if (fEditMode)
2823 return;
2825 // Handle column manipulation
2826 switch (fCurrentState) {
2827 case RESIZING_COLUMN:
2828 ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
2829 break;
2831 case PRESSING_COLUMN: {
2832 if (abs((int32)(position.x - (fClickPoint.x
2833 + fSelectedColumnRect.left))) > kColumnResizeAreaWidth
2834 || abs((int32)(position.y - (fClickPoint.y
2835 + fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
2836 // User has moved the mouse more than the tolerable amount,
2837 // initiate a drag.
2838 if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
2839 if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
2840 fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2841 ComputeDragBoundries(fSelectedColumn, position);
2842 SetViewCursor(fColumnMoveCursor, true);
2843 #if DRAG_TITLE_OUTLINE
2844 BRect invalidRect(fSelectedColumnRect);
2845 invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
2846 fCurrentDragPosition = position;
2847 Invalidate(invalidRect);
2848 #endif
2850 } else {
2851 if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
2852 // Dragged outside view
2853 fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2854 fSelectedColumn->SetVisible(false);
2855 BRect dragRect(fSelectedColumnRect);
2857 // There is a race condition where the mouse may have
2858 // moved by the time we get to handle this message.
2859 // If the user drags a column very quickly, this
2860 // results in the annoying bug where the cursor is
2861 // outside of the rectangle that is being dragged
2862 // around. Call GetMouse with the checkQueue flag set
2863 // to false so we can get the most recent position of
2864 // the mouse. This minimizes this problem (although
2865 // it is currently not possible to completely eliminate
2866 // it).
2867 uint32 buttons;
2868 GetMouse(&position, &buttons, false);
2869 dragRect.OffsetTo(position.x - fClickPoint.x,
2870 position.y - dragRect.Height() / 2);
2871 BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2876 break;
2879 case DRAG_COLUMN_INSIDE_TITLE: {
2880 if (transit == B_EXITED_VIEW
2881 && (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) {
2882 // Dragged outside view
2883 fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
2884 fSelectedColumn->SetVisible(false);
2885 BRect dragRect(fSelectedColumnRect);
2887 // See explanation above.
2888 uint32 buttons;
2889 GetMouse(&position, &buttons, false);
2891 dragRect.OffsetTo(position.x - fClickPoint.x,
2892 position.y - fClickPoint.y);
2893 BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
2894 } else if (position.x < fLeftDragBoundry
2895 || position.x > fRightDragBoundry) {
2896 DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2899 #if DRAG_TITLE_OUTLINE
2900 // Set up the invalid rect to include the rect for the previous
2901 // position of the drag rect, as well as the new one.
2902 BRect invalidRect(fSelectedColumnRect);
2903 invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
2904 if (position.x < fCurrentDragPosition.x)
2905 invalidRect.left -= fCurrentDragPosition.x - position.x;
2906 else
2907 invalidRect.right += position.x - fCurrentDragPosition.x;
2909 fCurrentDragPosition = position;
2910 Invalidate(invalidRect);
2911 #endif
2912 break;
2915 case DRAG_COLUMN_OUTSIDE_TITLE:
2916 if (transit == B_ENTERED_VIEW) {
2917 // Drag back into view
2918 EndRectTracking();
2919 fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
2920 fSelectedColumn->SetVisible(true);
2921 DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
2924 break;
2926 case INACTIVE:
2927 // Check for cursor changes if we are over the resize area for
2928 // a column.
2929 BColumn* resizeColumn = 0;
2930 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
2931 for (int index = 0; index < fColumns->CountItems(); index++) {
2932 BColumn* column = (BColumn*) fColumns->ItemAt(index);
2933 if (!column->IsVisible())
2934 continue;
2936 if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
2937 break;
2939 float rightEdge = leftEdge + column->Width();
2940 if (position.x > rightEdge - kColumnResizeAreaWidth / 2
2941 && position.x < rightEdge + kColumnResizeAreaWidth / 2
2942 && column->MaxWidth() > column->MinWidth()) {
2943 resizeColumn = column;
2944 break;
2947 leftEdge = rightEdge + 1;
2950 // Update the cursor
2951 if (resizeColumn) {
2952 if (resizeColumn->Width() == resizeColumn->MinWidth())
2953 SetViewCursor(fMinResizeCursor, true);
2954 else if (resizeColumn->Width() == resizeColumn->MaxWidth())
2955 SetViewCursor(fMaxResizeCursor, true);
2956 else
2957 SetViewCursor(fResizeCursor, true);
2958 } else
2959 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
2960 break;
2965 void
2966 TitleView::MouseUp(BPoint position)
2968 if (fEditMode)
2969 return;
2971 switch (fCurrentState) {
2972 case RESIZING_COLUMN:
2973 ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
2974 fCurrentState = INACTIVE;
2975 FixScrollBar(false);
2976 break;
2978 case PRESSING_COLUMN: {
2979 if (fMasterView->SortingEnabled()) {
2980 if (fSortColumns->HasItem(fSelectedColumn)) {
2981 if ((modifiers() & B_CONTROL_KEY) == 0
2982 && fSortColumns->CountItems() > 1) {
2983 fSortColumns->MakeEmpty();
2984 fSortColumns->AddItem(fSelectedColumn);
2987 fSelectedColumn->fSortAscending
2988 = !fSelectedColumn->fSortAscending;
2989 } else {
2990 if ((modifiers() & B_CONTROL_KEY) == 0)
2991 fSortColumns->MakeEmpty();
2993 fSortColumns->AddItem(fSelectedColumn);
2994 fSelectedColumn->fSortAscending = true;
2997 fOutlineView->StartSorting();
3000 fCurrentState = INACTIVE;
3001 Invalidate();
3002 break;
3005 case DRAG_COLUMN_INSIDE_TITLE:
3006 fCurrentState = INACTIVE;
3008 #if DRAG_TITLE_OUTLINE
3009 Invalidate(); // xxx Can make this smaller
3010 #else
3011 Invalidate(fSelectedColumnRect);
3012 #endif
3013 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3014 break;
3016 case DRAG_COLUMN_OUTSIDE_TITLE:
3017 fCurrentState = INACTIVE;
3018 EndRectTracking();
3019 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
3020 break;
3022 default:
3028 void
3029 TitleView::FrameResized(float width, float height)
3031 fVisibleRect.right = fVisibleRect.left + width;
3032 fVisibleRect.bottom = fVisibleRect.top + height;
3033 FixScrollBar(true);
3037 // #pragma mark - OutlineView
3040 OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns,
3041 BColumnListView* listView)
3043 BView(rect, "outline_view", B_FOLLOW_ALL_SIDES,
3044 B_WILL_DRAW | B_FRAME_EVENTS),
3045 fColumns(visibleColumns),
3046 fSortColumns(sortColumns),
3047 fItemsHeight(0.0),
3048 fVisibleRect(rect.OffsetToCopy(0, 0)),
3049 fFocusRow(0),
3050 fRollOverRow(0),
3051 fLastSelectedItem(0),
3052 fFirstSelectedItem(0),
3053 fSortThread(B_BAD_THREAD_ID),
3054 fCurrentState(INACTIVE),
3055 fMasterView(listView),
3056 fSelectionMode(B_MULTIPLE_SELECTION_LIST),
3057 fTrackMouse(false),
3058 fCurrentField(0),
3059 fCurrentRow(0),
3060 fCurrentColumn(0),
3061 fMouseDown(false),
3062 fCurrentCode(B_OUTSIDE_VIEW),
3063 fEditMode(false),
3064 fDragging(false),
3065 fClickCount(0),
3066 fDropHighlightY(-1)
3068 SetViewColor(B_TRANSPARENT_COLOR);
3070 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3071 // TODO: This needs to be smart about the size of the buffer.
3072 // Also, the buffer can be shared with the title's buffer.
3073 BRect doubleBufferRect(0, 0, 600, 35);
3074 fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
3075 fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
3076 B_FOLLOW_ALL_SIDES, 0);
3077 fDrawBuffer->Lock();
3078 fDrawBuffer->AddChild(fDrawBufferView);
3079 fDrawBuffer->Unlock();
3080 #endif
3082 FixScrollBar(true);
3083 fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
3084 fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
3088 OutlineView::~OutlineView()
3090 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3091 delete fDrawBuffer;
3092 #endif
3094 Clear();
3098 void
3099 OutlineView::Clear()
3101 DeselectAll();
3102 // Make sure selection list doesn't point to deleted rows!
3103 RecursiveDeleteRows(&fRows, false);
3104 fItemsHeight = 0.0;
3105 FixScrollBar(true);
3106 Invalidate();
3110 void
3111 OutlineView::SetSelectionMode(list_view_type mode)
3113 DeselectAll();
3114 fSelectionMode = mode;
3118 list_view_type
3119 OutlineView::SelectionMode() const
3121 return fSelectionMode;
3125 void
3126 OutlineView::Deselect(BRow* row)
3128 if (row == NULL)
3129 return;
3131 if (row->fNextSelected != 0) {
3132 row->fNextSelected->fPrevSelected = row->fPrevSelected;
3133 row->fPrevSelected->fNextSelected = row->fNextSelected;
3134 row->fNextSelected = 0;
3135 row->fPrevSelected = 0;
3136 Invalidate();
3141 void
3142 OutlineView::AddToSelection(BRow* row)
3144 if (row == NULL)
3145 return;
3147 if (row->fNextSelected == 0) {
3148 if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3149 DeselectAll();
3151 row->fNextSelected = fSelectionListDummyHead.fNextSelected;
3152 row->fPrevSelected = &fSelectionListDummyHead;
3153 row->fNextSelected->fPrevSelected = row;
3154 row->fPrevSelected->fNextSelected = row;
3156 BRect invalidRect;
3157 if (FindVisibleRect(row, &invalidRect))
3158 Invalidate(invalidRect);
3163 void
3164 OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
3166 if (list == NULL)
3167 return;
3169 while (true) {
3170 BRow* row = list->RemoveItemAt(0L);
3171 if (row == 0)
3172 break;
3174 if (row->fChildList)
3175 RecursiveDeleteRows(row->fChildList, true);
3177 delete row;
3180 if (isOwner)
3181 delete list;
3185 void
3186 OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
3188 // TODO: Remove code duplication (private function which takes a view
3189 // pointer, pass "this" in non-double buffered mode)!
3190 // Watch out for sourceRect versus destRect though!
3191 if (!column)
3192 return;
3194 font_height fh;
3195 GetFontHeight(&fh);
3196 float line = 0.0;
3197 bool tintedLine = true;
3198 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3199 line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
3201 BRow* row = iterator.CurrentRow();
3202 float rowHeight = row->Height();
3203 if (line > fVisibleRect.bottom)
3204 break;
3205 tintedLine = !tintedLine;
3207 if (line + rowHeight >= fVisibleRect.top) {
3208 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3209 BRect sourceRect(0, 0, column->Width(), rowHeight);
3210 #endif
3211 BRect destRect(leftEdge, line, leftEdge + column->Width(),
3212 line + rowHeight);
3214 rgb_color highColor;
3215 rgb_color lowColor;
3216 if (row->fNextSelected != 0) {
3217 if (fEditMode) {
3218 highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3219 lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3220 } else {
3221 highColor = fMasterView->Color(B_COLOR_SELECTION);
3222 lowColor = fMasterView->Color(B_COLOR_SELECTION);
3224 } else {
3225 highColor = fMasterView->Color(B_COLOR_BACKGROUND);
3226 lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3228 if (tintedLine)
3229 lowColor = tint_color(lowColor, kTintedLineTint);
3232 #if DOUBLE_BUFFERED_COLUMN_RESIZE
3233 fDrawBuffer->Lock();
3235 fDrawBufferView->SetHighColor(highColor);
3236 fDrawBufferView->SetLowColor(lowColor);
3238 BFont font;
3239 GetFont(&font);
3240 fDrawBufferView->SetFont(&font);
3241 fDrawBufferView->FillRect(sourceRect, B_SOLID_LOW);
3243 if (isFirstColumn) {
3244 // If this is the first column, double buffer drawing the latch
3245 // too.
3246 destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3247 - fMasterView->LatchWidth();
3248 sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
3249 - fMasterView->LatchWidth();
3251 LatchType pos = B_NO_LATCH;
3252 if (row->HasLatch())
3253 pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH;
3255 BRect latchRect(sourceRect);
3256 latchRect.right = latchRect.left + fMasterView->LatchWidth();
3257 fMasterView->DrawLatch(fDrawBufferView, latchRect, pos, row);
3260 BField* field = row->GetField(column->fFieldID);
3261 if (field) {
3262 BRect fieldRect(sourceRect);
3263 if (isFirstColumn)
3264 fieldRect.left += fMasterView->LatchWidth();
3266 #if CONSTRAIN_CLIPPING_REGION
3267 BRegion clipRegion(fieldRect);
3268 fDrawBufferView->PushState();
3269 fDrawBufferView->ConstrainClippingRegion(&clipRegion);
3270 #endif
3271 fDrawBufferView->SetHighColor(fMasterView->Color(
3272 row->fNextSelected ? B_COLOR_SELECTION_TEXT
3273 : B_COLOR_TEXT));
3274 float baseline = floor(fieldRect.top + fh.ascent
3275 + (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
3276 fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
3277 column->DrawField(field, fieldRect, fDrawBufferView);
3278 #if CONSTRAIN_CLIPPING_REGION
3279 fDrawBufferView->PopState();
3280 #endif
3283 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3284 && Window()->IsActive()) {
3285 fDrawBufferView->SetHighColor(fMasterView->Color(
3286 B_COLOR_ROW_DIVIDER));
3287 fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top,
3288 10000.0, sourceRect.bottom));
3291 fDrawBufferView->Sync();
3292 fDrawBuffer->Unlock();
3293 SetDrawingMode(B_OP_COPY);
3294 DrawBitmap(fDrawBuffer, sourceRect, destRect);
3296 #else
3298 SetHighColor(highColor);
3299 SetLowColor(lowColor);
3300 FillRect(destRect, B_SOLID_LOW);
3302 BField* field = row->GetField(column->fFieldID);
3303 if (field) {
3304 #if CONSTRAIN_CLIPPING_REGION
3305 BRegion clipRegion(destRect);
3306 PushState();
3307 ConstrainClippingRegion(&clipRegion);
3308 #endif
3309 SetHighColor(fMasterView->Color(row->fNextSelected
3310 ? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
3311 float baseline = floor(destRect.top + fh.ascent
3312 + (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
3313 MovePenTo(destRect.left + 8, baseline);
3314 column->DrawField(field, destRect, this);
3315 #if CONSTRAIN_CLIPPING_REGION
3316 PopState();
3317 #endif
3320 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3321 && Window()->IsActive()) {
3322 SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3323 StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
3325 #endif
3331 void
3332 OutlineView::Draw(BRect invalidBounds)
3334 #if SMART_REDRAW
3335 BRegion invalidRegion;
3336 GetClippingRegion(&invalidRegion);
3337 #endif
3339 font_height fh;
3340 GetFontHeight(&fh);
3342 float line = 0.0;
3343 bool tintedLine = true;
3344 int32 numColumns = fColumns->CountItems();
3345 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3346 iterator.GoToNext()) {
3347 BRow* row = iterator.CurrentRow();
3348 if (line > invalidBounds.bottom)
3349 break;
3351 tintedLine = !tintedLine;
3352 float rowHeight = row->Height();
3354 if (line >= invalidBounds.top - rowHeight) {
3355 bool isFirstColumn = true;
3356 float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
3358 // setup background color
3359 rgb_color lowColor;
3360 if (row->fNextSelected != 0) {
3361 if (Window()->IsActive()) {
3362 if (fEditMode)
3363 lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
3364 else
3365 lowColor = fMasterView->Color(B_COLOR_SELECTION);
3367 else
3368 lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
3369 } else
3370 lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
3371 if (tintedLine)
3372 lowColor = tint_color(lowColor, kTintedLineTint);
3374 for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) {
3375 BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
3376 if (!column->IsVisible())
3377 continue;
3379 if (!isFirstColumn && fieldLeftEdge > invalidBounds.right)
3380 break;
3382 if (fieldLeftEdge + column->Width() >= invalidBounds.left) {
3383 BRect fullRect(fieldLeftEdge, line,
3384 fieldLeftEdge + column->Width(), line + rowHeight);
3386 bool clippedFirstColumn = false;
3387 // This happens when a column is indented past the
3388 // beginning of the next column.
3390 SetHighColor(lowColor);
3392 BRect destRect(fullRect);
3393 if (isFirstColumn) {
3394 fullRect.left -= fMasterView->LatchWidth();
3395 destRect.left += iterator.CurrentLevel()
3396 * kOutlineLevelIndent;
3397 if (destRect.left >= destRect.right) {
3398 // clipped
3399 FillRect(BRect(0, line, fieldLeftEdge
3400 + column->Width(), line + rowHeight));
3401 clippedFirstColumn = true;
3404 FillRect(BRect(0, line, MAX(kLeftMargin,
3405 fMasterView->LatchWidth()), line + row->Height()));
3409 #if SMART_REDRAW
3410 if (!clippedFirstColumn
3411 && invalidRegion.Intersects(fullRect)) {
3412 #else
3413 if (!clippedFirstColumn) {
3414 #endif
3415 FillRect(fullRect); // Using color set above
3417 // Draw the latch widget if it has one.
3418 if (isFirstColumn) {
3419 if (row == fTargetRow
3420 && fCurrentState == LATCH_CLICKED) {
3421 // Note that this only occurs if the user is
3422 // holding down a latch while items are added
3423 // in the background.
3424 BPoint pos;
3425 uint32 buttons;
3426 GetMouse(&pos, &buttons);
3427 if (fLatchRect.Contains(pos)) {
3428 fMasterView->DrawLatch(this, fLatchRect,
3429 B_PRESSED_LATCH, fTargetRow);
3430 } else {
3431 fMasterView->DrawLatch(this, fLatchRect,
3432 row->fIsExpanded ? B_OPEN_LATCH
3433 : B_CLOSED_LATCH, fTargetRow);
3435 } else {
3436 LatchType pos = B_NO_LATCH;
3437 if (row->HasLatch())
3438 pos = row->fIsExpanded ? B_OPEN_LATCH
3439 : B_CLOSED_LATCH;
3441 fMasterView->DrawLatch(this,
3442 BRect(destRect.left
3443 - fMasterView->LatchWidth(),
3444 destRect.top, destRect.left,
3445 destRect.bottom), pos, row);
3449 SetHighColor(fMasterView->HighColor());
3450 // The master view just holds the high color for us.
3451 SetLowColor(lowColor);
3453 BField* field = row->GetField(column->fFieldID);
3454 if (field) {
3455 #if CONSTRAIN_CLIPPING_REGION
3456 BRegion clipRegion(destRect);
3457 PushState();
3458 ConstrainClippingRegion(&clipRegion);
3459 #endif
3460 SetHighColor(fMasterView->Color(
3461 row->fNextSelected ? B_COLOR_SELECTION_TEXT
3462 : B_COLOR_TEXT));
3463 float baseline = floor(destRect.top + fh.ascent
3464 + (destRect.Height() + 1
3465 - (fh.ascent+fh.descent)) / 2);
3466 MovePenTo(destRect.left + 8, baseline);
3467 column->DrawField(field, destRect, this);
3468 #if CONSTRAIN_CLIPPING_REGION
3469 PopState();
3470 #endif
3475 isFirstColumn = false;
3476 fieldLeftEdge += column->Width() + 1;
3479 if (fieldLeftEdge <= invalidBounds.right) {
3480 SetHighColor(lowColor);
3481 FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
3482 line + rowHeight));
3486 // indicate the keyboard focus row
3487 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
3488 && Window()->IsActive()) {
3489 SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
3490 StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
3493 line += rowHeight + 1;
3496 if (line <= invalidBounds.bottom) {
3497 // fill background below last item
3498 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3499 FillRect(BRect(invalidBounds.left, line, invalidBounds.right,
3500 invalidBounds.bottom));
3503 // Draw the drop target line
3504 if (fDropHighlightY != -1) {
3505 InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3506 1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3511 BRow*
3512 OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top)
3514 if (_rowIndent && _top) {
3515 float line = 0.0;
3516 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
3517 iterator.GoToNext()) {
3519 BRow* row = iterator.CurrentRow();
3520 if (line > ypos)
3521 break;
3523 float rowHeight = row->Height();
3524 if (ypos <= line + rowHeight) {
3525 *_top = line;
3526 *_rowIndent = iterator.CurrentLevel();
3527 return row;
3530 line += rowHeight + 1;
3534 return NULL;
3537 void OutlineView::SetMouseTrackingEnabled(bool enabled)
3539 fTrackMouse = enabled;
3540 if (!enabled && fDropHighlightY != -1) {
3541 // Erase the old target line
3542 InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
3543 1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3544 fDropHighlightY = -1;
3550 // Note that this interaction is not totally safe. If items are added to
3551 // the list in the background, the widget rect will be incorrect, possibly
3552 // resulting in drawing glitches. The code that adds items needs to be a little smarter
3553 // about invalidating state.
3555 void
3556 OutlineView::MouseDown(BPoint position)
3558 if (!fEditMode)
3559 fMasterView->MakeFocus(true);
3561 // Check to see if the user is clicking on a widget to open a section
3562 // of the list.
3563 bool reset_click_count = false;
3564 int32 indent;
3565 float rowTop;
3566 BRow* row = FindRow(position.y, &indent, &rowTop);
3567 if (row != NULL) {
3569 // Update fCurrentField
3570 bool handle_field = false;
3571 BField* new_field = 0;
3572 BRow* new_row = 0;
3573 BColumn* new_column = 0;
3574 BRect new_rect;
3576 if (position.y >= 0) {
3577 if (position.x >= 0) {
3578 float x = 0;
3579 for (int32 c = 0; c < fMasterView->CountColumns(); c++) {
3580 new_column = fMasterView->ColumnAt(c);
3581 if (!new_column->IsVisible())
3582 continue;
3583 if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x)
3584 + new_column->Width() >= position.x) {
3585 if (new_column->WantsEvents()) {
3586 new_field = row->GetField(c);
3587 new_row = row;
3588 FindRect(new_row,&new_rect);
3589 new_rect.left = MAX(kLeftMargin,
3590 fMasterView->LatchWidth()) + x;
3591 new_rect.right = new_rect.left
3592 + new_column->Width() - 1;
3593 handle_field = true;
3595 break;
3597 x += new_column->Width();
3602 // Handle mouse down
3603 if (handle_field) {
3604 fMouseDown = true;
3605 fFieldRect = new_rect;
3606 fCurrentColumn = new_column;
3607 fCurrentRow = new_row;
3608 fCurrentField = new_field;
3609 fCurrentCode = B_INSIDE_VIEW;
3610 BMessage* message = Window()->CurrentMessage();
3611 int32 buttons = 1;
3612 message->FindInt32("buttons", &buttons);
3613 fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
3614 fCurrentField, fFieldRect, position, buttons);
3617 if (!fEditMode) {
3619 fTargetRow = row;
3620 fTargetRowTop = rowTop;
3621 FindVisibleRect(fFocusRow, &fFocusRowRect);
3623 float leftWidgetBoundry = indent * kOutlineLevelIndent
3624 + MAX(kLeftMargin, fMasterView->LatchWidth())
3625 - fMasterView->LatchWidth();
3626 fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
3627 + fMasterView->LatchWidth(), rowTop + row->Height());
3628 if (fLatchRect.Contains(position) && row->HasLatch()) {
3629 fCurrentState = LATCH_CLICKED;
3630 if (fTargetRow->fNextSelected != 0)
3631 SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3632 else
3633 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3635 FillRect(fLatchRect);
3636 if (fLatchRect.Contains(position)) {
3637 fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3638 row);
3639 } else {
3640 fMasterView->DrawLatch(this, fLatchRect,
3641 fTargetRow->fIsExpanded ? B_OPEN_LATCH
3642 : B_CLOSED_LATCH, row);
3644 } else {
3645 Invalidate(fFocusRowRect);
3646 fFocusRow = fTargetRow;
3647 FindVisibleRect(fFocusRow, &fFocusRowRect);
3649 ASSERT(fTargetRow != 0);
3651 if ((modifiers() & B_CONTROL_KEY) == 0)
3652 DeselectAll();
3654 if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
3655 && fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
3656 SelectRange(fFirstSelectedItem, fTargetRow);
3658 else {
3659 if (fTargetRow->fNextSelected != 0) {
3660 // Unselect row
3661 fTargetRow->fNextSelected->fPrevSelected
3662 = fTargetRow->fPrevSelected;
3663 fTargetRow->fPrevSelected->fNextSelected
3664 = fTargetRow->fNextSelected;
3665 fTargetRow->fPrevSelected = 0;
3666 fTargetRow->fNextSelected = 0;
3667 fFirstSelectedItem = NULL;
3668 } else {
3669 // Select row
3670 if (fSelectionMode == B_SINGLE_SELECTION_LIST)
3671 DeselectAll();
3673 fTargetRow->fNextSelected
3674 = fSelectionListDummyHead.fNextSelected;
3675 fTargetRow->fPrevSelected
3676 = &fSelectionListDummyHead;
3677 fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
3678 fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
3679 fFirstSelectedItem = fTargetRow;
3682 Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
3683 fVisibleRect.right,
3684 fTargetRowTop + fTargetRow->Height()));
3687 fCurrentState = ROW_CLICKED;
3688 if (fLastSelectedItem != fTargetRow)
3689 reset_click_count = true;
3690 fLastSelectedItem = fTargetRow;
3691 fMasterView->SelectionChanged();
3696 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
3697 B_NO_POINTER_HISTORY);
3699 } else if (fFocusRow != 0) {
3700 // User clicked in open space, unhighlight focus row.
3701 FindVisibleRect(fFocusRow, &fFocusRowRect);
3702 fFocusRow = 0;
3703 Invalidate(fFocusRowRect);
3706 // We stash the click counts here because the 'clicks' field
3707 // is not in the CurrentMessage() when MouseUp is called... ;(
3708 if (reset_click_count)
3709 fClickCount = 1;
3710 else
3711 Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
3712 fClickPoint = position;
3717 void
3718 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
3719 const BMessage* /*dragMessage*/)
3721 if (!fMouseDown) {
3722 // Update fCurrentField
3723 bool handle_field = false;
3724 BField* new_field = 0;
3725 BRow* new_row = 0;
3726 BColumn* new_column = 0;
3727 BRect new_rect(0,0,0,0);
3728 if (position.y >=0 ) {
3729 float top;
3730 int32 indent;
3731 BRow* row = FindRow(position.y, &indent, &top);
3732 if (row && position.x >=0 ) {
3733 float x=0;
3734 for (int32 c=0;c<fMasterView->CountColumns();c++) {
3735 new_column = fMasterView->ColumnAt(c);
3736 if (!new_column->IsVisible())
3737 continue;
3738 if ((MAX(kLeftMargin,
3739 fMasterView->LatchWidth()) + x) + new_column->Width()
3740 > position.x) {
3742 if(new_column->WantsEvents()) {
3743 new_field = row->GetField(c);
3744 new_row = row;
3745 FindRect(new_row,&new_rect);
3746 new_rect.left = MAX(kLeftMargin,
3747 fMasterView->LatchWidth()) + x;
3748 new_rect.right = new_rect.left
3749 + new_column->Width() - 1;
3750 handle_field = true;
3752 break;
3754 x += new_column->Width();
3759 // Handle mouse moved
3760 if (handle_field) {
3761 if (new_field != fCurrentField) {
3762 if (fCurrentField) {
3763 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3764 fCurrentField, fFieldRect, position, 0,
3765 fCurrentCode = B_EXITED_VIEW);
3767 fCurrentColumn = new_column;
3768 fCurrentRow = new_row;
3769 fCurrentField = new_field;
3770 fFieldRect = new_rect;
3771 if (fCurrentField) {
3772 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3773 fCurrentField, fFieldRect, position, 0,
3774 fCurrentCode = B_ENTERED_VIEW);
3776 } else {
3777 if (fCurrentField) {
3778 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3779 fCurrentField, fFieldRect, position, 0,
3780 fCurrentCode = B_INSIDE_VIEW);
3783 } else {
3784 if (fCurrentField) {
3785 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3786 fCurrentField, fFieldRect, position, 0,
3787 fCurrentCode = B_EXITED_VIEW);
3788 fCurrentField = 0;
3789 fCurrentColumn = 0;
3790 fCurrentRow = 0;
3793 } else {
3794 if (fCurrentField) {
3795 if (fFieldRect.Contains(position)) {
3796 if (fCurrentCode == B_OUTSIDE_VIEW
3797 || fCurrentCode == B_EXITED_VIEW) {
3798 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3799 fCurrentField, fFieldRect, position, 1,
3800 fCurrentCode = B_ENTERED_VIEW);
3801 } else {
3802 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3803 fCurrentField, fFieldRect, position, 1,
3804 fCurrentCode = B_INSIDE_VIEW);
3806 } else {
3807 if (fCurrentCode == B_INSIDE_VIEW
3808 || fCurrentCode == B_ENTERED_VIEW) {
3809 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3810 fCurrentField, fFieldRect, position, 1,
3811 fCurrentCode = B_EXITED_VIEW);
3812 } else {
3813 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
3814 fCurrentField, fFieldRect, position, 1,
3815 fCurrentCode = B_OUTSIDE_VIEW);
3821 if (!fEditMode) {
3823 switch (fCurrentState) {
3824 case LATCH_CLICKED:
3825 if (fTargetRow->fNextSelected != 0)
3826 SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
3827 else
3828 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
3830 FillRect(fLatchRect);
3831 if (fLatchRect.Contains(position)) {
3832 fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
3833 fTargetRow);
3834 } else {
3835 fMasterView->DrawLatch(this, fLatchRect,
3836 fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
3837 fTargetRow);
3839 break;
3841 case ROW_CLICKED:
3842 if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
3843 || abs((int)(position.y - fClickPoint.y))
3844 > kRowDragSensitivity) {
3845 fCurrentState = DRAGGING_ROWS;
3846 fMasterView->InitiateDrag(fClickPoint,
3847 fTargetRow->fNextSelected != 0);
3849 break;
3851 case DRAGGING_ROWS:
3852 #if 0
3853 // falls through...
3854 #else
3855 if (fTrackMouse /*&& message*/) {
3856 if (fVisibleRect.Contains(position)) {
3857 float top;
3858 int32 indent;
3859 BRow* target = FindRow(position.y, &indent, &top);
3860 if (target)
3861 SetFocusRow(target, true);
3864 break;
3865 #endif
3867 default: {
3869 if (fTrackMouse /*&& message*/) {
3870 // Draw a highlight line...
3871 if (fVisibleRect.Contains(position)) {
3872 float top;
3873 int32 indent;
3874 BRow* target = FindRow(position.y, &indent, &top);
3875 if (target == fRollOverRow)
3876 break;
3877 if (fRollOverRow) {
3878 BRect rect;
3879 FindRect(fRollOverRow, &rect);
3880 Invalidate(rect);
3882 fRollOverRow = target;
3883 #if 0
3884 SetFocusRow(fRollOverRow,false);
3885 #else
3886 PushState();
3887 SetDrawingMode(B_OP_BLEND);
3888 SetHighColor(255, 255, 255, 255);
3889 BRect rect;
3890 FindRect(fRollOverRow, &rect);
3891 rect.bottom -= 1.0;
3892 FillRect(rect);
3893 PopState();
3894 #endif
3895 } else {
3896 if (fRollOverRow) {
3897 BRect rect;
3898 FindRect(fRollOverRow, &rect);
3899 Invalidate(rect);
3900 fRollOverRow = NULL;
3910 void
3911 OutlineView::MouseUp(BPoint position)
3913 if (fCurrentField) {
3914 fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
3915 fMouseDown = false;
3918 if (fEditMode)
3919 return;
3921 switch (fCurrentState) {
3922 case LATCH_CLICKED:
3923 if (fLatchRect.Contains(position)) {
3924 fMasterView->ExpandOrCollapse(fTargetRow,
3925 !fTargetRow->fIsExpanded);
3928 Invalidate(fLatchRect);
3929 fCurrentState = INACTIVE;
3930 break;
3932 case ROW_CLICKED:
3933 if (fClickCount > 1
3934 && abs((int)fClickPoint.x - (int)position.x)
3935 < kDoubleClickMoveSensitivity
3936 && abs((int)fClickPoint.y - (int)position.y)
3937 < kDoubleClickMoveSensitivity) {
3938 fMasterView->ItemInvoked();
3940 fCurrentState = INACTIVE;
3941 break;
3943 case DRAGGING_ROWS:
3944 fCurrentState = INACTIVE;
3945 // Falls through
3947 default:
3948 if (fDropHighlightY != -1) {
3949 InvertRect(BRect(0,
3950 fDropHighlightY - kDropHighlightLineHeight / 2,
3951 1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
3952 // Erase the old target line
3953 fDropHighlightY = -1;
3959 void
3960 OutlineView::MessageReceived(BMessage* message)
3962 if (message->WasDropped()) {
3963 fMasterView->MessageDropped(message,
3964 ConvertFromScreen(message->DropPoint()));
3965 } else {
3966 BView::MessageReceived(message);
3971 void
3972 OutlineView::ChangeFocusRow(bool up, bool updateSelection,
3973 bool addToCurrentSelection)
3975 int32 indent;
3976 float top;
3977 float newRowPos = 0;
3978 float verticalScroll = 0;
3980 if (fFocusRow) {
3981 // A row currently has the focus, get information about it
3982 newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
3983 if (newRowPos < fVisibleRect.top + 20)
3984 verticalScroll = newRowPos - 20;
3985 else if (newRowPos > fVisibleRect.bottom - 20)
3986 verticalScroll = newRowPos - fVisibleRect.Height() + 20;
3987 } else
3988 newRowPos = fVisibleRect.top + 2;
3989 // no row is currently focused, set this to the top of the window
3990 // so we will select the first visible item in the list.
3992 BRow* newRow = FindRow(newRowPos, &indent, &top);
3993 if (newRow) {
3994 if (fFocusRow) {
3995 fFocusRowRect.right = 10000;
3996 Invalidate(fFocusRowRect);
3998 BRow* oldFocusRow = fFocusRow;
3999 fFocusRow = newRow;
4000 fFocusRowRect.top = top;
4001 fFocusRowRect.left = 0;
4002 fFocusRowRect.right = 10000;
4003 fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
4004 Invalidate(fFocusRowRect);
4006 if (updateSelection) {
4007 if (!addToCurrentSelection
4008 || fSelectionMode == B_SINGLE_SELECTION_LIST) {
4009 DeselectAll();
4012 // if the focus row isn't selected, add it to the selection
4013 if (fFocusRow->fNextSelected == 0) {
4014 fFocusRow->fNextSelected
4015 = fSelectionListDummyHead.fNextSelected;
4016 fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4017 fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4018 fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4019 } else if (oldFocusRow != NULL
4020 && fSelectionListDummyHead.fNextSelected == oldFocusRow
4021 && (((IndexOf(oldFocusRow->fNextSelected)
4022 < IndexOf(oldFocusRow)) == up)
4023 || fFocusRow == oldFocusRow->fNextSelected)) {
4024 // if the focus row is selected, if:
4025 // 1. the previous focus row is last in the selection
4026 // 2a. the next selected row is now the focus row
4027 // 2b. or the next selected row is beyond the focus row
4028 // in the move direction
4029 // then deselect the previous focus row
4030 fSelectionListDummyHead.fNextSelected
4031 = oldFocusRow->fNextSelected;
4032 if (fSelectionListDummyHead.fNextSelected != NULL) {
4033 fSelectionListDummyHead.fNextSelected->fPrevSelected
4034 = &fSelectionListDummyHead;
4035 oldFocusRow->fNextSelected = NULL;
4037 oldFocusRow->fPrevSelected = NULL;
4040 fLastSelectedItem = fFocusRow;
4042 } else
4043 Invalidate(fFocusRowRect);
4045 if (verticalScroll != 0) {
4046 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4047 float min, max;
4048 vScrollBar->GetRange(&min, &max);
4049 if (verticalScroll < min)
4050 verticalScroll = min;
4051 else if (verticalScroll > max)
4052 verticalScroll = max;
4054 vScrollBar->SetValue(verticalScroll);
4057 if (newRow && updateSelection)
4058 fMasterView->SelectionChanged();
4062 void
4063 OutlineView::MoveFocusToVisibleRect()
4065 fFocusRow = 0;
4066 ChangeFocusRow(true, true, false);
4070 BRow*
4071 OutlineView::CurrentSelection(BRow* lastSelected) const
4073 BRow* row;
4074 if (lastSelected == 0)
4075 row = fSelectionListDummyHead.fNextSelected;
4076 else
4077 row = lastSelected->fNextSelected;
4080 if (row == &fSelectionListDummyHead)
4081 row = 0;
4083 return row;
4087 void
4088 OutlineView::ToggleFocusRowSelection(bool selectRange)
4090 if (fFocusRow == 0)
4091 return;
4093 if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
4094 SelectRange(fLastSelectedItem, fFocusRow);
4095 else {
4096 if (fFocusRow->fNextSelected != 0) {
4097 // Unselect row
4098 fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
4099 fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
4100 fFocusRow->fPrevSelected = 0;
4101 fFocusRow->fNextSelected = 0;
4102 } else {
4103 // Select row
4104 if (fSelectionMode == B_SINGLE_SELECTION_LIST)
4105 DeselectAll();
4107 fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
4108 fFocusRow->fPrevSelected = &fSelectionListDummyHead;
4109 fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
4110 fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
4114 fLastSelectedItem = fFocusRow;
4115 fMasterView->SelectionChanged();
4116 Invalidate(fFocusRowRect);
4120 void
4121 OutlineView::ToggleFocusRowOpen()
4123 if (fFocusRow)
4124 fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
4128 void
4129 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
4131 // TODO: Could use CopyBits here to speed things up.
4133 if (parentRow == NULL)
4134 return;
4136 if (parentRow->fIsExpanded == expand)
4137 return;
4139 parentRow->fIsExpanded = expand;
4141 BRect parentRect;
4142 if (FindRect(parentRow, &parentRect)) {
4143 // Determine my new height
4144 float subTreeHeight = 0.0;
4145 if (parentRow->fIsExpanded)
4146 for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4147 iterator.CurrentRow();
4148 iterator.GoToNext()
4151 subTreeHeight += iterator.CurrentRow()->Height()+1;
4153 else
4154 for (RecursiveOutlineIterator iterator(parentRow->fChildList);
4155 iterator.CurrentRow();
4156 iterator.GoToNext()
4159 subTreeHeight -= iterator.CurrentRow()->Height()+1;
4161 fItemsHeight += subTreeHeight;
4163 // Adjust focus row if necessary.
4164 if (FindRect(fFocusRow, &fFocusRowRect) == false) {
4165 // focus row is in a subtree that has collapsed,
4166 // move it up to the parent.
4167 fFocusRow = parentRow;
4168 FindRect(fFocusRow, &fFocusRowRect);
4171 Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
4172 fVisibleRect.bottom));
4173 FixScrollBar(false);
4177 void
4178 OutlineView::RemoveRow(BRow* row)
4180 if (row == NULL)
4181 return;
4183 BRow* parentRow;
4184 bool parentIsVisible;
4185 FindParent(row, &parentRow, &parentIsVisible);
4186 // NOTE: This could be a root row without a parent, in which case
4187 // it is always visible, though.
4189 // Adjust height for the visible sub-tree that is going to be removed.
4190 float subTreeHeight = 0.0f;
4191 if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4192 // The row itself is visible at least.
4193 subTreeHeight = row->Height() + 1;
4194 if (row->fIsExpanded) {
4195 // Adjust for the height of visible sub-items as well.
4196 // (By default, the iterator follows open branches only.)
4197 for (RecursiveOutlineIterator iterator(row->fChildList);
4198 iterator.CurrentRow(); iterator.GoToNext())
4199 subTreeHeight += iterator.CurrentRow()->Height() + 1;
4201 BRect invalid;
4202 if (FindRect(row, &invalid)) {
4203 invalid.bottom = Bounds().bottom;
4204 if (invalid.IsValid())
4205 Invalidate(invalid);
4209 fItemsHeight -= subTreeHeight;
4211 FixScrollBar(false);
4212 int32 indent = 0;
4213 float top = 0.0;
4214 if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
4215 // after removing this row, no rows are actually visible any more,
4216 // force a scroll to make them visible again
4217 if (fItemsHeight > fVisibleRect.Height())
4218 ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
4219 else
4220 ScrollBy(0.0, -Bounds().top);
4222 if (parentRow != NULL) {
4223 parentRow->fChildList->RemoveItem(row);
4224 if (parentRow->fChildList->CountItems() == 0) {
4225 delete parentRow->fChildList;
4226 parentRow->fChildList = 0;
4227 // It was the last child row of the parent, which also means the
4228 // latch disappears.
4229 BRect parentRowRect;
4230 if (parentIsVisible && FindRect(parentRow, &parentRowRect))
4231 Invalidate(parentRowRect);
4233 } else
4234 fRows.RemoveItem(row);
4236 // Adjust focus row if necessary.
4237 if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
4238 // focus row is in a subtree that is gone, move it up to the parent.
4239 fFocusRow = parentRow;
4240 if (fFocusRow)
4241 FindRect(fFocusRow, &fFocusRowRect);
4244 // Remove this from the selection if necessary
4245 if (row->fNextSelected != 0) {
4246 row->fNextSelected->fPrevSelected = row->fPrevSelected;
4247 row->fPrevSelected->fNextSelected = row->fNextSelected;
4248 row->fPrevSelected = 0;
4249 row->fNextSelected = 0;
4250 fMasterView->SelectionChanged();
4253 fCurrentColumn = 0;
4254 fCurrentRow = 0;
4255 fCurrentField = 0;
4259 BRowContainer*
4260 OutlineView::RowList()
4262 return &fRows;
4266 void
4267 OutlineView::UpdateRow(BRow* row)
4269 if (row) {
4270 // Determine if this row has changed its sort order
4271 BRow* parentRow = NULL;
4272 bool parentIsVisible = false;
4273 FindParent(row, &parentRow, &parentIsVisible);
4275 BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
4277 if(list) {
4278 int32 rowIndex = list->IndexOf(row);
4279 ASSERT(rowIndex >= 0);
4280 ASSERT(list->ItemAt(rowIndex) == row);
4282 bool rowMoved = false;
4283 if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
4284 rowMoved = true;
4286 if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
4287 row) < 0)
4288 rowMoved = true;
4290 if (rowMoved) {
4291 // Sort location of this row has changed.
4292 // Remove and re-add in the right spot
4293 SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
4294 } else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
4295 BRect invalidRect;
4296 if (FindVisibleRect(row, &invalidRect))
4297 Invalidate(invalidRect);
4304 void
4305 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
4307 if (!row)
4308 return;
4310 row->fParent = parentRow;
4312 if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) {
4313 // Ignore index here.
4314 if (parentRow) {
4315 if (parentRow->fChildList == NULL)
4316 parentRow->fChildList = new BRowContainer;
4318 AddSorted(parentRow->fChildList, row);
4319 } else
4320 AddSorted(&fRows, row);
4321 } else {
4322 // Note, a -1 index implies add to end if sorting is not enabled
4323 if (parentRow) {
4324 if (parentRow->fChildList == 0)
4325 parentRow->fChildList = new BRowContainer;
4327 if (Index < 0 || Index > parentRow->fChildList->CountItems())
4328 parentRow->fChildList->AddItem(row);
4329 else
4330 parentRow->fChildList->AddItem(row, Index);
4331 } else {
4332 if (Index < 0 || Index >= fRows.CountItems())
4333 fRows.AddItem(row);
4334 else
4335 fRows.AddItem(row, Index);
4339 if (parentRow == 0 || parentRow->fIsExpanded)
4340 fItemsHeight += row->Height() + 1;
4342 FixScrollBar(false);
4344 BRect newRowRect;
4345 bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
4347 if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
4348 // The focus row has moved.
4349 Invalidate(fFocusRowRect);
4350 FindRect(fFocusRow, &fFocusRowRect);
4351 Invalidate(fFocusRowRect);
4354 if (newRowIsInOpenBranch) {
4355 if (fCurrentState == INACTIVE) {
4356 if (newRowRect.bottom < fVisibleRect.top) {
4357 // The new row is totally above the current viewport, move
4358 // everything down and redraw the first line.
4359 BRect source(fVisibleRect);
4360 BRect dest(fVisibleRect);
4361 source.bottom -= row->Height() + 1;
4362 dest.top += row->Height() + 1;
4363 CopyBits(source, dest);
4364 Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
4365 fVisibleRect.top + newRowRect.Height()));
4366 } else if (newRowRect.top < fVisibleRect.bottom) {
4367 // New item is somewhere in the current region. Scroll everything
4368 // beneath it down and invalidate just the new row rect.
4369 BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
4370 fVisibleRect.bottom - newRowRect.Height());
4371 BRect dest(source);
4372 dest.OffsetBy(0, newRowRect.Height() + 1);
4373 CopyBits(source, dest);
4374 Invalidate(newRowRect);
4375 } // otherwise, this is below the currently visible region
4376 } else {
4377 // Adding the item may have caused the item that the user is currently
4378 // selected to move. This would cause annoying drawing and interaction
4379 // bugs, as the position of that item is cached. If this happens, resize
4380 // the scroll bar, then scroll back so the selected item is in view.
4381 BRect targetRect;
4382 if (FindRect(fTargetRow, &targetRect)) {
4383 float delta = targetRect.top - fTargetRowTop;
4384 if (delta != 0) {
4385 // This causes a jump because ScrollBy will copy a chunk of the view.
4386 // Since the actual contents of the view have been offset, we don't
4387 // want this, we just want to change the virtual origin of the window.
4388 // Constrain the clipping region so everything is clipped out so no
4389 // copy occurs.
4391 // xxx this currently doesn't work if the scroll bars aren't enabled.
4392 // everything will still move anyway. A minor annoyance.
4393 BRegion emptyRegion;
4394 ConstrainClippingRegion(&emptyRegion);
4395 PushState();
4396 ScrollBy(0, delta);
4397 PopState();
4398 ConstrainClippingRegion(NULL);
4400 fTargetRowTop += delta;
4401 fClickPoint.y += delta;
4402 fLatchRect.OffsetBy(0, delta);
4408 // If the parent was previously childless, it will need to have a latch
4409 // drawn.
4410 BRect parentRect;
4411 if (parentRow && parentRow->fChildList->CountItems() == 1
4412 && FindVisibleRect(parentRow, &parentRect))
4413 Invalidate(parentRect);
4417 void
4418 OutlineView::FixScrollBar(bool scrollToFit)
4420 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4421 if (vScrollBar) {
4422 if (fItemsHeight > fVisibleRect.Height()) {
4423 float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4424 vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
4426 // If the user is scrolled down too far when making the range smaller, the list
4427 // will jump suddenly, which is undesirable. In this case, don't fix the scroll
4428 // bar here. In ScrollTo, it checks to see if this has occured, and will
4429 // fix the scroll bars sneakily if the user has scrolled up far enough.
4430 if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
4431 vScrollBar->SetRange(0.0, maxScrollBarValue);
4432 vScrollBar->SetSteps(20.0, fVisibleRect.Height());
4434 } else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0)
4435 vScrollBar->SetRange(0.0, 0.0); // disable scroll bar.
4440 void
4441 OutlineView::AddSorted(BRowContainer* list, BRow* row)
4443 if (list && row) {
4444 // Find general vicinity with binary search.
4445 int32 lower = 0;
4446 int32 upper = list->CountItems()-1;
4447 while( lower < upper ) {
4448 int32 middle = lower + (upper-lower+1)/2;
4449 int32 cmp = CompareRows(row, list->ItemAt(middle));
4450 if( cmp < 0 ) upper = middle-1;
4451 else if( cmp > 0 ) lower = middle+1;
4452 else lower = upper = middle;
4455 // At this point, 'upper' and 'lower' at the last found item.
4456 // Arbitrarily use 'upper' and determine the final insertion
4457 // point -- either before or after this item.
4458 if( upper < 0 ) upper = 0;
4459 else if( upper < list->CountItems() ) {
4460 if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
4463 if (upper >= list->CountItems())
4464 list->AddItem(row); // Adding to end.
4465 else
4466 list->AddItem(row, upper); // Insert
4471 int32
4472 OutlineView::CompareRows(BRow* row1, BRow* row2)
4474 int32 itemCount (fSortColumns->CountItems());
4475 if (row1 && row2) {
4476 for (int32 index = 0; index < itemCount; index++) {
4477 BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
4478 int comp = 0;
4479 BField* field1 = (BField*) row1->GetField(column->fFieldID);
4480 BField* field2 = (BField*) row2->GetField(column->fFieldID);
4481 if (field1 && field2)
4482 comp = column->CompareFields(field1, field2);
4484 if (!column->fSortAscending)
4485 comp = -comp;
4487 if (comp != 0)
4488 return comp;
4491 return 0;
4495 void
4496 OutlineView::FrameResized(float width, float height)
4498 fVisibleRect.right = fVisibleRect.left + width;
4499 fVisibleRect.bottom = fVisibleRect.top + height;
4500 FixScrollBar(true);
4501 _inherited::FrameResized(width, height);
4505 void
4506 OutlineView::ScrollTo(BPoint position)
4508 fVisibleRect.OffsetTo(position.x, position.y);
4510 // In FixScrollBar, we might not have been able to change the size of
4511 // the scroll bar because the user was scrolled down too far. Take
4512 // this opportunity to sneak it in if we can.
4513 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
4514 float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
4515 float min, max;
4516 vScrollBar->GetRange(&min, &max);
4517 if (max != maxScrollBarValue && position.y > maxScrollBarValue)
4518 FixScrollBar(true);
4520 _inherited::ScrollTo(position);
4524 const BRect&
4525 OutlineView::VisibleRect() const
4527 return fVisibleRect;
4531 bool
4532 OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
4534 if (row && _rect) {
4535 float line = 0.0;
4536 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4537 iterator.GoToNext()) {
4538 if (line > fVisibleRect.bottom)
4539 break;
4541 if (iterator.CurrentRow() == row) {
4542 _rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4543 line + row->Height());
4544 return true;
4547 line += iterator.CurrentRow()->Height() + 1;
4550 return false;
4554 bool
4555 OutlineView::FindRect(const BRow* row, BRect* _rect)
4557 float line = 0.0;
4558 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4559 iterator.GoToNext()) {
4560 if (iterator.CurrentRow() == row) {
4561 _rect->Set(fVisibleRect.left, line, fVisibleRect.right,
4562 line + row->Height());
4563 return true;
4566 line += iterator.CurrentRow()->Height() + 1;
4569 return false;
4573 void
4574 OutlineView::ScrollTo(const BRow* row)
4576 BRect rect;
4577 if (FindRect(row, &rect)) {
4578 BRect bounds = Bounds();
4579 if (rect.top < bounds.top)
4580 ScrollTo(BPoint(bounds.left, rect.top));
4581 else if (rect.bottom > bounds.bottom)
4582 ScrollBy(0, rect.bottom - bounds.bottom);
4587 void
4588 OutlineView::DeselectAll()
4590 // Invalidate all selected rows
4591 float line = 0.0;
4592 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
4593 iterator.GoToNext()) {
4594 if (line > fVisibleRect.bottom)
4595 break;
4597 BRow* row = iterator.CurrentRow();
4598 if (line + row->Height() > fVisibleRect.top) {
4599 if (row->fNextSelected != 0)
4600 Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
4601 line + row->Height()));
4604 line += row->Height() + 1;
4607 // Set items not selected
4608 while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
4609 BRow* row = fSelectionListDummyHead.fNextSelected;
4610 row->fNextSelected->fPrevSelected = row->fPrevSelected;
4611 row->fPrevSelected->fNextSelected = row->fNextSelected;
4612 row->fNextSelected = 0;
4613 row->fPrevSelected = 0;
4618 BRow*
4619 OutlineView::FocusRow() const
4621 return fFocusRow;
4625 void
4626 OutlineView::SetFocusRow(BRow* row, bool Select)
4628 if (row) {
4629 if (Select)
4630 AddToSelection(row);
4632 if (fFocusRow == row)
4633 return;
4635 Invalidate(fFocusRowRect); // invalidate previous
4637 fTargetRow = fFocusRow = row;
4639 FindVisibleRect(fFocusRow, &fFocusRowRect);
4640 Invalidate(fFocusRowRect); // invalidate current
4642 fFocusRowRect.right = 10000;
4643 fMasterView->SelectionChanged();
4648 bool
4649 OutlineView::SortList(BRowContainer* list, bool isVisible)
4651 if (list) {
4652 // Shellsort
4653 BRow** items
4654 = (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
4655 int32 numItems = list->CountItems();
4656 int h;
4657 for (h = 1; h < numItems / 9; h = 3 * h + 1)
4660 for (;h > 0; h /= 3) {
4661 for (int step = h; step < numItems; step++) {
4662 BRow* temp = items[step];
4663 int i;
4664 for (i = step - h; i >= 0; i -= h) {
4665 if (CompareRows(temp, items[i]) < 0)
4666 items[i + h] = items[i];
4667 else
4668 break;
4671 items[i + h] = temp;
4675 if (isVisible) {
4676 Invalidate();
4678 InvalidateCachedPositions();
4679 int lockCount = Window()->CountLocks();
4680 for (int i = 0; i < lockCount; i++)
4681 Window()->Unlock();
4683 while (lockCount--)
4684 if (!Window()->Lock())
4685 return false; // Window is gone...
4688 return true;
4692 int32
4693 OutlineView::DeepSortThreadEntry(void* _outlineView)
4695 ((OutlineView*) _outlineView)->DeepSort();
4696 return 0;
4700 void
4701 OutlineView::DeepSort()
4703 struct stack_entry {
4704 bool isVisible;
4705 BRowContainer* list;
4706 int32 listIndex;
4707 } stack[kMaxDepth];
4708 int32 stackTop = 0;
4710 stack[stackTop].list = &fRows;
4711 stack[stackTop].isVisible = true;
4712 stack[stackTop].listIndex = 0;
4713 fNumSorted = 0;
4715 if (Window()->Lock() == false)
4716 return;
4718 bool doneSorting = false;
4719 while (!doneSorting && !fSortCancelled) {
4721 stack_entry* currentEntry = &stack[stackTop];
4723 // xxx Can make the invalidate area smaller by finding the rect for the
4724 // parent item and using that as the top of the invalid rect.
4726 bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
4727 if (!haveLock)
4728 return ; // window is gone.
4730 // Fix focus rect.
4731 InvalidateCachedPositions();
4732 if (fCurrentState != INACTIVE)
4733 fCurrentState = INACTIVE; // sorry...
4735 // next list.
4736 bool foundNextList = false;
4737 while (!foundNextList && !fSortCancelled) {
4738 for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
4739 index++) {
4740 BRow* parentRow = currentEntry->list->ItemAt(index);
4741 BRowContainer* childList = parentRow->fChildList;
4742 if (childList != 0) {
4743 currentEntry->listIndex = index + 1;
4744 stackTop++;
4745 ASSERT(stackTop < kMaxDepth);
4746 stack[stackTop].listIndex = 0;
4747 stack[stackTop].list = childList;
4748 stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
4749 foundNextList = true;
4750 break;
4754 if (!foundNextList) {
4755 // back up
4756 if (--stackTop < 0) {
4757 doneSorting = true;
4758 break;
4761 currentEntry = &stack[stackTop];
4766 Window()->Unlock();
4770 void
4771 OutlineView::StartSorting()
4773 // If this view is not yet attached to a window, don't start a sort thread!
4774 if (Window() == NULL)
4775 return;
4777 if (fSortThread != B_BAD_THREAD_ID) {
4778 thread_info tinfo;
4779 if (get_thread_info(fSortThread, &tinfo) == B_OK) {
4780 // Unlock window so this won't deadlock (sort thread is probably
4781 // waiting to lock window).
4783 int lockCount = Window()->CountLocks();
4784 for (int i = 0; i < lockCount; i++)
4785 Window()->Unlock();
4787 fSortCancelled = true;
4788 int32 status;
4789 wait_for_thread(fSortThread, &status);
4791 while (lockCount--)
4792 if (!Window()->Lock())
4793 return ; // Window is gone...
4797 fSortCancelled = false;
4798 fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
4799 resume_thread(fSortThread);
4803 void
4804 OutlineView::SelectRange(BRow* start, BRow* end)
4806 if (!start || !end)
4807 return;
4809 if (start == end) // start is always selected when this is called
4810 return;
4812 RecursiveOutlineIterator iterator(&fRows, false);
4813 while (iterator.CurrentRow() != 0) {
4814 if (iterator.CurrentRow() == end) {
4815 // reverse selection, swap to fix special case
4816 BRow* temp = start;
4817 start = end;
4818 end = temp;
4819 break;
4820 } else if (iterator.CurrentRow() == start)
4821 break;
4823 iterator.GoToNext();
4826 while (true) {
4827 BRow* row = iterator.CurrentRow();
4828 if (row) {
4829 if (row->fNextSelected == 0) {
4830 row->fNextSelected = fSelectionListDummyHead.fNextSelected;
4831 row->fPrevSelected = &fSelectionListDummyHead;
4832 row->fNextSelected->fPrevSelected = row;
4833 row->fPrevSelected->fNextSelected = row;
4835 } else
4836 break;
4838 if (row == end)
4839 break;
4841 iterator.GoToNext();
4844 Invalidate(); // xxx make invalidation smaller
4848 bool
4849 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
4851 bool result = false;
4852 if (row != NULL && outParent != NULL) {
4853 *outParent = row->fParent;
4855 if (outParentIsVisible != NULL) {
4856 // Walk up the parent chain to determine if this row is visible
4857 *outParentIsVisible = true;
4858 for (BRow* currentRow = row->fParent; currentRow != NULL;
4859 currentRow = currentRow->fParent) {
4860 if (!currentRow->fIsExpanded) {
4861 *outParentIsVisible = false;
4862 break;
4867 result = *outParent != NULL;
4870 return result;
4874 int32
4875 OutlineView::IndexOf(BRow* row)
4877 if (row) {
4878 if (row->fParent == 0)
4879 return fRows.IndexOf(row);
4881 ASSERT(row->fParent->fChildList);
4882 return row->fParent->fChildList->IndexOf(row);
4885 return B_ERROR;
4889 void
4890 OutlineView::InvalidateCachedPositions()
4892 if (fFocusRow)
4893 FindRect(fFocusRow, &fFocusRowRect);
4897 float
4898 OutlineView::GetColumnPreferredWidth(BColumn* column)
4900 float preferred = 0.0;
4901 for (RecursiveOutlineIterator iterator(&fRows); BRow* row =
4902 iterator.CurrentRow(); iterator.GoToNext()) {
4903 BField* field = row->GetField(column->fFieldID);
4904 if (field) {
4905 float width = column->GetPreferredWidth(field, this)
4906 + iterator.CurrentLevel() * kOutlineLevelIndent;
4907 preferred = max_c(preferred, width);
4911 BString name;
4912 column->GetColumnName(&name);
4913 preferred = max_c(preferred, StringWidth(name));
4915 // Constrain to preferred width. This makes the method do a little
4916 // more than asked, but it's for convenience.
4917 if (preferred < column->MinWidth())
4918 preferred = column->MinWidth();
4919 else if (preferred > column->MaxWidth())
4920 preferred = column->MaxWidth();
4922 return preferred;
4926 // #pragma mark -
4929 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
4930 bool openBranchesOnly)
4932 fStackIndex(0),
4933 fCurrentListIndex(0),
4934 fCurrentListDepth(0),
4935 fOpenBranchesOnly(openBranchesOnly)
4937 if (list == 0 || list->CountItems() == 0)
4938 fCurrentList = 0;
4939 else
4940 fCurrentList = list;
4944 BRow*
4945 RecursiveOutlineIterator::CurrentRow() const
4947 if (fCurrentList == 0)
4948 return 0;
4950 return fCurrentList->ItemAt(fCurrentListIndex);
4954 void
4955 RecursiveOutlineIterator::GoToNext()
4957 if (fCurrentList == 0)
4958 return;
4959 if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
4960 fCurrentList = 0;
4961 return;
4964 BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
4965 if(currentRow) {
4966 if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
4967 && currentRow->fChildList->CountItems() > 0) {
4968 // Visit child.
4969 // Put current list on the stack if it needs to be revisited.
4970 if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
4971 fStack[fStackIndex].fRowSet = fCurrentList;
4972 fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
4973 fStack[fStackIndex].fDepth = fCurrentListDepth;
4974 fStackIndex++;
4977 fCurrentList = currentRow->fChildList;
4978 fCurrentListIndex = 0;
4979 fCurrentListDepth++;
4980 } else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
4981 fCurrentListIndex++; // next item in current list
4982 else if (--fStackIndex >= 0) {
4983 fCurrentList = fStack[fStackIndex].fRowSet;
4984 fCurrentListIndex = fStack[fStackIndex].fIndex;
4985 fCurrentListDepth = fStack[fStackIndex].fDepth;
4986 } else
4987 fCurrentList = 0;
4992 int32
4993 RecursiveOutlineIterator::CurrentLevel() const
4995 return fCurrentListDepth;