xref: /haiku/src/apps/icon-o-matic/shape/PathManipulator.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
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