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