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