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