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