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