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