]> Pileus Git - ~andy/gtk/blob - gtk/gtkswitch.c
Change FSF Address
[~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   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
312   GtkStyleContext *context;
313   GtkStateFlags state;
314   GtkBorder padding;
315   gint width, slider_width, focus_width, focus_pad;
316   PangoLayout *layout;
317   PangoRectangle logical_rect;
318
319   context = gtk_widget_get_style_context (widget);
320   state = gtk_widget_get_state_flags (widget);
321
322   if (priv->is_active)
323     state |= GTK_STATE_FLAG_ACTIVE;
324
325   gtk_style_context_save (context);
326
327   gtk_style_context_set_state (context, state);
328   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
329   gtk_style_context_get_padding (context, state, &padding);
330
331   width = padding.left + padding.right;
332
333   gtk_style_context_restore (context);
334
335   gtk_widget_style_get (widget,
336                         "slider-width", &slider_width,
337                         "focus-line-width", &focus_width,
338                         "focus-padding", &focus_pad,
339                         NULL);
340
341   width += 2 * (focus_width + focus_pad);
342
343   /* Translators: if the "on" state label requires more than three
344    * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
345    * the state
346    */
347   layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
348   pango_layout_get_extents (layout, NULL, &logical_rect);
349   pango_extents_to_pixels (&logical_rect, NULL);
350   width += MAX (logical_rect.width, slider_width);
351
352   /* Translators: if the "off" state label requires more than three
353    * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
354    */
355   pango_layout_set_text (layout, C_("switch", "OFF"), -1);
356   pango_layout_get_extents (layout, NULL, &logical_rect);
357   pango_extents_to_pixels (&logical_rect, NULL);
358   width += MAX (logical_rect.width, slider_width);
359
360   g_object_unref (layout);
361
362   if (minimum)
363     *minimum = width;
364
365   if (natural)
366     *natural = width;
367 }
368
369 static void
370 gtk_switch_get_preferred_height (GtkWidget *widget,
371                                  gint      *minimum,
372                                  gint      *natural)
373 {
374   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
375   GtkStyleContext *context;
376   GtkStateFlags state;
377   GtkBorder padding;
378   gint height, focus_width, focus_pad;
379   PangoLayout *layout;
380   PangoRectangle logical_rect;
381   gchar *str;
382
383   context = gtk_widget_get_style_context (widget);
384   state = gtk_widget_get_state_flags (widget);
385
386   if (priv->is_active)
387     state |= GTK_STATE_FLAG_ACTIVE;
388
389   gtk_style_context_save (context);
390
391   gtk_style_context_set_state (context, state);
392   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
393   gtk_style_context_get_padding (context, state, &padding);
394
395   height = padding.top + padding.bottom;
396
397   gtk_style_context_restore (context);
398
399   gtk_widget_style_get (widget,
400                         "focus-line-width", &focus_width,
401                         "focus-padding", &focus_pad,
402                         NULL);
403
404   height += 2 * (focus_width + focus_pad);
405
406   str = g_strdup_printf ("%s%s",
407                          C_("switch", "ON"),
408                          C_("switch", "OFF"));
409
410   layout = gtk_widget_create_pango_layout (widget, str);
411   pango_layout_get_extents (layout, NULL, &logical_rect);
412   pango_extents_to_pixels (&logical_rect, NULL);
413   height += MAX (DEFAULT_SLIDER_HEIGHT, logical_rect.height);
414
415   g_object_unref (layout);
416   g_free (str);
417
418   if (minimum)
419     *minimum = height;
420
421   if (natural)
422     *natural = height;
423 }
424
425 static void
426 gtk_switch_size_allocate (GtkWidget     *widget,
427                           GtkAllocation *allocation)
428 {
429   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
430
431   gtk_widget_set_allocation (widget, allocation);
432
433   if (gtk_widget_get_realized (widget))
434     gdk_window_move_resize (priv->event_window,
435                             allocation->x,
436                             allocation->y,
437                             allocation->width,
438                             allocation->height);
439 }
440
441 static void
442 gtk_switch_realize (GtkWidget *widget)
443 {
444   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
445   GdkWindow *parent_window;
446   GdkWindowAttr attributes;
447   gint attributes_mask;
448   GtkAllocation allocation;
449
450   gtk_widget_set_realized (widget, TRUE);
451   parent_window = gtk_widget_get_parent_window (widget);
452   gtk_widget_set_window (widget, parent_window);
453   g_object_ref (parent_window);
454
455   gtk_widget_get_allocation (widget, &allocation);
456
457   attributes.window_type = GDK_WINDOW_CHILD;
458   attributes.wclass = GDK_INPUT_ONLY;
459   attributes.x = allocation.x;
460   attributes.y = allocation.y;
461   attributes.width = allocation.width;
462   attributes.height = allocation.height;
463   attributes.event_mask = gtk_widget_get_events (widget);
464   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
465                             GDK_BUTTON_RELEASE_MASK |
466                             GDK_BUTTON1_MOTION_MASK |
467                             GDK_POINTER_MOTION_HINT_MASK |
468                             GDK_POINTER_MOTION_MASK |
469                             GDK_ENTER_NOTIFY_MASK |
470                             GDK_LEAVE_NOTIFY_MASK);
471   attributes_mask = GDK_WA_X | GDK_WA_Y;
472
473   priv->event_window = gdk_window_new (parent_window,
474                                        &attributes,
475                                        attributes_mask);
476   gdk_window_set_user_data (priv->event_window, widget);
477 }
478
479 static void
480 gtk_switch_unrealize (GtkWidget *widget)
481 {
482   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
483
484   if (priv->event_window != NULL)
485     {
486       gdk_window_set_user_data (priv->event_window, NULL);
487       gdk_window_destroy (priv->event_window);
488       priv->event_window = NULL;
489     }
490
491   GTK_WIDGET_CLASS (gtk_switch_parent_class)->unrealize (widget);
492 }
493
494 static void
495 gtk_switch_map (GtkWidget *widget)
496 {
497   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
498
499   GTK_WIDGET_CLASS (gtk_switch_parent_class)->map (widget);
500
501   if (priv->event_window)
502     gdk_window_show (priv->event_window);
503 }
504
505 static void
506 gtk_switch_unmap (GtkWidget *widget)
507 {
508   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
509
510   if (priv->event_window)
511     gdk_window_hide (priv->event_window);
512
513   GTK_WIDGET_CLASS (gtk_switch_parent_class)->unmap (widget);
514 }
515
516 static inline void
517 gtk_switch_paint_handle (GtkWidget    *widget,
518                          cairo_t      *cr,
519                          GdkRectangle *box)
520 {
521   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
522   GtkStyleContext *context = gtk_widget_get_style_context (widget);
523   GtkStateFlags state;
524
525   state = gtk_widget_get_state_flags (widget);
526
527   if (priv->is_active)
528     state |= GTK_STATE_FLAG_ACTIVE;
529
530   gtk_style_context_save (context);
531   gtk_style_context_set_state (context, state);
532   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
533
534   gtk_render_slider (context, cr,
535                      box->x, box->y,
536                      box->width, box->height,
537                      GTK_ORIENTATION_HORIZONTAL);
538
539   gtk_style_context_restore (context);
540 }
541
542 static gboolean
543 gtk_switch_draw (GtkWidget *widget,
544                  cairo_t   *cr)
545 {
546   GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
547   GtkStyleContext *context;
548   GdkRectangle handle;
549   PangoLayout *layout;
550   PangoFontDescription *desc;
551   const PangoFontDescription *style_desc;
552   PangoRectangle rect;
553   gint label_x, label_y;
554   GtkStateFlags state;
555   GtkBorder padding;
556   gint focus_width, focus_pad;
557   gint x, y, width, height;
558   gint font_size, style_font_size;
559
560   gtk_widget_style_get (widget,
561                         "focus-line-width", &focus_width,
562                         "focus-padding", &focus_pad,
563                         NULL);
564
565   context = gtk_widget_get_style_context (widget);
566   state = gtk_widget_get_state_flags (widget);
567
568   if (priv->is_active)
569     state |= GTK_STATE_FLAG_ACTIVE;
570
571   gtk_style_context_save (context);
572
573   gtk_style_context_set_state (context, state);
574   gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
575
576   gtk_style_context_get_padding (context, state, &padding);
577
578   gtk_style_context_restore (context);
579
580   x = 0;
581   y = 0;
582   width = gtk_widget_get_allocated_width (widget);
583   height = gtk_widget_get_allocated_height (widget);
584
585   if (gtk_widget_has_visible_focus (widget))
586     gtk_render_focus (context, cr, x, y, width, height);
587
588   x += focus_width + focus_pad;
589   y += focus_width + focus_pad;
590   width -= 2 * (focus_width + focus_pad);
591   height -= 2 * (focus_width + focus_pad);
592
593   gtk_style_context_save (context);
594   gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
595   gtk_style_context_set_state (context, state);
596
597   gtk_render_background (context, cr, x, y, width, height);
598   gtk_render_frame (context, cr, x, y, width, height);
599
600   width -= padding.left + padding.right;
601   height -= padding.top + padding.bottom;
602
603   x += padding.left;
604   y += padding.top;
605
606   handle.y = y;
607   handle.width = width / 2;
608   handle.height = height;
609
610   /* Translators: if the "on" state label requires more than three
611    * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
612    * the state
613    */
614   layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
615
616   /* FIXME: this should be really done in the theme, but overriding font size
617    * from it doesn't currently work. So we have to hardcode this here and
618    * below for the "OFF" label.
619    */
620   desc = pango_font_description_new ();
621
622   style_desc = gtk_style_context_get_font (context, state);
623   style_font_size = pango_font_description_get_size (style_desc);
624   font_size = MAX (style_font_size - 1 * PANGO_SCALE, ceil (style_font_size * PANGO_SCALE_SMALL));
625
626   pango_font_description_set_size (desc, font_size);
627
628   pango_layout_set_font_description (layout, desc);
629
630   pango_layout_get_extents (layout, NULL, &rect);
631   pango_extents_to_pixels (&rect, NULL);
632
633   label_x = x +  ((width / 2) - rect.width) / 2;
634   label_y = y + (height - rect.height) / 2;
635
636   gtk_render_layout (context, cr, label_x, label_y, layout);
637
638   g_object_unref (layout);
639
640   /* Translators: if the "off" state label requires more than three
641    * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
642    */
643   layout = gtk_widget_create_pango_layout (widget, C_("switch", "OFF"));
644   pango_layout_set_font_description (layout, desc);
645
646   pango_layout_get_extents (layout, NULL, &rect);
647   pango_extents_to_pixels (&rect, NULL);
648
649   label_x = x +  (width / 2) + ((width / 2) - rect.width) / 2;
650   label_y = y + (height - rect.height) / 2;
651
652   gtk_render_layout (context, cr, label_x, label_y, layout);
653
654   g_object_unref (layout);
655
656   if (priv->is_dragging)
657     handle.x = x + priv->handle_x;
658   else if (priv->is_active)
659     handle.x = x + width - handle.width;
660   else
661     handle.x = x;
662
663   gtk_style_context_restore (context);
664
665   gtk_switch_paint_handle (widget, cr, &handle);
666
667   pango_font_description_free (desc);
668
669   return FALSE;
670 }
671
672 static void
673 gtk_switch_set_related_action (GtkSwitch *sw,
674                                GtkAction *action)
675 {
676   GtkSwitchPrivate *priv = sw->priv;
677
678   if (priv->action == action)
679     return;
680
681   gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (sw), action);
682
683   priv->action = action;
684 }
685
686 static void
687 gtk_switch_set_use_action_appearance (GtkSwitch *sw,
688                                       gboolean   use_appearance)
689 {
690   GtkSwitchPrivate *priv = sw->priv;
691
692   if (priv->use_action_appearance != use_appearance)
693     {
694       priv->use_action_appearance = use_appearance;
695
696       gtk_activatable_sync_action_properties (GTK_ACTIVATABLE (sw), priv->action);
697     }
698 }
699
700 static void
701 gtk_switch_update_action_observer (GtkSwitch *sw)
702 {
703   GtkWidget *window;
704
705   /* we are the only owner so this will clear all the signals */
706   g_clear_object (&sw->priv->action_observer);
707
708   window = gtk_widget_get_toplevel (GTK_WIDGET (sw));
709
710   if (GTK_IS_APPLICATION_WINDOW (window) && sw->priv->action_name)
711     {
712       GSimpleActionObserver *observer;
713
714       observer = gtk_application_window_create_observer (GTK_APPLICATION_WINDOW (window),
715                                                          sw->priv->action_name,
716                                                          sw->priv->action_target);
717
718       g_object_bind_property (observer, "active", sw, "active", G_BINDING_SYNC_CREATE);
719       g_object_bind_property (observer, "enabled", sw, "sensitive", G_BINDING_SYNC_CREATE);
720
721       sw->priv->action_observer = observer;
722     }
723 }
724
725 static void
726 gtk_switch_set_action_name (GtkActionable *actionable,
727                             const gchar   *action_name)
728 {
729   GtkSwitch *sw = GTK_SWITCH (actionable);
730
731   g_return_if_fail (GTK_IS_SWITCH (sw));
732
733   g_free (sw->priv->action_name);
734   sw->priv->action_name = g_strdup (action_name);
735
736   gtk_switch_update_action_observer (sw);
737
738   g_object_notify (G_OBJECT (sw), "action-name");
739 }
740
741 static void
742 gtk_switch_set_action_target_value (GtkActionable *actionable,
743                                     GVariant      *action_target)
744 {
745   GtkSwitch *sw = GTK_SWITCH (actionable);
746
747   g_return_if_fail (GTK_IS_SWITCH (sw));
748
749   if (action_target != sw->priv->action_target &&
750       (!action_target || !sw->priv->action_target ||
751        !g_variant_equal (action_target, sw->priv->action_target)))
752     {
753       if (sw->priv->action_target)
754         g_variant_unref (sw->priv->action_target);
755
756       sw->priv->action_target = NULL;
757
758       if (action_target)
759         sw->priv->action_target = g_variant_ref_sink (action_target);
760
761       gtk_switch_update_action_observer (sw);
762
763       g_object_notify (G_OBJECT (sw), "action-target");
764     }
765 }
766
767 static const gchar *
768 gtk_switch_get_action_name (GtkActionable *actionable)
769 {
770   GtkSwitch *sw = GTK_SWITCH (actionable);
771
772   return sw->priv->action_name;
773 }
774
775 static GVariant *
776 gtk_switch_get_action_target_value (GtkActionable *actionable)
777 {
778   GtkSwitch *sw = GTK_SWITCH (actionable);
779
780   return sw->priv->action_target;
781 }
782
783 static void
784 gtk_switch_actionable_iface_init (GtkActionableInterface *iface)
785 {
786   iface->get_action_name = gtk_switch_get_action_name;
787   iface->set_action_name = gtk_switch_set_action_name;
788   iface->get_action_target_value = gtk_switch_get_action_target_value;
789   iface->set_action_target_value = gtk_switch_set_action_target_value;
790 }
791
792 static void
793 gtk_switch_hierarchy_changed (GtkWidget *widget,
794                               GtkWidget *previous_toplevel)
795 {
796   GtkSwitch *sw = GTK_SWITCH (widget);
797   GtkWidgetClass *parent_class;
798
799   parent_class = GTK_WIDGET_CLASS (gtk_switch_parent_class);
800   if (parent_class->hierarchy_changed)
801     parent_class->hierarchy_changed (widget, previous_toplevel);
802
803   if (sw->priv->action_name)
804     {
805       GtkWidget *toplevel;
806
807       toplevel = gtk_widget_get_toplevel (widget);
808
809       if (toplevel != previous_toplevel)
810         gtk_switch_update_action_observer (sw);
811     }
812 }
813
814 static void
815 gtk_switch_set_property (GObject      *gobject,
816                          guint         prop_id,
817                          const GValue *value,
818                          GParamSpec   *pspec)
819 {
820   GtkSwitch *sw = GTK_SWITCH (gobject);
821
822   switch (prop_id)
823     {
824     case PROP_ACTIVE:
825       gtk_switch_set_active (sw, g_value_get_boolean (value));
826       break;
827
828     case PROP_RELATED_ACTION:
829       gtk_switch_set_related_action (sw, g_value_get_object (value));
830       break;
831
832     case PROP_USE_ACTION_APPEARANCE:
833       gtk_switch_set_use_action_appearance (sw, g_value_get_boolean (value));
834       break;
835
836     case PROP_ACTION_NAME:
837       gtk_switch_set_action_name (GTK_ACTIONABLE (sw), g_value_get_string (value));
838       break;
839
840     case PROP_ACTION_TARGET:
841       gtk_switch_set_action_target_value (GTK_ACTIONABLE (sw), g_value_get_variant (value));
842       break;
843
844     default:
845       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
846     }
847 }
848
849 static void
850 gtk_switch_get_property (GObject    *gobject,
851                          guint       prop_id,
852                          GValue     *value,
853                          GParamSpec *pspec)
854 {
855   GtkSwitchPrivate *priv = GTK_SWITCH (gobject)->priv;
856
857   switch (prop_id)
858     {
859     case PROP_ACTIVE:
860       g_value_set_boolean (value, priv->is_active);
861       break;
862
863     case PROP_RELATED_ACTION:
864       g_value_set_object (value, priv->action);
865       break;
866
867     case PROP_USE_ACTION_APPEARANCE:
868       g_value_set_boolean (value, priv->use_action_appearance);
869       break;
870
871     case PROP_ACTION_NAME:
872       g_value_set_string (value, priv->action_name);
873       break;
874
875     case PROP_ACTION_TARGET:
876       g_value_set_variant (value, priv->action_target);
877       break;
878
879     default:
880       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
881     }
882 }
883
884 static void
885 gtk_switch_dispose (GObject *object)
886 {
887   GtkSwitchPrivate *priv = GTK_SWITCH (object)->priv;
888
889   g_clear_object (&priv->action_observer);
890
891   if (priv->action_name)
892     {
893       g_free (priv->action_name);
894       priv->action_name = NULL;
895     }
896
897   if (priv->action)
898     {
899       gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (object), NULL);
900       priv->action = NULL;
901     }
902
903   G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object);
904 }
905
906 static void
907 gtk_switch_class_init (GtkSwitchClass *klass)
908 {
909   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
910   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
911   gpointer activatable_iface;
912
913   g_type_class_add_private (klass, sizeof (GtkSwitchPrivate));
914
915   activatable_iface = g_type_default_interface_peek (GTK_TYPE_ACTIVATABLE);
916   switch_props[PROP_RELATED_ACTION] =
917     g_param_spec_override ("related-action",
918                            g_object_interface_find_property (activatable_iface,
919                                                              "related-action"));
920
921   switch_props[PROP_USE_ACTION_APPEARANCE] =
922     g_param_spec_override ("use-action-appearance",
923                            g_object_interface_find_property (activatable_iface,
924                                                              "use-action-appearance"));
925
926   /**
927    * GtkSwitch:active:
928    *
929    * Whether the #GtkSwitch widget is in its on or off state.
930    */
931   switch_props[PROP_ACTIVE] =
932     g_param_spec_boolean ("active",
933                           P_("Active"),
934                           P_("Whether the switch is on or off"),
935                           FALSE,
936                           GTK_PARAM_READWRITE);
937
938   gobject_class->set_property = gtk_switch_set_property;
939   gobject_class->get_property = gtk_switch_get_property;
940   gobject_class->dispose = gtk_switch_dispose;
941
942   g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);
943
944   widget_class->get_preferred_width = gtk_switch_get_preferred_width;
945   widget_class->get_preferred_height = gtk_switch_get_preferred_height;
946   widget_class->size_allocate = gtk_switch_size_allocate;
947   widget_class->realize = gtk_switch_realize;
948   widget_class->unrealize = gtk_switch_unrealize;
949   widget_class->map = gtk_switch_map;
950   widget_class->unmap = gtk_switch_unmap;
951   widget_class->draw = gtk_switch_draw;
952   widget_class->button_press_event = gtk_switch_button_press;
953   widget_class->button_release_event = gtk_switch_button_release;
954   widget_class->motion_notify_event = gtk_switch_motion;
955   widget_class->enter_notify_event = gtk_switch_enter;
956   widget_class->leave_notify_event = gtk_switch_leave;
957   widget_class->hierarchy_changed = gtk_switch_hierarchy_changed;
958
959   klass->activate = gtk_switch_activate;
960
961   /**
962    * GtkSwitch:slider-width:
963    *
964    * The minimum width of the #GtkSwitch handle, in pixels.
965    */
966   gtk_widget_class_install_style_property (widget_class,
967                                            g_param_spec_int ("slider-width",
968                                                              P_("Slider Width"),
969                                                              P_("The minimum width of the handle"),
970                                                              DEFAULT_SLIDER_WIDTH, G_MAXINT,
971                                                              DEFAULT_SLIDER_WIDTH,
972                                                              GTK_PARAM_READABLE));
973
974   /**
975    * GtkSwitch::activate:
976    * @widget: the object which received the signal.
977    *
978    * The ::activate signal on GtkSwitch is an action signal and
979    * emitting it causes the switch to animate.
980    * Applications should never connect to this signal, but use the
981    * notify::active signal.
982    */
983   signals[ACTIVATE] =
984     g_signal_new (I_("activate"),
985                   G_OBJECT_CLASS_TYPE (gobject_class),
986                   G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
987                   G_STRUCT_OFFSET (GtkSwitchClass, activate),
988                   NULL, NULL,
989                   _gtk_marshal_VOID__VOID,
990                   G_TYPE_NONE, 0);
991   widget_class->activate_signal = signals[ACTIVATE];
992
993   g_object_class_override_property (gobject_class, PROP_ACTION_NAME, "action-name");
994   g_object_class_override_property (gobject_class, PROP_ACTION_TARGET, "action-target");
995
996   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SWITCH_ACCESSIBLE);
997 }
998
999 static void
1000 gtk_switch_init (GtkSwitch *self)
1001 {
1002   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_SWITCH, GtkSwitchPrivate);
1003   self->priv->use_action_appearance = TRUE;
1004   gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
1005   gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
1006 }
1007
1008 /**
1009  * gtk_switch_new:
1010  *
1011  * Creates a new #GtkSwitch widget.
1012  *
1013  * Return value: the newly created #GtkSwitch instance
1014  *
1015  * Since: 3.0
1016  */
1017 GtkWidget *
1018 gtk_switch_new (void)
1019 {
1020   return g_object_new (GTK_TYPE_SWITCH, NULL);
1021 }
1022
1023 /**
1024  * gtk_switch_set_active:
1025  * @sw: a #GtkSwitch
1026  * @is_active: %TRUE if @sw should be active, and %FALSE otherwise
1027  *
1028  * Changes the state of @sw to the desired one.
1029  *
1030  * Since: 3.0
1031  */
1032 void
1033 gtk_switch_set_active (GtkSwitch *sw,
1034                        gboolean   is_active)
1035 {
1036   GtkSwitchPrivate *priv;
1037
1038   g_return_if_fail (GTK_IS_SWITCH (sw));
1039
1040   is_active = !!is_active;
1041
1042   priv = sw->priv;
1043
1044   if (priv->is_active != is_active)
1045     {
1046       AtkObject *accessible;
1047       GtkWidget *widget;
1048       GtkStyleContext *context;
1049
1050       widget = GTK_WIDGET (sw);
1051       priv->is_active = is_active;
1052
1053       g_object_notify_by_pspec (G_OBJECT (sw), switch_props[PROP_ACTIVE]);
1054
1055       if (priv->action_observer)
1056         g_simple_action_observer_activate (priv->action_observer);
1057
1058       if (priv->action)
1059         gtk_action_activate (priv->action);
1060
1061       accessible = gtk_widget_get_accessible (GTK_WIDGET (sw));
1062       atk_object_notify_state_change (accessible, ATK_STATE_CHECKED, priv->is_active);
1063
1064       if (gtk_widget_get_realized (widget))
1065         {
1066           context = gtk_widget_get_style_context (widget);
1067           gtk_style_context_notify_state_change (context,
1068                                                  gtk_widget_get_window (widget),
1069                                                  NULL, GTK_STATE_ACTIVE, is_active);
1070         }
1071
1072       gtk_widget_queue_draw (GTK_WIDGET (sw));
1073     }
1074 }
1075
1076 /**
1077  * gtk_switch_get_active:
1078  * @sw: a #GtkSwitch
1079  *
1080  * Gets whether the #GtkSwitch is in its "on" or "off" state.
1081  *
1082  * Return value: %TRUE if the #GtkSwitch is active, and %FALSE otherwise
1083  *
1084  * Since: 3.0
1085  */
1086 gboolean
1087 gtk_switch_get_active (GtkSwitch *sw)
1088 {
1089   g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);
1090
1091   return sw->priv->is_active;
1092 }
1093
1094 static void
1095 gtk_switch_update (GtkActivatable *activatable,
1096                    GtkAction      *action,
1097                    const gchar    *property_name)
1098 {
1099   if (strcmp (property_name, "visible") == 0)
1100     {
1101       if (gtk_action_is_visible (action))
1102         gtk_widget_show (GTK_WIDGET (activatable));
1103       else
1104         gtk_widget_hide (GTK_WIDGET (activatable));
1105     }
1106   else if (strcmp (property_name, "sensitive") == 0)
1107     {
1108       gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
1109     }
1110   else if (strcmp (property_name, "active") == 0)
1111     {
1112       gtk_action_block_activate (action);
1113       gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
1114       gtk_action_unblock_activate (action);
1115     }
1116 }
1117
1118 static void
1119 gtk_switch_sync_action_properties (GtkActivatable *activatable,
1120                                    GtkAction      *action)
1121 {
1122   if (!action)
1123     return;
1124
1125   if (gtk_action_is_visible (action))
1126     gtk_widget_show (GTK_WIDGET (activatable));
1127   else
1128     gtk_widget_hide (GTK_WIDGET (activatable));
1129
1130   gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
1131
1132   gtk_action_block_activate (action);
1133   gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
1134   gtk_action_unblock_activate (action);
1135 }
1136
1137 static void
1138 gtk_switch_activatable_interface_init (GtkActivatableIface *iface)
1139 {
1140   iface->update = gtk_switch_update;
1141   iface->sync_action_properties = gtk_switch_sync_action_properties;
1142 }