1 //
2 // BaseEditEngine.cs
3 //
4 // Author:
5 //       Andrew Davis <andrew.3.1415@gmail.com>
6 //
7 // Copyright (c) 2014 Andrew Davis, GSoC 2014
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 //
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
18 //
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 // THE SOFTWARE.
26 
27 using System;
28 using Cairo;
29 using Pinta.Core;
30 using System.Collections.Generic;
31 using System.Linq;
32 using System.Text;
33 using Mono.Unix;
34 
35 namespace Pinta.Tools
36 {
37     //The EditEngine was created for tools that wish to utilize any of the control point, line/curve, hover point (reacting to the mouse),
38     //and etc. code that was originally used in the LineCurveTool for editability. If a class wishes to use it, it should create and instantiate
39     //a protected instance of the EditEngine inside the class and then utilize it in a similar fashion to any of the editable tools.
40     public abstract class BaseEditEngine
41     {
BaseEditEngine()42 		static BaseEditEngine ()
43 		{
44 			Gtk.IconFactory fact = new Gtk.IconFactory ();
45 			fact.Add ("Tools.Line.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Tools.Line.png")));
46 			fact.Add ("Tools.Rectangle.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Tools.Rectangle.png")));
47 			fact.Add ("Tools.Ellipse.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Tools.Ellipse.png")));
48 			fact.Add ("Tools.RoundedRectangle.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Tools.RoundedRectangle.png")));
49 			fact.AddDefault ();
50 		}
51 
52 		public enum ShapeTypes
53 		{
54 			OpenLineCurveSeries,
55 			ClosedLineCurveSeries,
56 			Ellipse,
57 			RoundedLineSeries
58 		}
59 
60 		public static Dictionary<ShapeTypes, ShapeTool> CorrespondingTools = new Dictionary<ShapeTypes, ShapeTool>();
61 
62         protected abstract string ShapeName { get; }
63 
64         protected readonly ShapeTool owner;
65 
66         protected bool is_drawing = false;
67 
68 		protected Rectangle? last_dirty = null;
69 		protected Rectangle? last_hover = null;
70 
71 		protected double last_control_pt_size = 0d;
72 
73         protected PointD shape_origin;
74         protected PointD current_point;
75 
76 		public static Color OutlineColor
77 		{
78 			get { return PintaCore.Palette.PrimaryColor; }
79 			set { PintaCore.Palette.PrimaryColor = value; }
80 		}
81 
82 		public static Color FillColor
83 		{
84 			get { return PintaCore.Palette.SecondaryColor; }
85 			set { PintaCore.Palette.SecondaryColor = value; }
86 		}
87 
88         protected ToolBarComboBox brush_width;
89         protected ToolBarLabel brush_width_label;
90         protected ToolBarButton brush_width_minus;
91         protected ToolBarButton brush_width_plus;
92         protected ToolBarLabel fill_label;
93         protected ToolBarDropDownButton fill_button;
94         protected Gtk.SeparatorToolItem fill_sep;
95 
96 		protected ToolBarLabel shape_type_label;
97 		protected ToolBarDropDownButton shape_type_button;
98 		protected Gtk.SeparatorToolItem shape_type_sep;
99 
100         protected DashPatternBox dash_pattern_box = new DashPatternBox();
101 		private string prev_dash_pattern = "-";
102 
103 		private bool prev_antialiasing = true;
104 
105         public int BrushWidth
106         {
107             get
108             {
109 				if (brush_width != null)
110 				{
111 					int width;
112 
113 					if (Int32.TryParse(brush_width.ComboBox.ActiveText, out width))
114 					{
115 						if (width > 0)
116 						{
117 							(brush_width.ComboBox as Gtk.ComboBoxEntry).Entry.Text = width.ToString();
118 
119 							return width;
120 						}
121 					}
122 
123 					(brush_width.ComboBox as Gtk.ComboBoxEntry).Entry.Text = BaseTool.DEFAULT_BRUSH_WIDTH.ToString();
124 				}
125 
126                 return BaseTool.DEFAULT_BRUSH_WIDTH;
127             }
128 
129 			set
130 			{
131 				if (brush_width != null)
132 				{
133 					(brush_width.ComboBox as Gtk.ComboBoxEntry).Entry.Text = value.ToString();
134 				}
135 			}
136         }
137 
138 		private int prev_brush_width = BaseTool.DEFAULT_BRUSH_WIDTH;
139 
140         private bool StrokeShape { get { return (int)fill_button.SelectedItem.Tag % 2 == 0; } }
141         private bool FillShape { get { return (int)fill_button.SelectedItem.Tag >= 1; } }
142 
143 		private ShapeTypes ShapeType
144 		{
145 			get
146 			{
147 				return (ShapeTypes)(int)shape_type_button.SelectedItem.Tag;
148 			}
149 		}
150 
151         protected static readonly Color hover_color =
152             new Color(ToolControl.FillColor.R, ToolControl.FillColor.G, ToolControl.FillColor.B, ToolControl.FillColor.A * 2d / 3d);
153 
154 		public const double ShapeClickStartingRange = 10d;
155 		public const double ShapeClickThicknessFactor = 1d;
156 		public const double DefaultEndPointTension = 0d;
157 		public const double DefaultMidPointTension = 1d / 3d;
158 
159 		public int SelectedPointIndex, SelectedShapeIndex;
160 		protected int prev_selected_shape_index;
161 
162         /// <summary>
163         /// The selected ControlPoint.
164         /// </summary>
165 		public ControlPoint SelectedPoint
166         {
167             get
168             {
169                 ShapeEngine selEngine = SelectedShapeEngine;
170 
171                 if (selEngine != null && selEngine.ControlPoints.Count > SelectedPointIndex)
172                 {
173                     return selEngine.ControlPoints[SelectedPointIndex];
174                 }
175                 else
176                 {
177                     return null;
178                 }
179             }
180 
181             set
182             {
183                 ShapeEngine selEngine = SelectedShapeEngine;
184 
185                 if (selEngine != null && selEngine.ControlPoints.Count > SelectedPointIndex)
186                 {
187                     selEngine.ControlPoints[SelectedPointIndex] = value;
188                 }
189             }
190         }
191 
192 		/// <summary>
193 		/// The active shape's ShapeEngine. A point does not have to be selected here, only a shape. This can be null.
194 		/// </summary>
195 		public ShapeEngine ActiveShapeEngine
196 		{
197 			get
198 			{
199 				if (SelectedShapeIndex > -1 && SEngines.Count > SelectedShapeIndex)
200 				{
201 					return SEngines[SelectedShapeIndex];
202 				}
203 				else
204 				{
205 					return null;
206 				}
207 			}
208 		}
209 
210 		/// <summary>
211 		/// The selected shape's ShapeEngine. This requires that a point in the shape be selected and should be used in most cases. This can be null.
212 		/// </summary>
213 		public ShapeEngine SelectedShapeEngine
214 		{
215 			get
216 			{
217 				if (SelectedPointIndex > -1)
218 				{
219 					return ActiveShapeEngine;
220 				}
221 				else
222 				{
223 					return null;
224 				}
225 			}
226 		}
227 
228 		protected PointD hover_point = new PointD(-1d, -1d);
229 		protected int hovered_pt_as_control_pt = -1;
230 
231 		protected bool changing_tension = false;
232 		protected PointD last_mouse_pos = new PointD(0d, 0d);
233 
234         //Helps to keep track of the first modification on a shape after the mouse is clicked, to prevent unnecessary history items.
235 		protected bool clicked_without_modifying = false;
236 
237         //Stores the editable shape data.
238 		public static ShapeEngineCollection SEngines = new ShapeEngineCollection();
239 
240         #region ToolbarEventHandlers
241 
BrushMinusButtonClickedEvent(object o, EventArgs args)242         protected virtual void BrushMinusButtonClickedEvent(object o, EventArgs args)
243         {
244             if (BrushWidth > 1)
245                 BrushWidth--;
246 
247 			//No need to store previous settings or redraw, as this is done in the Changed event handler.
248         }
249 
BrushPlusButtonClickedEvent(object o, EventArgs args)250         protected virtual void BrushPlusButtonClickedEvent(object o, EventArgs args)
251         {
252             BrushWidth++;
253 
254 			//No need to store previous settings or redraw, as this is done in the Changed event handler.
255         }
256 
Palette_PrimaryColorChanged(object sender, EventArgs e)257 		protected void Palette_PrimaryColorChanged(object sender, EventArgs e)
258 		{
259 			ShapeEngine activeEngine = ActiveShapeEngine;
260 
261 			if (activeEngine != null)
262 			{
263 				activeEngine.OutlineColor = OutlineColor.Clone();
264 
265 				DrawActiveShape(false, false, true, false, false);
266 			}
267 		}
268 
Palette_SecondaryColorChanged(object sender, EventArgs e)269 		protected void Palette_SecondaryColorChanged(object sender, EventArgs e)
270 		{
271 			ShapeEngine activeEngine = ActiveShapeEngine;
272 
273 			if (activeEngine != null)
274 			{
275 				activeEngine.FillColor = FillColor.Clone();
276 
277 				DrawActiveShape(false, false, true, false, false);
278 			}
279 		}
280 
OnFillStyleChanged(object sender, EventArgs e)281 		private void OnFillStyleChanged(object sender, EventArgs e)
282 		{
283 			DrawActiveShape (false, false, true, false, false);
284 		}
285 
286         #endregion ToolbarEventHandlers
287 
288 
BaseEditEngine(ShapeTool passedOwner)289         public BaseEditEngine(ShapeTool passedOwner)
290         {
291             owner = passedOwner;
292 
293 			owner.IsEditableShapeTool = true;
294 
295 			ResetShapes();
296         }
297 
HandleBuildToolBar(Gtk.Toolbar tb)298         public virtual void HandleBuildToolBar(Gtk.Toolbar tb)
299         {
300             if (brush_width_label == null)
301                 brush_width_label = new ToolBarLabel(string.Format(" {0}: ", Catalog.GetString("Brush width")));
302 
303             tb.AppendItem(brush_width_label);
304 
305             if (brush_width_minus == null)
306             {
307                 brush_width_minus = new ToolBarButton("Toolbar.MinusButton.png", "", Catalog.GetString("Decrease brush size"));
308                 brush_width_minus.Clicked += BrushMinusButtonClickedEvent;
309             }
310 
311             tb.AppendItem(brush_width_minus);
312 
313 			if (brush_width == null)
314 			{
315 				brush_width = new ToolBarComboBox(65, 1, true, "1", "2", "3", "4", "5", "6", "7", "8", "9",
316 					"10", "11", "12", "13", "14", "15", "20", "25", "30", "35", "40", "45", "50", "55");
317 
318 				brush_width.ComboBox.Changed += (o, e) =>
319 				{
320 					ShapeEngine selEngine = SelectedShapeEngine;
321 
322 					if (selEngine != null)
323 					{
324 						selEngine.BrushWidth = BrushWidth;
325                         StorePreviousSettings ();
326                         DrawActiveShape (false, false, true, false, false);
327 					}
328 				};
329 			}
330 
331             tb.AppendItem(brush_width);
332 
333             if (brush_width_plus == null)
334             {
335                 brush_width_plus = new ToolBarButton("Toolbar.PlusButton.png", "", Catalog.GetString("Increase brush size"));
336                 brush_width_plus.Clicked += BrushPlusButtonClickedEvent;
337             }
338 
339             tb.AppendItem(brush_width_plus);
340 
341             if (fill_sep == null)
342                 fill_sep = new Gtk.SeparatorToolItem();
343 
344             tb.AppendItem(fill_sep);
345 
346             if (fill_label == null)
347                 fill_label = new ToolBarLabel(string.Format(" {0}: ", Catalog.GetString("Fill Style")));
348 
349             tb.AppendItem(fill_label);
350 
351             if (fill_button == null)
352             {
353                 fill_button = new ToolBarDropDownButton();
354 
355                 fill_button.AddItem(Catalog.GetString("Outline Shape"), "ShapeTool.Outline.png", 0);
356                 fill_button.AddItem(Catalog.GetString("Fill Shape"), "ShapeTool.Fill.png", 1);
357                 fill_button.AddItem(Catalog.GetString("Fill and Outline Shape"), "ShapeTool.OutlineFill.png", 2);
358 		fill_button.SelectedItemChanged += OnFillStyleChanged;
359             }
360 
361             tb.AppendItem(fill_button);
362 
363 			if (shape_type_sep == null)
364 				shape_type_sep = new Gtk.SeparatorToolItem();
365 
366 			tb.AppendItem(shape_type_sep);
367 
368 			if (shape_type_label == null)
369 				shape_type_label = new ToolBarLabel(string.Format(" {0}: ", Catalog.GetString("Shape Type")));
370 
371 			tb.AppendItem(shape_type_label);
372 
373 			if (shape_type_button == null)
374 			{
375 				shape_type_button = new ToolBarDropDownButton();
376 
377 				shape_type_button.AddItem(Catalog.GetString("Open Line/Curve Series"), "Tools.Line.png", 0);
378 				shape_type_button.AddItem(Catalog.GetString("Closed Line/Curve Series"), "Tools.Rectangle.png", 1);
379 				shape_type_button.AddItem(Catalog.GetString("Ellipse"), "Tools.Ellipse.png", 2);
380 				shape_type_button.AddItem(Catalog.GetString("Rounded Line Series"), "Tools.RoundedRectangle.png", 3);
381 
382 				shape_type_button.SelectedItemChanged += (o, e) =>
383 				{
384 					ShapeTypes newShapeType = ShapeType;
385 					ShapeEngine selEngine = SelectedShapeEngine;
386 
387 					if (selEngine != null)
388 					{
389 						//Verify that the tool needs to be switched.
390 						if (GetCorrespondingTool(newShapeType) != this.owner)
391 						{
392 							//Create a new ShapesModifyHistoryItem so that the changing of the shape type can be undone.
393 							PintaCore.Workspace.ActiveDocument.History.PushNewItem(new ShapesModifyHistoryItem(
394 								this, owner.Icon, Catalog.GetString("Changed Shape Type")));
395 
396 							//Clone the old shape; it should be automatically garbage-collected. newShapeType already has the updated value.
397 							selEngine = selEngine.Convert(newShapeType, SelectedShapeIndex);
398 
399 							int previousSSI = SelectedShapeIndex;
400 
401 							ActivateCorrespondingTool(selEngine.ShapeType, true);
402 
403 							SelectedShapeIndex = previousSSI;
404 
405 							//Draw the updated shape with organized points generation (for mouse detection).
406 							DrawActiveShape(true, false, true, false, true);
407 						}
408 					}
409 				};
410 			}
411 
412 			shape_type_button.SelectedItem = shape_type_button.Items[(int)owner.ShapeType];
413 
414 			tb.AppendItem(shape_type_button);
415 
416 
417             Gtk.ComboBox dpbBox = dash_pattern_box.SetupToolbar(tb);
418 
419             if (dpbBox != null)
420             {
421                 dpbBox.Changed += (o, e) =>
422                 {
423 					ShapeEngine selEngine = SelectedShapeEngine;
424 
425 					if (selEngine != null)
426 					{
427 						selEngine.DashPattern = dpbBox.ActiveText;
428                         StorePreviousSettings ();
429                         DrawActiveShape (false, false, true, false, false);
430 					}
431                 };
432             }
433         }
434 
HandleActivated()435         public virtual void HandleActivated()
436         {
437 			RecallPreviousSettings();
438 
439             PintaCore.Palette.PrimaryColorChanged += new EventHandler(Palette_PrimaryColorChanged);
440             PintaCore.Palette.SecondaryColorChanged += new EventHandler(Palette_SecondaryColorChanged);
441         }
442 
HandleDeactivated(BaseTool newTool)443 		public virtual void HandleDeactivated(BaseTool newTool)
444 		{
445 			SelectedPointIndex = -1;
446 			SelectedShapeIndex = -1;
447 
448 			StorePreviousSettings();
449 
450 			//Determine if the tool being switched to will be another editable tool.
451 			if (PintaCore.Workspace.HasOpenDocuments && !newTool.IsEditableShapeTool)
452 			{
453 				//The tool being switched to is not editable. Finalize every editable shape not yet finalized.
454 				FinalizeAllShapes();
455 			}
456 
457             PintaCore.Palette.PrimaryColorChanged -= Palette_PrimaryColorChanged;
458             PintaCore.Palette.SecondaryColorChanged -= Palette_SecondaryColorChanged;
459         }
460 
HandleAfterSave()461 		public virtual void HandleAfterSave()
462 		{
463 			//When saving, everything will be finalized, which is good; however, afterwards, the user will expect
464 			//everything to remain editable. Currently, a finalization history item will always be added.
465 			PintaCore.Actions.Edit.Undo.Activate();
466 
467 			//Redraw all of the editable shapes in case saving caused some extra/unexpected behavior.
468 			DrawAllShapes();
469 		}
470 
HandleCommit()471         public virtual void HandleCommit()
472         {
473             //Finalize every editable shape not yet finalized.
474 			FinalizeAllShapes();
475         }
476 
HandleBeforeUndo()477 		public virtual bool HandleBeforeUndo()
478 		{
479 			return false;
480 		}
481 
HandleBeforeRedo()482 		public virtual bool HandleBeforeRedo()
483 		{
484 			return false;
485 		}
486 
HandleAfterUndo()487         public virtual void HandleAfterUndo()
488         {
489             ShapeEngine activeEngine = ActiveShapeEngine;
490 
491             if (activeEngine != null)
492             {
493 				UpdateToolbarSettings(activeEngine);
494             }
495 
496             //Draw the current state.
497 			DrawActiveShape(true, false, true, false, false);
498         }
499 
HandleAfterRedo()500         public virtual void HandleAfterRedo()
501         {
502             ShapeEngine activeEngine = ActiveShapeEngine;
503 
504             if (activeEngine != null)
505             {
506 				UpdateToolbarSettings(activeEngine);
507             }
508 
509             //Draw the current state.
510 			DrawActiveShape(true, false, true, false, false);
511         }
512 
HandleKeyDown(Gtk.DrawingArea canvas, Gtk.KeyPressEventArgs args)513         public virtual bool HandleKeyDown(Gtk.DrawingArea canvas, Gtk.KeyPressEventArgs args)
514         {
515 			Gdk.Key keyPressed = args.Event.Key;
516 
517 			if (keyPressed == Gdk.Key.Delete)
518             {
519                 if (SelectedPointIndex > -1)
520                 {
521                     List<ControlPoint> controlPoints = SelectedShapeEngine.ControlPoints;
522 
523 					//Either delete a ControlPoint or an entire shape (if there's only 1 ControlPoint left).
524                     if (controlPoints.Count > 1)
525                     {
526 						//Create a new ShapesModifyHistoryItem so that the deletion of a control point can be undone.
527 						PintaCore.Workspace.ActiveDocument.History.PushNewItem(
528 							new ShapesModifyHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Point Deleted")));
529 
530 						//Delete the selected point from the shape.
531 						controlPoints.RemoveAt(SelectedPointIndex);
532 
533 						//Set the newly selected point to be the median-most point on the shape, order-wise.
534                         if (SelectedPointIndex > controlPoints.Count / 2)
535                         {
536                             --SelectedPointIndex;
537                         }
538                     }
539                     else
540                     {
541 						Document doc = PintaCore.Workspace.ActiveDocument;
542 
543 						//Create a new ShapesHistoryItem so that the deletion of a shape can be undone.
544 						doc.History.PushNewItem(
545 							new ShapesHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Deleted"),
546 								doc.CurrentUserLayer.Surface.Clone(), doc.CurrentUserLayer, SelectedPointIndex, SelectedShapeIndex, false));
547 
548 
549 						//Since the shape itself will be deleted, remove its ReEditableLayer from the drawing loop.
550 
551 						ReEditableLayer removeMe = SEngines.ElementAt(SelectedShapeIndex).DrawingLayer;
552 
553 						if (removeMe.InTheLoop)
554 						{
555 							SEngines.ElementAt(SelectedShapeIndex).DrawingLayer.TryRemoveLayer();
556 						}
557 
558 
559 						//Delete the selected shape.
560 						SEngines.RemoveAt(SelectedShapeIndex);
561 
562 						//Redraw the workspace.
563 						doc.Workspace.Invalidate();
564 
565                         SelectedPointIndex = -1;
566 						SelectedShapeIndex = -1;
567                     }
568 
569                     hover_point = new PointD(-1d, -1d);
570 
571 					DrawActiveShape(true, false, true, false, false);
572                 }
573 
574                 args.RetVal = true;
575             }
576             else if (keyPressed == Gdk.Key.Return)
577             {
578                 //Finalize every editable shape not yet finalized.
579 				FinalizeAllShapes();
580 
581                 args.RetVal = true;
582             }
583             else if (keyPressed == Gdk.Key.space)
584             {
585                 ControlPoint selPoint = SelectedPoint;
586 
587                 if (selPoint != null)
588                 {
589                     //This can be assumed not to be null since selPoint was not null.
590                     ShapeEngine selEngine = SelectedShapeEngine;
591 
592                     //Create a new ShapesModifyHistoryItem so that the adding of a control point can be undone.
593                     PintaCore.Workspace.ActiveDocument.History.PushNewItem(
594 						new ShapesModifyHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Point Added")));
595 
596 
597                     bool shiftKey = (args.Event.State & Gdk.ModifierType.ShiftMask) == Gdk.ModifierType.ShiftMask;
598                     bool ctrlKey = (args.Event.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask;
599 
600                     PointD newPointPos;
601 
602                     if (ctrlKey)
603                     {
604                         //Ctrl + space combo: same position as currently selected point.
605                         newPointPos = new PointD(selPoint.Position.X, selPoint.Position.Y);
606                     }
607                     else
608                     {
609                         shape_origin = new PointD(selPoint.Position.X, selPoint.Position.Y);
610 
611                         if (shiftKey)
612                         {
613                             CalculateModifiedCurrentPoint();
614                         }
615 
616                         //Space only: position of mouse (after any potential shift alignment).
617                         newPointPos = new PointD(current_point.X, current_point.Y);
618                     }
619 
620                     //Place the new point on the outside-most end, order-wise.
621                     if ((double)SelectedPointIndex < (double)selEngine.ControlPoints.Count / 2d)
622                     {
623                         SelectedShapeEngine.ControlPoints.Insert(SelectedPointIndex,
624                             new ControlPoint(new PointD(newPointPos.X, newPointPos.Y), DefaultMidPointTension));
625                     }
626                     else
627                     {
628                         SelectedShapeEngine.ControlPoints.Insert(SelectedPointIndex + 1,
629                             new ControlPoint(new PointD(newPointPos.X, newPointPos.Y), DefaultMidPointTension));
630 
631                         ++SelectedPointIndex;
632                     }
633 
634 					DrawActiveShape(true, false, true, shiftKey, false);
635                 }
636 
637                 args.RetVal = true;
638             }
639             else if (keyPressed == Gdk.Key.Up)
640             {
641                 //Make sure a control point is selected.
642                 if (SelectedPointIndex > -1)
643                 {
644                     //Move the selected control point.
645                     SelectedPoint.Position.Y -= 1d;
646 
647 					DrawActiveShape(true, false, true, false, false);
648                 }
649 
650                 args.RetVal = true;
651             }
652             else if (keyPressed == Gdk.Key.Down)
653             {
654                 //Make sure a control point is selected.
655                 if (SelectedPointIndex > -1)
656                 {
657                     //Move the selected control point.
658                     SelectedPoint.Position.Y += 1d;
659 
660 					DrawActiveShape(true, false, true, false, false);
661                 }
662 
663                 args.RetVal = true;
664             }
665             else if (keyPressed == Gdk.Key.Left)
666             {
667                 //Make sure a control point is selected.
668                 if (SelectedPointIndex > -1)
669                 {
670                     if ((args.Event.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask)
671                     {
672                         //Change the selected control point to be the previous one.
673 
674 						--SelectedPointIndex;
675 
676                         if (SelectedPointIndex < 0)
677                         {
678 							ShapeEngine activeEngine = ActiveShapeEngine;
679 
680 							if (activeEngine != null)
681 							{
682 								SelectedPointIndex = activeEngine.ControlPoints.Count - 1;
683 							}
684                         }
685                     }
686                     else
687                     {
688                         //Move the selected control point.
689                         SelectedPoint.Position.X -= 1d;
690                     }
691 
692 					DrawActiveShape(true, false, true, false, false);
693                 }
694 
695                 args.RetVal = true;
696             }
697             else if (keyPressed == Gdk.Key.Right)
698             {
699                 //Make sure a control point is selected.
700                 if (SelectedPointIndex > -1)
701                 {
702                     if ((args.Event.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask)
703                     {
704 						//Change the selected control point to be the following one.
705 
706 						ShapeEngine activeEngine = ActiveShapeEngine;
707 
708 						if (activeEngine != null)
709 						{
710 							++SelectedPointIndex;
711 
712 							if (SelectedPointIndex > activeEngine.ControlPoints.Count - 1)
713 							{
714 								SelectedPointIndex = 0;
715 							}
716 						}
717                     }
718                     else
719                     {
720                         //Move the selected control point.
721                         SelectedPoint.Position.X += 1d;
722                     }
723 
724 					DrawActiveShape(true, false, true, false, false);
725                 }
726 
727                 args.RetVal = true;
728             }
729             else
730             {
731                 return false;
732             }
733 
734             return true;
735         }
736 
HandleKeyUp(Gtk.DrawingArea canvas, Gtk.KeyReleaseEventArgs args)737         public virtual bool HandleKeyUp(Gtk.DrawingArea canvas, Gtk.KeyReleaseEventArgs args)
738         {
739 			Gdk.Key keyReleased = args.Event.Key;
740 
741             if (keyReleased == Gdk.Key.Delete || keyReleased == Gdk.Key.Return || keyReleased == Gdk.Key.space
742                 || keyReleased == Gdk.Key.Up || keyReleased == Gdk.Key.Down
743                 || keyReleased == Gdk.Key.Left || keyReleased == Gdk.Key.Right)
744             {
745                 args.RetVal = true;
746 
747                 return true;
748             }
749             else
750             {
751                 return false;
752             }
753         }
754 
HandleMouseDown(Gtk.DrawingArea canvas, Gtk.ButtonPressEventArgs args, Cairo.PointD point)755         public virtual void HandleMouseDown(Gtk.DrawingArea canvas, Gtk.ButtonPressEventArgs args, Cairo.PointD point)
756         {
757             //If we are already drawing, ignore any additional mouse down events.
758 			if (is_drawing)
759 			{
760 				return;
761 			}
762 
763 			//Redraw the previously (and possibly currently) active shape without any control points in case another shape is made active.
764 			DrawActiveShape(false, false, false, false, false);
765 
766             Document doc = PintaCore.Workspace.ActiveDocument;
767 
768             shape_origin = new PointD(Utility.Clamp(point.X, 0, doc.ImageSize.Width - 1), Utility.Clamp(point.Y, 0, doc.ImageSize.Height - 1));
769             current_point = shape_origin;
770 
771             bool shiftKey = (args.Event.State & Gdk.ModifierType.ShiftMask) == Gdk.ModifierType.ShiftMask;
772 
773             if (shiftKey)
774             {
775                 CalculateModifiedCurrentPoint();
776             }
777 
778             is_drawing = true;
779 
780 
781             //Right clicking changes tension.
782             if (args.Event.Button == 1)
783             {
784                 changing_tension = false;
785             }
786             else
787             {
788                 changing_tension = true;
789             }
790 
791 
792 			bool ctrlKey = (args.Event.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask;
793 
794 
795 			int closestCPIndex, closestCPShapeIndex;
796 			ControlPoint closestControlPoint;
797 			double closestCPDistance;
798 
799 			SEngines.FindClosestControlPoint(current_point,
800 				out closestCPShapeIndex, out closestCPIndex, out closestControlPoint, out closestCPDistance);
801 
802             int closestShapeIndex, closestPointIndex;
803             PointD closestPoint;
804             double closestDistance;
805 
806             OrganizedPointCollection.FindClosestPoint(SEngines, current_point,
807                 out closestShapeIndex, out closestPointIndex, out closestPoint, out closestDistance);
808 
809             bool clickedOnControlPoint = false;
810 
811 			double currentClickRange = ShapeClickStartingRange + BrushWidth * ShapeClickThicknessFactor;
812 
813 			//Determine if the closest ControlPoint is within the expected click range.
814 			if (closestControlPoint != null && closestCPDistance < currentClickRange)
815 			{
816 				//User clicked directly on a ControlPoint on a shape.
817 
818 				clicked_without_modifying = true;
819 
820 				SelectedPointIndex = closestCPIndex;
821 				SelectedShapeIndex = closestCPShapeIndex;
822 
823 				clickedOnControlPoint = true;
824 			}
825 			else if (closestDistance < currentClickRange) //Determine if the user clicked close enough to a shape.
826 			{
827 				//User clicked on a generated point on a shape.
828 
829 				List<ControlPoint> controlPoints = SEngines[closestShapeIndex].ControlPoints;
830 
831 				//Note: compare the currentPoint's distance here because it's the actual mouse position.
832 				if (controlPoints.Count > closestPointIndex && current_point.Distance(controlPoints[closestPointIndex].Position) < currentClickRange)
833 				{
834 					//User clicked on a control point (on the "previous order" side of the point).
835 
836 					clicked_without_modifying = true;
837 
838 					SelectedPointIndex = closestPointIndex;
839 					SelectedShapeIndex = closestShapeIndex;
840 
841 					clickedOnControlPoint = true;
842 				}
843 				else if (closestPointIndex > 0)
844 				{
845 					if (current_point.Distance(controlPoints[closestPointIndex - 1].Position) < currentClickRange)
846 					{
847 						//User clicked on a control point (on the "following order" side of the point).
848 
849 						clicked_without_modifying = true;
850 
851 						SelectedPointIndex = closestPointIndex - 1;
852 						SelectedShapeIndex = closestShapeIndex;
853 
854 						clickedOnControlPoint = true;
855 					}
856 					else if (controlPoints.Count > 0 && current_point.Distance(controlPoints[controlPoints.Count - 1].Position) < currentClickRange)
857 					{
858 						//User clicked on a control point (on the "following order" side of the point).
859 
860 						clicked_without_modifying = true;
861 
862 						SelectedPointIndex = closestPointIndex - 1;
863 						SelectedShapeIndex = closestShapeIndex;
864 
865 						clickedOnControlPoint = true;
866 					}
867 				}
868 
869 				//Check for clicking on a non-control point. Don't do anything here if right clicked.
870 				if (!changing_tension && !clickedOnControlPoint && closestShapeIndex > -1 && closestPointIndex > -1 && SEngines.Count > closestShapeIndex)
871 				{
872 					//User clicked on a non-control point on a shape.
873 
874 					//Determine if the currently active tool matches the clicked on shape's corresponding tool, and if not, switch to it.
875 					if (ActivateCorrespondingTool(closestShapeIndex, true) != null)
876 					{
877 						//Pass on the event and its data to the newly activated tool.
878 						PintaCore.Tools.CurrentTool.DoMouseDown(canvas, args, point);
879 
880 						//Don't do anything else here once the tool is switched and the event is passed on.
881 						return;
882 					}
883 
884 					//The currently active tool matches the clicked on shape's corresponding tool.
885 
886 					//Only create a new shape if the user isn't holding the control key down.
887 					if (!ctrlKey)
888 					{
889 						//Create a new ShapesModifyHistoryItem so that the adding of a control point can be undone.
890 						doc.History.PushNewItem(new ShapesModifyHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Point Added")));
891 
892 						controlPoints.Insert(closestPointIndex,
893 							new ControlPoint(new PointD(current_point.X, current_point.Y), DefaultMidPointTension));
894 					}
895 
896 					//These should be set after creating the history item.
897 					SelectedPointIndex = closestPointIndex;
898 					SelectedShapeIndex = closestShapeIndex;
899 
900 					ShapeEngine activeEngine = ActiveShapeEngine;
901 
902 					if (activeEngine != null)
903 					{
904 						UpdateToolbarSettings(activeEngine);
905 					}
906 				}
907 			}
908 
909 			//Create a new shape if the user control + clicks on a shape or if the user simply clicks outside of any shapes.
910 			if (!changing_tension && (ctrlKey || (closestCPDistance >= currentClickRange && closestDistance >= currentClickRange)))
911             {
912 				//Verify that the user clicked inside the image bounds or that the user is
913 				//holding the Ctrl key (to ignore the Image bounds and draw on the edge).
914 				if ((point.X == shape_origin.X && point.Y == shape_origin.Y) || ctrlKey)
915 				{
916 					PointD prevSelPoint;
917 
918 					//First, store the position of the currently selected point.
919 					if (SelectedPoint != null && ctrlKey)
920 					{
921 						prevSelPoint = new PointD(SelectedPoint.Position.X, SelectedPoint.Position.Y);
922 					}
923 					else
924 					{
925 						//This doesn't matter, other than the fact that it gets set to a value in order for the code to build.
926 						prevSelPoint = new PointD(0d, 0d);
927 					}
928 
929 					//Create a new ShapesHistoryItem so that the creation of a new shape can be undone.
930 					doc.History.PushNewItem(new ShapesHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Added"),
931 						doc.CurrentUserLayer.Surface.Clone(), doc.CurrentUserLayer, SelectedPointIndex, SelectedShapeIndex, false));
932 
933 					//Create the shape, add its starting points, and add it to SEngines.
934 					SEngines.Add(CreateShape(ctrlKey, clickedOnControlPoint, prevSelPoint));
935 
936 					//Select the new shape.
937 					SelectedShapeIndex = SEngines.Count - 1;
938 
939 					ShapeEngine activeEngine = ActiveShapeEngine;
940 
941 					if (activeEngine != null)
942 					{
943 						//Set the AntiAliasing.
944 						activeEngine.AntiAliasing = owner.UseAntialiasing;
945 					}
946 
947 					StorePreviousSettings();
948 				}
949             }
950 			else if (clickedOnControlPoint)
951 			{
952 				//Since the user is not creating a new shape or control point but rather modifying an existing control point, it should be determined
953 				//whether the currently active tool matches the clicked on shape's corresponding tool, and if not, switch to it.
954 				if (ActivateCorrespondingTool(SelectedShapeIndex, true) != null)
955 				{
956 					//Pass on the event and its data to the newly activated tool.
957 					PintaCore.Tools.CurrentTool.DoMouseDown(canvas, args, point);
958 
959 					//Don't do anything else here once the tool is switched and the event is passed on.
960 					return;
961 				}
962 
963 				//The currently active tool matches the clicked on shape's corresponding tool.
964 
965 				ShapeEngine activeEngine = ActiveShapeEngine;
966 
967 				if (activeEngine != null)
968 				{
969 					UpdateToolbarSettings(activeEngine);
970 				}
971 			}
972 
973             //Determine if the user right clicks outside of any shapes (neither on their control points nor on their generated points).
974 			if ((closestCPDistance >= currentClickRange && closestDistance >= currentClickRange) && changing_tension)
975             {
976                 clicked_without_modifying = true;
977             }
978 
979 			DrawActiveShape(false, false, true, shiftKey, false);
980         }
981 
HandleMouseUp(Gtk.DrawingArea canvas, Gtk.ButtonReleaseEventArgs args, Cairo.PointD point)982         public virtual void HandleMouseUp(Gtk.DrawingArea canvas, Gtk.ButtonReleaseEventArgs args, Cairo.PointD point)
983         {
984             is_drawing = false;
985 
986             changing_tension = false;
987 
988 			DrawActiveShape(true, false, true, args.Event.IsShiftPressed(), false);
989         }
990 
HandleMouseMove(object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point)991         public virtual void HandleMouseMove(object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point)
992         {
993             Document doc = PintaCore.Workspace.ActiveDocument;
994 
995             current_point = new PointD(Utility.Clamp(point.X, 0, doc.ImageSize.Width - 1), Utility.Clamp(point.Y, 0, doc.ImageSize.Height - 1));
996 
997             bool shiftKey = (args.Event.State & Gdk.ModifierType.ShiftMask) == Gdk.ModifierType.ShiftMask;
998 
999             if (shiftKey)
1000             {
1001                 CalculateModifiedCurrentPoint();
1002             }
1003 
1004             if (!is_drawing)
1005             {
1006                 //Redraw the active shape to show a (temporary) highlighted control point (over any shape) when applicable.
1007 				DrawActiveShape(false, false, true, shiftKey, false);
1008             }
1009             else
1010             {
1011 				ControlPoint selPoint = SelectedPoint;
1012 
1013                 //Make sure a control point is selected.
1014 				if (selPoint != null)
1015                 {
1016                     if (clicked_without_modifying)
1017                     {
1018                         //Create a new ShapesModifyHistoryItem so that the modification of the shape can be undone.
1019                         doc.History.PushNewItem(
1020 							new ShapesModifyHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Modified")));
1021 
1022                         clicked_without_modifying = false;
1023                     }
1024 
1025                     List<ControlPoint> controlPoints = SelectedShapeEngine.ControlPoints;
1026 
1027                     if (!changing_tension)
1028                     {
1029                         //Moving a control point.
1030 
1031                         //Make sure the control point was moved.
1032 						if (current_point.X != selPoint.Position.X || current_point.Y != selPoint.Position.Y)
1033                         {
1034                             MovePoint(controlPoints);
1035                         }
1036                     }
1037                     else
1038                     {
1039                         //Changing a control point's tension.
1040 
1041                         //Unclamp the mouse position when changing tension.
1042                         current_point = new PointD(point.X, point.Y);
1043 
1044                         //Calculate the new tension based off of the movement of the mouse that's
1045                         //perpendicular to the previous and following control points.
1046 
1047                         PointD curPoint = selPoint.Position;
1048                         PointD prevPoint, nextPoint;
1049 
1050                         //Calculate the previous control point.
1051                         if (SelectedPointIndex > 0)
1052                         {
1053                             prevPoint = controlPoints[SelectedPointIndex - 1].Position;
1054                         }
1055                         else
1056                         {
1057                             //There is none.
1058                             prevPoint = curPoint;
1059                         }
1060 
1061                         //Calculate the following control point.
1062                         if (SelectedPointIndex < controlPoints.Count - 1)
1063                         {
1064                             nextPoint = controlPoints[SelectedPointIndex + 1].Position;
1065                         }
1066                         else
1067                         {
1068                             //There is none.
1069                             nextPoint = curPoint;
1070                         }
1071 
1072                         //The x and y differences are used as factors for the x and y change in the mouse position.
1073                         double xDiff = prevPoint.X - nextPoint.X;
1074                         double yDiff = prevPoint.Y - nextPoint.Y;
1075                         double totalDiff = xDiff + yDiff;
1076 
1077                         //Calculate the midpoint in between the previous and following points.
1078                         PointD midPoint = new PointD((prevPoint.X + nextPoint.X) / 2d, (prevPoint.Y + nextPoint.Y) / 2d);
1079 
1080                         double xChange = 0d, yChange = 0d;
1081 
1082                         //Calculate the x change in the mouse position.
1083                         if (curPoint.X <= midPoint.X)
1084                         {
1085                             xChange = current_point.X - last_mouse_pos.X;
1086                         }
1087                         else
1088                         {
1089                             xChange = last_mouse_pos.X - current_point.X;
1090                         }
1091 
1092                         //Calculate the y change in the mouse position.
1093                         if (curPoint.Y <= midPoint.Y)
1094                         {
1095                             yChange = current_point.Y - last_mouse_pos.Y;
1096                         }
1097                         else
1098                         {
1099                             yChange = last_mouse_pos.Y - current_point.Y;
1100                         }
1101 
1102                         //Update the control point's tension.
1103 
1104                         //Note: the difference factors are to be inverted for x and y change because this is perpendicular motion.
1105                         controlPoints[SelectedPointIndex].Tension +=
1106                             Math.Round(Utility.Clamp((xChange * yDiff + yChange * xDiff) / totalDiff, -1d, 1d)) / 50d;
1107 
1108                         //Restrict the new tension to range from 0d to 1d.
1109                         controlPoints[SelectedPointIndex].Tension =
1110 							Utility.Clamp(selPoint.Tension, 0d, 1d);
1111                     }
1112 
1113                     DrawActiveShape(false, false, true, shiftKey, false);
1114                 }
1115             }
1116 
1117             last_mouse_pos = current_point;
1118         }
1119 
1120 
1121 		/// <summary>
1122 		/// Draw the currently active shape.
1123 		/// </summary>
1124 		/// <param name="calculateOrganizedPoints">Whether to calculate the spatially organized
1125 		/// points for mouse detection after drawing the shape.</param>
1126 		/// <param name="finalize">Whether to finalize the drawing.</param>
1127 		/// <param name="drawHoverSelection">Whether to draw any hover point or selected point.</param>
1128 		/// <param name="shiftKey">Whether the shift key is being pressed. This is for width/height constraining/equalizing.</param>
1129 		/// <param name="preventSwitchBack">Whether to prevent switching back to the old tool if a tool change is necessary.</param>
DrawActiveShape(bool calculateOrganizedPoints, bool finalize, bool drawHoverSelection, bool shiftKey, bool preventSwitchBack)1130 		public void DrawActiveShape(bool calculateOrganizedPoints, bool finalize, bool drawHoverSelection, bool shiftKey, bool preventSwitchBack)
1131 		{
1132 			ShapeTool oldTool = BaseEditEngine.ActivateCorrespondingTool(SelectedShapeIndex, calculateOrganizedPoints);
1133 
1134 			//First, determine if the currently active tool matches the shape's corresponding tool, and if not, switch to it.
1135 			if (oldTool != null)
1136 			{
1137 				//The tool has switched, so call DrawActiveShape again but inside that tool.
1138 				((ShapeTool)PintaCore.Tools.CurrentTool).EditEngine.DrawActiveShape(
1139 					calculateOrganizedPoints, finalize, drawHoverSelection, shiftKey, preventSwitchBack);
1140 
1141 				//Afterwards, switch back to the old tool, unless specified otherwise.
1142 				if (!preventSwitchBack)
1143 				{
1144 					ActivateCorrespondingTool(oldTool.ShapeType, true);
1145 				}
1146 
1147 				return;
1148 			}
1149 
1150 			//The currently active tool should now match the shape's corresponding tool.
1151 
1152 			BeforeDraw();
1153 
1154 			ShapeEngine activeEngine = ActiveShapeEngine;
1155 
1156 			if (activeEngine == null)
1157 			{
1158 				//No shape will be drawn; however, the hover point still needs to be drawn if drawHoverSelection is true.
1159 				if (drawHoverSelection)
1160 				{
1161 					DrawTemporaryHoverPoint();
1162 				}
1163 			}
1164 			else
1165 			{
1166 				//Clear any temporary drawing, because something new will be drawn.
1167 				activeEngine.DrawingLayer.Layer.Clear();
1168 
1169 				Rectangle dirty;
1170 
1171 				//Determine if the drawing should be for finalizing the shape onto the image or drawing it temporarily.
1172 				if (finalize)
1173 				{
1174 					dirty = DrawFinalized(activeEngine, true, shiftKey);
1175 				}
1176 				else
1177 				{
1178 					dirty = DrawUnfinalized(activeEngine, drawHoverSelection, shiftKey);
1179 				}
1180 
1181 				//Determine if the organized (spatially hashed) points should be generated. This is for mouse interaction detection after drawing.
1182 				if (calculateOrganizedPoints)
1183 				{
1184 					OrganizePoints(activeEngine);
1185 				}
1186 
1187 				InvalidateAfterDraw(dirty);
1188 			}
1189 		}
1190 
1191 		/// <summary>
1192 		/// Do not call. Use DrawActiveShape.
1193 		/// </summary>
BeforeDraw()1194 		private void BeforeDraw()
1195 		{
1196 			//Clear the ToolLayer if it was used previously (e.g. for hover points when there was no active shape).
1197 			PintaCore.Workspace.ActiveDocument.ToolLayer.Clear();
1198 
1199 			//Invalidate the old hover point bounds, if any.
1200 			if (last_hover != null)
1201 			{
1202 				PintaCore.Workspace.Invalidate(last_hover.Value.ToGdkRectangle());
1203 
1204 				last_hover = null;
1205 			}
1206 
1207 			//Check to see if a new shape is selected.
1208 			if (prev_selected_shape_index != SelectedShapeIndex)
1209 			{
1210 				//A new shape is selected, so clear the previous dirty Rectangle.
1211 				last_dirty = null;
1212 
1213 				prev_selected_shape_index = SelectedShapeIndex;
1214 			}
1215 		}
1216 
1217 		/// <summary>
1218 		/// Do not call. Use DrawActiveShape.
1219 		/// </summary>
DrawTemporaryHoverPoint()1220 		private void DrawTemporaryHoverPoint()
1221 		{
1222 			Document doc = PintaCore.Workspace.ActiveDocument;
1223 
1224 			//Since there is no active ShapeEngine, the ToolLayer's surface will be used to draw the hover point on.
1225 			using (Context g = new Context(doc.ToolLayer.Surface))
1226 			{
1227 				g.AppendPath(doc.Selection.SelectionPath);
1228 				g.FillRule = FillRule.EvenOdd;
1229 				g.Clip();
1230 
1231 				CalculateHoverPoint();
1232 
1233 				//Draw the hover point. Note: the hover point has its own invalidation.
1234 				DrawHoverPoint(g);
1235 			}
1236 
1237 			doc.ToolLayer.Hidden = false;
1238 		}
1239 
1240 		/// <summary>
1241 		/// Do not call. Use DrawActiveShape.
1242 		/// </summary>
1243 		/// <param name="engine"></param>
1244 		/// <param name="dirty"></param>
1245 		/// <param name="shiftKey"></param>
DrawFinalized(ShapeEngine engine, bool createHistoryItem, bool shiftKey)1246 		private Rectangle DrawFinalized(ShapeEngine engine, bool createHistoryItem, bool shiftKey)
1247 		{
1248 			Document doc = PintaCore.Workspace.ActiveDocument;
1249 
1250 			//Finalize the shape onto the CurrentUserLayer.
1251 
1252 			ImageSurface undoSurface = null;
1253 
1254 			if (createHistoryItem)
1255 			{
1256 				//We only need to create a history item if there was a previous shape.
1257 				if (engine.ControlPoints.Count > 0)
1258 				{
1259 					undoSurface = doc.CurrentUserLayer.Surface.Clone();
1260 				}
1261 			}
1262 
1263 			//Draw the finalized shape.
1264 			Rectangle dirty = DrawShape(engine, doc.CurrentUserLayer, false, false);
1265 
1266 			if (createHistoryItem)
1267 			{
1268 				//Make sure that the undo surface isn't null.
1269 				if (undoSurface != null)
1270 				{
1271 					//Create a new ShapesHistoryItem so that the finalization of the shape can be undone.
1272 					doc.History.PushNewItem(new ShapesHistoryItem(this, owner.Icon, ShapeName + " " + Catalog.GetString("Finalized"),
1273 						undoSurface, doc.CurrentUserLayer, SelectedPointIndex, SelectedShapeIndex, false));
1274 				}
1275 			}
1276 
1277 			return dirty;
1278 		}
1279 
1280 		/// <summary>
1281 		/// Do not call. Use DrawActiveShape.
1282 		/// </summary>
1283 		/// <param name="engine"></param>
1284 		/// <param name="dirty"></param>
1285 		/// <param name="drawHoverSelection"></param>
1286 		/// <param name="shiftKey"></param>
DrawUnfinalized(ShapeEngine engine, bool drawHoverSelection, bool shiftKey)1287 		private Rectangle DrawUnfinalized(ShapeEngine engine, bool drawHoverSelection, bool shiftKey)
1288 		{
1289 			//Not finalizing the shape; drawing it on the temporary DrawingLayer.
1290 
1291 			//Calculate the hover point unless told otherwise.
1292 			if (drawHoverSelection)
1293 			{
1294 				CalculateHoverPoint();
1295 			}
1296 			else
1297 			{
1298 				//Do not draw the hover point. Instead, reset the hover point. NOTE: this is necessary even though the hover point
1299 				//is reset later. It affects the DrawShape call.
1300 				hover_point = new PointD(-1d, -1d);
1301 				hovered_pt_as_control_pt = -1;
1302 			}
1303 
1304 			//Draw the shape onto the temporary DrawingLayer.
1305 			Rectangle dirty = DrawShape(engine, engine.DrawingLayer.Layer, true, drawHoverSelection);
1306 
1307 			//Reset the hover point after each drawing.
1308 			hover_point = new PointD(-1d, -1d);
1309 			hovered_pt_as_control_pt = -1;
1310 
1311 			return dirty;
1312 		}
1313 
1314 		/// <summary>
1315 		/// Calculate the hover point, if any. Result is stored in hover_point.
1316 		/// </summary>
CalculateHoverPoint()1317 		private void CalculateHoverPoint()
1318 		{
1319 			if (SEngines.Count > 0)
1320 			{
1321 				hover_point = new PointD(-1d, -1d);
1322 
1323 				int closestCPIndex, closestCPShapeIndex;
1324 				ControlPoint closestControlPoint;
1325 				double closestCPDistance;
1326 
1327 				SEngines.FindClosestControlPoint(current_point,
1328 					out closestCPShapeIndex, out closestCPIndex, out closestControlPoint, out closestCPDistance);
1329 
1330 				int closestShapeIndex, closestPointIndex;
1331 				PointD closestPoint;
1332 				double closestDistance;
1333 
1334 				OrganizedPointCollection.FindClosestPoint(SEngines, current_point,
1335 					out closestShapeIndex, out closestPointIndex, out closestPoint, out closestDistance);
1336 
1337 				double currentClickRange = ShapeClickStartingRange + BrushWidth * ShapeClickThicknessFactor;
1338 
1339 				List<ControlPoint> controlPoints = SEngines[closestShapeIndex].ControlPoints;
1340 
1341 				//Determine if the closest ControlPoint is within the expected click range.
1342 				if (closestControlPoint != null && closestCPDistance < currentClickRange)
1343 				{
1344 					//User clicked directly on a ControlPoint on a shape.
1345 
1346 					hover_point.X = closestControlPoint.Position.X;
1347 					hover_point.Y = closestControlPoint.Position.Y;
1348 					hovered_pt_as_control_pt = closestCPIndex;
1349 				}
1350 				else if (closestDistance < currentClickRange) //Determine if the user is hovering the mouse close enough to a shape.
1351 				{
1352 					//User is hovering over a generated point on a shape.
1353 
1354 					if (controlPoints.Count > closestPointIndex)
1355 					{
1356 						//Note: compare the currentPoint's distance here because it's the actual mouse position.
1357 						if (current_point.Distance(controlPoints[closestPointIndex].Position) < currentClickRange)
1358 						{
1359 							//Mouse hovering over a control point (on the "previous order" side of the point).
1360 
1361 							hover_point.X = controlPoints[closestPointIndex].Position.X;
1362 							hover_point.Y = controlPoints[closestPointIndex].Position.Y;
1363 							hovered_pt_as_control_pt = closestPointIndex;
1364 						}
1365 						else if (closestPointIndex > 0)
1366 						{
1367 							if (current_point.Distance(controlPoints[closestPointIndex - 1].Position) < currentClickRange)
1368 							{
1369 								//Mouse hovering over a control point (on the "following order" side of the point).
1370 
1371 								hover_point.X = controlPoints[closestPointIndex - 1].Position.X;
1372 								hover_point.Y = controlPoints[closestPointIndex - 1].Position.Y;
1373 								hovered_pt_as_control_pt = closestPointIndex - 1;
1374 							}
1375 						}
1376 						else if (controlPoints.Count > 0 && current_point.Distance(controlPoints[controlPoints.Count - 1].Position) < currentClickRange)
1377 						{
1378 							//Mouse hovering over a control point (on the "following order" side of the point).
1379 
1380 							hovered_pt_as_control_pt = controlPoints.Count - 1;
1381 							hover_point.X = controlPoints[hovered_pt_as_control_pt].Position.X;
1382 							hover_point.Y = controlPoints[hovered_pt_as_control_pt].Position.Y;
1383 						}
1384 					}
1385 
1386 					if (hover_point.X < 0d)
1387 					{
1388 						hover_point.X = closestPoint.X;
1389 						hover_point.Y = closestPoint.Y;
1390 					}
1391 				}
1392 			}
1393 		}
1394 
1395 		/// <summary>
1396 		/// Do not call. Use DrawActiveShape.
1397 		/// </summary>
1398 		/// <param name="engine"></param>
OrganizePoints(ShapeEngine engine)1399 		private void OrganizePoints(ShapeEngine engine)
1400 		{
1401 			Document doc = PintaCore.Workspace.ActiveDocument;
1402 
1403 			//Organize the generated points for quick mouse interaction detection.
1404 
1405 			//First, clear the previously organized points, if any.
1406 			engine.OrganizedPoints.ClearCollection();
1407 
1408 			foreach (GeneratedPoint gp in engine.GeneratedPoints)
1409 			{
1410 				//For each generated point on the shape, calculate the spatial hashing for it and then store this information for later usage.
1411 				engine.OrganizedPoints.StoreAndOrganizePoint(new OrganizedPoint(new PointD(gp.Position.X, gp.Position.Y), gp.ControlPointIndex));
1412 			}
1413 		}
1414 
InvalidateAfterDraw(Rectangle dirty)1415 		private void InvalidateAfterDraw(Rectangle dirty)
1416 		{
1417 			Document doc = PintaCore.Workspace.ActiveDocument;
1418 
1419 			//Inflate to accomodate for previously drawn control points, if any.
1420 			int inflate = (int)(last_control_pt_size * 8d);
1421 			dirty = dirty.Inflate(inflate, inflate);
1422 
1423 			// Increase the size of the dirty rect to account for antialiasing.
1424 			if (owner.UseAntialiasing)
1425 			{
1426 				dirty = dirty.Inflate(1, 1);
1427 			}
1428 
1429 			//Combine, clamp, and invalidate the dirty Rectangle.
1430 			dirty = ((Rectangle?)dirty).UnionRectangles(last_dirty).Value;
1431 			dirty = dirty.Clamp();
1432 			doc.Workspace.Invalidate(dirty.ToGdkRectangle());
1433 
1434 			last_dirty = dirty;
1435 		}
1436 
1437 
DrawShape(ShapeEngine engine, Layer l, bool drawCP, bool drawHoverSelection)1438 		protected Rectangle DrawShape(ShapeEngine engine, Layer l, bool drawCP, bool drawHoverSelection)
1439 		{
1440 			Document doc = PintaCore.Workspace.ActiveDocument;
1441 
1442 			Rectangle? dirty = null;
1443 
1444 			ShapeEngine activeEngine = ActiveShapeEngine;
1445 
1446 			if (activeEngine != null)
1447 			{
1448 				using (Context g = new Context(l.Surface))
1449 				{
1450 					g.AppendPath(doc.Selection.SelectionPath);
1451 					g.FillRule = FillRule.EvenOdd;
1452 					g.Clip();
1453 
1454 					g.Antialias = activeEngine.AntiAliasing ? Antialias.Subpixel : Antialias.None;
1455 
1456 					g.SetDash(DashPatternBox.GenerateDashArray(activeEngine.DashPattern, activeEngine.BrushWidth), 0.0);
1457 
1458 					g.LineWidth = activeEngine.BrushWidth;
1459 
1460 					//Draw the shape.
1461 					if (activeEngine.ControlPoints.Count > 0)
1462 					{
1463 						//Generate the points that make up the shape.
1464 						activeEngine.GeneratePoints(activeEngine.BrushWidth);
1465 
1466                         PointD[] points = activeEngine.GetActualPoints ();
1467 
1468 						//Expand the invalidation rectangle as necessary.
1469 
1470 						if (FillShape)
1471 						{
1472                             Color fill_color = StrokeShape ? activeEngine.FillColor : activeEngine.OutlineColor;
1473                             dirty = dirty.UnionRectangles (g.FillPolygonal (points, fill_color));
1474 						}
1475 
1476 						if (StrokeShape)
1477 						{
1478 							dirty = dirty.UnionRectangles(g.DrawPolygonal(points, activeEngine.OutlineColor));
1479 						}
1480 					}
1481 
1482 					g.SetDash(new double[] { }, 0.0);
1483 
1484 					//Draw anything extra (that not every shape has), like arrows.
1485 					DrawExtras(ref dirty, g, engine);
1486 
1487 					if (drawCP)
1488 					{
1489 						DrawControlPoints(g, drawHoverSelection);
1490 					}
1491 				}
1492 			}
1493 
1494 
1495 			return dirty ?? new Rectangle(0d, 0d, 0d, 0d);
1496 		}
1497 
DrawControlPoints(Context g, bool drawHoverSelection)1498 		protected void DrawControlPoints(Context g, bool drawHoverSelection)
1499 		{
1500 			ShapeEngine activeEngine = ActiveShapeEngine;
1501 
1502 			if (activeEngine != null)
1503 			{
1504 				last_control_pt_size = Math.Min(activeEngine.BrushWidth + 1, 3);
1505 			}
1506 			else
1507 			{
1508 				last_control_pt_size = Math.Min(BrushWidth + 1, 3);
1509 			}
1510 
1511 			double controlPointOffset = (double)last_control_pt_size / 2d;
1512 
1513 			if (activeEngine != null)
1514 			{
1515 				//Draw the control points for the active shape.
1516 
1517 				if (drawHoverSelection)
1518 				{
1519 					ControlPoint selPoint = SelectedPoint;
1520 
1521 					if (selPoint != null)
1522 					{
1523 						//Draw a ring around the selected point.
1524 						g.FillStrokedEllipse(
1525 							new Rectangle(
1526 								selPoint.Position.X - controlPointOffset * 4d,
1527 								selPoint.Position.Y - controlPointOffset * 4d,
1528 								controlPointOffset * 8d, controlPointOffset * 8d),
1529 							ToolControl.FillColor, ToolControl.StrokeColor, 1);
1530 					}
1531 				}
1532 
1533 				List<ControlPoint> controlPoints = activeEngine.ControlPoints;
1534 
1535 				//Determine if the shape has one or more points.
1536 				if (controlPoints.Count > 0)
1537 				{
1538 					//Draw the control points for the shape.
1539 					for (int i = 0; i < controlPoints.Count; ++i)
1540 					{
1541 						//Skip drawing the hovered control point.
1542 						if (drawHoverSelection && hovered_pt_as_control_pt > -1 && hover_point.Distance(controlPoints[i].Position) < 1d)
1543 						{
1544 							continue;
1545 						}
1546 
1547 						//Draw each control point.
1548 						g.FillStrokedEllipse(
1549 							new Rectangle(
1550 								controlPoints[i].Position.X - controlPointOffset,
1551 								controlPoints[i].Position.Y - controlPointOffset,
1552 								last_control_pt_size, last_control_pt_size),
1553 							ToolControl.FillColor, ToolControl.StrokeColor, (int)last_control_pt_size);
1554 					}
1555 				}
1556 
1557 				if (drawHoverSelection)
1558 				{
1559 					//Draw the hover point.
1560 					DrawHoverPoint(g);
1561 				}
1562 			}
1563 		}
1564 
1565 		/// <summary>
1566 		/// Draws the hover point, if any.
1567 		/// </summary>
1568 		/// <param name="g"></param>
DrawHoverPoint(Context g)1569 		protected void DrawHoverPoint(Context g)
1570 		{
1571 			ShapeEngine activeEngine = ActiveShapeEngine;
1572 
1573 			if (activeEngine != null)
1574 			{
1575 				last_control_pt_size = Math.Min(activeEngine.BrushWidth + 1, 5);
1576 			}
1577 			else
1578 			{
1579 				last_control_pt_size = Math.Min(BrushWidth + 1, 5);
1580 			}
1581 
1582 			double controlPointOffset = (double)last_control_pt_size / 2d;
1583 
1584 			//Verify that the user isn't changing the tension of a control point and that there is a hover point to draw.
1585 			if (!changing_tension && hover_point.X > -1d)
1586 			{
1587 				Rectangle hoverOuterEllipseRect = new Rectangle(
1588 					hover_point.X - controlPointOffset * 3d, hover_point.Y - controlPointOffset * 3d,
1589 					controlPointOffset * 6d, controlPointOffset * 6d);
1590 
1591 				g.FillStrokedEllipse(hoverOuterEllipseRect, hover_color, hover_color, 1);
1592 
1593 				g.FillStrokedEllipse(new Rectangle(
1594 					hover_point.X - controlPointOffset, hover_point.Y - controlPointOffset,
1595 					last_control_pt_size, last_control_pt_size), hover_color, hover_color, (int)last_control_pt_size);
1596 
1597 
1598 				hoverOuterEllipseRect = hoverOuterEllipseRect.Inflate(1, 1);
1599 
1600 				//Since the hover point can be outside of the active shape's bounds (hovering over a different shape), a special
1601 				//invalidation call needs to be made for the hover point in order to ensure its visibility at all times.
1602 				PintaCore.Workspace.Invalidate(hoverOuterEllipseRect.ToGdkRectangle());
1603 
1604 				last_hover = hoverOuterEllipseRect;
1605 				last_hover = last_hover.Value.Clamp();
1606 			}
1607 		}
1608 
1609 
1610 		/// <summary>
1611 		/// Go through every editable shape and draw it.
1612 		/// </summary>
DrawAllShapes()1613 		public void DrawAllShapes()
1614 		{
1615 			Document doc = PintaCore.Workspace.ActiveDocument;
1616 
1617 			//Store the SelectedShapeIndex value for later restoration.
1618 			int previousToolSI = SelectedShapeIndex;
1619 
1620 			//Draw all of the shapes.
1621 			for (SelectedShapeIndex = 0; SelectedShapeIndex < SEngines.Count; ++SelectedShapeIndex)
1622 			{
1623 				//Only draw the selected point for the selected shape.
1624 				DrawActiveShape(true, false, previousToolSI == SelectedShapeIndex, false, true);
1625 			}
1626 
1627 			//Restore the previous SelectedShapeIndex value.
1628 			SelectedShapeIndex = previousToolSI;
1629 
1630 			//Determine if the currently active tool matches the shape's corresponding tool, and if not, switch to it.
1631 			BaseEditEngine.ActivateCorrespondingTool(SelectedShapeIndex, false);
1632 
1633 			//The currently active tool should now match the shape's corresponding tool.
1634 		}
1635 
1636 		/// <summary>
1637 		/// Go through every editable shape not yet finalized and finalize it.
1638 		/// </summary>
FinalizeAllShapes()1639 		protected void FinalizeAllShapes()
1640 		{
1641             if (SEngines.Count == 0)
1642                 return;
1643 
1644 			Document doc = PintaCore.Workspace.ActiveDocument;
1645 
1646 			ImageSurface undoSurface = doc.CurrentUserLayer.Surface.Clone();
1647 
1648 			int previousSelectedPointIndex = SelectedPointIndex;
1649 
1650 			Rectangle? dirty = null;
1651 
1652 			//Finalize all of the shapes.
1653 			for (SelectedShapeIndex = 0; SelectedShapeIndex < SEngines.Count; ++SelectedShapeIndex)
1654 			{
1655 				//Get a reference to each shape's corresponding tool.
1656 				ShapeTool correspondingTool = GetCorrespondingTool(SEngines[SelectedShapeIndex].ShapeType);
1657 
1658 				if (correspondingTool != null)
1659 				{
1660 					//Finalize the now active shape using its corresponding tool's EditEngine.
1661 
1662 					BaseEditEngine correspondingEngine = correspondingTool.EditEngine;
1663 
1664 					correspondingEngine.SelectedShapeIndex = SelectedShapeIndex;
1665 
1666 					correspondingEngine.BeforeDraw();
1667 
1668 					//Clear any temporary drawing, because something new will be drawn.
1669 					SEngines[SelectedShapeIndex].DrawingLayer.Layer.Clear();
1670 
1671 					//Draw the current shape with the corresponding tool's EditEngine.
1672 					dirty = dirty.UnionRectangles((Rectangle?)correspondingEngine.DrawFinalized(
1673 						SEngines[SelectedShapeIndex], false, false));
1674 				}
1675 			}
1676 
1677 			//Make sure that the undo surface isn't null.
1678 			if (undoSurface != null)
1679 			{
1680 				//Create a new ShapesHistoryItem so that the finalization of the shapes can be undone.
1681 				doc.History.PushNewItem(new ShapesHistoryItem(this, owner.Icon, Catalog.GetString("Finalized"),
1682 					undoSurface, doc.CurrentUserLayer, previousSelectedPointIndex, prev_selected_shape_index, true));
1683 			}
1684 
1685 			if (dirty.HasValue)
1686 			{
1687 				InvalidateAfterDraw(dirty.Value);
1688 			}
1689 
1690             // Ensure the ToolLayer gets hidden now that we're done with it
1691             doc.ToolLayer.Hidden = true;
1692 
1693 			//Clear out all of the data.
1694 			ResetShapes();
1695 		}
1696 
1697 		/// <summary>
1698 		/// Constrain the current point to snap to fixed angles from the previous point, or to
1699         /// produce a square / circle when drawing those shape types.
1700 		/// </summary>
CalculateModifiedCurrentPoint()1701 		protected void CalculateModifiedCurrentPoint()
1702 		{
1703 			ShapeEngine selEngine = SelectedShapeEngine;
1704 
1705 			//Don't bother calculating a modified point if there is no selected shape.
1706 			if (selEngine != null)
1707 			{
1708 				if (ShapeType != ShapeTypes.OpenLineCurveSeries && selEngine.ControlPoints.Count == 4) {
1709 					// Constrain to a square / circle.
1710 					var origin = selEngine.ControlPoints [(SelectedPointIndex + 2) % 4].Position;
1711 
1712 					var dx = current_point.X - origin.X;
1713 					var dy = current_point.Y - origin.Y;
1714 					var length = Math.Max (Math.Abs (dx), Math.Abs (dy));
1715 					dx = length * Math.Sign (dx);
1716 					dy = length * Math.Sign (dy);
1717 					current_point = new PointD (origin.X + dx, origin.Y + dy);
1718                 } else {
1719 					// Calculate the modified position of currentPoint such that the angle between the adjacent point
1720 					// (if any) and currentPoint is snapped to the closest angle out of a certain number of angles.
1721 					ControlPoint adjacentPoint;
1722 
1723 					if (SelectedPointIndex > 0) {
1724 						//Previous point.
1725 						adjacentPoint = selEngine.ControlPoints [SelectedPointIndex - 1];
1726 					} else if (selEngine.ControlPoints.Count > 1) {
1727 						//Previous point (looping around to the end) if there is more than 1 point.
1728 						adjacentPoint = selEngine.ControlPoints [selEngine.ControlPoints.Count - 1];
1729 					} else {
1730 						//Don't bother calculating a modified point because there is no reference point to align it with (there is only 1 point).
1731 						return;
1732 					}
1733 
1734 					PointD dir = new PointD (current_point.X - adjacentPoint.Position.X, current_point.Y - adjacentPoint.Position.Y);
1735 					double theta = Math.Atan2 (dir.Y, dir.X);
1736 					double len = Math.Sqrt (dir.X * dir.X + dir.Y * dir.Y);
1737 
1738 					theta = Math.Round (12 * theta / Math.PI) * Math.PI / 12;
1739 					current_point = new PointD ((adjacentPoint.Position.X + len * Math.Cos (theta)), (adjacentPoint.Position.Y + len * Math.Sin (theta)));
1740 				}
1741 			}
1742 		}
1743 
1744 		/// <summary>
1745 		/// Resets the editable data.
1746 		/// </summary>
ResetShapes()1747 		protected void ResetShapes()
1748 		{
1749 			SEngines = new ShapeEngineCollection();
1750 
1751 			//The fields are modified instead of the properties here because a redraw call is undesired (for speed/efficiency).
1752 			SelectedPointIndex = -1;
1753 			SelectedShapeIndex = -1;
1754 
1755 			is_drawing = false;
1756 
1757 			last_dirty = null;
1758 		}
1759 
1760 		/// <summary>
1761 		/// Activates the corresponding tool to the given shapeIndex value if the tool is not already active, and then returns the previous tool
1762 		/// if a tool switch has occurred or null otherwise. If a switch did occur and this was called in e.g. an event handler, it should most
1763 		/// likely pass the event data on to the newly activated tool (accessing it using PintaCore.Tools.CurrentTool) and then return.
1764 		/// </summary>
1765 		/// <param name="shapeIndex">The index of the shape in SEngines to find the corresponding tool to and switch to.</param>
1766 		/// <param name="permanentSwitch">Whether the tool switch is permanent or just temporary (for drawing).</param>
1767 		/// <returns>The *previous* tool if a tool switch has occurred or null otherwise.</returns>
ActivateCorrespondingTool(int shapeIndex, bool permanentSwitch)1768 		public static ShapeTool ActivateCorrespondingTool(int shapeIndex, bool permanentSwitch)
1769 		{
1770 			//First make sure that there is a validly selectable tool.
1771 			if (shapeIndex > -1 && SEngines.Count > shapeIndex)
1772 			{
1773 				return ActivateCorrespondingTool(SEngines[shapeIndex].ShapeType, permanentSwitch);
1774 			}
1775 
1776 			//Let the caller know that the active tool has not been switched.
1777 			return null;
1778 		}
1779 
1780 		/// <summary>
1781 		/// Activates the corresponding tool to the given shapeType value if the tool is not already active, and then returns the previous tool
1782 		/// if a tool switch has occurred or null otherwise. If a switch did occur and this was called in e.g. an event handler, it should most
1783 		/// likely pass the event data on to the newly activated tool (accessing it using PintaCore.Tools.CurrentTool) and then return.
1784 		/// </summary>
1785 		/// <param name="shapeType">The index of the shape in SEngines to find the corresponding tool to and switch to.</param>
1786 		/// <param name="permanentSwitch">Whether the tool switch is permanent or just temporary (for drawing).</param>
1787 		/// <returns>The *previous* tool if a tool switch has occurred or null otherwise.</returns>
ActivateCorrespondingTool(ShapeTypes shapeType, bool permanentSwitch)1788 		public static ShapeTool ActivateCorrespondingTool(ShapeTypes shapeType, bool permanentSwitch)
1789 		{
1790 			ShapeTool correspondingTool = GetCorrespondingTool(shapeType);
1791 
1792 			//Verify that the corresponding tool is valid and that it doesn't match the currently active tool.
1793 			if (correspondingTool != null && PintaCore.Tools.CurrentTool != correspondingTool)
1794 			{
1795 				ShapeTool oldTool = PintaCore.Tools.CurrentTool as ShapeTool;
1796 
1797 				//The active tool needs to be switched to the corresponding tool.
1798 				PintaCore.Tools.SetCurrentTool(correspondingTool);
1799 
1800 				ShapeTool newTool = (ShapeTool)PintaCore.Tools.CurrentTool;
1801 
1802 				//What happens next depends on whether the old tool was an editable ShapeTool.
1803 				if (oldTool != null && oldTool.IsEditableShapeTool)
1804 				{
1805 					if (permanentSwitch)
1806 					{
1807 						//Set the new tool's active shape and point to the old shape and point.
1808 						newTool.EditEngine.SelectedPointIndex = oldTool.EditEngine.SelectedPointIndex;
1809 						newTool.EditEngine.SelectedShapeIndex = oldTool.EditEngine.SelectedShapeIndex;
1810 
1811 						//Make sure neither tool thinks it is drawing anything.
1812 						newTool.EditEngine.is_drawing = false;
1813 						oldTool.EditEngine.is_drawing = false;
1814 					}
1815 
1816 					ShapeEngine activeEngine = newTool.EditEngine.ActiveShapeEngine;
1817 
1818 					if (activeEngine != null)
1819 					{
1820 						newTool.EditEngine.UpdateToolbarSettings(activeEngine);
1821 					}
1822 				}
1823 				else
1824 				{
1825 					if (permanentSwitch)
1826 					{
1827 						//Make sure that the new tool doesn't think it is drawing anything.
1828 						newTool.EditEngine.is_drawing = false;
1829 					}
1830 				}
1831 
1832 				//Let the caller know that the active tool has been switched.
1833 				return oldTool;
1834 			}
1835 
1836 			//Let the caller know that the active tool has not been switched.
1837 			return null;
1838 		}
1839 
1840 		/// <summary>
1841 		/// Gets the corresponding tool to the given shape type and then returns that tool.
1842 		/// </summary>
1843 		/// <param name="ShapeType">The shape type to find the corresponding tool to.</param>
1844 		/// <returns>The corresponding tool to the given shape type.</returns>
GetCorrespondingTool(ShapeTypes shapeType)1845 		public static ShapeTool GetCorrespondingTool(ShapeTypes shapeType)
1846 		{
1847 			ShapeTool correspondingTool = null;
1848 
1849 			//Get the corresponding BaseTool reference to the shape type.
1850 			CorrespondingTools.TryGetValue(shapeType, out correspondingTool);
1851 
1852 			return correspondingTool;
1853 		}
1854 
1855 
1856 		/// <summary>
1857 		/// Copy the given shape's settings to the toolbar settings. Calls StorePreviousSettings.
1858 		/// </summary>
1859 		/// <param name="engine"></param>
UpdateToolbarSettings(ShapeEngine engine)1860 		public virtual void UpdateToolbarSettings(ShapeEngine engine)
1861 		{
1862 			if (engine != null)
1863 			{
1864 				owner.UseAntialiasing = engine.AntiAliasing;
1865 
1866 				//Update the DashPatternBox to represent the current shape's DashPattern.
1867 				(dash_pattern_box.comboBox.ComboBox as Gtk.ComboBoxEntry).Entry.Text = engine.DashPattern;
1868 
1869 				OutlineColor = engine.OutlineColor.Clone();
1870 				FillColor = engine.FillColor.Clone();
1871 
1872 				BrushWidth = engine.BrushWidth;
1873 
1874 				StorePreviousSettings();
1875 			}
1876 		}
1877 
1878 		/// <summary>
1879 		/// Copy the previous settings to the toolbar settings.
1880 		/// </summary>
RecallPreviousSettings()1881 		protected virtual void RecallPreviousSettings()
1882 		{
1883 			if (dash_pattern_box.comboBox != null)
1884 			{
1885 				(dash_pattern_box.comboBox.ComboBox as Gtk.ComboBoxEntry).Entry.Text = prev_dash_pattern;
1886 			}
1887 
1888 			owner.UseAntialiasing = prev_antialiasing;
1889 			BrushWidth = prev_brush_width;
1890 		}
1891 
1892 		/// <summary>
1893 		/// Copy the toolbar settings to the previous settings.
1894 		/// </summary>
StorePreviousSettings()1895 		protected virtual void StorePreviousSettings()
1896 		{
1897 			if (dash_pattern_box.comboBox != null)
1898 			{
1899 				prev_dash_pattern = (dash_pattern_box.comboBox.ComboBox as Gtk.ComboBoxEntry).Entry.Text;
1900 			}
1901 
1902 			prev_antialiasing = owner.UseAntialiasing;
1903 			prev_brush_width = BrushWidth;
1904 		}
1905 
1906 		/// <summary>
1907 		/// Creates a new shape, adds its starting points, and returns it.
1908 		/// </summary>
1909 		/// <param name="ctrlKey"></param>
1910 		/// <param name="clickedOnControlPoint"></param>
1911 		/// <param name="prevSelPoint"></param>
CreateShape(bool ctrlKey, bool clickedOnControlPoint, PointD prevSelPoint)1912 		protected abstract ShapeEngine CreateShape(bool ctrlKey, bool clickedOnControlPoint, PointD prevSelPoint);
1913 
MovePoint(List<ControlPoint> controlPoints)1914         protected virtual void MovePoint(List<ControlPoint> controlPoints)
1915         {
1916 			//Update the control point's position.
1917 			controlPoints.ElementAt(SelectedPointIndex).Position = new PointD(current_point.X, current_point.Y);
1918         }
1919 
1920 		protected virtual void DrawExtras(ref Rectangle? dirty, Context g, ShapeEngine engine)
1921         {
1922 
1923         }
1924 
AddLinePoints(bool ctrlKey, bool clickedOnControlPoint, ShapeEngine selEngine, PointD prevSelPoint)1925 		protected void AddLinePoints(bool ctrlKey, bool clickedOnControlPoint, ShapeEngine selEngine, PointD prevSelPoint)
1926 		{
1927 			PointD startingPoint;
1928 
1929 			//Create the initial points of the shape. The second point will follow the mouse around until released.
1930 			if (ctrlKey && clickedOnControlPoint)
1931 			{
1932 				startingPoint = prevSelPoint;
1933 
1934 				clicked_without_modifying = false;
1935 			}
1936 			else
1937 			{
1938 				startingPoint = shape_origin;
1939 			}
1940 
1941 
1942 			selEngine.ControlPoints.Add(new ControlPoint(new PointD(startingPoint.X, startingPoint.Y), DefaultEndPointTension));
1943 			selEngine.ControlPoints.Add(
1944 				new ControlPoint(new PointD(startingPoint.X + .01d, startingPoint.Y + .01d), DefaultEndPointTension));
1945 
1946 
1947 			SelectedPointIndex = 1;
1948 			SelectedShapeIndex = SEngines.Count - 1;
1949 		}
1950 
AddRectanglePoints(bool ctrlKey, bool clickedOnControlPoint, ShapeEngine selEngine, PointD prevSelPoint)1951 		protected void AddRectanglePoints(bool ctrlKey, bool clickedOnControlPoint, ShapeEngine selEngine, PointD prevSelPoint)
1952 		{
1953 			PointD startingPoint;
1954 
1955 			//Create the initial points of the shape. The second point will follow the mouse around until released.
1956 			if (ctrlKey && clickedOnControlPoint)
1957 			{
1958 				startingPoint = prevSelPoint;
1959 
1960 				clicked_without_modifying = false;
1961 			}
1962 			else
1963 			{
1964 				startingPoint = shape_origin;
1965 			}
1966 
1967 
1968 			selEngine.ControlPoints.Add(new ControlPoint(new PointD(startingPoint.X, startingPoint.Y), 0.0));
1969 			selEngine.ControlPoints.Add(
1970 				new ControlPoint(new PointD(startingPoint.X, startingPoint.Y + .01d), 0.0));
1971 			selEngine.ControlPoints.Add(
1972 				new ControlPoint(new PointD(startingPoint.X + .01d, startingPoint.Y + .01d), 0.0));
1973 			selEngine.ControlPoints.Add(
1974 				new ControlPoint(new PointD(startingPoint.X + .01d, startingPoint.Y), 0.0));
1975 
1976 
1977 			SelectedPointIndex = 2;
1978 			SelectedShapeIndex = SEngines.Count - 1;
1979 		}
1980 
MoveRectangularPoint(List<ControlPoint> controlPoints)1981 		protected void MoveRectangularPoint(List<ControlPoint> controlPoints)
1982 		{
1983 			ShapeEngine selEngine = SelectedShapeEngine;
1984 
1985 			if (selEngine != null && selEngine.Closed && controlPoints.Count == 4)
1986 			{
1987 				//Figure out the indeces of the surrounding points. The lowest point index should be 0 and the highest 3.
1988 
1989 				int previousPointIndex = SelectedPointIndex - 1;
1990 				int nextPointIndex = SelectedPointIndex + 1;
1991 				int oppositePointIndex = SelectedPointIndex + 2;
1992 
1993 				if (previousPointIndex < 0)
1994 				{
1995 					previousPointIndex = controlPoints.Count - 1;
1996 				}
1997 
1998 				if (nextPointIndex >= controlPoints.Count)
1999 				{
2000 					nextPointIndex = 0;
2001 					oppositePointIndex = 1;
2002 				}
2003 				else if (oppositePointIndex >= controlPoints.Count)
2004 				{
2005 					oppositePointIndex = 0;
2006 				}
2007 
2008 
2009 				ControlPoint previousPoint = controlPoints.ElementAt(previousPointIndex);
2010 				ControlPoint oppositePoint = controlPoints.ElementAt(oppositePointIndex);
2011 				ControlPoint nextPoint = controlPoints.ElementAt(nextPointIndex);
2012 
2013 
2014 				//Now that we know the indexed order of the points, we can align everything properly.
2015 				if (SelectedPointIndex == 2 || SelectedPointIndex == 0)
2016 				{
2017 					//Control point visual order (counter-clockwise order always goes selectedPoint, previousPoint, oppositePoint, nextPoint,
2018 					//where moving point == selectedPoint):
2019 					//
2020 					//static (opposite) point		horizontally aligned point
2021 					//vertically aligned point		moving point
2022 					//OR
2023 					//moving point					vertically aligned point
2024 					//horizontally aligned point	static (opposite) point
2025 
2026 
2027 					//Update the previous control point's position.
2028 					previousPoint.Position = new PointD(previousPoint.Position.X, current_point.Y);
2029 
2030 					//Update the next control point's position.
2031 					nextPoint.Position = new PointD(current_point.X, nextPoint.Position.Y);
2032 
2033 
2034 					//Even though it's supposed to be static, just in case the points get out of order
2035 					//(they do sometimes), update the opposite control point's position.
2036 					oppositePoint.Position = new PointD(previousPoint.Position.X, nextPoint.Position.Y);
2037 				}
2038 				else
2039 				{
2040 					//Control point visual order (counter-clockwise order always goes selectedPoint, previousPoint, oppositePoint, nextPoint,
2041 					//where moving point == selectedPoint):
2042 					//
2043 					//horizontally aligned point	static (opposite) point
2044 					//moving point					vertically aligned point
2045 					//OR
2046 					//vertically aligned point		moving point
2047 					//static (opposite) point		horizontally aligned point
2048 
2049 
2050 					//Update the previous control point's position.
2051 					previousPoint.Position = new PointD(current_point.X, previousPoint.Position.Y);
2052 
2053 					//Update the next control point's position.
2054 					nextPoint.Position = new PointD(nextPoint.Position.X, current_point.Y);
2055 
2056 
2057 					//Even though it's supposed to be static, just in case the points get out of order
2058 					//(they do sometimes), update the opposite control point's position.
2059 					oppositePoint.Position = new PointD(nextPoint.Position.X, previousPoint.Position.Y);
2060 				}
2061 			}
2062 		}
2063     }
2064 }
2065