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