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