1/*
2 * Copyright © 2010 Yuvaraj Pandian T <yuvipanda@yuvi.in>
3 * Copyright © 2010 daniel g. siegel <dgsiegel@gnome.org>
4 * Copyright © 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
5 *
6 * Licensed under the GNU General Public License Version 2
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22using Gtk;
23
24[GtkTemplate (ui = "/org/gnome/Cheese/cheese-prefs.ui")]
25public class Cheese.PreferencesDialog : Gtk.Dialog
26{
27    private Cheese.Camera camera;
28
29    private GLib.Settings settings;
30
31    [GtkChild]
32    private unowned Gtk.ComboBox photo_resolution_combo;
33    [GtkChild]
34    private unowned Gtk.ComboBox video_resolution_combo;
35    [GtkChild]
36    private unowned Gtk.ComboBox source_combo;
37
38    private Gtk.ListStore camera_model;
39
40    [GtkChild]
41    private unowned Gtk.Adjustment brightness_adjustment;
42    [GtkChild]
43    private unowned Gtk.Adjustment contrast_adjustment;
44    [GtkChild]
45    private unowned Gtk.Adjustment hue_adjustment;
46    [GtkChild]
47    private unowned Gtk.Adjustment saturation_adjustment;
48
49    [GtkChild]
50    private unowned Gtk.SpinButton burst_repeat_spin;
51    [GtkChild]
52    private unowned Gtk.SpinButton burst_delay_spin;
53
54    [GtkChild]
55    private unowned Gtk.CheckButton countdown_check;
56    [GtkChild]
57    private unowned Gtk.CheckButton flash_check;
58
59    private MediaMode current_mode;
60
61public PreferencesDialog (Cheese.Camera camera)
62{
63    var gtk_settings = Gtk.Settings.get_default ();
64    Object (use_header_bar: gtk_settings.gtk_dialogs_use_header ? 1 : 0);
65
66    this.camera = camera;
67
68    settings = new GLib.Settings ("org.gnome.Cheese");
69
70    setup_combo_box_models ();
71    initialize_camera_devices ();
72    initialize_values_from_settings ();
73
74    /*
75     * Connect signals only after all the widgets have been setup
76     * Stops a bunch of unnecessary signals from being fired
77     */
78    camera.notify["num-camera-devices"].connect(this.on_camera_update_num_camera_devices);
79}
80
81  /**
82   * Set up combo box cell renderers.
83   */
84  private void setup_combo_box_models ()
85  {
86    CellRendererText cell = new CellRendererText ();
87
88    photo_resolution_combo.pack_start (cell, false);
89    photo_resolution_combo.set_attributes (cell, "text", 0);
90
91    video_resolution_combo.pack_start (cell, false);
92    video_resolution_combo.set_attributes (cell, "text", 0);
93
94    source_combo.pack_start (cell, false);
95    source_combo.set_attributes (cell, "text", 0);
96  }
97
98  /**
99   * Initialize and populate the camera device combo box model.
100   */
101  private void initialize_camera_devices ()
102  {
103    GLib.GenericArray<unowned Cheese.CameraDevice> devices = camera.get_camera_devices ();
104    camera_model = new Gtk.ListStore (2, typeof (string), typeof (Cheese.CameraDevice));
105
106    source_combo.model = camera_model;
107    source_combo.sensitive = false;
108
109    devices.foreach(add_camera_device);
110
111    settings.set_string ("camera", camera.get_selected_device ().get_name ());
112    setup_resolutions_for_device (camera.get_selected_device ());
113  }
114
115  /**
116   * Initialize and populate the resolution combo box models for the device.
117   *
118   * @param device the Cheese.CameraDevice for which to enumerate resolutions
119   */
120  private void setup_resolutions_for_device (Cheese.CameraDevice device)
121  {
122    var formats = device.get_format_list ();
123    Gtk.ListStore resolution_model = new Gtk.ListStore (2, typeof (string),
124        typeof (Cheese.VideoFormat));
125
126    photo_resolution_combo.model = resolution_model;
127    video_resolution_combo.model = resolution_model;
128
129    formats.foreach ((format) =>
130    {
131      TreeIter iter;
132      resolution_model.append (out iter);
133      resolution_model.set (iter,
134                            0, format.width.to_string () + " × " + format.height.to_string (),
135                            1, format);
136      if (camera.get_current_video_format ().width == format.width &&
137          camera.get_current_video_format ().height == format.height)
138      {
139        photo_resolution_combo.set_active_iter (iter);
140        settings.set_int ("photo-x-resolution", format.width);
141        settings.set_int ("photo-y-resolution", format.height);
142      }
143
144      if (settings.get_int ("video-x-resolution") == format.width &&
145          settings.get_int ("video-y-resolution") == format.height)
146      {
147        video_resolution_combo.set_active_iter (iter);
148      }
149    });
150
151    /* Video resolution combo shows photo resolution by default if previous
152     * user choice is not found in settings or not supported by current device.
153     * These values are saved to settings.
154     */
155    if (video_resolution_combo.get_active () == -1)
156    {
157      video_resolution_combo.set_active (photo_resolution_combo.get_active ());
158      settings.set_int ("video-x-resolution", settings.get_int ("photo-x-resolution"));
159      settings.set_int ("video-y-resolution", settings.get_int ("photo-y-resolution"));
160    }
161  }
162
163    /**
164     * Take the user preferences from GSettings.
165     */
166    private void initialize_values_from_settings ()
167    {
168      brightness_adjustment.value = settings.get_double ("brightness");
169      contrast_adjustment.value   = settings.get_double ("contrast");
170      hue_adjustment.value        = settings.get_double ("hue");
171      saturation_adjustment.value = settings.get_double ("saturation");
172
173        settings.bind ("burst-repeat", burst_repeat_spin, "value",
174                       SettingsBindFlags.DEFAULT);
175      burst_delay_spin.value  = settings.get_int ("burst-delay") / 1000;
176
177        settings.bind ("countdown", countdown_check, "active",
178                       SettingsBindFlags.DEFAULT);
179        settings.bind ("flash", flash_check, "active",
180                       SettingsBindFlags.DEFAULT);
181    }
182
183  /**
184   * Update the active device to the active iter of the device combo box.
185   *
186   * @param combo the video device combo box
187   */
188  [GtkCallback]
189  private void on_source_change (Gtk.ComboBox combo)
190  {
191    // TODO: Handle going from 1 to 0 devices, cleanly!
192    return_if_fail (camera.num_camera_devices > 0);
193
194    TreeIter iter;
195    Cheese.CameraDevice dev;
196
197    combo.get_active_iter (out iter);
198    combo.model.get (iter, 1, out dev);
199    camera.set_device (dev);
200    camera.switch_camera_device ();
201    setup_resolutions_for_device (camera.get_selected_device ());
202    settings.set_string ("camera", dev.get_name ());
203  }
204
205  /**
206   * Update the current photo capture resolution to the active iter of the
207   * photo resolution combo box.
208   *
209   * @param combo the photo resolution combo box
210   */
211  [GtkCallback]
212  private void on_photo_resolution_change (Gtk.ComboBox combo)
213  {
214    TreeIter iter;
215
216    Cheese.VideoFormat format;
217
218    combo.get_active_iter (out iter);
219    combo.model.get (iter, 1, out format);
220
221    if (current_mode == MediaMode.PHOTO || current_mode == MediaMode.BURST)
222      camera.set_video_format (format);
223
224    settings.set_int ("photo-x-resolution", format.width);
225    settings.set_int ("photo-y-resolution", format.height);
226  }
227
228  /**
229   * Update the current video capture resolution to the active iter of the
230   * video resolution combo box.
231   *
232   * @param combo the video resolution combo box
233   */
234  [GtkCallback]
235  private void on_video_resolution_change (Gtk.ComboBox combo)
236  {
237    TreeIter iter;
238
239    Cheese.VideoFormat format;
240
241    combo.get_active_iter (out iter);
242    combo.model.get (iter, 1, out format);
243
244    if (current_mode == MediaMode.VIDEO)
245      camera.set_video_format (format);
246
247    settings.set_int ("video-x-resolution", format.width);
248    settings.set_int ("video-y-resolution", format.height);
249  }
250
251    /**
252    * Hide the dialog when it is closed, rather than deleting it.
253    */
254    [GtkCallback]
255    private bool on_delete ()
256    {
257        return this.hide_on_delete ();
258    }
259
260    /**
261    * Hide the dialog when it is closed, rather than deleting it.
262    */
263    [GtkCallback]
264    private void on_dialog_close (Gtk.Button button)
265    {
266        this.hide ();
267    }
268
269    /**
270    * Show the help for the preferences dialog.
271    *
272    * @param button the help button
273    */
274    [GtkCallback]
275    private void on_dialog_help (Gtk.Button button)
276    {
277        try
278        {
279            Gtk.show_uri (this.get_screen (), "help:cheese/index#preferences",
280                          Gdk.CURRENT_TIME);
281        }
282        catch
283        {
284            warning ("%s", "Error showing help");
285        }
286    }
287
288  /**
289   * Change the burst-delay GSetting when changing the spin button.
290   *
291   * The burst delay is the time, in milliseconds, between individual photos in
292   * a burst.
293   *
294   * @param spinbutton the burst-delay spin button
295   */
296  [GtkCallback]
297  private void on_burst_delay_change (Gtk.SpinButton spinbutton)
298  {
299    settings.set_int ("burst-delay", (int) spinbutton.value * 1000);
300  }
301
302  /**
303   * Change the brightness of the image, and update the GSetting, when changing
304   * the scale.
305   *
306   * @param adjustment the adjustment of the brightness Gtk.Scale
307   */
308  [GtkCallback]
309  private void on_brightness_change (Gtk.Adjustment adjustment)
310  {
311    this.camera.set_balance_property ("brightness", adjustment.value);
312    settings.set_double ("brightness", adjustment.value);
313  }
314
315  /**
316   * Change the contrast of the image, and update the GSetting, when changing
317   * the scale.
318   *
319   * @param adjustment the adjustment of the contrast Gtk.Scale
320   */
321  [GtkCallback]
322  private void on_contrast_change (Gtk.Adjustment adjustment)
323  {
324    this.camera.set_balance_property ("contrast", adjustment.value);
325    settings.set_double ("contrast", adjustment.value);
326  }
327
328  /**
329   * Change the hue of the image, and update the GSetting, when changing the
330   * scale.
331   *
332   * @param adjustment the adjustment of the hue Gtk.Scale
333   */
334  [GtkCallback]
335  private void on_hue_change (Gtk.Adjustment adjustment)
336  {
337    this.camera.set_balance_property ("hue", adjustment.value);
338    settings.set_double ("hue", adjustment.value);
339  }
340
341  /**
342   * Change the saturation of the image, and update the GSetting, when changing
343   * the scale.
344   *
345   * @param adjustment the adjustment of the saturation Gtk.Scale
346   */
347  [GtkCallback]
348  private void on_saturation_change (Gtk.Adjustment adjustment)
349  {
350    this.camera.set_balance_property ("saturation", adjustment.value);
351    settings.set_double ("saturation", adjustment.value);
352  }
353
354  /**
355   * Update the video device combo box when a camera device was added or
356   * removed.
357   */
358  private void on_camera_update_num_camera_devices ()
359  {
360    GLib.GenericArray<unowned Cheese.CameraDevice> devices = camera.get_camera_devices ();
361    unowned Cheese.CameraDevice   dev;
362
363    // Add (if) / Remove (else) a camera device.
364    if (devices.length > camera_model.iter_n_children (null))
365    {
366      dev = devices.get (devices.length - 1);
367      add_camera_device(dev);
368    }
369    else
370    {
371      // First camera device in the combobox.
372      TreeIter iter;
373      camera_model.get_iter_first (out iter);
374
375      // Combobox active element.
376      TreeIter active_iter;
377      Cheese.CameraDevice active_device;
378      source_combo.get_active_iter (out active_iter);
379      camera_model.get (active_iter, 1, out active_device, -1);
380
381      // Find which device was removed.
382      bool device_removed = false;
383      devices.foreach ((device) =>
384      {
385        Cheese.CameraDevice new_device;
386        camera_model.get (iter, 1, out new_device, -1);
387
388        // Found the device that was removed.
389        if (device != new_device)
390        {
391            remove_camera_device (iter, new_device, active_device);
392            device_removed = true;
393            // Remember, this is from the anonymous function!
394            return;
395        }
396        camera_model.iter_next (ref iter);
397      });
398
399      // Special case: the last device on the list was removed.
400      if (!device_removed)
401      {
402        Cheese.CameraDevice old_device;
403        camera_model.get (iter, 1, out old_device, -1);
404        remove_camera_device (iter, old_device, active_device);
405      }
406    }
407
408    settings.set_string ("camera", camera.get_selected_device ().get_name ());
409    setup_resolutions_for_device (camera.get_selected_device ());
410  }
411
412  /**
413   * Add the supplied camera device to the device combo box model.
414   *
415   * This method is intended to be used with the foreach method of GLib
416   * containers.
417   *
418   * @param device a Cheese.CameraDevice to add to the device combo box model
419   */
420  private void add_camera_device (Cheese.CameraDevice device)
421  {
422    TreeIter iter;
423
424    camera_model.append (out iter);
425    camera_model.set (iter,
426                      0, device.get_name (),
427                      1, device);
428
429    if (camera.get_selected_device () == device)
430        source_combo.set_active_iter (iter);
431
432    if (camera_model.iter_n_children (null) > 1)
433      source_combo.sensitive = true;
434  }
435
436  /**
437   * Remove the supplied camera device from the device combo box model.
438   *
439   * @param iter the iterator of the device to remove
440   * @param device_node the device to remove from the combo box model
441   * @param active_device_node the currently-active camera device
442   */
443  private void remove_camera_device (TreeIter iter, Cheese.CameraDevice device_node,
444                             Cheese.CameraDevice active_device_node)
445  {
446      GLib.GenericArray<unowned Cheese.CameraDevice> devices = camera.get_camera_devices ();
447
448      // Check if the camera that we want to remove, is the active one
449      if (device_node == active_device_node)
450      {
451        if (devices.length > 0)
452          set_new_available_camera_device (iter);
453        else
454          this.hide ();
455      }
456
457#if VALA_0_36
458      camera_model.remove (ref iter);
459#else
460      camera_model.remove (iter);
461#endif
462
463      if (camera_model.iter_n_children (null) <= 1)
464        source_combo.sensitive = false;
465  }
466
467  /**
468   * Search for an available camera device and activate it in the device combo
469   * box model.
470   *
471   * @param iter a device in the combo box model to search either side of
472   */
473  private void set_new_available_camera_device (TreeIter iter)
474  {
475    TreeIter new_iter = iter;
476
477    if (!camera_model.iter_next (ref new_iter))
478    {
479      new_iter = iter;
480      camera_model.iter_previous (ref new_iter);
481    }
482    source_combo.set_active_iter (new_iter);
483  }
484
485
486    /**
487     * Set the current media mode (photo, video or burst).
488     *
489     * The current mode is used to update the video format on the Cheese.Camera
490     * when the resolution for the current mode is changed.
491     *
492     * @param mode the media mode to set
493     */
494    public void set_current_mode (MediaMode mode)
495    {
496        current_mode = mode;
497    }
498}
499