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