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