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