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