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