1 //
2 // PaintBrushTool.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 Pinta.Core;
31 using Mono.Unix;
32 
33 using Pinta.Tools.Brushes;
34 
35 namespace Pinta.Tools
36 {
37 	public class PaintBrushTool : BaseBrushTool
38 	{
39 		#region Properties
40 		public override string Name { get { return Catalog.GetString ("Paintbrush"); } }
41 		public override string Icon { get { return "Tools.Paintbrush.png"; } }
42 		public override string StatusBarText { get { return Catalog.GetString ("Left click to draw with primary color, right click to draw with secondary color."); } }
43 
44 		public override Gdk.Cursor DefaultCursor {
45 			get {
46 				int iconOffsetX, iconOffsetY;
47 				var icon = CreateIconWithShape ("Cursor.Paintbrush.png",
48 				                                CursorShape.Ellipse, BrushWidth, 8, 24,
49 				                                out iconOffsetX, out iconOffsetY);
50                 return new Gdk.Cursor (Gdk.Display.Default, icon, iconOffsetX, iconOffsetY);
51 			}
52 		}
53 		public override bool CursorChangesOnZoom { get { return true; } }
54 
55 		public override Gdk.Key ShortcutKey { get { return Gdk.Key.B; } }
56 		public override int Priority { get { return 25; } }
57 		#endregion
58 
59 		private BasePaintBrush default_brush;
60 		private BasePaintBrush active_brush;
61 		private ToolBarLabel brush_label;
62 		private ToolBarComboBox brush_combo_box;
63 		private Color stroke_color;
64         private Point last_point;
65 
OnActivated()66 		protected override void OnActivated ()
67 		{
68 			base.OnActivated ();
69 
70 			PintaCore.PaintBrushes.BrushAdded += HandleBrushAddedOrRemoved;
71 			PintaCore.PaintBrushes.BrushRemoved += HandleBrushAddedOrRemoved;
72 		}
73 
OnDeactivated(BaseTool newTool)74 		protected override void OnDeactivated (BaseTool newTool)
75 		{
76 			base.OnDeactivated (newTool);
77 
78 			PintaCore.PaintBrushes.BrushAdded -= HandleBrushAddedOrRemoved;
79 			PintaCore.PaintBrushes.BrushRemoved -= HandleBrushAddedOrRemoved;
80 		}
81 
OnBuildToolBar(Toolbar tb)82 		protected override void OnBuildToolBar (Toolbar tb)
83 		{
84 			base.OnBuildToolBar (tb);
85 
86 			// Change the cursor when the BrushWidth is changed.
87 			brush_width.ComboBox.Changed += (sender, e) => SetCursor (DefaultCursor);
88 
89 			tb.AppendItem (new Gtk.SeparatorToolItem ());
90 
91 			if (brush_label == null)
92 				brush_label = new ToolBarLabel (string.Format (" {0}:  ", Catalog.GetString ("Type")));
93 
94 			if (brush_combo_box == null) {
95 				brush_combo_box = new ToolBarComboBox (100, 0, false);
96 				brush_combo_box.ComboBox.Changed += (o, e) => {
97 					Gtk.TreeIter iter;
98 					if (brush_combo_box.ComboBox.GetActiveIter (out iter)) {
99 						active_brush = (BasePaintBrush)brush_combo_box.Model.GetValue (iter, 1);
100 					} else {
101 						active_brush = default_brush;
102 					}
103 				};
104 
105 				RebuildBrushComboBox ();
106 			}
107 
108 			tb.AppendItem (brush_label);
109 			tb.AppendItem (brush_combo_box);
110 		}
111 
112 		/// <summary>
113 		/// Rebuild the list of brushes when a brush is added or removed.
114 		/// </summary>
HandleBrushAddedOrRemoved(object sender, BrushEventArgs e)115 		private void HandleBrushAddedOrRemoved (object sender, BrushEventArgs e)
116 		{
117 			RebuildBrushComboBox ();
118 		}
119 
120 		/// <summary>
121 		/// Rebuild the list of brushes.
122 		/// </summary>
RebuildBrushComboBox()123 		private void RebuildBrushComboBox ()
124 		{
125 			brush_combo_box.Model.Clear ();
126 			default_brush = null;
127 
128 			foreach (var brush in PintaCore.PaintBrushes) {
129 				if (default_brush == null)
130 					default_brush = (BasePaintBrush)brush;
131 				brush_combo_box.Model.AppendValues (brush.Name, brush);
132 			}
133 
134 			brush_combo_box.ComboBox.Active = 0;
135 		}
136 
137 		#region Mouse Handlers
OnMouseDown(DrawingArea canvas, ButtonPressEventArgs args, PointD point)138 		protected override void OnMouseDown (DrawingArea canvas, ButtonPressEventArgs args, PointD point)
139 		{
140 			base.OnMouseDown (canvas, args, point);
141 			active_brush.DoMouseDown ();
142 		}
143 
OnMouseUp(DrawingArea canvas, ButtonReleaseEventArgs args, PointD point)144 		protected override void OnMouseUp (DrawingArea canvas, ButtonReleaseEventArgs args, PointD point)
145 		{
146 			base.OnMouseUp (canvas, args, point);
147 			active_brush.DoMouseUp ();
148 		}
149 
OnMouseMove(object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point)150 		protected override void OnMouseMove (object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point)
151 		{
152 			Document doc = PintaCore.Workspace.ActiveDocument;
153 
154 			if (mouse_button == 1) {
155 				stroke_color = PintaCore.Palette.PrimaryColor;
156 			} else if (mouse_button == 3) {
157 				stroke_color = PintaCore.Palette.SecondaryColor;
158 			} else {
159 				last_point = point_empty;
160 				return;
161 			}
162 
163 			// TODO: also multiply by pressure
164 			stroke_color = new Color (stroke_color.R, stroke_color.G, stroke_color.B,
165 				stroke_color.A * active_brush.StrokeAlphaMultiplier);
166 
167 			int x = (int)point.X;
168 			int y = (int)point.Y;
169 
170 			if (last_point.Equals (point_empty))
171 				last_point = new Point (x, y);
172 
173 			if (doc.Workspace.PointInCanvas (point))
174 				surface_modified = true;
175 
176 			var surf = doc.CurrentUserLayer.Surface;
177 			var invalidate_rect = Gdk.Rectangle.Zero;
178 			var brush_width = BrushWidth;
179 
180 			using (var g = new Context (surf)) {
181 				g.AppendPath (doc.Selection.SelectionPath);
182 				g.FillRule = FillRule.EvenOdd;
183 				g.Clip ();
184 
185 				g.Antialias = UseAntialiasing ? Antialias.Subpixel : Antialias.None;
186 				g.LineWidth = brush_width;
187 				g.LineJoin = LineJoin.Round;
188 				g.LineCap = BrushWidth == 1 ? LineCap.Butt : LineCap.Round;
189 				g.SetSourceColor (stroke_color);
190 
191                 invalidate_rect = active_brush.DoMouseMove (g, stroke_color, surf,
192 				                                            x, y, last_point.X, last_point.Y);
193 			}
194 
195 			// If we draw partially offscreen, Cairo gives us a bogus
196 			// dirty rectangle, so redraw everything.
197 			if (doc.Workspace.IsPartiallyOffscreen (invalidate_rect)) {
198 				doc.Workspace.Invalidate ();
199 			} else {
200 				doc.Workspace.Invalidate (doc.ClampToImageSize (invalidate_rect));
201 			}
202 
203 			last_point = new Point (x, y);
204 		}
205 		#endregion
206 	}
207 }
208