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