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