1 //
2 // PintaCanvas.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 Gdk;
29 using Gtk;
30 using Pinta.Core;
31 
32 namespace Pinta.Gui.Widgets
33 {
34 	[System.ComponentModel.ToolboxItem (true)]
35 	public class PintaCanvas : DrawingArea
36 	{
37 		Cairo.ImageSurface canvas;
38 		CanvasRenderer cr;
39 
40 		private Document document;
41 		private int hScrollAmount = 92;
42 
43 		public CanvasWindow CanvasWindow { get; private set; }
44 
PintaCanvas(CanvasWindow window, Document document)45 		public PintaCanvas (CanvasWindow window, Document document)
46 		{
47 			CanvasWindow = window;
48 			this.document = document;
49 
50 			cr = new CanvasRenderer (true);
51 
52 			// Keep the widget the same size as the canvas
53 			document.Workspace.CanvasSizeChanged += delegate (object sender, EventArgs e) {
54 				SetRequisition (document.Workspace.CanvasSize);
55 			};
56 
57 			// Update the canvas when the image changes
58 			document.Workspace.CanvasInvalidated += delegate (object sender, CanvasInvalidatedEventArgs e) {
59 				// If GTK+ hasn't created the canvas window yet, no need to invalidate it
60 				if (GdkWindow == null)
61 					return;
62 
63 				if (e.EntireSurface)
64 					GdkWindow.Invalidate ();
65 				else
66 					GdkWindow.InvalidateRect (e.Rectangle, false);
67 			};
68 
69 			// Give mouse press events to the current tool
70 			ButtonPressEvent += delegate (object sender, ButtonPressEventArgs e) {
71 				// The canvas gets the button press before the tab system, so
72 				// if this click is on a canvas that isn't currently the ActiveDocument yet,
73 				// we need to go ahead and make it the active document for the tools
74 				// to use it, even though right after this the tab system would have switched it
75 				if (PintaCore.Workspace.ActiveDocument != document)
76 					PintaCore.Workspace.SetActiveDocument (document);
77 
78 				PintaCore.Tools.CurrentTool.DoMouseDown (this, e, document.Workspace.WindowPointToCanvas (e.Event.X, e.Event.Y));
79 			};
80 
81 			// Give mouse release events to the current tool
82 			ButtonReleaseEvent += delegate (object sender, ButtonReleaseEventArgs e) {
83 				PintaCore.Tools.CurrentTool.DoMouseUp (this, e, document.Workspace.WindowPointToCanvas (e.Event.X, e.Event.Y));
84 			};
85 
86 			// Give mouse move events to the current tool
87 			MotionNotifyEvent += delegate (object sender, MotionNotifyEventArgs e) {
88 				var point = document.Workspace.WindowPointToCanvas (e.Event.X, e.Event.Y);
89 
90 				if (document.Workspace.PointInCanvas (point))
91 					PintaCore.Chrome.LastCanvasCursorPoint = point.ToGdkPoint ();
92 
93 				if (PintaCore.Tools.CurrentTool != null)
94 					PintaCore.Tools.CurrentTool.DoMouseMove ((DrawingArea)sender, e, point);
95 			};
96 		}
97 
98 		#region Protected Methods
OnExposeEvent(EventExpose e)99 		protected override bool OnExposeEvent (EventExpose e)
100 		{
101 			base.OnExposeEvent (e);
102 
103 			var scale = document.Workspace.Scale;
104 
105 			var x = (int)document.Workspace.Offset.X;
106 			var y = (int)document.Workspace.Offset.Y;
107 
108 			// Translate our expose area for the whole drawingarea to just our canvas
109 			var canvas_bounds = new Rectangle (x, y, document.Workspace.CanvasSize.Width, document.Workspace.CanvasSize.Height);
110 			canvas_bounds.Intersect (e.Area);
111 
112 			if (canvas_bounds.IsEmpty)
113 				return true;
114 
115 			canvas_bounds.X -= x;
116 			canvas_bounds.Y -= y;
117 
118 			// Resize our offscreen surface to a surface the size of our drawing area
119 			if (canvas == null || canvas.Width != canvas_bounds.Width || canvas.Height != canvas_bounds.Height) {
120 				if (canvas != null)
121 					(canvas as IDisposable).Dispose ();
122 
123 				canvas = CairoExtensions.CreateImageSurface (Cairo.Format.Argb32, canvas_bounds.Width, canvas_bounds.Height);
124 			}
125 
126 			cr.Initialize (document.ImageSize, document.Workspace.CanvasSize);
127 
128 			using (var g = CairoHelper.Create (GdkWindow)) {
129 				// Draw our canvas drop shadow
130 				g.DrawRectangle (new Cairo.Rectangle (x - 1, y - 1, document.Workspace.CanvasSize.Width + 2, document.Workspace.CanvasSize.Height + 2), new Cairo.Color (.5, .5, .5), 1);
131 				g.DrawRectangle (new Cairo.Rectangle (x - 2, y - 2, document.Workspace.CanvasSize.Width + 4, document.Workspace.CanvasSize.Height + 4), new Cairo.Color (.8, .8, .8), 1);
132 				g.DrawRectangle (new Cairo.Rectangle (x - 3, y - 3, document.Workspace.CanvasSize.Width + 6, document.Workspace.CanvasSize.Height + 6), new Cairo.Color (.9, .9, .9), 1);
133 
134 				// Set up our clip rectangle
135 				g.Rectangle (new Cairo.Rectangle (x, y, document.Workspace.CanvasSize.Width, document.Workspace.CanvasSize.Height));
136 				g.Clip ();
137 
138 				g.Translate (x, y);
139 
140 				// Render all the layers to a surface
141 				var layers = document.GetLayersToPaint ();
142 
143 				if (layers.Count == 0)
144 					canvas.Clear ();
145 
146 				cr.Render (layers, canvas, canvas_bounds.Location);
147 
148 				// Paint the surface to our canvas
149 				g.SetSourceSurface (canvas, canvas_bounds.X + (int)(0 * scale), canvas_bounds.Y + (int)(0 * scale));
150 				g.Paint ();
151 
152 				// Selection outline
153 				if (document.Selection.Visible) {
154 					bool fillSelection = PintaCore.Tools.CurrentTool.Name.Contains ("Select") &&
155 					                     !PintaCore.Tools.CurrentTool.Name.Contains ("Selected");
156 					document.Selection.Draw (g, scale, fillSelection);
157 				}
158 			}
159 
160 			return true;
161 		}
162 
OnScrollEvent(EventScroll evnt)163 		protected override bool OnScrollEvent (EventScroll evnt)
164 		{
165 			// Allow the user to zoom in/out with Ctrl-Mousewheel
166 			if (evnt.State.FilterModifierKeys () == ModifierType.ControlMask) {
167 				switch (evnt.Direction) {
168 					case ScrollDirection.Down:
169 					case ScrollDirection.Right:
170 						document.Workspace.ZoomOutFromMouseScroll (new Cairo.PointD (evnt.X, evnt.Y));
171 						return true;
172 					case ScrollDirection.Left:
173 					case ScrollDirection.Up:
174 						document.Workspace.ZoomInFromMouseScroll (new Cairo.PointD (evnt.X, evnt.Y));
175 						return true;
176 				}
177 			}
178 
179 			// Allow the user to scroll left/right with Shift-Mousewheel
180 			if (evnt.State.FilterModifierKeys () == ModifierType.ShiftMask) {
181 				switch (evnt.Direction) {
182 					case ScrollDirection.Down:
183 					case ScrollDirection.Right:
184 						document.Workspace.ScrollCanvas (hScrollAmount, 0);
185 						return true;
186 					case ScrollDirection.Up:
187 					case ScrollDirection.Left:
188 						document.Workspace.ScrollCanvas (-hScrollAmount, 0);
189 						return true;
190 				}
191 			}
192 
193 			return base.OnScrollEvent (evnt);
194 		}
195 		#endregion
196 
197 		#region Private Methods
SetRequisition(Size size)198 		private void SetRequisition (Size size)
199 		{
200 			Requisition req = new Requisition ();
201 			req.Width = size.Width;
202 			req.Height = size.Height;
203 			Requisition = req;
204 
205 			QueueResize ();
206 		}
207 
DoKeyPressEvent(object o, KeyPressEventArgs e)208 		public void DoKeyPressEvent (object o, KeyPressEventArgs e)
209 		{
210 			// Give the current tool a chance to handle the key press
211 			PintaCore.Tools.CurrentTool.DoKeyPress (this, e);
212 		}
213 
DoKeyReleaseEvent(object o, KeyReleaseEventArgs e)214 		public void DoKeyReleaseEvent (object o, KeyReleaseEventArgs e)
215 		{
216 			PintaCore.Tools.CurrentTool.DoKeyRelease (this, e);
217 		}
218 		#endregion
219 	}
220 }
221