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