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