1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | |
27 | #include "gtkcheckbutton.h" |
28 | |
29 | #include "gtkactionhelperprivate.h" |
30 | #include "gtkboxlayout.h" |
31 | #include "gtkbuiltiniconprivate.h" |
32 | #include "gtkcssnumbervalueprivate.h" |
33 | #include "gtkgestureclick.h" |
34 | #include "gtkintl.h" |
35 | #include "gtklabel.h" |
36 | #include "gtkprivate.h" |
37 | #include "gtkshortcuttrigger.h" |
38 | #include "gtkcssnodeprivate.h" |
39 | #include "gtkwidgetprivate.h" |
40 | #include "gtkmodelbuttonprivate.h" |
41 | |
42 | /** |
43 | * GtkCheckButton: |
44 | * |
45 | * A `GtkCheckButton` places a label next to an indicator. |
46 | * |
47 | * ![Example GtkCheckButtons](check-button.png) |
48 | * |
49 | * A `GtkCheckButton` is created by calling either [ctor@Gtk.CheckButton.new] |
50 | * or [ctor@Gtk.CheckButton.new_with_label]. |
51 | * |
52 | * The state of a `GtkCheckButton` can be set specifically using |
53 | * [method@Gtk.CheckButton.set_active], and retrieved using |
54 | * [method@Gtk.CheckButton.get_active]. |
55 | * |
56 | * # Inconsistent state |
57 | * |
58 | * In addition to "on" and "off", check buttons can be an |
59 | * "in between" state that is neither on nor off. This can be used |
60 | * e.g. when the user has selected a range of elements (such as some |
61 | * text or spreadsheet cells) that are affected by a check button, |
62 | * and the current values in that range are inconsistent. |
63 | * |
64 | * To set a `GtkCheckButton` to inconsistent state, use |
65 | * [method@Gtk.CheckButton.set_inconsistent]. |
66 | * |
67 | * # Grouping |
68 | * |
69 | * Check buttons can be grouped together, to form mutually exclusive |
70 | * groups - only one of the buttons can be toggled at a time, and toggling |
71 | * another one will switch the currently toggled one off. |
72 | * |
73 | * Grouped check buttons use a different indicator, and are commonly referred |
74 | * to as *radio buttons*. |
75 | * |
76 | * ![Example GtkCheckButtons](radio-button.png) |
77 | * |
78 | * To add a `GtkCheckButton` to a group, use [method@Gtk.CheckButton.set_group]. |
79 | * |
80 | * # CSS nodes |
81 | * |
82 | * ``` |
83 | * checkbutton[.text-button] |
84 | * ├── check |
85 | * ╰── [label] |
86 | * ``` |
87 | * |
88 | * A `GtkCheckButton` has a main node with name checkbutton. If the |
89 | * [property@Gtk.CheckButton:label] property is set, it contains a label |
90 | * child. The indicator node is named check when no group is set, and |
91 | * radio if the checkbutton is grouped together with other checkbuttons. |
92 | * |
93 | * # Accessibility |
94 | * |
95 | * `GtkCheckButton` uses the %GTK_ACCESSIBLE_ROLE_CHECKBOX role. |
96 | */ |
97 | |
98 | typedef struct { |
99 | GtkWidget *indicator_widget; |
100 | GtkWidget *label_widget; |
101 | |
102 | guint inconsistent: 1; |
103 | guint active: 1; |
104 | guint use_underline: 1; |
105 | |
106 | GtkCheckButton *group_next; |
107 | GtkCheckButton *group_prev; |
108 | |
109 | GtkActionHelper *action_helper; |
110 | } GtkCheckButtonPrivate; |
111 | |
112 | enum { |
113 | PROP_0, |
114 | PROP_ACTIVE, |
115 | PROP_GROUP, |
116 | PROP_LABEL, |
117 | PROP_INCONSISTENT, |
118 | PROP_USE_UNDERLINE, |
119 | |
120 | /* actionable properties */ |
121 | PROP_ACTION_NAME, |
122 | PROP_ACTION_TARGET, |
123 | LAST_PROP = PROP_ACTION_NAME |
124 | }; |
125 | |
126 | enum { |
127 | TOGGLED, |
128 | ACTIVATE, |
129 | LAST_SIGNAL |
130 | }; |
131 | |
132 | static void gtk_check_button_actionable_iface_init (GtkActionableInterface *iface); |
133 | |
134 | static guint signals[LAST_SIGNAL] = { 0 }; |
135 | static GParamSpec *props[LAST_PROP] = { NULL, }; |
136 | |
137 | G_DEFINE_TYPE_WITH_CODE (GtkCheckButton, gtk_check_button, GTK_TYPE_WIDGET, |
138 | G_ADD_PRIVATE (GtkCheckButton) |
139 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_check_button_actionable_iface_init)) |
140 | |
141 | static void |
142 | gtk_check_button_dispose (GObject *object) |
143 | { |
144 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (GTK_CHECK_BUTTON (object)); |
145 | |
146 | g_clear_object (&priv->action_helper); |
147 | |
148 | g_clear_pointer (&priv->indicator_widget, gtk_widget_unparent); |
149 | g_clear_pointer (&priv->label_widget, gtk_widget_unparent); |
150 | |
151 | gtk_check_button_set_group (GTK_CHECK_BUTTON (object), NULL); |
152 | |
153 | G_OBJECT_CLASS (gtk_check_button_parent_class)->dispose (object); |
154 | } |
155 | |
156 | static void |
157 | button_role_changed (GtkCheckButton *self) |
158 | { |
159 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
160 | |
161 | if (gtk_action_helper_get_role (helper: priv->action_helper) == GTK_BUTTON_ROLE_RADIO) |
162 | gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget), |
163 | name: g_quark_from_static_string(string: "radio" )); |
164 | else |
165 | gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget), |
166 | name: g_quark_from_static_string(string: "check" )); |
167 | } |
168 | |
169 | static void |
170 | ensure_action_helper (GtkCheckButton *self) |
171 | { |
172 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
173 | |
174 | if (priv->action_helper) |
175 | return; |
176 | |
177 | priv->action_helper = gtk_action_helper_new (GTK_ACTIONABLE (self)); |
178 | g_signal_connect_swapped (priv->action_helper, "notify::role" , |
179 | G_CALLBACK (button_role_changed), self); |
180 | } |
181 | |
182 | static void |
183 | gtk_check_button_set_action_name (GtkActionable *actionable, |
184 | const char *action_name) |
185 | { |
186 | GtkCheckButton *self = GTK_CHECK_BUTTON (actionable); |
187 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
188 | |
189 | ensure_action_helper (self); |
190 | |
191 | gtk_action_helper_set_action_name (helper: priv->action_helper, action_name); |
192 | } |
193 | |
194 | static void |
195 | gtk_check_button_set_action_target_value (GtkActionable *actionable, |
196 | GVariant *action_target) |
197 | { |
198 | GtkCheckButton *self = GTK_CHECK_BUTTON (actionable); |
199 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
200 | |
201 | ensure_action_helper (self); |
202 | |
203 | gtk_action_helper_set_action_target_value (helper: priv->action_helper, action_target); |
204 | } |
205 | |
206 | static void |
207 | gtk_check_button_set_property (GObject *object, |
208 | guint prop_id, |
209 | const GValue *value, |
210 | GParamSpec *pspec) |
211 | { |
212 | switch (prop_id) |
213 | { |
214 | case PROP_ACTIVE: |
215 | gtk_check_button_set_active (GTK_CHECK_BUTTON (object), setting: g_value_get_boolean (value)); |
216 | break; |
217 | case PROP_GROUP: |
218 | gtk_check_button_set_group (GTK_CHECK_BUTTON (object), group: g_value_get_object (value)); |
219 | break; |
220 | case PROP_LABEL: |
221 | gtk_check_button_set_label (GTK_CHECK_BUTTON (object), label: g_value_get_string (value)); |
222 | break; |
223 | case PROP_INCONSISTENT: |
224 | gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (object), inconsistent: g_value_get_boolean (value)); |
225 | break; |
226 | case PROP_USE_UNDERLINE: |
227 | gtk_check_button_set_use_underline (GTK_CHECK_BUTTON (object), setting: g_value_get_boolean (value)); |
228 | break; |
229 | case PROP_ACTION_NAME: |
230 | gtk_check_button_set_action_name (GTK_ACTIONABLE (object), action_name: g_value_get_string (value)); |
231 | break; |
232 | case PROP_ACTION_TARGET: |
233 | gtk_check_button_set_action_target_value (GTK_ACTIONABLE (object), action_target: g_value_get_variant (value)); |
234 | break; |
235 | default: |
236 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
237 | break; |
238 | } |
239 | } |
240 | |
241 | static void |
242 | gtk_check_button_get_property (GObject *object, |
243 | guint prop_id, |
244 | GValue *value, |
245 | GParamSpec *pspec) |
246 | { |
247 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (GTK_CHECK_BUTTON (object)); |
248 | |
249 | switch (prop_id) |
250 | { |
251 | case PROP_ACTIVE: |
252 | g_value_set_boolean (value, v_boolean: gtk_check_button_get_active (GTK_CHECK_BUTTON (object))); |
253 | break; |
254 | case PROP_LABEL: |
255 | g_value_set_string (value, v_string: gtk_check_button_get_label (GTK_CHECK_BUTTON (object))); |
256 | break; |
257 | case PROP_INCONSISTENT: |
258 | g_value_set_boolean (value, v_boolean: gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (object))); |
259 | break; |
260 | case PROP_USE_UNDERLINE: |
261 | g_value_set_boolean (value, v_boolean: gtk_check_button_get_use_underline (GTK_CHECK_BUTTON (object))); |
262 | break; |
263 | case PROP_ACTION_NAME: |
264 | g_value_set_string (value, v_string: gtk_action_helper_get_action_name (helper: priv->action_helper)); |
265 | break; |
266 | case PROP_ACTION_TARGET: |
267 | g_value_set_variant (value, variant: gtk_action_helper_get_action_target_value (helper: priv->action_helper)); |
268 | break; |
269 | default: |
270 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
271 | break; |
272 | } |
273 | } |
274 | |
275 | static const char * |
276 | gtk_check_button_get_action_name (GtkActionable *actionable) |
277 | { |
278 | GtkCheckButton *self = GTK_CHECK_BUTTON (actionable); |
279 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
280 | |
281 | return gtk_action_helper_get_action_name (helper: priv->action_helper); |
282 | } |
283 | |
284 | static GVariant * |
285 | gtk_check_button_get_action_target_value (GtkActionable *actionable) |
286 | { |
287 | GtkCheckButton *self = GTK_CHECK_BUTTON (actionable); |
288 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
289 | |
290 | return gtk_action_helper_get_action_target_value (helper: priv->action_helper); |
291 | } |
292 | |
293 | static void |
294 | gtk_check_button_actionable_iface_init (GtkActionableInterface *iface) |
295 | { |
296 | iface->get_action_name = gtk_check_button_get_action_name; |
297 | iface->set_action_name = gtk_check_button_set_action_name; |
298 | iface->get_action_target_value = gtk_check_button_get_action_target_value; |
299 | iface->set_action_target_value = gtk_check_button_set_action_target_value; |
300 | } |
301 | |
302 | static void |
303 | click_pressed_cb (GtkGestureClick *gesture, |
304 | guint n_press, |
305 | double x, |
306 | double y, |
307 | GtkWidget *widget) |
308 | { |
309 | if (gtk_widget_get_focus_on_click (widget) && !gtk_widget_has_focus (widget)) |
310 | gtk_widget_grab_focus (widget); |
311 | } |
312 | |
313 | static void |
314 | click_released_cb (GtkGestureClick *gesture, |
315 | guint n_press, |
316 | double x, |
317 | double y, |
318 | GtkWidget *widget) |
319 | { |
320 | GtkCheckButton *self = GTK_CHECK_BUTTON (widget); |
321 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
322 | |
323 | if (priv->active && (priv->group_prev || priv->group_next)) |
324 | return; |
325 | |
326 | gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED); |
327 | if (gtk_widget_is_sensitive (widget) && |
328 | gtk_widget_contains (widget, x, y)) |
329 | { |
330 | if (priv->action_helper) |
331 | gtk_action_helper_activate (helper: priv->action_helper); |
332 | else |
333 | gtk_check_button_set_active (self, setting: !priv->active); |
334 | } |
335 | } |
336 | |
337 | static void |
338 | update_accessible_state (GtkCheckButton *check_button) |
339 | { |
340 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self: check_button); |
341 | |
342 | GtkAccessibleTristate checked_state; |
343 | |
344 | if (priv->inconsistent) |
345 | checked_state = GTK_ACCESSIBLE_TRISTATE_MIXED; |
346 | else if (priv->active) |
347 | checked_state = GTK_ACCESSIBLE_TRISTATE_TRUE; |
348 | else |
349 | checked_state = GTK_ACCESSIBLE_TRISTATE_FALSE; |
350 | |
351 | gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: check_button), |
352 | first_state: GTK_ACCESSIBLE_STATE_CHECKED, checked_state, |
353 | -1); |
354 | } |
355 | |
356 | |
357 | static GtkCheckButton * |
358 | get_group_next (GtkCheckButton *self) |
359 | { |
360 | return ((GtkCheckButtonPrivate *)gtk_check_button_get_instance_private (self))->group_next; |
361 | } |
362 | |
363 | static GtkCheckButton * |
364 | get_group_prev (GtkCheckButton *self) |
365 | { |
366 | return ((GtkCheckButtonPrivate *)gtk_check_button_get_instance_private (self))->group_prev; |
367 | } |
368 | |
369 | static GtkCheckButton * |
370 | get_group_first (GtkCheckButton *self) |
371 | { |
372 | GtkCheckButton *group_first = NULL; |
373 | GtkCheckButton *iter; |
374 | |
375 | /* Find first in group */ |
376 | iter = self; |
377 | while (iter) |
378 | { |
379 | group_first = iter; |
380 | |
381 | iter = get_group_prev (self: iter); |
382 | if (!iter) |
383 | break; |
384 | } |
385 | |
386 | g_assert (group_first); |
387 | |
388 | return group_first; |
389 | } |
390 | |
391 | static GtkCheckButton * |
392 | get_group_active_button (GtkCheckButton *self) |
393 | { |
394 | GtkCheckButton *iter; |
395 | |
396 | for (iter = get_group_first (self); iter; iter = get_group_next (self: iter)) |
397 | { |
398 | if (gtk_check_button_get_active (self: iter)) |
399 | return iter; |
400 | } |
401 | |
402 | return NULL; |
403 | } |
404 | |
405 | static void |
406 | gtk_check_button_state_flags_changed (GtkWidget *widget, |
407 | GtkStateFlags previous_flags) |
408 | { |
409 | GtkCheckButton *self = GTK_CHECK_BUTTON (widget); |
410 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
411 | GtkStateFlags state = gtk_widget_get_state_flags (GTK_WIDGET (self)); |
412 | |
413 | gtk_widget_set_state_flags (widget: priv->indicator_widget, flags: state, TRUE); |
414 | |
415 | GTK_WIDGET_CLASS (gtk_check_button_parent_class)->state_flags_changed (widget, previous_flags); |
416 | } |
417 | |
418 | static gboolean |
419 | gtk_check_button_focus (GtkWidget *widget, |
420 | GtkDirectionType direction) |
421 | { |
422 | GtkCheckButton *self = GTK_CHECK_BUTTON (widget); |
423 | |
424 | if (gtk_widget_is_focus (widget)) |
425 | { |
426 | GtkCheckButton *iter; |
427 | GPtrArray *child_array; |
428 | GtkWidget *new_focus = NULL; |
429 | guint index; |
430 | gboolean found; |
431 | guint i; |
432 | |
433 | if (direction == GTK_DIR_TAB_FORWARD || |
434 | direction == GTK_DIR_TAB_BACKWARD) |
435 | return FALSE; |
436 | |
437 | child_array = g_ptr_array_new (); |
438 | for (iter = get_group_first (self); iter; iter = get_group_next (self: iter)) |
439 | g_ptr_array_add (array: child_array, data: iter); |
440 | |
441 | gtk_widget_focus_sort (widget, direction, focus_order: child_array); |
442 | found = g_ptr_array_find (haystack: child_array, needle: widget, index_: &index); |
443 | |
444 | if (found) |
445 | { |
446 | /* Start at the *next* widget in the list */ |
447 | if (index < child_array->len - 1) |
448 | index ++; |
449 | } |
450 | else |
451 | { |
452 | /* Search from the start of the list */ |
453 | index = 0; |
454 | } |
455 | |
456 | for (i = index; i < child_array->len; i ++) |
457 | { |
458 | GtkWidget *child = g_ptr_array_index (child_array, i); |
459 | |
460 | if (gtk_widget_get_mapped (widget: child) && gtk_widget_is_sensitive (widget: child)) |
461 | { |
462 | new_focus = child; |
463 | break; |
464 | } |
465 | } |
466 | |
467 | |
468 | if (new_focus) |
469 | { |
470 | gtk_widget_grab_focus (widget: new_focus); |
471 | gtk_widget_activate (widget: new_focus); |
472 | } |
473 | |
474 | g_ptr_array_free (array: child_array, TRUE); |
475 | |
476 | return TRUE; |
477 | } |
478 | else |
479 | { |
480 | GtkCheckButton *active_button; |
481 | |
482 | active_button = get_group_active_button (self); |
483 | if (active_button && active_button != self) |
484 | return FALSE; |
485 | |
486 | gtk_widget_grab_focus (widget); |
487 | return TRUE; |
488 | } |
489 | } |
490 | |
491 | static void |
492 | gtk_check_button_real_activate (GtkCheckButton *self) |
493 | { |
494 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
495 | |
496 | if (priv->active && (priv->group_prev || priv->group_next)) |
497 | return; |
498 | |
499 | if (priv->action_helper) |
500 | gtk_action_helper_activate (helper: priv->action_helper); |
501 | else |
502 | gtk_check_button_set_active (self, setting: !gtk_check_button_get_active (self)); |
503 | } |
504 | |
505 | static void |
506 | gtk_check_button_class_init (GtkCheckButtonClass *class) |
507 | { |
508 | const guint activate_keyvals[] = { |
509 | GDK_KEY_space, |
510 | GDK_KEY_KP_Space, |
511 | GDK_KEY_Return, |
512 | GDK_KEY_ISO_Enter, |
513 | GDK_KEY_KP_Enter |
514 | }; |
515 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
516 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
517 | GtkShortcutAction *activate_action; |
518 | |
519 | object_class->dispose = gtk_check_button_dispose; |
520 | object_class->set_property = gtk_check_button_set_property; |
521 | object_class->get_property = gtk_check_button_get_property; |
522 | |
523 | widget_class->state_flags_changed = gtk_check_button_state_flags_changed; |
524 | widget_class->focus = gtk_check_button_focus; |
525 | |
526 | class->activate = gtk_check_button_real_activate; |
527 | |
528 | /** |
529 | * GtkCheckButton:active: (attributes org.gtk.Property.get=gtk_check_button_get_active org.gtk.Property.set=gtk_check_button_set_active) |
530 | * |
531 | * If the check button is active. |
532 | * |
533 | * Setting `active` to %TRUE will add the `:checked:` state to both |
534 | * the check button and the indicator CSS node. |
535 | */ |
536 | props[PROP_ACTIVE] = |
537 | g_param_spec_boolean (name: "active" , |
538 | P_("Active" ), |
539 | P_("If the toggle button should be pressed in" ), |
540 | FALSE, |
541 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
542 | |
543 | /** |
544 | * GtkCheckButton:group: (attributes org.gtk.Property.set=gtk_check_button_set_group) |
545 | * |
546 | * The check button whose group this widget belongs to. |
547 | */ |
548 | props[PROP_GROUP] = |
549 | g_param_spec_object (name: "group" , |
550 | P_("Group" ), |
551 | P_("The check button whose group this widget belongs to." ), |
552 | GTK_TYPE_CHECK_BUTTON, |
553 | GTK_PARAM_WRITABLE); |
554 | |
555 | /** |
556 | * GtkCheckButton:label: (attributes org.gtk.Property.get=gtk_check_button_get_label org.gtk.Property.set=gtk_check_button_set_label) |
557 | * |
558 | * Text of the label inside the check button, if it contains a label widget. |
559 | */ |
560 | props[PROP_LABEL] = |
561 | g_param_spec_string (name: "label" , |
562 | P_("Label" ), |
563 | P_("Text of the label widget inside the button, if the button contains a label widget" ), |
564 | NULL, |
565 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
566 | |
567 | /** |
568 | * GtkCheckButton:inconsistent: (attributes org.gtk.Property.get=gtk_check_button_get_inconsistent org.gtk.Property.set=gtk_check_button_set_inconsistent) |
569 | * |
570 | * If the check button is in an “in between” state. |
571 | * |
572 | * The inconsistent state only affects visual appearance, |
573 | * not the semantics of the button. |
574 | */ |
575 | props[PROP_INCONSISTENT] = |
576 | g_param_spec_boolean (name: "inconsistent" , |
577 | P_("Inconsistent" ), |
578 | P_("If the check button is in an “in between” state" ), |
579 | FALSE, |
580 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
581 | |
582 | /** |
583 | * GtkCheckButton:use-underline: (attributes org.gtk.Property.get=gtk_check_button_get_use_underline org.gtk.Property.set=gtk_check_button_set_use_underline) |
584 | * |
585 | * If set, an underline in the text indicates that the following |
586 | * character is to be used as mnemonic. |
587 | */ |
588 | props[PROP_USE_UNDERLINE] = |
589 | g_param_spec_boolean (name: "use-underline" , |
590 | P_("Use underline" ), |
591 | P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key" ), |
592 | FALSE, |
593 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
594 | |
595 | g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props); |
596 | |
597 | g_object_class_override_property (oclass: object_class, property_id: PROP_ACTION_NAME, name: "action-name" ); |
598 | g_object_class_override_property (oclass: object_class, property_id: PROP_ACTION_TARGET, name: "action-target" ); |
599 | |
600 | /** |
601 | * GtkCheckButton::toggled: |
602 | * |
603 | * Emitted when the buttons's [property@Gtk.CheckButton:active] |
604 | * property changes. |
605 | */ |
606 | signals[TOGGLED] = |
607 | g_signal_new (I_("toggled" ), |
608 | G_OBJECT_CLASS_TYPE (object_class), |
609 | signal_flags: G_SIGNAL_RUN_FIRST, |
610 | G_STRUCT_OFFSET (GtkCheckButtonClass, toggled), |
611 | NULL, NULL, |
612 | NULL, |
613 | G_TYPE_NONE, n_params: 0); |
614 | |
615 | /** |
616 | * GtkCheckButton::activate: |
617 | * @widget: the object which received the signal. |
618 | * |
619 | * Emitted to when the check button is activated. |
620 | * |
621 | * The `::activate` signal on `GtkCheckButton` is an action signal and |
622 | * emitting it causes the button to animate press then release. |
623 | * |
624 | * Applications should never connect to this signal, but use the |
625 | * [signal@Gtk.CheckButton::toggled] signal. |
626 | * |
627 | * Since: 4.2 |
628 | */ |
629 | signals[ACTIVATE] = |
630 | g_signal_new (I_ ("activate" ), |
631 | G_OBJECT_CLASS_TYPE (object_class), |
632 | signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, |
633 | G_STRUCT_OFFSET (GtkCheckButtonClass, activate), |
634 | NULL, NULL, |
635 | NULL, |
636 | G_TYPE_NONE, n_params: 0); |
637 | |
638 | gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE]); |
639 | |
640 | activate_action = gtk_signal_action_new (signal_name: "activate" ); |
641 | for (guint i = 0; i < G_N_ELEMENTS (activate_keyvals); i++) |
642 | { |
643 | GtkShortcut *activate_shortcut = gtk_shortcut_new (trigger: gtk_keyval_trigger_new (keyval: activate_keyvals[i], modifiers: 0), |
644 | g_object_ref (activate_action)); |
645 | |
646 | gtk_widget_class_add_shortcut (widget_class, shortcut: activate_shortcut); |
647 | g_object_unref (object: activate_shortcut); |
648 | } |
649 | g_object_unref (object: activate_action); |
650 | |
651 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); |
652 | gtk_widget_class_set_css_name (widget_class, I_("checkbutton" )); |
653 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_CHECKBOX); |
654 | } |
655 | |
656 | static void |
657 | gtk_check_button_init (GtkCheckButton *self) |
658 | { |
659 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
660 | GtkGesture *gesture; |
661 | |
662 | gtk_widget_set_receives_default (GTK_WIDGET (self), FALSE); |
663 | priv->indicator_widget = gtk_builtin_icon_new (css_name: "check" ); |
664 | gtk_widget_set_halign (widget: priv->indicator_widget, align: GTK_ALIGN_CENTER); |
665 | gtk_widget_set_valign (widget: priv->indicator_widget, align: GTK_ALIGN_CENTER); |
666 | gtk_widget_set_parent (widget: priv->indicator_widget, GTK_WIDGET (self)); |
667 | |
668 | update_accessible_state (check_button: self); |
669 | |
670 | gesture = gtk_gesture_click_new (); |
671 | gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE); |
672 | gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE); |
673 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY); |
674 | g_signal_connect (gesture, "pressed" , G_CALLBACK (click_pressed_cb), self); |
675 | g_signal_connect (gesture, "released" , G_CALLBACK (click_released_cb), self); |
676 | gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), phase: GTK_PHASE_CAPTURE); |
677 | gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); |
678 | |
679 | gtk_widget_set_focusable (GTK_WIDGET (self), TRUE); |
680 | } |
681 | |
682 | /** |
683 | * gtk_check_button_new: |
684 | * |
685 | * Creates a new `GtkCheckButton`. |
686 | * |
687 | * Returns: a new `GtkCheckButton` |
688 | */ |
689 | GtkWidget * |
690 | gtk_check_button_new (void) |
691 | { |
692 | return g_object_new (GTK_TYPE_CHECK_BUTTON, NULL); |
693 | } |
694 | |
695 | /** |
696 | * gtk_check_button_new_with_label: |
697 | * @label: (nullable): the text for the check button. |
698 | * |
699 | * Creates a new `GtkCheckButton` with the given text. |
700 | * |
701 | * Returns: a new `GtkCheckButton` |
702 | */ |
703 | GtkWidget* |
704 | gtk_check_button_new_with_label (const char *label) |
705 | { |
706 | return g_object_new (GTK_TYPE_CHECK_BUTTON, first_property_name: "label" , label, NULL); |
707 | } |
708 | |
709 | /** |
710 | * gtk_check_button_new_with_mnemonic: |
711 | * @label: (nullable): The text of the button, with an underscore |
712 | * in front of the mnemonic character |
713 | * |
714 | * Creates a new `GtkCheckButton` with the given text and a mnemonic. |
715 | * |
716 | * Returns: a new `GtkCheckButton` |
717 | */ |
718 | GtkWidget* |
719 | gtk_check_button_new_with_mnemonic (const char *label) |
720 | { |
721 | return g_object_new (GTK_TYPE_CHECK_BUTTON, |
722 | first_property_name: "label" , label, |
723 | "use-underline" , TRUE, |
724 | NULL); |
725 | } |
726 | |
727 | /** |
728 | * gtk_check_button_set_inconsistent: (attributes org.gtk.Method.set_property=inconsistent) |
729 | * @check_button: a `GtkCheckButton` |
730 | * @inconsistent: %TRUE if state is inconsistent |
731 | * |
732 | * Sets the `GtkCheckButton` to inconsistent state. |
733 | * |
734 | * You shoud turn off the inconsistent state again if the user checks |
735 | * the check button. This has to be done manually. |
736 | */ |
737 | void |
738 | gtk_check_button_set_inconsistent (GtkCheckButton *check_button, |
739 | gboolean inconsistent) |
740 | { |
741 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self: check_button); |
742 | |
743 | g_return_if_fail (GTK_IS_CHECK_BUTTON (check_button)); |
744 | |
745 | inconsistent = !!inconsistent; |
746 | if (priv->inconsistent != inconsistent) |
747 | { |
748 | priv->inconsistent = inconsistent; |
749 | |
750 | if (inconsistent) |
751 | { |
752 | gtk_widget_set_state_flags (GTK_WIDGET (check_button), flags: GTK_STATE_FLAG_INCONSISTENT, FALSE); |
753 | gtk_widget_set_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_INCONSISTENT, FALSE); |
754 | } |
755 | else |
756 | { |
757 | gtk_widget_unset_state_flags (GTK_WIDGET (check_button), flags: GTK_STATE_FLAG_INCONSISTENT); |
758 | gtk_widget_unset_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_INCONSISTENT); |
759 | } |
760 | |
761 | update_accessible_state (check_button); |
762 | |
763 | g_object_notify_by_pspec (G_OBJECT (check_button), pspec: props[PROP_INCONSISTENT]); |
764 | } |
765 | } |
766 | |
767 | /** |
768 | * gtk_check_button_get_inconsistent: (attributes org.gtk.Method.get_property=inconsistent) |
769 | * @check_button: a `GtkCheckButton` |
770 | * |
771 | * Returns whether the check button is in an inconsistent state. |
772 | * |
773 | * Returns: %TRUE if @check_button is currently in an inconsistent state |
774 | */ |
775 | gboolean |
776 | gtk_check_button_get_inconsistent (GtkCheckButton *check_button) |
777 | { |
778 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self: check_button); |
779 | |
780 | g_return_val_if_fail (GTK_IS_CHECK_BUTTON (check_button), FALSE); |
781 | |
782 | return priv->inconsistent; |
783 | } |
784 | |
785 | /** |
786 | * gtk_check_button_get_active: (attributes org.gtk.Method.get_property=active) |
787 | * @self: a `GtkCheckButton` |
788 | * |
789 | * Returns whether the check button is active. |
790 | * |
791 | * Returns: whether the check button is active |
792 | */ |
793 | gboolean |
794 | gtk_check_button_get_active (GtkCheckButton *self) |
795 | { |
796 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
797 | |
798 | g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), FALSE); |
799 | |
800 | return priv->active; |
801 | } |
802 | |
803 | /** |
804 | * gtk_check_button_set_active: (attributes org.gtk.Method.set_property=active) |
805 | * @self: a `GtkCheckButton` |
806 | * @setting: the new value to set |
807 | * |
808 | * Changes the check buttons active state. |
809 | */ |
810 | void |
811 | gtk_check_button_set_active (GtkCheckButton *self, |
812 | gboolean setting) |
813 | { |
814 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
815 | |
816 | g_return_if_fail (GTK_IS_CHECK_BUTTON (self)); |
817 | |
818 | setting = !!setting; |
819 | |
820 | if (setting == priv->active) |
821 | return; |
822 | |
823 | if (setting) |
824 | { |
825 | gtk_widget_set_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED, FALSE); |
826 | gtk_widget_set_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_CHECKED, FALSE); |
827 | } |
828 | else |
829 | { |
830 | gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED); |
831 | gtk_widget_unset_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_CHECKED); |
832 | } |
833 | |
834 | if (setting && (priv->group_prev || priv->group_next)) |
835 | { |
836 | GtkCheckButton *group_first = NULL; |
837 | GtkCheckButton *iter; |
838 | |
839 | group_first = get_group_first (self); |
840 | g_assert (group_first); |
841 | |
842 | /* Set all buttons in group to !active */ |
843 | for (iter = group_first; iter; iter = get_group_next (self: iter)) |
844 | gtk_check_button_set_active (self: iter, FALSE); |
845 | |
846 | /* ... and the next code block will set this one to active */ |
847 | } |
848 | |
849 | priv->active = setting; |
850 | update_accessible_state (check_button: self); |
851 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_ACTIVE]); |
852 | g_signal_emit (instance: self, signal_id: signals[TOGGLED], detail: 0); |
853 | } |
854 | |
855 | /** |
856 | * gtk_check_button_get_label: (attributes org.gtk.Method.get_property=label) |
857 | * @self: a `GtkCheckButton` |
858 | * |
859 | * Returns the label of the check button. |
860 | * |
861 | * Returns: (nullable) (transfer none): The label @self shows next |
862 | * to the indicator. If no label is shown, %NULL will be returned. |
863 | */ |
864 | const char * |
865 | gtk_check_button_get_label (GtkCheckButton *self) |
866 | { |
867 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
868 | |
869 | g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), "" ); |
870 | |
871 | if (priv->label_widget) |
872 | return gtk_label_get_label (GTK_LABEL (priv->label_widget)); |
873 | |
874 | return NULL; |
875 | } |
876 | |
877 | /** |
878 | * gtk_check_button_set_label: (attributes org.gtk.Method.set_property=label) |
879 | * @self: a `GtkCheckButton` |
880 | * @label: (nullable): The text shown next to the indicator, or %NULL |
881 | * to show no text |
882 | * |
883 | * Sets the text of @self. |
884 | * |
885 | * If [property@Gtk.CheckButton:use-underline] is %TRUE, an underscore |
886 | * in @label is interpreted as mnemonic indicator, see |
887 | * [method@Gtk.CheckButton.set_use_underline] for details on this behavior. |
888 | */ |
889 | void |
890 | gtk_check_button_set_label (GtkCheckButton *self, |
891 | const char *label) |
892 | { |
893 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
894 | |
895 | g_return_if_fail (GTK_IS_CHECK_BUTTON (self)); |
896 | |
897 | if (label == NULL || label[0] == '\0') |
898 | { |
899 | g_clear_pointer (&priv->label_widget, gtk_widget_unparent); |
900 | gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "text-button" ); |
901 | } |
902 | else |
903 | { |
904 | if (!priv->label_widget) |
905 | { |
906 | priv->label_widget = gtk_label_new (NULL); |
907 | gtk_widget_set_hexpand (widget: priv->label_widget, TRUE); |
908 | gtk_label_set_xalign (GTK_LABEL (priv->label_widget), xalign: 0.0f); |
909 | gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), setting: priv->use_underline); |
910 | gtk_widget_insert_after (widget: priv->label_widget, GTK_WIDGET (self), previous_sibling: priv->indicator_widget); |
911 | } |
912 | gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "text-button" ); |
913 | gtk_label_set_label (GTK_LABEL (priv->label_widget), str: label); |
914 | } |
915 | |
916 | gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self), |
917 | first_property: GTK_ACCESSIBLE_PROPERTY_LABEL, label, |
918 | -1); |
919 | |
920 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_LABEL]); |
921 | } |
922 | |
923 | /** |
924 | * gtk_check_button_set_group: (attributes org.gtk.Method.set_property=group) |
925 | * @self: a `GtkCheckButton` |
926 | * @group: (nullable) (transfer none): another `GtkCheckButton` to |
927 | * form a group with |
928 | * |
929 | * Adds @self to the group of @group. |
930 | * |
931 | * In a group of multiple check buttons, only one button can be active |
932 | * at a time. The behavior of a checkbutton in a group is also commonly |
933 | * known as a *radio button*. |
934 | * |
935 | * Setting the group of a check button also changes the css name of the |
936 | * indicator widget's CSS node to 'radio'. |
937 | * |
938 | * Setting up groups in a cycle leads to undefined behavior. |
939 | * |
940 | * Note that the same effect can be achieved via the [iface@Gtk.Actionable] |
941 | * API, by using the same action with parameter type and state type 's' |
942 | * for all buttons in the group, and giving each button its own target |
943 | * value. |
944 | */ |
945 | void |
946 | gtk_check_button_set_group (GtkCheckButton *self, |
947 | GtkCheckButton *group) |
948 | { |
949 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
950 | GtkCheckButtonPrivate *group_priv = gtk_check_button_get_instance_private (self: group); |
951 | |
952 | g_return_if_fail (GTK_IS_CHECK_BUTTON (self)); |
953 | g_return_if_fail (self != group); |
954 | |
955 | if (!group) |
956 | { |
957 | if (priv->group_prev) |
958 | { |
959 | GtkCheckButtonPrivate *p = gtk_check_button_get_instance_private (self: priv->group_prev); |
960 | p->group_next = priv->group_next; |
961 | } |
962 | if (priv->group_next) |
963 | { |
964 | GtkCheckButtonPrivate *p = gtk_check_button_get_instance_private (self: priv->group_next); |
965 | p->group_prev = priv->group_prev; |
966 | } |
967 | |
968 | priv->group_next = NULL; |
969 | priv->group_prev = NULL; |
970 | |
971 | if (priv->indicator_widget) |
972 | gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget), |
973 | name: g_quark_from_static_string(string: "check" )); |
974 | |
975 | return; |
976 | } |
977 | |
978 | if (priv->group_next == group) |
979 | return; |
980 | |
981 | priv->group_prev = NULL; |
982 | if (group_priv->group_prev) |
983 | { |
984 | GtkCheckButtonPrivate *prev = gtk_check_button_get_instance_private (self: group_priv->group_prev); |
985 | |
986 | prev->group_next = self; |
987 | priv->group_prev = group_priv->group_prev; |
988 | } |
989 | |
990 | group_priv->group_prev = self; |
991 | priv->group_next = group; |
992 | |
993 | if (priv->indicator_widget) |
994 | gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget), |
995 | name: g_quark_from_static_string(string: "radio" )); |
996 | |
997 | gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: group_priv->indicator_widget), |
998 | name: g_quark_from_static_string(string: "radio" )); |
999 | |
1000 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_GROUP]); |
1001 | } |
1002 | |
1003 | /** |
1004 | * gtk_check_button_get_use_underline: (attributes org.gtk.Method.get_property=use-underline) |
1005 | * @self: a `GtkCheckButton` |
1006 | * |
1007 | * Returns whether underlines in the label indicate mnemonics. |
1008 | * |
1009 | * Returns: The value of the [property@Gtk.CheckButton:use-underline] property. |
1010 | * See [method@Gtk.CheckButton.set_use_underline] for details on how to set |
1011 | * a new value. |
1012 | */ |
1013 | gboolean |
1014 | gtk_check_button_get_use_underline (GtkCheckButton *self) |
1015 | { |
1016 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
1017 | |
1018 | g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), FALSE); |
1019 | |
1020 | return priv->use_underline; |
1021 | } |
1022 | |
1023 | /** |
1024 | * gtk_check_button_set_use_underline: (attributes org.gtk.Method.set_property=use-underline) |
1025 | * @self: a `GtkCheckButton` |
1026 | * @setting: the new value to set |
1027 | * |
1028 | * Sets whether underlines in the label indicate mnemonics. |
1029 | * |
1030 | * If @setting is %TRUE, an underscore character in @self's label |
1031 | * indicates a mnemonic accelerator key. This behavior is similar |
1032 | * to [property@Gtk.Label:use-underline]. |
1033 | */ |
1034 | void |
1035 | gtk_check_button_set_use_underline (GtkCheckButton *self, |
1036 | gboolean setting) |
1037 | { |
1038 | GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self); |
1039 | |
1040 | g_return_if_fail (GTK_IS_CHECK_BUTTON (self)); |
1041 | |
1042 | setting = !!setting; |
1043 | |
1044 | if (setting == priv->use_underline) |
1045 | return; |
1046 | |
1047 | priv->use_underline = setting; |
1048 | if (priv->label_widget) |
1049 | gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), setting: priv->use_underline); |
1050 | |
1051 | g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_USE_UNDERLINE]); |
1052 | } |
1053 | |