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