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