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