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