]> Pileus Git - ~andy/gtk/blob - gtk/gtkswitch.c
Forgotten changes
[~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  * Since: 3.0
797  */
798 GtkWidget *
799 gtk_switch_new (void)
800 {
801   return g_object_new (GTK_TYPE_SWITCH, NULL);
802 }
803
804 /**
805  * gtk_switch_set_active:
806  * @sw: a #GtkSwitch
807  * @is_active: %TRUE if @sw should be active, and %FALSE otherwise
808  *
809  * Changes the state of @sw to the desired one.
810  *
811  * Since: 3.0
812  */
813 void
814 gtk_switch_set_active (GtkSwitch *sw,
815                        gboolean   is_active)
816 {
817   GtkSwitchPrivate *priv;
818
819   g_return_if_fail (GTK_IS_SWITCH (sw));
820
821   is_active = !!is_active;
822
823   priv = sw->priv;
824
825   if (priv->is_active != is_active)
826     {
827       AtkObject *accessible;
828
829       priv->is_active = is_active;
830
831       g_object_notify_by_pspec (G_OBJECT (sw), switch_props[PROP_ACTIVE]);
832
833       if (priv->action)
834         gtk_action_activate (priv->action);
835
836       accessible = gtk_widget_get_accessible (GTK_WIDGET (sw));
837       atk_object_notify_state_change (accessible, ATK_STATE_CHECKED, priv->is_active);
838
839       gtk_widget_queue_draw (GTK_WIDGET (sw));
840     }
841 }
842
843 /**
844  * gtk_switch_get_active:
845  * @sw: a #GtkSwitch
846  *
847  * Gets whether the #GtkSwitch is in its "on" or "off" state.
848  *
849  * Return value: %TRUE if the #GtkSwitch is active, and %FALSE otherwise
850  *
851  * Since: 3.0
852  */
853 gboolean
854 gtk_switch_get_active (GtkSwitch *sw)
855 {
856   g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);
857
858   return sw->priv->is_active;
859 }
860
861 static void
862 gtk_switch_update (GtkActivatable *activatable,
863                    GtkAction      *action,
864                    const gchar    *property_name)
865 {
866   if (strcmp (property_name, "visible") == 0)
867     {
868       if (gtk_action_is_visible (action))
869         gtk_widget_show (GTK_WIDGET (activatable));
870       else
871         gtk_widget_hide (GTK_WIDGET (activatable));
872     }
873   else if (strcmp (property_name, "sensitive") == 0)
874     {
875       gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
876     }
877   else if (strcmp (property_name, "active") == 0)
878     {
879       gtk_action_block_activate (action);
880       gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
881       gtk_action_unblock_activate (action);
882     }
883 }
884
885 static void
886 gtk_switch_sync_action_properties (GtkActivatable *activatable,
887                                    GtkAction      *action)
888 {
889   if (!action)
890     return;
891
892   if (gtk_action_is_visible (action))
893     gtk_widget_show (GTK_WIDGET (activatable));
894   else
895     gtk_widget_hide (GTK_WIDGET (activatable));
896
897   gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
898
899   gtk_action_block_activate (action);
900   gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
901   gtk_action_unblock_activate (action);
902 }
903
904 static void
905 gtk_switch_activatable_interface_init (GtkActivatableIface *iface)
906 {
907   iface->update = gtk_switch_update;
908   iface->sync_action_properties = gtk_switch_sync_action_properties;
909 }
910
911 /* accessibility: object */
912
913 /* dummy typedefs */
914 typedef struct _GtkSwitchAccessible             GtkSwitchAccessible;
915 typedef struct _GtkSwitchAccessibleClass        GtkSwitchAccessibleClass;
916
917 ATK_DEFINE_TYPE (GtkSwitchAccessible, _gtk_switch_accessible, GTK_TYPE_WIDGET);
918
919 static AtkStateSet *
920 gtk_switch_accessible_ref_state_set (AtkObject *accessible)
921 {
922   AtkStateSet *state_set;
923   GtkWidget *widget;
924
925   state_set = ATK_OBJECT_CLASS (_gtk_switch_accessible_parent_class)->ref_state_set (accessible);
926
927   widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
928   if (widget == NULL)
929     return state_set;
930
931   if (gtk_switch_get_active (GTK_SWITCH (widget)))
932     atk_state_set_add_state (state_set, ATK_STATE_CHECKED);
933
934   return state_set;
935 }
936
937 static void
938 _gtk_switch_accessible_initialize (AtkObject *accessible,
939                                    gpointer   widget)
940 {
941   ATK_OBJECT_CLASS (_gtk_switch_accessible_parent_class)->initialize (accessible, widget);
942
943   atk_object_set_role (accessible, ATK_ROLE_TOGGLE_BUTTON);
944   atk_object_set_name (accessible, C_("light switch widget", "Switch"));
945   atk_object_set_description (accessible, _("Switches between on and off states"));
946 }
947
948 static void
949 _gtk_switch_accessible_class_init (GtkSwitchAccessibleClass *klass)
950 {
951   AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
952
953   atk_class->initialize = _gtk_switch_accessible_initialize;
954   atk_class->ref_state_set = gtk_switch_accessible_ref_state_set;
955 }
956
957 static void
958 _gtk_switch_accessible_init (GtkSwitchAccessible *self)
959 {
960 }
961
962 /* accessibility: factory */
963
964 typedef AtkObjectFactoryClass   GtkSwitchAccessibleFactoryClass;
965 typedef AtkObjectFactory        GtkSwitchAccessibleFactory;
966
967 G_DEFINE_TYPE (GtkSwitchAccessibleFactory,
968                gtk_switch_accessible_factory,
969                ATK_TYPE_OBJECT_FACTORY);
970
971 static GType
972 gtk_switch_accessible_factory_get_accessible_type (void)
973 {
974   return _gtk_switch_accessible_get_type ();
975 }
976
977 static AtkObject *
978 gtk_switch_accessible_factory_create_accessible (GObject *obj)
979 {
980   AtkObject *accessible;
981
982   accessible = g_object_new (_gtk_switch_accessible_get_type (), NULL);
983   atk_object_initialize (accessible, obj);
984
985   return accessible;
986 }
987
988 static void
989 gtk_switch_accessible_factory_class_init (AtkObjectFactoryClass *klass)
990 {
991   klass->create_accessible = gtk_switch_accessible_factory_create_accessible;
992   klass->get_accessible_type = gtk_switch_accessible_factory_get_accessible_type;
993 }
994
995 static void
996 gtk_switch_accessible_factory_init (AtkObjectFactory *factory)
997 {
998 }