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->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1.75)); 522 view->DrawString(titleString.String(), loc); 523 524 // show sort columns 525 bool secondary 526 = (fColumn->AttrHash() == fParent->PoseView()->SecondarySort()); 527 if (secondary 528 || (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) { 529 530 BPoint center(loc.x - 6, roundf((bounds.top + bounds.bottom) / 2.0)); 531 BPoint triangle[3]; 532 if (fParent->PoseView()->ReverseSort()) { 533 triangle[0] = center + BPoint(-3.5, 1.5); 534 triangle[1] = center + BPoint(3.5, 1.5); 535 triangle[2] = center + BPoint(0.0, -2.0); 536 } else { 537 triangle[0] = center + BPoint(-3.5, -1.5); 538 triangle[1] = center + BPoint(3.5, -1.5); 539 triangle[2] = center + BPoint(0.0, 2.0); 540 } 541 542 uint32 flags = view->Flags(); 543 view->SetFlags(flags | B_SUBPIXEL_PRECISE); 544 545 if (secondary) { 546 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 547 1.3)); 548 view->FillTriangle(triangle[0], triangle[1], triangle[2]); 549 } else { 550 view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 551 1.6)); 552 view->FillTriangle(triangle[0], triangle[1], triangle[2]); 553 } 554 555 view->SetFlags(flags); 556 } 557 558 view->SetHighColor(sLightShadowColor); 559 view->StrokeLine(bounds.RightTop(), bounds.RightBottom()); 560 } 561 562 563 // #pragma mark - ColumnTrackState 564 565 566 ColumnTrackState::ColumnTrackState(BTitleView* view, BColumnTitle* title, 567 BPoint where, bigtime_t pastClickTime) 568 : 569 fTitleView(view), 570 fTitle(title), 571 fFirstClickPoint(where), 572 fPastClickTime(pastClickTime), 573 fHasMoved(false) 574 { 575 } 576 577 578 void 579 ColumnTrackState::MouseUp(BPoint where) 580 { 581 // if it is pressed shortly and not moved, it is a click 582 // else it is a track 583 if (system_time() <= fPastClickTime && !fHasMoved) 584 Clicked(where); 585 else 586 Done(where); 587 } 588 589 590 void 591 ColumnTrackState::MouseMoved(BPoint where, uint32 buttons) 592 { 593 if (!fHasMoved && system_time() < fPastClickTime) { 594 BRect moveMargin(fFirstClickPoint, fFirstClickPoint); 595 moveMargin.InsetBy(-1, -1); 596 597 if (moveMargin.Contains(where)) 598 return; 599 } 600 601 Moved(where, buttons); 602 fHasMoved = true; 603 } 604 605 606 // #pragma mark - ColumnResizeState 607 608 609 ColumnResizeState::ColumnResizeState(BTitleView* view, BColumnTitle* title, 610 BPoint where, bigtime_t pastClickTime) 611 : 612 ColumnTrackState(view, title, where, pastClickTime), 613 fLastLineDrawPos(-1), 614 fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width()) 615 - where.x) 616 { 617 DrawLine(); 618 } 619 620 621 bool 622 ColumnResizeState::ValueChanged(BPoint where) 623 { 624 float newWidth = where.x + fInitialTrackOffset 625 - fTitle->fColumn->Offset(); 626 if (newWidth < kMinColumnWidth) 627 newWidth = kMinColumnWidth; 628 629 return newWidth != fTitle->fColumn->Width(); 630 } 631 632 633 void 634 ColumnResizeState::Moved(BPoint where, uint32) 635 { 636 float newWidth = where.x + fInitialTrackOffset 637 - fTitle->fColumn->Offset(); 638 if (newWidth < kMinColumnWidth) 639 newWidth = kMinColumnWidth; 640 641 BPoseView* poseView = fTitleView->PoseView(); 642 643 //bool shrink = (newWidth < fTitle->fColumn->Width()); 644 645 // resize the column 646 poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos, 647 _DrawLine, _UndrawLine); 648 649 BRect bounds(fTitleView->Bounds()); 650 bounds.left = fTitle->fColumn->Offset(); 651 652 // force title redraw 653 fTitleView->Draw(bounds, true, false); 654 } 655 656 657 void 658 ColumnResizeState::Done(BPoint /*where*/) 659 { 660 UndrawLine(); 661 } 662 663 664 void 665 ColumnResizeState::Clicked(BPoint /*where*/) 666 { 667 UndrawLine(); 668 } 669 670 671 void 672 ColumnResizeState::DrawLine() 673 { 674 BPoseView* poseView = fTitleView->PoseView(); 675 ASSERT(!poseView->IsDesktopWindow()); 676 677 BRect poseViewBounds(poseView->Bounds()); 678 // remember the line location 679 poseViewBounds.left = fTitle->Bounds().right; 680 fLastLineDrawPos = poseViewBounds.left; 681 682 // draw the line in the new location 683 _DrawLine(poseView, poseViewBounds.LeftTop(), 684 poseViewBounds.LeftBottom()); 685 } 686 687 688 void 689 ColumnResizeState::UndrawLine() 690 { 691 if (fLastLineDrawPos < 0) 692 return; 693 694 BRect poseViewBounds(fTitleView->PoseView()->Bounds()); 695 poseViewBounds.left = fLastLineDrawPos; 696 697 _UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(), 698 poseViewBounds.LeftBottom()); 699 } 700 701 702 // #pragma mark - ColumnDragState 703 704 705 ColumnDragState::ColumnDragState(BTitleView* view, BColumnTitle* columnTitle, 706 BPoint where, bigtime_t pastClickTime) 707 : 708 ColumnTrackState(view, columnTitle, where, pastClickTime), 709 fInitialMouseTrackOffset(where.x), 710 fTrackingRemovedColumn(false) 711 { 712 ASSERT(columnTitle); 713 ASSERT(fTitle); 714 ASSERT(fTitle->Column()); 715 DrawPressNoOutline(); 716 } 717 718 719 // ToDo: 720 // Autoscroll when dragging column left/right 721 // fix dragging back a column before the first column (now adds as last) 722 // make column swaps/adds not invalidate/redraw columns to the left 723 void 724 ColumnDragState::Moved(BPoint where, uint32) 725 { 726 // figure out where we are with the mouse 727 BRect titleBounds(fTitleView->Bounds()); 728 bool overTitleView = titleBounds.Contains(where); 729 BColumnTitle* overTitle = overTitleView 730 ? fTitleView->FindColumnTitle(where) : 0; 731 BRect titleBoundsWithMargin(titleBounds); 732 titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin); 733 bool inMarginRect = overTitleView 734 || titleBoundsWithMargin.Contains(where); 735 736 bool drawOutline = false; 737 bool undrawOutline = false; 738 739 if (fTrackingRemovedColumn) { 740 if (overTitleView) { 741 // tracked back with a removed title into the title bar, add it 742 // back 743 fTitleView->EndRectTracking(); 744 fColumnArchive.Seek(0, SEEK_SET); 745 BColumn* column = BColumn::InstantiateFromStream(&fColumnArchive); 746 ASSERT(column); 747 const BColumn* after = NULL; 748 if (overTitle) 749 after = overTitle->Column(); 750 751 fTitleView->PoseView()->AddColumn(column, after); 752 fTrackingRemovedColumn = false; 753 fTitle = fTitleView->FindColumnTitle(column); 754 fInitialMouseTrackOffset += fTitle->Bounds().left; 755 drawOutline = true; 756 } 757 } else { 758 if (!inMarginRect) { 759 // dragged a title out of the hysteresis margin around the 760 // title bar - remove it and start dragging it as a dotted outline 761 762 BRect rect(fTitle->Bounds()); 763 rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5); 764 fColumnArchive.Seek(0, SEEK_SET); 765 fTitle->Column()->ArchiveToStream(&fColumnArchive); 766 fInitialMouseTrackOffset -= fTitle->Bounds().left; 767 if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(), 768 false)) { 769 fTitle = 0; 770 fTitleView->BeginRectTracking(rect); 771 fTrackingRemovedColumn = true; 772 undrawOutline = true; 773 } 774 } else if (overTitle && overTitle != fTitle 775 // over a different column 776 && (overTitle->Bounds().left >= fTitle->Bounds().right 777 // over the one to the right 778 || where.x < overTitle->Bounds().left 779 + fTitle->Bounds().Width())) { 780 // over the one to the left, far enough to not snap 781 // right back 782 783 BColumn* column = fTitle->Column(); 784 fInitialMouseTrackOffset -= fTitle->Bounds().left; 785 // swap the columns 786 fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column()); 787 // re-grab the title object looking it up by the column 788 fTitle = fTitleView->FindColumnTitle(column); 789 // recalc initialMouseTrackOffset 790 fInitialMouseTrackOffset += fTitle->Bounds().left; 791 drawOutline = true; 792 } else 793 drawOutline = true; 794 } 795 796 if (drawOutline) 797 DrawOutline(where.x - fInitialMouseTrackOffset); 798 else if (undrawOutline) 799 UndrawOutline(); 800 } 801 802 803 void 804 ColumnDragState::Done(BPoint /*where*/) 805 { 806 if (fTrackingRemovedColumn) 807 fTitleView->EndRectTracking(); 808 UndrawOutline(); 809 } 810 811 812 void 813 ColumnDragState::Clicked(BPoint /*where*/) 814 { 815 BPoseView* poseView = fTitleView->PoseView(); 816 uint32 hash = fTitle->Column()->AttrHash(); 817 uint32 primarySort = poseView->PrimarySort(); 818 uint32 secondarySort = poseView->SecondarySort(); 819 bool shift = (modifiers() & B_SHIFT_KEY) != 0; 820 821 // For now: 822 // if we hit the primary sort field again 823 // then if shift key was down, switch primary and secondary 824 if (hash == primarySort) { 825 if (shift && secondarySort) { 826 poseView->SetPrimarySort(secondarySort); 827 poseView->SetSecondarySort(primarySort); 828 } else 829 poseView->SetReverseSort(!poseView->ReverseSort()); 830 } else if (shift) { 831 // hit secondary sort column with shift key, disable 832 if (hash == secondarySort) 833 poseView->SetSecondarySort(0); 834 else 835 poseView->SetSecondarySort(hash); 836 } else { 837 poseView->SetPrimarySort(hash); 838 poseView->SetReverseSort(false); 839 } 840 841 if (poseView->PrimarySort() == poseView->SecondarySort()) 842 poseView->SetSecondarySort(0); 843 844 UndrawOutline(); 845 846 poseView->SortPoses(); 847 poseView->Invalidate(); 848 } 849 850 851 bool 852 ColumnDragState::ValueChanged(BPoint) 853 { 854 return true; 855 } 856 857 858 void 859 ColumnDragState::DrawPressNoOutline() 860 { 861 fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle); 862 } 863 864 865 void 866 ColumnDragState::DrawOutline(float pos) 867 { 868 BRect outline(fTitle->Bounds()); 869 outline.OffsetBy(pos, 0); 870 fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline, 871 outline); 872 } 873 874 875 void 876 ColumnDragState::UndrawOutline() 877 { 878 fTitleView->Draw(fTitleView->Bounds(), true, false); 879 } 880 881 882 OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap; 883