/* Copyright (C) 2007 Christian Dywan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for the full license text. */ #include "katze-throbber.h" #include G_DEFINE_TYPE (KatzeThrobber, katze_throbber, GTK_TYPE_MISC) struct _KatzeThrobberPrivate { GtkIconSize icon_size; gchar* icon_name; GdkPixbuf* pixbuf; gchar* stock_id; gboolean animated; gchar* static_icon_name; GdkPixbuf* static_pixbuf; gchar* static_stock_id; gint index; gint timer_id; gint width; gint height; }; #define KATZE_THROBBER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KATZE_TYPE_THROBBER, KatzeThrobberPrivate)) enum { PROP_0, PROP_ICON_SIZE, PROP_ICON_NAME, PROP_PIXBUF, PROP_ANIMATED, PROP_STATIC_ICON_NAME, PROP_STATIC_PIXBUF, PROP_STATIC_STOCK_ID }; static void katze_throbber_dispose(GObject* object); static void katze_throbber_set_property (GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec); static void katze_throbber_get_property (GObject* object, guint prop_id, GValue* value, GParamSpec* pspec); static void katze_throbber_destroy (GtkObject* object); static void katze_throbber_realize (GtkWidget* widget); static void katze_throbber_unrealize (GtkWidget* widget); static void katze_throbber_map (GtkWidget* widget); static void katze_throbber_unmap (GtkWidget* widget); static void katze_throbber_style_set (GtkWidget* widget, GtkStyle* style); static void katze_throbber_screen_changed (GtkWidget* widget, GdkScreen* screen_prev); static void katze_throbber_size_request (GtkWidget* widget, GtkRequisition* requisition); static gboolean katze_throbber_expose_event (GtkWidget* widget, GdkEventExpose* event); static void icon_theme_changed (KatzeThrobber* throbber); static gboolean katze_throbber_timeout (KatzeThrobber* throbber); static void katze_throbber_timeout_destroy (KatzeThrobber* throbber); static void katze_throbber_class_init (KatzeThrobberClass* class) { GObjectClass* gobject_class = G_OBJECT_CLASS (class); gobject_class->dispose = katze_throbber_dispose; gobject_class->set_property = katze_throbber_set_property; gobject_class->get_property = katze_throbber_get_property; GtkObjectClass* object_class = GTK_OBJECT_CLASS (class); object_class->destroy = katze_throbber_destroy; GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (class); widget_class->realize = katze_throbber_realize; widget_class->unrealize = katze_throbber_unrealize; widget_class->map = katze_throbber_map; widget_class->unmap = katze_throbber_unmap; widget_class->style_set = katze_throbber_style_set; widget_class->screen_changed = katze_throbber_screen_changed; widget_class->size_request = katze_throbber_size_request; widget_class->expose_event = katze_throbber_expose_event; GParamFlags flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT; g_object_class_install_property (gobject_class, PROP_ICON_SIZE, g_param_spec_int ( "icon-size", "Icon size", "Symbolic size to use for the animation", 0, G_MAXINT, GTK_ICON_SIZE_MENU, flags)); g_object_class_install_property (gobject_class, PROP_ICON_NAME, g_param_spec_string ( "icon-name", "Icon Name", "The name of an icon containing animation frames", "process-working", flags)); g_object_class_install_property (gobject_class, PROP_PIXBUF, g_param_spec_object ( "pixbuf", "Pixbuf", "A GdkPixbuf containing animation frames", GDK_TYPE_PIXBUF, flags)); g_object_class_install_property (gobject_class, PROP_ANIMATED, g_param_spec_boolean ( "animated", "Animated", "Whether the throbber should be animated", FALSE, flags)); g_object_class_install_property (gobject_class, PROP_STATIC_ICON_NAME, g_param_spec_string ( "static-icon-name", "Static Icon Name", "The name of an icon to be used as the static image", NULL, flags)); g_object_class_install_property (gobject_class, PROP_PIXBUF, g_param_spec_object ( "static-pixbuf", "Static Pixbuf", "A GdkPixbuf to be used as the static image", GDK_TYPE_PIXBUF, flags)); g_object_class_install_property (gobject_class, PROP_STATIC_STOCK_ID, g_param_spec_string ( "static-stock-id", "Static Stock ID", "The stock ID of an icon to be used as the static image", NULL, flags)); g_type_class_add_private (object_class, sizeof (KatzeThrobberPrivate)); } static void katze_throbber_init (KatzeThrobber *throbber) { GTK_WIDGET_SET_FLAGS (throbber, GTK_NO_WINDOW); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); priv->timer_id = -1; } static void katze_throbber_dispose (GObject *object) { KatzeThrobber* throbber = KATZE_THROBBER (object); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); if (G_UNLIKELY (priv->timer_id >= 0)) g_source_remove (priv->timer_id); (*G_OBJECT_CLASS (katze_throbber_parent_class)->dispose) (object); } static void katze_throbber_destroy (GtkObject *object) { KatzeThrobber* throbber = KATZE_THROBBER (object); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); katze_assign (priv->icon_name, NULL); if (priv->pixbuf) katze_object_assign (priv->pixbuf, NULL); katze_assign (priv->static_icon_name, NULL); if (priv->static_pixbuf) katze_object_assign (priv->static_pixbuf, NULL); katze_assign (priv->static_stock_id, NULL); GTK_OBJECT_CLASS (katze_throbber_parent_class)->destroy (object); } static void katze_throbber_set_property (GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { KatzeThrobber* throbber = KATZE_THROBBER (object); switch (prop_id) { case PROP_ICON_SIZE: katze_throbber_set_icon_size (throbber, g_value_get_int (value)); break; case PROP_ICON_NAME: katze_throbber_set_icon_name (throbber, g_value_get_string (value)); break; case PROP_PIXBUF: katze_throbber_set_pixbuf (throbber, g_value_get_object (value)); break; case PROP_ANIMATED: katze_throbber_set_animated (throbber, g_value_get_boolean (value)); break; case PROP_STATIC_ICON_NAME: katze_throbber_set_static_icon_name (throbber, g_value_get_string (value)); break; case PROP_STATIC_PIXBUF: katze_throbber_set_static_pixbuf (throbber, g_value_get_object (value)); break; case PROP_STATIC_STOCK_ID: katze_throbber_set_static_stock_id (throbber, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void katze_throbber_get_property (GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { KatzeThrobber* throbber = KATZE_THROBBER (object); switch (prop_id) { case PROP_ICON_SIZE: g_value_set_int (value, katze_throbber_get_icon_size (throbber)); break; case PROP_ICON_NAME: g_value_set_string (value, katze_throbber_get_icon_name (throbber)); break; case PROP_PIXBUF: g_value_set_object (value, katze_throbber_get_pixbuf (throbber)); break; case PROP_ANIMATED: g_value_set_boolean (value, katze_throbber_get_animated (throbber)); break; case PROP_STATIC_ICON_NAME: g_value_set_string (value, katze_throbber_get_static_icon_name (throbber)); break; case PROP_STATIC_PIXBUF: g_value_set_object (value, katze_throbber_get_static_pixbuf (throbber)); break; case PROP_STATIC_STOCK_ID: g_value_set_string (value, katze_throbber_get_static_stock_id (throbber)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * katze_throbber_new: * * Creates a new throbber widget. * * Return value: a new #KatzeThrobber **/ GtkWidget* katze_throbber_new (void) { KatzeThrobber* throbber = g_object_new (KATZE_TYPE_THROBBER, NULL); return GTK_WIDGET (throbber); } /** * katze_throbber_set_icon_size: * @throbber: a #KatzeThrobber * @icon_size: the new icon size * * Sets the desired size of the throbber image. The animation and static image * will be displayed in this size. If a pixbuf is used for the animation every * single frame is assumed to have this size. **/ void katze_throbber_set_icon_size (KatzeThrobber* throbber, GtkIconSize icon_size) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); g_return_if_fail (gtk_icon_size_lookup (icon_size, &priv->width, &priv->height)); priv->icon_size = icon_size; g_object_notify (G_OBJECT (throbber), "icon-size"); } /** * katze_throbber_set_icon_name: * @throbber: a #KatzeThrobber * @icon_name: an icon name or %NULL * * Sets the name of an icon that should provide the animation frames. * * The pixbuf is automatically invalidated. **/ void katze_throbber_set_icon_name (KatzeThrobber* throbber, const gchar* icon_name) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); katze_assign (priv->icon_name, g_strdup (icon_name)); if (icon_name) icon_theme_changed (throbber); g_object_notify (G_OBJECT (throbber), "icon-name"); } /** * katze_throbber_set_pixbuf: * @throbber: a #KatzeThrobber * @pixbuf: a #GdkPixbuf or %NULL * * Sets the pixbuf that should provide the animation frames. Every frame * is assumed to have the icon size of the throbber, which can be specified * with katze_throbber_set_icon_size (). * * The icon name is automatically invalidated. **/ void katze_throbber_set_pixbuf (KatzeThrobber* throbber, GdkPixbuf *pixbuf) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); katze_object_assign (priv->pixbuf, pixbuf); if (pixbuf) { g_object_ref (pixbuf); katze_assign (priv->icon_name, NULL); } gtk_widget_queue_draw (GTK_WIDGET (throbber)); g_object_notify (G_OBJECT (throbber), "pixbuf"); } /** * katze_throbber_set_animated: * @throbber: a #KatzeThrobber * @animated: %TRUE to animate the throbber * * Sets the animation state of the throbber. **/ void katze_throbber_set_animated (KatzeThrobber* throbber, gboolean animated) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); if (G_UNLIKELY (priv->animated == animated)) return; priv->animated = animated; if (animated && (priv->timer_id < 0)) priv->timer_id = g_timeout_add_full ( G_PRIORITY_LOW, 50, (GSourceFunc)katze_throbber_timeout, throbber, (GDestroyNotify)katze_throbber_timeout_destroy); gtk_widget_queue_draw (GTK_WIDGET (throbber)); g_object_notify (G_OBJECT (throbber), "animated"); } /** * katze_throbber_set_static_icon_name: * @throbber: a #KatzeThrobber * @icon_name: an icon name or %NULL * * Sets the name of an icon that should provide the static image. * * The static pixbuf and stock ID are automatically invalidated. **/ void katze_throbber_set_static_icon_name (KatzeThrobber* throbber, const gchar* icon_name) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); katze_assign (priv->static_icon_name, g_strdup (icon_name)); if (icon_name) { katze_assign (priv->static_stock_id, NULL); icon_theme_changed (throbber); } g_object_notify (G_OBJECT (throbber), "static-icon-name"); } /** * katze_throbber_set_static_pixbuf: * @throbber: a #KatzeThrobber * @pixbuf: a #GdkPixbuf or %NULL * * Sets the pixbuf that should provide the static image. The pixbuf is * assumed to have the icon size of the throbber, which can be specified * with katze_throbber_set_icon_size (). * * The static icon name and stock ID are automatically invalidated. **/ void katze_throbber_set_static_pixbuf (KatzeThrobber* throbber, GdkPixbuf *pixbuf) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); katze_object_assign (priv->static_pixbuf, pixbuf); if (pixbuf) { g_object_ref (pixbuf); katze_assign (priv->static_icon_name, NULL); katze_assign (priv->static_stock_id, NULL); } g_object_notify (G_OBJECT (throbber), "static-pixbuf"); } /** * katze_throbber_set_static_stock_id: * @throbber: a #KatzeThrobber * @stock_id: a stock ID or %NULL * * Sets the stock ID of an icon that should provide the static image. * * The statc icon name and pixbuf are automatically invalidated. **/ void katze_throbber_set_static_stock_id (KatzeThrobber* throbber, const gchar* stock_id) { g_return_if_fail (KATZE_IS_THROBBER (throbber)); if (stock_id) { GtkStockItem stock_item; g_return_if_fail (gtk_stock_lookup (stock_id, &stock_item)); } KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); katze_assign (priv->static_stock_id, g_strdup (stock_id)); if (stock_id) icon_theme_changed (throbber); g_object_notify (G_OBJECT (throbber), "static-stock-id"); } /** * katze_throbber_get_icon_size: * @throbber: a #KatzeThrobber * * Retrieves the size of the throbber. * * Return value: the size of the throbber **/ GtkIconSize katze_throbber_get_icon_size (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), GTK_ICON_SIZE_INVALID); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->icon_size; } /** * katze_throbber_get_icon_name: * @throbber: a #KatzeThrobber * * Retrieves the name of the icon providing the animation frames. * * Return value: the name of the icon providing the animation frames, or %NULL **/ const gchar* katze_throbber_get_icon_name (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), NULL); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->icon_name; } /** * katze_throbber_get_pixbuf: * @throbber: a #KatzeThrobber * * Retrieves the #GdkPixbuf providing the animation frames if an icon name * or pixbuf is available. The caller of this function does not own a * reference to the returned pixbuf. * * Return value: the pixbuf providing the animation frames, or %NULL **/ GdkPixbuf* katze_throbber_get_pixbuf (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), NULL); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->pixbuf; } /** * katze_throbber_get_animated: * @throbber: a #KatzeThrobber * * Retrieves the status of the animation, whcih can be animated or static. * * Return value: %TRUE if the throbber is animated **/ gboolean katze_throbber_get_animated (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), FALSE); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->animated; } /** * katze_throbber_get_static_icon_name: * @throbber: a #KatzeThrobber * * Retrieves the name of the icon providing the static image, if an icon name * for the static image was specified. * * Return value: the name of the icon providing the static image, or %NULL **/ const gchar* katze_throbber_get_static_icon_name (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), NULL); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->static_icon_name; } /** * katze_throbber_get_static pixbuf: * @throbber: a #KatzeThrobber * * Retrieves the #GdkPixbuf providing the static image, if an icon name, a * pixbuf or a stock ID for the static image was specified. The caller of this * function does not own a reference to the returned pixbuf. * * Return value: the pixbuf providing the static image, or %NULL **/ GdkPixbuf* katze_throbber_get_static_pixbuf (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), NULL); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->static_pixbuf; } /** * katze_throbber_get_static_stock_id: * @throbber: a #KatzeThrobber * * Retrieves the stock ID of the icon providing the static image, if a * stock ID for the static image was specified. * * Return value: the stock ID of the icon providing the static image, or %NULL **/ const gchar* katze_throbber_get_static_stock_id (KatzeThrobber* throbber) { g_return_val_if_fail (KATZE_IS_THROBBER (throbber), NULL); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); return priv->static_stock_id; } static void katze_throbber_realize (GtkWidget* widget) { (*GTK_WIDGET_CLASS (katze_throbber_parent_class)->realize) (widget); icon_theme_changed (KATZE_THROBBER (widget)); } static void katze_throbber_unrealize (GtkWidget* widget) { if (GTK_WIDGET_CLASS (katze_throbber_parent_class)->unrealize) GTK_WIDGET_CLASS (katze_throbber_parent_class)->unrealize (widget); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (KATZE_THROBBER (widget)); katze_object_assign (priv->pixbuf, NULL); katze_object_assign (priv->static_pixbuf, NULL); } static void pixbuf_assign_icon (GdkPixbuf** pixbuf, const gchar* icon_name, KatzeThrobber* throbber) { if (*pixbuf) g_object_unref (*pixbuf); KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); GdkScreen* screen = gtk_widget_get_screen (GTK_WIDGET (throbber)); GtkIconTheme* icon_theme = gtk_icon_theme_get_for_screen (screen); *pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, MAX (priv->width, priv->height), (GtkIconLookupFlags) 0, NULL); } static void icon_theme_changed (KatzeThrobber* throbber) { KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); if (priv->icon_name) pixbuf_assign_icon (&priv->pixbuf, priv->icon_name, throbber); if (priv->static_icon_name) pixbuf_assign_icon (&priv->static_pixbuf, priv->static_icon_name, throbber); else if (priv->static_stock_id) { if (priv->static_pixbuf) g_object_unref (priv->static_pixbuf); priv->static_pixbuf = gtk_widget_render_icon (GTK_WIDGET (throbber), priv->static_stock_id, priv->icon_size, NULL); } gtk_widget_queue_draw (GTK_WIDGET (throbber)); } static void katze_throbber_map (GtkWidget* widget) { (*GTK_WIDGET_CLASS (katze_throbber_parent_class)->map) (widget); } static void katze_throbber_unmap (GtkWidget* widget) { if (GTK_WIDGET_CLASS (katze_throbber_parent_class)->unmap) GTK_WIDGET_CLASS (katze_throbber_parent_class)->unmap (widget); } static gboolean katze_throbber_timeout (KatzeThrobber* throbber) { KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); priv->index++; gtk_widget_queue_draw (GTK_WIDGET (throbber)); return priv->animated; } static void katze_throbber_timeout_destroy (KatzeThrobber* throbber) { KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (throbber); priv->index = 0; priv->timer_id = -1; } static void katze_throbber_style_set (GtkWidget* widget, GtkStyle* prev_style) { if (GTK_WIDGET_CLASS (katze_throbber_parent_class)->style_set) GTK_WIDGET_CLASS (katze_throbber_parent_class)->style_set (widget, prev_style); icon_theme_changed (KATZE_THROBBER (widget)); } static void katze_throbber_screen_changed (GtkWidget* widget, GdkScreen* prev_screen) { if (GTK_WIDGET_CLASS (katze_throbber_parent_class)->screen_changed) GTK_WIDGET_CLASS (katze_throbber_parent_class)->screen_changed ( widget, prev_screen); icon_theme_changed (KATZE_THROBBER (widget)); } static void katze_throbber_size_request (GtkWidget* widget, GtkRequisition* requisition) { KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (KATZE_THROBBER (widget)); requisition->width = priv->width; requisition->height = priv->height; GTK_WIDGET_CLASS (katze_throbber_parent_class)->size_request (widget, requisition); } static gboolean katze_throbber_expose_event (GtkWidget* widget, GdkEventExpose* event) { KatzeThrobberPrivate* priv = KATZE_THROBBER_GET_PRIVATE (KATZE_THROBBER (widget)); if (G_UNLIKELY (!priv->width || !priv->height)) return TRUE; if (G_UNLIKELY (!priv->pixbuf && !priv->static_pixbuf)) if (priv->animated && !priv->pixbuf && !priv->icon_name) return TRUE; if (!priv->animated && (priv->static_pixbuf || priv->static_icon_name || priv->static_stock_id)) { if (G_UNLIKELY (!priv->static_pixbuf && priv->static_icon_name)) { icon_theme_changed (KATZE_THROBBER (widget)); if (!priv->static_pixbuf) { g_warning ("Named icon '%s' couldn't be loaded", priv->static_icon_name); katze_assign (priv->static_icon_name, NULL); return TRUE; } } else if (G_UNLIKELY (!priv->static_pixbuf && priv->static_stock_id)) { icon_theme_changed (KATZE_THROBBER (widget)); if (!priv->static_pixbuf) { g_warning ("Stock icon '%s' couldn't be loaded", priv->static_stock_id); katze_assign (priv->static_stock_id, NULL); return TRUE; } } gdk_draw_pixbuf (event->window, NULL, priv->static_pixbuf, 0, 0, widget->allocation.x, widget->allocation.y, priv->width, priv->height, GDK_RGB_DITHER_NONE, 0, 0); } else { if (G_UNLIKELY (priv->icon_name && !priv->pixbuf)) { icon_theme_changed (KATZE_THROBBER (widget)); if (!priv->pixbuf) { g_warning ("Icon '%s' couldn't be loaded", priv->icon_name); katze_assign (priv->icon_name, NULL); return TRUE; } } if (G_UNLIKELY (!priv->pixbuf)) return TRUE; gint cols = gdk_pixbuf_get_width (priv->pixbuf) / priv->width; gint rows = gdk_pixbuf_get_height (priv->pixbuf) / priv->height; if (G_LIKELY (cols > 0 && rows > 0)) { gint index = priv->index % (cols * rows); if (G_LIKELY (priv->timer_id >= 0)) index = MAX (index, 1); guint x = (index % cols) * priv->width; guint y = (index / cols) * priv->height; gdk_draw_pixbuf (event->window, NULL, priv->pixbuf, x, y, widget->allocation.x, widget->allocation.y, priv->width, priv->height, GDK_RGB_DITHER_NONE, 0, 0); } else { g_warning ("Animation frames are broken"); katze_assign (priv->icon_name, NULL); katze_object_assign (priv->pixbuf, NULL); } } return TRUE; }