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