[rhythmbox] New widget for displaying album art images
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] New widget for displaying album art images
- Date: Wed, 28 Mar 2012 13:23:33 +0000 (UTC)
commit 739e2b31662f936ac90f4a7f247ee1acb8dfb282
Author: Jonathan Matthew <jonathan d14n org>
Date: Wed Mar 28 22:18:28 2012 +1000
New widget for displaying album art images
This is similar to the widget in the artdisplay plugin,
except it uses cairo for compositing rather than gdk-pixbuf,
it displays a larger image in a tooltip (up to a limit),
and it fades between images at a much higher frame rate.
configure.ac | 1 +
data/icons/hicolor/48x48/Makefile.am | 2 +-
data/icons/hicolor/48x48/status/Makefile.am | 11 +
.../48x48/status/rhythmbox-missing-artwork.png | Bin 0 -> 722 bytes
lib/rb-stock-icons.c | 1 +
lib/rb-stock-icons.h | 1 +
po/POTFILES.in | 1 +
widgets/Makefile.am | 6 +-
widgets/rb-fading-image.c | 681 ++++++++++++++++++++
widgets/rb-fading-image.h | 68 ++
10 files changed, 769 insertions(+), 3 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a469f1a..230230e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -851,6 +851,7 @@ data/icons/hicolor/32x32/apps/Makefile
data/icons/hicolor/32x32/places/Makefile
data/icons/hicolor/48x48/Makefile
data/icons/hicolor/48x48/apps/Makefile
+data/icons/hicolor/48x48/status/Makefile
data/icons/hicolor/256x256/Makefile
data/icons/hicolor/256x256/apps/Makefile
data/icons/hicolor/scalable/Makefile
diff --git a/data/icons/hicolor/48x48/Makefile.am b/data/icons/hicolor/48x48/Makefile.am
index ebbd145..1eae4a6 100644
--- a/data/icons/hicolor/48x48/Makefile.am
+++ b/data/icons/hicolor/48x48/Makefile.am
@@ -1 +1 @@
-SUBDIRS = apps
+SUBDIRS = apps status
diff --git a/data/icons/hicolor/48x48/status/Makefile.am b/data/icons/hicolor/48x48/status/Makefile.am
new file mode 100644
index 0000000..70f97fb
--- /dev/null
+++ b/data/icons/hicolor/48x48/status/Makefile.am
@@ -0,0 +1,11 @@
+themedir = $(datadir)/icons/hicolor
+size = 48x48
+context = status
+
+iconsdir = $(themedir)/$(size)/$(context)
+
+icons_DATA = \
+ rhythmbox-missing-artwork.png
+
+EXTRA_DIST = \
+ $(icons_DATA)
diff --git a/data/icons/hicolor/48x48/status/rhythmbox-missing-artwork.png b/data/icons/hicolor/48x48/status/rhythmbox-missing-artwork.png
new file mode 100644
index 0000000..e198da6
Binary files /dev/null and b/data/icons/hicolor/48x48/status/rhythmbox-missing-artwork.png differ
diff --git a/lib/rb-stock-icons.c b/lib/rb-stock-icons.c
index 8efa041..9c257ea 100644
--- a/lib/rb-stock-icons.c
+++ b/lib/rb-stock-icons.c
@@ -59,6 +59,7 @@ const char RB_STOCK_PLAYLIST[] = "playlist";
const char RB_STOCK_PLAYLIST_NEW[] = "playlist-new";
const char RB_STOCK_AUTO_PLAYLIST[] = "playlist-automatic";
const char RB_STOCK_AUTO_PLAYLIST_NEW[] = "playlist-automatic-new";
+const char RB_STOCK_MISSING_ARTWORK[] = "rhythmbox-missing-artwork";
const char GNOME_MEDIA_SHUFFLE[] = "media-playlist-shuffle";
const char GNOME_MEDIA_REPEAT[] = "media-playlist-repeat";
const char GNOME_MEDIA_EJECT[] = "media-eject";
diff --git a/lib/rb-stock-icons.h b/lib/rb-stock-icons.h
index 662fa5f..fee81fa 100644
--- a/lib/rb-stock-icons.h
+++ b/lib/rb-stock-icons.h
@@ -43,6 +43,7 @@ extern const char RB_STOCK_PLAYLIST[];
extern const char RB_STOCK_PLAYLIST_NEW[];
extern const char RB_STOCK_AUTO_PLAYLIST[];
extern const char RB_STOCK_AUTO_PLAYLIST_NEW[];
+extern const char RB_STOCK_MISSING_ARTWORK[];
extern const char GNOME_MEDIA_SHUFFLE[];
extern const char GNOME_MEDIA_REPEAT[];
extern const char GNOME_MEDIA_EJECT[];
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cad1047..27cd6c7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -181,6 +181,7 @@ widgets/rb-alert-dialog.c
widgets/rb-cell-renderer-pixbuf.c
widgets/rb-dialog.c
widgets/rb-entry-view.c
+widgets/rb-fading-image.c
widgets/rb-header.c
widgets/rb-library-browser.c
widgets/rb-property-view.c
diff --git a/widgets/Makefile.am b/widgets/Makefile.am
index a2aa52a..9f8510d 100644
--- a/widgets/Makefile.am
+++ b/widgets/Makefile.am
@@ -13,7 +13,8 @@ widgetinclude_HEADERS = \
rb-segmented-bar.h \
rb-song-info.h \
rb-source-toolbar.h \
- rb-uri-dialog.h
+ rb-uri-dialog.h \
+ rb-fading-image.h
librbwidgets_la_SOURCES = \
$(widgetinclude_HEADERS) \
@@ -44,7 +45,8 @@ librbwidgets_la_SOURCES = \
eggwrapbox.h \
eggwrapbox-enums.c \
eggwrapbox-enums.h \
- rb-source-toolbar.c
+ rb-source-toolbar.c \
+ rb-fading-image.c
INCLUDES = \
-DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
diff --git a/widgets/rb-fading-image.c b/widgets/rb-fading-image.c
new file mode 100644
index 0000000..2173ec3
--- /dev/null
+++ b/widgets/rb-fading-image.c
@@ -0,0 +1,681 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Jonathan Matthew <jonathan d14n org>
+ *
+ * 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.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <widgets/rb-fading-image.h>
+#include <lib/rb-debug.h>
+#include <lib/rb-util.h>
+
+#define RENDER_FRAME_TIME (1000 / 25) /* fps? */
+#define BORDER_WIDTH 1.0
+
+#define MAX_TOOLTIP_SIZE 256
+
+static void rb_fading_image_class_init (RBFadingImageClass *klass);
+static void rb_fading_image_init (RBFadingImage *image);
+
+struct _RBFadingImagePrivate
+{
+ char *fallback_icon;
+ cairo_pattern_t *current_pat;
+ cairo_pattern_t *next_pat;
+ cairo_pattern_t *fallback_pat;
+ gdouble alpha;
+
+ GdkPixbuf *current;
+ GdkPixbuf *current_full;
+ GdkPixbuf *next;
+ GdkPixbuf *next_full;
+ GdkPixbuf *fallback;
+ GdkPixbufLoader *loader;
+
+ guint64 start;
+ guint64 end;
+ gulong render_timer_id;
+};
+
+G_DEFINE_TYPE (RBFadingImage, rb_fading_image, GTK_TYPE_WIDGET)
+
+/**
+ * SECTION:rb-fading-image
+ * @short_description: image display widget that fades between two images
+ *
+ * This widget displays images, performing a simple fade transition between
+ * them. It also emits signals when URIs or pixbufs are dropped onto it.
+ */
+
+enum
+{
+ PROP_0,
+ PROP_FALLBACK
+};
+
+enum
+{
+ URI_DROPPED,
+ PIXBUF_DROPPED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static gboolean
+prepare_image (cairo_t *cr, cairo_pattern_t **save, GdkPixbuf *pixbuf)
+{
+ if (*save != NULL) {
+ cairo_set_source (cr, *save);
+ return TRUE;
+ }
+
+ if (pixbuf != NULL) {
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0.0, 0.0);
+ *save = cairo_get_source (cr);
+ cairo_pattern_reference (*save);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static void
+impl_realize (GtkWidget *widget)
+{
+ GtkAllocation allocation;
+ GdkWindowAttr attributes;
+ GdkWindow *window;
+ int attributes_mask;
+
+ gtk_widget_set_realized (widget, TRUE);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK;
+ attributes.visual = gtk_widget_get_visual (widget);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
+ gtk_widget_set_window (widget, window);
+ gdk_window_set_user_data (window, widget);
+
+ gtk_widget_set_can_focus (widget, TRUE);
+}
+
+static void
+draw_image (cairo_t *cr, GdkPixbuf *image, int width, int height, cairo_extend_t extend, double alpha)
+{
+ cairo_matrix_t matrix;
+ int image_width;
+ int image_height;
+
+ image_width = gdk_pixbuf_get_width (image);
+ image_height = gdk_pixbuf_get_height (image);
+
+ cairo_save (cr);
+
+ cairo_matrix_init_translate (&matrix,
+ - (BORDER_WIDTH + (width/2 - image_width/2)),
+ - (BORDER_WIDTH + (height/2 - image_height/2)));
+ cairo_pattern_set_matrix (cairo_get_source (cr), &matrix);
+ cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_BEST);
+ cairo_pattern_set_extend (cairo_get_source (cr), extend);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+ cairo_rectangle (cr, BORDER_WIDTH, BORDER_WIDTH, width, height);
+ cairo_clip (cr);
+ cairo_paint_with_alpha (cr, alpha);
+
+ cairo_restore (cr);
+}
+
+static gboolean
+impl_draw (GtkWidget *widget, cairo_t *cr)
+{
+ RBFadingImage *image;
+ int width;
+ int height;
+
+ image = RB_FADING_IMAGE (widget);
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+
+ cairo_save (cr);
+ cairo_set_line_width (cr, BORDER_WIDTH);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+
+ width -= 2 * BORDER_WIDTH;
+ height -= 2 * BORDER_WIDTH;
+
+ /* draw current image */
+ if (prepare_image (cr, &image->priv->current_pat, image->priv->current)) {
+ draw_image (cr,
+ image->priv->current,
+ width,
+ height,
+ CAIRO_EXTEND_NONE,
+ 1.0 - image->priv->alpha);
+ } else if (prepare_image (cr, &image->priv->fallback_pat, image->priv->fallback)) {
+ draw_image (cr,
+ image->priv->fallback,
+ width,
+ height,
+ CAIRO_EXTEND_PAD,
+ 1.0 - image->priv->alpha);
+ } else {
+ cairo_save (cr);
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ cairo_rectangle (cr, BORDER_WIDTH, BORDER_WIDTH, width, height);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_clip (cr);
+ cairo_paint (cr);
+ cairo_restore (cr);
+ }
+
+ /* if we're fading to a new image, draw that too */
+ if (image->priv->alpha < 0.001) {
+ /* do nothing */
+ } else if (prepare_image (cr, &image->priv->next_pat, image->priv->next)) {
+ draw_image (cr,
+ image->priv->next,
+ width,
+ height,
+ CAIRO_EXTEND_NONE,
+ image->priv->alpha);
+ } else if (prepare_image (cr, &image->priv->fallback_pat, image->priv->fallback)) {
+ draw_image (cr,
+ image->priv->fallback,
+ width,
+ height,
+ CAIRO_EXTEND_PAD,
+ image->priv->alpha);
+ } else {
+ /* also do nothing */
+ }
+
+ return TRUE;
+}
+
+static gboolean
+impl_query_tooltip (GtkWidget *widget, int x, int y, gboolean keyboard_mode, GtkTooltip *tooltip)
+{
+ RBFadingImage *image = RB_FADING_IMAGE (widget);
+ GdkPixbuf *scaled;
+ GdkPixbuf *full;
+
+ if (image->priv->render_timer_id != 0) {
+ full = image->priv->next_full;
+ scaled = image->priv->next;
+ } else {
+ full = image->priv->current_full;
+ scaled = image->priv->current;
+ }
+
+ if (full == NULL) {
+ gtk_tooltip_set_icon (tooltip, NULL);
+ gtk_tooltip_set_text (tooltip, _("Drop artwork here"));
+ return TRUE;
+ } else if (full == scaled) {
+ return FALSE;
+ } else {
+ gtk_tooltip_set_icon (tooltip, full);
+ return TRUE;
+ }
+}
+
+static void
+impl_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time_)
+{
+ GdkPixbuf *pixbuf;
+ char **uris;
+
+ pixbuf = gtk_selection_data_get_pixbuf (selection);
+ if (pixbuf != NULL) {
+ g_signal_emit (widget, signals[PIXBUF_DROPPED], 0, pixbuf);
+ g_object_unref (pixbuf);
+ return;
+ }
+
+ uris = gtk_selection_data_get_uris (selection);
+ if (uris != NULL) {
+ if (uris[0] != NULL) {
+ g_signal_emit (widget, signals[URI_DROPPED], 0, uris[0]);
+ }
+
+ g_strfreev (uris);
+ return;
+ }
+
+ rb_debug ("weird drag data received");
+}
+
+static void
+impl_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time_)
+{
+ RBFadingImage *image = RB_FADING_IMAGE (widget);
+
+ if (image->priv->current_full) {
+ gtk_selection_data_set_pixbuf (selection, image->priv->current_full);
+ }
+
+ /* might be nice if we could provide a uri here? */
+}
+
+
+static void
+impl_finalize (GObject *object)
+{
+ RBFadingImage *image = RB_FADING_IMAGE (object);
+
+ g_free (image->priv->fallback_icon);
+
+ if (image->priv->current_pat != NULL) {
+ cairo_pattern_destroy (image->priv->current_pat);
+ }
+ if (image->priv->next_pat != NULL) {
+ cairo_pattern_destroy (image->priv->next_pat);
+ }
+ if (image->priv->fallback_pat != NULL) {
+ cairo_pattern_destroy (image->priv->fallback_pat);
+ }
+
+ G_OBJECT_CLASS (rb_fading_image_parent_class)->finalize (object);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+ RBFadingImage *image = RB_FADING_IMAGE (object);
+
+ if (image->priv->render_timer_id != 0) {
+ g_source_remove (image->priv->render_timer_id);
+ image->priv->render_timer_id = 0;
+ }
+ if (image->priv->current != NULL) {
+ g_object_unref (image->priv->current);
+ image->priv->current = NULL;
+ }
+ if (image->priv->next != NULL) {
+ g_object_unref (image->priv->next);
+ image->priv->next = NULL;
+ }
+ if (image->priv->fallback != NULL) {
+ g_object_unref (image->priv->fallback);
+ image->priv->fallback = NULL;
+ }
+
+ G_OBJECT_CLASS (rb_fading_image_parent_class)->dispose (object);
+}
+
+static void
+impl_constructed (GObject *object)
+{
+ RBFadingImage *image;
+
+ RB_CHAIN_GOBJECT_METHOD (rb_fading_image_parent_class, constructed, object);
+
+ image = RB_FADING_IMAGE (object);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (image)),
+ GTK_STYLE_CLASS_SPINNER);
+
+ if (image->priv->fallback_icon != NULL) {
+ GError *error = NULL;
+ image->priv->fallback =
+ gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ image->priv->fallback_icon,
+ 48,
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ &error);
+ if (error != NULL) {
+ g_warning ("couldn't load fallback icon %s: %s", image->priv->fallback_icon, error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ gtk_widget_set_has_tooltip (GTK_WIDGET (image), TRUE);
+
+ /* drag and drop target */
+ gtk_drag_dest_set (GTK_WIDGET (image), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
+ gtk_drag_dest_add_image_targets (GTK_WIDGET (image));
+ gtk_drag_dest_add_uri_targets (GTK_WIDGET (image));
+
+ /* drag and drop source */
+ gtk_drag_source_set (GTK_WIDGET (image), GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY);
+ gtk_drag_source_add_image_targets (GTK_WIDGET (image));
+}
+
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ RBFadingImage *image = RB_FADING_IMAGE (object);
+
+ switch (prop_id) {
+ case PROP_FALLBACK:
+ g_value_set_string (value, image->priv->fallback_icon);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ RBFadingImage *image = RB_FADING_IMAGE (object);
+
+ switch (prop_id) {
+ case PROP_FALLBACK:
+ image->priv->fallback_icon = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+rb_fading_image_init (RBFadingImage *image)
+{
+ image->priv = G_TYPE_INSTANCE_GET_PRIVATE (image, RB_TYPE_FADING_IMAGE, RBFadingImagePrivate);
+}
+
+static void
+rb_fading_image_class_init (RBFadingImageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = impl_constructed;
+ object_class->dispose = impl_dispose;
+ object_class->finalize = impl_finalize;
+ object_class->set_property = impl_set_property;
+ object_class->get_property = impl_get_property;
+
+ widget_class->realize = impl_realize;
+ widget_class->draw = impl_draw;
+ widget_class->query_tooltip = impl_query_tooltip;
+ widget_class->drag_data_get = impl_drag_data_get;
+ widget_class->drag_data_received = impl_drag_data_received;
+
+ /**
+ * RBFadingImage:fallback:
+ *
+ * Name of an icon to display when no image is available.
+ */
+ g_object_class_install_property (object_class,
+ PROP_FALLBACK,
+ g_param_spec_string ("fallback",
+ "fallback",
+ "fallback icon name",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ /**
+ * RBFadingImage::uri-dropped
+ * @image: the #RBFadingImage
+ * @uri: the URI that was dropped
+ *
+ * Emitted when a URI is dragged and dropped on the image
+ */
+ signals[URI_DROPPED] =
+ g_signal_new ("uri-dropped",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+ /**
+ * RBFadingImage::pixbuf-dropped
+ * @image: the #RBFadingImage
+ * @pixbuf: the pixbuf that was dropped
+ *
+ * Emitted when an image is dragged and dropped on the image
+ */
+ signals[PIXBUF_DROPPED] =
+ g_signal_new ("pixbuf-dropped",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GDK_TYPE_PIXBUF);
+
+ g_type_class_add_private (klass, sizeof (RBFadingImagePrivate));
+}
+
+
+static GdkPixbuf *
+scale_thumbnail_if_necessary (RBFadingImage *image, GdkPixbuf *pixbuf)
+{
+ int w, h;
+ int pw, ph;
+ int sw, sh;
+ double factor;
+
+ w = gtk_widget_get_allocated_width (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
+ h = gtk_widget_get_allocated_height (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ if (pw <= w && ph <= h) {
+ return g_object_ref (pixbuf);
+ }
+
+ if (pw > ph) {
+ sw = w;
+ factor = (double) w / pw;
+ sh = (int)((double)ph * factor);
+ } else {
+ sh = h;
+ factor = (double) h / ph;
+ sw = (int)((double)pw * factor);
+ }
+
+ return gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_HYPER);
+}
+
+static GdkPixbuf *
+scale_full_if_necessary (RBFadingImage *image, GdkPixbuf *pixbuf)
+{
+ int pw, ph;
+ int sw, sh;
+ double factor;
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ if (pw <= MAX_TOOLTIP_SIZE && ph <= MAX_TOOLTIP_SIZE) {
+ return g_object_ref (pixbuf);
+ }
+ if (pw > ph) {
+ sw = MAX_TOOLTIP_SIZE;
+ factor = (double) MAX_TOOLTIP_SIZE / pw;
+ sh = (int)((double)ph * factor);
+ } else {
+ sh = MAX_TOOLTIP_SIZE;
+ factor = (double) MAX_TOOLTIP_SIZE / ph;
+ sw = (int)((double)pw * factor);
+ }
+
+ return gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_HYPER);
+}
+
+
+static void
+clear_next (RBFadingImage *image)
+{
+ if (image->priv->next_pat != NULL) {
+ cairo_pattern_destroy (image->priv->next_pat);
+ image->priv->next_pat = NULL;
+ }
+ if (image->priv->next != NULL) {
+ g_object_unref (image->priv->next);
+ image->priv->next = NULL;
+ }
+ if (image->priv->next_full != NULL) {
+ g_object_unref (image->priv->next_full);
+ image->priv->next_full = NULL;
+ }
+}
+
+static void
+replace_current (RBFadingImage *image, GdkPixbuf *next, GdkPixbuf *next_full)
+{
+ if (image->priv->current_pat != NULL) {
+ cairo_pattern_destroy (image->priv->current_pat);
+ image->priv->current_pat = NULL;
+ }
+ if (image->priv->current != NULL) {
+ g_object_unref (image->priv->current);
+ image->priv->current = NULL;
+ }
+ if (image->priv->current_full != NULL) {
+ g_object_unref (image->priv->current_full);
+ image->priv->current_full = NULL;
+ }
+ if (next != NULL) {
+ image->priv->current = g_object_ref (next);
+ }
+ if (next_full != NULL) {
+ image->priv->current_full = g_object_ref (next_full);
+ }
+
+}
+
+/**
+ * rb_fading_image_set_pixbuf:
+ * @image: a #RBFadingImage
+ * @pixbuf: (transfer none): the next pixbuf to display
+ *
+ * Sets the next image to be displayed.
+ */
+void
+rb_fading_image_set_pixbuf (RBFadingImage *image, GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *scaled = NULL;
+ GdkPixbuf *full = NULL;
+
+ clear_next (image);
+ if (pixbuf != NULL) {
+ scaled = scale_thumbnail_if_necessary (image, pixbuf);
+ full = scale_full_if_necessary (image, pixbuf);
+ }
+
+ if (image->priv->render_timer_id != 0) {
+ image->priv->next_full = full;
+ image->priv->next = scaled;
+ } else {
+ replace_current (image, scaled, full);
+ gtk_widget_queue_draw (GTK_WIDGET (image));
+ gtk_widget_trigger_tooltip_query (GTK_WIDGET (image));
+ }
+}
+
+static gboolean
+render_timer (RBFadingImage *image)
+{
+ gint64 now;
+
+ now = g_get_monotonic_time ();
+
+ /* calculate alpha, whether this is the last frame, etc. */
+ if (image->priv->next != NULL || image->priv->current != NULL) {
+ image->priv->alpha = (((double)now - image->priv->start) / (image->priv->end - image->priv->start));
+ if (image->priv->alpha > 1.0)
+ image->priv->alpha = 1.0;
+
+ gtk_widget_queue_draw (GTK_WIDGET (image));
+ }
+
+ if (now >= image->priv->end) {
+ replace_current (image, image->priv->next, image->priv->next_full);
+ clear_next (image);
+ gtk_widget_trigger_tooltip_query (GTK_WIDGET (image));
+ image->priv->alpha = 0.0;
+ image->priv->render_timer_id = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+update_render_timer (RBFadingImage *image)
+{
+ /* add timer if not already present */
+ if (image->priv->render_timer_id == 0) {
+ image->priv->render_timer_id = g_timeout_add (RENDER_FRAME_TIME,
+ (GSourceFunc) render_timer,
+ image);
+ }
+}
+
+/**
+ * rb_fading_image_start:
+ * @image: a #RBFadingImage
+ * @duration: length of fade in milliseconds
+ *
+ * Starts fading to the next image. If no next image has been supplied,
+ * the fallback image will be used instead. If the next image has been
+ * supplied, but has not finished loading yet, the fade will be delayed
+ * until it finishes. If the previous fade has not yet finished,
+ * something tricky happens.
+ */
+void
+rb_fading_image_start (RBFadingImage *image, guint64 duration)
+{
+ image->priv->start = g_get_monotonic_time ();
+ image->priv->end = image->priv->start + (duration * 1000);
+
+ update_render_timer (image);
+}
diff --git a/widgets/rb-fading-image.h b/widgets/rb-fading-image.h
new file mode 100644
index 0000000..ec710cb
--- /dev/null
+++ b/widgets/rb-fading-image.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 Jonathan Matthew <jonathan d14n org>
+ *
+ * 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.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef RB_FADING_IMAGE_H
+#define RB_FADING_IMAGE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_FADING_IMAGE (rb_fading_image_get_type ())
+#define RB_FADING_IMAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_FADING_IMAGE, RBFadingImage))
+#define RB_FADING_IMAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_FADING_IMAGE, RBFadingImageClass))
+#define RB_IS_FADING_IMAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_FADING_IMAGE))
+#define RB_IS_FADING_IMAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_FADING_IMAGE))
+#define RB_FADING_IMAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_FADING_IMAGE, RBFadingImageClass))
+
+typedef struct _RBFadingImage RBFadingImage;
+typedef struct _RBFadingImageClass RBFadingImageClass;
+typedef struct _RBFadingImagePrivate RBFadingImagePrivate;
+
+struct _RBFadingImage
+{
+ GtkWidget parent;
+
+ RBFadingImagePrivate *priv;
+};
+
+struct _RBFadingImageClass
+{
+ GtkWidgetClass parent;
+};
+
+/* create instances using g_object_new, since you'll need to set widget properties too */
+
+GType rb_fading_image_get_type (void);
+
+void rb_fading_image_set_pixbuf (RBFadingImage *image, GdkPixbuf *pixbuf);
+
+void rb_fading_image_start (RBFadingImage *image, guint64 duration);
+
+G_END_DECLS
+
+#endif /* RB_FADING_IMAGE_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]