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