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