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