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