]> Pileus Git - ~andy/gtk/blob - gtk/gtkspinner.c
Fix "active" property not being notified
[~andy/gtk] / gtk / gtkspinner.c
1 /* GTK - The GIMP Toolkit
2  *
3  * Copyright (C) 2007 John Stowers, Neil Jagdish Patel.
4  * Copyright (C) 2009 Bastien Nocera, David Zeuthen
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA  02111-1307, USA.
20  *
21  * Code adapted from egg-spinner
22  * by Christian Hergert <christian.hergert@gmail.com>
23  */
24
25 /*
26  * Modified by the GTK+ Team and others 2007.  See the AUTHORS
27  * file for a list of people on the GTK+ Team.  See the ChangeLog
28  * files for a list of changes.  These files are distributed with
29  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
30  */
31
32 #include "config.h"
33
34 #include "gtkintl.h"
35 #include "gtkaccessible.h"
36 #include "gtkimage.h"
37 #include "gtkspinner.h"
38 #include "gtkstyle.h"
39 #include "gtkalias.h"
40
41 #define GTK_SPINNER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_SPINNER, GtkSpinnerPrivate))
42
43 G_DEFINE_TYPE (GtkSpinner, gtk_spinner, GTK_TYPE_DRAWING_AREA);
44
45 enum {
46   PROP_0,
47   PROP_ACTIVE
48 };
49
50 struct _GtkSpinnerPrivate
51 {
52   guint current;
53   guint num_steps;
54   guint timeout;
55 };
56
57 static void gtk_spinner_class_init (GtkSpinnerClass *klass);
58 static void gtk_spinner_init (GtkSpinner *spinner);
59 static void gtk_spinner_dispose (GObject *gobject);
60 static gboolean gtk_spinner_expose (GtkWidget *widget, GdkEventExpose *event);
61 static void gtk_spinner_screen_changed (GtkWidget* widget, GdkScreen* old_screen);
62 static void gtk_spinner_style_set (GtkWidget *widget, GtkStyle *prev_style);
63 static void gtk_spinner_get_property (GObject    *object,
64                                       guint       param_id,
65                                       GValue     *value,
66                                       GParamSpec *pspec);
67 static void gtk_spinner_set_property (GObject      *object,
68                                       guint         param_id,
69                                       const GValue *value,
70                                       GParamSpec   *pspec);
71 static AtkObject *gtk_spinner_get_accessible (GtkWidget *widget);
72 static GType gtk_spinner_accessible_get_type (void);
73
74 static void
75 gtk_spinner_class_init (GtkSpinnerClass *klass)
76 {
77   GObjectClass *gobject_class;
78   GtkWidgetClass *widget_class;
79
80   gtk_spinner_parent_class = g_type_class_peek_parent (klass);
81
82   gobject_class = G_OBJECT_CLASS(klass);
83   g_type_class_add_private (gobject_class, sizeof (GtkSpinnerPrivate));
84   gobject_class->dispose = gtk_spinner_dispose;
85   gobject_class->get_property = gtk_spinner_get_property;
86   gobject_class->set_property = gtk_spinner_set_property;
87
88   widget_class = GTK_WIDGET_CLASS(klass);
89   widget_class->expose_event = gtk_spinner_expose;
90   widget_class->screen_changed = gtk_spinner_screen_changed;
91   widget_class->style_set = gtk_spinner_style_set;
92   widget_class->get_accessible = gtk_spinner_get_accessible;
93
94   /* GtkSpinner::active:
95    *
96    * Whether the spinner is active
97    *
98    * Since 2.20
99    */
100   g_object_class_install_property (gobject_class,
101                                    PROP_ACTIVE,
102                                    g_param_spec_boolean ("active",
103                                                          P_("Active"),
104                                                          P_("Whether the spinner is active"),
105                                                          FALSE,
106                                                          G_PARAM_READWRITE));
107   /**
108    * GtkSpinner::num-steps:
109    *
110    * The number of steps for the spinner to complete a full loop. The animation will
111    * complete a full revolution in one second.
112    *
113    * Since: 2.20
114    */
115   gtk_widget_class_install_style_property (widget_class,
116                                            g_param_spec_uint ("num-steps",
117                                                              P_("Number of steps"),
118                                                              P_("The number of steps for the spinner to complete a full loop. The animation will complete a full revolution in one second."),
119                                                              1,
120                                                              G_MAXUINT,
121                                                              12,
122                                                              G_PARAM_READABLE));
123 }
124
125 static void
126 gtk_spinner_get_property (GObject    *object,
127                           guint       param_id,
128                           GValue     *value,
129                           GParamSpec *pspec)
130 {
131   GtkSpinnerPrivate *priv;
132
133   priv = GTK_SPINNER_GET_PRIVATE (object);
134
135   switch (param_id)
136     {
137       case PROP_ACTIVE:
138         g_value_set_boolean (value, priv->timeout != 0);
139         break;
140       default:
141         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
142     }
143 }
144
145 static void
146 gtk_spinner_set_property (GObject      *object,
147                           guint         param_id,
148                           const GValue *value,
149                           GParamSpec   *pspec)
150 {
151   switch (param_id)
152     {
153       case PROP_ACTIVE:
154         if (g_value_get_boolean (value))
155           gtk_spinner_start (GTK_SPINNER (object));
156         else
157           gtk_spinner_stop (GTK_SPINNER (object));
158         break;
159       default:
160         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
161     }
162 }
163
164 static void
165 gtk_spinner_init (GtkSpinner *spinner)
166 {
167   GtkSpinnerPrivate *priv;
168
169   priv = GTK_SPINNER_GET_PRIVATE (spinner);
170   priv->current = 0;
171   priv->timeout = 0;
172
173   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (spinner), GTK_NO_WINDOW);
174 }
175
176 static gboolean
177 gtk_spinner_expose (GtkWidget *widget, GdkEventExpose *event)
178 {
179   GtkStateType state_type;
180   GtkSpinnerPrivate *priv;
181   int width, height;
182
183   priv = GTK_SPINNER_GET_PRIVATE (widget);
184
185   width = widget->allocation.width;
186   height = widget->allocation.height;
187
188   if ((width < 12) || (height <12))
189     gtk_widget_set_size_request (widget, 12, 12);
190
191   state_type = GTK_STATE_NORMAL;
192   if (!GTK_WIDGET_IS_SENSITIVE (widget))
193    state_type = GTK_STATE_INSENSITIVE;
194
195   gtk_paint_spinner (widget->style,
196                      widget->window,
197                      state_type,
198                      priv->current,
199                      event->area.x, event->area.y,
200                      event->area.width, event->area.height);
201
202   return FALSE;
203 }
204
205 static void
206 gtk_spinner_screen_changed (GtkWidget* widget, GdkScreen* old_screen)
207 {
208   GtkSpinner *spinner;
209   GdkScreen* new_screen;
210   GdkColormap* colormap;
211
212   spinner = GTK_SPINNER(widget);
213
214   new_screen = gtk_widget_get_screen (widget);
215   colormap = gdk_screen_get_rgba_colormap (new_screen);
216
217   if (!colormap)
218     {
219       colormap = gdk_screen_get_rgb_colormap (new_screen);
220     }
221
222   gtk_widget_set_colormap (widget, colormap);
223 }
224
225 static void
226 gtk_spinner_style_set (GtkWidget *widget,
227                        GtkStyle  *prev_style)
228 {
229   GtkSpinnerPrivate *priv;
230
231   priv = GTK_SPINNER_GET_PRIVATE (widget);
232
233   gtk_widget_style_get (GTK_WIDGET (widget),
234                         "num-steps", &(priv->num_steps),
235                         NULL);
236
237   if (priv->current > priv->num_steps)
238     priv->current = 0;
239 }
240
241 static gboolean
242 gtk_spinner_timeout (gpointer data)
243 {
244   GtkSpinnerPrivate *priv;
245
246   priv = GTK_SPINNER_GET_PRIVATE (data);
247
248   if (priv->current + 1 >= priv->num_steps)
249     {
250       priv->current = 0;
251     }
252   else
253     {
254       priv->current++;
255     }
256
257   gtk_widget_queue_draw (GTK_WIDGET (data));
258
259   return TRUE;
260 }
261 static void
262 gtk_spinner_dispose (GObject *gobject)
263 {
264   GtkSpinnerPrivate *priv;
265
266   priv = GTK_SPINNER_GET_PRIVATE (gobject);
267
268   if (priv->timeout != 0)
269     {
270       g_source_remove (priv->timeout);
271       priv->timeout = 0;
272     }
273
274   G_OBJECT_CLASS (gtk_spinner_parent_class)->dispose (gobject);
275 }
276
277 static GType
278 gtk_spinner_accessible_factory_get_accessible_type (void)
279 {
280   return gtk_spinner_accessible_get_type ();
281 }
282
283 static AtkObject *
284 gtk_spinner_accessible_new (GObject *obj)
285 {
286   AtkObject *accessible;
287
288   g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL);
289
290   accessible = g_object_new (gtk_spinner_accessible_get_type (), NULL);
291   atk_object_initialize (accessible, obj);
292
293   return accessible;
294 }
295
296 static AtkObject*
297 gtk_spinner_accessible_factory_create_accessible (GObject *obj)
298 {
299   return gtk_spinner_accessible_new (obj);
300 }
301
302 static void
303 gtk_spinner_accessible_factory_class_init (AtkObjectFactoryClass *klass)
304 {
305   klass->create_accessible = gtk_spinner_accessible_factory_create_accessible;
306   klass->get_accessible_type = gtk_spinner_accessible_factory_get_accessible_type;
307 }
308
309 static GType
310 gtk_spinner_accessible_factory_get_type (void)
311 {
312   static GType type = 0;
313
314   if (!type)
315     {
316       const GTypeInfo tinfo =
317       {
318         sizeof (AtkObjectFactoryClass),
319         NULL,           /* base_init */
320         NULL,           /* base_finalize */
321         (GClassInitFunc) gtk_spinner_accessible_factory_class_init,
322         NULL,           /* class_finalize */
323         NULL,           /* class_data */
324         sizeof (AtkObjectFactory),
325         0,             /* n_preallocs */
326         NULL, NULL
327       };
328
329       type = g_type_register_static (ATK_TYPE_OBJECT_FACTORY,
330                                     I_("GtkSpinnerAccessibleFactory"),
331                                     &tinfo, 0);
332     }
333   return type;
334 }
335
336 static AtkObjectClass *a11y_parent_class = NULL;
337
338 static void
339 gtk_spinner_accessible_initialize (AtkObject *accessible,
340                                    gpointer   widget)
341 {
342   atk_object_set_name (accessible, _("Spinner"));
343   atk_object_set_description (accessible, _("Provides visual status"));
344
345   a11y_parent_class->initialize (accessible, widget);
346 }
347
348 static void
349 gtk_spinner_accessible_class_init (AtkObjectClass *klass)
350 {
351   a11y_parent_class = g_type_class_peek_parent (klass);
352
353   klass->initialize = gtk_spinner_accessible_initialize;
354 }
355
356 static void
357 gtk_spinner_accessible_image_get_size (AtkImage *image,
358                                              gint     *width,
359                                              gint     *height)
360 {
361   GtkWidget *widget;
362
363   widget = GTK_ACCESSIBLE (image)->widget;
364   if (!widget)
365     {
366       *width = *height = 0;
367     }
368   else
369     {
370       *width = widget->allocation.width;
371       *height = widget->allocation.height;
372     }
373 }
374
375 static void
376 gtk_spinner_accessible_image_interface_init (AtkImageIface *iface)
377 {
378   iface->get_image_size = gtk_spinner_accessible_image_get_size;
379 }
380
381 static GType
382 gtk_spinner_accessible_get_type (void)
383 {
384   static GType type = 0;
385
386   /* Action interface
387      Name etc. ... */
388   if (G_UNLIKELY (type == 0))
389     {
390       const GInterfaceInfo atk_image_info = {
391               (GInterfaceInitFunc) gtk_spinner_accessible_image_interface_init,
392               (GInterfaceFinalizeFunc) NULL,
393               NULL
394       };
395       GType type;
396       GType parent_atk_type;
397       GTypeInfo tinfo = { 0 };
398       GTypeQuery query;
399       AtkObjectFactory *factory;
400
401       if ((type = g_type_from_name ("GtkSpinnerAccessible")))
402         return type;
403
404       factory = atk_registry_get_factory (atk_get_default_registry (),
405                                           GTK_TYPE_IMAGE);
406       if (!factory)
407         return G_TYPE_INVALID;
408
409       parent_atk_type = atk_object_factory_get_accessible_type (factory);
410       if (!parent_atk_type)
411         return G_TYPE_INVALID;
412
413       /*
414        * Figure out the size of the class and instance
415        * we are deriving from
416        */
417       g_type_query (parent_atk_type, &query);
418
419       tinfo.class_init = (GClassInitFunc) gtk_spinner_accessible_class_init;
420       tinfo.class_size    = query.class_size;
421       tinfo.instance_size = query.instance_size;
422
423       /* Register the type */
424       type = g_type_register_static (parent_atk_type,
425                                      "GtkSpinnerAccessible",
426                                      &tinfo, 0);
427
428       g_type_add_interface_static (type, ATK_TYPE_IMAGE,
429                                    &atk_image_info);
430     }
431
432   return type;
433 }
434
435 static AtkObject *
436 gtk_spinner_get_accessible (GtkWidget *widget)
437 {
438   static gboolean first_time = TRUE;
439
440   if (first_time)
441     {
442       AtkObjectFactory *factory;
443       AtkRegistry *registry;
444       GType derived_type;
445       GType derived_atk_type;
446
447       /*
448        * Figure out whether accessibility is enabled by looking at the
449        * type of the accessible object which would be created for
450        * the parent type of GtkSpinner.
451        */
452       derived_type = g_type_parent (GTK_TYPE_SPINNER);
453
454       registry = atk_get_default_registry ();
455       factory = atk_registry_get_factory (registry,
456                                           derived_type);
457       derived_atk_type = atk_object_factory_get_accessible_type (factory);
458       if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
459         atk_registry_set_factory_type (registry,
460                                        GTK_TYPE_SPINNER,
461                                        gtk_spinner_accessible_factory_get_type ());
462       first_time = FALSE;
463     }
464   return GTK_WIDGET_CLASS (gtk_spinner_parent_class)->get_accessible (widget);
465 }
466
467 /**
468  * gtk_spinner_new
469  *
470  * Returns a new spinner widget. Not yet started.
471  *
472  * Return value: a new #GtkSpinner
473  *
474  * Since: 2.20
475  */
476 GtkWidget *
477 gtk_spinner_new (void)
478 {
479   return g_object_new (GTK_TYPE_SPINNER, NULL);
480 }
481
482 /**
483  * gtk_spinner_start
484  *
485  * Starts the animation on the #GtkSpinner
486  *
487  * Since: 2.20
488  */
489 void
490 gtk_spinner_start (GtkSpinner *spinner)
491 {
492   GtkSpinnerPrivate *priv;
493
494   g_return_if_fail (GTK_IS_SPINNER (spinner));
495
496   priv = GTK_SPINNER_GET_PRIVATE (spinner);
497   if (priv->timeout != 0)
498     {
499       return;
500     }
501   priv->timeout = gdk_threads_add_timeout (1000 / priv->num_steps, gtk_spinner_timeout, spinner);
502   g_object_notify (G_OBJECT (spinner), "active");
503 }
504
505 /**
506  * gtk_spinner_stop
507  *
508  * Stops the animation on the #GtkSpinner
509  *
510  * Since: 2.20
511  */
512 void
513 gtk_spinner_stop (GtkSpinner *spinner)
514 {
515   GtkSpinnerPrivate *priv;
516
517   g_return_if_fail (GTK_IS_SPINNER (spinner));
518
519   priv = GTK_SPINNER_GET_PRIVATE (spinner);
520   if (priv->timeout == 0)
521     {
522       return;
523     }
524   g_source_remove (priv->timeout);
525   priv->timeout = 0;
526   g_object_notify (G_OBJECT (spinner), "active");
527 }
528
529 #define __GTK_SPINNER_C__
530 #include "gtkaliasdef.c"