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