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