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