]> Pileus Git - ~andy/gtk/blob - gtk/gtkswitch.c
css: Remove old animation code
[~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, see <http://www.gnu.org/licenses/>.
18  *
19  * Author:
20  *      Emmanuele Bassi <ebassi@linux.intel.com>
21  *      Matthias Clasen <mclasen@redhat.com>
22  *
23  * Based on similar code from Mx.
24  */
25
26 /**
27  * SECTION:gtkswitch
28  * @Short_Description: A "light switch" style toggle
29  * @Title: GtkSwitch
30  * @See_Also: #GtkToggleButton
31  *
32  * #GtkSwitch is a widget that has two states: on or off. The user can control
33  * which state should be active by clicking the empty area, or by dragging the
34  * handle.
35  */
36
37 #include "config.h"
38
39 #include "gtkswitch.h"
40
41 #include "gtkactivatable.h"
42 #include "gtkintl.h"
43 #include "gtkprivate.h"
44 #include "gtktoggleaction.h"
45 #include "gtkwidget.h"
46 #include "gtkmarshalers.h"
47 #include "gtkapplicationprivate.h"
48 #include "gtkactionable.h"
49 #include "a11y/gtkswitchaccessible.h"
50
51 #include <math.h>
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   gchar                 *action_name;
62   GVariant              *action_target;
63   GSimpleActionObserver *action_observer;
64
65   gint handle_x;
66   gint offset;
67   gint drag_start;
68   gint drag_threshold;
69
70   guint is_active             : 1;
71   guint is_dragging           : 1;
72   guint in_press              : 1;
73   guint in_switch             : 1;
74   guint use_action_appearance : 1;
75 };
76
77 enum
78 {
79   PROP_0,
80   PROP_ACTIVE,
81   PROP_RELATED_ACTION,
82   PROP_USE_ACTION_APPEARANCE,
83   LAST_PROP,
84   PROP_ACTION_NAME,
85   PROP_ACTION_TARGET
86 };
87
88 enum
89 {
90   ACTIVATE,
91   LAST_SIGNAL
92 };
93
94 static guint signals[LAST_SIGNAL] = { 0 };
95
96 static GParamSpec *switch_props[LAST_PROP] = { NULL, };
97
98 static void gtk_switch_hierarchy_changed (GtkWidget *widget,
99                                           GtkWidget *previous_toplevel);
100  
101 static void gtk_switch_actionable_iface_init (GtkActionableInterface *iface);
102 static void gtk_switch_activatable_interface_init (GtkActivatableIface *iface);
103
104 G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET,
105                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE,
106                                                 gtk_switch_actionable_iface_init)
107                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
108                                                 gtk_switch_activatable_interface_init));
109
110 static gboolean
111 gtk_switch_button_press (GtkWidget      *widget,
112                          GdkEventButton *event)
113 {
114   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
115   GtkAllocation allocation;
116
117   gtk_widget_get_allocation (widget, &allocation);
118
119   if (priv->is_active)
120     {
121       /* if the event occurred in the "off" area, then this
122        * is a direct toggle
123        */
124       if (event->x <= allocation.width / 2)
125         {
126           priv->in_press = TRUE;
127           return TRUE;
128         }
129
130       priv->offset = event->x - allocation.width / 2;
131     }
132   else
133     {
134       /* if the event occurred in the "on" area, then this
135        * is a direct toggle
136        */
137       if (event->x >= allocation.width / 2)
138         {
139           priv->in_press = TRUE;
140           return TRUE;
141         }
142
143       priv->offset = event->x;
144     }
145
146   priv->drag_start = event->x;
147
148   g_object_get (gtk_widget_get_settings (widget),
149                 "gtk-dnd-drag-threshold", &priv->drag_threshold,
150                 NULL);
151
152   return TRUE;
153 }
154
155 static gboolean
156 gtk_switch_motion (GtkWidget      *widget,
157                    GdkEventMotion *event)
158 {
159   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
160
161   /* if this is a direct toggle we don't handle motion */
162   if (priv->in_press)
163     return FALSE;
164
165   if (ABS (event->x - priv->drag_start) < priv->drag_threshold)
166     return TRUE;
167
168   if (event->state & GDK_BUTTON1_MASK)
169     {
170       gint position = event->x - priv->offset;
171       GtkAllocation allocation;
172       GtkStyleContext *context;
173       GtkStateFlags state;
174       GtkBorder padding;
175       gint width, focus_width, focus_pad;
176
177       gtk_widget_style_get (widget,
178                             "focus-line-width", &focus_width,
179                             "focus-padding", &focus_pad,
180                             NULL);
181
182       context = gtk_widget_get_style_context (widget);
183       state = gtk_widget_get_state_flags (widget);
184
185       gtk_style_context_save (context);
186       gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
187       gtk_style_context_get_padding (context, state, &padding);
188       gtk_style_context_restore (context);
189       
190       gtk_widget_get_allocation (widget, &allocation);
191
192       width = allocation.width - 2 * (focus_width + focus_pad);
193
194       /* constrain the handle within the trough width */
195       if (position > (width / 2) - padding.right)
196         priv->handle_x = width / 2 - padding.right;
197       else if (position < padding.left)
198         priv->handle_x = 0;
199       else
200         priv->handle_x = position;
201
202       priv->is_dragging = TRUE;
203
204       /* we need to redraw the handle */
205       gtk_widget_queue_draw (widget);
206
207       return TRUE;
208     }
209
210   return FALSE;
211 }
212
213 static gboolean
214 gtk_switch_button_release (GtkWidget      *widget,
215                            GdkEventButton *event)
216 {
217   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
218   GtkAllocation allocation;
219
220   gtk_widget_get_allocation (widget, &allocation);
221
222   /* dragged toggles have a "soft" grab, so we don't care whether we
223    * are in the switch or not when the button is released; we do care
224    * for direct toggles, instead
225    */
226   if (!priv->is_dragging && !priv->in_switch)
227     return FALSE;
228
229   /* direct toggle */
230   if (priv->in_press)
231     {
232       priv->in_press = FALSE;
233       gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
234
235       return TRUE;
236     }
237
238   /* toggle the switch if the handle was clicked but a drag had not been
239    * initiated */
240   if (!priv->is_dragging && !priv->in_press)
241     {
242       gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
243
244       return TRUE;
245     }
246
247   /* dragged toggle */
248   if (priv->is_dragging)
249     {
250       priv->is_dragging = FALSE;
251
252       /* if half the handle passed the middle of the switch, then we
253        * consider it to be on
254        */
255       if ((priv->handle_x + (allocation.width / 4)) >= (allocation.width / 2))
256         {
257           gtk_switch_set_active (GTK_SWITCH (widget), TRUE);
258           priv->handle_x = allocation.width / 2;
259         }
260       else
261         {
262           gtk_switch_set_active (GTK_SWITCH (widget), FALSE);
263           priv->handle_x = 0;
264         }
265
266       gtk_widget_queue_draw (widget);
267
268       return TRUE;
269     }
270
271   return FALSE;
272 }
273
274 static gboolean
275 gtk_switch_enter (GtkWidget        *widget,
276                   GdkEventCrossing *event)
277 {
278   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
279
280   if (event->window == priv->event_window)
281     priv->in_switch = TRUE;
282
283   return FALSE;
284 }
285
286 static gboolean
287 gtk_switch_leave (GtkWidget        *widget,
288                   GdkEventCrossing *event)
289 {
290   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
291
292   if (event->window == priv->event_window)
293     priv->in_switch = FALSE;
294
295   return FALSE;
296 }
297
298 static void
299 gtk_switch_activate (GtkSwitch *sw)
300 {
301   GtkSwitchPrivate *priv = sw->priv;
302
303   gtk_switch_set_active (sw, !priv->is_active);
304 }
305
306 static void
307 gtk_switch_get_preferred_width (GtkWidget *widget,
308                                 gint      *minimum,
309                                 gint      *natural)
310 {
311   GtkStyleContext *context;
312   GtkStateFlags state;
313   GtkBorder padding;
314   gint width, slider_width, focus_width, focus_pad;
315   PangoLayout *layout;
316   PangoRectangle logical_rect;
317
318   context = gtk_widget_get_style_context (widget);
319   state = gtk_style_context_get_state (context);
320
321   gtk_style_context_save (context);
322
323   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
324   gtk_style_context_get_padding (context, state, &padding);
325
326   width = padding.left + padding.right;
327
328   gtk_style_context_restore (context);
329
330   gtk_widget_style_get (widget,
331                         "slider-width", &slider_width,
332                         "focus-line-width", &focus_width,
333                         "focus-padding", &focus_pad,
334                         NULL);
335
336   width += 2 * (focus_width + focus_pad);
337
338   /* Translators: if the "on" state label requires more than three
339    * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
340    * the state
341    */
342   layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
343   pango_layout_get_extents (layout, NULL, &logical_rect);
344   pango_extents_to_pixels (&logical_rect, NULL);
345   width += MAX (logical_rect.width, slider_width);
346
347   /* Translators: if the "off" state label requires more than three
348    * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
349    */
350   pango_layout_set_text (layout, C_("switch", "OFF"), -1);
351   pango_layout_get_extents (layout, NULL, &logical_rect);
352   pango_extents_to_pixels (&logical_rect, NULL);
353   width += MAX (logical_rect.width, slider_width);
354
355   g_object_unref (layout);
356
357   if (minimum)
358     *minimum = width;
359
360   if (natural)
361     *natural = width;
362 }
363
364 static void
365 gtk_switch_get_preferred_height (GtkWidget *widget,
366                                  gint      *minimum,
367                                  gint      *natural)
368 {
369   GtkStyleContext *context;
370   GtkStateFlags state;
371   GtkBorder padding;
372   gint height, focus_width, focus_pad;
373   PangoLayout *layout;
374   PangoRectangle logical_rect;
375   gchar *str;
376
377   context = gtk_widget_get_style_context (widget);
378   state = gtk_style_context_get_state (context);
379
380   gtk_style_context_save (context);
381
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   GtkStyleContext *context = gtk_widget_get_style_context (widget);
512
513   gtk_style_context_save (context);
514   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
515
516   gtk_render_slider (context, cr,
517                      box->x, box->y,
518                      box->width, box->height,
519                      GTK_ORIENTATION_HORIZONTAL);
520
521   gtk_style_context_restore (context);
522 }
523
524 static gboolean
525 gtk_switch_draw (GtkWidget *widget,
526                  cairo_t   *cr)
527 {
528   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
529   GtkStyleContext *context;
530   GdkRectangle handle;
531   PangoLayout *layout;
532   PangoFontDescription *desc;
533   const PangoFontDescription *style_desc;
534   PangoRectangle rect;
535   gint label_x, label_y;
536   GtkBorder padding;
537   GtkStateFlags state;
538   gint focus_width, focus_pad;
539   gint x, y, width, height;
540   gint font_size, style_font_size;
541
542   gtk_widget_style_get (widget,
543                         "focus-line-width", &focus_width,
544                         "focus-padding", &focus_pad,
545                         NULL);
546
547   context = gtk_widget_get_style_context (widget);
548   state = gtk_widget_get_state_flags (widget);
549
550   gtk_style_context_save (context);
551
552   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
553
554   gtk_style_context_get_padding (context, state, &padding);
555
556   gtk_style_context_restore (context);
557
558   x = 0;
559   y = 0;
560   width = gtk_widget_get_allocated_width (widget);
561   height = gtk_widget_get_allocated_height (widget);
562
563   if (gtk_widget_has_visible_focus (widget))
564     gtk_render_focus (context, cr, x, y, width, height);
565
566   x += focus_width + focus_pad;
567   y += focus_width + focus_pad;
568   width -= 2 * (focus_width + focus_pad);
569   height -= 2 * (focus_width + focus_pad);
570
571   gtk_style_context_save (context);
572   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
573
574   gtk_render_background (context, cr, x, y, width, height);
575   gtk_render_frame (context, cr, x, y, width, height);
576
577   width -= padding.left + padding.right;
578   height -= padding.top + padding.bottom;
579
580   x += padding.left;
581   y += padding.top;
582
583   handle.y = y;
584   handle.width = width / 2;
585   handle.height = height;
586
587   /* Translators: if the "on" state label requires more than three
588    * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
589    * the state
590    */
591   layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
592
593   /* FIXME: this should be really done in the theme, but overriding font size
594    * from it doesn't currently work. So we have to hardcode this here and
595    * below for the "OFF" label.
596    */
597   desc = pango_font_description_new ();
598
599   style_desc = gtk_style_context_get_font (context, state);
600   style_font_size = pango_font_description_get_size (style_desc);
601   font_size = MAX (style_font_size - 1 * PANGO_SCALE, ceil (style_font_size * PANGO_SCALE_SMALL));
602
603   pango_font_description_set_size (desc, font_size);
604
605   pango_layout_set_font_description (layout, desc);
606
607   pango_layout_get_extents (layout, NULL, &rect);
608   pango_extents_to_pixels (&rect, NULL);
609
610   label_x = x +  ((width / 2) - rect.width) / 2;
611   label_y = y + (height - rect.height) / 2;
612
613   gtk_render_layout (context, cr, label_x, label_y, layout);
614
615   g_object_unref (layout);
616
617   /* Translators: if the "off" state label requires more than three
618    * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
619    */
620   layout = gtk_widget_create_pango_layout (widget, C_("switch", "OFF"));
621   pango_layout_set_font_description (layout, desc);
622
623   pango_layout_get_extents (layout, NULL, &rect);
624   pango_extents_to_pixels (&rect, NULL);
625
626   label_x = x +  (width / 2) + ((width / 2) - rect.width) / 2;
627   label_y = y + (height - rect.height) / 2;
628
629   gtk_render_layout (context, cr, label_x, label_y, layout);
630
631   g_object_unref (layout);
632
633   if (priv->is_dragging)
634     handle.x = x + priv->handle_x;
635   else if (priv->is_active)
636     handle.x = x + width - handle.width;
637   else
638     handle.x = x;
639
640   gtk_style_context_restore (context);
641
642   gtk_switch_paint_handle (widget, cr, &handle);
643
644   pango_font_description_free (desc);
645
646   return FALSE;
647 }
648
649 static void
650 gtk_switch_set_related_action (GtkSwitch *sw,
651                                GtkAction *action)
652 {
653   GtkSwitchPrivate *priv = sw->priv;
654
655   if (priv->action == action)
656     return;
657
658   gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (sw), action);
659
660   priv->action = action;
661 }
662
663 static void
664 gtk_switch_set_use_action_appearance (GtkSwitch *sw,
665                                       gboolean   use_appearance)
666 {
667   GtkSwitchPrivate *priv = sw->priv;
668
669   if (priv->use_action_appearance != use_appearance)
670     {
671       priv->use_action_appearance = use_appearance;
672
673       gtk_activatable_sync_action_properties (GTK_ACTIVATABLE (sw), priv->action);
674     }
675 }
676
677 static void
678 gtk_switch_update_action_observer (GtkSwitch *sw)
679 {
680   GtkWidget *window;
681
682   /* we are the only owner so this will clear all the signals */
683   g_clear_object (&sw->priv->action_observer);
684
685   window = gtk_widget_get_toplevel (GTK_WIDGET (sw));
686
687   if (GTK_IS_APPLICATION_WINDOW (window) && sw->priv->action_name)
688     {
689       GSimpleActionObserver *observer;
690
691       observer = gtk_application_window_create_observer (GTK_APPLICATION_WINDOW (window),
692                                                          sw->priv->action_name,
693                                                          sw->priv->action_target);
694
695       g_object_bind_property (observer, "active", sw, "active", G_BINDING_SYNC_CREATE);
696       g_object_bind_property (observer, "enabled", sw, "sensitive", G_BINDING_SYNC_CREATE);
697
698       sw->priv->action_observer = observer;
699     }
700 }
701
702 static void
703 gtk_switch_set_action_name (GtkActionable *actionable,
704                             const gchar   *action_name)
705 {
706   GtkSwitch *sw = GTK_SWITCH (actionable);
707
708   g_return_if_fail (GTK_IS_SWITCH (sw));
709
710   g_free (sw->priv->action_name);
711   sw->priv->action_name = g_strdup (action_name);
712
713   gtk_switch_update_action_observer (sw);
714
715   g_object_notify (G_OBJECT (sw), "action-name");
716 }
717
718 static void
719 gtk_switch_set_action_target_value (GtkActionable *actionable,
720                                     GVariant      *action_target)
721 {
722   GtkSwitch *sw = GTK_SWITCH (actionable);
723
724   g_return_if_fail (GTK_IS_SWITCH (sw));
725
726   if (action_target != sw->priv->action_target &&
727       (!action_target || !sw->priv->action_target ||
728        !g_variant_equal (action_target, sw->priv->action_target)))
729     {
730       if (sw->priv->action_target)
731         g_variant_unref (sw->priv->action_target);
732
733       sw->priv->action_target = NULL;
734
735       if (action_target)
736         sw->priv->action_target = g_variant_ref_sink (action_target);
737
738       gtk_switch_update_action_observer (sw);
739
740       g_object_notify (G_OBJECT (sw), "action-target");
741     }
742 }
743
744 static const gchar *
745 gtk_switch_get_action_name (GtkActionable *actionable)
746 {
747   GtkSwitch *sw = GTK_SWITCH (actionable);
748
749   return sw->priv->action_name;
750 }
751
752 static GVariant *
753 gtk_switch_get_action_target_value (GtkActionable *actionable)
754 {
755   GtkSwitch *sw = GTK_SWITCH (actionable);
756
757   return sw->priv->action_target;
758 }
759
760 static void
761 gtk_switch_actionable_iface_init (GtkActionableInterface *iface)
762 {
763   iface->get_action_name = gtk_switch_get_action_name;
764   iface->set_action_name = gtk_switch_set_action_name;
765   iface->get_action_target_value = gtk_switch_get_action_target_value;
766   iface->set_action_target_value = gtk_switch_set_action_target_value;
767 }
768
769 static void
770 gtk_switch_hierarchy_changed (GtkWidget *widget,
771                               GtkWidget *previous_toplevel)
772 {
773   GtkSwitch *sw = GTK_SWITCH (widget);
774   GtkWidgetClass *parent_class;
775
776   parent_class = GTK_WIDGET_CLASS (gtk_switch_parent_class);
777   if (parent_class->hierarchy_changed)
778     parent_class->hierarchy_changed (widget, previous_toplevel);
779
780   if (sw->priv->action_name)
781     {
782       GtkWidget *toplevel;
783
784       toplevel = gtk_widget_get_toplevel (widget);
785
786       if (toplevel != previous_toplevel)
787         gtk_switch_update_action_observer (sw);
788     }
789 }
790
791 static void
792 gtk_switch_set_property (GObject      *gobject,
793                          guint         prop_id,
794                          const GValue *value,
795                          GParamSpec   *pspec)
796 {
797   GtkSwitch *sw = GTK_SWITCH (gobject);
798
799   switch (prop_id)
800     {
801     case PROP_ACTIVE:
802       gtk_switch_set_active (sw, g_value_get_boolean (value));
803       break;
804
805     case PROP_RELATED_ACTION:
806       gtk_switch_set_related_action (sw, g_value_get_object (value));
807       break;
808
809     case PROP_USE_ACTION_APPEARANCE:
810       gtk_switch_set_use_action_appearance (sw, g_value_get_boolean (value));
811       break;
812
813     case PROP_ACTION_NAME:
814       gtk_switch_set_action_name (GTK_ACTIONABLE (sw), g_value_get_string (value));
815       break;
816
817     case PROP_ACTION_TARGET:
818       gtk_switch_set_action_target_value (GTK_ACTIONABLE (sw), g_value_get_variant (value));
819       break;
820
821     default:
822       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
823     }
824 }
825
826 static void
827 gtk_switch_get_property (GObject    *gobject,
828                          guint       prop_id,
829                          GValue     *value,
830                          GParamSpec *pspec)
831 {
832   GtkSwitchPrivate *priv = GTK_SWITCH (gobject)->priv;
833
834   switch (prop_id)
835     {
836     case PROP_ACTIVE:
837       g_value_set_boolean (value, priv->is_active);
838       break;
839
840     case PROP_RELATED_ACTION:
841       g_value_set_object (value, priv->action);
842       break;
843
844     case PROP_USE_ACTION_APPEARANCE:
845       g_value_set_boolean (value, priv->use_action_appearance);
846       break;
847
848     case PROP_ACTION_NAME:
849       g_value_set_string (value, priv->action_name);
850       break;
851
852     case PROP_ACTION_TARGET:
853       g_value_set_variant (value, priv->action_target);
854       break;
855
856     default:
857       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
858     }
859 }
860
861 static void
862 gtk_switch_dispose (GObject *object)
863 {
864   GtkSwitchPrivate *priv = GTK_SWITCH (object)->priv;
865
866   g_clear_object (&priv->action_observer);
867
868   if (priv->action_name)
869     {
870       g_free (priv->action_name);
871       priv->action_name = NULL;
872     }
873
874   if (priv->action)
875     {
876       gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (object), NULL);
877       priv->action = NULL;
878     }
879
880   G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object);
881 }
882
883 static void
884 gtk_switch_class_init (GtkSwitchClass *klass)
885 {
886   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
887   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
888   gpointer activatable_iface;
889
890   g_type_class_add_private (klass, sizeof (GtkSwitchPrivate));
891
892   activatable_iface = g_type_default_interface_peek (GTK_TYPE_ACTIVATABLE);
893   switch_props[PROP_RELATED_ACTION] =
894     g_param_spec_override ("related-action",
895                            g_object_interface_find_property (activatable_iface,
896                                                              "related-action"));
897
898   switch_props[PROP_USE_ACTION_APPEARANCE] =
899     g_param_spec_override ("use-action-appearance",
900                            g_object_interface_find_property (activatable_iface,
901                                                              "use-action-appearance"));
902
903   /**
904    * GtkSwitch:active:
905    *
906    * Whether the #GtkSwitch widget is in its on or off state.
907    */
908   switch_props[PROP_ACTIVE] =
909     g_param_spec_boolean ("active",
910                           P_("Active"),
911                           P_("Whether the switch is on or off"),
912                           FALSE,
913                           GTK_PARAM_READWRITE);
914
915   gobject_class->set_property = gtk_switch_set_property;
916   gobject_class->get_property = gtk_switch_get_property;
917   gobject_class->dispose = gtk_switch_dispose;
918
919   g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);
920
921   widget_class->get_preferred_width = gtk_switch_get_preferred_width;
922   widget_class->get_preferred_height = gtk_switch_get_preferred_height;
923   widget_class->size_allocate = gtk_switch_size_allocate;
924   widget_class->realize = gtk_switch_realize;
925   widget_class->unrealize = gtk_switch_unrealize;
926   widget_class->map = gtk_switch_map;
927   widget_class->unmap = gtk_switch_unmap;
928   widget_class->draw = gtk_switch_draw;
929   widget_class->button_press_event = gtk_switch_button_press;
930   widget_class->button_release_event = gtk_switch_button_release;
931   widget_class->motion_notify_event = gtk_switch_motion;
932   widget_class->enter_notify_event = gtk_switch_enter;
933   widget_class->leave_notify_event = gtk_switch_leave;
934   widget_class->hierarchy_changed = gtk_switch_hierarchy_changed;
935
936   klass->activate = gtk_switch_activate;
937
938   /**
939    * GtkSwitch:slider-width:
940    *
941    * The minimum width of the #GtkSwitch handle, in pixels.
942    */
943   gtk_widget_class_install_style_property (widget_class,
944                                            g_param_spec_int ("slider-width",
945                                                              P_("Slider Width"),
946                                                              P_("The minimum width of the handle"),
947                                                              DEFAULT_SLIDER_WIDTH, G_MAXINT,
948                                                              DEFAULT_SLIDER_WIDTH,
949                                                              GTK_PARAM_READABLE));
950
951   /**
952    * GtkSwitch::activate:
953    * @widget: the object which received the signal.
954    *
955    * The ::activate signal on GtkSwitch is an action signal and
956    * emitting it causes the switch to animate.
957    * Applications should never connect to this signal, but use the
958    * notify::active signal.
959    */
960   signals[ACTIVATE] =
961     g_signal_new (I_("activate"),
962                   G_OBJECT_CLASS_TYPE (gobject_class),
963                   G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
964                   G_STRUCT_OFFSET (GtkSwitchClass, activate),
965                   NULL, NULL,
966                   _gtk_marshal_VOID__VOID,
967                   G_TYPE_NONE, 0);
968   widget_class->activate_signal = signals[ACTIVATE];
969
970   g_object_class_override_property (gobject_class, PROP_ACTION_NAME, "action-name");
971   g_object_class_override_property (gobject_class, PROP_ACTION_TARGET, "action-target");
972
973   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SWITCH_ACCESSIBLE);
974 }
975
976 static void
977 gtk_switch_init (GtkSwitch *self)
978 {
979   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_SWITCH, GtkSwitchPrivate);
980   self->priv->use_action_appearance = TRUE;
981   gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
982   gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
983 }
984
985 /**
986  * gtk_switch_new:
987  *
988  * Creates a new #GtkSwitch widget.
989  *
990  * Return value: the newly created #GtkSwitch instance
991  *
992  * Since: 3.0
993  */
994 GtkWidget *
995 gtk_switch_new (void)
996 {
997   return g_object_new (GTK_TYPE_SWITCH, NULL);
998 }
999
1000 /**
1001  * gtk_switch_set_active:
1002  * @sw: a #GtkSwitch
1003  * @is_active: %TRUE if @sw should be active, and %FALSE otherwise
1004  *
1005  * Changes the state of @sw to the desired one.
1006  *
1007  * Since: 3.0
1008  */
1009 void
1010 gtk_switch_set_active (GtkSwitch *sw,
1011                        gboolean   is_active)
1012 {
1013   GtkSwitchPrivate *priv;
1014
1015   g_return_if_fail (GTK_IS_SWITCH (sw));
1016
1017   is_active = !!is_active;
1018
1019   priv = sw->priv;
1020
1021   if (priv->is_active != is_active)
1022     {
1023       AtkObject *accessible;
1024
1025       priv->is_active = is_active;
1026
1027       g_object_notify_by_pspec (G_OBJECT (sw), switch_props[PROP_ACTIVE]);
1028
1029       if (priv->action_observer)
1030         g_simple_action_observer_activate (priv->action_observer);
1031
1032       if (priv->action)
1033         gtk_action_activate (priv->action);
1034
1035       accessible = gtk_widget_get_accessible (GTK_WIDGET (sw));
1036       atk_object_notify_state_change (accessible, ATK_STATE_CHECKED, priv->is_active);
1037
1038       if (priv->is_active)
1039         gtk_widget_set_state_flags (GTK_WIDGET (sw), GTK_STATE_FLAG_ACTIVE, FALSE);
1040       else
1041         gtk_widget_unset_state_flags (GTK_WIDGET (sw), GTK_STATE_FLAG_ACTIVE);
1042
1043       gtk_widget_queue_draw (GTK_WIDGET (sw));
1044     }
1045 }
1046
1047 /**
1048  * gtk_switch_get_active:
1049  * @sw: a #GtkSwitch
1050  *
1051  * Gets whether the #GtkSwitch is in its "on" or "off" state.
1052  *
1053  * Return value: %TRUE if the #GtkSwitch is active, and %FALSE otherwise
1054  *
1055  * Since: 3.0
1056  */
1057 gboolean
1058 gtk_switch_get_active (GtkSwitch *sw)
1059 {
1060   g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);
1061
1062   return sw->priv->is_active;
1063 }
1064
1065 static void
1066 gtk_switch_update (GtkActivatable *activatable,
1067                    GtkAction      *action,
1068                    const gchar    *property_name)
1069 {
1070   if (strcmp (property_name, "visible") == 0)
1071     {
1072       if (gtk_action_is_visible (action))
1073         gtk_widget_show (GTK_WIDGET (activatable));
1074       else
1075         gtk_widget_hide (GTK_WIDGET (activatable));
1076     }
1077   else if (strcmp (property_name, "sensitive") == 0)
1078     {
1079       gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
1080     }
1081   else if (strcmp (property_name, "active") == 0)
1082     {
1083       gtk_action_block_activate (action);
1084       gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
1085       gtk_action_unblock_activate (action);
1086     }
1087 }
1088
1089 static void
1090 gtk_switch_sync_action_properties (GtkActivatable *activatable,
1091                                    GtkAction      *action)
1092 {
1093   if (!action)
1094     return;
1095
1096   if (gtk_action_is_visible (action))
1097     gtk_widget_show (GTK_WIDGET (activatable));
1098   else
1099     gtk_widget_hide (GTK_WIDGET (activatable));
1100
1101   gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
1102
1103   gtk_action_block_activate (action);
1104   gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
1105   gtk_action_unblock_activate (action);
1106 }
1107
1108 static void
1109 gtk_switch_activatable_interface_init (GtkActivatableIface *iface)
1110 {
1111   iface->update = gtk_switch_update;
1112   iface->sync_action_properties = gtk_switch_sync_action_properties;
1113 }