1 /* 2 * Copyright 2006-2009, Stephan Aßmus <superstippi@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "PathManipulator.h" 7 8 #include <float.h> 9 #include <stdio.h> 10 11 #include <Catalog.h> 12 #include <Cursor.h> 13 #include <Locale.h> 14 #include <Message.h> 15 #include <MenuItem.h> 16 #include <PopUpMenu.h> 17 #include <Window.h> 18 19 #include "cursors.h" 20 #include "support.h" 21 22 #include "CanvasView.h" 23 24 #include "AddPointCommand.h" 25 #include "ChangePointCommand.h" 26 //#include "CloseCommand.h" 27 #include "InsertPointCommand.h" 28 #include "FlipPointsCommand.h" 29 //#include "NewPathCommand.h" 30 #include "NudgePointsCommand.h" 31 //#include "RemovePathCommand.h" 32 #include "RemovePointsCommand.h" 33 //#include "ReversePathCommand.h" 34 //#include "SelectPathCommand.h" 35 //#include "SelectPointsCommand.h" 36 #include "SplitPointsCommand.h" 37 #include "TransformPointsBox.h" 38 39 40 #undef B_TRANSLATION_CONTEXT 41 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-PathManipulator" 42 #define POINT_EXTEND 3.0 43 #define CONTROL_POINT_EXTEND 2.0 44 #define INSERT_DIST_THRESHOLD 7.0 45 #define MOVE_THRESHOLD 9.0 46 47 48 enum { 49 UNDEFINED, 50 51 NEW_PATH, 52 53 ADD_POINT, 54 INSERT_POINT, 55 MOVE_POINT, 56 MOVE_POINT_IN, 57 MOVE_POINT_OUT, 58 CLOSE_PATH, 59 60 TOGGLE_SHARP, 61 TOGGLE_SHARP_IN, 62 TOGGLE_SHARP_OUT, 63 64 REMOVE_POINT, 65 REMOVE_POINT_IN, 66 REMOVE_POINT_OUT, 67 68 SELECT_POINTS, 69 TRANSFORM_POINTS, 70 TRANSLATE_POINTS, 71 72 SELECT_SUB_PATH, 73 }; 74 75 enum { 76 MSG_TRANSFORM = 'strn', 77 MSG_REMOVE_POINTS = 'srmp', 78 MSG_UPDATE_SHAPE_UI = 'udsi', 79 80 MSG_SPLIT_POINTS = 'splt', 81 MSG_FLIP_POINTS = 'flip', 82 }; 83 84 inline const char* 85 string_for_mode(uint32 mode) 86 { 87 switch (mode) { 88 case UNDEFINED: 89 return "UNDEFINED"; 90 case NEW_PATH: 91 return "NEW_PATH"; 92 case ADD_POINT: 93 return "ADD_POINT"; 94 case INSERT_POINT: 95 return "INSERT_POINT"; 96 case MOVE_POINT: 97 return "MOVE_POINT"; 98 case MOVE_POINT_IN: 99 return "MOVE_POINT_IN"; 100 case MOVE_POINT_OUT: 101 return "MOVE_POINT_OUT"; 102 case CLOSE_PATH: 103 return "CLOSE_PATH"; 104 case TOGGLE_SHARP: 105 return "TOGGLE_SHARP"; 106 case TOGGLE_SHARP_IN: 107 return "TOGGLE_SHARP_IN"; 108 case TOGGLE_SHARP_OUT: 109 return "TOGGLE_SHARP_OUT"; 110 case REMOVE_POINT: 111 return "REMOVE_POINT"; 112 case REMOVE_POINT_IN: 113 return "REMOVE_POINT_IN"; 114 case REMOVE_POINT_OUT: 115 return "REMOVE_POINT_OUT"; 116 case SELECT_POINTS: 117 return "SELECT_POINTS"; 118 case TRANSFORM_POINTS: 119 return "TRANSFORM_POINTS"; 120 case TRANSLATE_POINTS: 121 return "TRANSLATE_POINTS"; 122 case SELECT_SUB_PATH: 123 return "SELECT_SUB_PATH"; 124 } 125 return "<unknown mode>"; 126 } 127 128 class PathManipulator::Selection : protected BList 129 { 130 public: 131 inline Selection(int32 count = 20) 132 : BList(count) {} 133 inline ~Selection() {} 134 135 inline void Add(int32 value) 136 { 137 if (value >= 0) { 138 // keep the list sorted 139 int32 count = CountItems(); 140 int32 index = 0; 141 for (; index < count; index++) { 142 if (IndexAt(index) > value) { 143 break; 144 } 145 } 146 BList::AddItem((void*)value, index); 147 } 148 } 149 150 inline bool Remove(int32 value) 151 { return BList::RemoveItem((void*)value); } 152 153 inline bool Contains(int32 value) const 154 { return BList::HasItem((void*)value); } 155 156 inline bool IsEmpty() const 157 { return BList::IsEmpty(); } 158 159 inline int32 IndexAt(int32 index) const 160 { return (int32)BList::ItemAt(index); } 161 162 inline void MakeEmpty() 163 { BList::MakeEmpty(); } 164 165 inline int32* Items() const 166 { return (int32*)BList::Items(); } 167 168 inline const int32 CountItems() const 169 { return BList::CountItems(); } 170 171 inline Selection& operator =(const Selection& other) 172 { 173 MakeEmpty(); 174 int32 count = other.CountItems(); 175 int32* items = other.Items(); 176 for (int32 i = 0; i < count; i++) { 177 Add(items[i]); 178 } 179 return *this; 180 } 181 182 inline bool operator ==(const Selection& other) 183 { 184 if (other.CountItems() == CountItems()) { 185 int32* items = Items(); 186 int32* otherItems = other.Items(); 187 for (int32 i = 0; i < CountItems(); i++) { 188 if (items[i] != otherItems[i]) 189 return false; 190 items++; 191 otherItems++; 192 } 193 return true; 194 } else 195 return false; 196 } 197 198 inline bool operator !=(const Selection& other) 199 { 200 return !(*this == other); 201 } 202 }; 203 204 205 // constructor 206 PathManipulator::PathManipulator(VectorPath* path) 207 : Manipulator(NULL), 208 fCanvasView(NULL), 209 210 fCommandDown(false), 211 fOptionDown(false), 212 fShiftDown(false), 213 fAltDown(false), 214 215 fClickToClose(false), 216 217 fMode(NEW_PATH), 218 fFallBackMode(SELECT_POINTS), 219 220 fMouseDown(false), 221 222 fPath(path), 223 fCurrentPathPoint(-1), 224 225 fChangePointCommand(NULL), 226 fInsertPointCommand(NULL), 227 fAddPointCommand(NULL), 228 229 fSelection(new Selection()), 230 fOldSelection(new Selection()), 231 fTransformBox(NULL), 232 233 fNudgeOffset(0.0, 0.0), 234 fLastNudgeTime(system_time()), 235 fNudgeCommand(NULL) 236 { 237 fPath->Acquire(); 238 fPath->AddListener(this); 239 fPath->AddObserver(this); 240 } 241 242 // destructor 243 PathManipulator::~PathManipulator() 244 { 245 delete fChangePointCommand; 246 delete fInsertPointCommand; 247 delete fAddPointCommand; 248 249 delete fSelection; 250 delete fOldSelection; 251 delete fTransformBox; 252 253 delete fNudgeCommand; 254 255 fPath->RemoveObserver(this); 256 fPath->RemoveListener(this); 257 fPath->Release(); 258 } 259 260 261 // #pragma mark - 262 263 class StrokePathIterator : public VectorPath::Iterator { 264 public: 265 StrokePathIterator(CanvasView* canvasView, 266 BView* drawingView) 267 : fCanvasView(canvasView), 268 fDrawingView(drawingView) 269 { 270 fDrawingView->SetHighColor(0, 0, 0, 255); 271 fDrawingView->SetDrawingMode(B_OP_OVER); 272 } 273 virtual ~StrokePathIterator() 274 {} 275 276 virtual void MoveTo(BPoint point) 277 { 278 fBlack = true; 279 fSkip = false; 280 fDrawingView->SetHighColor(0, 0, 0, 255); 281 282 fCanvasView->ConvertFromCanvas(&point); 283 fDrawingView->MovePenTo(point); 284 } 285 virtual void LineTo(BPoint point) 286 { 287 fCanvasView->ConvertFromCanvas(&point); 288 if (!fSkip) { 289 if (fBlack) 290 fDrawingView->SetHighColor(255, 255, 255, 255); 291 else 292 fDrawingView->SetHighColor(0, 0, 0, 255); 293 fBlack = !fBlack; 294 295 fDrawingView->StrokeLine(point); 296 } else { 297 fDrawingView->MovePenTo(point); 298 } 299 fSkip = !fSkip; 300 } 301 302 private: 303 CanvasView* fCanvasView; 304 BView* fDrawingView; 305 bool fBlack; 306 bool fSkip; 307 }; 308 309 // Draw 310 void 311 PathManipulator::Draw(BView* into, BRect updateRect) 312 { 313 // draw the Bezier curve, but only if not "editing", 314 // the path is actually on top all other modifiers 315 // TODO: make this customizable in the GUI 316 317 #if __HAIKU__ 318 uint32 flags = into->Flags(); 319 into->SetFlags(flags | B_SUBPIXEL_PRECISE); 320 #endif // __HAIKU__ 321 322 StrokePathIterator iterator(fCanvasView, into); 323 fPath->Iterate(&iterator, fCanvasView->ZoomLevel()); 324 325 #if __HAIKU__ 326 into->SetFlags(flags); 327 #endif // __HAIKU__ 328 329 into->SetLowColor(0, 0, 0, 255); 330 BPoint point; 331 BPoint pointIn; 332 BPoint pointOut; 333 rgb_color focusColor = (rgb_color){ 255, 0, 0, 255 }; 334 rgb_color highlightColor = (rgb_color){ 60, 60, 255, 255 }; 335 for (int32 i = 0; fPath->GetPointsAt(i, point, pointIn, pointOut); i++) { 336 bool highlight = fCurrentPathPoint == i; 337 bool selected = fSelection->Contains(i); 338 rgb_color normal = selected ? focusColor : (rgb_color){ 0, 0, 0, 255 }; 339 into->SetLowColor(normal); 340 into->SetHighColor(255, 255, 255, 255); 341 // convert to view coordinate space 342 fCanvasView->ConvertFromCanvas(&point); 343 fCanvasView->ConvertFromCanvas(&pointIn); 344 fCanvasView->ConvertFromCanvas(&pointOut); 345 // connect the points belonging to one control point 346 into->SetDrawingMode(B_OP_INVERT); 347 into->StrokeLine(point, pointIn); 348 into->StrokeLine(point, pointOut); 349 // draw main control point 350 if (highlight && (fMode == MOVE_POINT || 351 fMode == TOGGLE_SHARP || 352 fMode == REMOVE_POINT || 353 fMode == SELECT_POINTS || 354 fMode == CLOSE_PATH)) { 355 356 into->SetLowColor(highlightColor); 357 } 358 359 into->SetDrawingMode(B_OP_COPY); 360 BRect r(point, point); 361 r.InsetBy(-POINT_EXTEND, -POINT_EXTEND); 362 into->StrokeRect(r, B_SOLID_LOW); 363 r.InsetBy(1.0, 1.0); 364 into->FillRect(r, B_SOLID_HIGH); 365 // draw in control point 366 if (highlight && (fMode == MOVE_POINT_IN || 367 fMode == TOGGLE_SHARP_IN || 368 fMode == REMOVE_POINT_IN || 369 fMode == SELECT_POINTS)) 370 into->SetLowColor(highlightColor); 371 else 372 into->SetLowColor(normal); 373 if (selected) { 374 into->SetHighColor(220, 220, 220, 255); 375 } else { 376 into->SetHighColor(170, 170, 170, 255); 377 } 378 if (pointIn != point) { 379 r.Set(pointIn.x - CONTROL_POINT_EXTEND, pointIn.y - CONTROL_POINT_EXTEND, 380 pointIn.x + CONTROL_POINT_EXTEND, pointIn.y + CONTROL_POINT_EXTEND); 381 into->StrokeRect(r, B_SOLID_LOW); 382 r.InsetBy(1.0, 1.0); 383 into->FillRect(r, B_SOLID_HIGH); 384 } 385 // draw out control point 386 if (highlight && (fMode == MOVE_POINT_OUT || 387 fMode == TOGGLE_SHARP_OUT || 388 fMode == REMOVE_POINT_OUT || 389 fMode == SELECT_POINTS)) 390 into->SetLowColor(highlightColor); 391 else 392 into->SetLowColor(normal); 393 if (pointOut != point) { 394 r.Set(pointOut.x - CONTROL_POINT_EXTEND, pointOut.y - CONTROL_POINT_EXTEND, 395 pointOut.x + CONTROL_POINT_EXTEND, pointOut.y + CONTROL_POINT_EXTEND); 396 into->StrokeRect(r, B_SOLID_LOW); 397 r.InsetBy(1.0, 1.0); 398 into->FillRect(r, B_SOLID_HIGH); 399 } 400 } 401 402 if (fTransformBox) { 403 fTransformBox->Draw(into, updateRect); 404 } 405 } 406 407 // #pragma mark - 408 409 // MouseDown 410 bool 411 PathManipulator::MouseDown(BPoint where) 412 { 413 fMouseDown = true; 414 415 if (fMode == TRANSFORM_POINTS) { 416 if (fTransformBox) { 417 fTransformBox->MouseDown(where); 418 419 // if (!fTransformBox->IsRotating()) 420 // fCanvasView->SetAutoScrolling(true); 421 } 422 return true; 423 } 424 425 if (fMode == MOVE_POINT && 426 fSelection->CountItems() > 1 && 427 fSelection->Contains(fCurrentPathPoint)) { 428 fMode = TRANSLATE_POINTS; 429 } 430 431 // apply the canvas view mouse filter depending on current mode 432 if (fMode == ADD_POINT || fMode == TRANSLATE_POINTS) 433 fCanvasView->FilterMouse(&where); 434 435 BPoint canvasWhere = where; 436 fCanvasView->ConvertToCanvas(&canvasWhere); 437 438 // maybe we're changing some point, so we construct the 439 // "ChangePointCommand" here so that the point is remembered 440 // in its current state 441 // apply the canvas view mouse filter depending on current mode 442 delete fChangePointCommand; 443 fChangePointCommand = NULL; 444 switch (fMode) { 445 case TOGGLE_SHARP: 446 case TOGGLE_SHARP_IN: 447 case TOGGLE_SHARP_OUT: 448 case MOVE_POINT: 449 case MOVE_POINT_IN: 450 case MOVE_POINT_OUT: 451 case REMOVE_POINT_IN: 452 case REMOVE_POINT_OUT: 453 fChangePointCommand = new ChangePointCommand(fPath, 454 fCurrentPathPoint, 455 fSelection->Items(), 456 fSelection->CountItems()); 457 _Select(fCurrentPathPoint, fShiftDown); 458 break; 459 } 460 461 // at this point we init doing something 462 switch (fMode) { 463 case ADD_POINT: 464 _AddPoint(canvasWhere); 465 break; 466 case INSERT_POINT: 467 _InsertPoint(canvasWhere, fCurrentPathPoint); 468 break; 469 470 case TOGGLE_SHARP: 471 _SetSharp(fCurrentPathPoint); 472 // continue by dragging out the _connected_ in/out points 473 break; 474 case TOGGLE_SHARP_IN: 475 _SetInOutConnected(fCurrentPathPoint, false); 476 // continue by moving the "in" point 477 _SetMode(MOVE_POINT_IN); 478 break; 479 case TOGGLE_SHARP_OUT: 480 _SetInOutConnected(fCurrentPathPoint, false); 481 // continue by moving the "out" point 482 _SetMode(MOVE_POINT_OUT); 483 break; 484 485 case MOVE_POINT: 486 case MOVE_POINT_IN: 487 case MOVE_POINT_OUT: 488 // the right thing happens since "fCurrentPathPoint" 489 // points to the correct index 490 break; 491 492 case CLOSE_PATH: 493 // SetClosed(true, true); 494 break; 495 496 case REMOVE_POINT: 497 if (fPath->CountPoints() == 1) { 498 // fCanvasView->Perform(new RemovePathCommand(this, fPath)); 499 } else { 500 fCanvasView->Perform(new RemovePointsCommand(fPath, 501 fCurrentPathPoint, 502 fSelection->Items(), 503 fSelection->CountItems())); 504 _RemovePoint(fCurrentPathPoint); 505 } 506 break; 507 case REMOVE_POINT_IN: 508 _RemovePointIn(fCurrentPathPoint); 509 break; 510 case REMOVE_POINT_OUT: 511 _RemovePointOut(fCurrentPathPoint); 512 break; 513 514 case SELECT_POINTS: { 515 // TODO: this works so that you can deselect all points 516 // when clicking outside the path even if pressing shift 517 // in case the path is open... a better way would be 518 // to deselect all on mouse up, if the mouse has not moved 519 bool appendSelection; 520 if (fPath->IsClosed()) 521 appendSelection = fShiftDown; 522 else 523 appendSelection = fShiftDown && fCurrentPathPoint >= 0; 524 525 if (!appendSelection) { 526 fSelection->MakeEmpty(); 527 _UpdateSelection(); 528 } 529 *fOldSelection = *fSelection; 530 if (fCurrentPathPoint >= 0) { 531 _Select(fCurrentPathPoint, appendSelection); 532 } 533 fCanvasView->BeginRectTracking(BRect(where, where), 534 B_TRACK_RECT_CORNER); 535 break; 536 } 537 } 538 539 fTrackingStart = canvasWhere; 540 // remember the subpixel position 541 // so that MouseMoved() will work even before 542 // the integer position becomes different 543 fLastCanvasPos = where; 544 fCanvasView->ConvertToCanvas(&fLastCanvasPos); 545 546 // the reason to exclude the select mode 547 // is that the BView rect tracking does not 548 // scroll the rect starting point along with us 549 // (since we're doing no real scrolling) 550 // if (fMode != SELECT_POINTS) 551 // fCanvasView->SetAutoScrolling(true); 552 553 UpdateCursor(); 554 555 return true; 556 } 557 558 // MouseMoved 559 void 560 PathManipulator::MouseMoved(BPoint where) 561 { 562 fCanvasView->FilterMouse(&where); 563 // NOTE: only filter mouse coords in mouse moved, no other 564 // mouse function 565 BPoint canvasWhere = where; 566 fCanvasView->ConvertToCanvas(&canvasWhere); 567 568 // since the tablet is generating mouse moved messages 569 // even if only the pressure changes (and not the actual mouse position) 570 // we insert this additional check to prevent too much calculation 571 if (fLastCanvasPos == canvasWhere) 572 return; 573 574 fLastCanvasPos = canvasWhere; 575 576 if (fMode == TRANSFORM_POINTS) { 577 if (fTransformBox) { 578 fTransformBox->MouseMoved(where); 579 } 580 return; 581 } 582 583 if (fMode == CLOSE_PATH) { 584 // continue by moving the point 585 _SetMode(MOVE_POINT); 586 delete fChangePointCommand; 587 fChangePointCommand = new ChangePointCommand(fPath, 588 fCurrentPathPoint, 589 fSelection->Items(), 590 fSelection->CountItems()); 591 } 592 593 // if (!fPrecise) { 594 // float offset = fmod(fOutlineWidth, 2.0) / 2.0; 595 // canvasWhere.point += BPoint(offset, offset); 596 // } 597 598 switch (fMode) { 599 case ADD_POINT: 600 case INSERT_POINT: 601 case TOGGLE_SHARP: 602 // drag the "out" control point, mirror the "in" control point 603 fPath->SetPointOut(fCurrentPathPoint, canvasWhere, true); 604 break; 605 case MOVE_POINT: 606 // drag all three control points at once 607 fPath->SetPoint(fCurrentPathPoint, canvasWhere); 608 break; 609 case MOVE_POINT_IN: 610 // drag in control point 611 fPath->SetPointIn(fCurrentPathPoint, canvasWhere); 612 break; 613 case MOVE_POINT_OUT: 614 // drag out control point 615 fPath->SetPointOut(fCurrentPathPoint, canvasWhere); 616 break; 617 618 case SELECT_POINTS: { 619 // change the selection 620 BRect r; 621 r.left = min_c(fTrackingStart.x, canvasWhere.x); 622 r.top = min_c(fTrackingStart.y, canvasWhere.y); 623 r.right = max_c(fTrackingStart.x, canvasWhere.x); 624 r.bottom = max_c(fTrackingStart.y, canvasWhere.y); 625 _Select(r); 626 break; 627 } 628 629 case TRANSLATE_POINTS: { 630 BPoint offset = canvasWhere - fTrackingStart; 631 _Nudge(offset); 632 fTrackingStart = canvasWhere; 633 break; 634 } 635 } 636 } 637 638 // MouseUp 639 Command* 640 PathManipulator::MouseUp() 641 { 642 // prevent carrying out actions more than once by only 643 // doing it if "fMouseDown" is true at the point of 644 // entering this function 645 if (!fMouseDown) 646 return NULL; 647 fMouseDown = false; 648 649 if (fMode == TRANSFORM_POINTS) { 650 if (fTransformBox) { 651 return fTransformBox->MouseUp(); 652 } 653 return NULL; 654 } 655 656 Command* command = NULL; 657 658 switch (fMode) { 659 660 case ADD_POINT: 661 command = fAddPointCommand; 662 fAddPointCommand = NULL; 663 _SetMode(MOVE_POINT_OUT); 664 break; 665 666 case INSERT_POINT: 667 command = fInsertPointCommand; 668 fInsertPointCommand = NULL; 669 break; 670 671 case SELECT_POINTS: 672 if (*fSelection != *fOldSelection) { 673 // command = new SelectPointsCommand(this, fPath, 674 // fOldSelection->Items(), 675 // fOldSelection->CountItems(), 676 // fSelection->Items(), 677 // fSelection->CountItems())); 678 } 679 fCanvasView->EndRectTracking(); 680 break; 681 682 case TOGGLE_SHARP: 683 case TOGGLE_SHARP_IN: 684 case TOGGLE_SHARP_OUT: 685 case MOVE_POINT: 686 case MOVE_POINT_IN: 687 case MOVE_POINT_OUT: 688 case REMOVE_POINT_IN: 689 case REMOVE_POINT_OUT: 690 command = fChangePointCommand; 691 fChangePointCommand = NULL; 692 break; 693 694 case TRANSLATE_POINTS: 695 if (!fNudgeCommand) { 696 // select just the point that was clicked 697 *fOldSelection = *fSelection; 698 if (fCurrentPathPoint >= 0) { 699 _Select(fCurrentPathPoint, fShiftDown); 700 } 701 if (*fSelection != *fOldSelection) { 702 // command = new SelectPointsCommand(this, fPath, 703 // fOldSelection->Items(), 704 // fOldSelection->CountItems(), 705 // fSelection->Items(), 706 // fSelection->CountItems())); 707 } 708 } else { 709 command = _FinishNudging(); 710 } 711 break; 712 } 713 714 return command; 715 } 716 717 // MouseOver 718 bool 719 PathManipulator::MouseOver(BPoint where) 720 { 721 if (fMode == TRANSFORM_POINTS) { 722 if (fTransformBox) { 723 return fTransformBox->MouseOver(where); 724 } 725 return false; 726 } 727 728 BPoint canvasWhere = where; 729 fCanvasView->ConvertToCanvas(&canvasWhere); 730 731 // since the tablet is generating mouse moved messages 732 // even if only the pressure changes (and not the actual mouse position) 733 // we insert this additional check to prevent too much calculation 734 if (fMouseDown && fLastCanvasPos == canvasWhere) 735 return false; 736 737 fLastCanvasPos = canvasWhere; 738 739 // hit testing 740 // (use a subpixel mouse pos) 741 fCanvasView->ConvertToCanvas(&where); 742 _SetModeForMousePos(where); 743 744 // TODO: always true? 745 return true; 746 } 747 748 // DoubleClicked 749 bool 750 PathManipulator::DoubleClicked(BPoint where) 751 { 752 return false; 753 } 754 755 // ShowContextMenu 756 bool 757 PathManipulator::ShowContextMenu(BPoint where) 758 { 759 // Change the selection to the current point if it isn't currently 760 // selected. This could will only be chosen if the user right-clicked 761 // a path point directly. 762 if (fCurrentPathPoint >= 0 && !fSelection->Contains(fCurrentPathPoint)) { 763 fSelection->MakeEmpty(); 764 _UpdateSelection(); 765 *fOldSelection = *fSelection; 766 _Select(fCurrentPathPoint, false); 767 } 768 769 BPopUpMenu* menu = new BPopUpMenu("context menu", false, false); 770 BMessage* message; 771 BMenuItem* item; 772 773 bool hasSelection = fSelection->CountItems() > 0; 774 775 if (fCurrentPathPoint < 0) { 776 message = new BMessage(B_SELECT_ALL); 777 item = new BMenuItem(B_TRANSLATE("Select All"), message, 'A'); 778 menu->AddItem(item); 779 780 menu->AddSeparatorItem(); 781 } 782 783 message = new BMessage(MSG_TRANSFORM); 784 item = new BMenuItem(B_TRANSLATE("Transform"), message, 'T'); 785 item->SetEnabled(hasSelection); 786 menu->AddItem(item); 787 788 message = new BMessage(MSG_SPLIT_POINTS); 789 item = new BMenuItem(B_TRANSLATE("Split"), message); 790 item->SetEnabled(hasSelection); 791 menu->AddItem(item); 792 793 message = new BMessage(MSG_FLIP_POINTS); 794 item = new BMenuItem(B_TRANSLATE("Flip"), message); 795 item->SetEnabled(hasSelection); 796 menu->AddItem(item); 797 798 message = new BMessage(MSG_REMOVE_POINTS); 799 item = new BMenuItem(B_TRANSLATE("Remove"), message); 800 item->SetEnabled(hasSelection); 801 menu->AddItem(item); 802 803 // go 804 menu->SetTargetForItems(fCanvasView); 805 menu->SetAsyncAutoDestruct(true); 806 menu->SetFont(be_plain_font); 807 where = fCanvasView->ConvertToScreen(where); 808 BRect mouseRect(where, where); 809 mouseRect.InsetBy(-10.0, -10.0); 810 where += BPoint(5.0, 5.0); 811 menu->Go(where, true, false, mouseRect, true); 812 813 return true; 814 } 815 816 // #pragma mark - 817 818 // Bounds 819 BRect 820 PathManipulator::Bounds() 821 { 822 BRect r = _ControlPointRect(); 823 fCanvasView->ConvertFromCanvas(&r); 824 return r; 825 } 826 827 // TrackingBounds 828 BRect 829 PathManipulator::TrackingBounds(BView* withinView) 830 { 831 return withinView->Bounds(); 832 } 833 834 // #pragma mark - 835 836 // MessageReceived 837 bool 838 PathManipulator::MessageReceived(BMessage* message, Command** _command) 839 { 840 bool result = true; 841 switch (message->what) { 842 case MSG_TRANSFORM: 843 if (!fSelection->IsEmpty()) 844 _SetMode(TRANSFORM_POINTS); 845 break; 846 case MSG_REMOVE_POINTS: 847 *_command = _Delete(); 848 break; 849 case MSG_SPLIT_POINTS: 850 *_command = new SplitPointsCommand(fPath, 851 fSelection->Items(), 852 fSelection->CountItems()); 853 break; 854 case MSG_FLIP_POINTS: 855 *_command = new FlipPointsCommand(fPath, 856 fSelection->Items(), 857 fSelection->CountItems()); 858 break; 859 case B_SELECT_ALL: { 860 *fOldSelection = *fSelection; 861 fSelection->MakeEmpty(); 862 int32 count = fPath->CountPoints(); 863 for (int32 i = 0; i < count; i++) 864 fSelection->Add(i); 865 if (*fOldSelection != *fSelection) { 866 // *_command = new SelectPointsCommand(this, fPath, 867 // fOldSelection->Items(), 868 // fOldSelection->CountItems(), 869 // fSelection->Items(), 870 // fSelection->CountItems())); 871 count = fSelection->CountItems(); 872 int32 indices[count]; 873 memcpy(indices, fSelection->Items(), count * sizeof(int32)); 874 _Select(indices, count); 875 } 876 break; 877 } 878 default: 879 result = false; 880 break; 881 } 882 return result; 883 } 884 885 886 // ModifiersChanged 887 void 888 PathManipulator::ModifiersChanged(uint32 modifiers) 889 { 890 fCommandDown = modifiers & B_COMMAND_KEY; 891 fOptionDown = modifiers & B_CONTROL_KEY; 892 fShiftDown = modifiers & B_SHIFT_KEY; 893 fAltDown = modifiers & B_OPTION_KEY; 894 895 if (fTransformBox) { 896 fTransformBox->ModifiersChanged(modifiers); 897 return; 898 } 899 // reevaluate mode 900 if (!fMouseDown) 901 _SetModeForMousePos(fLastCanvasPos); 902 } 903 904 // HandleKeyDown 905 bool 906 PathManipulator::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command) 907 { 908 bool result = true; 909 910 float nudgeDist = 1.0; 911 if (modifiers & B_SHIFT_KEY) 912 nudgeDist /= fCanvasView->ZoomLevel(); 913 914 switch (key) { 915 // commit 916 case B_RETURN: 917 if (fTransformBox) { 918 _SetTransformBox(NULL); 919 }// else 920 // _Perform(); 921 break; 922 // cancel 923 case B_ESCAPE: 924 if (fTransformBox) { 925 fTransformBox->Cancel(); 926 _SetTransformBox(NULL); 927 } else if (fFallBackMode == NEW_PATH) { 928 fFallBackMode = SELECT_POINTS; 929 _SetTransformBox(NULL); 930 }// else 931 // _Cancel(); 932 break; 933 case 't': 934 case 'T': 935 if (!fSelection->IsEmpty()) 936 _SetMode(TRANSFORM_POINTS); 937 else 938 result = false; 939 break; 940 // nudging 941 case B_UP_ARROW: 942 _Nudge(BPoint(0.0, -nudgeDist)); 943 break; 944 case B_DOWN_ARROW: 945 _Nudge(BPoint(0.0, nudgeDist)); 946 break; 947 case B_LEFT_ARROW: 948 _Nudge(BPoint(-nudgeDist, 0.0)); 949 break; 950 case B_RIGHT_ARROW: 951 _Nudge(BPoint(nudgeDist, 0.0)); 952 break; 953 954 case B_DELETE: 955 if (!fSelection->IsEmpty()) 956 *_command = _Delete(); 957 else 958 result = false; 959 break; 960 961 default: 962 result = false; 963 } 964 return result; 965 } 966 967 // HandleKeyUp 968 bool 969 PathManipulator::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command) 970 { 971 bool handled = true; 972 switch (key) { 973 // nudging 974 case B_UP_ARROW: 975 case B_DOWN_ARROW: 976 case B_LEFT_ARROW: 977 case B_RIGHT_ARROW: 978 *_command = _FinishNudging(); 979 break; 980 default: 981 handled = false; 982 break; 983 } 984 return handled; 985 } 986 987 // UpdateCursor 988 bool 989 PathManipulator::UpdateCursor() 990 { 991 if (fTransformBox) 992 return fTransformBox->UpdateCursor(); 993 994 const uchar* cursorData; 995 switch (fMode) { 996 case ADD_POINT: 997 cursorData = kPathAddCursor; 998 break; 999 case INSERT_POINT: 1000 cursorData = kPathInsertCursor; 1001 break; 1002 case MOVE_POINT: 1003 case MOVE_POINT_IN: 1004 case MOVE_POINT_OUT: 1005 case TRANSLATE_POINTS: 1006 cursorData = kPathMoveCursor; 1007 break; 1008 case CLOSE_PATH: 1009 cursorData = kPathCloseCursor; 1010 break; 1011 case TOGGLE_SHARP: 1012 case TOGGLE_SHARP_IN: 1013 case TOGGLE_SHARP_OUT: 1014 cursorData = kPathSharpCursor; 1015 break; 1016 case REMOVE_POINT: 1017 case REMOVE_POINT_IN: 1018 case REMOVE_POINT_OUT: 1019 cursorData = kPathRemoveCursor; 1020 break; 1021 case SELECT_POINTS: 1022 cursorData = kPathSelectCursor; 1023 break; 1024 1025 case SELECT_SUB_PATH: 1026 cursorData = B_HAND_CURSOR; 1027 break; 1028 1029 case UNDEFINED: 1030 default: 1031 cursorData = kStopCursor; 1032 break; 1033 } 1034 BCursor cursor(cursorData); 1035 fCanvasView->SetViewCursor(&cursor, true); 1036 fCanvasView->Sync(); 1037 1038 return true; 1039 } 1040 1041 // AttachedToView 1042 void 1043 PathManipulator::AttachedToView(BView* view) 1044 { 1045 fCanvasView = dynamic_cast<CanvasView*>(view); 1046 } 1047 1048 // DetachedFromView 1049 void 1050 PathManipulator::DetachedFromView(BView* view) 1051 { 1052 fCanvasView = NULL; 1053 } 1054 1055 // #pragma mark - 1056 1057 // ObjectChanged 1058 void 1059 PathManipulator::ObjectChanged(const Observable* object) 1060 { 1061 // TODO: refine VectorPath listener interface and 1062 // implement more efficiently 1063 BRect currentBounds = _ControlPointRect(); 1064 _InvalidateCanvas(currentBounds | fPreviousBounds); 1065 fPreviousBounds = currentBounds; 1066 1067 // reevaluate mode 1068 if (!fMouseDown && !fTransformBox) 1069 _SetModeForMousePos(fLastCanvasPos); 1070 } 1071 1072 // #pragma mark - 1073 1074 // PointAdded 1075 void 1076 PathManipulator::PointAdded(int32 index) 1077 { 1078 ObjectChanged(fPath); 1079 } 1080 1081 // PointRemoved 1082 void 1083 PathManipulator::PointRemoved(int32 index) 1084 { 1085 fSelection->Remove(index); 1086 ObjectChanged(fPath); 1087 } 1088 1089 // PointChanged 1090 void 1091 PathManipulator::PointChanged(int32 index) 1092 { 1093 ObjectChanged(fPath); 1094 } 1095 1096 // PathChanged 1097 void 1098 PathManipulator::PathChanged() 1099 { 1100 ObjectChanged(fPath); 1101 } 1102 1103 // PathClosedChanged 1104 void 1105 PathManipulator::PathClosedChanged() 1106 { 1107 ObjectChanged(fPath); 1108 } 1109 1110 // PathReversed 1111 void 1112 PathManipulator::PathReversed() 1113 { 1114 // reverse selection along with path 1115 int32 count = fSelection->CountItems(); 1116 int32 pointCount = fPath->CountPoints(); 1117 if (count > 0) { 1118 Selection temp; 1119 for (int32 i = 0; i < count; i++) { 1120 temp.Add((pointCount - 1) - fSelection->IndexAt(i)); 1121 } 1122 *fSelection = temp; 1123 } 1124 1125 ObjectChanged(fPath); 1126 } 1127 1128 // #pragma mark - 1129 1130 // ControlFlags 1131 uint32 1132 PathManipulator::ControlFlags() const 1133 { 1134 uint32 flags = 0; 1135 1136 // flags |= SHAPE_UI_FLAGS_CAN_REVERSE_PATH; 1137 // 1138 // if (!fSelection->IsEmpty()) 1139 // flags |= SHAPE_UI_FLAGS_HAS_SELECTION; 1140 // if (fPath->CountPoints() > 1) 1141 // flags |= SHAPE_UI_FLAGS_CAN_CLOSE_PATH; 1142 // if (fPath->IsClosed()) 1143 // flags |= SHAPE_UI_FLAGS_PATH_IS_CLOSED; 1144 // if (fTransformBox) 1145 // flags |= SHAPE_UI_FLAGS_IS_TRANSFORMING; 1146 1147 return flags; 1148 } 1149 1150 // ReversePath 1151 void 1152 PathManipulator::ReversePath() 1153 { 1154 int32 count = fSelection->CountItems(); 1155 int32 pointCount = fPath->CountPoints(); 1156 if (count > 0) { 1157 Selection temp; 1158 for (int32 i = 0; i < count; i++) { 1159 temp.Add((pointCount - 1) - fSelection->IndexAt(i)); 1160 } 1161 *fSelection = temp; 1162 } 1163 fPath->Reverse(); 1164 } 1165 1166 // #pragma mark - 1167 1168 // _SetMode 1169 void 1170 PathManipulator::_SetMode(uint32 mode) 1171 { 1172 if (fMode != mode) { 1173 //printf("switching mode: %s -> %s\n", string_for_mode(fMode), string_for_mode(mode)); 1174 fMode = mode; 1175 1176 if (fMode == TRANSFORM_POINTS) { 1177 _SetTransformBox(new TransformPointsBox(fCanvasView, 1178 this, 1179 fPath, 1180 fSelection->Items(), 1181 fSelection->CountItems())); 1182 // fCanvasView->Perform(new EnterTransformPointsCommand(this, 1183 // fSelection->Items(), 1184 // fSelection->CountItems())); 1185 } else { 1186 if (fTransformBox) 1187 _SetTransformBox(NULL); 1188 } 1189 1190 if (BWindow* window = fCanvasView->Window()) { 1191 window->PostMessage(MSG_UPDATE_SHAPE_UI); 1192 } 1193 UpdateCursor(); 1194 } 1195 } 1196 1197 1198 // _SetTransformBox 1199 void 1200 PathManipulator::_SetTransformBox(TransformPointsBox* transformBox) 1201 { 1202 if (fTransformBox == transformBox) 1203 return; 1204 1205 BRect dirty(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); 1206 if (fTransformBox) { 1207 // get rid of transform box display 1208 dirty = fTransformBox->Bounds(); 1209 delete fTransformBox; 1210 } 1211 1212 fTransformBox = transformBox; 1213 1214 // TODO: this is weird, fMode should only be set in _SetMode, not 1215 // here as well, also this method could be called this way 1216 // _SetModeForMousePos -> _SetMode -> _SetTransformBox 1217 // and then below it does _SetModeForMousePos again... 1218 if (fTransformBox) { 1219 fTransformBox->MouseMoved(fLastCanvasPos); 1220 if (fMode != TRANSFORM_POINTS) { 1221 fMode = TRANSFORM_POINTS; 1222 } 1223 dirty = dirty | fTransformBox->Bounds(); 1224 } else { 1225 if (fMode == TRANSFORM_POINTS) { 1226 _SetModeForMousePos(fLastCanvasPos); 1227 } 1228 } 1229 1230 if (dirty.IsValid()) { 1231 dirty.InsetBy(-8, -8); 1232 fCanvasView->Invalidate(dirty); 1233 } 1234 } 1235 1236 // _AddPoint 1237 void 1238 PathManipulator::_AddPoint(BPoint where) 1239 { 1240 if (fPath->AddPoint(where)) { 1241 fCurrentPathPoint = fPath->CountPoints() - 1; 1242 1243 delete fAddPointCommand; 1244 fAddPointCommand = new AddPointCommand(fPath, fCurrentPathPoint, 1245 fSelection->Items(), 1246 fSelection->CountItems()); 1247 1248 _Select(fCurrentPathPoint, fShiftDown); 1249 } 1250 } 1251 1252 // scale_point 1253 BPoint 1254 scale_point(BPoint a, BPoint b, float scale) 1255 { 1256 return BPoint(a.x + (b.x - a.x) * scale, 1257 a.y + (b.y - a.y) * scale); 1258 } 1259 1260 // _InsertPoint 1261 void 1262 PathManipulator::_InsertPoint(BPoint where, int32 index) 1263 { 1264 double scale; 1265 1266 BPoint point; 1267 BPoint pointIn; 1268 BPoint pointOut; 1269 1270 BPoint previous; 1271 BPoint previousOut; 1272 BPoint next; 1273 BPoint nextIn; 1274 1275 if (fPath->FindBezierScale(index - 1, where, &scale) 1276 && scale >= 0.0 && scale <= 1.0 1277 && fPath->GetPoint(index - 1, scale, point)) { 1278 1279 fPath->GetPointAt(index - 1, previous); 1280 fPath->GetPointOutAt(index - 1, previousOut); 1281 fPath->GetPointAt(index, next); 1282 fPath->GetPointInAt(index, nextIn); 1283 1284 where = scale_point(previousOut, nextIn, scale); 1285 1286 previousOut = scale_point(previous, previousOut, scale); 1287 nextIn = scale_point(next, nextIn, 1 - scale); 1288 pointIn = scale_point(previousOut, where, scale); 1289 pointOut = scale_point(nextIn, where, 1 - scale); 1290 1291 if (fPath->AddPoint(point, index)) { 1292 1293 fPath->SetPointIn(index, pointIn); 1294 fPath->SetPointOut(index, pointOut); 1295 1296 delete fInsertPointCommand; 1297 fInsertPointCommand = new InsertPointCommand(fPath, index, 1298 fSelection->Items(), 1299 fSelection->CountItems()); 1300 1301 fPath->SetPointOut(index - 1, previousOut); 1302 fPath->SetPointIn(index + 1, nextIn); 1303 1304 fCurrentPathPoint = index; 1305 _ShiftSelection(fCurrentPathPoint, 1); 1306 _Select(fCurrentPathPoint, fShiftDown); 1307 } 1308 } 1309 } 1310 1311 // _SetInOutConnected 1312 void 1313 PathManipulator::_SetInOutConnected(int32 index, bool connected) 1314 { 1315 fPath->SetInOutConnected(index, connected); 1316 } 1317 1318 // _SetSharp 1319 void 1320 PathManipulator::_SetSharp(int32 index) 1321 { 1322 BPoint p; 1323 fPath->GetPointAt(index, p); 1324 fPath->SetPoint(index, p, p, p, true); 1325 } 1326 1327 // _RemoveSelection 1328 void 1329 PathManipulator::_RemoveSelection() 1330 { 1331 // NOTE: copy selection since removing points will 1332 // trigger notifications, and that will influence the 1333 // selection 1334 Selection selection = *fSelection; 1335 int32 count = selection.CountItems(); 1336 for (int32 i = 0; i < count; i++) { 1337 if (!fPath->RemovePoint(selection.IndexAt(i) - i)) 1338 break; 1339 } 1340 1341 fPath->SetClosed(fPath->IsClosed() && fPath->CountPoints() > 1); 1342 1343 fSelection->MakeEmpty(); 1344 } 1345 1346 1347 // _RemovePoint 1348 void 1349 PathManipulator::_RemovePoint(int32 index) 1350 { 1351 if (fPath->RemovePoint(index)) { 1352 _Deselect(index); 1353 _ShiftSelection(index + 1, -1); 1354 } 1355 } 1356 1357 // _RemovePointIn 1358 void 1359 PathManipulator::_RemovePointIn(int32 index) 1360 { 1361 BPoint p; 1362 if (fPath->GetPointAt(index, p)) { 1363 fPath->SetPointIn(index, p); 1364 fPath->SetInOutConnected(index, false); 1365 } 1366 } 1367 1368 // _RemovePointOut 1369 void 1370 PathManipulator::_RemovePointOut(int32 index) 1371 { 1372 BPoint p; 1373 if (fPath->GetPointAt(index, p)) { 1374 fPath->SetPointOut(index, p); 1375 fPath->SetInOutConnected(index, false); 1376 } 1377 } 1378 1379 // _Delete 1380 Command* 1381 PathManipulator::_Delete() 1382 { 1383 Command* command = NULL; 1384 if (!fMouseDown) { 1385 // make sure we apply an on-going transformation before we proceed 1386 if (fTransformBox) { 1387 _SetTransformBox(NULL); 1388 } 1389 1390 if (fSelection->CountItems() == fPath->CountPoints()) { 1391 // command = new RemovePathCommand(fPath); 1392 } else { 1393 command = new RemovePointsCommand(fPath, 1394 fSelection->Items(), 1395 fSelection->CountItems()); 1396 _RemoveSelection(); 1397 } 1398 1399 _SetModeForMousePos(fLastCanvasPos); 1400 } 1401 1402 return command; 1403 } 1404 1405 // #pragma mark - 1406 1407 // _Select 1408 void 1409 PathManipulator::_Select(BRect r) 1410 { 1411 BPoint p; 1412 BPoint pIn; 1413 BPoint pOut; 1414 int32 count = fPath->CountPoints(); 1415 Selection temp; 1416 for (int32 i = 0; i < count && fPath->GetPointsAt(i, p, pIn, pOut); i++) { 1417 if (r.Contains(p) || r.Contains(pIn) || r.Contains(pOut)) { 1418 temp.Add(i); 1419 } 1420 } 1421 // merge old and new selection 1422 count = fOldSelection->CountItems(); 1423 for (int32 i = 0; i < count; i++) { 1424 int32 index = fOldSelection->IndexAt(i); 1425 if (temp.Contains(index)) 1426 temp.Remove(index); 1427 else 1428 temp.Add(index); 1429 } 1430 if (temp != *fSelection) { 1431 *fSelection = temp; 1432 _UpdateSelection(); 1433 } 1434 } 1435 1436 // _Select 1437 void 1438 PathManipulator::_Select(int32 index, bool extend) 1439 { 1440 if (!extend) 1441 fSelection->MakeEmpty(); 1442 if (fSelection->Contains(index)) 1443 fSelection->Remove(index); 1444 else 1445 fSelection->Add(index); 1446 // TODO: this can lead to unnecessary invalidation (maybe need to investigate) 1447 _UpdateSelection(); 1448 } 1449 1450 // _Select 1451 void 1452 PathManipulator::_Select(const int32* indices, int32 count, bool extend) 1453 { 1454 if (extend) { 1455 for (int32 i = 0; i < count; i++) { 1456 if (!fSelection->Contains(indices[i])) 1457 fSelection->Add(indices[i]); 1458 } 1459 } else { 1460 fSelection->MakeEmpty(); 1461 for (int32 i = 0; i < count; i++) { 1462 fSelection->Add(indices[i]); 1463 } 1464 } 1465 _UpdateSelection(); 1466 } 1467 1468 // _Deselect 1469 void 1470 PathManipulator::_Deselect(int32 index) 1471 { 1472 if (fSelection->Contains(index)) { 1473 fSelection->Remove(index); 1474 _UpdateSelection(); 1475 } 1476 } 1477 1478 // _ShiftSelection 1479 void 1480 PathManipulator::_ShiftSelection(int32 startIndex, int32 direction) 1481 { 1482 int32 count = fSelection->CountItems(); 1483 if (count > 0) { 1484 int32* selection = fSelection->Items(); 1485 for (int32 i = 0; i < count; i++) { 1486 if (selection[i] >= startIndex) { 1487 selection[i] += direction; 1488 } 1489 } 1490 } 1491 _UpdateSelection(); 1492 } 1493 1494 // _IsSelected 1495 bool 1496 PathManipulator::_IsSelected(int32 index) const 1497 { 1498 return fSelection->Contains(index); 1499 } 1500 1501 // #pragma mark - 1502 1503 // _InvalidateCanvas 1504 void 1505 PathManipulator::_InvalidateCanvas(BRect rect) const 1506 { 1507 // convert from canvas to view space 1508 fCanvasView->ConvertFromCanvas(&rect); 1509 fCanvasView->Invalidate(rect); 1510 } 1511 1512 // _InvalidateHighlightPoints 1513 void 1514 PathManipulator::_InvalidateHighlightPoints(int32 newIndex, uint32 newMode) 1515 { 1516 BRect oldRect = _ControlPointRect(fCurrentPathPoint, fMode); 1517 BRect newRect = _ControlPointRect(newIndex, newMode); 1518 if (oldRect.IsValid()) 1519 _InvalidateCanvas(oldRect); 1520 if (newRect.IsValid()) 1521 _InvalidateCanvas(newRect); 1522 } 1523 1524 // _UpdateSelection 1525 void 1526 PathManipulator::_UpdateSelection() const 1527 { 1528 _InvalidateCanvas(_ControlPointRect()); 1529 if (BWindow* window = fCanvasView->Window()) { 1530 window->PostMessage(MSG_UPDATE_SHAPE_UI); 1531 } 1532 } 1533 1534 // _ControlPointRect 1535 BRect 1536 PathManipulator::_ControlPointRect() const 1537 { 1538 BRect r = fPath->ControlPointBounds(); 1539 r.InsetBy(-POINT_EXTEND, -POINT_EXTEND); 1540 return r; 1541 } 1542 1543 // _ControlPointRect 1544 BRect 1545 PathManipulator::_ControlPointRect(int32 index, uint32 mode) const 1546 { 1547 BRect rect(0.0, 0.0, -1.0, -1.0); 1548 if (index >= 0) { 1549 BPoint p, pIn, pOut; 1550 fPath->GetPointsAt(index, p, pIn, pOut); 1551 switch (mode) { 1552 case MOVE_POINT: 1553 case TOGGLE_SHARP: 1554 case REMOVE_POINT: 1555 case CLOSE_PATH: 1556 rect.Set(p.x, p.y, p.x, p.y); 1557 rect.InsetBy(-POINT_EXTEND, -POINT_EXTEND); 1558 break; 1559 case MOVE_POINT_IN: 1560 case TOGGLE_SHARP_IN: 1561 case REMOVE_POINT_IN: 1562 rect.Set(pIn.x, pIn.y, pIn.x, pIn.y); 1563 rect.InsetBy(-CONTROL_POINT_EXTEND, -CONTROL_POINT_EXTEND); 1564 break; 1565 case MOVE_POINT_OUT: 1566 case TOGGLE_SHARP_OUT: 1567 case REMOVE_POINT_OUT: 1568 rect.Set(pOut.x, pOut.y, pOut.x, pOut.y); 1569 rect.InsetBy(-CONTROL_POINT_EXTEND, -CONTROL_POINT_EXTEND); 1570 break; 1571 case SELECT_POINTS: 1572 rect.Set(min4(p.x, pIn.x, pOut.x, pOut.x), 1573 min4(p.y, pIn.y, pOut.y, pOut.y), 1574 max4(p.x, pIn.x, pOut.x, pOut.x), 1575 max4(p.y, pIn.y, pOut.y, pOut.y)); 1576 rect.InsetBy(-POINT_EXTEND, -POINT_EXTEND); 1577 break; 1578 } 1579 } 1580 return rect; 1581 } 1582 1583 // #pragma mark - 1584 1585 // _SetModeForMousePos 1586 void 1587 PathManipulator::_SetModeForMousePos(BPoint where) 1588 { 1589 uint32 mode = UNDEFINED; 1590 int32 index = -1; 1591 1592 float zoomLevel = fCanvasView->ZoomLevel(); 1593 1594 // see if we're close enough at a control point 1595 BPoint point; 1596 BPoint pointIn; 1597 BPoint pointOut; 1598 for (int32 i = 0; fPath->GetPointsAt(i, point, pointIn, pointOut) 1599 && mode == UNDEFINED; i++) { 1600 1601 float distM = point_point_distance(point, where) * zoomLevel; 1602 float distIn = point_point_distance(pointIn, where) * zoomLevel; 1603 float distOut = point_point_distance(pointOut, where) * zoomLevel; 1604 1605 if (distM < MOVE_THRESHOLD) { 1606 if (i == 0 && fClickToClose 1607 && !fPath->IsClosed() && fPath->CountPoints() > 1) { 1608 mode = fCommandDown ? TOGGLE_SHARP : 1609 (fOptionDown ? REMOVE_POINT : CLOSE_PATH); 1610 index = i; 1611 } else { 1612 mode = fCommandDown ? TOGGLE_SHARP : 1613 (fOptionDown ? REMOVE_POINT : MOVE_POINT); 1614 index = i; 1615 } 1616 } 1617 if (distM - distIn > 0.00001 1618 && distIn < MOVE_THRESHOLD) { 1619 mode = fCommandDown ? TOGGLE_SHARP_IN : 1620 (fOptionDown ? REMOVE_POINT_IN : MOVE_POINT_IN); 1621 index = i; 1622 } 1623 if (distIn - distOut > 0.00001 1624 && distOut < distM && distOut < MOVE_THRESHOLD) { 1625 mode = fCommandDown ? TOGGLE_SHARP_OUT : 1626 (fOptionDown ? REMOVE_POINT_OUT : MOVE_POINT_OUT); 1627 index = i; 1628 } 1629 } 1630 // selection mode overrides any other mode, 1631 // but we need to check for it after we know 1632 // the index of the point under the mouse (code above) 1633 int32 pointCount = fPath->CountPoints(); 1634 if (fShiftDown && pointCount > 0) { 1635 mode = SELECT_POINTS; 1636 } 1637 1638 // see if user wants to start new sub path 1639 if (fAltDown) { 1640 mode = NEW_PATH; 1641 index = -1; 1642 } 1643 1644 // see if we're close enough at a line 1645 if (mode == UNDEFINED) { 1646 float distance; 1647 if (fPath->GetDistance(where, &distance, &index)) { 1648 if (distance < (INSERT_DIST_THRESHOLD / zoomLevel)) { 1649 mode = INSERT_POINT; 1650 } 1651 } else { 1652 // restore index, since it was changed by call above 1653 index = fCurrentPathPoint; 1654 } 1655 } 1656 1657 // nope, still undefined mode, last fall back 1658 if (mode == UNDEFINED) { 1659 if (fFallBackMode == SELECT_POINTS) { 1660 if (fPath->IsClosed() && pointCount > 0) { 1661 mode = SELECT_POINTS; 1662 index = -1; 1663 } else { 1664 mode = ADD_POINT; 1665 index = pointCount - 1; 1666 } 1667 } else { 1668 // user had clicked "New Path" icon 1669 mode = fFallBackMode; 1670 } 1671 } 1672 // switch mode if necessary 1673 if (mode != fMode || index != fCurrentPathPoint) { 1674 // invalidate path display (to highlight the respective point) 1675 _InvalidateHighlightPoints(index, mode); 1676 _SetMode(mode); 1677 fCurrentPathPoint = index; 1678 } 1679 } 1680 1681 // #pragma mark - 1682 1683 // _Nudge 1684 void 1685 PathManipulator::_Nudge(BPoint direction) 1686 { 1687 bigtime_t now = system_time(); 1688 if (now - fLastNudgeTime > 500000) { 1689 fCanvasView->Perform(_FinishNudging()); 1690 } 1691 fLastNudgeTime = now; 1692 fNudgeOffset += direction; 1693 1694 if (fTransformBox) { 1695 fTransformBox->NudgeBy(direction); 1696 return; 1697 } 1698 1699 if (!fNudgeCommand) { 1700 1701 bool fromSelection = !fSelection->IsEmpty(); 1702 1703 int32 count = fromSelection ? fSelection->CountItems() 1704 : fPath->CountPoints(); 1705 int32 indices[count]; 1706 control_point points[count]; 1707 1708 // init indices and points 1709 for (int32 i = 0; i < count; i++) { 1710 indices[i] = fromSelection ? fSelection->IndexAt(i) : i; 1711 fPath->GetPointsAt(indices[i], 1712 points[i].point, 1713 points[i].point_in, 1714 points[i].point_out, 1715 &points[i].connected); 1716 } 1717 1718 fNudgeCommand = new NudgePointsCommand(fPath, indices, points, count); 1719 1720 fNudgeCommand->SetNewTranslation(fNudgeOffset); 1721 fNudgeCommand->Redo(); 1722 1723 } else { 1724 fNudgeCommand->SetNewTranslation(fNudgeOffset); 1725 fNudgeCommand->Redo(); 1726 } 1727 1728 if (!fMouseDown) 1729 _SetModeForMousePos(fLastCanvasPos); 1730 } 1731 1732 // _FinishNudging 1733 Command* 1734 PathManipulator::_FinishNudging() 1735 { 1736 fNudgeOffset = BPoint(0.0, 0.0); 1737 1738 Command* command; 1739 1740 if (fTransformBox) { 1741 command = fTransformBox->FinishNudging(); 1742 } else { 1743 command = fNudgeCommand; 1744 fNudgeCommand = NULL; 1745 } 1746 1747 return command; 1748 } 1749 1750 1751