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/>.
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
;
57 private GtkClutter
.Embed viewport_widget
;
59 private Gtk
.Widget main_vbox
;
60 private Eog
.ThumbNav thumb_nav
;
61 private Cheese
.ThumbView thumb_view
;
63 private Gtk
.Box thumbnails_right
;
65 private Gtk
.Box thumbnails_bottom
;
67 private Gtk
.Widget leave_fullscreen_button_box
;
69 private Gtk
.Button take_action_button
;
71 private Gtk
.Image take_action_button_image
;
73 private Gtk
.ToggleButton effects_toggle_button
;
75 private Gtk
.Widget buttons_area
;
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
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
);
152 private void do_thumb_view_popup_menu (Gtk
.Widget widget
,
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);
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
)
178 path
= thumb_view
.get_path_at_pos ((int) event
.x
, (int) event
.y
);
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
,
203 else if (event
.type
== Gdk
.EventType
.2BUTTON_PRESS
)
213 * Open an image associated with a thumbnail in the default application.
215 private void on_file_open ()
217 string filename
, uri
;
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 ());
233 MessageDialog error_dialog
= new
MessageDialog (this
,
234 Gtk
.DialogFlags
.MODAL
|
235 Gtk
.DialogFlags
.DESTROY_WITH_PARENT
,
236 Gtk
.MessageType
.ERROR
,
238 _("Could not open %s"),
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 ()
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",
273 response
= confirmation_dialog
.run ();
274 if (response
== Gtk
.ResponseType
.ACCEPT
)
276 foreach (var file
in files
)
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
) {
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 ()
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
;
337 MessageDialog error_dialog
= new
MessageDialog (this
,
338 Gtk
.DialogFlags
.MODAL
|
339 Gtk
.DialogFlags
.DESTROY_WITH_PARENT
,
340 Gtk
.MessageType
.ERROR
,
342 _("Could not move %s to trash"),
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
;
363 filename
= thumb_view
.get_selected_image ();
364 if (filename
== null)
365 return; /* Nothing selected. */
367 save_as_dialog
= new
FileChooserDialog (_("Save File"),
369 Gtk
.FileChooserAction
.SAVE
,
370 _("_Cancel"), Gtk
.ResponseType
.CANCEL
,
371 _("Save"), Gtk
.ResponseType
.ACCEPT
,
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);
396 MessageDialog error_dialog
= new
MessageDialog (this
,
397 Gtk
.DialogFlags
.MODAL
|
398 Gtk
.DialogFlags
.DESTROY_WITH_PARENT
,
399 Gtk
.MessageType
.ERROR
,
401 _("Could not save %s"),
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
)
457 var formats
= camera
.get_video_formats ();
462 unowned Cheese
.VideoFormat format
;
468 case MediaMode
.PHOTO
:
469 case MediaMode
.BURST
:
470 width
= settings
.get_int ("photo-x-resolution");
471 height
= settings
.get_int ("photo-y-resolution");
473 case MediaMode
.VIDEO
:
474 width
= settings
.get_int ("video-x-resolution");
475 height
= settings
.get_int ("video-y-resolution");
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
);
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
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 ();
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
,
530 clear_fullscreen_timeout ();
531 this
.unfullscreen ();
533 buttons_area
.show ();
534 set_fullscreen_timeout ();
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
;
553 window_state_event
.connect (on_window_state_change_event
);
557 thumbnails_right
.hide ();
561 thumbnails_bottom
.hide ();
563 leave_fullscreen_button_box
.no_show_all
= false;
564 leave_fullscreen_button_box
.show_all ();
567 viewport_widget
.motion_notify_event
.connect (fullscreen_motion_notify_callback
);
568 set_fullscreen_timeout ();
574 thumbnails_right
.show_all ();
578 thumbnails_bottom
.show_all ();
580 leave_fullscreen_button_box
.hide ();
582 /* Stop timer so buttons_area does not get hidden after returning from
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 ();
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
);
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
);
629 thumbnails_right
.show_all ();
630 thumbnails_bottom
.hide ();
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
);
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 */
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
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,
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"))
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"),
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
);
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,
736 private bool burst_take_photo ()
738 if (is_bursting
&& burst_count
< settings
.get_int ("burst-repeat"))
746 toggle_photo_bursting (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 ();
767 case MediaMode
.BURST
:
768 toggle_photo_bursting (false);
770 case MediaMode
.VIDEO
:
771 toggle_video_recording (false);
775 action_cancelled
= 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
)
793 key
= Gdk
.keyval_name (event
.keyval
);
794 if (strcmp (key
, "Escape") == 0)
796 if (cancel_running_action ())
800 else if (is_effects_selector_active
)
802 effects_toggle_button
.set_active (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
)
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 ();
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 ()
853 timeout_layer
.text
= camera
.get_recorded_time ();
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
)
870 this
.disable_mode_change ();
871 // FIXME: Set the effects action to be inactive.
872 take_action_button
.tooltip_text
= _("Stop taking pictures");
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
);
885 burst_callback_id
= GLib
.Timeout
.add (burst_delay
, burst_take_photo
);
890 if (current_countdown
!= null && current_countdown
.running
)
891 current_countdown
.stop ();
894 this
.enable_mode_change ();
895 take_action_button
.tooltip_text
= _("Take multiple photos");
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
908 switch (current_mode
)
910 case MediaMode
.PHOTO
:
913 case MediaMode
.VIDEO
:
914 toggle_video_recording (!is_recording
);
916 case MediaMode
.BURST
:
917 toggle_photo_bursting (!is_bursting
);
920 assert_not_reached ();
927 * @param error the error to display, or null to hide the error layer
929 public void show_error (string? error
)
933 current_effects_grid
.hide ();
934 video_preview
.hide ();
935 error_layer
.text
= error
;
942 if (is_effects_selector_active
)
944 current_effects_grid
.show ();
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.
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
)
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 ();
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 ();
1041 if (effect
.is_preview_connected ())
1043 effect
.disable_preview ();
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);
1093 video_preview
.hide ();
1094 current_effects_grid
.show ();
1095 activate_effects_page ((int)current_effects_page
);
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.");
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);
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 ();
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;
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);
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 ()
1197 Effect effect
= effects_manager
.get_effect (settings
.get_string ("selected-effect"));
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
;
1232 selected
= camera
.get_selected_device ();
1234 if (selected
== null)
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
)
1251 if (i
+ 1 < cameras
.len
)
1253 next
= (Cheese
.CameraDevice
)cameras
.index (i
+ 1);
1257 next
= (Cheese
.CameraDevice
)cameras
.index (0);
1260 if (next
== selected
)
1262 /* Next is the same device.... */
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
;
1280 switch_camera_button
.set_visible (false);
1284 selected
= camera
.get_selected_device ();
1286 if (selected
== null)
1288 switch_camera_button
.set_visible (false);
1292 cameras
= camera
.get_camera_devices ();
1294 if (cameras
.len
> 1)
1296 switch_camera_button
.set_visible (true);
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");
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
);
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");
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 ();
1420 case MediaMode
.BURST
:
1421 take_action_button
.tooltip_text
= _("Take multiple photos using a webcam");
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"));
1437 switch (current_mode
)
1439 case MediaMode
.PHOTO
:
1440 set_window_title (_("Take a Photo"));
1443 case MediaMode
.VIDEO
:
1444 set_window_title (_("Record a Video"));
1447 case MediaMode
.BURST
:
1448 set_window_title (_("Take Multiple Photos"));
1456 * @param camera the camera to set
1458 public void set_camera (Camera camera
)
1460 this
.camera
= camera
;
1461 set_switch_camera_button_state ();