1 // 2 // BaseTool.cs 3 // 4 // Author: 5 // Jonathan Pobst <monkey@jpobst.com> 6 // 7 // Copyright (c) 2010 Jonathan Pobst 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 Gtk; 30 using Gdk; 31 using System.IO; 32 using Mono.Unix; 33 using Mono.Addins; 34 using System.Collections.Generic; 35 using Pinta; 36 37 namespace Pinta.Core 38 { MouseHandler(double x, double y, Gdk.ModifierType state)39 public delegate void MouseHandler (double x, double y, Gdk.ModifierType state); 40 41 [TypeExtensionPoint] 42 public abstract class BaseTool 43 { 44 public const int DEFAULT_BRUSH_WIDTH = 2; 45 46 protected static Cairo.Point point_empty = new Cairo.Point (-500, -500); 47 48 49 protected ToggleToolButton tool_item; 50 protected ToolItem tool_label; 51 protected ToolItem tool_image; 52 protected ToolItem tool_sep; 53 protected ToolBarDropDownButton antialiasing_button; 54 private ToolBarItem aaOn, aaOff; 55 protected ToolBarDropDownButton alphablending_button; 56 private ToolBarItem abOn, abOff; 57 public event MouseHandler MouseMoved; 58 public event MouseHandler MousePressed; 59 public event MouseHandler MouseReleased; 60 61 public Cursor CurrentCursor { get; private set; } 62 BaseTool()63 protected BaseTool () 64 { 65 CurrentCursor = DefaultCursor; 66 67 PintaCore.Workspace.ActiveDocumentChanged += Workspace_ActiveDocumentChanged; 68 } 69 BaseTool()70 static BaseTool () 71 { 72 Gtk.IconFactory fact = new Gtk.IconFactory (); 73 fact.Add ("Toolbar.AntiAliasingEnabledIcon.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Toolbar.AntiAliasingEnabledIcon.png"))); 74 fact.Add ("Toolbar.AntiAliasingDisabledIcon.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Toolbar.AntiAliasingDisabledIcon.png"))); 75 fact.Add ("Toolbar.BlendingEnabledIcon.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Toolbar.BlendingEnabledIcon.png"))); 76 fact.Add ("Toolbar.BlendingOverwriteIcon.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Toolbar.BlendingOverwriteIcon.png"))); 77 fact.Add ("Tools.FreeformShape.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Tools.FreeformShape.png"))); 78 fact.AddDefault (); 79 } 80 81 public virtual string Name { get { throw new ApplicationException ("Tool didn't override Name"); } } 82 public virtual string Icon { get { throw new ApplicationException ("Tool didn't override Icon"); } } 83 public virtual string ToolTip { get { throw new ApplicationException ("Tool didn't override ToolTip"); } } 84 public virtual string StatusBarText { get { return string.Empty; } } 85 public virtual ToggleToolButton ToolItem { get { if (tool_item == null) tool_item = CreateToolButton (); return tool_item; } } 86 public virtual bool Enabled { get { return true; } } 87 public virtual Gdk.Cursor DefaultCursor { get { return null; } } 88 public virtual Gdk.Key ShortcutKey { get { return (Gdk.Key)0; } } 89 90 //Whether or not the tool is an editable ShapeTool. 91 public bool IsEditableShapeTool = false; 92 93 public virtual bool UseAntialiasing 94 { 95 get 96 { 97 return (antialiasing_button != null) && 98 ShowAntialiasingButton && 99 (bool)antialiasing_button.SelectedItem.Tag; 100 } 101 102 set 103 { 104 if (!ShowAntialiasingButton || antialiasing_button == null) 105 return; 106 107 antialiasing_button.SelectedItem = value ? aaOn : aaOff; 108 } 109 } 110 111 public virtual bool UseAlphaBlending 112 { 113 get 114 { 115 return alphablending_button != null && 116 ShowAlphaBlendingButton && 117 (bool)alphablending_button.SelectedItem.Tag; 118 } 119 set 120 { 121 if (!ShowAlphaBlendingButton || alphablending_button == null) 122 return; 123 124 alphablending_button.SelectedItem = value ? abOn : abOff; 125 } 126 } 127 128 public virtual int Priority { get { return 75; } } 129 130 public virtual bool CursorChangesOnZoom { get { return false; } } 131 132 protected virtual bool ShowAntialiasingButton { get { return false; } } 133 protected virtual bool ShowAlphaBlendingButton { get { return false; } } 134 135 #region Public Methods DoMouseMove(object o, MotionNotifyEventArgs args, Cairo.PointD point)136 public void DoMouseMove (object o, MotionNotifyEventArgs args, Cairo.PointD point) 137 { 138 if (MouseMoved != null) 139 MouseMoved (point.X, point.Y, args.Event.State); 140 OnMouseMove (o, args, point); 141 } 142 DoBuildToolBar(Toolbar tb)143 public void DoBuildToolBar (Toolbar tb) 144 { 145 OnBuildToolBar (tb); 146 BuildRasterizationToolItems (tb); 147 } 148 DoClearToolBar(Toolbar tb)149 public void DoClearToolBar (Toolbar tb) 150 { 151 OnClearToolBar (tb); 152 } 153 DoMouseDown(DrawingArea canvas, ButtonPressEventArgs args, Cairo.PointD point)154 public void DoMouseDown (DrawingArea canvas, ButtonPressEventArgs args, Cairo.PointD point) 155 { 156 if (MousePressed != null) 157 MousePressed (point.X, point.Y, args.Event.State); 158 OnMouseDown (canvas, args, point); 159 } 160 DoMouseUp(DrawingArea canvas, ButtonReleaseEventArgs args, Cairo.PointD point)161 public void DoMouseUp (DrawingArea canvas, ButtonReleaseEventArgs args, Cairo.PointD point) 162 { 163 if (MouseReleased != null) 164 MouseReleased (point.X, point.Y, args.Event.State); 165 OnMouseUp (canvas, args, point); 166 } 167 DoCommit()168 public void DoCommit () 169 { 170 OnCommit (); 171 } 172 DoAfterSave()173 public void DoAfterSave() 174 { 175 AfterSave(); 176 } 177 DoActivated()178 public void DoActivated () 179 { 180 OnActivated (); 181 } 182 DoDeactivated(BaseTool newTool)183 public void DoDeactivated (BaseTool newTool) 184 { 185 OnDeactivated(newTool); 186 } 187 188 // Return true if the key was consumed. DoKeyPress(DrawingArea canvas, KeyPressEventArgs args)189 public void DoKeyPress (DrawingArea canvas, KeyPressEventArgs args) 190 { 191 OnKeyDown (canvas, args); 192 } 193 DoKeyRelease(DrawingArea canvas, KeyReleaseEventArgs args)194 public void DoKeyRelease (DrawingArea canvas, KeyReleaseEventArgs args) 195 { 196 OnKeyUp (canvas, args); 197 } 198 TryHandlePaste(Clipboard cb)199 public virtual bool TryHandlePaste (Clipboard cb) 200 { 201 return false; 202 } 203 TryHandleCut(Clipboard cb)204 public virtual bool TryHandleCut (Clipboard cb) 205 { 206 return false; 207 } 208 TryHandleCopy(Clipboard cb)209 public virtual bool TryHandleCopy (Clipboard cb) 210 { 211 return false; 212 } 213 TryHandleUndo()214 public virtual bool TryHandleUndo () 215 { 216 return false; 217 } 218 TryHandleRedo()219 public virtual bool TryHandleRedo () 220 { 221 return false; 222 } 223 AfterUndo()224 public virtual void AfterUndo() 225 { 226 227 } 228 AfterRedo()229 public virtual void AfterRedo() 230 { 231 232 } 233 234 #endregion 235 236 #region Protected Methods OnMouseMove(object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point)237 protected virtual void OnMouseMove (object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point) 238 { 239 } 240 BuildRasterizationToolItems(Toolbar tb)241 protected virtual void BuildRasterizationToolItems (Toolbar tb) 242 { 243 if (ShowAlphaBlendingButton || ShowAntialiasingButton) 244 tb.AppendItem (new SeparatorToolItem ()); 245 246 if (ShowAntialiasingButton) 247 BuildAntialiasingTool (tb); 248 if (ShowAlphaBlendingButton) 249 BuildAlphaBlending(tb); 250 251 AfterBuildRasterization(); 252 } 253 AfterBuildRasterization()254 protected virtual void AfterBuildRasterization() 255 { 256 257 } 258 OnBuildToolBar(Toolbar tb)259 protected virtual void OnBuildToolBar (Toolbar tb) 260 { 261 if (tool_label == null) 262 tool_label = new ToolBarLabel (string.Format (" {0}: ", Catalog.GetString ("Tool"))); 263 264 tb.AppendItem (tool_label); 265 266 if (tool_image == null) 267 tool_image = new ToolBarImage (Icon); 268 269 tb.AppendItem (tool_image); 270 271 if (tool_sep == null) 272 tool_sep = new SeparatorToolItem (); 273 274 tb.AppendItem (tool_sep); 275 } 276 OnClearToolBar(Toolbar tb)277 protected virtual void OnClearToolBar (Toolbar tb) 278 { 279 while (tb.NItems > 0) 280 tb.Remove (tb.Children[tb.NItems - 1]); 281 } 282 OnMouseDown(DrawingArea canvas, Gtk.ButtonPressEventArgs args, Cairo.PointD point)283 protected virtual void OnMouseDown (DrawingArea canvas, Gtk.ButtonPressEventArgs args, Cairo.PointD point) 284 { 285 } 286 OnMouseUp(DrawingArea canvas, Gtk.ButtonReleaseEventArgs args, Cairo.PointD point)287 protected virtual void OnMouseUp (DrawingArea canvas, Gtk.ButtonReleaseEventArgs args, Cairo.PointD point) 288 { 289 } 290 OnKeyDown(DrawingArea canvas, Gtk.KeyPressEventArgs args)291 protected virtual void OnKeyDown (DrawingArea canvas, Gtk.KeyPressEventArgs args) 292 { 293 } 294 OnKeyUp(DrawingArea canvas, Gtk.KeyReleaseEventArgs args)295 protected virtual void OnKeyUp (DrawingArea canvas, Gtk.KeyReleaseEventArgs args) 296 { 297 } 298 299 /// <summary> 300 /// This is called whenever a menu option is called, for 301 /// tools that are in a temporary state while being used, and 302 /// need to commit their work when another option is selected. 303 /// </summary> OnCommit()304 protected virtual void OnCommit () 305 { 306 } 307 AfterSave()308 protected virtual void AfterSave() 309 { 310 311 } 312 OnActivated()313 protected virtual void OnActivated () 314 { 315 SetCursor (DefaultCursor); 316 } 317 OnDeactivated(BaseTool newTool)318 protected virtual void OnDeactivated(BaseTool newTool) 319 { 320 SetCursor (null); 321 } 322 CreateToolButton()323 protected virtual ToggleToolButton CreateToolButton () 324 { 325 Gtk.Image i2 = new Gtk.Image (PintaCore.Resources.GetIcon (Icon)); 326 i2.Show (); 327 328 ToggleToolButton tool_item = new ToggleToolButton (); 329 tool_item.IconWidget = i2; 330 tool_item.Show (); 331 tool_item.Label = Name; 332 333 if (ShortcutKey != (Gdk.Key)0) 334 tool_item.TooltipText = string.Format ("{0}\n{2}: {1}\n\n{3}", Name, ShortcutKey.ToString ().ToUpperInvariant (), Catalog.GetString ("Shortcut key"), StatusBarText); 335 else 336 tool_item.TooltipText = Name; 337 338 return tool_item; 339 } 340 SetCursor(Gdk.Cursor cursor)341 public void SetCursor (Gdk.Cursor cursor) 342 { 343 CurrentCursor = cursor; 344 345 if (PintaCore.Workspace.HasOpenDocuments && PintaCore.Workspace.ActiveWorkspace.Canvas.GdkWindow != null) 346 PintaCore.Workspace.ActiveWorkspace.Canvas.GdkWindow.Cursor = cursor; 347 } 348 349 /// <summary> 350 /// Create a cursor icon with a shape that visually represents the tool's thickness. 351 /// </summary> 352 /// <param name="imgName">A string containing the name of the tool's icon image to use.</param> 353 /// <param name="shape">The shape to draw.</param> 354 /// <param name="shapeWidth">The width of the shape.</param> 355 /// <param name="imgToShapeX">The horizontal distance between the image's top-left corner and the shape center.</param> 356 /// <param name="imgToShapeY">The verical distance between the image's top-left corner and the shape center.</param> 357 /// <param name="shapeX">The X position in the returned Pixbuf that will be the center of the shape.</param> 358 /// <param name="shapeY">The Y position in the returned Pixbuf that will be the center of the shape.</param> 359 /// <returns>The new cursor icon with an shape that represents the tool's thickness.</returns> CreateIconWithShape(string imgName, CursorShape shape, int shapeWidth, int imgToShapeX, int imgToShapeY, out int shapeX, out int shapeY)360 protected Gdk.Pixbuf CreateIconWithShape(string imgName, CursorShape shape, int shapeWidth, 361 int imgToShapeX, int imgToShapeY, 362 out int shapeX, out int shapeY) 363 { 364 Gdk.Pixbuf img = PintaCore.Resources.GetIcon(imgName); 365 366 double zoom = 1d; 367 if (PintaCore.Workspace.HasOpenDocuments) 368 { 369 zoom = Math.Min(30d, PintaCore.Workspace.ActiveDocument.Workspace.Scale); 370 } 371 372 shapeWidth = (int)Math.Min(800d, ((double)shapeWidth) * zoom); 373 int halfOfShapeWidth = shapeWidth / 2; 374 375 // Calculate bounding boxes around the both image and shape 376 // relative to the image top-left corner. 377 Gdk.Rectangle imgBBox = new Gdk.Rectangle(0, 0, img.Width, img.Height); 378 Gdk.Rectangle shapeBBox = new Gdk.Rectangle( 379 imgToShapeX - halfOfShapeWidth, 380 imgToShapeY - halfOfShapeWidth, 381 shapeWidth, 382 shapeWidth); 383 384 // Inflate shape bounding box to allow for anti-aliasing 385 shapeBBox.Inflate(2, 2); 386 387 // To determine required size of icon, 388 // find union of the image and shape bounding boxes 389 // (still relative to image top-left corner) 390 Gdk.Rectangle iconBBox = imgBBox.Union (shapeBBox); 391 392 // Image top-left corner in icon co-ordinates 393 int imgX = imgBBox.Left - iconBBox.Left; 394 int imgY = imgBBox.Top - iconBBox.Top; 395 396 // Shape center point in icon co-ordinates 397 shapeX = imgToShapeX - iconBBox.Left; 398 shapeY = imgToShapeY - iconBBox.Top; 399 400 using (ImageSurface i = CairoExtensions.CreateImageSurface (Format.ARGB32, iconBBox.Width, iconBBox.Height)) { 401 using (Context g = new Context (i)) { 402 // Don't show shape if shapeWidth less than 3, 403 if (shapeWidth > 3) { 404 int diam = Math.Max (1, shapeWidth - 2); 405 Cairo.Rectangle shapeRect = new Cairo.Rectangle (shapeX - halfOfShapeWidth, 406 shapeY - halfOfShapeWidth, 407 diam, 408 diam); 409 410 Cairo.Color outerColor = new Cairo.Color (255, 255, 255, 0.75); 411 Cairo.Color innerColor = new Cairo.Color (0, 0, 0); 412 413 switch (shape) { 414 case CursorShape.Ellipse: 415 g.DrawEllipse (shapeRect, outerColor, 2); 416 shapeRect = shapeRect.Inflate (-1, -1); 417 g.DrawEllipse (shapeRect, innerColor, 1); 418 break; 419 case CursorShape.Rectangle: 420 g.DrawRectangle (shapeRect, outerColor, 1); 421 shapeRect = shapeRect.Inflate (-1, -1); 422 g.DrawRectangle (shapeRect, innerColor, 1); 423 break; 424 } 425 } 426 427 // Draw the image 428 g.DrawPixbuf (img, new Cairo.Point (imgX, imgY)); 429 } 430 431 return CairoExtensions.ToPixbuf (i); 432 } 433 } 434 #endregion 435 436 #region Private Methods BuildAlphaBlending(Toolbar tb)437 private void BuildAlphaBlending (Toolbar tb) 438 { 439 if (alphablending_button != null) { 440 tb.AppendItem (alphablending_button); 441 return; 442 } 443 444 alphablending_button = new ToolBarDropDownButton (); 445 446 abOn = alphablending_button.AddItem (Catalog.GetString ("Normal Blending"), "Toolbar.BlendingEnabledIcon.png", true); 447 abOff = alphablending_button.AddItem (Catalog.GetString ("Overwrite"), "Toolbar.BlendingOverwriteIcon.png", false); 448 449 tb.AppendItem (alphablending_button); 450 } 451 BuildAntialiasingTool(Toolbar tb)452 private void BuildAntialiasingTool (Toolbar tb) 453 { 454 if (antialiasing_button != null) { 455 tb.AppendItem (antialiasing_button); 456 return; 457 } 458 459 antialiasing_button = new ToolBarDropDownButton (); 460 461 aaOn = antialiasing_button.AddItem (Catalog.GetString ("Antialiasing On"), "Toolbar.AntiAliasingEnabledIcon.png", true); 462 aaOff = antialiasing_button.AddItem (Catalog.GetString ("Antialiasing Off"), "Toolbar.AntiAliasingDisabledIcon.png", false); 463 464 tb.AppendItem (antialiasing_button); 465 } 466 Workspace_ActiveDocumentChanged(object sender, EventArgs e)467 private void Workspace_ActiveDocumentChanged (object sender, EventArgs e) 468 { 469 if (PintaCore.Tools.CurrentTool == this) 470 SetCursor (DefaultCursor); 471 } 472 #endregion 473 } 474 } 475