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