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 // ListView title drawing and mouse manipulation classes 37 38 39 #include "TitleView.h" 40 41 #include <Alert.h> 42 #include <Application.h> 43 #include <ControlLook.h> 44 #include <Debug.h> 45 #include <PopUpMenu.h> 46 #include <Window.h> 47 48 #include <stdio.h> 49 #include <string.h> 50 51 #include "Commands.h" 52 #include "ContainerWindow.h" 53 #include "PoseView.h" 54 #include "Utilities.h" 55 56 57 #define APP_SERVER_CLEARS_BACKGROUND 1 58 59 60 static rgb_color sTitleBackground; 61 static rgb_color sDarkTitleBackground; 62 static rgb_color sShineColor; 63 static rgb_color sLightShadowColor; 64 static rgb_color sShadowColor; 65 static rgb_color sDarkShadowColor; 66 67 const rgb_color kHighlightColor = {100, 100, 210, 255}; 68 69 70 static void 71 _DrawLine(BPoseView* view, BPoint from, BPoint to) 72 { 73 rgb_color highColor = view->HighColor(); 74 view->SetHighColor(tint_color(view->LowColor(), B_DARKEN_1_TINT)); 75 view->StrokeLine(from, to); 76 view->SetHighColor(highColor); 77 } 78 79 80 static void 81 _UndrawLine(BPoseView* view, BPoint from, BPoint to) 82 { 83 view->StrokeLine(from, to, B_SOLID_LOW); 84 } 85 86 87 static void 88 _DrawOutline(BView* view, BRect where) 89 { 90 if (be_control_look != NULL) { 91 where.right++; 92 where.bottom--; 93 } else 94 where.InsetBy(1, 1); 95 rgb_color highColor = view->HighColor(); 96 view->SetHighColor(kHighlightColor); 97 view->StrokeRect(where); 98 view->SetHighColor(highColor); 99 } 100 101 102 // #pragma mark - BTitleView 103 104 105 BTitleView::BTitleView(BRect frame, BPoseView* view) 106 : 107 BView(frame, "TitleView", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW), 108 fPoseView(view), 109 fTitleList(10, true), 110 fHorizontalResizeCursor(B_CURSOR_ID_RESIZE_EAST_WEST), 111 fPreviouslyClickedColumnTitle(0), 112 fTrackingState(NULL) 113 { 114 sTitleBackground = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 0.88f); 115 // 216 -> 220 116 sDarkTitleBackground = tint_color(sTitleBackground, B_DARKEN_1_TINT); 117 sShineColor = tint_color(sTitleBackground, B_LIGHTEN_MAX_TINT); 118 sLightShadowColor = tint_color(sTitleBackground, B_DARKEN_2_TINT); 119 sShadowColor = tint_color(sTitleBackground, B_DARKEN_4_TINT); 120 sDarkShadowColor = tint_color(sShadowColor, B_DARKEN_2_TINT); 121 122 SetHighColor(sTitleBackground); 123 SetLowColor(sTitleBackground); 124 #if APP_SERVER_CLEARS_BACKGROUND 125 SetViewColor(sTitleBackground); 126 #else 127 SetViewColor(B_TRANSPARENT_COLOR); 128 #endif 129 130 BFont font(be_plain_font); 131 font.SetSize(9); 132 SetFont(&font); 133 134 Reset(); 135 } 136 137 138 BTitleView::~BTitleView() 139 { 140 delete fTrackingState; 141 } 142 143 144 void 145 BTitleView::Reset() 146 { 147 fTitleList.MakeEmpty(); 148 149 for (int32 index = 0; ; index++) { 150 BColumn* column = fPoseView->ColumnAt(index); 151 if (!column) 152 break; 153 fTitleList.AddItem(new BColumnTitle(this, column)); 154 } 155 Invalidate(); 156 } 157 158 159 void 160 BTitleView::AddTitle(BColumn* column, const BColumn* after) 161 { 162 int32 count = fTitleList.CountItems(); 163 int32 index; 164 if (after) { 165 for (index = 0; index < count; index++) { 166 BColumn* titleColumn = fTitleList.ItemAt(index)->Column(); 167 168 if (after == titleColumn) { 169 index++; 170 break; 171 } 172 } 173 } else 174 index = count; 175 176 fTitleList.AddItem(new BColumnTitle(this, column), index); 177 Invalidate(); 178 } 179 180 181 void 182 BTitleView::RemoveTitle(BColumn* column) 183 { 184 int32 count = fTitleList.CountItems(); 185 for (int32 index = 0; index < count; index++) { 186 BColumnTitle* title = fTitleList.ItemAt(index); 187 if (title->Column() == column) { 188 fTitleList.RemoveItem(title); 189 break; 190 } 191 } 192 193 Invalidate(); 194 } 195 196 197 void 198 BTitleView::Draw(BRect rect) 199 { 200 Draw(rect, false); 201 } 202 203 204 void 205 BTitleView::Draw(BRect /*updateRect*/, bool useOffscreen, bool updateOnly, 206 const BColumnTitle* pressedColumn, 207 void (*trackRectBlitter)(BView*, BRect), BRect passThru) 208 { 209 BRect bounds(Bounds()); 210 BView* view; 211 212 if (useOffscreen) { 213 ASSERT(sOffscreen); 214 BRect frame(bounds); 215 frame.right += frame.left; 216 // ToDo: this is kind of messy way of avoiding being clipped 217 // by the amount the title is scrolled to the left 218 view = sOffscreen->BeginUsing(frame); 219 view->SetOrigin(-bounds.left, 0); 220 view->SetLowColor(LowColor()); 221 view->SetHighColor(HighColor()); 222 BFont font(be_plain_font); 223 font.SetSize(9); 224 view->SetFont(&font); 225 } else 226 view = this; 227 228 if (be_control_look != NULL) { 229 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 230 view->SetHighColor(tint_color(base, B_DARKEN_2_TINT)); 231 view->StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); 232 bounds.bottom--; 233 234 be_control_look->DrawButtonBackground(view, bounds, bounds, base, 0, 235 BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER); 236 } else { 237 // fill background with light gray background 238 if (!updateOnly) 239 view->FillRect(bounds, B_SOLID_LOW); 240 241 view->BeginLineArray(4); 242 view->AddLine(bounds.LeftTop(), bounds.RightTop(), sShadowColor); 243 view->AddLine(bounds.LeftBottom(), bounds.RightBottom(), 244 sShadowColor); 245 // draw lighter gray and white inset lines 246 bounds.InsetBy(0, 1); 247 view->AddLine(bounds.LeftBottom(), bounds.RightBottom(), 248 sLightShadowColor); 249 view->AddLine(bounds.LeftTop(), bounds.RightTop(), sShineColor); 250 view->EndLineArray(); 251 } 252 253 int32 count = fTitleList.CountItems(); 254 float minx = bounds.right; 255 float maxx = bounds.left; 256 for (int32 index = 0; index < count; index++) { 257 BColumnTitle* title = fTitleList.ItemAt(index); 258 title->Draw(view, title == pressedColumn); 259 BRect titleBounds(title->Bounds()); 260 if (titleBounds.left < minx) 261 minx = titleBounds.left; 262 if (titleBounds.right > maxx) 263 maxx = titleBounds.right; 264 } 265 266 if (be_control_look != NULL) { 267 bounds = Bounds(); 268 minx--; 269 view->SetHighColor(sLightShadowColor); 270 view->StrokeLine(BPoint(minx, bounds.top), 271 BPoint(minx, bounds.bottom - 1)); 272 } else { 273 // first and last shades before and after first column 274 maxx++; 275 minx--; 276 view->BeginLineArray(2); 277 view->AddLine(BPoint(minx, bounds.top), 278 BPoint(minx, bounds.bottom), sShadowColor); 279 view->AddLine(BPoint(maxx, bounds.top), 280 BPoint(maxx, bounds.bottom), sShineColor); 281 view->EndLineArray(); 282 } 283 284 #if !(APP_SERVER_CLEARS_BACKGROUND) 285 FillRect(BRect(bounds.left, bounds.top + 1, minx - 1, bounds.bottom - 1), 286 B_SOLID_LOW); 287 FillRect(BRect(maxx + 1, bounds.top + 1, bounds.right, bounds.bottom - 1), 288 B_SOLID_LOW); 289 #endif 290 291 if (useOffscreen) { 292 if (trackRectBlitter) 293 (trackRectBlitter)(view, passThru); 294 295 view->Sync(); 296 DrawBitmap(sOffscreen->Bitmap()); 297 sOffscreen->DoneUsing(); 298 } else if (trackRectBlitter) 299 (trackRectBlitter)(view, passThru); 300 } 301 302 303 void 304 BTitleView::MouseDown(BPoint where) 305 { 306 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 307 if (window == NULL) 308 return; 309 310 if (!window->IsActive()) { 311 // window wasn't active, activate it and bail 312 window->Activate(); 313 return; 314 } 315 316 // finish any pending edits 317 fPoseView->CommitActivePose(); 318 319 BColumnTitle* title = FindColumnTitle(where); 320 BColumnTitle* resizedTitle = InColumnResizeArea(where); 321 322 uint32 buttons; 323 GetMouse(&where, &buttons); 324 325 // Check if the user clicked the secondary mouse button. 326 // if so, display the attribute menu: 327 328 if (SecondaryMouseButtonDown(modifiers(), buttons)) { 329 BPopUpMenu* menu = new BPopUpMenu("Attributes", false, false); 330 menu->SetFont(be_plain_font); 331 window->NewAttributeMenu(menu); 332 window->AddMimeTypesToMenu(menu); 333 window->MarkAttributeMenu(menu); 334 menu->SetTargetForItems(window->PoseView()); 335 menu->Go(ConvertToScreen(where), true, false); 336 return; 337 } 338 339 bigtime_t doubleClickSpeed; 340 get_click_speed(&doubleClickSpeed); 341 342 if (resizedTitle) { 343 bool force = static_cast<bool>(buttons & B_TERTIARY_MOUSE_BUTTON); 344 if (force || buttons & B_PRIMARY_MOUSE_BUTTON) { 345 if (force || fPreviouslyClickedColumnTitle != 0) { 346 if (force || system_time() - fPreviousLeftClickTime 347 < doubleClickSpeed) { 348 if (fPoseView-> 349 ResizeColumnToWidest(resizedTitle->Column())) { 350 Invalidate(); 351 return; 352 } 353 } 354 } 355 fPreviousLeftClickTime = system_time(); 356 fPreviouslyClickedColumnTitle = resizedTitle; 357 } 358 } else if (!title) 359 return; 360 361 SetMouseEventMask(B_POINTER_EVENTS, 362 B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS); 363 364 // track the mouse 365 if (resizedTitle) { 366 fTrackingState = new ColumnResizeState(this, resizedTitle, where, 367 system_time() + doubleClickSpeed); 368 } else { 369 fTrackingState = new ColumnDragState(this, title, where, 370 system_time() + doubleClickSpeed); 371 } 372 } 373 374 375 void 376 BTitleView::MouseUp(BPoint where) 377 { 378 if (fTrackingState == NULL) 379 return; 380 381 fTrackingState->MouseUp(where); 382 383 delete fTrackingState; 384 fTrackingState = NULL; 385 } 386 387 388 void 389 BTitleView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 390 { 391 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 392 if (window == NULL) 393 return; 394 395 if (fTrackingState != NULL) { 396 int32 buttons = 0; 397 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 398 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 399 fTrackingState->MouseMoved(where, buttons); 400 return; 401 } 402 403 switch (code) { 404 default: 405 if (InColumnResizeArea(where) && window->IsActive()) 406 SetViewCursor(&fHorizontalResizeCursor); 407 else 408 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 409 break; 410 411 case B_EXITED_VIEW: 412 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 413 break; 414 } 415 416 _inherited::MouseMoved(where, code, dragMessage); 417 } 418 419 420 BColumnTitle* 421 BTitleView::InColumnResizeArea(BPoint where) const 422 { 423 int32 count = fTitleList.CountItems(); 424 for (int32 index = 0; index < count; index++) { 425 BColumnTitle* title = fTitleList.ItemAt(index); 426 if (title->InColumnResizeArea(where)) 427 return title; 428 } 429 430 return NULL; 431 } 432 433 434 BColumnTitle* 435 BTitleView::FindColumnTitle(BPoint where) const 436 { 437 int32 count = fTitleList.CountItems(); 438 for (int32 index = 0; index < count; index++) { 439 BColumnTitle* title = fTitleList.ItemAt(index); 440 if (title->Bounds().Contains(where)) 441 return title; 442 } 443 444 return NULL; 445 } 446 447 448 BColumnTitle* 449 BTitleView::FindColumnTitle(const BColumn* column) const 450 { 451 int32 count = fTitleList.CountItems(); 452 for (int32 index = 0; index < count; index++) { 453 BColumnTitle* title = fTitleList.ItemAt(index); 454 if (title->Column() == column) 455 return title; 456 } 457 458 return NULL; 459 } 460 461 462 // #pragma mark - BColumnTitle 463 464 465 BColumnTitle::BColumnTitle(BTitleView* view, BColumn* column) 466 : 467 fColumn(column), 468 fParent(view) 469 { 470 } 471 472 473 bool 474 BColumnTitle::InColumnResizeArea(BPoint where) const 475 { 476 BRect edge(Bounds()); 477 edge.left = edge.right - kEdgeSize; 478 edge.right += kEdgeSize; 479 480 return edge.Contains(where); 481 } 482 483 484 BRect 485 BColumnTitle::Bounds() const 486 { 487 BRect bounds(fColumn->Offset() - kTitleColumnLeftExtraMargin, 0, 0, 488 kTitleViewHeight); 489 bounds.right = bounds.left + fColumn->Width() + kTitleColumnExtraMargin; 490 491 return bounds; 492 } 493 494 495 void 496 BColumnTitle::Draw(BView* view, bool pressed) 497 { 498 BRect bounds(Bounds()); 499 BPoint loc(0, bounds.bottom - 4); 500 501 if (pressed) { 502 if (be_control_look != NULL) { 503 bounds.bottom--; 504 BRect rect(bounds); 505 rect.right--; 506 rgb_color base = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 507 B_DARKEN_1_TINT); 508 509 be_control_look->DrawButtonBackground(view, rect, rect, base, 0, 510 BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER); 511 } else { 512 view->SetLowColor(sDarkTitleBackground); 513 view->FillRect(bounds, B_SOLID_LOW); 514 } 515 } 516 517 BString titleString(fColumn->Title()); 518 view->TruncateString(&titleString, B_TRUNCATE_END, 519 bounds.Width() - kTitleColumnExtraMargin); 520 float resultingWidth = view->StringWidth(titleString.String()); 521 522 switch (fColumn->Alignment()) { 523 case B_ALIGN_LEFT: 524 default: 525 loc.x = bounds.left + 1 + kTitleColumnLeftExtraMargin; 526 break; 527 528 case B_ALIGN_CENTER: 529 loc.x = bounds.left + (bounds.Width() / 2) - (resultingWidth / 2); 530 break; 531 532 case B_ALIGN_RIGHT: 533 loc.x = bounds.right - resultingWidth 534 - kTitleColumnRightExtraMargin; 535 break; 536 } 537 538 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1.75)); 539 view->DrawString(titleString.String(), loc); 540 541 // show sort columns 542 bool secondary 543 = (fColumn->AttrHash() == fParent->PoseView()->SecondarySort()); 544 if (secondary 545 || (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) { 546 547 BPoint center(loc.x - 6, roundf((bounds.top + bounds.bottom) / 2.0)); 548 BPoint triangle[3]; 549 if (fParent->PoseView()->ReverseSort()) { 550 triangle[0] = center + BPoint(-3.5, 1.5); 551 triangle[1] = center + BPoint(3.5, 1.5); 552 triangle[2] = center + BPoint(0.0, -2.0); 553 } else { 554 triangle[0] = center + BPoint(-3.5, -1.5); 555 triangle[1] = center + BPoint(3.5, -1.5); 556 triangle[2] = center + BPoint(0.0, 2.0); 557 } 558 559 uint32 flags = view->Flags(); 560 view->SetFlags(flags | B_SUBPIXEL_PRECISE); 561 562 if (secondary) { 563 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 564 1.3)); 565 view->FillTriangle(triangle[0], triangle[1], triangle[2]); 566 } else { 567 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 568 1.6)); 569 view->FillTriangle(triangle[0], triangle[1], triangle[2]); 570 } 571 572 view->SetFlags(flags); 573 } 574 575 if (be_control_look != NULL) { 576 view->SetHighColor(sLightShadowColor); 577 view->StrokeLine(bounds.RightTop(), bounds.RightBottom()); 578 } else { 579 BRect rect(bounds); 580 581 view->SetHighColor(sShadowColor); 582 view->StrokeRect(rect); 583 584 view->BeginLineArray(4); 585 // draw lighter gray and white inset lines 586 rect.InsetBy(1, 1); 587 view->AddLine(rect.LeftBottom(), rect.RightBottom(), 588 pressed ? sLightShadowColor : sLightShadowColor); 589 view->AddLine(rect.LeftTop(), rect.RightTop(), 590 pressed ? sDarkShadowColor : sShineColor); 591 592 view->AddLine(rect.LeftTop(), rect.LeftBottom(), 593 pressed ? sDarkShadowColor : sShineColor); 594 view->AddLine(rect.RightTop(), rect.RightBottom(), 595 pressed ? sLightShadowColor : sLightShadowColor); 596 597 view->EndLineArray(); 598 } 599 } 600 601 602 // #pragma mark - ColumnTrackState 603 604 605 ColumnTrackState::ColumnTrackState(BTitleView* view, BColumnTitle* title, 606 BPoint where, bigtime_t pastClickTime) 607 : 608 fTitleView(view), 609 fTitle(title), 610 fFirstClickPoint(where), 611 fPastClickTime(pastClickTime), 612 fHasMoved(false) 613 { 614 } 615 616 617 void 618 ColumnTrackState::MouseUp(BPoint where) 619 { 620 // if it is pressed shortly and not moved, it is a click 621 // else it is a track 622 if (system_time() <= fPastClickTime && !fHasMoved) 623 Clicked(where); 624 else 625 Done(where); 626 } 627 628 629 void 630 ColumnTrackState::MouseMoved(BPoint where, uint32 buttons) 631 { 632 if (!fHasMoved && system_time() < fPastClickTime) { 633 BRect moveMargin(fFirstClickPoint, fFirstClickPoint); 634 moveMargin.InsetBy(-1, -1); 635 636 if (moveMargin.Contains(where)) 637 return; 638 } 639 640 Moved(where, buttons); 641 fHasMoved = true; 642 } 643 644 645 // #pragma mark - ColumnResizeState 646 647 648 ColumnResizeState::ColumnResizeState(BTitleView* view, BColumnTitle* title, 649 BPoint where, bigtime_t pastClickTime) 650 : 651 ColumnTrackState(view, title, where, pastClickTime), 652 fLastLineDrawPos(-1), 653 fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width()) 654 - where.x) 655 { 656 DrawLine(); 657 } 658 659 660 bool 661 ColumnResizeState::ValueChanged(BPoint where) 662 { 663 float newWidth = where.x + fInitialTrackOffset 664 - fTitle->fColumn->Offset(); 665 if (newWidth < kMinColumnWidth) 666 newWidth = kMinColumnWidth; 667 668 return newWidth != fTitle->fColumn->Width(); 669 } 670 671 672 void 673 ColumnResizeState::Moved(BPoint where, uint32) 674 { 675 float newWidth = where.x + fInitialTrackOffset 676 - fTitle->fColumn->Offset(); 677 if (newWidth < kMinColumnWidth) 678 newWidth = kMinColumnWidth; 679 680 BPoseView* poseView = fTitleView->PoseView(); 681 682 //bool shrink = (newWidth < fTitle->fColumn->Width()); 683 684 // resize the column 685 poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos, 686 _DrawLine, _UndrawLine); 687 688 BRect bounds(fTitleView->Bounds()); 689 bounds.left = fTitle->fColumn->Offset(); 690 691 // force title redraw 692 fTitleView->Draw(bounds, true, false); 693 } 694 695 696 void 697 ColumnResizeState::Done(BPoint /*where*/) 698 { 699 UndrawLine(); 700 } 701 702 703 void 704 ColumnResizeState::Clicked(BPoint /*where*/) 705 { 706 UndrawLine(); 707 } 708 709 710 void 711 ColumnResizeState::DrawLine() 712 { 713 BPoseView* poseView = fTitleView->PoseView(); 714 ASSERT(!poseView->IsDesktopWindow()); 715 716 BRect poseViewBounds(poseView->Bounds()); 717 // remember the line location 718 poseViewBounds.left = fTitle->Bounds().right; 719 fLastLineDrawPos = poseViewBounds.left; 720 721 // draw the line in the new location 722 _DrawLine(poseView, poseViewBounds.LeftTop(), 723 poseViewBounds.LeftBottom()); 724 } 725 726 727 void 728 ColumnResizeState::UndrawLine() 729 { 730 if (fLastLineDrawPos < 0) 731 return; 732 733 BRect poseViewBounds(fTitleView->PoseView()->Bounds()); 734 poseViewBounds.left = fLastLineDrawPos; 735 736 _UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(), 737 poseViewBounds.LeftBottom()); 738 } 739 740 741 // #pragma mark - ColumnDragState 742 743 744 ColumnDragState::ColumnDragState(BTitleView* view, BColumnTitle* columnTitle, 745 BPoint where, bigtime_t pastClickTime) 746 : 747 ColumnTrackState(view, columnTitle, where, pastClickTime), 748 fInitialMouseTrackOffset(where.x), 749 fTrackingRemovedColumn(false) 750 { 751 ASSERT(columnTitle); 752 ASSERT(fTitle); 753 ASSERT(fTitle->Column()); 754 DrawPressNoOutline(); 755 } 756 757 758 // ToDo: 759 // Autoscroll when dragging column left/right 760 // fix dragging back a column before the first column (now adds as last) 761 // make column swaps/adds not invalidate/redraw columns to the left 762 void 763 ColumnDragState::Moved(BPoint where, uint32) 764 { 765 // figure out where we are with the mouse 766 BRect titleBounds(fTitleView->Bounds()); 767 bool overTitleView = titleBounds.Contains(where); 768 BColumnTitle* overTitle = overTitleView 769 ? fTitleView->FindColumnTitle(where) : 0; 770 BRect titleBoundsWithMargin(titleBounds); 771 titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin); 772 bool inMarginRect = overTitleView 773 || titleBoundsWithMargin.Contains(where); 774 775 bool drawOutline = false; 776 bool undrawOutline = false; 777 778 if (fTrackingRemovedColumn) { 779 if (overTitleView) { 780 // tracked back with a removed title into the title bar, add it 781 // back 782 fTitleView->EndRectTracking(); 783 fColumnArchive.Seek(0, SEEK_SET); 784 BColumn* column = BColumn::InstantiateFromStream(&fColumnArchive); 785 ASSERT(column); 786 const BColumn* after = NULL; 787 if (overTitle) 788 after = overTitle->Column(); 789 790 fTitleView->PoseView()->AddColumn(column, after); 791 fTrackingRemovedColumn = false; 792 fTitle = fTitleView->FindColumnTitle(column); 793 fInitialMouseTrackOffset += fTitle->Bounds().left; 794 drawOutline = true; 795 } 796 } else { 797 if (!inMarginRect) { 798 // dragged a title out of the hysteresis margin around the 799 // title bar - remove it and start dragging it as a dotted outline 800 801 BRect rect(fTitle->Bounds()); 802 rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5); 803 fColumnArchive.Seek(0, SEEK_SET); 804 fTitle->Column()->ArchiveToStream(&fColumnArchive); 805 fInitialMouseTrackOffset -= fTitle->Bounds().left; 806 if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(), 807 false)) { 808 fTitle = 0; 809 fTitleView->BeginRectTracking(rect); 810 fTrackingRemovedColumn = true; 811 undrawOutline = true; 812 } 813 } else if (overTitle && overTitle != fTitle 814 // over a different column 815 && (overTitle->Bounds().left >= fTitle->Bounds().right 816 // over the one to the right 817 || where.x < overTitle->Bounds().left 818 + fTitle->Bounds().Width())) { 819 // over the one to the left, far enough to not snap 820 // right back 821 822 BColumn* column = fTitle->Column(); 823 fInitialMouseTrackOffset -= fTitle->Bounds().left; 824 // swap the columns 825 fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column()); 826 // re-grab the title object looking it up by the column 827 fTitle = fTitleView->FindColumnTitle(column); 828 // recalc initialMouseTrackOffset 829 fInitialMouseTrackOffset += fTitle->Bounds().left; 830 drawOutline = true; 831 } else 832 drawOutline = true; 833 } 834 835 if (drawOutline) 836 DrawOutline(where.x - fInitialMouseTrackOffset); 837 else if (undrawOutline) 838 UndrawOutline(); 839 } 840 841 842 void 843 ColumnDragState::Done(BPoint /*where*/) 844 { 845 if (fTrackingRemovedColumn) 846 fTitleView->EndRectTracking(); 847 UndrawOutline(); 848 } 849 850 851 void 852 ColumnDragState::Clicked(BPoint /*where*/) 853 { 854 BPoseView* poseView = fTitleView->PoseView(); 855 uint32 hash = fTitle->Column()->AttrHash(); 856 uint32 primarySort = poseView->PrimarySort(); 857 uint32 secondarySort = poseView->SecondarySort(); 858 bool shift = (modifiers() & B_SHIFT_KEY) != 0; 859 860 // For now: 861 // if we hit the primary sort field again 862 // then if shift key was down, switch primary and secondary 863 if (hash == primarySort) { 864 if (shift && secondarySort) { 865 poseView->SetPrimarySort(secondarySort); 866 poseView->SetSecondarySort(primarySort); 867 } else 868 poseView->SetReverseSort(!poseView->ReverseSort()); 869 } else if (shift) { 870 // hit secondary sort column with shift key, disable 871 if (hash == secondarySort) 872 poseView->SetSecondarySort(0); 873 else 874 poseView->SetSecondarySort(hash); 875 } else { 876 poseView->SetPrimarySort(hash); 877 poseView->SetReverseSort(false); 878 } 879 880 if (poseView->PrimarySort() == poseView->SecondarySort()) 881 poseView->SetSecondarySort(0); 882 883 UndrawOutline(); 884 885 poseView->SortPoses(); 886 poseView->Invalidate(); 887 } 888 889 890 bool 891 ColumnDragState::ValueChanged(BPoint) 892 { 893 return true; 894 } 895 896 897 void 898 ColumnDragState::DrawPressNoOutline() 899 { 900 fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle); 901 } 902 903 904 void 905 ColumnDragState::DrawOutline(float pos) 906 { 907 BRect outline(fTitle->Bounds()); 908 outline.OffsetBy(pos, 0); 909 fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline, 910 outline); 911 } 912 913 914 void 915 ColumnDragState::UndrawOutline() 916 { 917 fTitleView->Draw(fTitleView->Bounds(), true, false); 918 } 919 920 921 OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap; 922