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