/* * Nautilus * * Copyright (C) 2000 Eazel, Inc. * Copyright (C) 2002-2004 Marco Pesenti Gritti * Copyright (C) 2004 Christian Persch * * Nautilus 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. * * Nautilus 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Andy Hertzfeld * * Ephy port by Marco Pesenti Gritti * * This is the spinner (for busy feedback) for the location bar * * $Id$ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ephy-spinner.h" #include #include #include #define spinner_DEFAULT_TIMEOUT 100 /* Milliseconds Per Frame */ #define EPHY_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER, EphySpinnerDetails)) struct EphySpinnerDetails { GList *image_list; GdkPixbuf *quiescent_pixbuf; GtkIconTheme *icon_theme; GtkIconSize size; int max_frame; int delay; int current_frame; guint timer_task; }; static void ephy_spinner_class_init (EphySpinnerClass *class); static void ephy_spinner_init (EphySpinner *spinner); static void ephy_spinner_load_images (EphySpinner *spinner); static void ephy_spinner_unload_images (EphySpinner *spinner); static void ephy_spinner_remove_update_callback (EphySpinner *spinner); static void ephy_spinner_theme_changed (GtkIconTheme *icon_theme, EphySpinner *spinner); static GObjectClass *parent_class = NULL; GType ephy_spinner_get_type (void) { static GType type = 0; if (type == 0) { static const GTypeInfo our_info = { sizeof (EphySpinnerClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) ephy_spinner_class_init, NULL, NULL, /* class_data */ sizeof (EphySpinner), 0, /* n_preallocs */ (GInstanceInitFunc) ephy_spinner_init }; type = g_type_register_static (GTK_TYPE_EVENT_BOX, "EphySpinner", &our_info, 0); } return type; } static gboolean is_throbbing (EphySpinner *spinner) { return spinner->details->timer_task != 0; } /* loop through all the images taking their union to compute the width and height of the spinner */ static void get_spinner_dimensions (EphySpinner *spinner, int *spinner_width, int* spinner_height) { int current_width, current_height; int pixbuf_width, pixbuf_height; GList *image_list; GdkPixbuf *pixbuf; if (spinner->details->image_list == NULL) { ephy_spinner_load_images (spinner); } current_width = 0; current_height = 0; if (spinner->details->quiescent_pixbuf != NULL) { /* start with the quiescent image */ current_width = gdk_pixbuf_get_width (spinner->details->quiescent_pixbuf); current_height = gdk_pixbuf_get_height (spinner->details->quiescent_pixbuf); } /* loop through all the installed images, taking the union */ image_list = spinner->details->image_list; while (image_list != NULL) { pixbuf = GDK_PIXBUF (image_list->data); pixbuf_width = gdk_pixbuf_get_width (pixbuf); pixbuf_height = gdk_pixbuf_get_height (pixbuf); if (pixbuf_width > current_width) { current_width = pixbuf_width; } if (pixbuf_height > current_height) { current_height = pixbuf_height; } image_list = image_list->next; } /* return the result */ *spinner_width = current_width; *spinner_height = current_height; } static void ephy_spinner_init (EphySpinner *spinner) { GtkWidget *widget = GTK_WIDGET (spinner); gtk_widget_set_events (widget, gtk_widget_get_events (widget) | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); spinner->details = EPHY_SPINNER_GET_PRIVATE (spinner); spinner->details->delay = spinner_DEFAULT_TIMEOUT; /* FIXME: icon theme is per-screen, not global */ spinner->details->icon_theme = gtk_icon_theme_get_default (); g_signal_connect_object (spinner->details->icon_theme, "changed", G_CALLBACK (ephy_spinner_theme_changed), spinner, 0); spinner->details->quiescent_pixbuf = NULL; spinner->details->image_list = NULL; spinner->details->max_frame = 0; spinner->details->current_frame = 0; spinner->details->timer_task = 0; spinner->details->size = GTK_ICON_SIZE_INVALID; } /* handler for handling theme changes */ static void ephy_spinner_theme_changed (GtkIconTheme *icon_theme, EphySpinner *spinner) { gtk_widget_hide (GTK_WIDGET (spinner)); ephy_spinner_load_images (spinner); gtk_widget_show (GTK_WIDGET (spinner)); gtk_widget_queue_resize ( GTK_WIDGET (spinner)); } /* here's the routine that selects the image to draw, based on the spinner's state */ static GdkPixbuf * select_spinner_image (EphySpinner *spinner) { GList *element; if (spinner->details->timer_task == 0) { if (spinner->details->quiescent_pixbuf) { return g_object_ref (spinner->details->quiescent_pixbuf); } else { return NULL; } } if (spinner->details->image_list == NULL) { return NULL; } element = g_list_nth (spinner->details->image_list, spinner->details->current_frame); g_return_val_if_fail (element != NULL, NULL); return g_object_ref (element->data); } /* handle expose events */ static int ephy_spinner_expose (GtkWidget *widget, GdkEventExpose *event) { EphySpinner *spinner = EPHY_SPINNER (widget); GdkPixbuf *pixbuf; GdkGC *gc; int x_offset, y_offset, width, height; GdkRectangle pix_area, dest; if (!GTK_WIDGET_DRAWABLE (spinner)) return TRUE; if (spinner->details->image_list == NULL) { ephy_spinner_load_images (spinner); } pixbuf = select_spinner_image (spinner); if (pixbuf == NULL) { return FALSE; } width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); /* Compute the offsets for the image centered on our allocation */ x_offset = (widget->allocation.width - width) / 2; y_offset = (widget->allocation.height - height) / 2; pix_area.x = x_offset + widget->allocation.x; pix_area.y = y_offset + widget->allocation.y; pix_area.width = width; pix_area.height = height; if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest)) { g_object_unref (pixbuf); return FALSE; } gc = gdk_gc_new (widget->window); gdk_draw_pixbuf (widget->window, gc, pixbuf, dest.x - x_offset - widget->allocation.x, dest.y - y_offset - widget->allocation.y, dest.x, dest.y, dest.width, dest.height, GDK_RGB_DITHER_MAX, 0, 0); g_object_unref (gc); g_object_unref (pixbuf); return FALSE; } /* here's the actual timeout task to bump the frame and schedule a redraw */ static gboolean bump_spinner_frame (gpointer callback_data) { EphySpinner *spinner; spinner = EPHY_SPINNER (callback_data); if (!GTK_WIDGET_DRAWABLE (spinner)) { return TRUE; } spinner->details->current_frame += 1; if (spinner->details->current_frame > spinner->details->max_frame - 1) { spinner->details->current_frame = 0; } gtk_widget_queue_draw (GTK_WIDGET (spinner)); return TRUE; } /** * ephy_spinner_start: * @spinner: a #EphySpinner * * Start the spinner animation. **/ void ephy_spinner_start (EphySpinner *spinner) { if (is_throbbing (spinner)) { return; } if (spinner->details->timer_task != 0) { g_source_remove (spinner->details->timer_task); } /* reset the frame count */ spinner->details->current_frame = 0; spinner->details->timer_task = g_timeout_add (spinner->details->delay, bump_spinner_frame, spinner); } static void ephy_spinner_remove_update_callback (EphySpinner *spinner) { if (spinner->details->timer_task != 0) { g_source_remove (spinner->details->timer_task); } spinner->details->timer_task = 0; } /** * ephy_spinner_stop: * @spinner: a #EphySpinner * * Stop the spinner animation. **/ void ephy_spinner_stop (EphySpinner *spinner) { if (!is_throbbing (spinner)) { return; } ephy_spinner_remove_update_callback (spinner); gtk_widget_queue_draw (GTK_WIDGET (spinner)); } /* routines to load the images used to draw the spinner */ /* unload all the images, and the list itself */ static void ephy_spinner_unload_images (EphySpinner *spinner) { if (spinner->details->quiescent_pixbuf != NULL) { g_object_unref (spinner->details->quiescent_pixbuf); spinner->details->quiescent_pixbuf = NULL; } /* unref all the images in the list, and then let go of the list itself */ g_list_foreach (spinner->details->image_list, (GFunc) g_object_unref, NULL); g_list_free (spinner->details->image_list); spinner->details->image_list = NULL; spinner->details->current_frame = 0; } static GdkPixbuf * scale_to_real_size (EphySpinner *spinner, GdkPixbuf *pixbuf) { GdkPixbuf *result; int sw, sh, pw, ph; if (spinner->details->size == GTK_ICON_SIZE_INVALID) { return g_object_ref (pixbuf); } if (!gtk_icon_size_lookup (spinner->details->size, &sw, &sh)) { return NULL; } pw = gdk_pixbuf_get_width (pixbuf); ph = gdk_pixbuf_get_height (pixbuf); if (pw != sw || ph != sh) { result = gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_BILINEAR); } else { result = g_object_ref (pixbuf); } return result; } static GdkPixbuf * extract_frame (EphySpinner *spinner, GdkPixbuf *grid_pixbuf, int x, int y, int size) { GdkPixbuf *pixbuf, *result; if (x + size > gdk_pixbuf_get_width (grid_pixbuf) || y + size > gdk_pixbuf_get_height (grid_pixbuf)) { return NULL; } pixbuf = gdk_pixbuf_new_subpixbuf (grid_pixbuf, x, y, size, size); g_return_val_if_fail (pixbuf != NULL, NULL); result = scale_to_real_size (spinner, pixbuf); g_object_unref (pixbuf); return result; } /* load all of the images of the spinner sequentially */ static void ephy_spinner_load_images (EphySpinner *spinner) { int grid_width, grid_height, x, y, size; const char *icon; GdkPixbuf *icon_pixbuf, *pixbuf; GList *image_list; GtkIconInfo *icon_info; ephy_spinner_unload_images (spinner); /* Load the animation */ icon_info = gtk_icon_theme_lookup_icon (spinner->details->icon_theme, "gnome-spinner", -1, 0); if (icon_info == NULL) { g_warning ("Throbber animation not found"); return; } size = gtk_icon_info_get_base_size (icon_info); icon = gtk_icon_info_get_filename (icon_info); g_return_if_fail (icon != NULL); icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL); grid_width = gdk_pixbuf_get_width (icon_pixbuf); grid_height = gdk_pixbuf_get_height (icon_pixbuf); image_list = NULL; for (y = 0; y < grid_height; y += size) { for (x = 0; x < grid_width ; x += size) { pixbuf = extract_frame (spinner, icon_pixbuf, x, y, size); if (pixbuf) { image_list = g_list_prepend (image_list, pixbuf); } else { g_warning ("Cannot extract frame from the grid"); } } } spinner->details->image_list = g_list_reverse (image_list); spinner->details->max_frame = g_list_length (spinner->details->image_list); gtk_icon_info_free (icon_info); g_object_unref (icon_pixbuf); /* Load the rest icon */ icon_info = gtk_icon_theme_lookup_icon (spinner->details->icon_theme, "gnome-spinner-rest", -1, 0); if (icon_info == NULL) { g_warning ("Throbber rest icon not found"); return; } size = gtk_icon_info_get_base_size (icon_info); icon = gtk_icon_info_get_filename (icon_info); g_return_if_fail (icon != NULL); icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL); spinner->details->quiescent_pixbuf = scale_to_real_size (spinner, icon_pixbuf); g_object_unref (icon_pixbuf); gtk_icon_info_free (icon_info); } /* * ephy_spinner_set_size: * @spinner: a #EphySpinner * @size: the size of type %GtkIconSize * * Set the size of the spinner. Use %GTK_ICON_SIZE_INVALID to use the * native size of the icon. **/ void ephy_spinner_set_size (EphySpinner *spinner, GtkIconSize size) { if (size != spinner->details->size) { ephy_spinner_unload_images (spinner); spinner->details->size = size; gtk_widget_queue_resize (GTK_WIDGET (spinner)); } } /* handle setting the size */ static void ephy_spinner_size_request (GtkWidget *widget, GtkRequisition *requisition) { EphySpinner *spinner = EPHY_SPINNER (widget); int width = 0, height = 0; if (spinner->details->size == GTK_ICON_SIZE_INVALID) { get_spinner_dimensions (spinner, &width, &height); } else { gtk_icon_size_lookup (spinner->details->size, &width, &height); } requisition->width = width; requisition->height = height; /* allocate some extra margin so we don't butt up against toolbar edges */ if (spinner->details->size != GTK_ICON_SIZE_MENU) { requisition->width += 4; requisition->height += 4; } } static void ephy_spinner_finalize (GObject *object) { EphySpinner *spinner = EPHY_SPINNER (object); ephy_spinner_remove_update_callback (spinner); ephy_spinner_unload_images (spinner); G_OBJECT_CLASS (parent_class)->finalize (object); } static void ephy_spinner_class_init (EphySpinnerClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; object_class = G_OBJECT_CLASS (class); widget_class = GTK_WIDGET_CLASS (class); parent_class = g_type_class_peek_parent (class); object_class->finalize = ephy_spinner_finalize; widget_class->expose_event = ephy_spinner_expose; widget_class->size_request = ephy_spinner_size_request; g_type_class_add_private (object_class, sizeof (EphySpinnerDetails)); } /* * ephy_spinner_new: * * Create a new #EphySpinner. The spinner is a widget * that gives the user feedback about network status with * an animated image. * * Return Value: the spinner #GtkWidget **/ GtkWidget * ephy_spinner_new (void) { return GTK_WIDGET (g_object_new (EPHY_TYPE_SPINNER, "visible-window", FALSE, NULL)); }