]> git.0d.be Git - empathy.git/blob - libempathy-gtk/ephy-spinner.c
Updatre python binding
[empathy.git] / libempathy-gtk / ephy-spinner.c
1 /* 
2  * Copyright © 2000 Eazel, Inc.
3  * Copyright © 2002-2004 Marco Pesenti Gritti
4  * Copyright © 2004, 2006 Christian Persch
5  *
6  * Nautilus is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Nautilus is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Author: Andy Hertzfeld <andy@eazel.com>
21  *
22  * Ephy port by Marco Pesenti Gritti <marco@it.gnome.org>
23  * 
24  * $Id: ephy-spinner.c 2114 2006-12-25 12:15:00Z mr $
25  */
26
27 #include "config.h"
28
29 #include "ephy-spinner.h"
30
31 /* #include "ephy-debug.h" */
32 #define LOG(msg, args...)
33 #define START_PROFILER(name)
34 #define STOP_PROFILER(name)
35
36 #include <gdk-pixbuf/gdk-pixbuf.h>
37 #include <gtk/gtkicontheme.h>
38 #include <gtk/gtkiconfactory.h>
39 #include <gtk/gtksettings.h>
40
41 /* Spinner cache implementation */
42
43 #define EPHY_TYPE_SPINNER_CACHE                 (ephy_spinner_cache_get_type())
44 #define EPHY_SPINNER_CACHE(object)              (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCache))
45 #define EPHY_SPINNER_CACHE_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCacheClass))
46 #define EPHY_IS_SPINNER_CACHE(object)           (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_SPINNER_CACHE))
47 #define EPHY_IS_SPINNER_CACHE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_SPINNER_CACHE))
48 #define EPHY_SPINNER_CACHE_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCacheClass))
49
50 typedef struct _EphySpinnerCache        EphySpinnerCache;
51 typedef struct _EphySpinnerCacheClass   EphySpinnerCacheClass;
52 typedef struct _EphySpinnerCachePrivate EphySpinnerCachePrivate;
53
54 struct _EphySpinnerCacheClass
55 {
56         GObjectClass parent_class;
57 };
58
59 struct _EphySpinnerCache
60 {
61         GObject parent_object;
62
63         /*< private >*/
64         EphySpinnerCachePrivate *priv;
65 };
66
67 #define EPHY_SPINNER_CACHE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCachePrivate))
68
69 struct _EphySpinnerCachePrivate
70 {
71         /* Hash table of GdkScreen -> EphySpinnerCacheData */
72         GHashTable *hash;
73 };
74
75 typedef struct
76 {
77         guint ref_count;
78         GtkIconSize size;
79         int width;
80         int height;
81         GdkPixbuf **animation_pixbufs;
82         guint n_animation_pixbufs;
83 } EphySpinnerImages;
84
85 #define LAST_ICON_SIZE                  GTK_ICON_SIZE_DIALOG + 1
86 #define SPINNER_ICON_NAME               "process-working"
87 #define SPINNER_FALLBACK_ICON_NAME      "gnome-spinner"
88 #define EPHY_SPINNER_IMAGES_INVALID     ((EphySpinnerImages *) 0x1)
89
90 typedef struct
91 {
92         GdkScreen *screen;
93         GtkIconTheme *icon_theme;
94         EphySpinnerImages *images[LAST_ICON_SIZE];
95 } EphySpinnerCacheData;
96
97 static void ephy_spinner_cache_class_init (EphySpinnerCacheClass *klass);
98 static void ephy_spinner_cache_init       (EphySpinnerCache *cache);
99
100 static GObjectClass *ephy_spinner_cache_parent_class;
101
102 static GType
103 ephy_spinner_cache_get_type (void)
104 {
105         static GType type = 0;
106
107         if (G_UNLIKELY (type == 0))
108         {
109                 const GTypeInfo our_info =
110                 {
111                         sizeof (EphySpinnerCacheClass),
112                         NULL,
113                         NULL,
114                         (GClassInitFunc) ephy_spinner_cache_class_init,
115                         NULL,
116                         NULL,
117                         sizeof (EphySpinnerCache),
118                         0,
119                         (GInstanceInitFunc) ephy_spinner_cache_init
120                 };
121
122                 type = g_type_register_static (G_TYPE_OBJECT,
123                                                "EphySpinnerCache",
124                                                &our_info, 0);
125         }
126
127         return type;
128 }
129
130 static EphySpinnerImages *
131 ephy_spinner_images_ref (EphySpinnerImages *images)
132 {
133         g_return_val_if_fail (images != NULL, NULL);
134
135         images->ref_count++;
136
137         return images;
138 }
139
140 static void
141 ephy_spinner_images_unref (EphySpinnerImages *images)
142 {
143         g_return_if_fail (images != NULL);
144
145         images->ref_count--;
146         if (images->ref_count == 0)
147         {
148                 guint i;
149
150                 LOG ("Freeing spinner images %p for size %d", images, images->size);
151
152                 for (i = 0; i < images->n_animation_pixbufs; ++i)
153                 {
154                         g_object_unref (images->animation_pixbufs[i]);
155                 }
156                 g_free (images->animation_pixbufs);
157
158                 g_free (images);
159         }
160 }
161
162 static void
163 ephy_spinner_cache_data_unload (EphySpinnerCacheData *data)
164 {
165         GtkIconSize size;
166         EphySpinnerImages *images;
167
168         g_return_if_fail (data != NULL);
169
170         LOG ("EphySpinnerDataCache unload for screen %p", data->screen);
171
172         for (size = GTK_ICON_SIZE_INVALID; size < LAST_ICON_SIZE; ++size)
173         {
174                 images = data->images[size];
175                 data->images[size] = NULL;
176
177                 if (images != NULL && images != EPHY_SPINNER_IMAGES_INVALID)
178                 {
179                         ephy_spinner_images_unref (images);
180                 }
181         }
182 }
183
184 static GdkPixbuf *
185 extract_frame (GdkPixbuf *grid_pixbuf,
186                int x,
187                int y,
188                int size)
189 {
190         GdkPixbuf *pixbuf;
191
192         if (x + size > gdk_pixbuf_get_width (grid_pixbuf) ||
193             y + size > gdk_pixbuf_get_height (grid_pixbuf))
194         {
195                 return NULL;
196         }
197
198         pixbuf = gdk_pixbuf_new_subpixbuf (grid_pixbuf,
199                                            x, y,
200                                            size, size);
201         g_return_val_if_fail (pixbuf != NULL, NULL);
202
203         return pixbuf;
204 }
205
206 static GdkPixbuf *
207 scale_to_size (GdkPixbuf *pixbuf,
208                int dw,
209                int dh)
210 {
211         GdkPixbuf *result;
212         int pw, ph;
213
214         g_return_val_if_fail (pixbuf != NULL, NULL);
215
216         pw = gdk_pixbuf_get_width (pixbuf);
217         ph = gdk_pixbuf_get_height (pixbuf);
218
219         if (pw != dw || ph != dh)
220         {
221                 result = gdk_pixbuf_scale_simple (pixbuf, dw, dh,
222                                                   GDK_INTERP_BILINEAR);
223                 g_object_unref (pixbuf);
224                 return result;
225         }
226
227         return pixbuf;
228 }
229
230 static EphySpinnerImages *
231 ephy_spinner_images_load (GdkScreen *screen,
232                           GtkIconTheme *icon_theme,
233                           GtkIconSize icon_size)
234 {
235         EphySpinnerImages *images;
236         GdkPixbuf *icon_pixbuf, *pixbuf;
237         GtkIconInfo *icon_info = NULL;
238         int grid_width, grid_height, x, y, requested_size, size, isw, ish, n;
239         const char *icon;
240         GSList *list = NULL, *l;
241
242         LOG ("EphySpinnerCacheData loading for screen %p at size %d", screen, icon_size);
243
244         START_PROFILER ("loading spinner animation")
245
246         if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen),
247                                                 icon_size, &isw, &ish)) goto loser;
248
249         requested_size = MAX (ish, isw);
250
251         /* Load the animation. The 'rest icon' is the 0th frame */
252         icon_info = gtk_icon_theme_lookup_icon (icon_theme,
253                                                 SPINNER_ICON_NAME,
254                                                 requested_size, 0);
255         if (icon_info == NULL)
256         {
257                 g_warning ("Throbber animation not found");
258         
259                 /* If the icon naming spec compliant name wasn't found, try the old name */
260                 icon_info = gtk_icon_theme_lookup_icon (icon_theme,
261                                                         SPINNER_FALLBACK_ICON_NAME,
262                                                         requested_size, 0);
263                 if (icon_info == NULL)
264                 {
265                         g_warning ("Throbber fallback animation not found either");
266                         goto loser;
267                 }
268         }
269         g_assert (icon_info != NULL);
270
271         size = gtk_icon_info_get_base_size (icon_info);
272         icon = gtk_icon_info_get_filename (icon_info);
273         if (icon == NULL) goto loser;
274
275         icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL);
276         gtk_icon_info_free (icon_info);
277         icon_info = NULL;
278
279         if (icon_pixbuf == NULL)
280         {
281                 g_warning ("Could not load the spinner file");
282                 goto loser;
283         }
284
285         grid_width = gdk_pixbuf_get_width (icon_pixbuf);
286         grid_height = gdk_pixbuf_get_height (icon_pixbuf);
287
288         n = 0;
289         for (y = 0; y < grid_height; y += size)
290         {
291                 for (x = 0; x < grid_width ; x += size)
292                 {
293                         pixbuf = extract_frame (icon_pixbuf, x, y, size);
294
295                         if (pixbuf)
296                         {
297                                 list = g_slist_prepend (list, pixbuf);
298                                 ++n;
299                         }
300                         else
301                         {
302                                 g_warning ("Cannot extract frame (%d, %d) from the grid\n", x, y);
303                         }
304                 }
305         }
306
307         g_object_unref (icon_pixbuf);
308
309         if (list == NULL) goto loser;
310         g_assert (n > 0);
311
312         if (size > requested_size)
313         {
314                 for (l = list; l != NULL; l = l->next)
315                 {
316                         l->data = scale_to_size (l->data, isw, ish);
317                 }
318         }
319
320         /* Now we've successfully got all the data */
321         images = g_new (EphySpinnerImages, 1);
322         images->ref_count = 1;
323
324         images->size = icon_size;
325         images->width = images->height = requested_size;
326
327         images->n_animation_pixbufs = n;
328         images->animation_pixbufs = g_new (GdkPixbuf *, n);
329
330         for (l = list; l != NULL; l = l->next)
331         {
332                 g_assert (l->data != NULL);
333                 images->animation_pixbufs[--n] = l->data;
334         }
335         g_assert (n == 0);
336
337         g_slist_free (list);
338
339         STOP_PROFILER ("loading spinner animation")
340
341         return images;
342
343 loser:
344         if (icon_info)
345         {
346                 gtk_icon_info_free (icon_info);
347         }
348         g_slist_foreach (list, (GFunc) g_object_unref, NULL);
349
350         STOP_PROFILER ("loading spinner animation")
351
352         return NULL;
353 }
354
355 static EphySpinnerCacheData *
356 ephy_spinner_cache_data_new (GdkScreen *screen)
357 {
358         EphySpinnerCacheData *data;
359
360         data = g_new0 (EphySpinnerCacheData, 1);
361
362         data->screen = screen;
363         data->icon_theme = gtk_icon_theme_get_for_screen (screen);
364         g_signal_connect_swapped (data->icon_theme, "changed",
365                                   G_CALLBACK (ephy_spinner_cache_data_unload),
366                                   data);
367
368         return data;
369 }
370
371 static void
372 ephy_spinner_cache_data_free (EphySpinnerCacheData *data)
373 {
374         g_return_if_fail (data != NULL);
375         g_return_if_fail (data->icon_theme != NULL);
376
377         g_signal_handlers_disconnect_by_func
378                 (data->icon_theme,
379                  G_CALLBACK (ephy_spinner_cache_data_unload), data);
380
381         ephy_spinner_cache_data_unload (data);
382
383         g_free (data);
384 }
385
386 static EphySpinnerImages *
387 ephy_spinner_cache_get_images (EphySpinnerCache *cache,
388                                GdkScreen *screen,
389                                GtkIconSize icon_size)
390 {
391         EphySpinnerCachePrivate *priv = cache->priv;
392         EphySpinnerCacheData *data;
393         EphySpinnerImages *images;
394
395         LOG ("Getting animation images for screen %p at size %d", screen, icon_size);
396
397         g_return_val_if_fail (icon_size >= 0 && icon_size < LAST_ICON_SIZE, NULL);
398
399         /* Backward compat: "invalid" meant "native" size which doesn't exist anymore */
400         if (icon_size == GTK_ICON_SIZE_INVALID)
401         {
402                 icon_size = GTK_ICON_SIZE_DIALOG;
403         }
404
405         data = g_hash_table_lookup (priv->hash, screen);
406         if (data == NULL)
407         {
408                 data = ephy_spinner_cache_data_new (screen);
409                 /* FIXME: think about what happens when the screen's display is closed later on */
410                 g_hash_table_insert (priv->hash, screen, data);
411         }
412
413         images = data->images[icon_size];
414         if (images == EPHY_SPINNER_IMAGES_INVALID)
415         {
416                 /* Load failed, but don't try endlessly again! */
417                 return NULL;
418         }
419
420         if (images != NULL)
421         {
422                 /* Return cached data */
423                 return ephy_spinner_images_ref (images);
424         }
425
426         images = ephy_spinner_images_load (screen, data->icon_theme, icon_size);
427
428         if (images == NULL)
429         {
430                 /* Mark as failed-to-load */
431                 data->images[icon_size] = EPHY_SPINNER_IMAGES_INVALID;
432
433                 return NULL;
434         }
435
436         data->images[icon_size] = images;
437
438         return ephy_spinner_images_ref (images);
439 }
440
441 static void
442 ephy_spinner_cache_init (EphySpinnerCache *cache)
443 {
444         EphySpinnerCachePrivate *priv;
445
446         priv = cache->priv = EPHY_SPINNER_CACHE_GET_PRIVATE (cache);
447
448         LOG ("EphySpinnerCache initialising");
449
450         priv->hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
451                                             NULL,
452                                             (GDestroyNotify) ephy_spinner_cache_data_free);
453 }
454
455 static void
456 ephy_spinner_cache_finalize (GObject *object)
457 {
458         EphySpinnerCache *cache = EPHY_SPINNER_CACHE (object); 
459         EphySpinnerCachePrivate *priv = cache->priv;
460
461         g_hash_table_destroy (priv->hash);
462
463         LOG ("EphySpinnerCache finalised");
464
465         G_OBJECT_CLASS (ephy_spinner_cache_parent_class)->finalize (object);
466 }
467
468 static void
469 ephy_spinner_cache_class_init (EphySpinnerCacheClass *klass)
470 {
471         GObjectClass *object_class = G_OBJECT_CLASS (klass);
472
473         ephy_spinner_cache_parent_class = g_type_class_peek_parent (klass);
474
475         object_class->finalize = ephy_spinner_cache_finalize;
476
477         g_type_class_add_private (object_class, sizeof (EphySpinnerCachePrivate));
478 }
479
480 static EphySpinnerCache *spinner_cache = NULL;
481
482 static EphySpinnerCache *
483 ephy_spinner_cache_ref (void)
484 {
485         if (spinner_cache == NULL)
486         {
487                 EphySpinnerCache **cache_ptr;
488
489                 spinner_cache = g_object_new (EPHY_TYPE_SPINNER_CACHE, NULL);
490                 cache_ptr = &spinner_cache;
491                 g_object_add_weak_pointer (G_OBJECT (spinner_cache),
492                                            (gpointer *) cache_ptr);
493
494                 return spinner_cache;
495         }
496                 
497         return g_object_ref (spinner_cache);
498 }
499
500 /* Spinner implementation */
501
502 #define SPINNER_TIMEOUT 125 /* ms */
503
504 #define EPHY_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER, EphySpinnerDetails))
505
506 struct _EphySpinnerDetails
507 {
508         GtkIconTheme *icon_theme;
509         EphySpinnerCache *cache;
510         GtkIconSize size;
511         EphySpinnerImages *images;
512         guint current_image;
513         guint timeout;
514         guint timer_task;
515         guint spinning : 1;
516         guint need_load : 1;
517 };
518
519 static void ephy_spinner_class_init (EphySpinnerClass *class);
520 static void ephy_spinner_init       (EphySpinner *spinner);
521
522 static GObjectClass *parent_class;
523
524 GType
525 ephy_spinner_get_type (void)
526 {
527         static GType type = 0;
528
529         if (G_UNLIKELY (type == 0))
530         {
531                 const GTypeInfo our_info =
532                 {
533                         sizeof (EphySpinnerClass),
534                         NULL, /* base_init */
535                         NULL, /* base_finalize */
536                         (GClassInitFunc) ephy_spinner_class_init,
537                         NULL,
538                         NULL, /* class_data */
539                         sizeof (EphySpinner),
540                         0, /* n_preallocs */
541                         (GInstanceInitFunc) ephy_spinner_init
542                 };
543
544                 type = g_type_register_static (GTK_TYPE_WIDGET,
545                                                "EphySpinner",
546                                                &our_info, 0);
547         }
548
549         return type;
550 }
551
552 static gboolean
553 ephy_spinner_load_images (EphySpinner *spinner)
554 {
555         EphySpinnerDetails *details = spinner->details;
556
557         if (details->need_load)
558         {
559                 START_PROFILER ("ephy_spinner_load_images")
560
561                 details->images =
562                         ephy_spinner_cache_get_images
563                                 (details->cache,
564                                  gtk_widget_get_screen (GTK_WIDGET (spinner)),
565                                  details->size);
566
567                 STOP_PROFILER ("ephy_spinner_load_images")
568
569                 details->current_image = 0; /* 'rest' icon */
570                 details->need_load = FALSE;
571         }
572
573         return details->images != NULL;
574 }
575
576 static void
577 ephy_spinner_unload_images (EphySpinner *spinner)
578 {
579         EphySpinnerDetails *details = spinner->details;
580
581         if (details->images != NULL)
582         {
583                 ephy_spinner_images_unref (details->images);
584                 details->images = NULL;
585         }
586
587         details->current_image = 0;
588         details->need_load = TRUE;
589 }
590
591 static void
592 icon_theme_changed_cb (GtkIconTheme *icon_theme,
593                        EphySpinner *spinner)
594 {
595         ephy_spinner_unload_images (spinner);
596         gtk_widget_queue_resize (GTK_WIDGET (spinner));
597 }
598
599 static void
600 ephy_spinner_init (EphySpinner *spinner)
601 {
602         EphySpinnerDetails *details;
603
604         details = spinner->details = EPHY_SPINNER_GET_PRIVATE (spinner);
605
606         GTK_WIDGET_SET_FLAGS (GTK_WIDGET (spinner), GTK_NO_WINDOW);
607
608         details->cache = ephy_spinner_cache_ref ();
609         details->size = GTK_ICON_SIZE_DIALOG;
610         details->spinning = FALSE;
611         details->timeout = SPINNER_TIMEOUT;
612         details->need_load = TRUE;
613 }
614
615 static int
616 ephy_spinner_expose (GtkWidget *widget,
617                      GdkEventExpose *event)
618 {
619         EphySpinner *spinner = EPHY_SPINNER (widget);
620         EphySpinnerDetails *details = spinner->details;
621         EphySpinnerImages *images;
622         GdkPixbuf *pixbuf;
623         GdkGC *gc;
624         int x_offset, y_offset, width, height;
625         GdkRectangle pix_area, dest;
626
627         if (!GTK_WIDGET_DRAWABLE (spinner))
628         {
629                 return FALSE;
630         }
631
632         if (details->need_load &&
633             !ephy_spinner_load_images (spinner))
634         {
635                 return FALSE;
636         }
637
638         images = details->images;
639         if (images == NULL)
640         {
641                 return FALSE;
642         }
643
644         /* Otherwise |images| will be NULL anyway */
645         g_assert (images->n_animation_pixbufs > 0);
646                 
647         g_assert (details->current_image >= 0 &&
648                   details->current_image < images->n_animation_pixbufs);
649
650         pixbuf = images->animation_pixbufs[details->current_image];
651
652         g_assert (pixbuf != NULL);
653
654         width = gdk_pixbuf_get_width (pixbuf);
655         height = gdk_pixbuf_get_height (pixbuf);
656
657         /* Compute the offsets for the image centered on our allocation */
658         x_offset = (widget->allocation.width - width) / 2;
659         y_offset = (widget->allocation.height - height) / 2;
660
661         pix_area.x = x_offset + widget->allocation.x;
662         pix_area.y = y_offset + widget->allocation.y;
663         pix_area.width = width;
664         pix_area.height = height;
665
666         if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest))
667         {
668                 return FALSE;
669         }
670
671         gc = gdk_gc_new (widget->window);
672         gdk_draw_pixbuf (widget->window, gc, pixbuf,
673                          dest.x - x_offset - widget->allocation.x,
674                          dest.y - y_offset - widget->allocation.y,
675                          dest.x, dest.y,
676                          dest.width, dest.height,
677                          GDK_RGB_DITHER_MAX, 0, 0);
678         g_object_unref (gc);
679
680         return FALSE;
681 }
682
683 static gboolean
684 bump_spinner_frame_cb (EphySpinner *spinner)
685 {
686         EphySpinnerDetails *details = spinner->details;
687
688         /* This can happen when we've unloaded the images on a theme
689          * change, but haven't been in the queued size request yet.
690          * Just skip this update.
691          */
692         if (details->images == NULL) return TRUE;
693
694         details->current_image++;
695         if (details->current_image >= details->images->n_animation_pixbufs)
696         {
697                 /* the 0th frame is the 'rest' icon */
698                 details->current_image = MIN (1, details->images->n_animation_pixbufs);
699         }
700
701         gtk_widget_queue_draw (GTK_WIDGET (spinner));
702
703         /* run again */
704         return TRUE;
705 }
706
707 /**
708  * ephy_spinner_start:
709  * @spinner: a #EphySpinner
710  *
711  * Start the spinner animation.
712  **/
713 void
714 ephy_spinner_start (EphySpinner *spinner)
715 {
716         EphySpinnerDetails *details = spinner->details;
717
718         details->spinning = TRUE;
719
720         if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)) &&
721             details->timer_task == 0 &&
722             ephy_spinner_load_images (spinner))
723         {
724                 /* the 0th frame is the 'rest' icon */
725                 details->current_image = MIN (1, details->images->n_animation_pixbufs);
726
727                 details->timer_task =
728                         g_timeout_add_full (G_PRIORITY_LOW,
729                                             details->timeout,
730                                             (GSourceFunc) bump_spinner_frame_cb,
731                                             spinner,
732                                             NULL);
733         }
734 }
735
736 static void
737 ephy_spinner_remove_update_callback (EphySpinner *spinner)
738 {
739         EphySpinnerDetails *details = spinner->details;
740
741         if (details->timer_task != 0)
742         {
743                 g_source_remove (details->timer_task);
744                 details->timer_task = 0;
745         }
746 }
747
748 /**
749  * ephy_spinner_stop:
750  * @spinner: a #EphySpinner
751  *
752  * Stop the spinner animation.
753  **/
754 void
755 ephy_spinner_stop (EphySpinner *spinner)
756 {
757         EphySpinnerDetails *details = spinner->details;
758
759         details->spinning = FALSE;
760         details->current_image = 0;
761
762         if (details->timer_task != 0)
763         {
764                 ephy_spinner_remove_update_callback (spinner);
765
766                 if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)))
767                 {
768                         gtk_widget_queue_draw (GTK_WIDGET (spinner));
769                 }
770         }
771 }
772
773 /*
774  * ephy_spinner_set_size:
775  * @spinner: a #EphySpinner
776  * @size: the size of type %GtkIconSize
777  *
778  * Set the size of the spinner.
779  **/
780 void
781 ephy_spinner_set_size (EphySpinner *spinner,
782                        GtkIconSize size)
783 {
784         if (size == GTK_ICON_SIZE_INVALID)
785         {
786                 size = GTK_ICON_SIZE_DIALOG;
787         }
788
789         if (size != spinner->details->size)
790         {
791                 ephy_spinner_unload_images (spinner);
792
793                 spinner->details->size = size;
794
795                 gtk_widget_queue_resize (GTK_WIDGET (spinner));
796         }
797 }
798
799 #if 0
800 /*
801  * ephy_spinner_set_timeout:
802  * @spinner: a #EphySpinner
803  * @timeout: time delay between updates to the spinner.
804  *
805  * Sets the timeout delay for spinner updates.
806  **/
807 void
808 ephy_spinner_set_timeout (EphySpinner *spinner,
809                           guint timeout)
810 {
811         EphySpinnerDetails *details = spinner->details;
812
813         if (timeout != details->timeout)
814         {
815                 ephy_spinner_stop (spinner);
816
817                 details->timeout = timeout;
818
819                 if (details->spinning)
820                 {
821                         ephy_spinner_start (spinner);
822                 }
823         }
824 }
825 #endif
826
827 static void
828 ephy_spinner_size_request (GtkWidget *widget,
829                            GtkRequisition *requisition)
830 {
831         EphySpinner *spinner = EPHY_SPINNER (widget);
832         EphySpinnerDetails *details = spinner->details;
833
834         if ((details->need_load &&
835              !ephy_spinner_load_images (spinner)) ||
836             details->images == NULL)
837         {
838                 requisition->width = requisition->height = 0;
839                 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget),
840                                                    details->size,
841                                                    &requisition->width,
842                                                    &requisition->height);
843                 return;
844         }
845
846         requisition->width = details->images->width;
847         requisition->height = details->images->height;
848
849         /* FIXME fix this hack */
850         /* allocate some extra margin so we don't butt up against toolbar edges */
851         if (details->size != GTK_ICON_SIZE_MENU)
852         {
853                 requisition->width += 2;
854                 requisition->height += 2;
855         }
856 }
857
858 static void
859 ephy_spinner_map (GtkWidget *widget)
860 {
861         EphySpinner *spinner = EPHY_SPINNER (widget);
862         EphySpinnerDetails *details = spinner->details;
863
864         GTK_WIDGET_CLASS (parent_class)->map (widget);
865
866         if (details->spinning)
867         {
868                 ephy_spinner_start (spinner);
869         }
870 }
871
872 static void
873 ephy_spinner_unmap (GtkWidget *widget)
874 {
875         EphySpinner *spinner = EPHY_SPINNER (widget);
876
877         ephy_spinner_remove_update_callback (spinner);
878
879         GTK_WIDGET_CLASS (parent_class)->unmap (widget);
880 }
881
882 static void
883 ephy_spinner_dispose (GObject *object)
884 {
885         EphySpinner *spinner = EPHY_SPINNER (object);
886
887         g_signal_handlers_disconnect_by_func
888                         (spinner->details->icon_theme,
889                  G_CALLBACK (icon_theme_changed_cb), spinner);
890
891         G_OBJECT_CLASS (parent_class)->dispose (object);
892 }
893
894 static void
895 ephy_spinner_finalize (GObject *object)
896 {
897         EphySpinner *spinner = EPHY_SPINNER (object);
898
899         ephy_spinner_remove_update_callback (spinner);
900         ephy_spinner_unload_images (spinner);
901
902         g_object_unref (spinner->details->cache);
903
904         G_OBJECT_CLASS (parent_class)->finalize (object);
905 }
906
907 static void
908 ephy_spinner_screen_changed (GtkWidget *widget,
909                              GdkScreen *old_screen)
910 {
911         EphySpinner *spinner = EPHY_SPINNER (widget);
912         EphySpinnerDetails *details = spinner->details;
913         GdkScreen *screen;
914
915         if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
916         {
917                 GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, old_screen);
918         }
919
920         screen = gtk_widget_get_screen (widget);
921
922         /* FIXME: this seems to be happening when then spinner is destroyed!? */
923         if (old_screen == screen) return;
924
925         /* We'll get mapped again on the new screen, but not unmapped from
926          * the old screen, so remove timeout here.
927          */
928         ephy_spinner_remove_update_callback (spinner);
929
930         ephy_spinner_unload_images (spinner);
931
932         if (old_screen != NULL)
933         {
934                 g_signal_handlers_disconnect_by_func
935                         (gtk_icon_theme_get_for_screen (old_screen),
936                          G_CALLBACK (icon_theme_changed_cb), spinner);
937         }
938
939         details->icon_theme = gtk_icon_theme_get_for_screen (screen);
940         g_signal_connect (details->icon_theme, "changed",
941                           G_CALLBACK (icon_theme_changed_cb), spinner);
942 }
943
944 static void
945 ephy_spinner_class_init (EphySpinnerClass *class)
946 {
947         GObjectClass *object_class =  G_OBJECT_CLASS (class);
948         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
949
950         parent_class = g_type_class_peek_parent (class);
951
952         object_class->dispose = ephy_spinner_dispose;
953         object_class->finalize = ephy_spinner_finalize;
954
955         widget_class->expose_event = ephy_spinner_expose;
956         widget_class->size_request = ephy_spinner_size_request;
957         widget_class->map = ephy_spinner_map;
958         widget_class->unmap = ephy_spinner_unmap;
959         widget_class->screen_changed = ephy_spinner_screen_changed;
960
961         g_type_class_add_private (object_class, sizeof (EphySpinnerDetails));
962 }
963
964 /*
965  * ephy_spinner_new:
966  *
967  * Create a new #EphySpinner. The spinner is a widget
968  * that gives the user feedback about network status with
969  * an animated image.
970  *
971  * Return Value: the spinner #GtkWidget
972  **/
973 GtkWidget *
974 ephy_spinner_new (void)
975 {
976         return GTK_WIDGET (g_object_new (EPHY_TYPE_SPINNER, NULL));
977 }