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