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