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