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