]> Pileus Git - ~andy/gtk/blob - gtk/gtkswitch.c
switch: Add accessibility implementation
[~andy/gtk] / gtk / gtkswitch.c
1 /* GTK - The GIMP Toolkit
2  *
3  * Copyright (C) 2010  Intel Corporation
4  * Copyright (C) 2010  RedHat, Inc.
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  * Author:
22  *      Emmanuele Bassi <ebassi@linux.intel.com>
23  *      Matthias Clasen <mclasen@redhat.com>
24  *
25  * Based on similar code from:
26  *      Thomas Wood <thos@linux.intel.com>
27  */
28
29 /**
30  * SECTION:gtkswitch
31  * @Short_Description: A "light switch" style toggle
32  * @Title: GtkSwitch
33  * @See_Also: #GtkToggleButton
34  *
35  * #GtkSwitch is a widget that has two states: on or off. The user can control
36  * which state should be active by clicking the empty area, or by dragging the
37  * handle.
38  */
39
40 #include "config.h"
41
42 #include "gtkswitch.h"
43
44 #include <gdk/gdkkeysyms.h>
45
46 #include "gtkaccessible.h"
47 #include "gtkactivatable.h"
48 #include "gtkintl.h"
49 #include "gtkstyle.h"
50 #include "gtkprivate.h"
51 #include "gtktoggleaction.h"
52 #include "gtkwidget.h"
53
54 #define DEFAULT_SLIDER_WIDTH    (36)
55 #define DEFAULT_SLIDER_HEIGHT   (22)
56
57 struct _GtkSwitchPrivate
58 {
59   GdkWindow *event_window;
60   GtkAction *action;
61
62   gint handle_x;
63   gint offset;
64   gint drag_start;
65   gint drag_threshold;
66
67   guint is_active             : 1;
68   guint is_dragging           : 1;
69   guint in_press              : 1;
70   guint in_switch             : 1;
71   guint use_action_appearance : 1;
72 };
73
74 enum
75 {
76   PROP_0,
77   PROP_ACTIVE,
78   PROP_RELATED_ACTION,
79   PROP_USE_ACTION_APPEARANCE,
80   LAST_PROP
81 };
82
83 static GParamSpec *switch_props[LAST_PROP] = { NULL, };
84
85 static GType gtk_switch_accessible_factory_get_type (void);
86
87 static void gtk_switch_activatable_interface_init (GtkActivatableIface *iface);
88
89 G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET,
90                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
91                                                 gtk_switch_activatable_interface_init));
92
93 static gboolean
94 gtk_switch_button_press (GtkWidget      *widget,
95                          GdkEventButton *event)
96 {
97   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
98   GtkAllocation allocation;
99
100   gtk_widget_get_allocation (widget, &allocation);
101
102   if (priv->is_active)
103     {
104       /* if the event occurred in the "off" area, then this
105        * is a direct toggle
106        */
107       if (event->x <= allocation.width / 2)
108         {
109           priv->in_press = TRUE;
110           return FALSE;
111         }
112
113       priv->offset = event->x - allocation.width / 2;
114     }
115   else
116     {
117       /* if the event occurred in the "on" area, then this
118        * is a direct toggle
119        */
120       if (event->x >= allocation.width / 2)
121         {
122           priv->in_press = TRUE;
123           return FALSE;
124         }
125
126       priv->offset = event->x;
127     }
128
129   priv->drag_start = event->x;
130
131   g_object_get (gtk_widget_get_settings (widget),
132                 "gtk-dnd-drag-threshold", &priv->drag_threshold,
133                 NULL);
134
135   return FALSE;
136 }
137
138 static gboolean
139 gtk_switch_motion (GtkWidget      *widget,
140                    GdkEventMotion *event)
141 {
142   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
143
144   /* if this is a direct toggle we don't handle motion */
145   if (priv->in_press)
146     return FALSE;
147
148   if (ABS (event->x - priv->drag_start) < priv->drag_threshold)
149     return TRUE;
150
151   if (event->state & GDK_BUTTON1_MASK)
152     {
153       gint position = event->x - priv->offset;
154       GtkAllocation allocation;
155       GtkStyle *style;
156
157       style = gtk_widget_get_style (widget);
158       gtk_widget_get_allocation (widget, &allocation);
159
160       /* constrain the handle within the trough width */
161       if (position > (allocation.width / 2 - style->xthickness))
162         priv->handle_x = allocation.width / 2 - style->xthickness;
163       else if (position < style->xthickness)
164         priv->handle_x = style->xthickness;
165       else
166         priv->handle_x = position;
167
168       priv->is_dragging = TRUE;
169
170       /* we need to redraw the handle */
171       gtk_widget_queue_draw (widget);
172
173       return TRUE;
174     }
175
176   return FALSE;
177 }
178
179 static gboolean
180 gtk_switch_button_release (GtkWidget      *widget,
181                            GdkEventButton *event)
182 {
183   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
184   GtkAllocation allocation;
185
186   gtk_widget_get_allocation (widget, &allocation);
187
188   /* dragged toggles have a "soft" grab, so we don't care whether we
189    * are in the switch or not when the button is released; we do care
190    * for direct toggles, instead
191    */
192   if (!priv->is_dragging && !priv->in_switch)
193     return FALSE;
194
195   /* direct toggle */
196   if (priv->in_press)
197     {
198       priv->in_press = FALSE;
199       gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
200
201       return TRUE;
202     }
203
204   /* dragged toggle */
205   if (priv->is_dragging)
206     {
207       priv->is_dragging = FALSE;
208
209       /* if half the handle passed the middle of the switch, then we
210        * consider it to be on
211        */
212       if ((priv->handle_x + (allocation.width / 4)) >= (allocation.width / 2))
213         {
214           gtk_switch_set_active (GTK_SWITCH (widget), TRUE);
215           priv->handle_x = allocation.width / 2;
216         }
217       else
218         {
219           gtk_switch_set_active (GTK_SWITCH (widget), FALSE);
220           priv->handle_x = 0;
221         }
222
223       gtk_widget_queue_draw (widget);
224
225       return TRUE;
226     }
227
228   return FALSE;
229 }
230
231 static gboolean
232 gtk_switch_enter (GtkWidget        *widget,
233                   GdkEventCrossing *event)
234 {
235   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
236
237   if (event->window == priv->event_window)
238     priv->in_switch = TRUE;
239
240   return FALSE;
241 }
242
243 static gboolean
244 gtk_switch_leave (GtkWidget        *widget,
245                   GdkEventCrossing *event)
246 {
247   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
248
249   if (event->window == priv->event_window)
250     priv->in_switch = FALSE;
251
252   return FALSE;
253 }
254
255 static gboolean
256 gtk_switch_key_release (GtkWidget   *widget,
257                         GdkEventKey *event)
258 {
259   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
260
261   if (event->keyval == GDK_KEY_Return ||
262       event->keyval == GDK_KEY_KP_Enter ||
263       event->keyval == GDK_KEY_ISO_Enter ||
264       event->keyval == GDK_KEY_space ||
265       event->keyval == GDK_KEY_KP_Space)
266     {
267       gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
268     }
269
270   return FALSE;
271 }
272
273 static void
274 gtk_switch_get_preferred_width (GtkWidget *widget,
275                                 gint      *minimum,
276                                 gint      *natural)
277 {
278   GtkStyle *style = gtk_widget_get_style (widget);
279   gint width, slider_width, focus_width, focus_pad;
280   PangoLayout *layout;
281   PangoRectangle logical_rect;
282
283   width = style->xthickness * 2;
284
285   gtk_widget_style_get (widget,
286                         "slider-width", &slider_width,
287                         "focus-line-width", &focus_width,
288                         "focus-padding", &focus_pad,
289                         NULL);
290
291   width += 2 * (focus_width + focus_pad);
292
293   /* Translators: if the "on" state label requires more than three
294    * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
295    * the state
296    */
297   layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
298   pango_layout_get_extents (layout, NULL, &logical_rect);
299   pango_extents_to_pixels (&logical_rect, NULL);
300   width += MAX (logical_rect.width, slider_width);
301
302   /* Translators: if the "off" state label requires more than three
303    * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
304    */
305   pango_layout_set_text (layout, C_("switch", "OFF"), -1);
306   pango_layout_get_extents (layout, NULL, &logical_rect);
307   pango_extents_to_pixels (&logical_rect, NULL);
308   width += MAX (logical_rect.width, slider_width);
309
310   g_object_unref (layout);
311
312   if (minimum)
313     *minimum = width;
314
315   if (natural)
316     *natural = width;
317 }
318
319 static void
320 gtk_switch_get_preferred_height (GtkWidget *widget,
321                                  gint      *minimum,
322                                  gint      *natural)
323 {
324   GtkStyle *style = gtk_widget_get_style (widget);
325   gint height, focus_width, focus_pad;
326   PangoLayout *layout;
327   PangoRectangle logical_rect;
328   gchar *str;
329
330   height = style->ythickness * 2;
331
332   gtk_widget_style_get (widget,
333                         "focus-line-width", &focus_width,
334                         "focus-padding", &focus_pad,
335                         NULL);
336
337   height += 2 * (focus_width + focus_pad);
338
339   str = g_strdup_printf ("%s%s",
340                          C_("switch", "ON"),
341                          C_("switch", "OFF"));
342
343   layout = gtk_widget_create_pango_layout (widget, str);
344   pango_layout_get_extents (layout, NULL, &logical_rect);
345   pango_extents_to_pixels (&logical_rect, NULL);
346   height += MAX (DEFAULT_SLIDER_HEIGHT, logical_rect.height);
347
348   g_object_unref (layout);
349   g_free (str);
350
351   if (minimum)
352     *minimum = height;
353
354   if (natural)
355     *natural = height;
356 }
357
358 static void
359 gtk_switch_size_allocate (GtkWidget     *widget,
360                           GtkAllocation *allocation)
361 {
362   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
363
364   gtk_widget_set_allocation (widget, allocation);
365
366   if (gtk_widget_get_realized (widget))
367     gdk_window_move_resize (priv->event_window,
368                             allocation->x,
369                             allocation->y,
370                             allocation->width,
371                             allocation->height);
372 }
373
374 static void
375 gtk_switch_realize (GtkWidget *widget)
376 {
377   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
378   GdkWindow *parent_window;
379   GdkWindowAttr attributes;
380   gint attributes_mask;
381   GtkAllocation allocation;
382
383   gtk_widget_set_realized (widget, TRUE);
384   parent_window = gtk_widget_get_parent_window (widget);
385   gtk_widget_set_window (widget, parent_window);
386   g_object_ref (parent_window);
387
388   gtk_widget_get_allocation (widget, &allocation);
389
390   attributes.window_type = GDK_WINDOW_CHILD;
391   attributes.wclass = GDK_INPUT_ONLY;
392   attributes.x = allocation.x;
393   attributes.y = allocation.y;
394   attributes.width = allocation.width;
395   attributes.height = allocation.height;
396   attributes.event_mask = gtk_widget_get_events (widget);
397   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
398                             GDK_BUTTON_RELEASE_MASK |
399                             GDK_BUTTON1_MOTION_MASK |
400                             GDK_POINTER_MOTION_HINT_MASK |
401                             GDK_POINTER_MOTION_MASK |
402                             GDK_ENTER_NOTIFY_MASK |
403                             GDK_LEAVE_NOTIFY_MASK);
404   attributes_mask = GDK_WA_X | GDK_WA_Y;
405
406   priv->event_window = gdk_window_new (parent_window,
407                                        &attributes,
408                                        attributes_mask);
409   gdk_window_set_user_data (priv->event_window, widget);
410
411   gtk_widget_style_attach (widget);
412 }
413
414 static void
415 gtk_switch_unrealize (GtkWidget *widget)
416 {
417   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
418
419   if (priv->event_window != NULL)
420     {
421       gdk_window_set_user_data (priv->event_window, NULL);
422       gdk_window_destroy (priv->event_window);
423       priv->event_window = NULL;
424     }
425
426   GTK_WIDGET_CLASS (gtk_switch_parent_class)->unrealize (widget);
427 }
428
429 static void
430 gtk_switch_map (GtkWidget *widget)
431 {
432   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
433
434   GTK_WIDGET_CLASS (gtk_switch_parent_class)->map (widget);
435
436   if (priv->event_window)
437     gdk_window_show (priv->event_window);
438 }
439
440 static void
441 gtk_switch_unmap (GtkWidget *widget)
442 {
443   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
444
445   if (priv->event_window)
446     gdk_window_hide (priv->event_window);
447
448   GTK_WIDGET_CLASS (gtk_switch_parent_class)->unmap (widget);
449 }
450
451 static inline void
452 gtk_switch_paint_handle (GtkWidget    *widget,
453                          cairo_t      *cr,
454                          GdkRectangle *box)
455 {
456   GtkStyle *style = gtk_widget_get_style (widget);
457
458   gtk_paint_slider (style, cr,
459                     gtk_widget_get_state (widget),
460                     GTK_SHADOW_OUT,
461                     GTK_WIDGET (widget), "switch-slider",
462                     box->x,
463                     box->y,
464                     box->width,
465                     box->height,
466                     GTK_ORIENTATION_HORIZONTAL);
467 }
468
469 static gboolean
470 gtk_switch_draw (GtkWidget *widget,
471                  cairo_t   *cr)
472 {
473   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
474   GtkStyle *style;
475   GdkRectangle handle;
476   PangoLayout *layout;
477   PangoRectangle rect;
478   gint label_x, label_y;
479   GtkStateType state;
480   gint focus_width, focus_pad;
481   gint x, y, width, height;
482
483   gtk_widget_style_get (widget,
484                         "focus-line-width", &focus_width,
485                         "focus-padding", &focus_pad,
486                         NULL);
487
488   style = gtk_widget_get_style (widget);
489
490   x = 0;
491   y = 0;
492   width = gtk_widget_get_allocated_width (widget);
493   height = gtk_widget_get_allocated_height (widget);
494
495   if (gtk_widget_has_focus (widget))
496     gtk_paint_focus (style, cr,
497                      gtk_widget_get_state (widget),
498                      widget, "button",
499                      x, y, width, height);
500
501   x += focus_width + focus_pad;
502   y += focus_width + focus_pad;
503   width -= 2 * (focus_width + focus_pad);
504   height -= 2 * (focus_width + focus_pad);
505
506   state = priv->is_active ? GTK_STATE_SELECTED : gtk_widget_get_state (widget);
507
508   /* background - XXX should this be a flat box instead? we're missing
509    * the border given by the shadow with that, which would require
510    * fixing all theme engines to add a subtle border for this widget
511    */
512   gtk_paint_box (style, cr,
513                  state,
514                  GTK_SHADOW_IN,
515                  widget, "switch-background",
516                  x, y, width, height);
517
518   if (!gtk_widget_is_sensitive (widget))
519     state = GTK_STATE_INSENSITIVE;
520
521   /* XXX the +1/-1 it's pixel wriggling after checking with the default
522    * theme and xmag
523    */
524   handle.y = y + style->ythickness + 1;
525   handle.width = (width - style->xthickness * 2) / 2;
526   handle.height = (height - style->ythickness * 2) - 1;
527
528   /* Translators: if the "on" state label requires more than three
529    * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
530    * the state
531    */
532   layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
533   pango_layout_get_extents (layout, NULL, &rect);
534   pango_extents_to_pixels (&rect, NULL);
535
536   label_x = x + style->xthickness
537           + ((width / 2) - rect.width - (style->xthickness * 2)) / 2;
538   label_y = y + style->ythickness
539           + (height - rect.height - (style->ythickness * 2)) / 2;
540
541   gtk_paint_layout (style, cr,
542                     state,
543                     FALSE,
544                     widget, "switch-on-label",
545                     label_x, label_y,
546                     layout);
547
548   g_object_unref (layout);
549
550   /* Translators: if the "off" state label requires more than three
551    * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
552    */
553   layout = gtk_widget_create_pango_layout (widget, C_("switch", "OFF"));
554   pango_layout_get_extents (layout, NULL, &rect);
555   pango_extents_to_pixels (&rect, NULL);
556
557   label_x = x + style->xthickness
558           + (width / 2)
559           + ((width / 2) - rect.width - (style->xthickness * 2)) / 2;
560   label_y = y + style->ythickness
561           + (height - rect.height - (style->ythickness * 2)) / 2;
562
563   gtk_paint_layout (style, cr,
564                     state,
565                     FALSE,
566                     widget, "switch-off-label",
567                     label_x, label_y,
568                     layout);
569
570   g_object_unref (layout);
571
572   if (priv->is_dragging)
573     handle.x = x + priv->handle_x;
574   else if (priv->is_active)
575     handle.x = x + width - handle.width - style->xthickness;
576   else
577     handle.x = x + style->xthickness;
578
579   gtk_switch_paint_handle (widget, cr, &handle);
580
581   return FALSE;
582 }
583
584 static AtkObject *
585 gtk_switch_get_accessible (GtkWidget *widget)
586 {
587   static gboolean first_time = TRUE;
588
589   if (G_UNLIKELY (first_time))
590     {
591       AtkObjectFactory *factory;
592       AtkRegistry *registry;
593       GType derived_type;
594       GType derived_atk_type;
595
596       /* Figure out whether accessibility is enabled by looking at the
597        * type of the accessible object which would be created for the
598        * parent type of GtkSwitch
599        */
600       derived_type = g_type_parent (GTK_TYPE_SWITCH);
601
602       registry = atk_get_default_registry ();
603       factory = atk_registry_get_factory (registry, derived_type);
604       derived_atk_type = atk_object_factory_get_accessible_type (factory);
605       if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
606         atk_registry_set_factory_type (registry,
607                                        GTK_TYPE_SWITCH,
608                                        gtk_switch_accessible_factory_get_type ());
609
610       first_time = FALSE;
611     }
612
613   return GTK_WIDGET_CLASS (gtk_switch_parent_class)->get_accessible (widget);
614 }
615
616 static void
617 gtk_switch_set_related_action (GtkSwitch *sw,
618                                GtkAction *action)
619 {
620   GtkSwitchPrivate *priv = sw->priv;
621
622   if (priv->action == action)
623     return;
624
625   gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (sw), action);
626
627   priv->action = action;
628 }
629
630 static void
631 gtk_switch_set_use_action_appearance (GtkSwitch *sw,
632                                       gboolean   use_appearance)
633 {
634   GtkSwitchPrivate *priv = sw->priv;
635
636   if (priv->use_action_appearance != use_appearance)
637     {
638       priv->use_action_appearance = use_appearance;
639
640       gtk_activatable_sync_action_properties (GTK_ACTIVATABLE (sw), priv->action);
641     }
642 }
643
644 static void
645 gtk_switch_set_property (GObject      *gobject,
646                          guint         prop_id,
647                          const GValue *value,
648                          GParamSpec   *pspec)
649 {
650   GtkSwitch *sw = GTK_SWITCH (gobject);
651
652   switch (prop_id)
653     {
654     case PROP_ACTIVE:
655       gtk_switch_set_active (sw, g_value_get_boolean (value));
656       break;
657
658     case PROP_RELATED_ACTION:
659       gtk_switch_set_related_action (sw, g_value_get_object (value));
660       break;
661
662     case PROP_USE_ACTION_APPEARANCE:
663       gtk_switch_set_use_action_appearance (sw, g_value_get_boolean (value));
664       break;
665
666     default:
667       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
668     }
669 }
670
671 static void
672 gtk_switch_get_property (GObject    *gobject,
673                          guint       prop_id,
674                          GValue     *value,
675                          GParamSpec *pspec)
676 {
677   GtkSwitchPrivate *priv = GTK_SWITCH (gobject)->priv;
678
679   switch (prop_id)
680     {
681     case PROP_ACTIVE:
682       g_value_set_boolean (value, priv->is_active);
683       break;
684
685     case PROP_RELATED_ACTION:
686       g_value_set_object (value, priv->action);
687       break;
688
689     case PROP_USE_ACTION_APPEARANCE:
690       g_value_set_boolean (value, priv->use_action_appearance);
691       break;
692
693     default:
694       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
695     }
696 }
697
698 static void
699 gtk_switch_dispose (GObject *object)
700 {
701   GtkSwitchPrivate *priv = GTK_SWITCH (object)->priv;
702
703   if (priv->action)
704     {
705       gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (object), NULL);
706       priv->action = NULL;
707     }
708
709   G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object);
710 }
711
712 static void
713 gtk_switch_class_init (GtkSwitchClass *klass)
714 {
715   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
716   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
717   gpointer activatable_iface;
718
719   g_type_class_add_private (klass, sizeof (GtkSwitchPrivate));
720
721   activatable_iface = g_type_default_interface_peek (GTK_TYPE_ACTIVATABLE);
722   switch_props[PROP_RELATED_ACTION] =
723     g_param_spec_override ("related-action",
724                            g_object_interface_find_property (activatable_iface,
725                                                              "related-action"));
726
727   switch_props[PROP_USE_ACTION_APPEARANCE] =
728     g_param_spec_override ("use-action-appearance",
729                            g_object_interface_find_property (activatable_iface,
730                                                              "use-action-appearance"));
731
732   /**
733    * GtkSwitch:active:
734    *
735    * Whether the #GtkSwitch widget is in its on or off state.
736    */
737   switch_props[PROP_ACTIVE] =
738     g_param_spec_boolean ("active",
739                           P_("Active"),
740                           P_("Whether the switch is on or off"),
741                           FALSE,
742                           GTK_PARAM_READWRITE);
743
744   gobject_class->set_property = gtk_switch_set_property;
745   gobject_class->get_property = gtk_switch_get_property;
746   gobject_class->dispose = gtk_switch_dispose;
747
748   g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);
749
750   widget_class->get_preferred_width = gtk_switch_get_preferred_width;
751   widget_class->get_preferred_height = gtk_switch_get_preferred_height;
752   widget_class->size_allocate = gtk_switch_size_allocate;
753   widget_class->realize = gtk_switch_realize;
754   widget_class->unrealize = gtk_switch_unrealize;
755   widget_class->map = gtk_switch_map;
756   widget_class->unmap = gtk_switch_unmap;
757   widget_class->draw = gtk_switch_draw;
758   widget_class->button_press_event = gtk_switch_button_press;
759   widget_class->button_release_event = gtk_switch_button_release;
760   widget_class->motion_notify_event = gtk_switch_motion;
761   widget_class->enter_notify_event = gtk_switch_enter;
762   widget_class->leave_notify_event = gtk_switch_leave;
763   widget_class->key_release_event = gtk_switch_key_release;
764   widget_class->get_accessible = gtk_switch_get_accessible;
765
766   /**
767    * GtkSwitch:slider-width:
768    *
769    * The minimum width of the #GtkSwitch handle, in pixels.
770    */
771   gtk_widget_class_install_style_property (widget_class,
772                                            g_param_spec_int ("slider-width",
773                                                              P_("Slider Width"),
774                                                              P_("The minimum width of the handle"),
775                                                              DEFAULT_SLIDER_WIDTH, G_MAXINT,
776                                                              DEFAULT_SLIDER_WIDTH,
777                                                              GTK_PARAM_READABLE));
778 }
779
780 static void
781 gtk_switch_init (GtkSwitch *self)
782 {
783   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_SWITCH, GtkSwitchPrivate);
784   self->priv->use_action_appearance = TRUE;
785   gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
786   gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
787 }
788
789 /**
790  * gtk_switch_new:
791  *
792  * Creates a new #GtkSwitch widget.
793  *
794  * Return value: the newly created #GtkSwitch instance
795  */
796 GtkWidget *
797 gtk_switch_new (void)
798 {
799   return g_object_new (GTK_TYPE_SWITCH, NULL);
800 }
801
802 /**
803  * gtk_switch_set_active:
804  * @sw: a #GtkSwitch
805  * @is_active: %TRUE if @sw should be active, and %FALSE otherwise
806  *
807  * Changes the state of @sw to the desired one.
808  */
809 void
810 gtk_switch_set_active (GtkSwitch *sw,
811                        gboolean   is_active)
812 {
813   GtkSwitchPrivate *priv;
814
815   g_return_if_fail (GTK_IS_SWITCH (sw));
816
817   is_active = !!is_active;
818
819   priv = sw->priv;
820
821   if (priv->is_active != is_active)
822     {
823       AtkObject *accessible;
824
825       priv->is_active = is_active;
826
827       g_object_notify_by_pspec (G_OBJECT (sw), switch_props[PROP_ACTIVE]);
828
829       if (priv->action)
830         gtk_action_activate (priv->action);
831
832       accessible = gtk_widget_get_accessible (GTK_WIDGET (sw));
833       atk_object_notify_state_change (accessible, ATK_STATE_CHECKED, priv->is_active);
834
835       gtk_widget_queue_draw (GTK_WIDGET (sw));
836     }
837 }
838
839 /**
840  * gtk_switch_get_active:
841  * @sw: a #GtkSwitch
842  *
843  * Gets whether the #GtkSwitch is in its "on" or "off" state.
844  *
845  * Return value: %TRUE if the #GtkSwitch is active, and %FALSE otherwise
846  */
847 gboolean
848 gtk_switch_get_active (GtkSwitch *sw)
849 {
850   g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);
851
852   return sw->priv->is_active;
853 }
854
855 static void
856 gtk_switch_update (GtkActivatable *activatable,
857                    GtkAction      *action,
858                    const gchar    *property_name)
859 {
860   if (strcmp (property_name, "visible") == 0)
861     {
862       if (gtk_action_is_visible (action))
863         gtk_widget_show (GTK_WIDGET (activatable));
864       else
865         gtk_widget_hide (GTK_WIDGET (activatable));
866     }
867   else if (strcmp (property_name, "sensitive") == 0)
868     {
869       gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
870     }
871   else if (strcmp (property_name, "active") == 0)
872     {
873       gtk_action_block_activate (action);
874       gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
875       gtk_action_unblock_activate (action);
876     }
877 }
878
879 static void
880 gtk_switch_sync_action_properties (GtkActivatable *activatable,
881                                    GtkAction      *action)
882 {
883   if (!action)
884     return;
885
886   if (gtk_action_is_visible (action))
887     gtk_widget_show (GTK_WIDGET (activatable));
888   else
889     gtk_widget_hide (GTK_WIDGET (activatable));
890
891   gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
892
893   gtk_action_block_activate (action);
894   gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
895   gtk_action_unblock_activate (action);
896 }
897
898 static void
899 gtk_switch_activatable_interface_init (GtkActivatableIface *iface)
900 {
901   iface->update = gtk_switch_update;
902   iface->sync_action_properties = gtk_switch_sync_action_properties;
903 }
904
905 /* accessibility: object */
906
907 static AtkObjectClass *a11y_parent_class = NULL;
908
909 static AtkStateSet *
910 gtk_switch_accessible_ref_state_set (AtkObject *accessible)
911 {
912   AtkStateSet *state_set;
913   GtkWidget *widget;
914
915   state_set = a11y_parent_class->ref_state_set (accessible);
916
917   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
918   if (widget == NULL)
919     return state_set;
920
921   if (gtk_switch_get_active (GTK_SWITCH (widget)))
922     atk_state_set_add_state (state_set, ATK_STATE_CHECKED);
923
924   return state_set;
925 }
926
927 static void
928 gtk_switch_accessible_initialize (AtkObject *accessible,
929                                   gpointer   widget)
930 {
931   a11y_parent_class->initialize (accessible, widget);
932
933   atk_object_set_role (accessible, ATK_ROLE_TOGGLE_BUTTON);
934   atk_object_set_name (accessible, C_("light switch widget", "Switch"));
935   atk_object_set_description (accessible, _("Switches between on and off states"));
936 }
937
938 static void
939 gtk_switch_accessible_class_init (AtkObjectClass *klass)
940 {
941   a11y_parent_class = g_type_class_peek_parent (klass);
942
943   klass->initialize = gtk_switch_accessible_initialize;
944   klass->ref_state_set = gtk_switch_accessible_ref_state_set;
945 }
946
947 static GType
948 gtk_switch_accessible_get_type (void)
949 {
950   static GType type = 0;
951
952   /* Action interface
953      Name etc. ... */
954   if (G_UNLIKELY (type == 0))
955     {
956       GType parent_atk_type;
957       GTypeInfo tinfo = { 0 };
958       GTypeQuery query;
959       AtkObjectFactory *factory;
960
961       if ((type = g_type_from_name ("GtkSwitchAccessible")) != G_TYPE_INVALID)
962         return type;
963
964       /* we inherit from the same ATK factory that provides support
965        * for GtkWidget; if Gail is being used then this means GailWidget.
966        */
967       factory = atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET);
968       if (factory == G_TYPE_INVALID)
969         return G_TYPE_INVALID;
970
971       parent_atk_type = atk_object_factory_get_accessible_type (factory);
972       if (parent_atk_type == G_TYPE_INVALID)
973         return G_TYPE_INVALID;
974
975       g_type_query (parent_atk_type, &query);
976
977       tinfo.class_init    = (GClassInitFunc) gtk_switch_accessible_class_init;
978       tinfo.class_size    = query.class_size;
979       tinfo.instance_size = query.instance_size;
980
981       type = g_type_register_static (parent_atk_type,
982                                      I_("GtkSwitchAccessible"),
983                                      &tinfo, 0);
984     }
985
986   return type;
987 }
988 /* accessibility: factory */
989
990 typedef AtkObjectFactoryClass   GtkSwitchAccessibleFactoryClass;
991 typedef AtkObjectFactory        GtkSwitchAccessibleFactory;
992
993 G_DEFINE_TYPE (GtkSwitchAccessibleFactory,
994                gtk_switch_accessible_factory,
995                ATK_TYPE_OBJECT_FACTORY);
996
997 static GType
998 gtk_switch_accessible_factory_get_accessible_type (void)
999 {
1000   return gtk_switch_accessible_get_type ();
1001 }
1002
1003 static AtkObject *
1004 gtk_switch_accessible_factory_create_accessible (GObject *obj)
1005 {
1006   AtkObject *accessible;
1007
1008   accessible = g_object_new (gtk_switch_accessible_get_type (), NULL);
1009   atk_object_initialize (accessible, obj);
1010
1011   return accessible;
1012 }
1013
1014 static void
1015 gtk_switch_accessible_factory_class_init (AtkObjectFactoryClass *klass)
1016 {
1017   klass->create_accessible = gtk_switch_accessible_factory_create_accessible;
1018   klass->get_accessible_type = gtk_switch_accessible_factory_get_accessible_type;
1019 }
1020
1021 static void
1022 gtk_switch_accessible_factory_init (AtkObjectFactory *factory)
1023 {
1024 }