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 "gtktogglebutton.h" |
28 | |
29 | #include "gtkaccessible.h" |
30 | #include "gtkbuttonprivate.h" |
31 | #include "gtkenums.h" |
32 | #include "gtkintl.h" |
33 | #include "gtklabel.h" |
34 | #include "gtkmain.h" |
35 | #include "gtkmarshalers.h" |
36 | #include "gtkprivate.h" |
37 | |
38 | /** |
39 | * GtkToggleButton: |
40 | * |
41 | * A `GtkToggleButton` is a button which remains “pressed-in” when |
42 | * clicked. |
43 | * |
44 | * Clicking again will cause the toggle button to return to its normal state. |
45 | * |
46 | * A toggle button is created by calling either [ctor@Gtk.ToggleButton.new] or |
47 | * [ctor@Gtk.ToggleButton.new_with_label]. If using the former, it is advisable |
48 | * to pack a widget, (such as a `GtkLabel` and/or a `GtkImage`), into the toggle |
49 | * button’s container. (See [class@Gtk.Button] for more information). |
50 | * |
51 | * The state of a `GtkToggleButton` can be set specifically using |
52 | * [method@Gtk.ToggleButton.set_active], and retrieved using |
53 | * [method@Gtk.ToggleButton.get_active]. |
54 | * |
55 | * To simply switch the state of a toggle button, use |
56 | * [method@Gtk.ToggleButton.toggled]. |
57 | * |
58 | * ## Grouping |
59 | * |
60 | * Toggle buttons can be grouped together, to form mutually exclusive |
61 | * groups - only one of the buttons can be toggled at a time, and toggling |
62 | * another one will switch the currently toggled one off. |
63 | * |
64 | * To add a `GtkToggleButton` to a group, use [method@Gtk.ToggleButton.set_group]. |
65 | * |
66 | * ## CSS nodes |
67 | * |
68 | * `GtkToggleButton` has a single CSS node with name button. To differentiate |
69 | * it from a plain `GtkButton`, it gets the `.toggle` style class. |
70 | * |
71 | * ## Creating two `GtkToggleButton` widgets. |
72 | * |
73 | * ```c |
74 | * static void |
75 | * output_state (GtkToggleButton *source, |
76 | * gpointer user_data) |
77 | * { |
78 | * g_print ("Toggle button "%s" is active: %s", |
79 | * gtk_button_get_label (GTK_BUTTON (source)), |
80 | * gtk_toggle_button_get_active (source) ? "Yes" : "No"); |
81 | * } |
82 | * |
83 | * static void |
84 | * make_toggles (void) |
85 | * { |
86 | * GtkWidget *window, *toggle1, *toggle2; |
87 | * GtkWidget *box; |
88 | * const char *text; |
89 | * |
90 | * window = gtk_window_new (); |
91 | * box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); |
92 | * |
93 | * text = "Hi, I’m toggle button one"; |
94 | * toggle1 = gtk_toggle_button_new_with_label (text); |
95 | * |
96 | * g_signal_connect (toggle1, "toggled", |
97 | * G_CALLBACK (output_state), |
98 | * NULL); |
99 | * gtk_box_append (GTK_BOX (box), toggle1); |
100 | * |
101 | * text = "Hi, I’m toggle button two"; |
102 | * toggle2 = gtk_toggle_button_new_with_label (text); |
103 | * g_signal_connect (toggle2, "toggled", |
104 | * G_CALLBACK (output_state), |
105 | * NULL); |
106 | * gtk_box_append (GTK_BOX (box), toggle2); |
107 | * |
108 | * gtk_window_set_child (GTK_WINDOW (window), box); |
109 | * gtk_widget_show (window); |
110 | * } |
111 | * ``` |
112 | */ |
113 | |
114 | typedef struct _GtkToggleButtonPrivate GtkToggleButtonPrivate; |
115 | struct _GtkToggleButtonPrivate |
116 | { |
117 | GtkToggleButton *group_next; |
118 | GtkToggleButton *group_prev; |
119 | |
120 | guint active : 1; |
121 | }; |
122 | |
123 | enum { |
124 | TOGGLED, |
125 | LAST_SIGNAL |
126 | }; |
127 | |
128 | enum { |
129 | PROP_0, |
130 | PROP_ACTIVE, |
131 | PROP_GROUP, |
132 | NUM_PROPERTIES |
133 | }; |
134 | |
135 | static guint toggle_button_signals[LAST_SIGNAL] = { 0 }; |
136 | static GParamSpec *toggle_button_props[NUM_PROPERTIES] = { NULL, }; |
137 | |
138 | G_DEFINE_TYPE_WITH_CODE (GtkToggleButton, gtk_toggle_button, GTK_TYPE_BUTTON, |
139 | G_ADD_PRIVATE (GtkToggleButton)) |
140 | |
141 | static void |
142 | gtk_toggle_button_set_property (GObject *object, |
143 | guint prop_id, |
144 | const GValue *value, |
145 | GParamSpec *pspec) |
146 | { |
147 | GtkToggleButton *tb = GTK_TOGGLE_BUTTON (object); |
148 | |
149 | switch (prop_id) |
150 | { |
151 | case PROP_ACTIVE: |
152 | gtk_toggle_button_set_active (toggle_button: tb, is_active: g_value_get_boolean (value)); |
153 | break; |
154 | case PROP_GROUP: |
155 | gtk_toggle_button_set_group (GTK_TOGGLE_BUTTON (object), group: g_value_get_object (value)); |
156 | break; |
157 | default: |
158 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
159 | break; |
160 | } |
161 | } |
162 | |
163 | static void |
164 | gtk_toggle_button_get_property (GObject *object, |
165 | guint prop_id, |
166 | GValue *value, |
167 | GParamSpec *pspec) |
168 | { |
169 | GtkToggleButton *tb = GTK_TOGGLE_BUTTON (object); |
170 | GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: tb); |
171 | |
172 | switch (prop_id) |
173 | { |
174 | case PROP_ACTIVE: |
175 | g_value_set_boolean (value, v_boolean: priv->active); |
176 | break; |
177 | default: |
178 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
179 | break; |
180 | } |
181 | } |
182 | |
183 | static gboolean |
184 | gtk_toggle_button_mnemonic_activate (GtkWidget *widget, |
185 | gboolean group_cycling) |
186 | { |
187 | /* |
188 | * We override the standard implementation in |
189 | * gtk_widget_real_mnemonic_activate() in order to focus the widget even |
190 | * if there is no mnemonic conflict. |
191 | */ |
192 | if (gtk_widget_get_focusable (widget)) |
193 | gtk_widget_grab_focus (widget); |
194 | |
195 | if (!group_cycling) |
196 | gtk_widget_activate (widget); |
197 | |
198 | return TRUE; |
199 | } |
200 | |
201 | static void |
202 | gtk_toggle_button_clicked (GtkButton *button) |
203 | { |
204 | GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button); |
205 | GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button); |
206 | |
207 | if (priv->active && (priv->group_prev || priv->group_next)) |
208 | return; |
209 | |
210 | if (gtk_button_get_action_helper (button)) |
211 | return; |
212 | |
213 | gtk_toggle_button_set_active (toggle_button, is_active: !priv->active); |
214 | } |
215 | |
216 | static void |
217 | gtk_toggle_button_dispose (GObject *object) |
218 | { |
219 | GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (object); |
220 | |
221 | gtk_toggle_button_set_group (toggle_button, NULL); |
222 | |
223 | G_OBJECT_CLASS (gtk_toggle_button_parent_class)->dispose (object); |
224 | } |
225 | |
226 | static GtkToggleButton * |
227 | get_group_next (GtkToggleButton *self) |
228 | { |
229 | return ((GtkToggleButtonPrivate *)gtk_toggle_button_get_instance_private (self))->group_next; |
230 | } |
231 | |
232 | static GtkToggleButton * |
233 | get_group_prev (GtkToggleButton *self) |
234 | { |
235 | return ((GtkToggleButtonPrivate *)gtk_toggle_button_get_instance_private (self))->group_prev; |
236 | } |
237 | |
238 | static GtkToggleButton * |
239 | get_group_first (GtkToggleButton *self) |
240 | { |
241 | GtkToggleButton *group_first = NULL; |
242 | GtkToggleButton *iter; |
243 | |
244 | /* Find first in group */ |
245 | iter = self; |
246 | while (iter) |
247 | { |
248 | group_first = iter; |
249 | |
250 | iter = get_group_prev (self: iter); |
251 | if (!iter) |
252 | break; |
253 | } |
254 | |
255 | g_assert (group_first); |
256 | |
257 | return group_first; |
258 | } |
259 | |
260 | static void |
261 | gtk_toggle_button_class_init (GtkToggleButtonClass *class) |
262 | { |
263 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
264 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
265 | GtkButtonClass *button_class = GTK_BUTTON_CLASS (class); |
266 | |
267 | gobject_class->dispose = gtk_toggle_button_dispose; |
268 | gobject_class->set_property = gtk_toggle_button_set_property; |
269 | gobject_class->get_property = gtk_toggle_button_get_property; |
270 | |
271 | widget_class->mnemonic_activate = gtk_toggle_button_mnemonic_activate; |
272 | |
273 | button_class->clicked = gtk_toggle_button_clicked; |
274 | |
275 | class->toggled = NULL; |
276 | |
277 | /** |
278 | * GtkToggleButton:active: (attributes org.gtk.Property.get=gtk_toggle_button_get_active org.gtk.Property.set=gtk_toggle_button_set_active) |
279 | * |
280 | * If the toggle button should be pressed in. |
281 | */ |
282 | toggle_button_props[PROP_ACTIVE] = |
283 | g_param_spec_boolean (name: "active" , |
284 | P_("Active" ), |
285 | P_("If the toggle button should be pressed in" ), |
286 | FALSE, |
287 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); |
288 | |
289 | /** |
290 | * GtkToggleButton:group: (attributes org.gtk.Property.set=gtk_toggle_button_set_group) |
291 | * |
292 | * The toggle button whose group this widget belongs to. |
293 | */ |
294 | toggle_button_props[PROP_GROUP] = |
295 | g_param_spec_object (name: "group" , |
296 | P_("Group" ), |
297 | P_("The toggle button whose group this widget belongs to." ), |
298 | GTK_TYPE_TOGGLE_BUTTON, |
299 | GTK_PARAM_WRITABLE); |
300 | |
301 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: toggle_button_props); |
302 | |
303 | /** |
304 | * GtkToggleButton::toggled: |
305 | * @togglebutton: the object which received the signal. |
306 | * |
307 | * Emitted whenever the `GtkToggleButton`'s state is changed. |
308 | */ |
309 | toggle_button_signals[TOGGLED] = |
310 | g_signal_new (I_("toggled" ), |
311 | G_OBJECT_CLASS_TYPE (gobject_class), |
312 | signal_flags: G_SIGNAL_RUN_FIRST, |
313 | G_STRUCT_OFFSET (GtkToggleButtonClass, toggled), |
314 | NULL, NULL, |
315 | NULL, |
316 | G_TYPE_NONE, n_params: 0); |
317 | |
318 | gtk_widget_class_set_css_name (widget_class, I_("button" )); |
319 | } |
320 | |
321 | static void |
322 | gtk_toggle_button_init (GtkToggleButton *toggle_button) |
323 | { |
324 | GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button); |
325 | |
326 | priv->active = FALSE; |
327 | |
328 | gtk_widget_add_css_class (GTK_WIDGET (toggle_button), css_class: "toggle" ); |
329 | } |
330 | |
331 | |
332 | /** |
333 | * gtk_toggle_button_new: |
334 | * |
335 | * Creates a new toggle button. |
336 | * |
337 | * A widget should be packed into the button, as in [ctor@Gtk.Button.new]. |
338 | * |
339 | * Returns: a new toggle button. |
340 | */ |
341 | GtkWidget * |
342 | gtk_toggle_button_new (void) |
343 | { |
344 | return g_object_new (GTK_TYPE_TOGGLE_BUTTON, NULL); |
345 | } |
346 | |
347 | /** |
348 | * gtk_toggle_button_new_with_label: |
349 | * @label: a string containing the message to be placed in the toggle button. |
350 | * |
351 | * Creates a new toggle button with a text label. |
352 | * |
353 | * Returns: a new toggle button. |
354 | */ |
355 | GtkWidget * |
356 | gtk_toggle_button_new_with_label (const char *label) |
357 | { |
358 | return g_object_new (GTK_TYPE_TOGGLE_BUTTON, first_property_name: "label" , label, NULL); |
359 | } |
360 | |
361 | /** |
362 | * gtk_toggle_button_new_with_mnemonic: |
363 | * @label: the text of the button, with an underscore in front of the |
364 | * mnemonic character |
365 | * |
366 | * Creates a new `GtkToggleButton` containing a label. |
367 | * |
368 | * The label will be created using [ctor@Gtk.Label.new_with_mnemonic], |
369 | * so underscores in @label indicate the mnemonic for the button. |
370 | * |
371 | * Returns: a new `GtkToggleButton` |
372 | */ |
373 | GtkWidget * |
374 | gtk_toggle_button_new_with_mnemonic (const char *label) |
375 | { |
376 | return g_object_new (GTK_TYPE_TOGGLE_BUTTON, |
377 | first_property_name: "label" , label, |
378 | "use-underline" , TRUE, |
379 | NULL); |
380 | } |
381 | |
382 | /** |
383 | * gtk_toggle_button_set_active: (attributes org.gtk.Method.set_property=active) |
384 | * @toggle_button: a `GtkToggleButton`. |
385 | * @is_active: %TRUE or %FALSE. |
386 | * |
387 | * Sets the status of the toggle button. |
388 | * |
389 | * Set to %TRUE if you want the `GtkToggleButton` to be “pressed in”, |
390 | * and %FALSE to raise it. |
391 | * |
392 | * If the status of the button changes, this action causes the |
393 | * [signal@GtkToggleButton::toggled] signal to be emitted. |
394 | */ |
395 | void |
396 | gtk_toggle_button_set_active (GtkToggleButton *toggle_button, |
397 | gboolean is_active) |
398 | { |
399 | GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button); |
400 | |
401 | g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button)); |
402 | |
403 | is_active = is_active != FALSE; |
404 | |
405 | if (priv->active == is_active) |
406 | return; |
407 | |
408 | if (is_active && (priv->group_prev || priv->group_next)) |
409 | { |
410 | GtkToggleButton *group_first = NULL; |
411 | GtkToggleButton *iter; |
412 | |
413 | group_first = get_group_first (self: toggle_button); |
414 | g_assert (group_first); |
415 | |
416 | /* Set all buttons in group to !active */ |
417 | for (iter = group_first; iter; iter = get_group_next (self: iter)) |
418 | gtk_toggle_button_set_active (toggle_button: iter, FALSE); |
419 | |
420 | /* ... and the next code block will set this one to active */ |
421 | } |
422 | |
423 | priv->active = is_active; |
424 | |
425 | if (is_active) |
426 | gtk_widget_set_state_flags (GTK_WIDGET (toggle_button), flags: GTK_STATE_FLAG_CHECKED, FALSE); |
427 | else |
428 | gtk_widget_unset_state_flags (GTK_WIDGET (toggle_button), flags: GTK_STATE_FLAG_CHECKED); |
429 | |
430 | gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: toggle_button), |
431 | first_state: GTK_ACCESSIBLE_STATE_PRESSED, is_active, |
432 | -1); |
433 | |
434 | gtk_toggle_button_toggled (toggle_button); |
435 | |
436 | g_object_notify_by_pspec (G_OBJECT (toggle_button), pspec: toggle_button_props[PROP_ACTIVE]); |
437 | } |
438 | |
439 | /** |
440 | * gtk_toggle_button_get_active: (attributes org.gtk.Method.get_property=active) |
441 | * @toggle_button: a `GtkToggleButton`. |
442 | * |
443 | * Queries a `GtkToggleButton` and returns its current state. |
444 | * |
445 | * Returns %TRUE if the toggle button is pressed in and %FALSE |
446 | * if it is raised. |
447 | * |
448 | * Returns: whether the button is pressed |
449 | */ |
450 | gboolean |
451 | gtk_toggle_button_get_active (GtkToggleButton *toggle_button) |
452 | { |
453 | GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button); |
454 | |
455 | g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE); |
456 | |
457 | return priv->active; |
458 | } |
459 | |
460 | /** |
461 | * gtk_toggle_button_toggled: |
462 | * @toggle_button: a `GtkToggleButton`. |
463 | * |
464 | * Emits the ::toggled signal on the `GtkToggleButton`. |
465 | * |
466 | * There is no good reason for an application ever to call this function. |
467 | */ |
468 | void |
469 | gtk_toggle_button_toggled (GtkToggleButton *toggle_button) |
470 | { |
471 | g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button)); |
472 | |
473 | g_signal_emit (instance: toggle_button, signal_id: toggle_button_signals[TOGGLED], detail: 0); |
474 | } |
475 | |
476 | /** |
477 | * gtk_toggle_button_set_group: (attributes org.gtk.Method.set_property=group) |
478 | * @toggle_button: a `GtkToggleButton` |
479 | * @group: (nullable) (transfer none): another `GtkToggleButton` to |
480 | * form a group with |
481 | * |
482 | * Adds @self to the group of @group. |
483 | * |
484 | * In a group of multiple toggle buttons, only one button can be active |
485 | * at a time. |
486 | * |
487 | * Setting up groups in a cycle leads to undefined behavior. |
488 | * |
489 | * Note that the same effect can be achieved via the [iface@Gtk.Actionable] |
490 | * API, by using the same action with parameter type and state type 's' |
491 | * for all buttons in the group, and giving each button its own target |
492 | * value. |
493 | */ |
494 | void |
495 | gtk_toggle_button_set_group (GtkToggleButton *toggle_button, |
496 | GtkToggleButton *group) |
497 | { |
498 | GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button); |
499 | GtkToggleButtonPrivate *group_priv = gtk_toggle_button_get_instance_private (self: group); |
500 | |
501 | g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button)); |
502 | g_return_if_fail (toggle_button != group); |
503 | |
504 | if (!group) |
505 | { |
506 | if (priv->group_prev) |
507 | { |
508 | GtkToggleButtonPrivate *p = gtk_toggle_button_get_instance_private (self: priv->group_prev); |
509 | p->group_next = priv->group_next; |
510 | } |
511 | if (priv->group_next) |
512 | { |
513 | GtkToggleButtonPrivate *p = gtk_toggle_button_get_instance_private (self: priv->group_next); |
514 | p->group_prev = priv->group_prev; |
515 | } |
516 | |
517 | priv->group_next = NULL; |
518 | priv->group_prev = NULL; |
519 | g_object_notify_by_pspec (G_OBJECT (toggle_button), pspec: toggle_button_props[PROP_GROUP]); |
520 | return; |
521 | } |
522 | |
523 | if (priv->group_next == group) |
524 | return; |
525 | |
526 | priv->group_prev = NULL; |
527 | if (group_priv->group_prev) |
528 | { |
529 | GtkToggleButtonPrivate *prev = gtk_toggle_button_get_instance_private (self: group_priv->group_prev); |
530 | |
531 | prev->group_next = toggle_button; |
532 | priv->group_prev = group_priv->group_prev; |
533 | } |
534 | |
535 | group_priv->group_prev = toggle_button; |
536 | priv->group_next = group; |
537 | |
538 | g_object_notify_by_pspec (G_OBJECT (toggle_button), pspec: toggle_button_props[PROP_GROUP]); |
539 | } |
540 | |