]> git.0d.be Git - empathy.git/blob - src/ephy-spinner.c
Display the name of the CM in the TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR msg
[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 < 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 < images->n_animation_pixbufs);
646
647         pixbuf = images->animation_pixbufs[details->current_image];
648
649         g_assert (pixbuf != NULL);
650
651         width = gdk_pixbuf_get_width (pixbuf);
652         height = gdk_pixbuf_get_height (pixbuf);
653
654         /* Compute the offsets for the image centered on our allocation */
655         x_offset = (widget->allocation.width - width) / 2;
656         y_offset = (widget->allocation.height - height) / 2;
657
658         pix_area.x = x_offset + widget->allocation.x;
659         pix_area.y = y_offset + widget->allocation.y;
660         pix_area.width = width;
661         pix_area.height = height;
662
663         if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest))
664         {
665                 return FALSE;
666         }
667
668         gc = gdk_gc_new (gtk_widget_get_window (widget));
669         gdk_draw_pixbuf (gtk_widget_get_window (widget), gc, pixbuf,
670                          dest.x - x_offset - widget->allocation.x,
671                          dest.y - y_offset - widget->allocation.y,
672                          dest.x, dest.y,
673                          dest.width, dest.height,
674                          GDK_RGB_DITHER_MAX, 0, 0);
675         g_object_unref (gc);
676
677         return FALSE;
678 }
679
680 static gboolean
681 bump_spinner_frame_cb (EphySpinner *spinner)
682 {
683         EphySpinnerDetails *details = spinner->details;
684
685         /* This can happen when we've unloaded the images on a theme
686          * change, but haven't been in the queued size request yet.
687          * Just skip this update.
688          */
689         if (details->images == NULL) return TRUE;
690
691         details->current_image++;
692         if (details->current_image >= details->images->n_animation_pixbufs)
693         {
694                 /* the 0th frame is the 'rest' icon */
695                 details->current_image = MIN (1, details->images->n_animation_pixbufs);
696         }
697
698         gtk_widget_queue_draw (GTK_WIDGET (spinner));
699
700         /* run again */
701         return TRUE;
702 }
703
704 /**
705  * ephy_spinner_start:
706  * @spinner: a #EphySpinner
707  *
708  * Start the spinner animation.
709  **/
710 void
711 ephy_spinner_start (EphySpinner *spinner)
712 {
713         EphySpinnerDetails *details = spinner->details;
714
715         details->spinning = TRUE;
716
717         if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)) &&
718             details->timer_task == 0 &&
719             ephy_spinner_load_images (spinner))
720         {
721                 /* the 0th frame is the 'rest' icon */
722                 details->current_image = MIN (1, details->images->n_animation_pixbufs);
723
724                 details->timer_task =
725                         g_timeout_add_full (G_PRIORITY_LOW,
726                                             details->timeout,
727                                             (GSourceFunc) bump_spinner_frame_cb,
728                                             spinner,
729                                             NULL);
730         }
731 }
732
733 static void
734 ephy_spinner_remove_update_callback (EphySpinner *spinner)
735 {
736         EphySpinnerDetails *details = spinner->details;
737
738         if (details->timer_task != 0)
739         {
740                 g_source_remove (details->timer_task);
741                 details->timer_task = 0;
742         }
743 }
744
745 /**
746  * ephy_spinner_stop:
747  * @spinner: a #EphySpinner
748  *
749  * Stop the spinner animation.
750  **/
751 void
752 ephy_spinner_stop (EphySpinner *spinner)
753 {
754         EphySpinnerDetails *details = spinner->details;
755
756         details->spinning = FALSE;
757         details->current_image = 0;
758
759         if (details->timer_task != 0)
760         {
761                 ephy_spinner_remove_update_callback (spinner);
762
763                 if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)))
764                 {
765                         gtk_widget_queue_draw (GTK_WIDGET (spinner));
766                 }
767         }
768 }
769
770 /*
771  * ephy_spinner_set_size:
772  * @spinner: a #EphySpinner
773  * @size: the size of type %GtkIconSize
774  *
775  * Set the size of the spinner.
776  **/
777 void
778 ephy_spinner_set_size (EphySpinner *spinner,
779                        GtkIconSize size)
780 {
781         if (size == GTK_ICON_SIZE_INVALID)
782         {
783                 size = GTK_ICON_SIZE_DIALOG;
784         }
785
786         if (size != spinner->details->size)
787         {
788                 ephy_spinner_unload_images (spinner);
789
790                 spinner->details->size = size;
791
792                 gtk_widget_queue_resize (GTK_WIDGET (spinner));
793         }
794 }
795
796 #if 0
797 /*
798  * ephy_spinner_set_timeout:
799  * @spinner: a #EphySpinner
800  * @timeout: time delay between updates to the spinner.
801  *
802  * Sets the timeout delay for spinner updates.
803  **/
804 void
805 ephy_spinner_set_timeout (EphySpinner *spinner,
806                           guint timeout)
807 {
808         EphySpinnerDetails *details = spinner->details;
809
810         if (timeout != details->timeout)
811         {
812                 ephy_spinner_stop (spinner);
813
814                 details->timeout = timeout;
815
816                 if (details->spinning)
817                 {
818                         ephy_spinner_start (spinner);
819                 }
820         }
821 }
822 #endif
823
824 static void
825 ephy_spinner_size_request (GtkWidget *widget,
826                            GtkRequisition *requisition)
827 {
828         EphySpinner *spinner = EPHY_SPINNER (widget);
829         EphySpinnerDetails *details = spinner->details;
830
831         if ((details->need_load &&
832              !ephy_spinner_load_images (spinner)) ||
833             details->images == NULL)
834         {
835                 requisition->width = requisition->height = 0;
836                 gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget),
837                                                    details->size,
838                                                    &requisition->width,
839                                                    &requisition->height);
840                 return;
841         }
842
843         requisition->width = details->images->width;
844         requisition->height = details->images->height;
845
846         /* FIXME fix this hack */
847         /* allocate some extra margin so we don't butt up against toolbar edges */
848         if (details->size != GTK_ICON_SIZE_MENU)
849         {
850                 requisition->width += 2;
851                 requisition->height += 2;
852         }
853 }
854
855 static void
856 ephy_spinner_map (GtkWidget *widget)
857 {
858         EphySpinner *spinner = EPHY_SPINNER (widget);
859         EphySpinnerDetails *details = spinner->details;
860
861         GTK_WIDGET_CLASS (parent_class)->map (widget);
862
863         if (details->spinning)
864         {
865                 ephy_spinner_start (spinner);
866         }
867 }
868
869 static void
870 ephy_spinner_unmap (GtkWidget *widget)
871 {
872         EphySpinner *spinner = EPHY_SPINNER (widget);
873
874         ephy_spinner_remove_update_callback (spinner);
875
876         GTK_WIDGET_CLASS (parent_class)->unmap (widget);
877 }
878
879 static void
880 ephy_spinner_dispose (GObject *object)
881 {
882         EphySpinner *spinner = EPHY_SPINNER (object);
883
884         g_signal_handlers_disconnect_by_func
885                         (spinner->details->icon_theme,
886                  G_CALLBACK (icon_theme_changed_cb), spinner);
887
888         G_OBJECT_CLASS (parent_class)->dispose (object);
889 }
890
891 static void
892 ephy_spinner_finalize (GObject *object)
893 {
894         EphySpinner *spinner = EPHY_SPINNER (object);
895
896         ephy_spinner_remove_update_callback (spinner);
897         ephy_spinner_unload_images (spinner);
898
899         g_object_unref (spinner->details->cache);
900
901         G_OBJECT_CLASS (parent_class)->finalize (object);
902 }
903
904 static void
905 ephy_spinner_screen_changed (GtkWidget *widget,
906                              GdkScreen *old_screen)
907 {
908         EphySpinner *spinner = EPHY_SPINNER (widget);
909         EphySpinnerDetails *details = spinner->details;
910         GdkScreen *screen;
911
912         if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
913         {
914                 GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, old_screen);
915         }
916
917         screen = gtk_widget_get_screen (widget);
918
919         /* FIXME: this seems to be happening when then spinner is destroyed!? */
920         if (old_screen == screen) return;
921
922         /* We'll get mapped again on the new screen, but not unmapped from
923          * the old screen, so remove timeout here.
924          */
925         ephy_spinner_remove_update_callback (spinner);
926
927         ephy_spinner_unload_images (spinner);
928
929         if (old_screen != NULL)
930         {
931                 g_signal_handlers_disconnect_by_func
932                         (gtk_icon_theme_get_for_screen (old_screen),
933                          G_CALLBACK (icon_theme_changed_cb), spinner);
934         }
935
936         details->icon_theme = gtk_icon_theme_get_for_screen (screen);
937         g_signal_connect (details->icon_theme, "changed",
938                           G_CALLBACK (icon_theme_changed_cb), spinner);
939 }
940
941 static void
942 ephy_spinner_class_init (EphySpinnerClass *class)
943 {
944         GObjectClass *object_class =  G_OBJECT_CLASS (class);
945         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
946
947         parent_class = g_type_class_peek_parent (class);
948
949         object_class->dispose = ephy_spinner_dispose;
950         object_class->finalize = ephy_spinner_finalize;
951
952         widget_class->expose_event = ephy_spinner_expose;
953         widget_class->size_request = ephy_spinner_size_request;
954         widget_class->map = ephy_spinner_map;
955         widget_class->unmap = ephy_spinner_unmap;
956         widget_class->screen_changed = ephy_spinner_screen_changed;
957
958         g_type_class_add_private (object_class, sizeof (EphySpinnerDetails));
959 }
960
961 /*
962  * ephy_spinner_new:
963  *
964  * Create a new #EphySpinner. The spinner is a widget
965  * that gives the user feedback about network status with
966  * an animated image.
967  *
968  * Return Value: the spinner #GtkWidget
969  **/
970 GtkWidget *
971 ephy_spinner_new (void)
972 {
973         return GTK_WIDGET (g_object_new (EPHY_TYPE_SPINNER, NULL));
974 }