Update Chinese (Taiwan) translation
[cheese.git] / src / cheese-window.vala
blob09879fc51e7a3d758783e61101f48364ed5746c2
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>
6 * Licensed under the GNU General Public License Version 2
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.
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.
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/>.
22 using Gtk;
23 using Gdk;
24 using GtkClutter;
25 using Clutter;
26 using Config;
27 using Eog;
28 using Gst;
29 using CanberraGtk;
31 const int FULLSCREEN_TIMEOUT_INTERVAL = 5 * 1000;
32 const uint EFFECTS_PER_PAGE = 9;
34 [GtkTemplate (ui = "/org/gnome/Cheese/cheese-main-window.ui")]
35 public class Cheese.MainWindow : Gtk.ApplicationWindow
37 private const GLib.ActionEntry actions[] = {
38 { "file-open", on_file_open },
39 { "file-saveas", on_file_saveas },
40 { "file-trash", on_file_trash },
41 { "file-delete", on_file_delete },
42 { "effects-next", on_effects_next },
43 { "effects-previous", on_effects_previous }
46 private MediaMode current_mode;
48 private Clutter.Script clutter_builder;
50 private Gtk.Builder header_bar_ui = new Gtk.Builder.from_resource ("/org/gnome/Cheese/headerbar.ui");
52 private Gtk.HeaderBar header_bar;
54 private GLib.Settings settings;
56 [GtkChild]
57 private GtkClutter.Embed viewport_widget;
58 [GtkChild]
59 private Gtk.Widget main_vbox;
60 private Eog.ThumbNav thumb_nav;
61 private Cheese.ThumbView thumb_view;
62 [GtkChild]
63 private Gtk.Box thumbnails_right;
64 [GtkChild]
65 private Gtk.Box thumbnails_bottom;
66 [GtkChild]
67 private Gtk.Widget leave_fullscreen_button_box;
68 [GtkChild]
69 private Gtk.Button take_action_button;
70 [GtkChild]
71 private Gtk.Image take_action_button_image;
72 [GtkChild]
73 private Gtk.ToggleButton effects_toggle_button;
74 [GtkChild]
75 private Gtk.Widget buttons_area;
76 [GtkChild]
77 private Gtk.Button switch_camera_button;
78 private Gtk.Menu thumbnail_popup;
80 private Clutter.Stage viewport;
81 private Clutter.Actor viewport_layout;
82 private Clutter.Actor video_preview;
83 private Clutter.BinLayout viewport_layout_manager;
84 private Clutter.Text countdown_layer;
85 private Clutter.Actor background_layer;
86 private Clutter.Text error_layer;
87 private Clutter.Text timeout_layer;
89 private Clutter.Actor current_effects_grid;
90 private uint current_effects_page = 0;
91 private List<Clutter.Actor> effects_grids;
93 private bool is_fullscreen;
94 private bool is_wide_mode;
95 private bool is_recording; /* Video Recording Flag */
96 private bool is_bursting;
97 private bool is_effects_selector_active;
98 private bool action_cancelled;
99 private bool was_maximized;
101 private Cheese.Camera camera;
102 private Cheese.FileUtil fileutil;
103 private Cheese.Flash flash;
105 private Cheese.EffectsManager effects_manager;
107 private Cheese.Effect selected_effect;
110 * Responses from the delete files confirmation dialog.
112 * @param SKIP skip a single file
113 * @param SKIP_ALL skill all following files
115 enum DeleteResponse
117 SKIP = 1,
118 SKIP_ALL = 2
121 public MainWindow (Gtk.Application application)
123 GLib.Object (application: application);
125 header_bar = header_bar_ui.get_object ("header_bar") as Gtk.HeaderBar;
127 Gtk.Settings settings = Gtk.Settings.get_default ();
129 if (settings.gtk_dialogs_use_header)
131 header_bar.visible = true;
132 this.set_titlebar (header_bar);
136 private void set_window_title (string title)
138 header_bar.set_title (title);
139 this.set_title (title);
142 private bool on_window_state_change_event (Gtk.Widget widget,
143 Gdk.EventWindowState event)
145 was_maximized = (((event.new_window_state - event.changed_mask)
146 & Gdk.WindowState.MAXIMIZED) != 0);
148 window_state_event.disconnect (on_window_state_change_event);
149 return false;
152 private void do_thumb_view_popup_menu (Gtk.Widget widget,
153 uint button,
154 uint time)
156 thumbnail_popup.popup (null, widget, null, button, time);
159 private bool on_thumb_view_popup_menu (Gtk.Widget thumbview)
161 do_thumb_view_popup_menu (thumbview, 0, 0);
163 return true;
167 * Popup a context menu when right-clicking on a thumbnail.
169 * @param iconview the thumbnail view that emitted the signal
170 * @param event the event
171 * @return false to allow further processing of the event, true to indicate
172 * that the event was handled completely
174 public bool on_thumbnail_button_press_event (Gtk.Widget iconview,
175 Gdk.EventButton event)
177 Gtk.TreePath path;
178 path = thumb_view.get_path_at_pos ((int) event.x, (int) event.y);
180 if (path == null)
182 return false;
185 if (!thumb_view.path_is_selected (path))
187 thumb_view.unselect_all ();
188 thumb_view.select_path (path);
189 thumb_view.set_cursor (path, null, false);
192 if (event.type == Gdk.EventType.BUTTON_PRESS)
194 Gdk.Event* button_press = (Gdk.Event*)(event);
196 if (button_press->triggers_context_menu ())
198 do_thumb_view_popup_menu (thumb_view, event.button,
199 event.time);
200 return true;
203 else if (event.type == Gdk.EventType.2BUTTON_PRESS)
205 on_file_open ();
206 return true;
209 return false;
213 * Open an image associated with a thumbnail in the default application.
215 private void on_file_open ()
217 string filename, uri;
219 Gdk.Screen screen;
220 filename = thumb_view.get_selected_image ();
222 if (filename == null)
223 return; /* Nothing selected. */
227 uri = GLib.Filename.to_uri (filename);
228 screen = this.get_screen ();
229 Gtk.show_uri (screen, uri, Gtk.get_current_event_time ());
231 catch (Error err)
233 MessageDialog error_dialog = new MessageDialog (this,
234 Gtk.DialogFlags.MODAL |
235 Gtk.DialogFlags.DESTROY_WITH_PARENT,
236 Gtk.MessageType.ERROR,
237 Gtk.ButtonsType.OK,
238 _("Could not open %s"),
239 filename);
241 error_dialog.run ();
242 error_dialog.destroy ();
247 * Delete the requested image or images in the thumbview from storage.
249 * A confirmation dialog is shown to the user before deleting any files.
251 private void on_file_delete ()
253 int response;
254 int error_response;
255 bool skip_all_errors = false;
257 var files = thumb_view.get_selected_images_list ();
258 var files_length = files.length ();
260 var confirmation_dialog = new MessageDialog.with_markup (this,
261 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
262 Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE,
263 GLib.ngettext("Are you sure you want to permanently delete the file?",
264 "Are you sure you want to permanently delete %d files?",
265 files_length), files_length);
266 confirmation_dialog.add_button (_("_Cancel"), Gtk.ResponseType.CANCEL);
267 confirmation_dialog.add_button (_("_Delete"), Gtk.ResponseType.ACCEPT);
268 confirmation_dialog.format_secondary_text ("%s",
269 GLib.ngettext("If you delete an item, it will be permanently lost",
270 "If you delete the items, they will be permanently lost",
271 files_length));
273 response = confirmation_dialog.run ();
274 if (response == Gtk.ResponseType.ACCEPT)
276 foreach (var file in files)
278 if (file == null)
279 return;
283 file.delete (null);
285 catch (Error err)
287 warning ("Unable to delete file: %s", err.message);
289 if (!skip_all_errors) {
290 var error_dialog = new MessageDialog (this,
291 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
292 Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE,
293 _("Could not delete %s"), file.get_path ());
295 error_dialog.add_button (_("_Cancel"), Gtk.ResponseType.CANCEL);
296 error_dialog.add_button (_("Skip"), DeleteResponse.SKIP);
297 error_dialog.add_button (_("Skip all"), DeleteResponse.SKIP_ALL);
299 error_response = error_dialog.run ();
300 if (error_response == DeleteResponse.SKIP_ALL) {
301 skip_all_errors = true;
302 } else if (error_response == Gtk.ResponseType.CANCEL) {
303 break;
306 error_dialog.destroy ();
311 confirmation_dialog.destroy ();
315 * Move the requested image in the thumbview to the trash.
317 * A confirmation dialog is shown to the user before moving the file.
319 private void on_file_trash ()
321 File file;
323 GLib.List<GLib.File> files = thumb_view.get_selected_images_list ();
325 for (int i = 0; i < files.length (); i++)
327 file = files<GLib.File>.nth (i).data;
328 if (file == null)
329 return;
333 file.trash (null);
335 catch (Error err)
337 MessageDialog error_dialog = new MessageDialog (this,
338 Gtk.DialogFlags.MODAL |
339 Gtk.DialogFlags.DESTROY_WITH_PARENT,
340 Gtk.MessageType.ERROR,
341 Gtk.ButtonsType.OK,
342 _("Could not move %s to trash"),
343 file.get_path ());
345 error_dialog.run ();
346 error_dialog.destroy ();
352 * Save the selected file in the thumbview to an alternate storage location.
354 * A file chooser dialog is shown to the user, asking where the file should
355 * be saved and the filename.
357 private void on_file_saveas ()
359 string filename, basename;
360 FileChooserDialog save_as_dialog;
361 int response;
363 filename = thumb_view.get_selected_image ();
364 if (filename == null)
365 return; /* Nothing selected. */
367 save_as_dialog = new FileChooserDialog (_("Save File"),
368 this,
369 Gtk.FileChooserAction.SAVE,
370 _("_Cancel"), Gtk.ResponseType.CANCEL,
371 _("Save"), Gtk.ResponseType.ACCEPT,
372 null);
374 save_as_dialog.do_overwrite_confirmation = true;
375 basename = GLib.Filename.display_basename (filename);
376 save_as_dialog.set_current_name (basename);
377 save_as_dialog.set_current_folder (GLib.Environment.get_home_dir ());
379 response = save_as_dialog.run ();
381 save_as_dialog.hide ();
382 if (response == Gtk.ResponseType.ACCEPT)
384 string target_filename;
385 target_filename = save_as_dialog.get_filename ();
387 File src = File.new_for_path (filename);
388 File dest = File.new_for_path (target_filename);
392 src.copy (dest, FileCopyFlags.OVERWRITE, null, null);
394 catch (Error err)
396 MessageDialog error_dialog = new MessageDialog (this,
397 Gtk.DialogFlags.MODAL |
398 Gtk.DialogFlags.DESTROY_WITH_PARENT,
399 Gtk.MessageType.ERROR,
400 Gtk.ButtonsType.OK,
401 _("Could not save %s"),
402 target_filename);
404 error_dialog.run ();
405 error_dialog.destroy ();
408 save_as_dialog.destroy ();
412 * Toggle fullscreen mode.
414 * @param fullscreen whether the window should be fullscreen
416 public void set_fullscreen (bool fullscreen)
418 set_fullscreen_mode (fullscreen);
422 * Make the media capture mode actions sensitive.
424 private void enable_mode_change ()
426 var mode = this.application.lookup_action ("mode") as SimpleAction;
427 mode.set_enabled (true);
428 var effects = this.application.lookup_action ("effects") as SimpleAction;
429 effects.set_enabled (true);
430 var preferences = this.application.lookup_action ("preferences") as SimpleAction;
431 preferences.set_enabled (true);
435 * Make the media capture mode actions insensitive.
437 private void disable_mode_change ()
439 var mode = this.application.lookup_action ("mode") as SimpleAction;
440 mode.set_enabled (false);
441 var effects = this.application.lookup_action ("effects") as SimpleAction;
442 effects.set_enabled (false);
443 var preferences = this.application.lookup_action ("preferences") as SimpleAction;
444 preferences.set_enabled (false);
448 * Set the capture resolution, based on the current capture mode.
450 * @param mode the current capture mode (photo, video or burst)
452 private void set_resolution(MediaMode mode)
454 if (camera == null)
455 return;
457 var formats = camera.get_video_formats ();
459 if (formats == null)
460 return;
462 unowned Cheese.VideoFormat format;
463 int width = 0;
464 int height = 0;
466 switch (mode)
468 case MediaMode.PHOTO:
469 case MediaMode.BURST:
470 width = settings.get_int ("photo-x-resolution");
471 height = settings.get_int ("photo-y-resolution");
472 break;
473 case MediaMode.VIDEO:
474 width = settings.get_int ("video-x-resolution");
475 height = settings.get_int ("video-y-resolution");
476 break;
479 for (int i = 0; i < formats.length (); i++)
481 format = formats<VideoFormat>.nth (i).data;
482 if (width == format.width && height == format.height)
484 camera.set_video_format (format);
485 break;
490 private TimeoutSource fullscreen_timeout;
492 * Clear the fullscreen activity timeout.
494 private void clear_fullscreen_timeout ()
496 if (fullscreen_timeout != null)
498 fullscreen_timeout.destroy ();
499 fullscreen_timeout = null;
504 * Set the fullscreen timeout, for hiding the UI if there is no mouse
505 * movement.
507 private void set_fullscreen_timeout ()
509 fullscreen_timeout = new TimeoutSource (FULLSCREEN_TIMEOUT_INTERVAL);
510 fullscreen_timeout.attach (null);
511 fullscreen_timeout.set_callback (() => {buttons_area.hide ();
512 clear_fullscreen_timeout ();
513 this.fullscreen ();
514 return true; });
518 * Show the UI in fullscreen if there is any mouse activity.
520 * Start a new timeout at the end of every mouse pointer movement. All
521 * timeouts will be cancelled, except one created during the last movement
522 * event. Show() is called even if the button is not hidden.
524 * @param viewport the widget to check for mouse activity on
525 * @param e the (unused) event
527 private bool fullscreen_motion_notify_callback (Gtk.Widget viewport,
528 EventMotion e)
530 clear_fullscreen_timeout ();
531 this.unfullscreen ();
532 this.maximize ();
533 buttons_area.show ();
534 set_fullscreen_timeout ();
535 return true;
539 * Enable or disable fullscreen mode to the requested state.
541 * @param fullscreen_mode whether to enable or disable fullscreen mode
543 private void set_fullscreen_mode (bool fullscreen)
545 /* After the first time the window has been shown using this.show_all (),
546 * the value of leave_fullscreen_button_box.no_show_all should be set to false
547 * So that the next time leave_fullscreen_button_box.show_all () is called, the button is actually shown
548 * FIXME: If this code can be made cleaner/clearer, please do */
550 is_fullscreen = fullscreen;
551 if (fullscreen)
553 window_state_event.connect (on_window_state_change_event);
555 if (is_wide_mode)
557 thumbnails_right.hide ();
559 else
561 thumbnails_bottom.hide ();
563 leave_fullscreen_button_box.no_show_all = false;
564 leave_fullscreen_button_box.show_all ();
566 this.fullscreen ();
567 viewport_widget.motion_notify_event.connect (fullscreen_motion_notify_callback);
568 set_fullscreen_timeout ();
570 else
572 if (is_wide_mode)
574 thumbnails_right.show_all ();
576 else
578 thumbnails_bottom.show_all ();
580 leave_fullscreen_button_box.hide ();
582 /* Stop timer so buttons_area does not get hidden after returning from
583 * fullscreen mode */
584 clear_fullscreen_timeout ();
585 /* Show the buttons area anyway - it might've been hidden in fullscreen mode */
586 buttons_area.show ();
587 viewport_widget.motion_notify_event.disconnect (fullscreen_motion_notify_callback);
588 this.unfullscreen ();
590 if (was_maximized)
592 this.maximize ();
594 else
596 this.unmaximize ();
602 * Enable or disable wide mode to the requested state.
604 * @param wide_mode whether to enable or disable wide mode
606 public void set_wide_mode (bool wide_mode)
608 is_wide_mode = wide_mode;
610 /* keep the viewport to its current size while rearranging the ui,
611 * so that thumbview moves from right to bottom and viceversa
612 * while the rest of the window stays unchanged */
613 Gtk.Allocation alloc;
614 viewport_widget.get_allocation (out alloc);
615 viewport_widget.set_size_request (alloc.width, alloc.height);
617 if (is_wide_mode)
619 thumb_view.set_vertical (true);
620 thumb_nav.set_vertical (true);
621 if (thumbnails_bottom.get_children () != null)
623 thumbnails_bottom.remove (thumb_nav);
625 thumbnails_right.add (thumb_nav);
627 if (!is_fullscreen)
629 thumbnails_right.show_all ();
630 thumbnails_bottom.hide ();
633 else
635 thumb_view.set_vertical (false);
636 thumb_nav.set_vertical (false);
637 if (thumbnails_right.get_children () != null)
639 thumbnails_right.remove (thumb_nav);
641 thumbnails_bottom.add (thumb_nav);
643 if (!is_fullscreen)
645 thumbnails_bottom.show_all ();
646 thumbnails_right.hide ();
650 /* handy trick to keep the window to the desired size while not
651 * requesting a fixed one. This way the window is resized to its
652 * natural size (particularly with the constraints imposed by the
653 * viewport, see above) but can still be shrinked down */
655 Gtk.Requisition req;
656 this.get_preferred_size(out req, out req);
657 this.resize (req.width, req.height);
658 viewport_widget.set_size_request (-1, -1);
662 * Make sure that the layout manager manages the entire stage.
664 * @param actor unused
665 * @param box unused
666 * @param flags unused
668 public void on_stage_resize (Clutter.Actor actor,
669 Clutter.ActorBox box,
670 Clutter.AllocationFlags flags)
672 this.viewport_layout.set_size (viewport.width, viewport.height);
673 this.background_layer.set_size (viewport.width, viewport.height);
674 this.timeout_layer.set_position (video_preview.width/3 + viewport.width/2,
675 viewport.height-20);
679 * The method to call when the countdown is finished.
681 private void finish_countdown_callback ()
683 if (action_cancelled == false)
685 string file_name = fileutil.get_new_media_filename (this.current_mode);
687 if (settings.get_boolean ("flash"))
689 this.flash.fire ();
691 CanberraGtk.play_for_widget (this.main_vbox, 0,
692 Canberra.PROP_EVENT_ID, "camera-shutter",
693 Canberra.PROP_MEDIA_ROLE, "event",
694 Canberra.PROP_EVENT_DESCRIPTION, _("Shutter sound"),
695 null);
696 this.camera.take_photo (file_name);
699 if (current_mode == MediaMode.PHOTO)
701 enable_mode_change ();
705 Countdown current_countdown;
707 * Start to take a photo, starting a countdown if it is enabled.
709 public void take_photo ()
711 if (settings.get_boolean ("countdown"))
713 if (current_mode == MediaMode.PHOTO)
715 disable_mode_change ();
718 current_countdown = new Countdown (this.countdown_layer);
719 current_countdown.start (finish_countdown_callback);
721 else
723 finish_countdown_callback ();
727 private int burst_count;
728 private uint burst_callback_id;
731 * Take a photo during burst mode, and increment the burst count.
733 * @return true if there are more photos to be taken in the current burst,
734 * false otherwise
736 private bool burst_take_photo ()
738 if (is_bursting && burst_count < settings.get_int ("burst-repeat"))
740 this.take_photo ();
741 burst_count++;
742 return true;
744 else
746 toggle_photo_bursting (false);
747 return false;
752 * Cancel the current action (if any)
754 private bool cancel_running_action ()
756 if ((current_countdown != null && current_countdown.running)
757 || is_bursting || is_recording)
759 action_cancelled = true;
761 switch (current_mode)
763 case MediaMode.PHOTO:
764 current_countdown.stop ();
765 finish_countdown_callback ();
766 break;
767 case MediaMode.BURST:
768 toggle_photo_bursting (false);
769 break;
770 case MediaMode.VIDEO:
771 toggle_video_recording (false);
772 break;
775 action_cancelled = false;
777 return true;
780 return false;
784 * Cancel the current activity if the escape key is pressed.
786 * @param event the key event, to check which key was pressed
787 * @return false, to allow further processing of the event
789 private bool on_key_release (Gdk.EventKey event)
791 string key;
793 key = Gdk.keyval_name (event.keyval);
794 if (strcmp (key, "Escape") == 0)
796 if (cancel_running_action ())
798 return false;
800 else if (is_effects_selector_active)
802 effects_toggle_button.set_active (false);
805 return false;
809 * Toggle whether video recording is active.
811 * @param is_start whether to start video recording
813 public void toggle_video_recording (bool is_start)
815 if (is_start)
817 camera.start_video_recording (fileutil.get_new_media_filename (this.current_mode));
818 /* Will be called every 1 second while
819 * update_timeout_layer returns true.
821 Timeout.add_seconds (1, update_timeout_layer);
822 take_action_button.tooltip_text = _("Stop recording");
823 take_action_button_image.set_from_icon_name ("media-playback-stop-symbolic", Gtk.IconSize.BUTTON);
824 this.is_recording = true;
825 this.disable_mode_change ();
827 else
829 camera.stop_video_recording ();
830 /* The timeout_layer always shows the "00:00:00"
831 * string when not recording, in order to notify
832 * the user about two things:
833 * + The user is making use of the recording mode.
834 * + The user is currently not recording.
836 timeout_layer.text = "00:00:00";
837 take_action_button.tooltip_text = _("Record a video");
838 take_action_button_image.set_from_icon_name ("camera-web-symbolic", Gtk.IconSize.BUTTON);
839 this.is_recording = false;
840 this.enable_mode_change ();
845 * Update the timeout layer displayed timer.
847 * @return false, if the source, Timeout.add_seconds (used
848 * in the toogle_video_recording method), should be removed.
850 private bool update_timeout_layer ()
852 if (is_recording) {
853 timeout_layer.text = camera.get_recorded_time ();
854 return true;
856 else
857 return false;
861 * Toggle whether photo bursting is active.
863 * @param is_start whether to start capturing a photo burst
865 public void toggle_photo_bursting (bool is_start)
867 if (is_start)
869 is_bursting = true;
870 this.disable_mode_change ();
871 // FIXME: Set the effects action to be inactive.
872 take_action_button.tooltip_text = _("Stop taking pictures");
873 burst_take_photo ();
875 /* Use the countdown duration if it is greater than the burst delay, plus
876 * about 500 ms for taking the photo. */
877 var burst_delay = settings.get_int ("burst-delay");
878 var countdown_duration = 500 + settings.get_int ("countdown-duration") * 1000;
879 if ((burst_delay - countdown_duration) < 1000 && settings.get_boolean ("countdown"))
881 burst_callback_id = GLib.Timeout.add (countdown_duration, burst_take_photo);
883 else
885 burst_callback_id = GLib.Timeout.add (burst_delay, burst_take_photo);
888 else
890 if (current_countdown != null && current_countdown.running)
891 current_countdown.stop ();
893 is_bursting = false;
894 this.enable_mode_change ();
895 take_action_button.tooltip_text = _("Take multiple photos");
896 burst_count = 0;
897 fileutil.reset_burst ();
898 GLib.Source.remove (burst_callback_id);
903 * Take a photo or burst of photos, or record a video, based on the current
904 * capture mode.
906 public void shoot ()
908 switch (current_mode)
910 case MediaMode.PHOTO:
911 take_photo ();
912 break;
913 case MediaMode.VIDEO:
914 toggle_video_recording (!is_recording);
915 break;
916 case MediaMode.BURST:
917 toggle_photo_bursting (!is_bursting);
918 break;
919 default:
920 assert_not_reached ();
925 * Show an error.
927 * @param error the error to display, or null to hide the error layer
929 public void show_error (string? error)
931 if (error != null)
933 current_effects_grid.hide ();
934 video_preview.hide ();
935 error_layer.text = error;
936 error_layer.show ();
938 else
940 error_layer.hide ();
942 if (is_effects_selector_active)
944 current_effects_grid.show ();
946 else
948 video_preview.show ();
954 * Toggle the display of the effect selector.
956 * @param effects whether effects should be enabled
958 public void set_effects (bool effects)
960 toggle_effects_selector (effects);
964 * Change the selected effect, as a new one was selected.
966 * @param tap unused
967 * @param source the actor (with associated effect) that was selected
969 public void on_selected_effect_change (Clutter.TapAction tap,
970 Clutter.Actor source)
972 /* Disable the effects selector after selecting an effect. */
973 effects_toggle_button.set_active (false);
975 selected_effect = source.get_data ("effect");
976 camera.set_effect (selected_effect);
977 settings.set_string ("selected-effect", selected_effect.name);
981 * Navigate back one page of effects.
983 private void on_effects_previous ()
985 if (is_previous_effects_page ())
987 activate_effects_page ((int)current_effects_page - 1);
992 * Navigate forward one page of effects.
994 private void on_effects_next ()
996 if (is_next_effects_page ())
998 activate_effects_page ((int)current_effects_page + 1);
1003 * Switch to the supplied page of effects.
1005 * @param number the effects page to switch to
1007 private void activate_effects_page (int number)
1009 if (!is_effects_selector_active)
1010 return;
1011 current_effects_page = number;
1012 if (viewport_layout.get_children ().index (current_effects_grid) != -1)
1014 viewport_layout.remove_child (current_effects_grid);
1016 current_effects_grid = effects_grids.nth_data (number);
1017 current_effects_grid.opacity = 0;
1018 viewport_layout.add_child (current_effects_grid);
1019 current_effects_grid.save_easing_state ();
1020 current_effects_grid.set_easing_mode (Clutter.AnimationMode.LINEAR);
1021 current_effects_grid.set_easing_duration (500);
1022 current_effects_grid.opacity = 255;
1023 current_effects_grid.restore_easing_state ();
1026 uint i = 0;
1027 foreach (var effect in effects_manager.effects)
1029 uint page_nr = i / EFFECTS_PER_PAGE;
1030 if (page_nr == number)
1032 if (!effect.is_preview_connected ())
1034 Clutter.Actor texture = effect.get_data<Clutter.Actor> ("texture");
1035 camera.connect_effect_texture (effect, texture);
1037 effect.enable_preview ();
1039 else
1041 if (effect.is_preview_connected ())
1043 effect.disable_preview ();
1047 i++;
1050 setup_effects_page_switch_sensitivity ();
1054 * Control the sensitivity of the effects page navigation buttons.
1056 private void setup_effects_page_switch_sensitivity ()
1058 var effects_next = this.lookup_action ("effects-next") as SimpleAction;
1059 var effects_previous = this.lookup_action ("effects-previous") as SimpleAction;
1061 effects_next.set_enabled (is_effects_selector_active
1062 && is_next_effects_page ());
1063 effects_previous.set_enabled (is_effects_selector_active
1064 && is_previous_effects_page ());
1067 private bool is_next_effects_page ()
1069 // Calculate the number of effects visible up to the current page.
1070 return (current_effects_page + 1) * EFFECTS_PER_PAGE < effects_manager.effects.length ();
1073 private bool is_previous_effects_page ()
1075 return current_effects_page != 0;
1079 * Toggle the visibility of the effects selector.
1081 * @param active whether the selector should be active
1083 private void toggle_effects_selector (bool active)
1085 is_effects_selector_active = active;
1087 if (effects_grids.length () == 0)
1089 show_error (active ? _("No effects found") : null);
1091 else if (active)
1093 video_preview.hide ();
1094 current_effects_grid.show ();
1095 activate_effects_page ((int)current_effects_page);
1097 else
1099 current_effects_grid.hide ();
1100 video_preview.show ();
1103 camera.toggle_effects_pipeline (active);
1104 setup_effects_page_switch_sensitivity ();
1105 update_header_bar_title ();
1109 * Create the effects selector.
1111 private void setup_effects_selector ()
1113 if (current_effects_grid == null)
1115 effects_manager = new EffectsManager ();
1116 effects_manager.load_effects ();
1118 /* Must initialize effects_grids before returning, as it is dereferenced later, bug 654671. */
1119 effects_grids = new List<Clutter.Actor> ();
1121 if (effects_manager.effects.length () == 0)
1123 warning ("gnome-video-effects is not installed.");
1124 return;
1127 foreach (var effect in effects_manager.effects)
1129 Clutter.GridLayout grid_layout = new GridLayout ();
1130 var grid = new Clutter.Actor ();
1131 grid.set_layout_manager (grid_layout);
1132 effects_grids.append (grid);
1133 grid_layout.set_column_spacing (10);
1134 grid_layout.set_row_spacing (10);
1137 uint i = 0;
1138 foreach (var effect in effects_manager.effects)
1140 Clutter.Actor texture = new Clutter.Actor ();
1141 Clutter.BinLayout layout = new Clutter.BinLayout (Clutter.BinAlignment.CENTER,
1142 Clutter.BinAlignment.CENTER);
1143 var box = new Clutter.Actor ();
1144 box.set_layout_manager (layout);
1145 Clutter.Text text = new Clutter.Text ();
1146 var rect = new Clutter.Actor ();
1148 rect.opacity = 128;
1149 rect.background_color = Clutter.Color.from_string ("black");
1151 texture.content_gravity = Clutter.ContentGravity.RESIZE_ASPECT;
1152 box.add_child (texture);
1153 box.reactive = true;
1154 box.min_height = 40;
1155 box.min_width = 50;
1156 var tap = new Clutter.TapAction ();
1157 box.add_action (tap);
1158 tap.tap.connect (on_selected_effect_change);
1159 box.set_data ("effect", effect);
1160 effect.set_data ("texture", texture);
1162 text.text = effect.name;
1163 text.color = Clutter.Color.from_string ("white");
1165 rect.height = text.height + 5;
1166 rect.x_align = Clutter.ActorAlign.FILL;
1167 rect.y_align = Clutter.ActorAlign.END;
1168 rect.x_expand = true;
1169 rect.y_expand = true;
1170 box.add_child (rect);
1172 text.x_align = Clutter.ActorAlign.CENTER;
1173 text.y_align = Clutter.ActorAlign.END;
1174 text.x_expand = true;
1175 text.y_expand = true;
1176 box.add_child (text);
1178 var grid_layout = effects_grids.nth_data (i / EFFECTS_PER_PAGE).layout_manager as GridLayout;
1179 grid_layout.attach (box, ((int)(i % EFFECTS_PER_PAGE)) % 3,
1180 ((int)(i % EFFECTS_PER_PAGE)) / 3, 1, 1);
1182 i++;
1185 setup_effects_page_switch_sensitivity ();
1186 current_effects_grid = effects_grids.nth_data (0);
1191 * Update the UI when the camera starts playing.
1193 public void camera_state_change_playing ()
1195 show_error (null);
1197 Effect effect = effects_manager.get_effect (settings.get_string ("selected-effect"));
1198 if (effect != null)
1200 camera.set_effect (effect);
1205 * Report an error as the camerabin switched to the NULL state.
1207 public void camera_state_change_null ()
1209 cancel_running_action ();
1211 if (!error_layer.visible)
1213 show_error (_("There was an error playing video from the webcam"));
1218 * Select next camera in list and activate it.
1220 public void on_switch_camera_clicked ()
1222 Cheese.CameraDevice selected;
1223 Cheese.CameraDevice next = null;
1224 GLib.PtrArray cameras;
1225 uint i;
1227 if (camera == null)
1229 return;
1232 selected = camera.get_selected_device ();
1234 if (selected == null)
1236 return;
1239 cameras = camera.get_camera_devices ();
1241 for (i = 0; i < cameras.len; i++)
1243 next = (Cheese.CameraDevice )cameras.index (i);
1245 if (next == selected)
1247 break;
1251 if (i + 1 < cameras.len)
1253 next = (Cheese.CameraDevice )cameras.index (i + 1);
1255 else
1257 next = (Cheese.CameraDevice )cameras.index (0);
1260 if (next == selected)
1262 /* Next is the same device.... */
1263 return;
1266 camera.set_device (next);
1267 camera.switch_camera_device ();
1271 * Set switch camera buttons visible state.
1273 public void set_switch_camera_button_state ()
1275 Cheese.CameraDevice selected;
1276 GLib.PtrArray cameras;
1278 if (camera == null)
1280 switch_camera_button.set_visible (false);
1281 return;
1284 selected = camera.get_selected_device ();
1286 if (selected == null)
1288 switch_camera_button.set_visible (false);
1289 return;
1292 cameras = camera.get_camera_devices ();
1294 if (cameras.len > 1)
1296 switch_camera_button.set_visible (true);
1297 return;
1300 switch_camera_button.set_visible (false);
1304 * Load the UI from the GtkBuilder description.
1306 public void setup_ui ()
1308 clutter_builder = new Clutter.Script ();
1309 fileutil = new FileUtil ();
1310 flash = new Flash (this);
1311 settings = new GLib.Settings ("org.gnome.Cheese");
1313 var menu = application.get_menu_by_id ("thumbview-menu");
1314 thumbnail_popup = new Gtk.Menu.from_model (menu);
1316 this.add_action_entries (actions, this);
1320 clutter_builder.load_from_resource ("/org/gnome/Cheese/cheese-viewport.json");
1322 catch (Error err)
1324 error ("Error: %s", err.message);
1327 viewport = viewport_widget.get_stage () as Clutter.Stage;
1329 video_preview = clutter_builder.get_object ("video_preview") as Clutter.Actor;
1330 viewport_layout = clutter_builder.get_object ("viewport_layout") as Clutter.Actor;
1331 viewport_layout_manager = clutter_builder.get_object ("viewport_layout_manager") as Clutter.BinLayout;
1332 countdown_layer = clutter_builder.get_object ("countdown_layer") as Clutter.Text;
1333 background_layer = clutter_builder.get_object ("background") as Clutter.Actor;
1334 error_layer = clutter_builder.get_object ("error_layer") as Clutter.Text;
1335 timeout_layer = clutter_builder.get_object ("timeout_layer") as Clutter.Text;
1337 video_preview.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
1338 viewport.add_child (background_layer);
1339 viewport_layout.set_layout_manager (viewport_layout_manager);
1341 viewport.add_child (viewport_layout);
1342 viewport.add_child (timeout_layer);
1344 viewport.allocation_changed.connect (on_stage_resize);
1346 thumb_view = new Cheese.ThumbView ();
1347 thumb_nav = new Eog.ThumbNav (thumb_view, false);
1348 thumbnail_popup.attach_to_widget (thumb_view, null);
1349 thumb_view.popup_menu.connect (on_thumb_view_popup_menu);
1351 Gtk.CssProvider css;
1354 var file = File.new_for_uri ("resource:///org/gnome/Cheese/cheese.css");
1355 css = new Gtk.CssProvider ();
1356 css.load_from_file (file);
1358 catch (Error e)
1360 // TODO: Use parsing-error signal.
1361 error ("Error parsing CSS: %s\n", e.message);
1364 Gtk.StyleContext.add_provider_for_screen (screen, css, STYLE_PROVIDER_PRIORITY_USER);
1366 thumb_view.button_press_event.connect (on_thumbnail_button_press_event);
1368 switch_camera_button.clicked.connect (on_switch_camera_clicked);
1370 /* needed for the sizing tricks in set_wide_mode (allocation is 0
1371 * if the widget is not realized */
1372 viewport_widget.realize ();
1374 set_wide_mode (false);
1376 setup_effects_selector ();
1378 this.key_release_event.connect (on_key_release);
1381 public Clutter.Actor get_video_preview ()
1383 return video_preview;
1387 * Setup the thumbview thumbnail monitors.
1389 public void start_thumbview_monitors ()
1391 thumb_view.start_monitoring_video_path (fileutil.get_video_path ());
1392 thumb_view.start_monitoring_photo_path (fileutil.get_photo_path ());
1396 * Set the current media mode (photo, video or burst).
1398 * @param mode the media mode to set
1400 public void set_current_mode (MediaMode mode)
1402 current_mode = mode;
1404 set_resolution (current_mode);
1405 update_header_bar_title ();
1406 timeout_layer.hide ();
1408 switch (current_mode)
1410 case MediaMode.PHOTO:
1411 take_action_button.tooltip_text = _("Take a photo using a webcam");
1412 break;
1414 case MediaMode.VIDEO:
1415 take_action_button.tooltip_text = _("Record a video using a webcam");
1416 timeout_layer.text = "00:00:00";
1417 timeout_layer.show ();
1418 break;
1420 case MediaMode.BURST:
1421 take_action_button.tooltip_text = _("Take multiple photos using a webcam");
1422 break;
1427 * Set the header bar title.
1429 private void update_header_bar_title ()
1431 if (is_effects_selector_active)
1433 set_window_title (_("Choose an Effect"));
1435 else
1437 switch (current_mode)
1439 case MediaMode.PHOTO:
1440 set_window_title (_("Take a Photo"));
1441 break;
1443 case MediaMode.VIDEO:
1444 set_window_title (_("Record a Video"));
1445 break;
1447 case MediaMode.BURST:
1448 set_window_title (_("Take Multiple Photos"));
1449 break;
1454 * Set the camera.
1456 * @param camera the camera to set
1458 public void set_camera (Camera camera)
1460 this.camera = camera;
1461 set_switch_camera_button_state ();