/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2009 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #ifdef HAVE_CLUTTER #include #include #endif /* HAVE_CLUTTER */ #if HAVE_GSTREAMER #include #include #endif /* HAVE_GSTREAMER */ #include "gth-slideshow.h" #include "gth-transition.h" #include "actions.h" #include #define HIDE_CURSOR_DELAY 1 #define HIDE_PAUSED_SIGN_DELAY 1 #define DEFAULT_DELAY 2000 #define _GST_PLAY_FLAG_AUDIO (1 << 1) typedef enum { GTH_SLIDESHOW_DIRECTION_FORWARD, GTH_SLIDESHOW_DIRECTION_BACKWARD } GthSlideshowDirection; struct _GthSlideshowPrivate { GthProjector *projector; GthBrowser *browser; GList *file_list; /* GthFileData */ gboolean automatic; gboolean wrap_around; GList *current; GthImagePreloader *preloader; GList *transitions; /* GthTransition */ int n_transitions; GthTransition *transition; GthSlideshowDirection direction; #if HAVE_CLUTTER ClutterTimeline *timeline; ClutterActor *image1; ClutterActor *image2; ClutterActor *paused_actor; guint32 last_button_event_time; #endif GthImage *current_image; GtkWidget *viewer; guint next_event; guint delay; guint hide_cursor_event; GRand *rand; gboolean first_show; gboolean one_loaded; char **audio_files; gboolean audio_loop; #if HAVE_GSTREAMER int current_audio_file; GstElement *playbin; #endif GdkPixbuf *pause_pixbuf; gboolean paused; gboolean paint_paused; guint hide_paused_sign; gboolean animating; gboolean random_order; GthScreensaver *screensaver; }; G_DEFINE_TYPE_WITH_CODE (GthSlideshow, gth_slideshow, GTH_TYPE_WINDOW, G_ADD_PRIVATE (GthSlideshow)) static const GActionEntry actions[] = { { "slideshow-close", gth_slideshow_activate_close }, { "slideshow-toggle-pause", gth_slideshow_activate_toggle_pause }, { "slideshow-next-image", gth_slideshow_activate_next_image }, { "slideshow-previous-image", gth_slideshow_activate_previous_image }, }; static int shuffle_func (gconstpointer a, gconstpointer b) { return g_random_int_range (-1, 2); } static void _gth_slideshow_reset_current (GthSlideshow *self) { if (self->priv->random_order) self->priv->file_list = g_list_sort (self->priv->file_list, shuffle_func); if (self->priv->direction == GTH_SLIDESHOW_DIRECTION_FORWARD) self->priv->current = g_list_first (self->priv->file_list); else self->priv->current = g_list_last (self->priv->file_list); } static void preloader_load_ready_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GthSlideshow *self = user_data; GthFileData *requested; GthImage *image; int requested_size; int original_width; int original_height; GError *error = NULL; if (! gth_image_preloader_load_finish (GTH_IMAGE_PRELOADER (source_object), result, &requested, &image, &requested_size, &original_width, &original_height, &error)) { g_clear_error (&error); gth_slideshow_load_next_image (self); return; } _g_object_unref (self->priv->current_image); self->priv->current_image = _g_object_ref (image); if (self->priv->current_image == NULL) { gth_slideshow_load_next_image (self); return; } self->priv->one_loaded = TRUE; self->priv->projector->image_ready (self, self->priv->current_image); _g_object_unref (requested); _g_object_unref (image); } static void _gth_slideshow_load_current_image (GthSlideshow *self) { GthFileData *requested_file; GthFileData *next_file; GthFileData *prev_file; int screen_width; int screen_height; if (self->priv->next_event != 0) { g_source_remove (self->priv->next_event); self->priv->next_event = 0; } if (self->priv->current == NULL) { if (! self->priv->one_loaded || ! self->priv->wrap_around) { gth_slideshow_close (self); return; } _gth_slideshow_reset_current (self); } requested_file = (GthFileData *) self->priv->current->data; if (self->priv->current->next != NULL) next_file = (GthFileData *) self->priv->current->next->data; else next_file = NULL; if (self->priv->current->prev != NULL) prev_file = (GthFileData *) self->priv->current->prev->data; else prev_file = NULL; _gtk_widget_get_screen_size (GTK_WIDGET (self), &screen_width, &screen_height); gth_image_preloader_load (self->priv->preloader, requested_file, MAX (screen_width, screen_height), NULL, preloader_load_ready_cb, self, 2, next_file, prev_file); } static gboolean next_image_cb (gpointer user_data) { GthSlideshow *self = user_data; if (self->priv->next_event != 0) { g_source_remove (self->priv->next_event); self->priv->next_event = 0; } gth_slideshow_load_next_image (self); return FALSE; } static void view_next_image_automatically (GthSlideshow *self) { if (self->priv->automatic && ! self->priv->paused) gth_screensaver_inhibit (self->priv->screensaver, GTK_WINDOW (self), _("Playing a presentation")); else gth_screensaver_uninhibit (self->priv->screensaver); if (self->priv->automatic) { if (self->priv->next_event != 0) g_source_remove (self->priv->next_event); self->priv->next_event = g_timeout_add (self->priv->delay, next_image_cb, self); } } static void gth_slideshow_finalize (GObject *object) { GthSlideshow *self = GTH_SLIDESHOW (object); if (self->priv->next_event != 0) g_source_remove (self->priv->next_event); if (self->priv->hide_cursor_event != 0) g_source_remove (self->priv->hide_cursor_event); _g_object_unref (self->priv->pause_pixbuf); _g_object_unref (self->priv->current_image); _g_object_list_unref (self->priv->file_list); _g_object_unref (self->priv->browser); _g_object_unref (self->priv->preloader); _g_object_list_unref (self->priv->transitions); g_rand_free (self->priv->rand); g_strfreev (self->priv->audio_files); #if HAVE_GSTREAMER if (self->priv->playbin != NULL) { gst_element_set_state (self->priv->playbin, GST_STATE_NULL); gst_object_unref (GST_OBJECT (self->priv->playbin)); self->priv->playbin = NULL; } #endif if (self->priv->screensaver != NULL) { gth_screensaver_uninhibit (self->priv->screensaver); g_object_unref (self->priv->screensaver); } G_OBJECT_CLASS (gth_slideshow_parent_class)->finalize (object); } static void gth_slideshow_class_init (GthSlideshowClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gth_slideshow_finalize; } static gboolean hide_cursor_cb (gpointer data) { GthSlideshow *self = data; g_source_remove (self->priv->hide_cursor_event); self->priv->hide_cursor_event = 0; self->priv->projector->hide_cursor (self); return FALSE; } #if HAVE_GSTREAMER static gboolean player_done_cb (gpointer user_data) { GthSlideshow *self = user_data; self->priv->current_audio_file++; if ((self->priv->audio_files[self->priv->current_audio_file] == NULL) && self->priv->audio_loop) { self->priv->current_audio_file = 0; } gst_element_set_state (self->priv->playbin, GST_STATE_READY); g_object_set (G_OBJECT (self->priv->playbin), "uri", self->priv->audio_files[self->priv->current_audio_file], NULL); gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING); return FALSE; } static void pipeline_eos_cb (GstBus *bus, GstMessage *message, gpointer user_data) { g_idle_add (player_done_cb, user_data); } #endif static void gth_slideshow_show_cb (GtkWidget *widget, GthSlideshow *self) { if (! self->priv->first_show) return; self->priv->first_show = FALSE; #if HAVE_GSTREAMER if ((self->priv->audio_files != NULL) && (self->priv->audio_files[0] != NULL) && gstreamer_init ()) { self->priv->current_audio_file = 0; if (self->priv->playbin == NULL) { GstBus *bus; self->priv->playbin = gst_element_factory_make ("playbin", "playbin"); g_object_set (self->priv->playbin, "flags", _GST_PLAY_FLAG_AUDIO, "volume", 1.0, NULL); bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->playbin)); gst_bus_add_signal_watch (bus); g_signal_connect (bus, "message::eos", G_CALLBACK (pipeline_eos_cb), self); } else gst_element_set_state (self->priv->playbin, GST_STATE_READY); g_object_set (G_OBJECT (self->priv->playbin), "uri", self->priv->audio_files[self->priv->current_audio_file], NULL); gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING); } #endif _gth_slideshow_reset_current (self); _gth_slideshow_load_current_image (self); } static gboolean _gth_slideshow_key_press_cb (GthSlideshow *self, GdkEventKey *event, gpointer user_data) { return gth_window_activate_shortcut (GTH_WINDOW (self), GTH_SHORTCUT_CONTEXT_SLIDESHOW, NULL, event->keyval, event->state); } static void gth_slideshow_init (GthSlideshow *self) { self->priv = gth_slideshow_get_instance_private (self); self->priv->file_list = NULL; self->priv->next_event = 0; self->priv->delay = DEFAULT_DELAY; self->priv->automatic = FALSE; self->priv->wrap_around = FALSE; self->priv->transitions = NULL; self->priv->n_transitions = 0; self->priv->rand = g_rand_new (); self->priv->first_show = TRUE; self->priv->audio_files = NULL; self->priv->paused = FALSE; self->priv->animating = FALSE; self->priv->direction = GTH_SLIDESHOW_DIRECTION_FORWARD; self->priv->random_order = FALSE; self->priv->current_image = NULL; self->priv->screensaver = gth_screensaver_new (NULL); self->priv->preloader = gth_image_preloader_new (); } static void _gth_slideshow_construct (GthSlideshow *self, GthProjector *projector, GthBrowser *browser, GList *file_list) { self->priv->projector = projector; self->priv->browser = _g_object_ref (browser); self->priv->file_list = _g_object_list_ref (file_list); self->priv->one_loaded = FALSE; self->priv->pause_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "slideshow-pause", 100, 0, NULL); if (self->priv->pause_pixbuf == NULL) self->priv->pause_pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "media-playback-pause-symbolic", 100, 0, NULL); self->priv->projector->construct (self); g_action_map_add_action_entries (G_ACTION_MAP (self), actions, G_N_ELEMENTS (actions), self); gth_window_copy_shortcuts (GTH_WINDOW (self), GTH_WINDOW (self->priv->browser), GTH_SHORTCUT_CONTEXT_SLIDESHOW); g_signal_connect (self, "show", G_CALLBACK (gth_slideshow_show_cb), self); g_signal_connect (self, "key-press-event", G_CALLBACK (_gth_slideshow_key_press_cb), NULL); } GtkWidget * gth_slideshow_new (GthProjector *projector, GthBrowser *browser, GList *file_list /* GthFileData */) { GthSlideshow *window; g_return_val_if_fail (projector != NULL, NULL); window = (GthSlideshow *) g_object_new (GTH_TYPE_SLIDESHOW, NULL); _gth_slideshow_construct (window, projector, browser, file_list); return (GtkWidget*) window; } void gth_slideshow_set_delay (GthSlideshow *self, guint msecs) { self->priv->delay = msecs; } void gth_slideshow_set_automatic (GthSlideshow *self, gboolean automatic) { self->priv->automatic = automatic; } void gth_slideshow_set_wrap_around (GthSlideshow *self, gboolean wrap_around) { self->priv->wrap_around = wrap_around; } void gth_slideshow_set_transitions (GthSlideshow *self, GList *transitions) { _g_object_list_unref (self->priv->transitions); self->priv->transitions = _g_object_list_ref (transitions); self->priv->n_transitions = g_list_length (self->priv->transitions); } void gth_slideshow_set_playlist (GthSlideshow *self, char **files) { self->priv->audio_files = g_strdupv (files); self->priv->audio_loop = TRUE; } void gth_slideshow_set_random_order (GthSlideshow *self, gboolean random) { self->priv->random_order = random; } void gth_slideshow_toggle_pause (GthSlideshow *self) { g_return_if_fail (GTH_IS_SLIDESHOW (self)); self->priv->paused = ! self->priv->paused; if (self->priv->paused) { self->priv->projector->paused (self); #if HAVE_GSTREAMER if (self->priv->playbin != NULL) gst_element_set_state (self->priv->playbin, GST_STATE_PAUSED); #endif } else { /* resume */ gth_slideshow_load_next_image (self); #if HAVE_GSTREAMER if (self->priv->playbin != NULL) gst_element_set_state (self->priv->playbin, GST_STATE_PLAYING); #endif } } void gth_slideshow_load_next_image (GthSlideshow *self) { g_return_if_fail (GTH_IS_SLIDESHOW (self)); self->priv->projector->load_next_image (self); self->priv->direction = GTH_SLIDESHOW_DIRECTION_FORWARD; if (self->priv->paused) return; self->priv->current = self->priv->current->next; _gth_slideshow_load_current_image (self); } void gth_slideshow_load_prev_image (GthSlideshow *self) { g_return_if_fail (GTH_IS_SLIDESHOW (self)); self->priv->projector->load_prev_image (self); self->priv->direction = GTH_SLIDESHOW_DIRECTION_BACKWARD; if (self->priv->paused) return; self->priv->current = self->priv->current->prev; _gth_slideshow_load_current_image (self); } void gth_slideshow_next_image_or_resume (GthSlideshow *self) { g_return_if_fail (GTH_IS_SLIDESHOW (self)); if (self->priv->paused) gth_slideshow_toggle_pause (self); else gth_slideshow_load_next_image (self); } static void _gth_slideshow_close_cb (gpointer user_data) { GthSlideshow *self = user_data; gboolean close_browser; GthBrowser *browser; browser = self->priv->browser; close_browser = ! gtk_widget_get_visible (GTK_WIDGET (browser)); self->priv->projector->show_cursor (self); self->priv->projector->finalize (self); gtk_widget_destroy (GTK_WIDGET (self)); if (close_browser) gth_window_close (GTH_WINDOW (browser)); } void gth_slideshow_close (GthSlideshow *self) { call_when_idle (_gth_slideshow_close_cb, self); } /* -- default projector -- */ static void default_projector_load_next_image (GthSlideshow *self) { /* void */ } static void default_projector_load_prev_image (GthSlideshow *self) { /* void */ } static void default_projector_image_ready (GthSlideshow *self, GthImage *image) { gth_image_viewer_set_image (GTH_IMAGE_VIEWER (self->priv->viewer), image, -1, -1); view_next_image_automatically (self); } static void default_projector_finalize (GthSlideshow *self) { if (self->priv->hide_paused_sign != 0) { g_source_remove (self->priv->hide_paused_sign); self->priv->hide_paused_sign = 0; } } static void default_projector_show_cursor (GthSlideshow *self) { gth_image_viewer_show_cursor (GTH_IMAGE_VIEWER (self->priv->viewer)); } static void default_projector_hide_cursor (GthSlideshow *self) { gth_image_viewer_hide_cursor (GTH_IMAGE_VIEWER (self->priv->viewer)); } static void default_projector_paused (GthSlideshow *self) { if (self->priv->hide_paused_sign != 0) { g_source_remove (self->priv->hide_paused_sign); self->priv->hide_paused_sign = 0; } self->priv->paint_paused = TRUE; gtk_widget_queue_draw (self->priv->viewer); } static void viewer_event_cb (GtkWidget *widget, GdkEvent *event, GthSlideshow *self) { if (event->type == GDK_MOTION_NOTIFY) { gth_image_viewer_show_cursor (GTH_IMAGE_VIEWER (self->priv->viewer)); if (self->priv->hide_cursor_event != 0) g_source_remove (self->priv->hide_cursor_event); self->priv->hide_cursor_event = g_timeout_add_seconds (HIDE_CURSOR_DELAY, hide_cursor_cb, self); } else if (event->type == GDK_BUTTON_PRESS) { switch (((GdkEventButton *) event)->button) { case 1: gth_slideshow_load_next_image (self); break; case 3: gth_slideshow_load_prev_image (self); break; default: break; } } } static gboolean hide_paused_sign_cb (gpointer user_data) { GthSlideshow *self = user_data; g_source_remove (self->priv->hide_paused_sign); self->priv->hide_paused_sign = 0; self->priv->paint_paused = FALSE; gtk_widget_queue_draw (self->priv->viewer); return FALSE; } static void default_projector_pause_painter (GthImageViewer *image_viewer, cairo_t *cr, gpointer user_data) { GthSlideshow *self = user_data; int screen_width; int screen_height; double dest_x; double dest_y; if (! self->priv->paused || ! self->priv->paint_paused || (self->priv->pause_pixbuf == NULL)) return; if (! _gtk_widget_get_screen_size (GTK_WIDGET (image_viewer), &screen_width, &screen_height)) return; dest_x = (screen_width - gdk_pixbuf_get_width (self->priv->pause_pixbuf)) / 2.0; dest_y = (screen_height - gdk_pixbuf_get_height (self->priv->pause_pixbuf)) / 2.0; gdk_cairo_set_source_pixbuf (cr, self->priv->pause_pixbuf, dest_x, dest_y); cairo_rectangle (cr, dest_x, dest_y, gdk_pixbuf_get_width (self->priv->pause_pixbuf), gdk_pixbuf_get_height (self->priv->pause_pixbuf)); cairo_fill (cr); if (self->priv->hide_paused_sign != 0) g_source_remove (self->priv->hide_paused_sign); self->priv->hide_paused_sign = g_timeout_add_seconds (HIDE_PAUSED_SIGN_DELAY, hide_paused_sign_cb, self); } static void default_projector_construct (GthSlideshow *self) { GSettings *viewer_settings; self->priv->hide_paused_sign = 0; self->priv->paint_paused = FALSE; self->priv->viewer = gth_image_viewer_new (); gth_image_viewer_set_fit_mode (GTH_IMAGE_VIEWER (self->priv->viewer), GTH_FIT_SIZE); gth_image_viewer_set_zoom_change (GTH_IMAGE_VIEWER (self->priv->viewer), GTH_ZOOM_CHANGE_FIT_SIZE); viewer_settings = g_settings_new (PIX_IMAGE_VIEWER_SCHEMA); gth_image_viewer_set_zoom_quality (GTH_IMAGE_VIEWER (self->priv->viewer), g_settings_get_enum (viewer_settings, PREF_IMAGE_VIEWER_ZOOM_QUALITY)); g_object_unref (viewer_settings); gth_image_viewer_add_painter (GTH_IMAGE_VIEWER (self->priv->viewer), default_projector_pause_painter, self); gth_image_viewer_set_transparency_style (GTH_IMAGE_VIEWER (self->priv->viewer), GTH_TRANSPARENCY_STYLE_CHECKERED); g_signal_connect (self->priv->viewer, "button-press-event", G_CALLBACK (viewer_event_cb), self); g_signal_connect (self->priv->viewer, "motion-notify-event", G_CALLBACK (viewer_event_cb), self); g_signal_connect (self->priv->viewer, "key-press-event", G_CALLBACK (viewer_event_cb), self); g_signal_connect (self->priv->viewer, "key-release-event", G_CALLBACK (viewer_event_cb), self); gtk_widget_show (self->priv->viewer); gtk_container_add (GTK_CONTAINER (self), self->priv->viewer); } GthProjector default_projector = { default_projector_construct, default_projector_paused, default_projector_show_cursor, default_projector_hide_cursor, default_projector_finalize, default_projector_image_ready, default_projector_load_prev_image, default_projector_load_next_image }; #ifdef HAVE_CLUTTER /* -- clutter projector -- */ static void _gth_slideshow_swap_current_and_next (GthSlideshow *self) { ClutterGeometry tmp_geometry; self->current_image = self->next_image; if (self->current_image == self->priv->image1) self->next_image = self->priv->image2; else self->next_image = self->priv->image1; tmp_geometry = self->current_geometry; self->current_geometry = self->next_geometry; self->next_geometry = tmp_geometry; } static void reset_texture_transformation (GthSlideshow *self, ClutterActor *texture) { float stage_w, stage_h; if (texture == NULL) return; clutter_actor_get_size (self->stage, &stage_w, &stage_h); clutter_actor_set_pivot_point (texture, stage_w / 2.0, stage_h / 2.0); clutter_actor_set_opacity (texture, 255); clutter_actor_set_rotation_angle (texture, CLUTTER_X_AXIS, 0.0); clutter_actor_set_rotation_angle (texture, CLUTTER_Y_AXIS, 0.0); clutter_actor_set_rotation_angle (texture, CLUTTER_Z_AXIS, 0.0); clutter_actor_set_scale (texture, 1.0, 1.0); } static void _gth_slideshow_reset_textures_position (GthSlideshow *self) { if (self->next_image != NULL) { clutter_actor_set_size (self->next_image, (float) self->next_geometry.width, (float) self->next_geometry.height); clutter_actor_set_position (self->next_image, (float) self->next_geometry.x, (float) self->next_geometry.y); } if (self->current_image != NULL) { clutter_actor_set_size (self->current_image, (float) self->current_geometry.width, (float) self->current_geometry.height); clutter_actor_set_position (self->current_image, (float) self->current_geometry.x, (float) self->current_geometry.y); } if ((self->current_image != NULL) && (self->next_image != NULL)) { clutter_actor_set_child_above_sibling (CLUTTER_ACTOR (self->stage), self->current_image, self->next_image); clutter_actor_hide (self->next_image); } if (self->current_image != NULL) clutter_actor_show (self->current_image); reset_texture_transformation (self, self->next_image); reset_texture_transformation (self, self->current_image); } static void _gth_slideshow_animation_completed (GthSlideshow *self) { self->priv->animating = FALSE; if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_FORWARD) _gth_slideshow_swap_current_and_next (self); _gth_slideshow_reset_textures_position (self); } static void clutter_projector_load_next_image (GthSlideshow *self) { if (clutter_timeline_is_playing (self->priv->timeline)) { clutter_timeline_pause (self->priv->timeline); _gth_slideshow_animation_completed (self); } clutter_timeline_set_direction (self->priv->timeline, CLUTTER_TIMELINE_FORWARD); } static void clutter_projector_load_prev_image (GthSlideshow *self) { if (clutter_timeline_is_playing (self->priv->timeline)) { clutter_timeline_pause (self->priv->timeline); _gth_slideshow_animation_completed (self); } clutter_timeline_set_direction (self->priv->timeline, CLUTTER_TIMELINE_BACKWARD); } static void animation_completed_cb (ClutterTimeline *timeline, GthSlideshow *self) { _gth_slideshow_animation_completed (self); view_next_image_automatically (self); } static void animation_frame_cb (ClutterTimeline *timeline, int msecs, GthSlideshow *self) { if (self->priv->transition != NULL) gth_transition_frame (self->priv->transition, self, clutter_timeline_get_progress (self->priv->timeline)); if (self->first_frame) self->first_frame = FALSE; } static void animation_started_cb (ClutterTimeline *timeline, GthSlideshow *self) { self->priv->animating = TRUE; self->first_frame = TRUE; } static GthTransition * _gth_slideshow_get_transition (GthSlideshow *self) { if (self->priv->transitions == NULL) return NULL; else if (self->priv->transitions->next == NULL) return self->priv->transitions->data; else return g_list_nth_data (self->priv->transitions, g_rand_int_range (self->priv->rand, 0, self->priv->n_transitions)); } static void clutter_projector_image_ready (GthSlideshow *self, GthImage *image_data) { GdkPixbuf *pixbuf; GdkPixbuf *image; ClutterActor *texture; int pixbuf_w, pixbuf_h; float stage_w, stage_h; int pixbuf_x, pixbuf_y; clutter_actor_get_size (self->stage, &stage_w, &stage_h); if ((stage_w == 0) || (stage_h == 0)) return; pixbuf = gth_image_get_pixbuf (image_data); image = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf), FALSE, gdk_pixbuf_get_bits_per_sample (pixbuf), stage_w, stage_h); gdk_pixbuf_fill (image, 0x000000ff); pixbuf_w = gdk_pixbuf_get_width (pixbuf); pixbuf_h = gdk_pixbuf_get_height (pixbuf); scale_keeping_ratio (&pixbuf_w, &pixbuf_h, (int) stage_w, (int) stage_h, TRUE); pixbuf_x = (stage_w - pixbuf_w) / 2; pixbuf_y = (stage_h - pixbuf_h) / 2; gdk_pixbuf_composite (pixbuf, image, pixbuf_x, pixbuf_y, pixbuf_w, pixbuf_h, pixbuf_x, pixbuf_y, (double) pixbuf_w / gdk_pixbuf_get_width (pixbuf), (double) pixbuf_h / gdk_pixbuf_get_height (pixbuf), GDK_INTERP_BILINEAR, 255); if (self->next_image == self->priv->image1) texture = self->priv->image1; else texture = self->priv->image2; gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (texture), image, NULL); self->next_geometry.x = 0; self->next_geometry.y = 0; self->next_geometry.width = stage_w; self->next_geometry.height = stage_h; _gth_slideshow_reset_textures_position (self); if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_BACKWARD) _gth_slideshow_swap_current_and_next (self); self->priv->transition = _gth_slideshow_get_transition (self); clutter_timeline_rewind (self->priv->timeline); clutter_timeline_start (self->priv->timeline); if (self->current_image == NULL) clutter_timeline_advance (self->priv->timeline, GTH_TRANSITION_DURATION); g_object_unref (image); g_object_unref (pixbuf); } static void clutter_projector_finalize (GthSlideshow *self) { _g_object_unref (self->priv->timeline); } static void clutter_projector_show_cursor (GthSlideshow *self) { clutter_stage_show_cursor (CLUTTER_STAGE (self->stage)); } static void clutter_projector_hide_cursor (GthSlideshow *self) { clutter_stage_hide_cursor (CLUTTER_STAGE (self->stage)); } static void clutter_projector_paused (GthSlideshow *self) { float stage_w; float stage_h; if (self->priv->animating) { clutter_timeline_pause (self->priv->timeline); _gth_slideshow_animation_completed (self); } clutter_actor_get_size (self->stage, &stage_w, &stage_h); clutter_actor_set_position (self->priv->paused_actor, stage_w / 2.0, stage_h / 2.0); clutter_actor_set_pivot_point (self->priv->paused_actor, 0.5, 0.5); clutter_actor_set_scale (self->priv->paused_actor, 1.0, 1.0); clutter_actor_set_opacity (self->priv->paused_actor, 255); clutter_actor_set_child_above_sibling (CLUTTER_ACTOR (self->stage), self->priv->paused_actor, NULL); clutter_actor_show (self->priv->paused_actor); clutter_actor_save_easing_state (self->priv->paused_actor); clutter_actor_set_easing_mode (self->priv->paused_actor, CLUTTER_LINEAR); clutter_actor_set_easing_duration (self->priv->paused_actor, 500); clutter_actor_set_scale (self->priv->paused_actor, 3.0, 3.0); clutter_actor_set_opacity (self->priv->paused_actor, 0); clutter_actor_restore_easing_state (self->priv->paused_actor); } static void stage_input_cb (ClutterStage *stage, ClutterEvent *event, GthSlideshow *self) { if (event->type == CLUTTER_MOTION) { clutter_stage_show_cursor (CLUTTER_STAGE (self->stage)); if (self->priv->hide_cursor_event != 0) g_source_remove (self->priv->hide_cursor_event); self->priv->hide_cursor_event = g_timeout_add (HIDE_CURSOR_DELAY, hide_cursor_cb, self); } else if (event->type == CLUTTER_BUTTON_PRESS) { guint32 event_time; /* avoid a double button_press emission that seems to be a * clutter bug */ event_time = ((ClutterButtonEvent *)event)->time; if (self->priv->last_button_event_time == event_time) return; self->priv->last_button_event_time = event_time; switch (clutter_event_get_button (event)) { case 1: gth_slideshow_load_next_image (self); break; case 3: gth_slideshow_load_prev_image (self); break; default: break; } } } static void adapt_image_size_to_stage_size (GthSlideshow *self) { gfloat stage_w, stage_h; GdkPixbuf *pixbuf; GdkPixbuf *image; int pixbuf_w, pixbuf_h; int pixbuf_x, pixbuf_y; ClutterActor *texture; if (self->current_image == NULL) return; clutter_actor_get_size (self->stage, &stage_w, &stage_h); if ((stage_w == 0) || (stage_h == 0)) return; if (self->priv->current_image == NULL) return; pixbuf = gth_image_get_pixbuf (self->priv->current_image); image = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf), FALSE, gdk_pixbuf_get_bits_per_sample (pixbuf), stage_w, stage_h); gdk_pixbuf_fill (image, 0x000000ff); pixbuf_w = gdk_pixbuf_get_width (pixbuf); pixbuf_h = gdk_pixbuf_get_height (pixbuf); scale_keeping_ratio (&pixbuf_w, &pixbuf_h, (int) stage_w, (int) stage_h, TRUE); pixbuf_x = (stage_w - pixbuf_w) / 2; pixbuf_y = (stage_h - pixbuf_h) / 2; gdk_pixbuf_composite (pixbuf, image, pixbuf_x, pixbuf_y, pixbuf_w, pixbuf_h, pixbuf_x, pixbuf_y, (double) pixbuf_w / gdk_pixbuf_get_width (pixbuf), (double) pixbuf_h / gdk_pixbuf_get_height (pixbuf), GDK_INTERP_BILINEAR, 255); if (self->current_image == self->priv->image1) texture = self->priv->image1; else texture = self->priv->image2; gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (texture), image, NULL); self->current_geometry.x = 0; self->current_geometry.y = 0; self->current_geometry.width = stage_w; self->current_geometry.height = stage_h; _gth_slideshow_reset_textures_position (self); g_object_unref (image); g_object_unref (pixbuf); } static void gth_slideshow_size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer user_data) { adapt_image_size_to_stage_size (GTH_SLIDESHOW (user_data)); } static void clutter_projector_construct (GthSlideshow *self) { GtkWidget *embed; ClutterColor background_color = { 0x0, 0x0, 0x0, 0xff }; embed = gtk_clutter_embed_new (); self->stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (embed)); clutter_stage_hide_cursor (CLUTTER_STAGE (self->stage)); clutter_actor_set_background_color (CLUTTER_ACTOR (self->stage), &background_color); self->priv->last_button_event_time = 0; g_signal_connect (self->stage, "button-press-event", G_CALLBACK (stage_input_cb), self); g_signal_connect (self->stage, "motion-event", G_CALLBACK (stage_input_cb), self); g_signal_connect (self->stage, "key-press-event", G_CALLBACK (stage_input_cb), self); g_signal_connect (self->stage, "key-release-event", G_CALLBACK (stage_input_cb), self); self->priv->image1 = gtk_clutter_texture_new (); clutter_actor_hide (self->priv->image1); clutter_actor_add_child (CLUTTER_ACTOR (self->stage), self->priv->image1); self->priv->image2 = gtk_clutter_texture_new (); clutter_actor_hide (self->priv->image2); clutter_actor_add_child (CLUTTER_ACTOR (self->stage), self->priv->image2); self->current_image = NULL; self->next_image = self->priv->image1; self->priv->timeline = clutter_timeline_new (GTH_TRANSITION_DURATION); clutter_timeline_set_progress_mode (self->priv->timeline, CLUTTER_EASE_IN_OUT_SINE); g_signal_connect (self->priv->timeline, "completed", G_CALLBACK (animation_completed_cb), self); g_signal_connect (self->priv->timeline, "new-frame", G_CALLBACK (animation_frame_cb), self); g_signal_connect (self->priv->timeline, "started", G_CALLBACK (animation_started_cb), self); self->priv->paused_actor = gtk_clutter_texture_new (); if (self->priv->pause_pixbuf != NULL) gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (self->priv->paused_actor), self->priv->pause_pixbuf, NULL); else gtk_clutter_texture_set_from_icon_name (GTK_CLUTTER_TEXTURE (self->priv->paused_actor), GTK_WIDGET (self), "media-playback-pause-symbolic", GTK_ICON_SIZE_DIALOG, NULL); clutter_actor_hide (self->priv->paused_actor); clutter_actor_add_child (CLUTTER_ACTOR (self->stage), self->priv->paused_actor); g_signal_connect (self, "size-allocate", G_CALLBACK (gth_slideshow_size_allocate_cb), self); gtk_widget_show (embed); gtk_container_add (GTK_CONTAINER (self), embed); } GthProjector clutter_projector = { clutter_projector_construct, clutter_projector_paused, clutter_projector_show_cursor, clutter_projector_hide_cursor, clutter_projector_finalize, clutter_projector_image_ready, clutter_projector_load_prev_image, clutter_projector_load_next_image }; #endif /* HAVE_CLUTTER*/