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