1 //
2 // ViewActions.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 System.Globalization;
29 using Mono.Unix;
30 using Gtk;
31 
32 namespace Pinta.Core
33 {
34 	public class ViewActions
35 	{
36 		public Gtk.Action ZoomIn { get; private set; }
37 		public Gtk.Action ZoomOut { get; private set; }
38 		public Gtk.Action ZoomToWindow { get; private set; }
39 		public Gtk.Action ZoomToSelection { get; private set; }
40 		public Gtk.Action ActualSize { get; private set; }
41         public Gtk.ToggleAction ToolBar { get; private set; }
42         public Gtk.ToggleAction ImageTabs { get; private set; }
43         public Gtk.ToggleAction PixelGrid { get; private set; }
44 		public Gtk.ToggleAction Rulers { get; private set; }
45 		public Gtk.RadioAction Pixels { get; private set; }
46 		public Gtk.RadioAction Inches { get; private set; }
47 		public Gtk.RadioAction Centimeters { get; private set; }
48 		public Gtk.Action Fullscreen { get; private set; }
49 
50 		public ToolBarComboBox ZoomComboBox { get; private set; }
51 		public string[] ZoomCollection { get; private set; }
52 
53 		private string old_zoom_text = "";
54 		private bool zoom_to_window_activated = false;
55 
56 		public bool ZoomToWindowActivated {
57 			get { return zoom_to_window_activated; }
58 			set
59 			{
60 				zoom_to_window_activated = value;
61 				old_zoom_text = ZoomComboBox.ComboBox.ActiveText;
62 			}
63 		}
64 
ViewActions()65 		public ViewActions ()
66 		{
67 			Gtk.IconFactory fact = new Gtk.IconFactory ();
68 			fact.Add ("Menu.View.ActualSize.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.ActualSize.png")));
69 			fact.Add ("Menu.View.Grid.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.Grid.png")));
70 			fact.Add ("Menu.View.Rulers.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.Rulers.png")));
71 			fact.Add ("Menu.View.ZoomIn.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.ZoomIn.png")));
72 			fact.Add ("Menu.View.ZoomOut.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.ZoomOut.png")));
73 			fact.Add ("Menu.View.ZoomToSelection.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.ZoomToSelection.png")));
74 			fact.Add ("Menu.View.ZoomToWindow.png", new Gtk.IconSet (PintaCore.Resources.GetIcon ("Menu.View.ZoomToWindow.png")));
75 			fact.AddDefault ();
76 
77 			ZoomIn = new Gtk.Action ("ZoomIn", Catalog.GetString ("Zoom In"), null, Stock.ZoomIn);
78 			ZoomOut = new Gtk.Action ("ZoomOut", Catalog.GetString ("Zoom Out"), null, Stock.ZoomOut);
79 			ZoomToWindow = new Gtk.Action ("ZoomToWindow", Catalog.GetString ("Best Fit"), null, Stock.ZoomFit);
80 			ZoomToSelection = new Gtk.Action ("ZoomToSelection", Catalog.GetString ("Zoom to Selection"), null, "Menu.View.ZoomToSelection.png");
81 			ActualSize = new Gtk.Action ("ActualSize", Catalog.GetString ("Normal Size"), null, Stock.Zoom100);
82             ToolBar = new Gtk.ToggleAction ("Toolbar", Catalog.GetString ("Toolbar"), null, null);
83             ImageTabs = new Gtk.ToggleAction ("ImageTabs", Catalog.GetString ("Image Tabs"), null, null);
84             PixelGrid = new Gtk.ToggleAction ("PixelGrid", Catalog.GetString ("Pixel Grid"), null, "Menu.View.Grid.png");
85 			Rulers = new Gtk.ToggleAction ("Rulers", Catalog.GetString ("Rulers"), null, "Menu.View.Rulers.png");
86 			Pixels = new Gtk.RadioAction ("Pixels", Catalog.GetString ("Pixels"), null, null, 0);
87 			Inches = new Gtk.RadioAction ("Inches", Catalog.GetString ("Inches"), null, null, 1);
88 			Centimeters = new Gtk.RadioAction ("Centimeters", Catalog.GetString ("Centimeters"), null, null, 2);
89 			Fullscreen = new Gtk.Action ("Fullscreen", Catalog.GetString ("Fullscreen"), null, Stock.Fullscreen);
90 
91 			ZoomCollection = new string[] {
92 				ToPercent (36),
93 				ToPercent (24),
94 				ToPercent (16),
95 				ToPercent (12),
96 				ToPercent (8),
97 				ToPercent (7),
98 				ToPercent (6),
99 				ToPercent (5),
100 				ToPercent (4),
101 				ToPercent (3),
102 				ToPercent (2),
103 				ToPercent (1.75),
104 				ToPercent (1.5),
105 				ToPercent (1.25),
106 				ToPercent (1),
107 				ToPercent (0.66),
108 				ToPercent (0.5),
109 				ToPercent (0.33),
110 				ToPercent (0.25),
111 				ToPercent (0.16),
112 				ToPercent (0.12),
113 				ToPercent (0.08),
114 				ToPercent (0.05),
115 				Catalog.GetString ("Window")
116 			};
117 			ZoomComboBox = new ToolBarComboBox (90, DefaultZoomIndex(), true, ZoomCollection);
118 
119 			// Make sure these are the same group so only one will be selected at a time
120 			Inches.Group = Pixels.Group;
121 			Centimeters.Group = Pixels.Group;
122 
123             // The toolbar is shown by default.
124             ToolBar.Active = true;
125             ImageTabs.Active = true;
126 		}
127 
128 		#region Initialization
CreateMainMenu(Gtk.Menu menu)129 		public void CreateMainMenu (Gtk.Menu menu)
130 		{
131 			MenuItem show_pad = (MenuItem)menu.Children[0];
132 			menu.Remove (show_pad);
133 
134 			menu.Append (ToolBar.CreateMenuItem ());
135 			menu.Append (PixelGrid.CreateMenuItem ());
136             menu.Append (Rulers.CreateMenuItem ());
137             menu.Append (ImageTabs.CreateMenuItem ());
138             menu.AppendSeparator ();
139 
140 			ImageMenuItem zoomin = ZoomIn.CreateAcceleratedMenuItem (Gdk.Key.plus, Gdk.ModifierType.ControlMask);
141 			zoomin.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.equal, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
142 			zoomin.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.equal, 0, AccelFlags.Visible));
143 			zoomin.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.KP_Add, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
144 			zoomin.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.KP_Add, 0, AccelFlags.Visible));
145 			menu.Append (zoomin);
146 
147 			ImageMenuItem zoomout = ZoomOut.CreateAcceleratedMenuItem (Gdk.Key.minus, Gdk.ModifierType.ControlMask);
148 			zoomout.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.minus, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
149 			zoomout.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.minus, 0, AccelFlags.Visible));
150 			zoomout.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.KP_Subtract, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
151 			zoomout.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.KP_Subtract, 0, AccelFlags.Visible));
152 			menu.Append (zoomout);
153 
154 			ImageMenuItem actualsize = ActualSize.CreateAcceleratedMenuItem (Gdk.Key.Key_0, Gdk.ModifierType.ControlMask);
155 			actualsize.AddAccelerator ("activate", PintaCore.Actions.AccelGroup, new AccelKey (Gdk.Key.A, Gdk.ModifierType.ControlMask | Gdk.ModifierType.ShiftMask, AccelFlags.Visible));
156 			menu.Append (actualsize);
157 			menu.Append (ZoomToWindow.CreateAcceleratedMenuItem (Gdk.Key.B, Gdk.ModifierType.ControlMask));
158 			//menu.Append (ZoomToSelection.CreateAcceleratedMenuItem (Gdk.Key.B, Gdk.ModifierType.ControlMask | Gdk.ModifierType.ShiftMask));
159 			menu.Append (Fullscreen.CreateAcceleratedMenuItem (Gdk.Key.F11, Gdk.ModifierType.None));
160 
161 			menu.AppendSeparator ();
162 
163 			Gtk.Action unit_action = new Gtk.Action ("RulerUnits", Mono.Unix.Catalog.GetString ("Ruler Units"), null, null);
164 			Menu unit_menu = (Menu)menu.AppendItem (unit_action.CreateSubMenuItem ()).Submenu;
165 			unit_menu.Append (Pixels.CreateMenuItem ());
166 			unit_menu.Append (Inches.CreateMenuItem ());
167 			unit_menu.Append (Centimeters.CreateMenuItem ());
168 
169 			menu.AppendSeparator ();
170 			menu.Append (show_pad);
171 		}
172 
CreateToolBar(Gtk.Toolbar toolbar)173 		public void CreateToolBar (Gtk.Toolbar toolbar)
174 		{
175 			toolbar.AppendItem (new Gtk.SeparatorToolItem ());
176 			toolbar.AppendItem (ZoomOut.CreateToolBarItem ());
177 			toolbar.AppendItem (ZoomComboBox);
178 			toolbar.AppendItem (ZoomIn.CreateToolBarItem ());
179 		}
180 
RegisterHandlers()181 		public void RegisterHandlers ()
182 		{
183 			ZoomIn.Activated += HandlePintaCoreActionsViewZoomInActivated;
184 			ZoomOut.Activated += HandlePintaCoreActionsViewZoomOutActivated;
185 			ZoomComboBox.ComboBox.Changed += HandlePintaCoreActionsViewZoomComboBoxComboBoxChanged;
186 			(ZoomComboBox.ComboBox as Gtk.ComboBoxEntry).Entry.FocusOutEvent += new Gtk.FocusOutEventHandler (ComboBox_FocusOutEvent);
187 			(ZoomComboBox.ComboBox as Gtk.ComboBoxEntry).Entry.FocusInEvent += new Gtk.FocusInEventHandler (Entry_FocusInEvent);
188 			ActualSize.Activated += HandlePintaCoreActionsViewActualSizeActivated;
189 
190 			PixelGrid.Toggled += delegate (object sender, EventArgs e) {
191 				PintaCore.Workspace.Invalidate ();
192 			};
193 
194 			var isFullscreen = false;
195 
196 			Fullscreen.Activated += (foo, bar) => {
197 				if (!isFullscreen)
198 					PintaCore.Chrome.MainWindow.Fullscreen ();
199 				else
200 					PintaCore.Chrome.MainWindow.Unfullscreen ();
201 
202 				isFullscreen = !isFullscreen;
203 			};
204 		}
205 
206 		private string temp_zoom;
207 		private bool suspend_zoom_change;
208 
Entry_FocusInEvent(object o, Gtk.FocusInEventArgs args)209 		private void Entry_FocusInEvent (object o, Gtk.FocusInEventArgs args)
210 		{
211 			temp_zoom = PintaCore.Actions.View.ZoomComboBox.ComboBox.ActiveText;
212 		}
213 
ComboBox_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)214 		private void ComboBox_FocusOutEvent (object o, Gtk.FocusOutEventArgs args)
215 		{
216 			string text = PintaCore.Actions.View.ZoomComboBox.ComboBox.ActiveText;
217 			double percent;
218 
219 			if (!TryParsePercent (text, out percent)) {
220 				(PintaCore.Actions.View.ZoomComboBox.ComboBox as Gtk.ComboBoxEntry).Entry.Text = temp_zoom;
221 				return;
222 			}
223 
224 			if (percent > 3600)
225 				PintaCore.Actions.View.ZoomComboBox.ComboBox.Active = 0;
226 		}
227 		#endregion
228 
229 		/// <summary>
230 		/// Converts the string representation of a percent (with or without a '%' sign) to a numeric value
231 		/// </summary>
TryParsePercent(string text, out double percent)232 		public static bool TryParsePercent (string text, out double percent)
233 		{
234 			var culture = CultureInfo.CurrentCulture;
235 			var format = culture.NumberFormat;
236 
237 			// In order to use double.TryParse, we must:
238 			// - replace the decimal separator for percents with the regular separator.
239 			// - remove the percent sign. We remove both the locale's percent sign and
240 			//   the standard one (U+0025). When running under mono, the 'fr' locale
241 			//   uses U+066A but the translation string uses U+0025, so there may be a bug in Mono.
242 			// - remove the group separators, since they might be different from the regular
243 			//   group separator, and the group sizes could also be different.
244 			text = text.Replace (format.PercentGroupSeparator, string.Empty);
245 			text = text.Replace (format.PercentSymbol, string.Empty);
246 			text = text.Replace ("%", string.Empty);
247 			text = text.Replace (format.PercentDecimalSeparator, format.NumberDecimalSeparator);
248 			text = text.Trim();
249 
250 			return double.TryParse (text,
251 			                        NumberStyles.AllowDecimalPoint |
252 			                        NumberStyles.AllowLeadingWhite |
253 			                        NumberStyles.AllowTrailingWhite,
254 			                        culture, out percent);
255 		}
256 
257 		/// <summary>
258 		/// Convert the given number to a percentage string using the current locale.
259 		/// </summary>
ToPercent(double n)260 		public static string ToPercent (double n)
261 		{
262 			var percent = (n * 100).ToString ("N0", CultureInfo.CurrentCulture);
263 			// Translators: This specifies the format of the zoom percentage choices
264 			// in the toolbar.
265 			return string.Format (Catalog.GetString ("{0}%"), percent);
266 		}
267 
SuspendZoomUpdate()268 		public void SuspendZoomUpdate ()
269 		{
270 			suspend_zoom_change = true;
271 		}
272 
ResumeZoomUpdate()273 		public void ResumeZoomUpdate ()
274 		{
275 			suspend_zoom_change = false;
276 		}
277 
UpdateCanvasScale()278 		public void UpdateCanvasScale ()
279 		{
280 			string text = PintaCore.Actions.View.ZoomComboBox.ComboBox.ActiveText;
281 
282 			// stay in "Zoom to Window" mode if this function was called without the zoom level being changed by the user (e.g. if the
283 			// image was rotated or cropped) and "Zoom to Window" mode is active
284 			if (text == Catalog.GetString ("Window") || (ZoomToWindowActivated && old_zoom_text == text))
285 			{
286 				PintaCore.Actions.View.ZoomToWindow.Activate ();
287 				ZoomToWindowActivated = true;
288 				return;
289 			}
290 			else
291 			{
292 				ZoomToWindowActivated = false;
293 			}
294 
295 			double percent;
296 
297 			if (!TryParsePercent (text, out percent))
298 				return;
299 
300 			percent = Math.Min (percent, 3600);
301 			percent = percent / 100.0;
302 
303 			PintaCore.Workspace.Scale = percent;
304 		}
305 
306 		#region Action Handlers
HandlePintaCoreActionsViewActualSizeActivated(object sender, EventArgs e)307 		private void HandlePintaCoreActionsViewActualSizeActivated (object sender, EventArgs e)
308 		{
309 			int default_zoom = DefaultZoomIndex ();
310 			if (ZoomComboBox.ComboBox.Active != default_zoom)
311 			{
312 				ZoomComboBox.ComboBox.Active = default_zoom;
313 				UpdateCanvasScale ();
314 			}
315 		}
316 
HandlePintaCoreActionsViewZoomComboBoxComboBoxChanged(object sender, EventArgs e)317 		private void HandlePintaCoreActionsViewZoomComboBoxComboBoxChanged (object sender, EventArgs e)
318 		{
319 			if (suspend_zoom_change)
320 				return;
321 
322 			PintaCore.Workspace.ActiveDocument.Workspace.ZoomManually ();
323 		}
324 
HandlePintaCoreActionsViewZoomOutActivated(object sender, EventArgs e)325 		private void HandlePintaCoreActionsViewZoomOutActivated (object sender, EventArgs e)
326 		{
327 			PintaCore.Workspace.ActiveDocument.Workspace.ZoomOut ();
328 		}
329 
HandlePintaCoreActionsViewZoomInActivated(object sender, EventArgs e)330 		private void HandlePintaCoreActionsViewZoomInActivated (object sender, EventArgs e)
331 		{
332 			PintaCore.Workspace.ActiveDocument.Workspace.ZoomIn ();
333 		}
334 		#endregion
335 
336 		/// <summary>
337 		/// Returns the index in the ZoomCollection of the default zoom level
338 		/// </summary>
DefaultZoomIndex()339 		private int DefaultZoomIndex()
340 		{
341 			return Array.IndexOf (ZoomCollection, ToPercent (1));
342 		}
343 	}
344 }
345