1 //
2 // NewImageDialog.cs
3 //
4 // Author:
5 //       Jonathan Pobst <monkey@jpobst.com>
6 //
7 // Copyright (c) 2015 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.Collections.Generic;
29 using System.Linq;
30 
31 using Gtk;
32 using Mono.Unix;
33 using Pinta.Core;
34 
35 namespace Pinta
36 {
37 	public class NewImageDialog : Dialog
38 	{
39 		/// <summary>
40 		/// Configures and builds a NewImageDialog object.
41 		/// </summary>
42 		/// <param name="imgWidth">Initial value of the width entry.</param>
43         /// <param name="imgHeight">Initial value of the height entry.</param>
44         /// <param name="isClipboardSize">Indicates if there is an image on the clipboard (and the size parameters represent the clipboard image size).</param>
45         private bool allow_background_color;
46         private bool has_clipboard;
47         private bool suppress_events;
48 
49         private Gdk.Size clipboard_size;
50 
51         private List<Gdk.Size> preset_sizes;
52         private PreviewArea preview;
53 
54         private ComboBox preset_combo;
55         private Entry width_entry;
56         private Entry height_entry;
57 
58         private RadioButton portrait_radio;
59         private RadioButton landscape_radio;
60 
61         private RadioButton white_bg_radio;
62         private RadioButton secondary_bg_radio;
63         private RadioButton trans_bg_radio;
64 
NewImageDialog(int initialWidth, int initialHeight, BackgroundType initial_bg_type, bool isClipboardSize)65         public NewImageDialog (int initialWidth, int initialHeight, BackgroundType initial_bg_type, bool isClipboardSize)
66             : base (string.Empty, PintaCore.Chrome.MainWindow, DialogFlags.Modal, Gtk.Stock.Cancel, Gtk.ResponseType.Cancel, Gtk.Stock.Ok, Gtk.ResponseType.Ok)
67         {
68             Title = Catalog.GetString ("New Image");
69             WindowPosition = Gtk.WindowPosition.CenterOnParent;
70 
71             // We don't show the background color option if it's the same as "White"
72             allow_background_color = PintaCore.Palette.SecondaryColor.ToColorBgra () != ColorBgra.White;
73 
74             BorderWidth = 4;
75             VBox.Spacing = 4;
76 
77             Resizable = false;
78             DefaultResponse = ResponseType.Ok;
79 
80             Icon = PintaCore.Resources.GetIcon (Stock.New, 16);
81             AlternativeButtonOrder = new int[] { (int)ResponseType.Ok, (int)ResponseType.Cancel };
82 
83             has_clipboard = isClipboardSize;
84             clipboard_size = new Gdk.Size (initialWidth, initialHeight);
85 
86             InitializePresets ();
87             BuildDialog ();
88 
89             if (initial_bg_type == BackgroundType.SecondaryColor && allow_background_color)
90                 secondary_bg_radio.Active = true;
91             else if (initial_bg_type == BackgroundType.Transparent)
92                 trans_bg_radio.Active = true;
93             else
94                 white_bg_radio.Active = true;
95 
96             width_entry.Text = initialWidth.ToString ();
97             height_entry.Text = initialHeight.ToString ();
98 
99             width_entry.GrabFocus ();
100             width_entry.SelectRegion (0, width_entry.Text.Length);
101 
102             WireUpEvents ();
103 
104             UpdateOrientation ();
105             UpdatePresetSelection ();
106             preview.Update (NewImageSize, NewImageBackground);
107         }
108 
109         public int NewImageWidth { get { return int.Parse (width_entry.Text); } }
110         public int NewImageHeight { get { return int.Parse (height_entry.Text); } }
111         public Gdk.Size NewImageSize { get { return new Gdk.Size (NewImageWidth, NewImageHeight); } }
112 
113         public enum BackgroundType
114         {
115             White,
116             Transparent,
117             SecondaryColor
118         }
119 
120         public BackgroundType NewImageBackgroundType {
121             get {
122                 if (white_bg_radio.Active)
123                     return BackgroundType.White;
124                 else if (trans_bg_radio.Active)
125                     return BackgroundType.Transparent;
126                 else
127                     return BackgroundType.SecondaryColor;
128             }
129         }
130 
131         public Cairo.Color NewImageBackground {
132             get {
133                 switch (NewImageBackgroundType) {
134                     case BackgroundType.White:
135                         return new Cairo.Color (1, 1, 1);
136                     case BackgroundType.Transparent:
137                         return new Cairo.Color (1, 1, 1, 0);
138                     case BackgroundType.SecondaryColor:
139                     default:
140                         return PintaCore.Palette.SecondaryColor;
141                 }
142             }
143         }
144 
145 
146         private bool IsValidSize {
147             get {
148                 int width = 0;
149                 int height = 0;
150 
151                 if (!int.TryParse (width_entry.Text, out width))
152                     return false;
153                 if (!int.TryParse (height_entry.Text, out height))
154                     return false;
155 
156                 return width > 0 && height > 0;
157             }
158         }
159 
160         private Gdk.Size SelectedPresetSize {
161             get {
162                 if (preset_combo.ActiveText == Catalog.GetString ("Clipboard") || preset_combo.ActiveText == Catalog.GetString ("Custom"))
163                     return Gdk.Size.Empty;
164 
165                 var text_parts = preset_combo.ActiveText.Split (' ');
166                 var width = int.Parse (text_parts[0]);
167                 var height = int.Parse (text_parts[2]);
168 
169                 return new Gdk.Size (width, height);
170             }
171         }
172 
InitializePresets()173         private void InitializePresets ()
174         {
175             // Some arbitrary presets
176             preset_sizes = new List<Gdk.Size> ();
177 
178             preset_sizes.Add (new Gdk.Size (640, 480));
179             preset_sizes.Add (new Gdk.Size (800, 600));
180             preset_sizes.Add (new Gdk.Size (1024, 768));
181             preset_sizes.Add (new Gdk.Size (1600, 1200));
182         }
183 
BuildDialog()184         private void BuildDialog ()
185         {
186             // Layout table for preset, width, and height
187             var layout_table = new Table (3, 2, false);
188 
189             layout_table.RowSpacing = 5;
190             layout_table.ColumnSpacing = 6;
191 
192             // Preset Combo
193             var size_label = new Label (Catalog.GetString ("Preset:"));
194             size_label.SetAlignment (1f, .5f);
195 
196             var preset_entries = new List<string> ();
197 
198             if (has_clipboard)
199                 preset_entries.Add (Catalog.GetString ("Clipboard"));
200 
201             preset_entries.Add (Catalog.GetString ("Custom"));
202             preset_entries.AddRange (preset_sizes.Select (p => string.Format ("{0} x {1}", p.Width, p.Height)));
203 
204             preset_combo = new ComboBox (preset_entries.ToArray ());
205             preset_combo.Active = 0;
206 
207             layout_table.Attach (size_label, 0, 1, 0, 1, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Fill, 0, 0);
208             layout_table.Attach (preset_combo, 1, 2, 0, 1, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Fill, 0, 0);
209 
210             // Width Entry
211             var width_label = new Label (Catalog.GetString ("Width:"));
212             width_label.SetAlignment (1f, .5f);
213 
214             width_entry = new Entry ();
215             width_entry.WidthRequest = 50;
216             width_entry.ActivatesDefault = true;
217 
218             var width_units = new Label (Catalog.GetString ("pixels"));
219 
220             var width_hbox = new HBox ();
221             width_hbox.PackStart (width_entry, false, false, 0);
222             width_hbox.PackStart (width_units, false, false, 5);
223 
224             layout_table.Attach (width_label, 0, 1, 1, 2, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Fill, 0, 0);
225             layout_table.Attach (width_hbox, 1, 2, 1, 2, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Fill, 0, 0);
226 
227             // Height Entry
228             var height_label = new Label (Catalog.GetString ("Height:"));
229             height_label.SetAlignment (1f, .5f);
230 
231             height_entry = new Entry ();
232             height_entry.WidthRequest = 50;
233             height_entry.ActivatesDefault = true;
234 
235             var height_units = new Label (Catalog.GetString ("pixels"));
236 
237             var height_hbox = new HBox ();
238             height_hbox.PackStart (height_entry, false, false, 0);
239             height_hbox.PackStart (height_units, false, false, 5);
240 
241             layout_table.Attach (height_label, 0, 1, 2, 3, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Fill, 0, 0);
242             layout_table.Attach (height_hbox, 1, 2, 2, 3, AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Fill, 0, 0);
243 
244             // Orientation Radio options
245             var orientation_label = new Label (Catalog.GetString ("Orientation:"));
246             orientation_label.SetAlignment (0f, .5f);
247 
248             portrait_radio = new RadioButton (Catalog.GetString ("Portrait"));
249             var portrait_image = new Image (PintaCore.Resources.GetIcon (Stock.OrientationPortrait, 16));
250 
251             var portrait_hbox = new HBox ();
252 
253             portrait_hbox.PackStart (portrait_image, false, false, 7);
254             portrait_hbox.PackStart (portrait_radio, false, false, 0);
255 
256             landscape_radio = new RadioButton (portrait_radio, Catalog.GetString ("Landscape"));
257             var landscape_image = new Image (PintaCore.Resources.GetIcon (Stock.OrientationLandscape, 16));
258 
259             var landscape_hbox = new HBox ();
260 
261             landscape_hbox.PackStart (landscape_image, false, false, 7);
262             landscape_hbox.PackStart (landscape_radio, false, false, 0);
263 
264             // Orientation VBox
265             var orientation_vbox = new VBox ();
266             orientation_vbox.PackStart (orientation_label, false, false, 4);
267             orientation_vbox.PackStart (portrait_hbox, false, false, 0);
268             orientation_vbox.PackStart (landscape_hbox, false, false, 0);
269 
270             // Background Color options
271             var background_label = new Label (Catalog.GetString ("Background:"));
272             background_label.SetAlignment (0f, .5f);
273 
274             white_bg_radio = new RadioButton (Catalog.GetString ("White"));
275             var image_white = new Image (GdkExtensions.CreateColorSwatch (16, new Gdk.Color (255, 255, 255)));
276 
277             var hbox_white = new HBox ();
278 
279             hbox_white.PackStart (image_white, false, false, 7);
280             hbox_white.PackStart (white_bg_radio, false, false, 0);
281 
282             secondary_bg_radio = new RadioButton (white_bg_radio, Catalog.GetString ("Background Color"));
283             var image_bg = new Image (GdkExtensions.CreateColorSwatch (16, PintaCore.Palette.SecondaryColor.ToGdkColor ()));
284 
285             var hbox_bg = new HBox ();
286 
287             hbox_bg.PackStart (image_bg, false, false, 7);
288             hbox_bg.PackStart (secondary_bg_radio, false, false, 0);
289 
290             trans_bg_radio = new RadioButton (secondary_bg_radio, Catalog.GetString ("Transparent"));
291             var image_trans = new Image (GdkExtensions.CreateTransparentColorSwatch (true));
292 
293             var hbox_trans = new HBox ();
294 
295             hbox_trans.PackStart (image_trans, false, false, 7);
296             hbox_trans.PackStart (trans_bg_radio, false, false, 0);
297 
298             // Background VBox
299             var background_vbox = new VBox ();
300             background_vbox.PackStart (background_label, false, false, 4);
301             background_vbox.PackStart (hbox_white, false, false, 0);
302 
303             if (allow_background_color)
304                 background_vbox.PackStart (hbox_bg, false, false, 0);
305 
306             background_vbox.PackStart (hbox_trans, false, false, 0);
307 
308             // Put all the options together
309             var options_vbox = new VBox ();
310             options_vbox.Spacing = 10;
311 
312             options_vbox.PackStart (layout_table, false, false, 3);
313             options_vbox.PackStart (orientation_vbox, false, false, 0);
314             options_vbox.PackStart (background_vbox, false, false, 4);
315 
316             // Layout the preview + the options
317             preview = new PreviewArea ();
318 
319             var preview_label = new Label (Catalog.GetString ("Preview"));
320 
321             var preview_vbox = new VBox ();
322             preview_vbox.PackStart (preview_label, false, false, 0);
323             preview_vbox.PackStart (preview, true, true, 0);
324 
325 
326             var main_hbox = new HBox (false, 10);
327 
328             main_hbox.PackStart (options_vbox, false, false, 0);
329             main_hbox.PackStart (preview_vbox, true, true, 0);
330 
331             VBox.Add (main_hbox);
332 
333             ShowAll ();
334         }
335 
WireUpEvents()336         private void WireUpEvents ()
337         {
338             // Handle preset combo changes
339             preset_combo.Changed += (o, e) => {
340                 var new_size = IsValidSize ? NewImageSize : Gdk.Size.Empty;
341 
342                 if (has_clipboard && preset_combo.ActiveText == Catalog.GetString ("Clipboard"))
343                     new_size = clipboard_size;
344                 else if (preset_combo.ActiveText == Catalog.GetString ("Custom"))
345                     return;
346                 else
347                     new_size = SelectedPresetSize;
348 
349                 suppress_events = true;
350                 width_entry.Text = new_size.Width.ToString ();
351                 height_entry.Text = new_size.Height.ToString ();
352                 suppress_events = false;
353 
354                 UpdateOkButton ();
355                 if (!IsValidSize)
356                     return;
357 
358                 UpdateOrientation ();
359                 preview.Update (NewImageSize);
360             };
361 
362             // Handle width/height entry changes
363             width_entry.Changed += (o, e) => {
364                 if (suppress_events)
365                     return;
366 
367                 UpdateOkButton ();
368 
369                 if (!IsValidSize)
370                     return;
371 
372                 if (NewImageSize != SelectedPresetSize)
373                     preset_combo.Active = has_clipboard ? 1 : 0;
374 
375                 UpdateOrientation ();
376                 UpdatePresetSelection ();
377                 preview.Update (NewImageSize);
378             };
379 
380             height_entry.Changed += (o, e) => {
381                 if (suppress_events)
382                     return;
383 
384                 UpdateOkButton ();
385 
386                 if (!IsValidSize)
387                     return;
388 
389                 if (NewImageSize != SelectedPresetSize)
390                     preset_combo.Active = has_clipboard ? 1 : 0;
391 
392                 UpdateOrientation ();
393                 UpdatePresetSelection ();
394                 preview.Update (NewImageSize);
395             };
396 
397             // Handle orientation changes
398             portrait_radio.Toggled += (o, e) => {
399                 if (portrait_radio.Active && IsValidSize && NewImageWidth > NewImageHeight) {
400                     var temp = NewImageWidth;
401                     width_entry.Text = height_entry.Text;
402                     height_entry.Text = temp.ToString ();
403                     preview.Update (NewImageSize);
404                 }
405             };
406 
407             landscape_radio.Toggled += (o, e) => {
408                 if (landscape_radio.Active && IsValidSize && NewImageWidth < NewImageHeight) {
409                     var temp = NewImageWidth;
410                     width_entry.Text = height_entry.Text;
411                     height_entry.Text = temp.ToString ();
412                     preview.Update (NewImageSize);
413                 }
414             };
415 
416             // Handle background color changes
417             white_bg_radio.Toggled += (o, e) => { if (white_bg_radio.Active) preview.Update (new Cairo.Color (1, 1, 1)); };
418             secondary_bg_radio.Toggled += (o, e) => { if (secondary_bg_radio.Active) preview.Update (PintaCore.Palette.SecondaryColor); };
419             trans_bg_radio.Toggled += (o, e) => { if (trans_bg_radio.Active ) preview.Update (new Cairo.Color (1, 1, 1, 0)); };
420         }
421 
UpdateOrientation()422         private void UpdateOrientation ()
423         {
424             if (NewImageWidth < NewImageHeight && !portrait_radio.Active)
425                 portrait_radio.Activate ();
426             else if (NewImageWidth > NewImageHeight && !landscape_radio.Active)
427                 landscape_radio.Activate ();
428 
429             for (var i = 1; i < preset_combo.GetItemCount (); i++) {
430                 var text = preset_combo.GetValueAt<string> (i);
431 
432                 if (text == Catalog.GetString ("Clipboard") || text == Catalog.GetString ("Custom"))
433                     continue;
434 
435                 var text_parts = text.Split ('x');
436                 var width = int.Parse (text_parts[0].Trim ());
437                 var height = int.Parse (text_parts[1].Trim ());
438 
439                 var new_size = new Gdk.Size (NewImageWidth < NewImageHeight ? Math.Min (width, height) : Math.Max (width, height), NewImageWidth < NewImageHeight ? Math.Max (width, height) : Math.Min (width, height));
440                 var new_text = string.Format ("{0} x {1}", new_size.Width, new_size.Height);
441 
442                 preset_combo.SetValueAt (i, new_text);
443             }
444         }
445 
UpdateOkButton()446         private void UpdateOkButton ()
447         {
448             foreach (Widget widget in ActionArea.Children)
449                 if (widget is Button && (widget as Button).Label == "gtk-ok")
450                     widget.Sensitive = IsValidSize;
451         }
452 
UpdatePresetSelection()453         private void UpdatePresetSelection ()
454         {
455             if (!IsValidSize)
456                 return;
457 
458             var text = string.Format ("{0} x {1}", NewImageWidth, NewImageHeight);
459             var index = preset_combo.FindValue (text);
460 
461             if (index >= 0 && preset_combo.Active != index)
462                 preset_combo.Active = index;
463         }
464 
465         private class PreviewArea : DrawingArea
466         {
467             private Gdk.Size size;
468             private Cairo.Color color;
469 
470             private int max_size = 250;
471 
PreviewArea()472             public PreviewArea ()
473             {
474                 WidthRequest = 300;
475             }
476 
Update(Gdk.Size size)477             public void Update (Gdk.Size size)
478             {
479                 this.size = size;
480 
481                 this.QueueDraw ();
482             }
483 
Update(Cairo.Color color)484             public void Update (Cairo.Color color)
485             {
486                 this.color = color;
487 
488                 this.QueueDraw ();
489             }
490 
Update(Gdk.Size size, Cairo.Color color)491             public void Update (Gdk.Size size, Cairo.Color color)
492             {
493                 this.size = size;
494                 this.color = color;
495 
496                 this.QueueDraw ();
497             }
498 
OnExposeEvent(Gdk.EventExpose evnt)499             protected override bool OnExposeEvent (Gdk.EventExpose evnt)
500             {
501                 base.OnExposeEvent (evnt);
502 
503                 if (size == Gdk.Size.Empty)
504                     return true;
505 
506                 var preview_size = Gdk.Size.Empty;
507                 var widget_size = GdkWindow.GetBounds ();
508 
509                 // Figure out the dimensions of the preview to draw
510                 if (size.Width <= max_size && size.Height <= max_size)
511                     preview_size = size;
512                 else if (size.Width > size.Height)
513                     preview_size = new Gdk.Size (max_size, (int)(max_size / ((float)size.Width / (float)size.Height)));
514                 else
515                     preview_size = new Gdk.Size ((int)(max_size / ((float)size.Height / (float)size.Width)), max_size);
516 
517                 using (var g = Gdk.CairoHelper.Create (GdkWindow)) {
518                     var r = new Cairo.Rectangle ((widget_size.Width - preview_size.Width) / 2, (widget_size.Height - preview_size.Height) / 2, preview_size.Width, preview_size.Height);
519 
520                     if (color.A == 0) {
521                         // Fill with transparent checkerboard pattern
522                         using (var grid = GdkExtensions.CreateTransparentColorSwatch (false))
523                         using (var surf = grid.ToSurface ())
524                         using (var pattern = surf.ToTiledPattern ())
525                             g.FillRectangle (r, pattern);
526                     } else {
527                         // Fill with selected color
528                         g.FillRectangle (r, color);
529                     }
530 
531                     // Draw our canvas drop shadow
532                     g.DrawRectangle (new Cairo.Rectangle (r.X - 1, r.Y - 1, r.Width + 2, r.Height + 2), new Cairo.Color (.5, .5, .5), 1);
533                     g.DrawRectangle (new Cairo.Rectangle (r.X - 2, r.Y - 2, r.Width + 4, r.Height + 4), new Cairo.Color (.8, .8, .8), 1);
534                     g.DrawRectangle (new Cairo.Rectangle (r.X - 3, r.Y - 3, r.Width + 6, r.Height + 6), new Cairo.Color (.9, .9, .9), 1);
535                 }
536 
537                 return true;
538             }
539         }
540     }
541 }
542 
543