1 // 2 // DocumentWorkspace.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 Mono.Unix; 30 31 namespace Pinta.Core 32 { 33 public class DocumentWorkspace 34 { 35 private Document document; 36 private Size canvas_size; 37 private enum ZoomType 38 { 39 ZoomIn, 40 ZoomOut, 41 ZoomManually 42 } 43 DocumentWorkspace(Document document)44 internal DocumentWorkspace (Document document) 45 { 46 this.document = document; 47 History = new DocumentWorkspaceHistory (document); 48 } 49 50 #region Public Events 51 public event EventHandler<CanvasInvalidatedEventArgs> CanvasInvalidated; 52 public event EventHandler CanvasSizeChanged; 53 #endregion 54 55 #region Public Properties 56 public Gtk.DrawingArea Canvas { get; set; } 57 58 public bool CanvasFitsInWindow { 59 get { 60 Gtk.Viewport view = (Gtk.Viewport)Canvas.Parent; 61 62 int window_x = view.Allocation.Width; 63 int window_y = view.Children[0].Allocation.Height; 64 65 if (CanvasSize.Width <= window_x && CanvasSize.Height <= window_y) 66 return true; 67 68 return false; 69 } 70 } 71 72 public Size CanvasSize { 73 get { return canvas_size; } 74 set { 75 if (canvas_size.Width != value.Width || canvas_size.Height != value.Height) { 76 canvas_size = value; 77 OnCanvasSizeChanged (); 78 } 79 } 80 } 81 82 public DocumentWorkspaceHistory History { get; private set; } 83 84 public bool ImageFitsInWindow { 85 get { 86 Gtk.Viewport view = (Gtk.Viewport)Canvas.Parent; 87 88 int window_x = view.Allocation.Width; 89 int window_y = view.Children[0].Allocation.Height; 90 91 if (document.ImageSize.Width <= window_x && document.ImageSize.Height <= window_y) 92 return true; 93 94 return false; 95 } 96 } 97 98 public Cairo.PointD Offset { 99 get { return new Cairo.PointD ((Canvas.Allocation.Width - canvas_size.Width) / 2, (Canvas.Allocation.Height - canvas_size.Height) / 2); } 100 } 101 102 public double Scale { 103 get { return (double)CanvasSize.Width / (double)document.ImageSize.Width; } 104 set { 105 if (value != (double)CanvasSize.Width / (double)document.ImageSize.Width || value != (double)CanvasSize.Height / (double)document.ImageSize.Height) { 106 if (document.ImageSize.Width == 0) 107 { 108 document.ImageSize = new Size(1, document.ImageSize.Height); 109 } 110 111 if (document.ImageSize.Height == 0) 112 { 113 document.ImageSize = new Size(document.ImageSize.Width, 1); 114 } 115 116 int new_x = Math.Max ((int)(document.ImageSize.Width * value), 1); 117 int new_y = Math.Max ((int)(((long)new_x * document.ImageSize.Height) / document.ImageSize.Width), 1); 118 119 CanvasSize = new Gdk.Size (new_x, new_y); 120 Invalidate (); 121 122 if (PintaCore.Tools.CurrentTool.CursorChangesOnZoom) 123 { 124 //The current tool's cursor changes when the zoom changes. 125 PintaCore.Tools.CurrentTool.SetCursor(PintaCore.Tools.CurrentTool.DefaultCursor); 126 } 127 } 128 } 129 } 130 131 #endregion 132 133 #region Public Methods Invalidate()134 public void Invalidate () 135 { 136 OnCanvasInvalidated (new CanvasInvalidatedEventArgs ()); 137 } 138 139 /// <summary> 140 /// Repaints a rectangle region on the canvas. 141 /// </summary> 142 /// <param name='canvasRect'> 143 /// The rectangle region of the canvas requiring repainting 144 /// </param> Invalidate(Gdk.Rectangle canvasRect)145 public void Invalidate (Gdk.Rectangle canvasRect) 146 { 147 Cairo.PointD canvasTopLeft = new Cairo.PointD(canvasRect.Left, canvasRect.Top); 148 Cairo.PointD canvasBtmRight = new Cairo.PointD(canvasRect.Right + 1, canvasRect.Bottom + 1); 149 150 Cairo.PointD winTopLeft = CanvasPointToWindow(canvasTopLeft.X, canvasTopLeft.Y); 151 Cairo.PointD winBtmRight = CanvasPointToWindow(canvasBtmRight.X, canvasBtmRight.Y); 152 153 Gdk.Rectangle winRect = Utility.PointsToRectangle(winTopLeft, winBtmRight, false).ToGdkRectangle(); 154 155 OnCanvasInvalidated (new CanvasInvalidatedEventArgs (winRect)); 156 } 157 158 /// <summary> 159 /// Determines whether the rectangle lies (at least partially) outside the canvas area. 160 /// </summary> IsPartiallyOffscreen(Gdk.Rectangle rect)161 public bool IsPartiallyOffscreen (Gdk.Rectangle rect) 162 { 163 return (rect.IsEmpty || rect.Left < 0 || rect.Top < 0); 164 } 165 PointInCanvas(Cairo.PointD point)166 public bool PointInCanvas (Cairo.PointD point) 167 { 168 if (point.X < 0 || point.Y < 0) 169 return false; 170 171 if (point.X >= document.ImageSize.Width || point.Y >= document.ImageSize.Height) 172 return false; 173 174 return true; 175 } 176 RecenterView(double x, double y)177 public void RecenterView (double x, double y) 178 { 179 Gtk.Viewport view = (Gtk.Viewport)Canvas.Parent; 180 181 view.Hadjustment.Value = Utility.Clamp (x * Scale - view.Hadjustment.PageSize / 2, view.Hadjustment.Lower, view.Hadjustment.Upper); 182 view.Vadjustment.Value = Utility.Clamp (y * Scale - view.Vadjustment.PageSize / 2, view.Vadjustment.Lower, view.Vadjustment.Upper); 183 } 184 ScrollCanvas(int dx, int dy)185 public void ScrollCanvas (int dx, int dy) 186 { 187 Gtk.Viewport view = (Gtk.Viewport)Canvas.Parent; 188 189 view.Hadjustment.Value = Utility.Clamp (dx + view.Hadjustment.Value, view.Hadjustment.Lower, view.Hadjustment.Upper - view.Hadjustment.PageSize); 190 view.Vadjustment.Value = Utility.Clamp (dy + view.Vadjustment.Value, view.Vadjustment.Lower, view.Vadjustment.Upper - view.Vadjustment.PageSize); 191 } 192 193 /// <summary> 194 /// Converts a point from window coordinates to canvas coordinates 195 /// </summary> 196 /// <param name='x'> 197 /// The X coordinate of the window point 198 /// </param> 199 /// <param name='y'> 200 /// The Y coordinate of the window point 201 /// </param> WindowPointToCanvas(double x, double y)202 public Cairo.PointD WindowPointToCanvas (double x, double y) 203 { 204 ScaleFactor sf = new ScaleFactor (PintaCore.Workspace.ImageSize.Width, 205 PintaCore.Workspace.CanvasSize.Width); 206 Cairo.PointD pt = sf.ScalePoint (new Cairo.PointD (x - Offset.X, y - Offset.Y)); 207 return new Cairo.PointD(pt.X, pt.Y); 208 } 209 210 /// <summary> 211 /// Converts a point from canvas coordinates to window coordinates 212 /// </summary> 213 /// <param name='x'> 214 /// The X coordinate of the canvas point 215 /// </param> 216 /// <param name='y'> 217 /// The Y coordinate of the canvas point 218 /// </param> CanvasPointToWindow(double x, double y)219 public Cairo.PointD CanvasPointToWindow (double x, double y) 220 { 221 ScaleFactor sf = new ScaleFactor (PintaCore.Workspace.ImageSize.Width, 222 PintaCore.Workspace.CanvasSize.Width); 223 Cairo.PointD pt = sf.UnscalePoint (new Cairo.PointD (x, y)); 224 return new Cairo.PointD(pt.X + Offset.X, pt.Y + Offset.Y); 225 } 226 ZoomIn()227 public void ZoomIn () 228 { 229 ZoomAndRecenterView (ZoomType.ZoomIn, new Cairo.PointD (-1, -1)); // Zoom in relative to the center of the viewport. 230 } 231 ZoomOut()232 public void ZoomOut () 233 { 234 ZoomAndRecenterView (ZoomType.ZoomOut, new Cairo.PointD (-1, -1)); // Zoom out relative to the center of the viewport. 235 } 236 ZoomInFromMouseScroll(Cairo.PointD point)237 public void ZoomInFromMouseScroll (Cairo.PointD point) 238 { 239 ZoomAndRecenterView (ZoomType.ZoomIn, point); // Zoom in relative to mouse position. 240 } 241 ZoomOutFromMouseScroll(Cairo.PointD point)242 public void ZoomOutFromMouseScroll (Cairo.PointD point) 243 { 244 ZoomAndRecenterView (ZoomType.ZoomOut, point); // Zoom out relative to mouse position. 245 } 246 ZoomManually()247 public void ZoomManually () 248 { 249 ZoomAndRecenterView (ZoomType.ZoomManually, new Cairo.PointD (-1, -1)); 250 } 251 ZoomToRectangle(Cairo.Rectangle rect)252 public void ZoomToRectangle (Cairo.Rectangle rect) 253 { 254 double ratio; 255 256 if (document.ImageSize.Width / rect.Width <= document.ImageSize.Height / rect.Height) 257 ratio = document.ImageSize.Width / rect.Width; 258 else 259 ratio = document.ImageSize.Height / rect.Height; 260 261 (PintaCore.Actions.View.ZoomComboBox.ComboBox as Gtk.ComboBoxEntry).Entry.Text = ViewActions.ToPercent (ratio); 262 Gtk.Main.Iteration (); //Force update of scrollbar upper before recenter 263 RecenterView (rect.X + rect.Width / 2, rect.Y + rect.Height / 2); 264 } 265 #endregion 266 267 #region Private Methods OnCanvasInvalidated(CanvasInvalidatedEventArgs e)268 protected internal void OnCanvasInvalidated (CanvasInvalidatedEventArgs e) 269 { 270 if (CanvasInvalidated != null) 271 CanvasInvalidated (this, e); 272 } 273 OnCanvasSizeChanged()274 public void OnCanvasSizeChanged () 275 { 276 if (CanvasSizeChanged != null) 277 CanvasSizeChanged (this, EventArgs.Empty); 278 } 279 ZoomAndRecenterView(ZoomType zoomType, Cairo.PointD point)280 private void ZoomAndRecenterView (ZoomType zoomType, Cairo.PointD point) 281 { 282 if (zoomType == ZoomType.ZoomOut && (CanvasSize.Width == 1 || CanvasSize.Height ==1)) 283 return; //Can't zoom in past a 1x1 px canvas 284 285 double zoom; 286 287 if (!ViewActions.TryParsePercent (PintaCore.Actions.View.ZoomComboBox.ComboBox.ActiveText, out zoom)) 288 zoom = Scale * 100; 289 290 zoom = Math.Min (zoom, 3600); 291 292 if (Canvas.GdkWindow != null) 293 Canvas.GdkWindow.FreezeUpdates (); 294 295 PintaCore.Actions.View.SuspendZoomUpdate (); 296 297 Gtk.Viewport view = (Gtk.Viewport)Canvas.Parent; 298 299 bool adjustOnMousePosition = point.X >= 0.0 && point.Y >= 0.0; 300 301 double center_x = adjustOnMousePosition ? 302 point.X : view.Hadjustment.Value + (view.Hadjustment.PageSize / 2.0); 303 double center_y = adjustOnMousePosition ? 304 point.Y : view.Vadjustment.Value + (view.Vadjustment.PageSize / 2.0); 305 306 center_x = (center_x - Offset.X) / Scale; 307 center_y = (center_y - Offset.Y) / Scale; 308 309 if (zoomType == ZoomType.ZoomIn || zoomType == ZoomType.ZoomOut) { 310 int i = 0; 311 312 Predicate<string> UpdateZoomLevel = zoomInList => 313 { 314 double zoom_level; 315 if (!ViewActions.TryParsePercent (zoomInList, out zoom_level)) 316 return false; 317 318 switch (zoomType) { 319 case ZoomType.ZoomIn: 320 if (zoomInList == Catalog.GetString ("Window") || zoom_level <= zoom) { 321 PintaCore.Actions.View.ZoomComboBox.ComboBox.Active = i - 1; 322 return true; 323 } 324 325 break; 326 327 case ZoomType.ZoomOut: 328 if (zoomInList == Catalog.GetString ("Window")) 329 return true; 330 331 if (zoom_level < zoom) { 332 PintaCore.Actions.View.ZoomComboBox.ComboBox.Active = i; 333 return true; 334 } 335 336 break; 337 } 338 339 return false; 340 }; 341 342 foreach (string item in PintaCore.Actions.View.ZoomCollection) { 343 if (UpdateZoomLevel (item)) 344 break; 345 346 i++; 347 } 348 } 349 350 PintaCore.Actions.View.UpdateCanvasScale (); 351 352 // Quick fix : need to manually update Upper limit because the value is not changing after updating the canvas scale. 353 // TODO : I think there is an event need to be fired so that those values updated automatically. 354 view.Hadjustment.Upper = CanvasSize.Width < view.Hadjustment.PageSize ? view.Hadjustment.PageSize : CanvasSize.Width; 355 view.Vadjustment.Upper = CanvasSize.Height < view.Vadjustment.PageSize ? view.Vadjustment.PageSize : CanvasSize.Height; 356 357 RecenterView (center_x, center_y); 358 359 PintaCore.Actions.View.ResumeZoomUpdate (); 360 if (Canvas.GdkWindow != null) 361 Canvas.GdkWindow.ThawUpdates (); 362 } 363 #endregion 364 } 365 } 366