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 if (size.height > B_H_SCROLL_BAR_HEIGHT) 2159 size.height = B_H_SCROLL_BAR_HEIGHT; 2160 if (size.width > Bounds().Width() / 2) 2161 size.width = floorf(Bounds().Width() / 2); 2162 2163 BPoint offset(hScrollBarRect.LeftTop()); 2164 2165 if (fBorderStyle == B_PLAIN_BORDER) { 2166 offset += BPoint(0, 1); 2167 } else if (fBorderStyle == B_FANCY_BORDER) { 2168 offset += BPoint(-1, 2); 2169 size.height -= 1; 2170 } 2171 2172 fStatusView->MoveTo(offset); 2173 fStatusView->ResizeTo(size.width, size.height); 2174 hScrollBarRect.left = offset.x + size.width + 1; 2175 } 2176 2177 fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop()); 2178 fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(), 2179 hScrollBarRect.Height()); 2180 2181 fOutlineView->FixScrollBar(true); 2182 } 2183 2184 2185 void 2186 BColumnListView::_Init() 2187 { 2188 SetViewColor(B_TRANSPARENT_32_BIT); 2189 2190 BRect bounds(Bounds()); 2191 if (bounds.Width() <= 0) 2192 bounds.right = 100; 2193 2194 if (bounds.Height() <= 0) 2195 bounds.bottom = 100; 2196 2197 fCustomColors = false; 2198 _UpdateColors(); 2199 2200 BRect titleRect; 2201 BRect outlineRect; 2202 BRect vScrollBarRect; 2203 BRect hScrollBarRect; 2204 _GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect, 2205 hScrollBarRect); 2206 2207 fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this); 2208 AddChild(fOutlineView); 2209 2210 2211 fTitleView = new TitleView(titleRect, fOutlineView, &fColumns, 2212 &fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); 2213 AddChild(fTitleView); 2214 2215 fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar", 2216 fOutlineView, 0.0, bounds.Height(), B_VERTICAL); 2217 AddChild(fVerticalScrollBar); 2218 2219 fHorizontalScrollBar = new BScrollBar(hScrollBarRect, 2220 "horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL); 2221 AddChild(fHorizontalScrollBar); 2222 2223 if (!fShowingHorizontalScrollBar) 2224 fHorizontalScrollBar->Hide(); 2225 2226 fOutlineView->FixScrollBar(true); 2227 } 2228 2229 2230 void 2231 BColumnListView::_UpdateColors() 2232 { 2233 if (fCustomColors) 2234 return; 2235 2236 fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR); 2237 fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR); 2238 fColorList[B_COLOR_ROW_DIVIDER] = tint_color( 2239 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT); 2240 fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); 2241 fColorList[B_COLOR_SELECTION_TEXT] = 2242 ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR); 2243 2244 // For non focus selection uses the selection color as BListView 2245 fColorList[B_COLOR_NON_FOCUS_SELECTION] = 2246 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); 2247 2248 // edit mode doesn't work very well 2249 fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color( 2250 ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT); 2251 fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180; 2252 2253 // Unused color 2254 fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR); 2255 2256 fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_PANEL_BACKGROUND_COLOR); 2257 fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR); 2258 2259 // Unused colors 2260 fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR); 2261 fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR); 2262 } 2263 2264 2265 void 2266 BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect, 2267 BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect) 2268 { 2269 const float vScrollBarWidth = be_control_look->GetScrollBarWidth(B_VERTICAL), 2270 hScrollBarHeight = be_control_look->GetScrollBarWidth(B_HORIZONTAL); 2271 2272 titleRect = bounds; 2273 titleRect.bottom = titleRect.top + std::max(kMinTitleHeight, 2274 ceilf(be_plain_font->Size() * kTitleSpacing)); 2275 #if !LOWER_SCROLLBAR 2276 titleRect.right -= vScrollBarWidth; 2277 #endif 2278 2279 outlineRect = bounds; 2280 outlineRect.top = titleRect.bottom + 1.0; 2281 outlineRect.right -= vScrollBarWidth; 2282 if (fShowingHorizontalScrollBar) 2283 outlineRect.bottom -= hScrollBarHeight; 2284 2285 vScrollBarRect = bounds; 2286 #if LOWER_SCROLLBAR 2287 vScrollBarRect.top += std::max(kMinTitleHeight, 2288 ceilf(be_plain_font->Size() * kTitleSpacing)); 2289 #endif 2290 2291 vScrollBarRect.left = vScrollBarRect.right - vScrollBarWidth; 2292 if (fShowingHorizontalScrollBar) 2293 vScrollBarRect.bottom -= hScrollBarHeight; 2294 2295 hScrollBarRect = bounds; 2296 hScrollBarRect.top = hScrollBarRect.bottom - hScrollBarHeight; 2297 hScrollBarRect.right -= vScrollBarWidth; 2298 2299 // Adjust stuff so the border will fit. 2300 if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) { 2301 titleRect.InsetBy(1, 0); 2302 titleRect.OffsetBy(0, 1); 2303 outlineRect.InsetBy(1, 1); 2304 } else if (fBorderStyle == B_FANCY_BORDER) { 2305 titleRect.InsetBy(2, 0); 2306 titleRect.OffsetBy(0, 2); 2307 outlineRect.InsetBy(2, 2); 2308 2309 vScrollBarRect.OffsetBy(-1, 0); 2310 #if LOWER_SCROLLBAR 2311 vScrollBarRect.top += 2; 2312 vScrollBarRect.bottom -= 1; 2313 #else 2314 vScrollBarRect.InsetBy(0, 1); 2315 #endif 2316 hScrollBarRect.OffsetBy(0, -1); 2317 hScrollBarRect.InsetBy(1, 0); 2318 } 2319 } 2320 2321 2322 // #pragma mark - 2323 2324 2325 TitleView::TitleView(BRect rect, OutlineView* horizontalSlave, 2326 BList* visibleColumns, BList* sortColumns, BColumnListView* listView, 2327 uint32 resizingMode) 2328 : 2329 BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS), 2330 fOutlineView(horizontalSlave), 2331 fColumns(visibleColumns), 2332 fSortColumns(sortColumns), 2333 // fColumnsWidth(0), 2334 fVisibleRect(rect.OffsetToCopy(0, 0)), 2335 fCurrentState(INACTIVE), 2336 fColumnPop(NULL), 2337 fMasterView(listView), 2338 fEditMode(false), 2339 fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE 2340 | B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE) 2341 { 2342 SetViewColor(B_TRANSPARENT_COLOR); 2343 2344 fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8); 2345 fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8); 2346 2347 fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8); 2348 fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8); 2349 2350 fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST); 2351 fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST); 2352 fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST); 2353 fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE); 2354 2355 FixScrollBar(true); 2356 } 2357 2358 2359 TitleView::~TitleView() 2360 { 2361 delete fColumnPop; 2362 fColumnPop = NULL; 2363 2364 delete fUpSortArrow; 2365 delete fDownSortArrow; 2366 2367 delete fResizeCursor; 2368 delete fMaxResizeCursor; 2369 delete fMinResizeCursor; 2370 delete fColumnMoveCursor; 2371 } 2372 2373 2374 void 2375 TitleView::ColumnAdded(BColumn* column) 2376 { 2377 #ifdef DOUBLE_BUFFERED_COLUMN_RESIZE 2378 fOutlineView->ResizeBufferView()->UpdateMaxWidth(column->MaxWidth()); 2379 #endif 2380 // fColumnsWidth += column->Width(); 2381 FixScrollBar(false); 2382 Invalidate(); 2383 } 2384 2385 2386 void 2387 TitleView::ColumnResized(BColumn* column, float oldWidth) 2388 { 2389 // fColumnsWidth += column->Width() - oldWidth; 2390 FixScrollBar(false); 2391 Invalidate(); 2392 } 2393 2394 2395 void 2396 TitleView::SetColumnVisible(BColumn* column, bool visible) 2397 { 2398 if (column->fVisible == visible) 2399 return; 2400 2401 // If setting it visible, do this first so we can find its position 2402 // to invalidate. If hiding it, do it last. 2403 if (visible) 2404 column->fVisible = visible; 2405 2406 BRect titleInvalid; 2407 GetTitleRect(column, &titleInvalid); 2408 2409 // Now really set the visibility 2410 column->fVisible = visible; 2411 2412 // if (visible) 2413 // fColumnsWidth += column->Width(); 2414 // else 2415 // fColumnsWidth -= column->Width(); 2416 2417 BRect outlineInvalid(fOutlineView->VisibleRect()); 2418 outlineInvalid.left = titleInvalid.left; 2419 titleInvalid.right = outlineInvalid.right; 2420 2421 Invalidate(titleInvalid); 2422 fOutlineView->Invalidate(outlineInvalid); 2423 2424 FixScrollBar(false); 2425 } 2426 2427 2428 void 2429 TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect) 2430 { 2431 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 2432 int32 numColumns = fColumns->CountItems(); 2433 for (int index = 0; index < numColumns; index++) { 2434 BColumn* column = (BColumn*) fColumns->ItemAt(index); 2435 if (!column->IsVisible()) 2436 continue; 2437 2438 if (column == findColumn) { 2439 _rect->Set(leftEdge, 0, leftEdge + column->Width(), 2440 fVisibleRect.bottom); 2441 return; 2442 } 2443 2444 leftEdge += column->Width() + 1; 2445 } 2446 2447 TRESPASS(); 2448 } 2449 2450 2451 int32 2452 TitleView::FindColumn(BPoint position, float* _leftEdge) 2453 { 2454 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 2455 int32 numColumns = fColumns->CountItems(); 2456 for (int index = 0; index < numColumns; index++) { 2457 BColumn* column = (BColumn*) fColumns->ItemAt(index); 2458 if (!column->IsVisible()) 2459 continue; 2460 2461 if (leftEdge > position.x) 2462 break; 2463 2464 if (position.x >= leftEdge 2465 && position.x <= leftEdge + column->Width()) { 2466 *_leftEdge = leftEdge; 2467 return index; 2468 } 2469 2470 leftEdge += column->Width() + 1; 2471 } 2472 2473 return 0; 2474 } 2475 2476 2477 void 2478 TitleView::FixScrollBar(bool scrollToFit) 2479 { 2480 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL); 2481 if (hScrollBar == NULL) 2482 return; 2483 2484 float virtualWidth = _VirtualWidth(); 2485 2486 if (virtualWidth > fVisibleRect.Width()) { 2487 hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth); 2488 2489 // Perform the little trick if the user is scrolled over too far. 2490 // See OutlineView::FixScrollBar for a more in depth explanation 2491 float maxScrollBarValue = virtualWidth - fVisibleRect.Width(); 2492 if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) { 2493 hScrollBar->SetRange(0.0, maxScrollBarValue); 2494 hScrollBar->SetSteps(50, fVisibleRect.Width()); 2495 } 2496 } else if (hScrollBar->Value() == 0.0) { 2497 // disable scroll bar. 2498 hScrollBar->SetRange(0.0, 0.0); 2499 } 2500 } 2501 2502 2503 void 2504 TitleView::DragSelectedColumn(BPoint position) 2505 { 2506 float invalidLeft = fSelectedColumnRect.left; 2507 float invalidRight = fSelectedColumnRect.right; 2508 2509 float leftEdge; 2510 int32 columnIndex = FindColumn(position, &leftEdge); 2511 fSelectedColumnRect.OffsetTo(leftEdge, 0); 2512 2513 MoveColumn(fSelectedColumn, columnIndex); 2514 2515 fSelectedColumn->fVisible = true; 2516 ComputeDragBoundries(fSelectedColumn, position); 2517 2518 // Redraw the new column position 2519 GetTitleRect(fSelectedColumn, &fSelectedColumnRect); 2520 invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft); 2521 invalidRight = MAX(fSelectedColumnRect.right, invalidRight); 2522 2523 Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom)); 2524 fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight, 2525 fOutlineView->VisibleRect().bottom)); 2526 2527 DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true); 2528 } 2529 2530 2531 void 2532 TitleView::MoveColumn(BColumn* column, int32 index) 2533 { 2534 fColumns->RemoveItem((void*) column); 2535 2536 if (-1 == index) { 2537 // Re-add the column at the end of the list. 2538 fColumns->AddItem((void*) column); 2539 } else { 2540 fColumns->AddItem((void*) column, index); 2541 } 2542 } 2543 2544 2545 void 2546 TitleView::SetColumnFlags(column_flags flags) 2547 { 2548 fColumnFlags = flags; 2549 } 2550 2551 2552 float 2553 TitleView::MarginWidth() const 2554 { 2555 return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin; 2556 } 2557 2558 2559 void 2560 TitleView::ResizeSelectedColumn(BPoint position, bool preferred) 2561 { 2562 float minWidth = fSelectedColumn->MinWidth(); 2563 float maxWidth = fSelectedColumn->MaxWidth(); 2564 2565 float oldWidth = fSelectedColumn->Width(); 2566 float originalEdge = fSelectedColumnRect.left + oldWidth; 2567 if (preferred) { 2568 float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn); 2569 fSelectedColumn->SetWidth(width); 2570 } else if (position.x > fSelectedColumnRect.left + maxWidth) 2571 fSelectedColumn->SetWidth(maxWidth); 2572 else if (position.x < fSelectedColumnRect.left + minWidth) 2573 fSelectedColumn->SetWidth(minWidth); 2574 else 2575 fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1); 2576 2577 float dX = fSelectedColumnRect.left + fSelectedColumn->Width() 2578 - originalEdge; 2579 if (dX != 0) { 2580 float columnHeight = fVisibleRect.Height(); 2581 BRect originalRect(originalEdge, 0, 1000000.0, columnHeight); 2582 BRect movedRect(originalRect); 2583 movedRect.OffsetBy(dX, 0); 2584 2585 // Update the size of the title column 2586 BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight); 2587 BRect destRect(sourceRect); 2588 destRect.OffsetBy(fSelectedColumnRect.left, 0); 2589 2590 #if DOUBLE_BUFFERED_COLUMN_RESIZE 2591 ColumnResizeBufferView* bufferView = fOutlineView->ResizeBufferView(); 2592 bufferView->Lock(); 2593 DrawTitle(bufferView, sourceRect, fSelectedColumn, false); 2594 bufferView->Sync(); 2595 bufferView->Unlock(); 2596 2597 CopyBits(originalRect, movedRect); 2598 DrawBitmap(bufferView->Bitmap(), sourceRect, destRect); 2599 #else 2600 CopyBits(originalRect, movedRect); 2601 DrawTitle(this, destRect, fSelectedColumn, false); 2602 #endif 2603 2604 // Update the body view 2605 BRect slaveSize = fOutlineView->VisibleRect(); 2606 BRect slaveSource(originalRect); 2607 slaveSource.bottom = slaveSize.bottom; 2608 BRect slaveDest(movedRect); 2609 slaveDest.bottom = slaveSize.bottom; 2610 fOutlineView->CopyBits(slaveSource, slaveDest); 2611 fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left, 2612 fResizingFirstColumn); 2613 2614 // fColumnsWidth += dX; 2615 2616 // Update the cursor 2617 if (fSelectedColumn->Width() == minWidth) 2618 SetViewCursor(fMinResizeCursor, true); 2619 else if (fSelectedColumn->Width() == maxWidth) 2620 SetViewCursor(fMaxResizeCursor, true); 2621 else 2622 SetViewCursor(fResizeCursor, true); 2623 2624 ColumnResized(fSelectedColumn, oldWidth); 2625 } 2626 } 2627 2628 2629 void 2630 TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint) 2631 { 2632 float previousColumnLeftEdge = -1000000.0; 2633 float nextColumnRightEdge = 1000000.0; 2634 2635 bool foundColumn = false; 2636 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 2637 int32 numColumns = fColumns->CountItems(); 2638 for (int index = 0; index < numColumns; index++) { 2639 BColumn* column = (BColumn*) fColumns->ItemAt(index); 2640 if (!column->IsVisible()) 2641 continue; 2642 2643 if (column == findColumn) { 2644 foundColumn = true; 2645 continue; 2646 } 2647 2648 if (foundColumn) { 2649 nextColumnRightEdge = leftEdge + column->Width(); 2650 break; 2651 } else 2652 previousColumnLeftEdge = leftEdge; 2653 2654 leftEdge += column->Width() + 1; 2655 } 2656 2657 float rightEdge = leftEdge + findColumn->Width(); 2658 2659 fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(), 2660 leftEdge); 2661 fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge); 2662 } 2663 2664 2665 void 2666 TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed) 2667 { 2668 BRect drawRect; 2669 drawRect = rect; 2670 2671 font_height fh; 2672 GetFontHeight(&fh); 2673 2674 float baseline = floor(drawRect.top + fh.ascent 2675 + (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2); 2676 2677 BRect bgRect = rect; 2678 2679 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 2680 view->SetHighColor(tint_color(base, B_DARKEN_2_TINT)); 2681 view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom()); 2682 2683 bgRect.bottom--; 2684 bgRect.right--; 2685 2686 if (depressed) 2687 base = tint_color(base, B_DARKEN_1_TINT); 2688 2689 be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0, 2690 BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER); 2691 2692 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 2693 B_DARKEN_2_TINT)); 2694 view->StrokeLine(rect.RightTop(), rect.RightBottom()); 2695 2696 // If no column given, nothing else to draw. 2697 if (column == NULL) 2698 return; 2699 2700 view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT)); 2701 2702 BFont font; 2703 GetFont(&font); 2704 view->SetFont(&font); 2705 2706 int sortIndex = fSortColumns->IndexOf(column); 2707 if (sortIndex >= 0) { 2708 // Draw sort notation. 2709 BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline); 2710 2711 if (fSortColumns->CountItems() > 1) { 2712 char str[256]; 2713 sprintf(str, "%d", sortIndex + 1); 2714 const float w = view->StringWidth(str); 2715 upperLeft.x -= w; 2716 2717 view->SetDrawingMode(B_OP_COPY); 2718 view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth, 2719 baseline)); 2720 view->DrawString(str); 2721 } 2722 2723 float bmh = fDownSortArrow->Bounds().Height()+1; 2724 2725 view->SetDrawingMode(B_OP_OVER); 2726 2727 if (column->fSortAscending) { 2728 BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight() 2729 - fDownSortArrow->Bounds().IntegerHeight()) / 2); 2730 view->DrawBitmapAsync(fDownSortArrow, leftTop); 2731 } else { 2732 BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight() 2733 - fUpSortArrow->Bounds().IntegerHeight()) / 2); 2734 view->DrawBitmapAsync(fUpSortArrow, leftTop); 2735 } 2736 2737 upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2); 2738 if (upperLeft.y < drawRect.top) 2739 upperLeft.y = drawRect.top; 2740 2741 // Adjust title stuff for sort indicator 2742 drawRect.right = upperLeft.x - 2; 2743 } 2744 2745 if (drawRect.right > drawRect.left) { 2746 #if CONSTRAIN_CLIPPING_REGION 2747 BRegion clipRegion(drawRect); 2748 view->PushState(); 2749 view->ConstrainClippingRegion(&clipRegion); 2750 #endif 2751 view->MovePenTo(BPoint(drawRect.left + 8, baseline)); 2752 view->SetDrawingMode(B_OP_OVER); 2753 view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT)); 2754 column->DrawTitle(drawRect, view); 2755 2756 #if CONSTRAIN_CLIPPING_REGION 2757 view->PopState(); 2758 #endif 2759 } 2760 } 2761 2762 2763 float 2764 TitleView::_VirtualWidth() const 2765 { 2766 float width = MarginWidth(); 2767 2768 int32 count = fColumns->CountItems(); 2769 for (int32 i = 0; i < count; i++) { 2770 BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i)); 2771 if (column->IsVisible()) 2772 width += column->Width(); 2773 } 2774 2775 return width; 2776 } 2777 2778 2779 void 2780 TitleView::Draw(BRect invalidRect) 2781 { 2782 float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 2783 for (int32 columnIndex = 0; columnIndex < fColumns->CountItems(); 2784 columnIndex++) { 2785 2786 BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex); 2787 if (!column->IsVisible()) 2788 continue; 2789 2790 if (columnLeftEdge > invalidRect.right) 2791 break; 2792 2793 if (columnLeftEdge + column->Width() >= invalidRect.left) { 2794 BRect titleRect(columnLeftEdge, 0, 2795 columnLeftEdge + column->Width(), fVisibleRect.Height()); 2796 DrawTitle(this, titleRect, column, 2797 (fCurrentState == DRAG_COLUMN_INSIDE_TITLE 2798 && fSelectedColumn == column)); 2799 } 2800 2801 columnLeftEdge += column->Width() + 1; 2802 } 2803 2804 2805 // bevels for right title margin 2806 if (columnLeftEdge <= invalidRect.right) { 2807 BRect titleRect(columnLeftEdge, 0, Bounds().right + 2, 2808 fVisibleRect.Height()); 2809 DrawTitle(this, titleRect, NULL, false); 2810 } 2811 2812 // bevels for left title margin 2813 if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) { 2814 BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1, 2815 fVisibleRect.Height()); 2816 DrawTitle(this, titleRect, NULL, false); 2817 } 2818 2819 #if DRAG_TITLE_OUTLINE 2820 // (internal) column drag indicator 2821 if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) { 2822 BRect dragRect(fSelectedColumnRect); 2823 dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0); 2824 if (dragRect.Intersects(invalidRect)) { 2825 SetHighColor(0, 0, 255); 2826 StrokeRect(dragRect); 2827 } 2828 } 2829 #endif 2830 } 2831 2832 2833 void 2834 TitleView::ScrollTo(BPoint position) 2835 { 2836 fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0); 2837 fVisibleRect.OffsetTo(position.x, position.y); 2838 2839 // Perform the little trick if the user is scrolled over too far. 2840 // See OutlineView::ScrollTo for a more in depth explanation 2841 float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width(); 2842 BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL); 2843 float min, max; 2844 hScrollBar->GetRange(&min, &max); 2845 if (max != maxScrollBarValue && position.x > maxScrollBarValue) 2846 FixScrollBar(true); 2847 2848 _inherited::ScrollTo(position); 2849 } 2850 2851 2852 void 2853 TitleView::MessageReceived(BMessage* message) 2854 { 2855 if (message->what == kToggleColumn) { 2856 int32 num; 2857 if (message->FindInt32("be:field_num", &num) == B_OK) { 2858 for (int index = 0; index < fColumns->CountItems(); index++) { 2859 BColumn* column = (BColumn*) fColumns->ItemAt(index); 2860 if (column == NULL) 2861 continue; 2862 2863 if (column->LogicalFieldNum() == num) 2864 column->SetVisible(!column->IsVisible()); 2865 } 2866 } 2867 return; 2868 } 2869 2870 BView::MessageReceived(message); 2871 } 2872 2873 2874 void 2875 TitleView::MouseDown(BPoint position) 2876 { 2877 if (fEditMode) 2878 return; 2879 2880 int32 buttons = 1; 2881 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 2882 if (buttons == B_SECONDARY_MOUSE_BUTTON 2883 && (fColumnFlags & B_ALLOW_COLUMN_POPUP)) { 2884 // Right mouse button -- bring up menu to show/hide columns. 2885 if (fColumnPop == NULL) 2886 fColumnPop = new BPopUpMenu("Columns", false, false); 2887 2888 fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true); 2889 BMessenger me(this); 2890 for (int index = 0; index < fColumns->CountItems(); index++) { 2891 BColumn* column = (BColumn*) fColumns->ItemAt(index); 2892 if (column == NULL) 2893 continue; 2894 2895 BString name; 2896 column->GetColumnName(&name); 2897 BMessage* message = new BMessage(kToggleColumn); 2898 message->AddInt32("be:field_num", column->LogicalFieldNum()); 2899 BMenuItem* item = new BMenuItem(name.String(), message); 2900 item->SetMarked(column->IsVisible()); 2901 item->SetTarget(me); 2902 fColumnPop->AddItem(item); 2903 } 2904 2905 BPoint screenPosition = ConvertToScreen(position); 2906 BRect sticky(screenPosition, screenPosition); 2907 sticky.InsetBy(-5, -5); 2908 fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true); 2909 2910 return; 2911 } 2912 2913 fResizingFirstColumn = true; 2914 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 2915 for (int index = 0; index < fColumns->CountItems(); index++) { 2916 BColumn* column = (BColumn*)fColumns->ItemAt(index); 2917 if (column == NULL || !column->IsVisible()) 2918 continue; 2919 2920 if (leftEdge > position.x + kColumnResizeAreaWidth / 2) 2921 break; 2922 2923 // check for resizing a column 2924 float rightEdge = leftEdge + column->Width(); 2925 2926 if (column->ShowHeading()) { 2927 if (position.x > rightEdge - kColumnResizeAreaWidth / 2 2928 && position.x < rightEdge + kColumnResizeAreaWidth / 2 2929 && column->MaxWidth() > column->MinWidth() 2930 && (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) { 2931 2932 int32 clicks = 0; 2933 fSelectedColumn = column; 2934 fSelectedColumnRect.Set(leftEdge, 0, rightEdge, 2935 fVisibleRect.Height()); 2936 Window()->CurrentMessage()->FindInt32("clicks", &clicks); 2937 if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) { 2938 ResizeSelectedColumn(position, true); 2939 fCurrentState = INACTIVE; 2940 break; 2941 } 2942 fCurrentState = RESIZING_COLUMN; 2943 fClickPoint = BPoint(position.x - rightEdge - 1, 2944 position.y - fSelectedColumnRect.top); 2945 SetMouseEventMask(B_POINTER_EVENTS, 2946 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY); 2947 break; 2948 } 2949 2950 fResizingFirstColumn = false; 2951 2952 // check for clicking on a column 2953 if (position.x > leftEdge && position.x < rightEdge) { 2954 fCurrentState = PRESSING_COLUMN; 2955 fSelectedColumn = column; 2956 fSelectedColumnRect.Set(leftEdge, 0, rightEdge, 2957 fVisibleRect.Height()); 2958 DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true); 2959 fClickPoint = BPoint(position.x - fSelectedColumnRect.left, 2960 position.y - fSelectedColumnRect.top); 2961 SetMouseEventMask(B_POINTER_EVENTS, 2962 B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY); 2963 break; 2964 } 2965 } 2966 leftEdge = rightEdge + 1; 2967 } 2968 } 2969 2970 2971 void 2972 TitleView::MouseMoved(BPoint position, uint32 transit, 2973 const BMessage* dragMessage) 2974 { 2975 if (fEditMode) 2976 return; 2977 2978 // Handle column manipulation 2979 switch (fCurrentState) { 2980 case RESIZING_COLUMN: 2981 ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0)); 2982 break; 2983 2984 case PRESSING_COLUMN: { 2985 if (abs((int32)(position.x - (fClickPoint.x 2986 + fSelectedColumnRect.left))) > kColumnResizeAreaWidth 2987 || abs((int32)(position.y - (fClickPoint.y 2988 + fSelectedColumnRect.top))) > kColumnResizeAreaWidth) { 2989 // User has moved the mouse more than the tolerable amount, 2990 // initiate a drag. 2991 if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) { 2992 if(fColumnFlags & B_ALLOW_COLUMN_MOVE) { 2993 fCurrentState = DRAG_COLUMN_INSIDE_TITLE; 2994 ComputeDragBoundries(fSelectedColumn, position); 2995 SetViewCursor(fColumnMoveCursor, true); 2996 #if DRAG_TITLE_OUTLINE 2997 BRect invalidRect(fSelectedColumnRect); 2998 invalidRect.OffsetTo(position.x - fClickPoint.x, 0); 2999 fCurrentDragPosition = position; 3000 Invalidate(invalidRect); 3001 #endif 3002 } 3003 } else { 3004 if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) { 3005 // Dragged outside view 3006 fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE; 3007 fSelectedColumn->SetVisible(false); 3008 BRect dragRect(fSelectedColumnRect); 3009 3010 // There is a race condition where the mouse may have 3011 // moved by the time we get to handle this message. 3012 // If the user drags a column very quickly, this 3013 // results in the annoying bug where the cursor is 3014 // outside of the rectangle that is being dragged 3015 // around. Call GetMouse with the checkQueue flag set 3016 // to false so we can get the most recent position of 3017 // the mouse. This minimizes this problem (although 3018 // it is currently not possible to completely eliminate 3019 // it). 3020 uint32 buttons; 3021 GetMouse(&position, &buttons, false); 3022 dragRect.OffsetTo(position.x - fClickPoint.x, 3023 position.y - dragRect.Height() / 2); 3024 BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT); 3025 } 3026 } 3027 } 3028 3029 break; 3030 } 3031 3032 case DRAG_COLUMN_INSIDE_TITLE: { 3033 if (transit == B_EXITED_VIEW 3034 && (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) { 3035 // Dragged outside view 3036 fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE; 3037 fSelectedColumn->SetVisible(false); 3038 BRect dragRect(fSelectedColumnRect); 3039 3040 // See explanation above. 3041 uint32 buttons; 3042 GetMouse(&position, &buttons, false); 3043 3044 dragRect.OffsetTo(position.x - fClickPoint.x, 3045 position.y - fClickPoint.y); 3046 BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT); 3047 } else if (position.x < fLeftDragBoundry 3048 || position.x > fRightDragBoundry) { 3049 DragSelectedColumn(position - BPoint(fClickPoint.x, 0)); 3050 } 3051 3052 #if DRAG_TITLE_OUTLINE 3053 // Set up the invalid rect to include the rect for the previous 3054 // position of the drag rect, as well as the new one. 3055 BRect invalidRect(fSelectedColumnRect); 3056 invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0); 3057 if (position.x < fCurrentDragPosition.x) 3058 invalidRect.left -= fCurrentDragPosition.x - position.x; 3059 else 3060 invalidRect.right += position.x - fCurrentDragPosition.x; 3061 3062 fCurrentDragPosition = position; 3063 Invalidate(invalidRect); 3064 #endif 3065 break; 3066 } 3067 3068 case DRAG_COLUMN_OUTSIDE_TITLE: 3069 if (transit == B_ENTERED_VIEW) { 3070 // Drag back into view 3071 EndRectTracking(); 3072 fCurrentState = DRAG_COLUMN_INSIDE_TITLE; 3073 fSelectedColumn->SetVisible(true); 3074 DragSelectedColumn(position - BPoint(fClickPoint.x, 0)); 3075 } 3076 3077 break; 3078 3079 case INACTIVE: 3080 // Check for cursor changes if we are over the resize area for 3081 // a column. 3082 BColumn* resizeColumn = 0; 3083 float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 3084 for (int index = 0; index < fColumns->CountItems(); index++) { 3085 BColumn* column = (BColumn*) fColumns->ItemAt(index); 3086 if (!column->IsVisible()) 3087 continue; 3088 3089 if (leftEdge > position.x + kColumnResizeAreaWidth / 2) 3090 break; 3091 3092 float rightEdge = leftEdge + column->Width(); 3093 if (position.x > rightEdge - kColumnResizeAreaWidth / 2 3094 && position.x < rightEdge + kColumnResizeAreaWidth / 2 3095 && column->MaxWidth() > column->MinWidth()) { 3096 resizeColumn = column; 3097 break; 3098 } 3099 3100 leftEdge = rightEdge + 1; 3101 } 3102 3103 // Update the cursor 3104 if (resizeColumn) { 3105 if (resizeColumn->Width() == resizeColumn->MinWidth()) 3106 SetViewCursor(fMinResizeCursor, true); 3107 else if (resizeColumn->Width() == resizeColumn->MaxWidth()) 3108 SetViewCursor(fMaxResizeCursor, true); 3109 else 3110 SetViewCursor(fResizeCursor, true); 3111 } else 3112 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true); 3113 break; 3114 } 3115 } 3116 3117 3118 void 3119 TitleView::MouseUp(BPoint position) 3120 { 3121 if (fEditMode) 3122 return; 3123 3124 switch (fCurrentState) { 3125 case RESIZING_COLUMN: 3126 ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0)); 3127 fCurrentState = INACTIVE; 3128 FixScrollBar(false); 3129 break; 3130 3131 case PRESSING_COLUMN: { 3132 if (fMasterView->SortingEnabled()) { 3133 if (fSortColumns->HasItem(fSelectedColumn)) { 3134 if ((modifiers() & B_CONTROL_KEY) == 0 3135 && fSortColumns->CountItems() > 1) { 3136 fSortColumns->MakeEmpty(); 3137 fSortColumns->AddItem(fSelectedColumn); 3138 } 3139 3140 fSelectedColumn->fSortAscending 3141 = !fSelectedColumn->fSortAscending; 3142 } else { 3143 if ((modifiers() & B_CONTROL_KEY) == 0) 3144 fSortColumns->MakeEmpty(); 3145 3146 fSortColumns->AddItem(fSelectedColumn); 3147 fSelectedColumn->fSortAscending = true; 3148 } 3149 3150 fOutlineView->StartSorting(); 3151 } 3152 3153 fCurrentState = INACTIVE; 3154 Invalidate(); 3155 break; 3156 } 3157 3158 case DRAG_COLUMN_INSIDE_TITLE: 3159 fCurrentState = INACTIVE; 3160 3161 #if DRAG_TITLE_OUTLINE 3162 Invalidate(); // xxx Can make this smaller 3163 #else 3164 Invalidate(fSelectedColumnRect); 3165 #endif 3166 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true); 3167 break; 3168 3169 case DRAG_COLUMN_OUTSIDE_TITLE: 3170 fCurrentState = INACTIVE; 3171 EndRectTracking(); 3172 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true); 3173 break; 3174 3175 default: 3176 ; 3177 } 3178 } 3179 3180 3181 void 3182 TitleView::FrameResized(float width, float height) 3183 { 3184 fVisibleRect.right = fVisibleRect.left + width; 3185 fVisibleRect.bottom = fVisibleRect.top + height; 3186 FixScrollBar(true); 3187 } 3188 3189 3190 // #pragma mark - OutlineView 3191 3192 3193 OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns, 3194 BColumnListView* listView) 3195 : 3196 BView(rect, "outline_view", B_FOLLOW_ALL_SIDES, 3197 B_WILL_DRAW | B_FRAME_EVENTS), 3198 fColumns(visibleColumns), 3199 fSortColumns(sortColumns), 3200 fItemsHeight(0.0), 3201 fVisibleRect(rect.OffsetToCopy(0, 0)), 3202 fFocusRow(0), 3203 fRollOverRow(0), 3204 fLastSelectedItem(0), 3205 fFirstSelectedItem(0), 3206 fSortThread(B_BAD_THREAD_ID), 3207 fCurrentState(INACTIVE), 3208 fMasterView(listView), 3209 fSelectionMode(B_MULTIPLE_SELECTION_LIST), 3210 fTrackMouse(false), 3211 fCurrentField(0), 3212 fCurrentRow(0), 3213 fCurrentColumn(0), 3214 fMouseDown(false), 3215 fCurrentCode(B_OUTSIDE_VIEW), 3216 fEditMode(false), 3217 fDragging(false), 3218 fClickCount(0), 3219 fDropHighlightY(-1) 3220 { 3221 SetViewColor(B_TRANSPARENT_COLOR); 3222 3223 #if DOUBLE_BUFFERED_COLUMN_RESIZE 3224 fResizeBufferView = new ColumnResizeBufferView(); 3225 #endif 3226 3227 FixScrollBar(true); 3228 fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead; 3229 fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead; 3230 } 3231 3232 3233 OutlineView::~OutlineView() 3234 { 3235 #if DOUBLE_BUFFERED_COLUMN_RESIZE 3236 delete fResizeBufferView; 3237 #endif 3238 3239 Clear(); 3240 } 3241 3242 3243 void 3244 OutlineView::Clear() 3245 { 3246 DeselectAll(); 3247 // Make sure selection list doesn't point to deleted rows! 3248 RecursiveDeleteRows(&fRows, false); 3249 fItemsHeight = 0.0; 3250 FixScrollBar(true); 3251 Invalidate(); 3252 } 3253 3254 3255 void 3256 OutlineView::SetSelectionMode(list_view_type mode) 3257 { 3258 DeselectAll(); 3259 fSelectionMode = mode; 3260 } 3261 3262 3263 list_view_type 3264 OutlineView::SelectionMode() const 3265 { 3266 return fSelectionMode; 3267 } 3268 3269 3270 void 3271 OutlineView::Deselect(BRow* row) 3272 { 3273 if (row == NULL) 3274 return; 3275 3276 if (row->fNextSelected != 0) { 3277 row->fNextSelected->fPrevSelected = row->fPrevSelected; 3278 row->fPrevSelected->fNextSelected = row->fNextSelected; 3279 row->fNextSelected = 0; 3280 row->fPrevSelected = 0; 3281 Invalidate(); 3282 } 3283 } 3284 3285 3286 void 3287 OutlineView::AddToSelection(BRow* row) 3288 { 3289 if (row == NULL) 3290 return; 3291 3292 if (row->fNextSelected == 0) { 3293 if (fSelectionMode == B_SINGLE_SELECTION_LIST) 3294 DeselectAll(); 3295 3296 row->fNextSelected = fSelectionListDummyHead.fNextSelected; 3297 row->fPrevSelected = &fSelectionListDummyHead; 3298 row->fNextSelected->fPrevSelected = row; 3299 row->fPrevSelected->fNextSelected = row; 3300 3301 BRect invalidRect; 3302 if (FindVisibleRect(row, &invalidRect)) 3303 Invalidate(invalidRect); 3304 } 3305 } 3306 3307 3308 void 3309 OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner) 3310 { 3311 if (list == NULL) 3312 return; 3313 3314 while (true) { 3315 BRow* row = list->RemoveItemAt(0L); 3316 if (row == 0) 3317 break; 3318 3319 if (row->fChildList) 3320 RecursiveDeleteRows(row->fChildList, true); 3321 3322 delete row; 3323 } 3324 3325 if (isOwner) 3326 delete list; 3327 } 3328 3329 3330 void 3331 OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn) 3332 { 3333 // TODO: Remove code duplication (private function which takes a view 3334 // pointer, pass "this" in non-double buffered mode)! 3335 // Watch out for sourceRect versus destRect though! 3336 if (!column) 3337 return; 3338 3339 font_height fh; 3340 GetFontHeight(&fh); 3341 float line = 0.0; 3342 bool tintedLine = true; 3343 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); 3344 line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) { 3345 3346 BRow* row = iterator.CurrentRow(); 3347 float rowHeight = row->Height(); 3348 if (line > fVisibleRect.bottom) 3349 break; 3350 tintedLine = !tintedLine; 3351 3352 if (line + rowHeight >= fVisibleRect.top) { 3353 #if DOUBLE_BUFFERED_COLUMN_RESIZE 3354 BRect sourceRect(0, 0, column->Width(), rowHeight); 3355 #endif 3356 BRect destRect(leftEdge, line, leftEdge + column->Width(), 3357 line + rowHeight); 3358 3359 rgb_color highColor; 3360 rgb_color lowColor; 3361 if (row->fNextSelected != 0) { 3362 if (fEditMode) { 3363 highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND); 3364 lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND); 3365 } else { 3366 highColor = fMasterView->Color(B_COLOR_SELECTION); 3367 lowColor = fMasterView->Color(B_COLOR_SELECTION); 3368 } 3369 } else { 3370 highColor = fMasterView->Color(B_COLOR_BACKGROUND); 3371 lowColor = fMasterView->Color(B_COLOR_BACKGROUND); 3372 } 3373 if (tintedLine) 3374 lowColor = tint_color(lowColor, kTintedLineTint); 3375 3376 3377 #if DOUBLE_BUFFERED_COLUMN_RESIZE 3378 fResizeBufferView->Lock(); 3379 3380 fResizeBufferView->SetHighColor(highColor); 3381 fResizeBufferView->SetLowColor(lowColor); 3382 3383 BFont font; 3384 GetFont(&font); 3385 fResizeBufferView->SetFont(&font); 3386 fResizeBufferView->FillRect(sourceRect, B_SOLID_LOW); 3387 3388 if (isFirstColumn) { 3389 // If this is the first column, double buffer drawing the latch 3390 // too. 3391 destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent 3392 - fMasterView->LatchWidth(); 3393 sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent 3394 - fMasterView->LatchWidth(); 3395 3396 LatchType pos = B_NO_LATCH; 3397 if (row->HasLatch()) 3398 pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH; 3399 3400 BRect latchRect(sourceRect); 3401 latchRect.right = latchRect.left + fMasterView->LatchWidth(); 3402 fMasterView->DrawLatch(fResizeBufferView, latchRect, pos, row); 3403 } 3404 3405 BField* field = row->GetField(column->fFieldID); 3406 if (field) { 3407 BRect fieldRect(sourceRect); 3408 if (isFirstColumn) 3409 fieldRect.left += fMasterView->LatchWidth(); 3410 3411 #if CONSTRAIN_CLIPPING_REGION 3412 BRegion clipRegion(fieldRect); 3413 fResizeBufferView->PushState(); 3414 fResizeBufferView->ConstrainClippingRegion(&clipRegion); 3415 #endif 3416 fResizeBufferView->SetHighColor(fMasterView->Color( 3417 row->fNextSelected ? B_COLOR_SELECTION_TEXT 3418 : B_COLOR_TEXT)); 3419 float baseline = floor(fieldRect.top + fh.ascent 3420 + (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2); 3421 fResizeBufferView->MovePenTo(fieldRect.left + 8, baseline); 3422 column->DrawField(field, fieldRect, fResizeBufferView); 3423 #if CONSTRAIN_CLIPPING_REGION 3424 fResizeBufferView->PopState(); 3425 #endif 3426 } 3427 3428 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus() 3429 && Window()->IsActive()) { 3430 fResizeBufferView->SetHighColor(fMasterView->Color( 3431 B_COLOR_ROW_DIVIDER)); 3432 fResizeBufferView->StrokeRect(BRect(-1, sourceRect.top, 3433 10000.0, sourceRect.bottom)); 3434 } 3435 3436 fResizeBufferView->Sync(); 3437 fResizeBufferView->Unlock(); 3438 SetDrawingMode(B_OP_COPY); 3439 DrawBitmap(fResizeBufferView->Bitmap(), sourceRect, destRect); 3440 3441 #else 3442 3443 SetHighColor(highColor); 3444 SetLowColor(lowColor); 3445 FillRect(destRect, B_SOLID_LOW); 3446 3447 BField* field = row->GetField(column->fFieldID); 3448 if (field) { 3449 #if CONSTRAIN_CLIPPING_REGION 3450 BRegion clipRegion(destRect); 3451 PushState(); 3452 ConstrainClippingRegion(&clipRegion); 3453 #endif 3454 SetHighColor(fMasterView->Color(row->fNextSelected 3455 ? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT)); 3456 float baseline = floor(destRect.top + fh.ascent 3457 + (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2); 3458 MovePenTo(destRect.left + 8, baseline); 3459 column->DrawField(field, destRect, this); 3460 #if CONSTRAIN_CLIPPING_REGION 3461 PopState(); 3462 #endif 3463 } 3464 3465 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus() 3466 && Window()->IsActive()) { 3467 SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER)); 3468 StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom)); 3469 } 3470 #endif 3471 } 3472 } 3473 } 3474 3475 3476 void 3477 OutlineView::Draw(BRect invalidBounds) 3478 { 3479 #if SMART_REDRAW 3480 BRegion invalidRegion; 3481 GetClippingRegion(&invalidRegion); 3482 #endif 3483 3484 font_height fh; 3485 GetFontHeight(&fh); 3486 3487 float line = 0.0; 3488 bool tintedLine = true; 3489 int32 numColumns = fColumns->CountItems(); 3490 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); 3491 iterator.GoToNext()) { 3492 BRow* row = iterator.CurrentRow(); 3493 if (line > invalidBounds.bottom) 3494 break; 3495 3496 tintedLine = !tintedLine; 3497 float rowHeight = row->Height(); 3498 3499 if (line >= invalidBounds.top - rowHeight) { 3500 bool isFirstColumn = true; 3501 float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); 3502 3503 // setup background color 3504 rgb_color lowColor; 3505 if (row->fNextSelected != 0) { 3506 if (Window()->IsActive()) { 3507 if (fEditMode) 3508 lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND); 3509 else 3510 lowColor = fMasterView->Color(B_COLOR_SELECTION); 3511 } 3512 else 3513 lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION); 3514 } else 3515 lowColor = fMasterView->Color(B_COLOR_BACKGROUND); 3516 if (tintedLine) 3517 lowColor = tint_color(lowColor, kTintedLineTint); 3518 3519 for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) { 3520 BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex); 3521 if (!column->IsVisible()) 3522 continue; 3523 3524 if (!isFirstColumn && fieldLeftEdge > invalidBounds.right) 3525 break; 3526 3527 if (fieldLeftEdge + column->Width() >= invalidBounds.left) { 3528 BRect fullRect(fieldLeftEdge, line, 3529 fieldLeftEdge + column->Width(), line + rowHeight); 3530 3531 bool clippedFirstColumn = false; 3532 // This happens when a column is indented past the 3533 // beginning of the next column. 3534 3535 SetHighColor(lowColor); 3536 3537 BRect destRect(fullRect); 3538 if (isFirstColumn) { 3539 fullRect.left -= fMasterView->LatchWidth(); 3540 destRect.left += iterator.CurrentLevel() 3541 * kOutlineLevelIndent; 3542 if (destRect.left >= destRect.right) { 3543 // clipped 3544 FillRect(BRect(0, line, fieldLeftEdge 3545 + column->Width(), line + rowHeight)); 3546 clippedFirstColumn = true; 3547 } 3548 3549 FillRect(BRect(0, line, MAX(kLeftMargin, 3550 fMasterView->LatchWidth()), line + row->Height())); 3551 } 3552 3553 3554 #if SMART_REDRAW 3555 if (!clippedFirstColumn 3556 && invalidRegion.Intersects(fullRect)) { 3557 #else 3558 if (!clippedFirstColumn) { 3559 #endif 3560 FillRect(fullRect); // Using color set above 3561 3562 // Draw the latch widget if it has one. 3563 if (isFirstColumn) { 3564 if (row == fTargetRow 3565 && fCurrentState == LATCH_CLICKED) { 3566 // Note that this only occurs if the user is 3567 // holding down a latch while items are added 3568 // in the background. 3569 BPoint pos; 3570 uint32 buttons; 3571 GetMouse(&pos, &buttons); 3572 if (fLatchRect.Contains(pos)) { 3573 fMasterView->DrawLatch(this, fLatchRect, 3574 B_PRESSED_LATCH, fTargetRow); 3575 } else { 3576 fMasterView->DrawLatch(this, fLatchRect, 3577 row->fIsExpanded ? B_OPEN_LATCH 3578 : B_CLOSED_LATCH, fTargetRow); 3579 } 3580 } else { 3581 LatchType pos = B_NO_LATCH; 3582 if (row->HasLatch()) 3583 pos = row->fIsExpanded ? B_OPEN_LATCH 3584 : B_CLOSED_LATCH; 3585 3586 fMasterView->DrawLatch(this, 3587 BRect(destRect.left 3588 - fMasterView->LatchWidth(), 3589 destRect.top, destRect.left, 3590 destRect.bottom), pos, row); 3591 } 3592 } 3593 3594 SetHighColor(fMasterView->HighColor()); 3595 // The master view just holds the high color for us. 3596 SetLowColor(lowColor); 3597 3598 BField* field = row->GetField(column->fFieldID); 3599 if (field) { 3600 #if CONSTRAIN_CLIPPING_REGION 3601 BRegion clipRegion(destRect); 3602 PushState(); 3603 ConstrainClippingRegion(&clipRegion); 3604 #endif 3605 SetHighColor(fMasterView->Color( 3606 row->fNextSelected ? B_COLOR_SELECTION_TEXT 3607 : B_COLOR_TEXT)); 3608 float baseline = floor(destRect.top + fh.ascent 3609 + (destRect.Height() + 1 3610 - (fh.ascent+fh.descent)) / 2); 3611 MovePenTo(destRect.left + 8, baseline); 3612 column->DrawField(field, destRect, this); 3613 #if CONSTRAIN_CLIPPING_REGION 3614 PopState(); 3615 #endif 3616 } 3617 } 3618 } 3619 3620 isFirstColumn = false; 3621 fieldLeftEdge += column->Width() + 1; 3622 } 3623 3624 if (fieldLeftEdge <= invalidBounds.right) { 3625 SetHighColor(lowColor); 3626 FillRect(BRect(fieldLeftEdge, line, invalidBounds.right, 3627 line + rowHeight)); 3628 } 3629 } 3630 3631 // indicate the keyboard focus row 3632 if (fFocusRow == row && !fEditMode && fMasterView->IsFocus() 3633 && Window()->IsActive()) { 3634 SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER)); 3635 StrokeRect(BRect(0, line, 10000.0, line + rowHeight)); 3636 } 3637 3638 line += rowHeight + 1; 3639 } 3640 3641 if (line <= invalidBounds.bottom) { 3642 // fill background below last item 3643 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); 3644 FillRect(BRect(invalidBounds.left, line, invalidBounds.right, 3645 invalidBounds.bottom)); 3646 } 3647 3648 // Draw the drop target line 3649 if (fDropHighlightY != -1) { 3650 InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 3651 1000000, fDropHighlightY + kDropHighlightLineHeight / 2)); 3652 } 3653 } 3654 3655 3656 BRow* 3657 OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top) 3658 { 3659 if (_rowIndent && _top) { 3660 float line = 0.0; 3661 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); 3662 iterator.GoToNext()) { 3663 3664 BRow* row = iterator.CurrentRow(); 3665 if (line > ypos) 3666 break; 3667 3668 float rowHeight = row->Height(); 3669 if (ypos <= line + rowHeight) { 3670 *_top = line; 3671 *_rowIndent = iterator.CurrentLevel(); 3672 return row; 3673 } 3674 3675 line += rowHeight + 1; 3676 } 3677 } 3678 3679 return NULL; 3680 } 3681 3682 void OutlineView::SetMouseTrackingEnabled(bool enabled) 3683 { 3684 fTrackMouse = enabled; 3685 if (!enabled && fDropHighlightY != -1) { 3686 // Erase the old target line 3687 InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 3688 1000000, fDropHighlightY + kDropHighlightLineHeight / 2)); 3689 fDropHighlightY = -1; 3690 } 3691 } 3692 3693 3694 // 3695 // Note that this interaction is not totally safe. If items are added to 3696 // the list in the background, the widget rect will be incorrect, possibly 3697 // resulting in drawing glitches. The code that adds items needs to be a little smarter 3698 // about invalidating state. 3699 // 3700 void 3701 OutlineView::MouseDown(BPoint position) 3702 { 3703 if (!fEditMode) 3704 fMasterView->MakeFocus(true); 3705 3706 // Check to see if the user is clicking on a widget to open a section 3707 // of the list. 3708 bool reset_click_count = false; 3709 int32 indent; 3710 float rowTop; 3711 BRow* row = FindRow(position.y, &indent, &rowTop); 3712 if (row != NULL) { 3713 3714 // Update fCurrentField 3715 bool handle_field = false; 3716 BField* new_field = 0; 3717 BRow* new_row = 0; 3718 BColumn* new_column = 0; 3719 BRect new_rect; 3720 3721 if (position.y >= 0) { 3722 if (position.x >= 0) { 3723 float x = 0; 3724 for (int32 c = 0; c < fMasterView->CountColumns(); c++) { 3725 new_column = fMasterView->ColumnAt(c); 3726 if (!new_column->IsVisible()) 3727 continue; 3728 if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x) 3729 + new_column->Width() >= position.x) { 3730 if (new_column->WantsEvents()) { 3731 new_field = row->GetField(c); 3732 new_row = row; 3733 FindRect(new_row,&new_rect); 3734 new_rect.left = MAX(kLeftMargin, 3735 fMasterView->LatchWidth()) + x; 3736 new_rect.right = new_rect.left 3737 + new_column->Width() - 1; 3738 handle_field = true; 3739 } 3740 break; 3741 } 3742 x += new_column->Width(); 3743 } 3744 } 3745 } 3746 3747 // Handle mouse down 3748 if (handle_field) { 3749 fMouseDown = true; 3750 fFieldRect = new_rect; 3751 fCurrentColumn = new_column; 3752 fCurrentRow = new_row; 3753 fCurrentField = new_field; 3754 fCurrentCode = B_INSIDE_VIEW; 3755 BMessage* message = Window()->CurrentMessage(); 3756 int32 buttons = 1; 3757 message->FindInt32("buttons", &buttons); 3758 fCurrentColumn->MouseDown(fMasterView, fCurrentRow, 3759 fCurrentField, fFieldRect, position, buttons); 3760 } 3761 3762 if (!fEditMode) { 3763 3764 fTargetRow = row; 3765 fTargetRowTop = rowTop; 3766 FindVisibleRect(fFocusRow, &fFocusRowRect); 3767 3768 float leftWidgetBoundry = indent * kOutlineLevelIndent 3769 + MAX(kLeftMargin, fMasterView->LatchWidth()) 3770 - fMasterView->LatchWidth(); 3771 fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry 3772 + fMasterView->LatchWidth(), rowTop + row->Height()); 3773 if (fLatchRect.Contains(position) && row->HasLatch()) { 3774 fCurrentState = LATCH_CLICKED; 3775 if (fTargetRow->fNextSelected != 0) 3776 SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); 3777 else 3778 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); 3779 3780 FillRect(fLatchRect); 3781 if (fLatchRect.Contains(position)) { 3782 fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH, 3783 row); 3784 } else { 3785 fMasterView->DrawLatch(this, fLatchRect, 3786 fTargetRow->fIsExpanded ? B_OPEN_LATCH 3787 : B_CLOSED_LATCH, row); 3788 } 3789 } else { 3790 Invalidate(fFocusRowRect); 3791 fFocusRow = fTargetRow; 3792 FindVisibleRect(fFocusRow, &fFocusRowRect); 3793 3794 ASSERT(fTargetRow != 0); 3795 3796 if ((modifiers() & B_CONTROL_KEY) == 0) 3797 DeselectAll(); 3798 3799 if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0 3800 && fSelectionMode == B_MULTIPLE_SELECTION_LIST) { 3801 SelectRange(fFirstSelectedItem, fTargetRow); 3802 } 3803 else { 3804 if (fTargetRow->fNextSelected != 0) { 3805 // Unselect row 3806 fTargetRow->fNextSelected->fPrevSelected 3807 = fTargetRow->fPrevSelected; 3808 fTargetRow->fPrevSelected->fNextSelected 3809 = fTargetRow->fNextSelected; 3810 fTargetRow->fPrevSelected = 0; 3811 fTargetRow->fNextSelected = 0; 3812 fFirstSelectedItem = NULL; 3813 } else { 3814 // Select row 3815 if (fSelectionMode == B_SINGLE_SELECTION_LIST) 3816 DeselectAll(); 3817 3818 fTargetRow->fNextSelected 3819 = fSelectionListDummyHead.fNextSelected; 3820 fTargetRow->fPrevSelected 3821 = &fSelectionListDummyHead; 3822 fTargetRow->fNextSelected->fPrevSelected = fTargetRow; 3823 fTargetRow->fPrevSelected->fNextSelected = fTargetRow; 3824 fFirstSelectedItem = fTargetRow; 3825 } 3826 3827 Invalidate(BRect(fVisibleRect.left, fTargetRowTop, 3828 fVisibleRect.right, 3829 fTargetRowTop + fTargetRow->Height())); 3830 } 3831 3832 fCurrentState = ROW_CLICKED; 3833 if (fLastSelectedItem != fTargetRow) 3834 reset_click_count = true; 3835 fLastSelectedItem = fTargetRow; 3836 fMasterView->SelectionChanged(); 3837 3838 } 3839 } 3840 3841 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | 3842 B_NO_POINTER_HISTORY); 3843 3844 } else if (fFocusRow != 0) { 3845 // User clicked in open space, unhighlight focus row. 3846 FindVisibleRect(fFocusRow, &fFocusRowRect); 3847 fFocusRow = 0; 3848 Invalidate(fFocusRowRect); 3849 } 3850 3851 // We stash the click counts here because the 'clicks' field 3852 // is not in the CurrentMessage() when MouseUp is called... ;( 3853 if (reset_click_count) 3854 fClickCount = 1; 3855 else 3856 Window()->CurrentMessage()->FindInt32("clicks", &fClickCount); 3857 fClickPoint = position; 3858 3859 } 3860 3861 3862 void 3863 OutlineView::MouseMoved(BPoint position, uint32 /*transit*/, 3864 const BMessage* /*dragMessage*/) 3865 { 3866 if (!fMouseDown) { 3867 // Update fCurrentField 3868 bool handle_field = false; 3869 BField* new_field = 0; 3870 BRow* new_row = 0; 3871 BColumn* new_column = 0; 3872 BRect new_rect(0,0,0,0); 3873 if (position.y >=0 ) { 3874 float top; 3875 int32 indent; 3876 BRow* row = FindRow(position.y, &indent, &top); 3877 if (row && position.x >=0 ) { 3878 float x=0; 3879 for (int32 c=0;c<fMasterView->CountColumns();c++) { 3880 new_column = fMasterView->ColumnAt(c); 3881 if (!new_column->IsVisible()) 3882 continue; 3883 if ((MAX(kLeftMargin, 3884 fMasterView->LatchWidth()) + x) + new_column->Width() 3885 > position.x) { 3886 3887 if(new_column->WantsEvents()) { 3888 new_field = row->GetField(c); 3889 new_row = row; 3890 FindRect(new_row,&new_rect); 3891 new_rect.left = MAX(kLeftMargin, 3892 fMasterView->LatchWidth()) + x; 3893 new_rect.right = new_rect.left 3894 + new_column->Width() - 1; 3895 handle_field = true; 3896 } 3897 break; 3898 } 3899 x += new_column->Width(); 3900 } 3901 } 3902 } 3903 3904 // Handle mouse moved 3905 if (handle_field) { 3906 if (new_field != fCurrentField) { 3907 if (fCurrentField) { 3908 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3909 fCurrentField, fFieldRect, position, 0, 3910 fCurrentCode = B_EXITED_VIEW); 3911 } 3912 fCurrentColumn = new_column; 3913 fCurrentRow = new_row; 3914 fCurrentField = new_field; 3915 fFieldRect = new_rect; 3916 if (fCurrentField) { 3917 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3918 fCurrentField, fFieldRect, position, 0, 3919 fCurrentCode = B_ENTERED_VIEW); 3920 } 3921 } else { 3922 if (fCurrentField) { 3923 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3924 fCurrentField, fFieldRect, position, 0, 3925 fCurrentCode = B_INSIDE_VIEW); 3926 } 3927 } 3928 } else { 3929 if (fCurrentField) { 3930 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3931 fCurrentField, fFieldRect, position, 0, 3932 fCurrentCode = B_EXITED_VIEW); 3933 fCurrentField = 0; 3934 fCurrentColumn = 0; 3935 fCurrentRow = 0; 3936 } 3937 } 3938 } else { 3939 if (fCurrentField) { 3940 if (fFieldRect.Contains(position)) { 3941 if (fCurrentCode == B_OUTSIDE_VIEW 3942 || fCurrentCode == B_EXITED_VIEW) { 3943 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3944 fCurrentField, fFieldRect, position, 1, 3945 fCurrentCode = B_ENTERED_VIEW); 3946 } else { 3947 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3948 fCurrentField, fFieldRect, position, 1, 3949 fCurrentCode = B_INSIDE_VIEW); 3950 } 3951 } else { 3952 if (fCurrentCode == B_INSIDE_VIEW 3953 || fCurrentCode == B_ENTERED_VIEW) { 3954 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3955 fCurrentField, fFieldRect, position, 1, 3956 fCurrentCode = B_EXITED_VIEW); 3957 } else { 3958 fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, 3959 fCurrentField, fFieldRect, position, 1, 3960 fCurrentCode = B_OUTSIDE_VIEW); 3961 } 3962 } 3963 } 3964 } 3965 3966 if (!fEditMode) { 3967 3968 switch (fCurrentState) { 3969 case LATCH_CLICKED: 3970 if (fTargetRow->fNextSelected != 0) 3971 SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); 3972 else 3973 SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); 3974 3975 FillRect(fLatchRect); 3976 if (fLatchRect.Contains(position)) { 3977 fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH, 3978 fTargetRow); 3979 } else { 3980 fMasterView->DrawLatch(this, fLatchRect, 3981 fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH, 3982 fTargetRow); 3983 } 3984 break; 3985 3986 case ROW_CLICKED: 3987 if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity 3988 || abs((int)(position.y - fClickPoint.y)) 3989 > kRowDragSensitivity) { 3990 fCurrentState = DRAGGING_ROWS; 3991 fMasterView->InitiateDrag(fClickPoint, 3992 fTargetRow->fNextSelected != 0); 3993 } 3994 break; 3995 3996 case DRAGGING_ROWS: 3997 #if 0 3998 // falls through... 3999 #else 4000 if (fTrackMouse /*&& message*/) { 4001 if (fVisibleRect.Contains(position)) { 4002 float top; 4003 int32 indent; 4004 BRow* target = FindRow(position.y, &indent, &top); 4005 if (target) 4006 SetFocusRow(target, true); 4007 } 4008 } 4009 break; 4010 #endif 4011 4012 default: { 4013 4014 if (fTrackMouse /*&& message*/) { 4015 // Draw a highlight line... 4016 if (fVisibleRect.Contains(position)) { 4017 float top; 4018 int32 indent; 4019 BRow* target = FindRow(position.y, &indent, &top); 4020 if (target == fRollOverRow) 4021 break; 4022 if (fRollOverRow) { 4023 BRect rect; 4024 FindRect(fRollOverRow, &rect); 4025 Invalidate(rect); 4026 } 4027 fRollOverRow = target; 4028 #if 0 4029 SetFocusRow(fRollOverRow,false); 4030 #else 4031 PushState(); 4032 SetDrawingMode(B_OP_BLEND); 4033 SetHighColor(255, 255, 255, 255); 4034 BRect rect; 4035 FindRect(fRollOverRow, &rect); 4036 rect.bottom -= 1.0; 4037 FillRect(rect); 4038 PopState(); 4039 #endif 4040 } else { 4041 if (fRollOverRow) { 4042 BRect rect; 4043 FindRect(fRollOverRow, &rect); 4044 Invalidate(rect); 4045 fRollOverRow = NULL; 4046 } 4047 } 4048 } 4049 } 4050 } 4051 } 4052 } 4053 4054 4055 void 4056 OutlineView::MouseUp(BPoint position) 4057 { 4058 if (fCurrentField) { 4059 fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField); 4060 fMouseDown = false; 4061 } 4062 4063 if (fEditMode) 4064 return; 4065 4066 switch (fCurrentState) { 4067 case LATCH_CLICKED: 4068 if (fLatchRect.Contains(position)) { 4069 fMasterView->ExpandOrCollapse(fTargetRow, 4070 !fTargetRow->fIsExpanded); 4071 } 4072 4073 Invalidate(fLatchRect); 4074 fCurrentState = INACTIVE; 4075 break; 4076 4077 case ROW_CLICKED: 4078 if (fClickCount > 1 4079 && abs((int)fClickPoint.x - (int)position.x) 4080 < kDoubleClickMoveSensitivity 4081 && abs((int)fClickPoint.y - (int)position.y) 4082 < kDoubleClickMoveSensitivity) { 4083 fMasterView->ItemInvoked(); 4084 } 4085 fCurrentState = INACTIVE; 4086 break; 4087 4088 case DRAGGING_ROWS: 4089 fCurrentState = INACTIVE; 4090 // Falls through 4091 4092 default: 4093 if (fDropHighlightY != -1) { 4094 InvertRect(BRect(0, 4095 fDropHighlightY - kDropHighlightLineHeight / 2, 4096 1000000, fDropHighlightY + kDropHighlightLineHeight / 2)); 4097 // Erase the old target line 4098 fDropHighlightY = -1; 4099 } 4100 } 4101 } 4102 4103 4104 void 4105 OutlineView::MessageReceived(BMessage* message) 4106 { 4107 if (message->WasDropped()) { 4108 fMasterView->MessageDropped(message, 4109 ConvertFromScreen(message->DropPoint())); 4110 } else { 4111 BView::MessageReceived(message); 4112 } 4113 } 4114 4115 4116 #if DOUBLE_BUFFERED_COLUMN_RESIZE 4117 4118 ColumnResizeBufferView* 4119 OutlineView::ResizeBufferView() 4120 { 4121 return fResizeBufferView; 4122 } 4123 4124 #endif 4125 4126 4127 void 4128 OutlineView::ChangeFocusRow(bool up, bool updateSelection, 4129 bool addToCurrentSelection) 4130 { 4131 int32 indent; 4132 float top; 4133 float newRowPos = 0; 4134 float verticalScroll = 0; 4135 4136 if (fFocusRow) { 4137 // A row currently has the focus, get information about it 4138 newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4); 4139 if (newRowPos < fVisibleRect.top + 20) 4140 verticalScroll = newRowPos - 20; 4141 else if (newRowPos > fVisibleRect.bottom - 20) 4142 verticalScroll = newRowPos - fVisibleRect.Height() + 20; 4143 } else 4144 newRowPos = fVisibleRect.top + 2; 4145 // no row is currently focused, set this to the top of the window 4146 // so we will select the first visible item in the list. 4147 4148 BRow* newRow = FindRow(newRowPos, &indent, &top); 4149 if (newRow) { 4150 if (fFocusRow) { 4151 fFocusRowRect.right = 10000; 4152 Invalidate(fFocusRowRect); 4153 } 4154 BRow* oldFocusRow = fFocusRow; 4155 fFocusRow = newRow; 4156 fFocusRowRect.top = top; 4157 fFocusRowRect.left = 0; 4158 fFocusRowRect.right = 10000; 4159 fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height(); 4160 Invalidate(fFocusRowRect); 4161 4162 if (updateSelection) { 4163 if (!addToCurrentSelection 4164 || fSelectionMode == B_SINGLE_SELECTION_LIST) { 4165 DeselectAll(); 4166 } 4167 4168 // if the focus row isn't selected, add it to the selection 4169 if (fFocusRow->fNextSelected == 0) { 4170 fFocusRow->fNextSelected 4171 = fSelectionListDummyHead.fNextSelected; 4172 fFocusRow->fPrevSelected = &fSelectionListDummyHead; 4173 fFocusRow->fNextSelected->fPrevSelected = fFocusRow; 4174 fFocusRow->fPrevSelected->fNextSelected = fFocusRow; 4175 } else if (oldFocusRow != NULL 4176 && fSelectionListDummyHead.fNextSelected == oldFocusRow 4177 && (((IndexOf(oldFocusRow->fNextSelected) 4178 < IndexOf(oldFocusRow)) == up) 4179 || fFocusRow == oldFocusRow->fNextSelected)) { 4180 // if the focus row is selected, if: 4181 // 1. the previous focus row is last in the selection 4182 // 2a. the next selected row is now the focus row 4183 // 2b. or the next selected row is beyond the focus row 4184 // in the move direction 4185 // then deselect the previous focus row 4186 fSelectionListDummyHead.fNextSelected 4187 = oldFocusRow->fNextSelected; 4188 if (fSelectionListDummyHead.fNextSelected != NULL) { 4189 fSelectionListDummyHead.fNextSelected->fPrevSelected 4190 = &fSelectionListDummyHead; 4191 oldFocusRow->fNextSelected = NULL; 4192 } 4193 oldFocusRow->fPrevSelected = NULL; 4194 } 4195 4196 fLastSelectedItem = fFocusRow; 4197 } 4198 } else 4199 Invalidate(fFocusRowRect); 4200 4201 if (verticalScroll != 0) { 4202 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL); 4203 float min, max; 4204 vScrollBar->GetRange(&min, &max); 4205 if (verticalScroll < min) 4206 verticalScroll = min; 4207 else if (verticalScroll > max) 4208 verticalScroll = max; 4209 4210 vScrollBar->SetValue(verticalScroll); 4211 } 4212 4213 if (newRow && updateSelection) 4214 fMasterView->SelectionChanged(); 4215 } 4216 4217 4218 void 4219 OutlineView::MoveFocusToVisibleRect() 4220 { 4221 fFocusRow = 0; 4222 ChangeFocusRow(true, true, false); 4223 } 4224 4225 4226 BRow* 4227 OutlineView::CurrentSelection(BRow* lastSelected) const 4228 { 4229 BRow* row; 4230 if (lastSelected == 0) 4231 row = fSelectionListDummyHead.fNextSelected; 4232 else 4233 row = lastSelected->fNextSelected; 4234 4235 4236 if (row == &fSelectionListDummyHead) 4237 row = 0; 4238 4239 return row; 4240 } 4241 4242 4243 void 4244 OutlineView::ToggleFocusRowSelection(bool selectRange) 4245 { 4246 if (fFocusRow == 0) 4247 return; 4248 4249 if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST) 4250 SelectRange(fLastSelectedItem, fFocusRow); 4251 else { 4252 if (fFocusRow->fNextSelected != 0) { 4253 // Unselect row 4254 fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected; 4255 fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected; 4256 fFocusRow->fPrevSelected = 0; 4257 fFocusRow->fNextSelected = 0; 4258 } else { 4259 // Select row 4260 if (fSelectionMode == B_SINGLE_SELECTION_LIST) 4261 DeselectAll(); 4262 4263 fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected; 4264 fFocusRow->fPrevSelected = &fSelectionListDummyHead; 4265 fFocusRow->fNextSelected->fPrevSelected = fFocusRow; 4266 fFocusRow->fPrevSelected->fNextSelected = fFocusRow; 4267 } 4268 } 4269 4270 fLastSelectedItem = fFocusRow; 4271 fMasterView->SelectionChanged(); 4272 Invalidate(fFocusRowRect); 4273 } 4274 4275 4276 void 4277 OutlineView::ToggleFocusRowOpen() 4278 { 4279 if (fFocusRow) 4280 fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded); 4281 } 4282 4283 4284 void 4285 OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand) 4286 { 4287 // TODO: Could use CopyBits here to speed things up. 4288 4289 if (parentRow == NULL) 4290 return; 4291 4292 if (parentRow->fIsExpanded == expand) 4293 return; 4294 4295 parentRow->fIsExpanded = expand; 4296 4297 BRect parentRect; 4298 if (FindRect(parentRow, &parentRect)) { 4299 // Determine my new height 4300 float subTreeHeight = 0.0; 4301 if (parentRow->fIsExpanded) 4302 for (RecursiveOutlineIterator iterator(parentRow->fChildList); 4303 iterator.CurrentRow(); 4304 iterator.GoToNext() 4305 ) 4306 { 4307 subTreeHeight += iterator.CurrentRow()->Height()+1; 4308 } 4309 else 4310 for (RecursiveOutlineIterator iterator(parentRow->fChildList); 4311 iterator.CurrentRow(); 4312 iterator.GoToNext() 4313 ) 4314 { 4315 subTreeHeight -= iterator.CurrentRow()->Height()+1; 4316 } 4317 fItemsHeight += subTreeHeight; 4318 4319 // Adjust focus row if necessary. 4320 if (FindRect(fFocusRow, &fFocusRowRect) == false) { 4321 // focus row is in a subtree that has collapsed, 4322 // move it up to the parent. 4323 fFocusRow = parentRow; 4324 FindRect(fFocusRow, &fFocusRowRect); 4325 } 4326 4327 Invalidate(BRect(0, parentRect.top, fVisibleRect.right, 4328 fVisibleRect.bottom)); 4329 FixScrollBar(false); 4330 } 4331 } 4332 4333 void 4334 OutlineView::RemoveRow(BRow* row) 4335 { 4336 if (row == NULL) 4337 return; 4338 4339 BRow* parentRow = NULL; 4340 bool parentIsVisible = false; 4341 FindParent(row, &parentRow, &parentIsVisible); 4342 // NOTE: This could be a root row without a parent, in which case 4343 // it is always visible, though. 4344 4345 // Adjust height for the visible sub-tree that is going to be removed. 4346 float subTreeHeight = 0.0f; 4347 if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) { 4348 // The row itself is visible at least. 4349 subTreeHeight = row->Height() + 1; 4350 if (row->fIsExpanded) { 4351 // Adjust for the height of visible sub-items as well. 4352 // (By default, the iterator follows open branches only.) 4353 for (RecursiveOutlineIterator iterator(row->fChildList); 4354 iterator.CurrentRow(); iterator.GoToNext()) 4355 subTreeHeight += iterator.CurrentRow()->Height() + 1; 4356 } 4357 BRect invalid; 4358 if (FindRect(row, &invalid)) { 4359 invalid.bottom = Bounds().bottom; 4360 if (invalid.IsValid()) 4361 Invalidate(invalid); 4362 } 4363 } 4364 4365 fItemsHeight -= subTreeHeight; 4366 4367 FixScrollBar(false); 4368 int32 indent = 0; 4369 float top = 0.0; 4370 if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) { 4371 // after removing this row, no rows are actually visible any more, 4372 // force a scroll to make them visible again 4373 if (fItemsHeight > fVisibleRect.Height()) 4374 ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top); 4375 else 4376 ScrollBy(0.0, -Bounds().top); 4377 } 4378 if (parentRow != NULL) { 4379 parentRow->fChildList->RemoveItem(row); 4380 if (parentRow->fChildList->CountItems() == 0) { 4381 delete parentRow->fChildList; 4382 parentRow->fChildList = 0; 4383 // It was the last child row of the parent, which also means the 4384 // latch disappears. 4385 BRect parentRowRect; 4386 if (parentIsVisible && FindRect(parentRow, &parentRowRect)) 4387 Invalidate(parentRowRect); 4388 } 4389 } else 4390 fRows.RemoveItem(row); 4391 4392 // Adjust focus row if necessary. 4393 if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) { 4394 // focus row is in a subtree that is gone, move it up to the parent. 4395 fFocusRow = parentRow; 4396 if (fFocusRow) 4397 FindRect(fFocusRow, &fFocusRowRect); 4398 } 4399 4400 // Remove this from the selection if necessary 4401 if (row->fNextSelected != 0) { 4402 row->fNextSelected->fPrevSelected = row->fPrevSelected; 4403 row->fPrevSelected->fNextSelected = row->fNextSelected; 4404 row->fPrevSelected = 0; 4405 row->fNextSelected = 0; 4406 fMasterView->SelectionChanged(); 4407 } 4408 4409 fCurrentColumn = 0; 4410 fCurrentRow = 0; 4411 fCurrentField = 0; 4412 } 4413 4414 4415 BRowContainer* 4416 OutlineView::RowList() 4417 { 4418 return &fRows; 4419 } 4420 4421 4422 void 4423 OutlineView::UpdateRow(BRow* row) 4424 { 4425 if (row) { 4426 // Determine if this row has changed its sort order 4427 BRow* parentRow = NULL; 4428 bool parentIsVisible = false; 4429 FindParent(row, &parentRow, &parentIsVisible); 4430 4431 BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList; 4432 4433 if(list) { 4434 int32 rowIndex = list->IndexOf(row); 4435 ASSERT(rowIndex >= 0); 4436 ASSERT(list->ItemAt(rowIndex) == row); 4437 4438 bool rowMoved = false; 4439 if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0) 4440 rowMoved = true; 4441 4442 if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1), 4443 row) < 0) 4444 rowMoved = true; 4445 4446 if (rowMoved) { 4447 // Sort location of this row has changed. 4448 // Remove and re-add in the right spot 4449 SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)); 4450 } else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) { 4451 BRect invalidRect; 4452 if (FindVisibleRect(row, &invalidRect)) 4453 Invalidate(invalidRect); 4454 } 4455 } 4456 } 4457 } 4458 4459 4460 void 4461 OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow) 4462 { 4463 if (!row) 4464 return; 4465 4466 row->fParent = parentRow; 4467 4468 if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) { 4469 // Ignore index here. 4470 if (parentRow) { 4471 if (parentRow->fChildList == NULL) 4472 parentRow->fChildList = new BRowContainer; 4473 4474 AddSorted(parentRow->fChildList, row); 4475 } else 4476 AddSorted(&fRows, row); 4477 } else { 4478 // Note, a -1 index implies add to end if sorting is not enabled 4479 if (parentRow) { 4480 if (parentRow->fChildList == 0) 4481 parentRow->fChildList = new BRowContainer; 4482 4483 if (Index < 0 || Index > parentRow->fChildList->CountItems()) 4484 parentRow->fChildList->AddItem(row); 4485 else 4486 parentRow->fChildList->AddItem(row, Index); 4487 } else { 4488 if (Index < 0 || Index >= fRows.CountItems()) 4489 fRows.AddItem(row); 4490 else 4491 fRows.AddItem(row, Index); 4492 } 4493 } 4494 4495 #ifdef DOUBLE_BUFFERED_COLUMN_RESIZE 4496 ResizeBufferView()->UpdateMaxHeight(row->Height()); 4497 #endif 4498 4499 if (parentRow == 0 || parentRow->fIsExpanded) 4500 fItemsHeight += row->Height() + 1; 4501 4502 FixScrollBar(false); 4503 4504 BRect newRowRect; 4505 const bool newRowIsInOpenBranch = FindRect(row, &newRowRect); 4506 4507 if (newRowIsInOpenBranch) { 4508 if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) { 4509 // The focus row has moved. 4510 Invalidate(fFocusRowRect); 4511 FindRect(fFocusRow, &fFocusRowRect); 4512 Invalidate(fFocusRowRect); 4513 } 4514 4515 if (fCurrentState == INACTIVE) { 4516 if (newRowRect.bottom < fVisibleRect.top) { 4517 // The new row is totally above the current viewport, move 4518 // everything down and redraw the first line. 4519 BRect source(fVisibleRect); 4520 BRect dest(fVisibleRect); 4521 source.bottom -= row->Height() + 1; 4522 dest.top += row->Height() + 1; 4523 CopyBits(source, dest); 4524 Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right, 4525 fVisibleRect.top + newRowRect.Height())); 4526 } else if (newRowRect.top < fVisibleRect.bottom) { 4527 // New item is somewhere in the current region. Scroll everything 4528 // beneath it down and invalidate just the new row rect. 4529 BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right, 4530 fVisibleRect.bottom - newRowRect.Height()); 4531 BRect dest(source); 4532 dest.OffsetBy(0, newRowRect.Height() + 1); 4533 CopyBits(source, dest); 4534 Invalidate(newRowRect); 4535 } // otherwise, this is below the currently visible region 4536 } else { 4537 // Adding the item may have caused the item that the user is currently 4538 // selected to move. This would cause annoying drawing and interaction 4539 // bugs, as the position of that item is cached. If this happens, resize 4540 // the scroll bar, then scroll back so the selected item is in view. 4541 BRect targetRect; 4542 if (FindRect(fTargetRow, &targetRect)) { 4543 float delta = targetRect.top - fTargetRowTop; 4544 if (delta != 0) { 4545 // This causes a jump because ScrollBy will copy a chunk of the view. 4546 // Since the actual contents of the view have been offset, we don't 4547 // want this, we just want to change the virtual origin of the window. 4548 // Constrain the clipping region so everything is clipped out so no 4549 // copy occurs. 4550 // 4551 // xxx this currently doesn't work if the scroll bars aren't enabled. 4552 // everything will still move anyway. A minor annoyance. 4553 BRegion emptyRegion; 4554 ConstrainClippingRegion(&emptyRegion); 4555 PushState(); 4556 ScrollBy(0, delta); 4557 PopState(); 4558 ConstrainClippingRegion(NULL); 4559 4560 fTargetRowTop += delta; 4561 fClickPoint.y += delta; 4562 fLatchRect.OffsetBy(0, delta); 4563 } 4564 } 4565 } 4566 } 4567 4568 // If the parent was previously childless, it will need to have a latch 4569 // drawn. 4570 BRect parentRect; 4571 if (parentRow && parentRow->fChildList->CountItems() == 1 4572 && FindVisibleRect(parentRow, &parentRect)) 4573 Invalidate(parentRect); 4574 } 4575 4576 4577 void 4578 OutlineView::FixScrollBar(bool scrollToFit) 4579 { 4580 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL); 4581 if (vScrollBar) { 4582 if (fItemsHeight > fVisibleRect.Height()) { 4583 float maxScrollBarValue = fItemsHeight - fVisibleRect.Height(); 4584 vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight); 4585 4586 // If the user is scrolled down too far when making the range smaller, the list 4587 // will jump suddenly, which is undesirable. In this case, don't fix the scroll 4588 // bar here. In ScrollTo, it checks to see if this has occured, and will 4589 // fix the scroll bars sneakily if the user has scrolled up far enough. 4590 if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) { 4591 vScrollBar->SetRange(0.0, maxScrollBarValue); 4592 vScrollBar->SetSteps(20.0, fVisibleRect.Height()); 4593 } 4594 } else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0) 4595 vScrollBar->SetRange(0.0, 0.0); // disable scroll bar. 4596 } 4597 } 4598 4599 4600 void 4601 OutlineView::AddSorted(BRowContainer* list, BRow* row) 4602 { 4603 if (list && row) { 4604 // Find general vicinity with binary search. 4605 int32 lower = 0; 4606 int32 upper = list->CountItems()-1; 4607 while( lower < upper ) { 4608 int32 middle = lower + (upper-lower+1)/2; 4609 int32 cmp = CompareRows(row, list->ItemAt(middle)); 4610 if( cmp < 0 ) upper = middle-1; 4611 else if( cmp > 0 ) lower = middle+1; 4612 else lower = upper = middle; 4613 } 4614 4615 // At this point, 'upper' and 'lower' at the last found item. 4616 // Arbitrarily use 'upper' and determine the final insertion 4617 // point -- either before or after this item. 4618 if( upper < 0 ) upper = 0; 4619 else if( upper < list->CountItems() ) { 4620 if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++; 4621 } 4622 4623 if (upper >= list->CountItems()) 4624 list->AddItem(row); // Adding to end. 4625 else 4626 list->AddItem(row, upper); // Insert 4627 } 4628 } 4629 4630 4631 int32 4632 OutlineView::CompareRows(BRow* row1, BRow* row2) 4633 { 4634 int32 itemCount (fSortColumns->CountItems()); 4635 if (row1 && row2) { 4636 for (int32 index = 0; index < itemCount; index++) { 4637 BColumn* column = (BColumn*) fSortColumns->ItemAt(index); 4638 int comp = 0; 4639 BField* field1 = (BField*) row1->GetField(column->fFieldID); 4640 BField* field2 = (BField*) row2->GetField(column->fFieldID); 4641 if (field1 && field2) 4642 comp = column->CompareFields(field1, field2); 4643 4644 if (!column->fSortAscending) 4645 comp = -comp; 4646 4647 if (comp != 0) 4648 return comp; 4649 } 4650 } 4651 return 0; 4652 } 4653 4654 4655 void 4656 OutlineView::FrameResized(float width, float height) 4657 { 4658 fVisibleRect.right = fVisibleRect.left + width; 4659 fVisibleRect.bottom = fVisibleRect.top + height; 4660 FixScrollBar(true); 4661 _inherited::FrameResized(width, height); 4662 } 4663 4664 4665 void 4666 OutlineView::ScrollTo(BPoint position) 4667 { 4668 fVisibleRect.OffsetTo(position.x, position.y); 4669 4670 // In FixScrollBar, we might not have been able to change the size of 4671 // the scroll bar because the user was scrolled down too far. Take 4672 // this opportunity to sneak it in if we can. 4673 BScrollBar* vScrollBar = ScrollBar(B_VERTICAL); 4674 float maxScrollBarValue = fItemsHeight - fVisibleRect.Height(); 4675 float min, max; 4676 vScrollBar->GetRange(&min, &max); 4677 if (max != maxScrollBarValue && position.y > maxScrollBarValue) 4678 FixScrollBar(true); 4679 4680 _inherited::ScrollTo(position); 4681 } 4682 4683 4684 const BRect& 4685 OutlineView::VisibleRect() const 4686 { 4687 return fVisibleRect; 4688 } 4689 4690 4691 bool 4692 OutlineView::FindVisibleRect(BRow* row, BRect* _rect) 4693 { 4694 if (row && _rect) { 4695 float line = 0.0; 4696 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); 4697 iterator.GoToNext()) { 4698 4699 if (iterator.CurrentRow() == row) { 4700 _rect->Set(fVisibleRect.left, line, fVisibleRect.right, 4701 line + row->Height()); 4702 return line <= fVisibleRect.bottom; 4703 } 4704 4705 line += iterator.CurrentRow()->Height() + 1; 4706 } 4707 } 4708 return false; 4709 } 4710 4711 4712 bool 4713 OutlineView::FindRect(const BRow* row, BRect* _rect) 4714 { 4715 float line = 0.0; 4716 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); 4717 iterator.GoToNext()) { 4718 if (iterator.CurrentRow() == row) { 4719 _rect->Set(fVisibleRect.left, line, fVisibleRect.right, 4720 line + row->Height()); 4721 return true; 4722 } 4723 4724 line += iterator.CurrentRow()->Height() + 1; 4725 } 4726 4727 return false; 4728 } 4729 4730 4731 void 4732 OutlineView::ScrollTo(const BRow* row) 4733 { 4734 BRect rect; 4735 if (FindRect(row, &rect)) { 4736 BRect bounds = Bounds(); 4737 if (rect.top < bounds.top) 4738 ScrollTo(BPoint(bounds.left, rect.top)); 4739 else if (rect.bottom > bounds.bottom) 4740 ScrollBy(0, rect.bottom - bounds.bottom); 4741 } 4742 } 4743 4744 4745 void 4746 OutlineView::DeselectAll() 4747 { 4748 // Invalidate all selected rows 4749 float line = 0.0; 4750 for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); 4751 iterator.GoToNext()) { 4752 if (line > fVisibleRect.bottom) 4753 break; 4754 4755 BRow* row = iterator.CurrentRow(); 4756 if (line + row->Height() > fVisibleRect.top) { 4757 if (row->fNextSelected != 0) 4758 Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right, 4759 line + row->Height())); 4760 } 4761 4762 line += row->Height() + 1; 4763 } 4764 4765 // Set items not selected 4766 while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) { 4767 BRow* row = fSelectionListDummyHead.fNextSelected; 4768 row->fNextSelected->fPrevSelected = row->fPrevSelected; 4769 row->fPrevSelected->fNextSelected = row->fNextSelected; 4770 row->fNextSelected = 0; 4771 row->fPrevSelected = 0; 4772 } 4773 } 4774 4775 4776 BRow* 4777 OutlineView::FocusRow() const 4778 { 4779 return fFocusRow; 4780 } 4781 4782 4783 void 4784 OutlineView::SetFocusRow(BRow* row, bool Select) 4785 { 4786 if (row) { 4787 if (Select) 4788 AddToSelection(row); 4789 4790 if (fFocusRow == row) 4791 return; 4792 4793 Invalidate(fFocusRowRect); // invalidate previous 4794 4795 fTargetRow = fFocusRow = row; 4796 4797 FindVisibleRect(fFocusRow, &fFocusRowRect); 4798 Invalidate(fFocusRowRect); // invalidate current 4799 4800 fFocusRowRect.right = 10000; 4801 fMasterView->SelectionChanged(); 4802 } 4803 } 4804 4805 4806 bool 4807 OutlineView::SortList(BRowContainer* list, bool isVisible) 4808 { 4809 if (list) { 4810 // Shellsort 4811 BRow** items 4812 = (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items(); 4813 int32 numItems = list->CountItems(); 4814 int h; 4815 for (h = 1; h < numItems / 9; h = 3 * h + 1) 4816 ; 4817 4818 for (;h > 0; h /= 3) { 4819 for (int step = h; step < numItems; step++) { 4820 BRow* temp = items[step]; 4821 int i; 4822 for (i = step - h; i >= 0; i -= h) { 4823 if (CompareRows(temp, items[i]) < 0) 4824 items[i + h] = items[i]; 4825 else 4826 break; 4827 } 4828 4829 items[i + h] = temp; 4830 } 4831 } 4832 4833 if (isVisible) { 4834 Invalidate(); 4835 4836 InvalidateCachedPositions(); 4837 int lockCount = Window()->CountLocks(); 4838 for (int i = 0; i < lockCount; i++) 4839 Window()->Unlock(); 4840 4841 while (lockCount--) 4842 if (!Window()->Lock()) 4843 return false; // Window is gone... 4844 } 4845 } 4846 return true; 4847 } 4848 4849 4850 int32 4851 OutlineView::DeepSortThreadEntry(void* _outlineView) 4852 { 4853 ((OutlineView*) _outlineView)->DeepSort(); 4854 return 0; 4855 } 4856 4857 4858 void 4859 OutlineView::DeepSort() 4860 { 4861 struct stack_entry { 4862 bool isVisible; 4863 BRowContainer* list; 4864 int32 listIndex; 4865 } stack[kMaxDepth]; 4866 int32 stackTop = 0; 4867 4868 stack[stackTop].list = &fRows; 4869 stack[stackTop].isVisible = true; 4870 stack[stackTop].listIndex = 0; 4871 fNumSorted = 0; 4872 4873 if (Window()->Lock() == false) 4874 return; 4875 4876 bool doneSorting = false; 4877 while (!doneSorting && !fSortCancelled) { 4878 4879 stack_entry* currentEntry = &stack[stackTop]; 4880 4881 // xxx Can make the invalidate area smaller by finding the rect for the 4882 // parent item and using that as the top of the invalid rect. 4883 4884 bool haveLock = SortList(currentEntry->list, currentEntry->isVisible); 4885 if (!haveLock) 4886 return ; // window is gone. 4887 4888 // Fix focus rect. 4889 InvalidateCachedPositions(); 4890 if (fCurrentState != INACTIVE) 4891 fCurrentState = INACTIVE; // sorry... 4892 4893 // next list. 4894 bool foundNextList = false; 4895 while (!foundNextList && !fSortCancelled) { 4896 for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems(); 4897 index++) { 4898 BRow* parentRow = currentEntry->list->ItemAt(index); 4899 BRowContainer* childList = parentRow->fChildList; 4900 if (childList != 0) { 4901 currentEntry->listIndex = index + 1; 4902 stackTop++; 4903 ASSERT(stackTop < kMaxDepth); 4904 stack[stackTop].listIndex = 0; 4905 stack[stackTop].list = childList; 4906 stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded); 4907 foundNextList = true; 4908 break; 4909 } 4910 } 4911 4912 if (!foundNextList) { 4913 // back up 4914 if (--stackTop < 0) { 4915 doneSorting = true; 4916 break; 4917 } 4918 4919 currentEntry = &stack[stackTop]; 4920 } 4921 } 4922 } 4923 4924 Window()->Unlock(); 4925 } 4926 4927 4928 void 4929 OutlineView::StartSorting() 4930 { 4931 // If this view is not yet attached to a window, don't start a sort thread! 4932 if (Window() == NULL) 4933 return; 4934 4935 if (fSortThread != B_BAD_THREAD_ID) { 4936 thread_info tinfo; 4937 if (get_thread_info(fSortThread, &tinfo) == B_OK) { 4938 // Unlock window so this won't deadlock (sort thread is probably 4939 // waiting to lock window). 4940 4941 int lockCount = Window()->CountLocks(); 4942 for (int i = 0; i < lockCount; i++) 4943 Window()->Unlock(); 4944 4945 fSortCancelled = true; 4946 int32 status; 4947 wait_for_thread(fSortThread, &status); 4948 4949 while (lockCount--) 4950 if (!Window()->Lock()) 4951 return ; // Window is gone... 4952 } 4953 } 4954 4955 fSortCancelled = false; 4956 fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this); 4957 resume_thread(fSortThread); 4958 } 4959 4960 4961 void 4962 OutlineView::SelectRange(BRow* start, BRow* end) 4963 { 4964 if (!start || !end) 4965 return; 4966 4967 if (start == end) // start is always selected when this is called 4968 return; 4969 4970 RecursiveOutlineIterator iterator(&fRows, false); 4971 while (iterator.CurrentRow() != 0) { 4972 if (iterator.CurrentRow() == end) { 4973 // reverse selection, swap to fix special case 4974 BRow* temp = start; 4975 start = end; 4976 end = temp; 4977 break; 4978 } else if (iterator.CurrentRow() == start) 4979 break; 4980 4981 iterator.GoToNext(); 4982 } 4983 4984 while (true) { 4985 BRow* row = iterator.CurrentRow(); 4986 if (row) { 4987 if (row->fNextSelected == 0) { 4988 row->fNextSelected = fSelectionListDummyHead.fNextSelected; 4989 row->fPrevSelected = &fSelectionListDummyHead; 4990 row->fNextSelected->fPrevSelected = row; 4991 row->fPrevSelected->fNextSelected = row; 4992 } 4993 } else 4994 break; 4995 4996 if (row == end) 4997 break; 4998 4999 iterator.GoToNext(); 5000 } 5001 5002 Invalidate(); // xxx make invalidation smaller 5003 } 5004 5005 5006 bool 5007 OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible) 5008 { 5009 bool result = false; 5010 if (row != NULL && outParent != NULL) { 5011 *outParent = row->fParent; 5012 5013 if (outParentIsVisible != NULL) { 5014 // Walk up the parent chain to determine if this row is visible 5015 *outParentIsVisible = true; 5016 for (BRow* currentRow = row->fParent; currentRow != NULL; 5017 currentRow = currentRow->fParent) { 5018 if (!currentRow->fIsExpanded) { 5019 *outParentIsVisible = false; 5020 break; 5021 } 5022 } 5023 } 5024 5025 result = *outParent != NULL; 5026 } 5027 5028 return result; 5029 } 5030 5031 5032 int32 5033 OutlineView::IndexOf(BRow* row) 5034 { 5035 if (row) { 5036 if (row->fParent == 0) 5037 return fRows.IndexOf(row); 5038 5039 ASSERT(row->fParent->fChildList); 5040 return row->fParent->fChildList->IndexOf(row); 5041 } 5042 5043 return B_ERROR; 5044 } 5045 5046 5047 void 5048 OutlineView::InvalidateCachedPositions() 5049 { 5050 if (fFocusRow) 5051 FindRect(fFocusRow, &fFocusRowRect); 5052 } 5053 5054 5055 float 5056 OutlineView::GetColumnPreferredWidth(BColumn* column) 5057 { 5058 float preferred = 0.0; 5059 for (RecursiveOutlineIterator iterator(&fRows); BRow* row = 5060 iterator.CurrentRow(); iterator.GoToNext()) { 5061 BField* field = row->GetField(column->fFieldID); 5062 if (field) { 5063 float width = column->GetPreferredWidth(field, this) 5064 + iterator.CurrentLevel() * kOutlineLevelIndent; 5065 preferred = max_c(preferred, width); 5066 } 5067 } 5068 5069 BString name; 5070 column->GetColumnName(&name); 5071 preferred = max_c(preferred, StringWidth(name)); 5072 5073 // Constrain to preferred width. This makes the method do a little 5074 // more than asked, but it's for convenience. 5075 if (preferred < column->MinWidth()) 5076 preferred = column->MinWidth(); 5077 else if (preferred > column->MaxWidth()) 5078 preferred = column->MaxWidth(); 5079 5080 return preferred; 5081 } 5082 5083 5084 // #pragma mark - 5085 5086 5087 RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list, 5088 bool openBranchesOnly) 5089 : 5090 fStackIndex(0), 5091 fCurrentListIndex(0), 5092 fCurrentListDepth(0), 5093 fOpenBranchesOnly(openBranchesOnly) 5094 { 5095 if (list == 0 || list->CountItems() == 0) 5096 fCurrentList = 0; 5097 else 5098 fCurrentList = list; 5099 } 5100 5101 5102 BRow* 5103 RecursiveOutlineIterator::CurrentRow() const 5104 { 5105 if (fCurrentList == 0) 5106 return 0; 5107 5108 return fCurrentList->ItemAt(fCurrentListIndex); 5109 } 5110 5111 5112 void 5113 RecursiveOutlineIterator::GoToNext() 5114 { 5115 if (fCurrentList == 0) 5116 return; 5117 if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) { 5118 fCurrentList = 0; 5119 return; 5120 } 5121 5122 BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex); 5123 if(currentRow) { 5124 if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly) 5125 && currentRow->fChildList->CountItems() > 0) { 5126 // Visit child. 5127 // Put current list on the stack if it needs to be revisited. 5128 if (fCurrentListIndex < fCurrentList->CountItems() - 1) { 5129 fStack[fStackIndex].fRowSet = fCurrentList; 5130 fStack[fStackIndex].fIndex = fCurrentListIndex + 1; 5131 fStack[fStackIndex].fDepth = fCurrentListDepth; 5132 fStackIndex++; 5133 } 5134 5135 fCurrentList = currentRow->fChildList; 5136 fCurrentListIndex = 0; 5137 fCurrentListDepth++; 5138 } else if (fCurrentListIndex < fCurrentList->CountItems() - 1) 5139 fCurrentListIndex++; // next item in current list 5140 else if (--fStackIndex >= 0) { 5141 fCurrentList = fStack[fStackIndex].fRowSet; 5142 fCurrentListIndex = fStack[fStackIndex].fIndex; 5143 fCurrentListDepth = fStack[fStackIndex].fDepth; 5144 } else 5145 fCurrentList = 0; 5146 } 5147 } 5148 5149 5150 int32 5151 RecursiveOutlineIterator::CurrentLevel() const 5152 { 5153 return fCurrentListDepth; 5154 } 5155 5156 5157