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