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