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 * GtkSwitch:
28 *
29 * `GtkSwitch` is a "light switch" that has two states: on or off.
30 *
31 * ![An example GtkSwitch](switch.png)
32 *
33 * The user can control which state should be active by clicking the
34 * empty area, or by dragging the handle.
35 *
36 * `GtkSwitch` can also handle situations where the underlying state
37 * changes with a delay. See [signal@GtkSwitch::state-set] for details.
38 *
39 * # CSS nodes
40 *
41 * ```
42 * switch
43 * ├── label
44 * ├── label
45 * ╰── slider
46 * ```
47 *
48 * `GtkSwitch` has four css nodes, the main node with the name switch and
49 * subnodes for the slider and the on and off labels. Neither of them is
50 * using any style classes.
51 *
52 * # Accessibility
53 *
54 * `GtkSwitch` uses the %GTK_ACCESSIBLE_ROLE_SWITCH role.
55 */
56
57#include "config.h"
58
59#include "gtkswitch.h"
60
61#include "gtkactionable.h"
62#include "gtkactionhelperprivate.h"
63#include "gtkgestureclick.h"
64#include "gtkgesturepan.h"
65#include "gtkgesturesingle.h"
66#include "gtkgizmoprivate.h"
67#include "gtkintl.h"
68#include "gtkimage.h"
69#include "gtkcustomlayout.h"
70#include "gtkmarshalers.h"
71#include "gtkprivate.h"
72#include "gtkprogresstrackerprivate.h"
73#include "gtksettingsprivate.h"
74#include "gtkwidgetprivate.h"
75
76typedef struct _GtkSwitchClass GtkSwitchClass;
77
78struct _GtkSwitch
79{
80 GtkWidget parent_instance;
81
82 GtkActionHelper *action_helper;
83
84 GtkGesture *pan_gesture;
85 GtkGesture *click_gesture;
86
87 double handle_pos;
88 guint tick_id;
89
90 guint state : 1;
91 guint is_active : 1;
92
93 GtkProgressTracker tracker;
94
95 GtkWidget *on_image;
96 GtkWidget *off_image;
97 GtkWidget *slider;
98};
99
100struct _GtkSwitchClass
101{
102 GtkWidgetClass parent_class;
103
104 void (* activate) (GtkSwitch *self);
105
106 gboolean (* state_set) (GtkSwitch *self,
107 gboolean state);
108};
109
110enum
111{
112 PROP_0,
113 PROP_ACTIVE,
114 PROP_STATE,
115 LAST_PROP,
116 PROP_ACTION_NAME,
117 PROP_ACTION_TARGET
118};
119
120enum
121{
122 ACTIVATE,
123 STATE_SET,
124 LAST_SIGNAL
125};
126
127static guint signals[LAST_SIGNAL] = { 0 };
128
129static GParamSpec *switch_props[LAST_PROP] = { NULL, };
130
131static void gtk_switch_actionable_iface_init (GtkActionableInterface *iface);
132
133G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET,
134 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE,
135 gtk_switch_actionable_iface_init))
136
137static void
138gtk_switch_end_toggle_animation (GtkSwitch *self)
139{
140 if (self->tick_id != 0)
141 {
142 gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: self->tick_id);
143 self->tick_id = 0;
144 }
145}
146
147static gboolean
148gtk_switch_on_frame_clock_update (GtkWidget *widget,
149 GdkFrameClock *clock,
150 gpointer user_data)
151{
152 GtkSwitch *self = GTK_SWITCH (widget);
153
154 gtk_progress_tracker_advance_frame (tracker: &self->tracker,
155 frame_time: gdk_frame_clock_get_frame_time (frame_clock: clock));
156
157 if (gtk_progress_tracker_get_state (tracker: &self->tracker) != GTK_PROGRESS_STATE_AFTER)
158 {
159 if (self->is_active)
160 self->handle_pos = 1.0 - gtk_progress_tracker_get_ease_out_cubic (tracker: &self->tracker, FALSE);
161 else
162 self->handle_pos = gtk_progress_tracker_get_ease_out_cubic (tracker: &self->tracker, FALSE);
163 }
164 else
165 {
166 gtk_switch_set_active (self, is_active: !self->is_active);
167 }
168
169 gtk_widget_queue_allocate (GTK_WIDGET (self));
170
171 return G_SOURCE_CONTINUE;
172}
173
174#define ANIMATION_DURATION 100
175
176static void
177gtk_switch_begin_toggle_animation (GtkSwitch *self)
178{
179 if (gtk_settings_get_enable_animations (settings: gtk_widget_get_settings (GTK_WIDGET (self))))
180 {
181 gtk_progress_tracker_start (tracker: &self->tracker, duration: 1000 * ANIMATION_DURATION, delay: 0, iteration_count: 1.0);
182 if (self->tick_id == 0)
183 self->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
184 callback: gtk_switch_on_frame_clock_update,
185 NULL, NULL);
186 }
187 else
188 {
189 gtk_switch_set_active (self, is_active: !self->is_active);
190 }
191}
192
193static void
194gtk_switch_click_gesture_pressed (GtkGestureClick *gesture,
195 int n_press,
196 double x,
197 double y,
198 GtkSwitch *self)
199{
200 graphene_rect_t switch_bounds;
201
202 if (!gtk_widget_compute_bounds (GTK_WIDGET (self), GTK_WIDGET (self), out_bounds: &switch_bounds))
203 return;
204
205 /* If the press didn't happen in the draggable handle,
206 * cancel the pan gesture right away
207 */
208 if ((self->is_active && x <= switch_bounds.size.width / 2.0) ||
209 (!self->is_active && x > switch_bounds.size.width / 2.0))
210 gtk_gesture_set_state (gesture: self->pan_gesture, state: GTK_EVENT_SEQUENCE_DENIED);
211}
212
213static void
214gtk_switch_click_gesture_released (GtkGestureClick *gesture,
215 int n_press,
216 double x,
217 double y,
218 GtkSwitch *self)
219{
220 GdkEventSequence *sequence;
221
222 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
223
224 if (gtk_widget_contains (GTK_WIDGET (self), x, y) &&
225 gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
226 {
227 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
228 gtk_switch_begin_toggle_animation (self);
229 }
230}
231
232static void
233gtk_switch_pan_gesture_pan (GtkGesturePan *gesture,
234 GtkPanDirection direction,
235 double offset,
236 GtkSwitch *self)
237{
238 GtkWidget *widget = GTK_WIDGET (self);
239 int width;
240
241 width = gtk_widget_get_width (widget);
242
243 if (direction == GTK_PAN_DIRECTION_LEFT)
244 offset = -offset;
245
246 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
247
248 if (self->is_active)
249 offset += width / 2;
250
251 offset /= width / 2;
252 /* constrain the handle within the trough width */
253 self->handle_pos = CLAMP (offset, 0, 1.0);
254
255 /* we need to redraw the handle */
256 gtk_widget_queue_allocate (widget);
257}
258
259static void
260gtk_switch_pan_gesture_drag_end (GtkGestureDrag *gesture,
261 double x,
262 double y,
263 GtkSwitch *self)
264{
265 GdkEventSequence *sequence;
266 gboolean active;
267
268 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
269
270 if (gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence) == GTK_EVENT_SEQUENCE_CLAIMED)
271 {
272 /* if half the handle passed the middle of the switch, then we
273 * consider it to be on
274 */
275 active = self->handle_pos >= 0.5;
276 }
277 else if (!gtk_gesture_handles_sequence (gesture: self->click_gesture, sequence))
278 active = self->is_active;
279 else
280 return;
281
282 self->handle_pos = active ? 1.0 : 0.0;
283 gtk_switch_set_active (self, is_active: active);
284 gtk_widget_queue_allocate (GTK_WIDGET (self));
285}
286
287static void
288gtk_switch_activate (GtkSwitch *self)
289{
290 gtk_switch_begin_toggle_animation (self);
291}
292
293static void
294gtk_switch_measure (GtkWidget *widget,
295 GtkOrientation orientation,
296 int for_size,
297 int *minimum,
298 int *natural,
299 int *minimum_baseline,
300 int *natural_baseline)
301{
302 GtkSwitch *self = GTK_SWITCH (widget);
303 int slider_minimum, slider_natural;
304 int on_nat, off_nat;
305
306 gtk_widget_measure (widget: self->slider, orientation, for_size: -1,
307 minimum: &slider_minimum, natural: &slider_natural,
308 NULL, NULL);
309
310 gtk_widget_measure (widget: self->on_image, orientation, for_size, NULL, natural: &on_nat, NULL, NULL);
311 gtk_widget_measure (widget: self->off_image, orientation, for_size, NULL, natural: &off_nat, NULL, NULL);
312
313 if (orientation == GTK_ORIENTATION_HORIZONTAL)
314 {
315 int text_width = MAX (on_nat, off_nat);
316 *minimum = 2 * MAX (slider_minimum, text_width);
317 *natural = 2 * MAX (slider_natural, text_width);
318 }
319 else
320 {
321 int text_height = MAX (on_nat, off_nat);
322 *minimum = MAX (slider_minimum, text_height);
323 *natural = MAX (slider_natural, text_height);
324 }
325}
326
327static void
328gtk_switch_allocate (GtkWidget *widget,
329 int width,
330 int height,
331 int baseline)
332{
333 GtkSwitch *self = GTK_SWITCH (widget);
334 GtkAllocation child_alloc;
335 int min;
336
337 gtk_widget_size_allocate (widget: self->slider,
338 allocation: &(GtkAllocation) {
339 round (x: self->handle_pos * (width / 2)), 0,
340 width / 2, height
341 }, baseline: -1);
342
343 /* Center ON icon in left half */
344 gtk_widget_measure (widget: self->on_image, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, minimum: &min, NULL, NULL, NULL);
345 child_alloc.x = ((width / 2) - min) / 2;
346 child_alloc.width = min;
347 gtk_widget_measure (widget: self->on_image, orientation: GTK_ORIENTATION_VERTICAL, for_size: min, minimum: &min, NULL, NULL, NULL);
348 child_alloc.y = (height - min) / 2;
349 child_alloc.height = min;
350 gtk_widget_size_allocate (widget: self->on_image, allocation: &child_alloc, baseline: -1);
351
352 /* Center OFF icon in right half */
353 gtk_widget_measure (widget: self->off_image, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, minimum: &min, NULL, NULL, NULL);
354 child_alloc.x = (width / 2) + ((width / 2) - min) / 2;
355 child_alloc.width = min;
356 gtk_widget_measure (widget: self->off_image, orientation: GTK_ORIENTATION_VERTICAL, for_size: min, minimum: &min, NULL, NULL, NULL);
357 child_alloc.y = (height - min) / 2;
358 child_alloc.height = min;
359 gtk_widget_size_allocate (widget: self->off_image, allocation: &child_alloc, baseline: -1);
360}
361
362static void
363gtk_switch_set_action_name (GtkActionable *actionable,
364 const char *action_name)
365{
366 GtkSwitch *self = GTK_SWITCH (actionable);
367
368 if (!self->action_helper)
369 self->action_helper = gtk_action_helper_new (widget: actionable);
370
371 gtk_action_helper_set_action_name (helper: self->action_helper, action_name);
372}
373
374static void
375gtk_switch_set_action_target_value (GtkActionable *actionable,
376 GVariant *action_target)
377{
378 GtkSwitch *self = GTK_SWITCH (actionable);
379
380 if (!self->action_helper)
381 self->action_helper = gtk_action_helper_new (widget: actionable);
382
383 gtk_action_helper_set_action_target_value (helper: self->action_helper, action_target);
384}
385
386static const char *
387gtk_switch_get_action_name (GtkActionable *actionable)
388{
389 GtkSwitch *self = GTK_SWITCH (actionable);
390
391 return gtk_action_helper_get_action_name (helper: self->action_helper);
392}
393
394static GVariant *
395gtk_switch_get_action_target_value (GtkActionable *actionable)
396{
397 GtkSwitch *self = GTK_SWITCH (actionable);
398
399 return gtk_action_helper_get_action_target_value (helper: self->action_helper);
400}
401
402static void
403gtk_switch_actionable_iface_init (GtkActionableInterface *iface)
404{
405 iface->get_action_name = gtk_switch_get_action_name;
406 iface->set_action_name = gtk_switch_set_action_name;
407 iface->get_action_target_value = gtk_switch_get_action_target_value;
408 iface->set_action_target_value = gtk_switch_set_action_target_value;
409}
410
411static void
412gtk_switch_set_property (GObject *gobject,
413 guint prop_id,
414 const GValue *value,
415 GParamSpec *pspec)
416{
417 GtkSwitch *self = GTK_SWITCH (gobject);
418
419 switch (prop_id)
420 {
421 case PROP_ACTIVE:
422 gtk_switch_set_active (self, is_active: g_value_get_boolean (value));
423 break;
424
425 case PROP_STATE:
426 gtk_switch_set_state (self, state: g_value_get_boolean (value));
427 break;
428
429 case PROP_ACTION_NAME:
430 gtk_switch_set_action_name (GTK_ACTIONABLE (self), action_name: g_value_get_string (value));
431 break;
432
433 case PROP_ACTION_TARGET:
434 gtk_switch_set_action_target_value (GTK_ACTIONABLE (self), action_target: g_value_get_variant (value));
435 break;
436
437 default:
438 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
439 }
440}
441
442static void
443gtk_switch_get_property (GObject *gobject,
444 guint prop_id,
445 GValue *value,
446 GParamSpec *pspec)
447{
448 GtkSwitch *self = GTK_SWITCH (gobject);
449
450 switch (prop_id)
451 {
452 case PROP_ACTIVE:
453 g_value_set_boolean (value, v_boolean: self->is_active);
454 break;
455
456 case PROP_STATE:
457 g_value_set_boolean (value, v_boolean: self->state);
458 break;
459
460 case PROP_ACTION_NAME:
461 g_value_set_string (value, v_string: gtk_action_helper_get_action_name (helper: self->action_helper));
462 break;
463
464 case PROP_ACTION_TARGET:
465 g_value_set_variant (value, variant: gtk_action_helper_get_action_target_value (helper: self->action_helper));
466 break;
467
468 default:
469 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
470 }
471}
472
473static void
474gtk_switch_dispose (GObject *object)
475{
476 GtkSwitch *self = GTK_SWITCH (object);
477
478 g_clear_object (&self->action_helper);
479
480 G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object);
481}
482
483static void
484gtk_switch_finalize (GObject *object)
485{
486 GtkSwitch *self = GTK_SWITCH (object);
487
488 gtk_switch_end_toggle_animation (self);
489
490 gtk_widget_unparent (widget: self->on_image);
491 gtk_widget_unparent (widget: self->off_image);
492 gtk_widget_unparent (widget: self->slider);
493
494 G_OBJECT_CLASS (gtk_switch_parent_class)->finalize (object);
495}
496
497static gboolean
498state_set (GtkSwitch *self,
499 gboolean state)
500{
501 if (self->action_helper)
502 gtk_action_helper_activate (helper: self->action_helper);
503
504 gtk_switch_set_state (self, state);
505
506 return TRUE;
507}
508
509static void
510gtk_switch_class_init (GtkSwitchClass *klass)
511{
512 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
513 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
514
515 /**
516 * GtkSwitch:active: (attributes org.gtk.Property.get=gtk_switch_get_active org.gtk.Property.set=gtk_switch_set_active)
517 *
518 * Whether the `GtkSwitch` widget is in its on or off state.
519 */
520 switch_props[PROP_ACTIVE] =
521 g_param_spec_boolean (name: "active",
522 P_("Active"),
523 P_("Whether the switch is on or off"),
524 FALSE,
525 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
526
527 /**
528 * GtkSwitch:state: (attributes org.gtk.Property.get=gtk_switch_get_state org.gtk.Property.set=gtk_switch_set_state)
529 *
530 * The backend state that is controlled by the switch.
531 *
532 * See [signal@GtkSwitch::state-set] for details.
533 */
534 switch_props[PROP_STATE] =
535 g_param_spec_boolean (name: "state",
536 P_("State"),
537 P_("The backend state"),
538 FALSE,
539 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
540
541 gobject_class->set_property = gtk_switch_set_property;
542 gobject_class->get_property = gtk_switch_get_property;
543 gobject_class->dispose = gtk_switch_dispose;
544 gobject_class->finalize = gtk_switch_finalize;
545
546 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: switch_props);
547
548 klass->activate = gtk_switch_activate;
549 klass->state_set = state_set;
550
551 /**
552 * GtkSwitch::activate:
553 * @widget: the object which received the signal
554 *
555 * Emitted to animate the switch.
556 *
557 * Applications should never connect to this signal,
558 * but use the [property@Gtk.Switch:active] property.
559 */
560 signals[ACTIVATE] =
561 g_signal_new (I_("activate"),
562 G_OBJECT_CLASS_TYPE (gobject_class),
563 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
564 G_STRUCT_OFFSET (GtkSwitchClass, activate),
565 NULL, NULL,
566 NULL,
567 G_TYPE_NONE, n_params: 0);
568
569 gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE]);
570
571 /**
572 * GtkSwitch::state-set:
573 * @widget: the object on which the signal was emitted
574 * @state: the new state of the switch
575 *
576 * Emitted to change the underlying state.
577 *
578 * The ::state-set signal is emitted when the user changes the switch
579 * position. The default handler keeps the state in sync with the
580 * [property@Gtk.Switch:active] property.
581 *
582 * To implement delayed state change, applications can connect to this
583 * signal, initiate the change of the underlying state, and call
584 * [method@Gtk.Switch.set_state] when the underlying state change is
585 * complete. The signal handler should return %TRUE to prevent the
586 * default handler from running.
587 *
588 * Visually, the underlying state is represented by the trough color of
589 * the switch, while the [property@Gtk.Switch:active] property is
590 * represented by the position of the switch.
591 *
592 * Returns: %TRUE to stop the signal emission
593 */
594 signals[STATE_SET] =
595 g_signal_new (I_("state-set"),
596 G_OBJECT_CLASS_TYPE (gobject_class),
597 signal_flags: G_SIGNAL_RUN_LAST,
598 G_STRUCT_OFFSET (GtkSwitchClass, state_set),
599 accumulator: _gtk_boolean_handled_accumulator, NULL,
600 c_marshaller: _gtk_marshal_BOOLEAN__BOOLEAN,
601 G_TYPE_BOOLEAN, n_params: 1,
602 G_TYPE_BOOLEAN);
603 g_signal_set_va_marshaller (signal_id: signals[STATE_SET],
604 G_TYPE_FROM_CLASS (gobject_class),
605 va_marshaller: _gtk_marshal_BOOLEAN__BOOLEANv);
606
607 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ACTION_NAME, name: "action-name");
608 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ACTION_TARGET, name: "action-target");
609
610 gtk_widget_class_set_css_name (widget_class, I_("switch"));
611
612 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SWITCH);
613}
614
615static void
616gtk_switch_init (GtkSwitch *self)
617{
618 GtkLayoutManager *layout;
619 GtkGesture *gesture;
620
621 gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
622
623 gesture = gtk_gesture_click_new ();
624 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
625 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
626 g_signal_connect (gesture, "pressed",
627 G_CALLBACK (gtk_switch_click_gesture_pressed), self);
628 g_signal_connect (gesture, "released",
629 G_CALLBACK (gtk_switch_click_gesture_released), self);
630 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
631 phase: GTK_PHASE_BUBBLE);
632 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
633 self->click_gesture = gesture;
634
635 gesture = gtk_gesture_pan_new (orientation: GTK_ORIENTATION_HORIZONTAL);
636 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
637 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
638 g_signal_connect (gesture, "pan",
639 G_CALLBACK (gtk_switch_pan_gesture_pan), self);
640 g_signal_connect (gesture, "drag-end",
641 G_CALLBACK (gtk_switch_pan_gesture_drag_end), self);
642 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
643 phase: GTK_PHASE_CAPTURE);
644 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
645 self->pan_gesture = gesture;
646
647 layout = gtk_custom_layout_new (NULL,
648 measure: gtk_switch_measure,
649 allocate: gtk_switch_allocate);
650 gtk_widget_set_layout_manager (GTK_WIDGET (self), layout_manager: layout);
651
652 self->on_image = g_object_new (GTK_TYPE_IMAGE,
653 first_property_name: "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
654 "icon-name", "switch-on-symbolic",
655 NULL);
656 gtk_widget_set_parent (widget: self->on_image, GTK_WIDGET (self));
657
658 self->off_image = g_object_new (GTK_TYPE_IMAGE,
659 first_property_name: "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
660 "icon-name", "switch-off-symbolic",
661 NULL);
662 gtk_widget_set_parent (widget: self->off_image, GTK_WIDGET (self));
663
664 self->slider = gtk_gizmo_new_with_role (css_name: "slider",
665 role: GTK_ACCESSIBLE_ROLE_NONE,
666 NULL, NULL, NULL, NULL, NULL, NULL);
667 gtk_widget_set_parent (widget: self->slider, GTK_WIDGET (self));
668
669 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: self),
670 first_state: GTK_ACCESSIBLE_STATE_CHECKED, FALSE,
671 -1);
672}
673
674/**
675 * gtk_switch_new:
676 *
677 * Creates a new `GtkSwitch` widget.
678 *
679 * Returns: the newly created `GtkSwitch` instance
680 */
681GtkWidget *
682gtk_switch_new (void)
683{
684 return g_object_new (GTK_TYPE_SWITCH, NULL);
685}
686
687/**
688 * gtk_switch_set_active: (attributes org.gtk.Method.set_property=active)
689 * @self: a `GtkSwitch`
690 * @is_active: %TRUE if @self should be active, and %FALSE otherwise
691 *
692 * Changes the state of @self to the desired one.
693 */
694void
695gtk_switch_set_active (GtkSwitch *self,
696 gboolean is_active)
697{
698 g_return_if_fail (GTK_IS_SWITCH (self));
699
700 gtk_switch_end_toggle_animation (self);
701
702 is_active = !!is_active;
703
704 if (self->is_active != is_active)
705 {
706 gboolean handled;
707
708 self->is_active = is_active;
709
710 if (self->is_active)
711 self->handle_pos = 1.0;
712 else
713 self->handle_pos = 0.0;
714
715 g_signal_emit (instance: self, signal_id: signals[STATE_SET], detail: 0, is_active, &handled);
716
717 g_object_notify_by_pspec (G_OBJECT (self), pspec: switch_props[PROP_ACTIVE]);
718
719 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: self),
720 first_state: GTK_ACCESSIBLE_STATE_CHECKED, is_active,
721 -1);
722
723 gtk_widget_queue_allocate (GTK_WIDGET (self));
724 }
725}
726
727/**
728 * gtk_switch_get_active: (attributes org.gtk.Method.get_property=active)
729 * @self: a `GtkSwitch`
730 *
731 * Gets whether the `GtkSwitch` is in its “on” or “off” state.
732 *
733 * Returns: %TRUE if the `GtkSwitch` is active, and %FALSE otherwise
734 */
735gboolean
736gtk_switch_get_active (GtkSwitch *self)
737{
738 g_return_val_if_fail (GTK_IS_SWITCH (self), FALSE);
739
740 return self->is_active;
741}
742
743/**
744 * gtk_switch_set_state: (attributes org.gtk.Method.set_property=state)
745 * @self: a `GtkSwitch`
746 * @state: the new state
747 *
748 * Sets the underlying state of the `GtkSwitch`.
749 *
750 * Normally, this is the same as [property@Gtk.Switch:active], unless
751 * the switch is set up for delayed state changes. This function is
752 * typically called from a [signal@Gtk.Switch::state-set] signal handler.
753 *
754 * See [signal@Gtk.Switch::state-set] for details.
755 */
756void
757gtk_switch_set_state (GtkSwitch *self,
758 gboolean state)
759{
760 g_return_if_fail (GTK_IS_SWITCH (self));
761
762 state = state != FALSE;
763
764 if (self->state == state)
765 return;
766
767 self->state = state;
768
769 /* This will be a no-op if we're switching the state in response
770 * to a UI change. We're setting active anyway, to catch 'spontaneous'
771 * state changes.
772 */
773 gtk_switch_set_active (self, is_active: state);
774
775 if (state)
776 gtk_widget_set_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED, FALSE);
777 else
778 gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED);
779
780 g_object_notify_by_pspec (G_OBJECT (self), pspec: switch_props[PROP_STATE]);
781}
782
783/**
784 * gtk_switch_get_state: (attributes org.gtk.Method.get_property=state)
785 * @self: a `GtkSwitch`
786 *
787 * Gets the underlying state of the `GtkSwitch`.
788 *
789 * Returns: the underlying state
790 */
791gboolean
792gtk_switch_get_state (GtkSwitch *self)
793{
794 g_return_val_if_fail (GTK_IS_SWITCH (self), FALSE);
795
796 return self->state;
797}
798

source code of gtk/gtk/gtkswitch.c