summaryrefslogtreecommitdiff
path: root/src/SlideshowPage.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/SlideshowPage.vala')
-rw-r--r--src/SlideshowPage.vala466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/SlideshowPage.vala b/src/SlideshowPage.vala
new file mode 100644
index 0000000..f22fd53
--- /dev/null
+++ b/src/SlideshowPage.vala
@@ -0,0 +1,466 @@
+/* Copyright 2009-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+class SlideshowPage : SinglePhotoPage {
+ private const int READAHEAD_COUNT = 5;
+ private const int CHECK_ADVANCE_MSEC = 250;
+
+ private SourceCollection sources;
+ private ViewCollection controller;
+ private Photo current;
+ private Gtk.ToolButton play_pause_button;
+ private Gtk.ToolButton settings_button;
+ private PixbufCache cache = null;
+ private Timer timer = new Timer();
+ private bool playing = true;
+ private bool exiting = false;
+ private string[] transitions;
+
+ private Screensaver screensaver;
+
+ public signal void hide_toolbar();
+
+ private class SettingsDialog : Gtk.Dialog {
+ private Gtk.Builder builder = null;
+ Gtk.SpinButton delay_entry;
+ Gtk.Scale delay_hscale;
+ Gtk.ComboBoxText transition_effect_selector;
+ Gtk.Scale transition_effect_hscale;
+ Gtk.SpinButton transition_effect_entry;
+ Gtk.Adjustment transition_effect_adjustment;
+ Gtk.CheckButton show_title_button;
+ Gtk.Box pane;
+
+ public SettingsDialog() {
+ builder = AppWindow.create_builder();
+ pane = builder.get_object("slideshow_settings_pane") as Gtk.Box;
+ get_content_area().add(pane);
+
+ double delay = Config.Facade.get_instance().get_slideshow_delay();
+
+ set_modal(true);
+ set_transient_for(AppWindow.get_fullscreen());
+
+ add_buttons(Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.Stock.OK, Gtk.ResponseType.OK);
+ set_title(_("Settings"));
+
+ Gtk.Adjustment adjustment = new Gtk.Adjustment(delay, Config.Facade.SLIDESHOW_DELAY_MIN, Config.Facade.SLIDESHOW_DELAY_MAX, 0.1, 1, 0);
+ delay_hscale = builder.get_object("delay_hscale") as Gtk.Scale;
+ delay_hscale.adjustment = adjustment;
+
+ delay_entry = builder.get_object("delay_entry") as Gtk.SpinButton;
+ delay_entry.adjustment = adjustment;
+ delay_entry.set_value(delay);
+ delay_entry.set_numeric(true);
+ delay_entry.set_activates_default(true);
+
+ transition_effect_selector = builder.get_object("transition_effect_selector") as Gtk.ComboBoxText;
+
+ // get last effect id
+ string effect_id = Config.Facade.get_instance().get_slideshow_transition_effect_id();
+
+ // null effect first, always, and set active in case no other one is found
+ string null_display_name = TransitionEffectsManager.get_instance().get_effect_name(
+ TransitionEffectsManager.NULL_EFFECT_ID);
+ transition_effect_selector.append_text(null_display_name);
+ transition_effect_selector.set_active(0);
+
+ int i = 1;
+ foreach (string display_name in
+ TransitionEffectsManager.get_instance().get_effect_names(utf8_ci_compare)) {
+ if (display_name == null_display_name)
+ continue;
+
+ transition_effect_selector.append_text(display_name);
+ if (effect_id == TransitionEffectsManager.get_instance().get_id_for_effect_name(display_name))
+ transition_effect_selector.set_active(i);
+
+ ++i;
+ }
+ transition_effect_selector.changed.connect(on_transition_changed);
+
+ double transition_delay = Config.Facade.get_instance().get_slideshow_transition_delay();
+ transition_effect_adjustment = new Gtk.Adjustment(transition_delay,
+ Config.Facade.SLIDESHOW_TRANSITION_DELAY_MIN, Config.Facade.SLIDESHOW_TRANSITION_DELAY_MAX,
+ 0.1, 1, 0);
+ transition_effect_hscale = builder.get_object("transition_effect_hscale") as Gtk.Scale;
+ transition_effect_hscale.adjustment = transition_effect_adjustment;
+
+ transition_effect_entry = builder.get_object("transition_effect_entry") as Gtk.SpinButton;
+ transition_effect_entry.adjustment = transition_effect_adjustment;
+ transition_effect_entry.set_value(transition_delay);
+ transition_effect_entry.set_numeric(true);
+ transition_effect_entry.set_activates_default(true);
+
+ bool show_title = Config.Facade.get_instance().get_slideshow_show_title();
+ show_title_button = builder.get_object("show_title_button") as Gtk.CheckButton;
+ show_title_button.active = show_title;
+
+ set_default_response(Gtk.ResponseType.OK);
+
+ on_transition_changed();
+ }
+
+ private void on_transition_changed() {
+ string selected = transition_effect_selector.get_active_text();
+ bool sensitive = selected != null
+ && selected != TransitionEffectsManager.NULL_EFFECT_ID;
+
+ transition_effect_hscale.sensitive = sensitive;
+ transition_effect_entry.sensitive = sensitive;
+ }
+
+ public double get_delay() {
+ return delay_entry.get_value();
+ }
+
+ public double get_transition_delay() {
+ return transition_effect_entry.get_value();
+ }
+
+ public string get_transition_effect_id() {
+ string? active = transition_effect_selector.get_active_text();
+ if (active == null)
+ return TransitionEffectsManager.NULL_EFFECT_ID;
+
+ string? id = TransitionEffectsManager.get_instance().get_id_for_effect_name(active);
+
+ return (id != null) ? id : TransitionEffectsManager.NULL_EFFECT_ID;
+ }
+
+ public bool get_show_title() {
+ return show_title_button.active;
+ }
+ }
+
+ public SlideshowPage(SourceCollection sources, ViewCollection controller, Photo start) {
+ base(_("Slideshow"), true);
+
+ this.sources = sources;
+ this.controller = controller;
+
+ Gee.Collection<string> pluggables = TransitionEffectsManager.get_instance().get_effect_ids();
+ Gee.ArrayList<string> a = new Gee.ArrayList<string>();
+ a.add_all(pluggables);
+ a.remove(NullTransitionDescriptor.EFFECT_ID);
+ a.remove(RandomEffectDescriptor.EFFECT_ID);
+ transitions = a.to_array();
+ current = start;
+
+ update_transition_effect();
+
+ // Set up toolbar
+ Gtk.Toolbar toolbar = get_toolbar();
+
+ // add toolbar buttons
+ Gtk.ToolButton previous_button = new Gtk.ToolButton.from_stock(Gtk.Stock.GO_BACK);
+ previous_button.set_label(_("Back"));
+ previous_button.set_tooltip_text(_("Go to the previous photo"));
+ previous_button.clicked.connect(on_previous_photo);
+
+ toolbar.insert(previous_button, -1);
+
+ play_pause_button = new Gtk.ToolButton.from_stock(Gtk.Stock.MEDIA_PAUSE);
+ play_pause_button.set_label(_("Pause"));
+ play_pause_button.set_tooltip_text(_("Pause the slideshow"));
+ play_pause_button.clicked.connect(on_play_pause);
+
+ toolbar.insert(play_pause_button, -1);
+
+ Gtk.ToolButton next_button = new Gtk.ToolButton.from_stock(Gtk.Stock.GO_FORWARD);
+ next_button.set_label(_("Next"));
+ next_button.set_tooltip_text(_("Go to the next photo"));
+ next_button.clicked.connect(on_next_photo);
+
+ toolbar.insert(next_button, -1);
+
+ settings_button = new Gtk.ToolButton.from_stock(Gtk.Stock.PREFERENCES);
+ settings_button.set_label(_("Settings"));
+ settings_button.set_tooltip_text(_("Change slideshow settings"));
+ settings_button.clicked.connect(on_change_settings);
+ settings_button.is_important = true;
+
+ toolbar.insert(settings_button, -1);
+
+ screensaver = new Screensaver();
+ }
+
+ public override void switched_to() {
+ base.switched_to();
+
+ // create a cache for the size of this display
+ cache = new PixbufCache(sources, PixbufCache.PhotoType.BASELINE, get_canvas_scaling(),
+ READAHEAD_COUNT);
+
+ Gdk.Pixbuf pixbuf;
+ if (get_next_photo(current, Direction.FORWARD, out current, out pixbuf))
+ set_pixbuf(pixbuf, current.get_dimensions(), Direction.FORWARD);
+
+ // start the auto-advance timer
+ Timeout.add(CHECK_ADVANCE_MSEC, auto_advance);
+ timer.start();
+
+ screensaver.inhibit("Playing slideshow");
+ }
+
+ public override void switching_from() {
+ base.switching_from();
+
+ screensaver.uninhibit();
+ exiting = true;
+ }
+
+ private bool get_next_photo(Photo start, Direction direction, out Photo next,
+ out Gdk.Pixbuf next_pixbuf) {
+ next = start;
+
+ for (;;) {
+ try {
+ // Fails if a photo source file is missing.
+ next_pixbuf = cache.fetch(next);
+ } catch (Error err) {
+ warning("Unable to fetch pixbuf for %s: %s", next.to_string(), err.message);
+
+ // Look for the next good photo
+ DataView view = controller.get_view_for_source(next);
+ view = (direction == Direction.FORWARD)
+ ? controller.get_next(view)
+ : controller.get_previous(view);
+ next = (Photo) view.get_source();
+
+ // An entire slideshow set might be missing, so check for a loop.
+ if ((next == start && next != current) || next == current) {
+ AppWindow.error_message(_("All photo source files are missing."), get_container());
+ AppWindow.get_instance().end_fullscreen();
+
+ next = null;
+ next_pixbuf = null;
+
+ return false;
+ }
+
+ continue;
+ }
+
+ // prefetch this photo's extended neighbors: the next photo highest priority, the prior
+ // one normal, and the extended neighbors lowest, to recognize immediate needs
+ DataSource forward, back;
+ controller.get_immediate_neighbors(next, out forward, out back, Photo.TYPENAME);
+ cache.prefetch((Photo) forward, BackgroundJob.JobPriority.HIGHEST);
+ cache.prefetch((Photo) back, BackgroundJob.JobPriority.NORMAL);
+
+ Gee.Set<DataSource> neighbors = controller.get_extended_neighbors(next, Photo.TYPENAME);
+ neighbors.remove(forward);
+ neighbors.remove(back);
+
+ cache.prefetch_many((Gee.Collection<Photo>) neighbors, BackgroundJob.JobPriority.LOWEST);
+
+ return true;
+ }
+ }
+
+ private void on_play_pause() {
+ if (playing) {
+ play_pause_button.set_stock_id(Gtk.Stock.MEDIA_PLAY);
+ play_pause_button.set_label(_("Play"));
+ play_pause_button.set_tooltip_text(_("Continue the slideshow"));
+ } else {
+ play_pause_button.set_stock_id(Gtk.Stock.MEDIA_PAUSE);
+ play_pause_button.set_label(_("Pause"));
+ play_pause_button.set_tooltip_text(_("Pause the slideshow"));
+ }
+
+ playing = !playing;
+
+ // reset the timer
+ timer.start();
+ }
+
+ protected override void on_previous_photo() {
+ DataView view = controller.get_view_for_source(current);
+
+ Photo? prev_photo = null;
+ DataView? start_view = controller.get_previous(view);
+ DataView? prev_view = start_view;
+
+ while (prev_view != null) {
+ if (prev_view.get_source() is Photo) {
+ prev_photo = (Photo) prev_view.get_source();
+ break;
+ }
+
+ prev_view = controller.get_previous(prev_view);
+
+ if (prev_view == start_view) {
+ warning("on_previous( ): can't advance to previous photo: collection has only videos");
+ return;
+ }
+ }
+
+ advance(prev_photo, Direction.BACKWARD);
+ }
+
+ protected override void on_next_photo() {
+ DataView view = controller.get_view_for_source(current);
+
+ Photo? next_photo = null;
+ DataView? start_view = controller.get_next(view);
+ DataView? next_view = start_view;
+
+ while (next_view != null) {
+ if (next_view.get_source() is Photo) {
+ next_photo = (Photo) next_view.get_source();
+ break;
+ }
+
+ next_view = controller.get_next(next_view);
+
+ if (next_view == start_view) {
+ warning("on_next( ): can't advance to next photo: collection has only videos");
+ return;
+ }
+ }
+
+ if (Config.Facade.get_instance().get_slideshow_transition_effect_id() ==
+ RandomEffectDescriptor.EFFECT_ID) {
+ random_transition_effect();
+ }
+
+ advance(next_photo, Direction.FORWARD);
+ }
+
+ private void advance(Photo photo, Direction direction) {
+ current = photo;
+
+ // set pixbuf
+ Gdk.Pixbuf next_pixbuf;
+ if (get_next_photo(current, direction, out current, out next_pixbuf))
+ set_pixbuf(next_pixbuf, current.get_dimensions(), direction);
+
+ // reset the advance timer
+ timer.start();
+ }
+
+ private bool auto_advance() {
+ if (exiting)
+ return false;
+
+ if (!playing)
+ return true;
+
+ if (timer.elapsed() < Config.Facade.get_instance().get_slideshow_delay())
+ return true;
+
+ on_next_photo();
+
+ return true;
+ }
+
+ public override bool key_press_event(Gdk.EventKey event) {
+ bool handled = true;
+ switch (Gdk.keyval_name(event.keyval)) {
+ case "space":
+ on_play_pause();
+ break;
+
+ default:
+ handled = false;
+ break;
+ }
+
+ if (handled)
+ return true;
+
+ return (base.key_press_event != null) ? base.key_press_event(event) : true;
+ }
+
+ private void on_change_settings() {
+ SettingsDialog settings_dialog = new SettingsDialog();
+ settings_dialog.show_all();
+
+ bool slideshow_playing = playing;
+ playing = false;
+ hide_toolbar();
+
+ if (settings_dialog.run() == Gtk.ResponseType.OK) {
+ // sync with the config setting so it will persist
+ Config.Facade.get_instance().set_slideshow_delay(settings_dialog.get_delay());
+
+ Config.Facade.get_instance().set_slideshow_transition_delay(settings_dialog.get_transition_delay());
+ Config.Facade.get_instance().set_slideshow_transition_effect_id(settings_dialog.get_transition_effect_id());
+ Config.Facade.get_instance().set_slideshow_show_title(settings_dialog.get_show_title());
+
+ update_transition_effect();
+ }
+
+ settings_dialog.destroy();
+ playing = slideshow_playing;
+ timer.start();
+ }
+
+ private void update_transition_effect() {
+ string effect_id = Config.Facade.get_instance().get_slideshow_transition_effect_id();
+ double effect_delay = Config.Facade.get_instance().get_slideshow_transition_delay();
+
+ set_transition(effect_id, (int) (effect_delay * 1000.0));
+ }
+
+ private void random_transition_effect() {
+ double effect_delay = Config.Facade.get_instance().get_slideshow_transition_delay();
+ string effect_id = TransitionEffectsManager.NULL_EFFECT_ID;
+ if (0 < transitions.length) {
+ int random = Random.int_range(0, transitions.length);
+ effect_id = transitions[random];
+ }
+ set_transition(effect_id, (int) (effect_delay * 1000.0));
+ }
+
+ // Paint the title of the photo
+ private void paint_title(Cairo.Context ctx, Dimensions ctx_dim) {
+ string? title = current.get_title();
+
+ // If the photo doesn't have a title, don't paint anything
+ if (title == null || title == "")
+ return;
+
+ Pango.Layout layout = create_pango_layout(title);
+ Pango.AttrList list = new Pango.AttrList();
+ Pango.Attribute size = Pango.attr_scale_new(3);
+ list.insert(size.copy());
+ layout.set_attributes(list);
+ layout.set_width((int) ((ctx_dim.width * 0.9) * Pango.SCALE));
+
+ // Find the right position
+ int title_width, title_height;
+ layout.get_pixel_size(out title_width, out title_height);
+ double x = ctx_dim.width * 0.2;
+ double y = ctx_dim.height * 0.90;
+
+ // Move the title up if it is too high
+ if (y + title_height >= ctx_dim.height * 0.95)
+ y = ctx_dim.height * 0.95 - title_height;
+ // Move to the left if the title is too long
+ if (x + title_width >= ctx_dim.width * 0.95)
+ x = ctx_dim.width / 2 - title_width / 2;
+
+ set_source_color_from_string(ctx, "#fff");
+ ctx.move_to(x, y);
+ Pango.cairo_show_layout(ctx, layout);
+ Pango.cairo_layout_path(ctx, layout);
+ ctx.set_line_width(1.5);
+ set_source_color_from_string(ctx, "#000");
+ ctx.stroke();
+ }
+
+ public override void paint(Cairo.Context ctx, Dimensions ctx_dim) {
+ base.paint(ctx, ctx_dim);
+
+ if (Config.Facade.get_instance().get_slideshow_show_title() && !is_transition_in_progress())
+ paint_title(ctx, ctx_dim);
+ }
+}
+