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
55 enum {
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
82 enum {
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
91 inline const char*
string_for_mode(uint32 mode)92 string_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).
139 class PathManipulator::Selection : protected std::vector<int32>
140 {
141 public:
Selection(int32 count=20)142 inline Selection(int32 count = 20)
143 : _inherited() { reserve(count); }
~Selection()144 inline ~Selection() {}
145
Add(int32 value)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
Remove(int32 value)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
Contains(int32 value) const162 inline bool Contains(int32 value) const
163 { return std::binary_search(begin(), end(), value); }
164
IsEmpty() const165 inline bool IsEmpty() const
166 { return size() == 0; }
167
IndexAt(int32 index) const168 inline int32 IndexAt(int32 index) const
169 { return at(index); }
170
MakeEmpty()171 inline void MakeEmpty()
172 { clear(); }
173
Items() const174 inline const int32* Items() const
175 { return &(*this)[0]; }
176
CountItems() const177 inline const int32 CountItems() const
178 { return size(); }
179
operator =(const Selection & other)180 inline Selection& operator =(const Selection& other)
181 {
182 _inherited::operator=(other);
183 return *this;
184 }
185
operator ==(const Selection & other)186 inline bool operator ==(const Selection& other)
187 { return (_inherited)*this == (_inherited)other; }
188
operator !=(const Selection & other)189 inline bool operator !=(const Selection& other)
190 { return (_inherited)*this != (_inherited)other; }
191
192 private:
193 typedef std::vector<int32> _inherited;
194 };
195
196
PathManipulator(VectorPath * path)197 PathManipulator::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
~PathManipulator()234 PathManipulator::~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
255 class StrokePathIterator : public VectorPath::Iterator {
256 public:
StrokePathIterator(CanvasView * canvasView,BView * drawingView)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 }
~StrokePathIterator()265 virtual ~StrokePathIterator()
266 {}
267
MoveTo(BPoint point)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 }
LineTo(BPoint point)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
302 void
Draw(BView * into,BRect updateRect)303 PathManipulator::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
403 bool
MouseDown(BPoint where)404 PathManipulator::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
552 void
MouseMoved(BPoint where)553 PathManipulator::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
632 Command*
MouseUp()633 PathManipulator::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
711 bool
MouseOver(BPoint where)712 PathManipulator::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
742 bool
DoubleClicked(BPoint where)743 PathManipulator::DoubleClicked(BPoint where)
744 {
745 return false;
746 }
747
748
749 bool
ShowContextMenu(BPoint where)750 PathManipulator::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
813 BRect
Bounds()814 PathManipulator::Bounds()
815 {
816 BRect r = _ControlPointRect();
817 fCanvasView->ConvertFromCanvas(&r);
818 return r;
819 }
820
821
822 BRect
TrackingBounds(BView * withinView)823 PathManipulator::TrackingBounds(BView* withinView)
824 {
825 return withinView->Bounds();
826 }
827
828
829 // #pragma mark -
830
831
832 bool
MessageReceived(BMessage * message,Command ** _command)833 PathManipulator::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
872 void
ModifiersChanged(uint32 modifiers)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
890 bool
HandleKeyDown(uint32 key,uint32 modifiers,Command ** _command)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
953 bool
HandleKeyUp(uint32 key,uint32 modifiers,Command ** _command)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
973 bool
UpdateCursor()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
1027 void
AttachedToView(BView * view)1028 PathManipulator::AttachedToView(BView* view)
1029 {
1030 fCanvasView = dynamic_cast<CanvasView*>(view);
1031 }
1032
1033
1034 void
DetachedFromView(BView * view)1035 PathManipulator::DetachedFromView(BView* view)
1036 {
1037 fCanvasView = NULL;
1038 }
1039
1040
1041 // #pragma mark -
1042
1043
1044 void
ObjectChanged(const Observable * object)1045 PathManipulator::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
1062 void
PointAdded(int32 index)1063 PathManipulator::PointAdded(int32 index)
1064 {
1065 ObjectChanged(fPath);
1066 }
1067
1068
1069 void
PointRemoved(int32 index)1070 PathManipulator::PointRemoved(int32 index)
1071 {
1072 fSelection->Remove(index);
1073 ObjectChanged(fPath);
1074 }
1075
1076
1077 void
PointChanged(int32 index)1078 PathManipulator::PointChanged(int32 index)
1079 {
1080 ObjectChanged(fPath);
1081 }
1082
1083
1084 void
PathChanged()1085 PathManipulator::PathChanged()
1086 {
1087 ObjectChanged(fPath);
1088 }
1089
1090
1091 void
PathClosedChanged()1092 PathManipulator::PathClosedChanged()
1093 {
1094 ObjectChanged(fPath);
1095 }
1096
1097
1098 void
PathReversed()1099 PathManipulator::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
1119 uint32
ControlFlags() const1120 PathManipulator::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
1139 void
ReversePath()1140 PathManipulator::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
1158 void
_SetMode(uint32 mode)1159 PathManipulator::_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
1187 void
_SetTransformBox(TransformPointsBox * transformBox)1188 PathManipulator::_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
1225 void
_AddPoint(BPoint where)1226 PathManipulator::_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
1241 BPoint
scale_point(BPoint a,BPoint b,float scale)1242 scale_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
1249 void
_InsertPoint(BPoint where,int32 index)1250 PathManipulator::_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
1300 void
_SetInOutConnected(int32 index,bool connected)1301 PathManipulator::_SetInOutConnected(int32 index, bool connected)
1302 {
1303 fPath->SetInOutConnected(index, connected);
1304 }
1305
1306
1307 void
_SetSharp(int32 index)1308 PathManipulator::_SetSharp(int32 index)
1309 {
1310 BPoint p;
1311 fPath->GetPointAt(index, p);
1312 fPath->SetPoint(index, p, p, p, true);
1313 }
1314
1315
1316 void
_RemoveSelection()1317 PathManipulator::_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
1335 void
_RemovePoint(int32 index)1336 PathManipulator::_RemovePoint(int32 index)
1337 {
1338 if (fPath->RemovePoint(index)) {
1339 _Deselect(index);
1340 _ShiftSelection(index + 1, -1);
1341 }
1342 }
1343
1344
1345 void
_RemovePointIn(int32 index)1346 PathManipulator::_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
1356 void
_RemovePointOut(int32 index)1357 PathManipulator::_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
1367 Command*
_Delete()1368 PathManipulator::_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
1396 void
_Select(BRect r)1397 PathManipulator::_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
1425 void
_Select(int32 index,bool extend)1426 PathManipulator::_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
1439 void
_Select(const int32 * indices,int32 count,bool extend)1440 PathManipulator::_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
1457 void
_Deselect(int32 index)1458 PathManipulator::_Deselect(int32 index)
1459 {
1460 if (fSelection->Contains(index)) {
1461 fSelection->Remove(index);
1462 _UpdateSelection();
1463 }
1464 }
1465
1466
1467 void
_ShiftSelection(int32 startIndex,int32 direction)1468 PathManipulator::_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
1484 bool
_IsSelected(int32 index) const1485 PathManipulator::_IsSelected(int32 index) const
1486 {
1487 return fSelection->Contains(index);
1488 }
1489
1490
1491 // #pragma mark -
1492
1493
1494 void
_InvalidateCanvas(BRect rect) const1495 PathManipulator::_InvalidateCanvas(BRect rect) const
1496 {
1497 // convert from canvas to view space
1498 fCanvasView->ConvertFromCanvas(&rect);
1499 fCanvasView->Invalidate(rect);
1500 }
1501
1502
1503 void
_InvalidateHighlightPoints(int32 newIndex,uint32 newMode)1504 PathManipulator::_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
1515 void
_UpdateSelection() const1516 PathManipulator::_UpdateSelection() const
1517 {
1518 _InvalidateCanvas(_ControlPointRect());
1519 if (BWindow* window = fCanvasView->Window()) {
1520 window->PostMessage(MSG_UPDATE_SHAPE_UI);
1521 }
1522 }
1523
1524
1525 BRect
_ControlPointRect() const1526 PathManipulator::_ControlPointRect() const
1527 {
1528 BRect r = fPath->ControlPointBounds();
1529 r.InsetBy(-POINT_EXTEND, -POINT_EXTEND);
1530 return r;
1531 }
1532
1533
1534 BRect
_ControlPointRect(int32 index,uint32 mode) const1535 PathManipulator::_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
1577 void
_SetModeForMousePos(BPoint where)1578 PathManipulator::_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
1676 void
_Nudge(BPoint direction)1677 PathManipulator::_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
1725 Command*
_FinishNudging()1726 PathManipulator::_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