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