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