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