1/* gtkcellrenderertoggle.c
2 * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library 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 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtkcellrenderertoggle.h"
21
22#include "gtkcssnumbervalueprivate.h"
23#include "gtkcsstransientnodeprivate.h"
24#include "gtkintl.h"
25#include "gtkmarshalers.h"
26#include "gtkprivate.h"
27#include "gtkrendericonprivate.h"
28#include "gtksnapshot.h"
29#include "gtkstylecontextprivate.h"
30#include "gtkwidgetprivate.h"
31#include "gtktreeprivate.h"
32
33#include <stdlib.h>
34
35/**
36 * GtkCellRendererToggle:
37 *
38 * Renders a toggle button in a cell
39 *
40 * `GtkCellRendererToggle` renders a toggle button in a cell. The
41 * button is drawn as a radio or a checkbutton, depending on the
42 * `GtkCellRendererToggle:radio` property.
43 * When activated, it emits the `GtkCellRendererToggle::toggled` signal.
44 */
45
46
47static void gtk_cell_renderer_toggle_get_property (GObject *object,
48 guint param_id,
49 GValue *value,
50 GParamSpec *pspec);
51static void gtk_cell_renderer_toggle_set_property (GObject *object,
52 guint param_id,
53 const GValue *value,
54 GParamSpec *pspec);
55static void gtk_cell_renderer_toggle_get_size (GtkCellRendererToggle *self,
56 GtkWidget *widget,
57 const GdkRectangle *cell_area,
58 int *x_offset,
59 int *y_offset,
60 int *width,
61 int *height);
62static void gtk_cell_renderer_toggle_snapshot (GtkCellRenderer *cell,
63 GtkSnapshot *snapshot,
64 GtkWidget *widget,
65 const GdkRectangle *background_area,
66 const GdkRectangle *cell_area,
67 GtkCellRendererState flags);
68static gboolean gtk_cell_renderer_toggle_activate (GtkCellRenderer *cell,
69 GdkEvent *event,
70 GtkWidget *widget,
71 const char *path,
72 const GdkRectangle *background_area,
73 const GdkRectangle *cell_area,
74 GtkCellRendererState flags);
75
76
77enum {
78 TOGGLED,
79 LAST_SIGNAL
80};
81
82enum {
83 PROP_0,
84 PROP_ACTIVATABLE,
85 PROP_ACTIVE,
86 PROP_RADIO,
87 PROP_INCONSISTENT
88};
89
90static guint toggle_cell_signals[LAST_SIGNAL] = { 0 };
91
92typedef struct _GtkCellRendererTogglePrivate GtkCellRendererTogglePrivate;
93typedef struct _GtkCellRendererToggleClass GtkCellRendererToggleClass;
94
95struct _GtkCellRendererToggle
96{
97 GtkCellRenderer parent;
98};
99
100struct _GtkCellRendererToggleClass
101{
102 GtkCellRendererClass parent_class;
103
104 void (* toggled) (GtkCellRendererToggle *cell,
105 const char *path);
106};
107
108struct _GtkCellRendererTogglePrivate
109{
110 guint active : 1;
111 guint activatable : 1;
112 guint inconsistent : 1;
113 guint radio : 1;
114 GtkCssNode *cssnode;
115};
116
117
118G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererToggle, gtk_cell_renderer_toggle, GTK_TYPE_CELL_RENDERER)
119
120
121static void
122gtk_cell_renderer_toggle_init (GtkCellRendererToggle *celltoggle)
123{
124 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: celltoggle);
125
126 priv->activatable = TRUE;
127 priv->active = FALSE;
128 priv->radio = FALSE;
129
130 g_object_set (object: celltoggle, first_property_name: "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
131 gtk_cell_renderer_set_padding (GTK_CELL_RENDERER (celltoggle), xpad: 2, ypad: 2);
132
133 priv->inconsistent = FALSE;
134}
135
136static GtkSizeRequestMode
137gtk_cell_renderer_toggle_get_request_mode (GtkCellRenderer *cell)
138{
139 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
140}
141
142static void
143gtk_cell_renderer_toggle_get_preferred_width (GtkCellRenderer *cell,
144 GtkWidget *widget,
145 int *minimum,
146 int *natural)
147{
148 int width = 0;
149
150 gtk_cell_renderer_toggle_get_size (GTK_CELL_RENDERER_TOGGLE (cell), widget,
151 NULL,
152 NULL, NULL, width: &width, NULL);
153
154 if (minimum)
155 *minimum = width;
156 if (natural)
157 *natural = width;
158}
159
160static void
161gtk_cell_renderer_toggle_get_preferred_height (GtkCellRenderer *cell,
162 GtkWidget *widget,
163 int *minimum,
164 int *natural)
165{
166 int height = 0;
167
168 gtk_cell_renderer_toggle_get_size (GTK_CELL_RENDERER_TOGGLE (cell), widget,
169 NULL,
170 NULL, NULL, NULL, height: &height);
171
172 if (minimum)
173 *minimum = height;
174 if (natural)
175 *natural = height;
176}
177
178static void
179gtk_cell_renderer_toggle_class_init (GtkCellRendererToggleClass *class)
180{
181 GObjectClass *object_class = G_OBJECT_CLASS (class);
182 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
183
184 object_class->get_property = gtk_cell_renderer_toggle_get_property;
185 object_class->set_property = gtk_cell_renderer_toggle_set_property;
186
187 cell_class->get_request_mode = gtk_cell_renderer_toggle_get_request_mode;
188 cell_class->get_preferred_width = gtk_cell_renderer_toggle_get_preferred_width;
189 cell_class->get_preferred_height = gtk_cell_renderer_toggle_get_preferred_height;
190 cell_class->snapshot = gtk_cell_renderer_toggle_snapshot;
191 cell_class->activate = gtk_cell_renderer_toggle_activate;
192
193 g_object_class_install_property (oclass: object_class,
194 property_id: PROP_ACTIVE,
195 pspec: g_param_spec_boolean (name: "active",
196 P_("Toggle state"),
197 P_("The toggle state of the button"),
198 FALSE,
199 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
200
201 g_object_class_install_property (oclass: object_class,
202 property_id: PROP_INCONSISTENT,
203 pspec: g_param_spec_boolean (name: "inconsistent",
204 P_("Inconsistent state"),
205 P_("The inconsistent state of the button"),
206 FALSE,
207 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
208
209 g_object_class_install_property (oclass: object_class,
210 property_id: PROP_ACTIVATABLE,
211 pspec: g_param_spec_boolean (name: "activatable",
212 P_("Activatable"),
213 P_("The toggle button can be activated"),
214 TRUE,
215 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
216
217 g_object_class_install_property (oclass: object_class,
218 property_id: PROP_RADIO,
219 pspec: g_param_spec_boolean (name: "radio",
220 P_("Radio state"),
221 P_("Draw the toggle button as a radio button"),
222 FALSE,
223 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
224
225
226 /**
227 * GtkCellRendererToggle::toggled:
228 * @cell_renderer: the object which received the signal
229 * @path: string representation of `GtkTreePath` describing the
230 * event location
231 *
232 * The ::toggled signal is emitted when the cell is toggled.
233 *
234 * It is the responsibility of the application to update the model
235 * with the correct value to store at @path. Often this is simply the
236 * opposite of the value currently stored at @path.
237 **/
238 toggle_cell_signals[TOGGLED] =
239 g_signal_new (I_("toggled"),
240 G_OBJECT_CLASS_TYPE (object_class),
241 signal_flags: G_SIGNAL_RUN_LAST,
242 G_STRUCT_OFFSET (GtkCellRendererToggleClass, toggled),
243 NULL, NULL,
244 NULL,
245 G_TYPE_NONE, n_params: 1,
246 G_TYPE_STRING);
247}
248
249static void
250gtk_cell_renderer_toggle_get_property (GObject *object,
251 guint param_id,
252 GValue *value,
253 GParamSpec *pspec)
254{
255 GtkCellRendererToggle *celltoggle = GTK_CELL_RENDERER_TOGGLE (object);
256 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: celltoggle);
257
258 switch (param_id)
259 {
260 case PROP_ACTIVE:
261 g_value_set_boolean (value, v_boolean: priv->active);
262 break;
263 case PROP_INCONSISTENT:
264 g_value_set_boolean (value, v_boolean: priv->inconsistent);
265 break;
266 case PROP_ACTIVATABLE:
267 g_value_set_boolean (value, v_boolean: priv->activatable);
268 break;
269 case PROP_RADIO:
270 g_value_set_boolean (value, v_boolean: priv->radio);
271 break;
272 default:
273 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
274 break;
275 }
276}
277
278
279static void
280gtk_cell_renderer_toggle_set_property (GObject *object,
281 guint param_id,
282 const GValue *value,
283 GParamSpec *pspec)
284{
285 GtkCellRendererToggle *celltoggle = GTK_CELL_RENDERER_TOGGLE (object);
286 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: celltoggle);
287
288 switch (param_id)
289 {
290 case PROP_ACTIVE:
291 if (priv->active != g_value_get_boolean (value))
292 {
293 priv->active = g_value_get_boolean (value);
294 g_object_notify_by_pspec (object, pspec);
295 }
296 break;
297 case PROP_INCONSISTENT:
298 if (priv->inconsistent != g_value_get_boolean (value))
299 {
300 priv->inconsistent = g_value_get_boolean (value);
301 g_object_notify_by_pspec (object, pspec);
302 }
303 break;
304 case PROP_ACTIVATABLE:
305 if (priv->activatable != g_value_get_boolean (value))
306 {
307 priv->activatable = g_value_get_boolean (value);
308 g_object_notify_by_pspec (object, pspec);
309 }
310 break;
311 case PROP_RADIO:
312 if (priv->radio != g_value_get_boolean (value))
313 {
314 gtk_cell_renderer_toggle_set_radio (toggle: celltoggle, radio: g_value_get_boolean (value));
315 g_object_notify_by_pspec (object, pspec);
316 }
317 break;
318 default:
319 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
320 break;
321 }
322}
323
324/**
325 * gtk_cell_renderer_toggle_new:
326 *
327 * Creates a new `GtkCellRendererToggle`. Adjust rendering
328 * parameters using object properties. Object properties can be set
329 * globally (with g_object_set()). Also, with `GtkTreeViewColumn`, you
330 * can bind a property to a value in a `GtkTreeModel`. For example, you
331 * can bind the “active” property on the cell renderer to a boolean value
332 * in the model, thus causing the check button to reflect the state of
333 * the model.
334 *
335 * Returns: the new cell renderer
336 **/
337GtkCellRenderer *
338gtk_cell_renderer_toggle_new (void)
339{
340 return g_object_new (GTK_TYPE_CELL_RENDERER_TOGGLE, NULL);
341}
342
343static GtkStyleContext *
344gtk_cell_renderer_toggle_save_context (GtkCellRendererToggle *cell,
345 GtkWidget *widget)
346{
347 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: cell);
348 GtkStyleContext *context;
349 GtkCssNode *cssnode;
350
351 context = gtk_widget_get_style_context (widget);
352
353 cssnode = gtk_css_transient_node_new (parent: gtk_widget_get_css_node (widget));
354 if (priv->radio)
355 gtk_css_node_set_name (cssnode, name: g_quark_from_static_string (string: "radio"));
356 else
357 gtk_css_node_set_name (cssnode, name: g_quark_from_static_string (string: "check"));
358 gtk_style_context_save_to_node (context, node: cssnode);
359 g_object_unref (object: cssnode);
360
361 return context;
362}
363
364static void
365gtk_cell_renderer_toggle_restore_context (GtkCellRendererToggle *cell,
366 GtkStyleContext *context)
367{
368 gtk_style_context_restore (context);
369}
370
371static int
372calc_indicator_size (GtkStyleContext *context)
373{
374 GtkCssStyle *style = gtk_style_context_lookup_style (context);
375 return _gtk_css_number_value_get (number: style->icon->icon_size, one_hundred_percent: 100);
376}
377
378static void
379gtk_cell_renderer_toggle_get_size (GtkCellRendererToggle *self,
380 GtkWidget *widget,
381 const GdkRectangle *cell_area,
382 int *x_offset,
383 int *y_offset,
384 int *width,
385 int *height)
386{
387 GtkCellRenderer *cell = GTK_CELL_RENDERER (self);
388 int calc_width;
389 int calc_height;
390 int xpad, ypad;
391 GtkStyleContext *context;
392 GtkBorder border, padding;
393
394 gtk_cell_renderer_get_padding (cell, xpad: &xpad, ypad: &ypad);
395
396 context = gtk_cell_renderer_toggle_save_context (cell: self, widget);
397 gtk_style_context_get_padding (context, padding: &padding);
398 gtk_style_context_get_border (context, border: &border);
399
400 calc_width = calc_height = calc_indicator_size (context);
401 calc_width += xpad * 2 + padding.left + padding.right + border.left + border.right;
402 calc_height += ypad * 2 + padding.top + padding.bottom + border.top + border.bottom;
403
404 gtk_cell_renderer_toggle_restore_context (cell: self, context);
405
406 if (width)
407 *width = calc_width;
408
409 if (height)
410 *height = calc_height;
411
412 if (cell_area)
413 {
414 float xalign, yalign;
415
416 gtk_cell_renderer_get_alignment (cell, xalign: &xalign, yalign: &yalign);
417
418 if (x_offset)
419 {
420 *x_offset = ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ?
421 (1.0 - xalign) : xalign) * (cell_area->width - calc_width);
422 *x_offset = MAX (*x_offset, 0);
423 }
424 if (y_offset)
425 {
426 *y_offset = yalign * (cell_area->height - calc_height);
427 *y_offset = MAX (*y_offset, 0);
428 }
429 }
430 else
431 {
432 if (x_offset)
433 *x_offset = 0;
434 if (y_offset)
435 *y_offset = 0;
436 }
437}
438
439static void
440gtk_cell_renderer_toggle_snapshot (GtkCellRenderer *cell,
441 GtkSnapshot *snapshot,
442 GtkWidget *widget,
443 const GdkRectangle *background_area,
444 const GdkRectangle *cell_area,
445 GtkCellRendererState flags)
446{
447 GtkCellRendererToggle *celltoggle = GTK_CELL_RENDERER_TOGGLE (cell);
448 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: celltoggle);
449 GtkStyleContext *context;
450 int width, height;
451 int x_offset, y_offset;
452 int xpad, ypad;
453 GtkStateFlags state;
454 GtkBorder padding, border;
455
456 gtk_cell_renderer_toggle_get_size (self: celltoggle, widget, cell_area,
457 x_offset: &x_offset, y_offset: &y_offset,
458 width: &width, height: &height);
459 gtk_cell_renderer_get_padding (cell, xpad: &xpad, ypad: &ypad);
460 width -= xpad * 2;
461 height -= ypad * 2;
462
463 if (width <= 0 || height <= 0)
464 return;
465
466 state = gtk_cell_renderer_get_state (cell, widget, cell_state: flags);
467
468 if (!priv->activatable)
469 state |= GTK_STATE_FLAG_INSENSITIVE;
470
471 state &= ~(GTK_STATE_FLAG_INCONSISTENT | GTK_STATE_FLAG_CHECKED);
472
473 if (priv->inconsistent)
474 state |= GTK_STATE_FLAG_INCONSISTENT;
475
476 if (priv->active)
477 state |= GTK_STATE_FLAG_CHECKED;
478
479 gtk_snapshot_push_clip (snapshot,
480 bounds: &GRAPHENE_RECT_INIT (
481 cell_area->x, cell_area->y,
482 cell_area->width, cell_area->height
483 ));
484
485 context = gtk_cell_renderer_toggle_save_context (cell: celltoggle, widget);
486 gtk_style_context_set_state (context, flags: state);
487
488 gtk_snapshot_render_background (snapshot, context,
489 x: cell_area->x + x_offset + xpad,
490 y: cell_area->y + y_offset + ypad,
491 width, height);
492 gtk_snapshot_render_frame (snapshot, context,
493 x: cell_area->x + x_offset + xpad,
494 y: cell_area->y + y_offset + ypad,
495 width, height);
496
497 gtk_style_context_get_padding (context, padding: &padding);
498 gtk_style_context_get_border (context, border: &border);
499
500 gtk_snapshot_translate (snapshot,
501 point: &GRAPHENE_POINT_INIT (cell_area->x + x_offset + xpad + padding.left + border.left,
502 cell_area->y + y_offset + ypad + padding.top + border.top));
503 gtk_css_style_snapshot_icon (style: gtk_style_context_lookup_style (context), snapshot,
504 width: width - padding.left - padding.right - border.left - border.right,
505 height: height - padding.top - padding.bottom - border.top - border.bottom);
506
507 gtk_cell_renderer_toggle_restore_context (cell: celltoggle, context);
508 gtk_snapshot_pop (snapshot);
509}
510
511static int
512gtk_cell_renderer_toggle_activate (GtkCellRenderer *cell,
513 GdkEvent *event,
514 GtkWidget *widget,
515 const char *path,
516 const GdkRectangle *background_area,
517 const GdkRectangle *cell_area,
518 GtkCellRendererState flags)
519{
520 GtkCellRendererToggle *celltoggle = GTK_CELL_RENDERER_TOGGLE (cell);
521 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: celltoggle);
522
523 if (priv->activatable)
524 {
525 g_signal_emit (instance: cell, signal_id: toggle_cell_signals[TOGGLED], detail: 0, path);
526 return TRUE;
527 }
528
529 return FALSE;
530}
531
532/**
533 * gtk_cell_renderer_toggle_set_radio:
534 * @toggle: a `GtkCellRendererToggle`
535 * @radio: %TRUE to make the toggle look like a radio button
536 *
537 * If @radio is %TRUE, the cell renderer renders a radio toggle
538 * (i.e. a toggle in a group of mutually-exclusive toggles).
539 * If %FALSE, it renders a check toggle (a standalone boolean option).
540 * This can be set globally for the cell renderer, or changed just
541 * before rendering each cell in the model (for `GtkTreeView`, you set
542 * up a per-row setting using `GtkTreeViewColumn` to associate model
543 * columns with cell renderer properties).
544 **/
545void
546gtk_cell_renderer_toggle_set_radio (GtkCellRendererToggle *toggle,
547 gboolean radio)
548{
549 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: toggle);
550
551 g_return_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (toggle));
552
553 priv->radio = radio;
554}
555
556/**
557 * gtk_cell_renderer_toggle_get_radio:
558 * @toggle: a `GtkCellRendererToggle`
559 *
560 * Returns whether we’re rendering radio toggles rather than checkboxes.
561 *
562 * Returns: %TRUE if we’re rendering radio toggles rather than checkboxes
563 **/
564gboolean
565gtk_cell_renderer_toggle_get_radio (GtkCellRendererToggle *toggle)
566{
567 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: toggle);
568
569 g_return_val_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (toggle), FALSE);
570
571 return priv->radio;
572}
573
574/**
575 * gtk_cell_renderer_toggle_get_active:
576 * @toggle: a `GtkCellRendererToggle`
577 *
578 * Returns whether the cell renderer is active. See
579 * gtk_cell_renderer_toggle_set_active().
580 *
581 * Returns: %TRUE if the cell renderer is active.
582 **/
583gboolean
584gtk_cell_renderer_toggle_get_active (GtkCellRendererToggle *toggle)
585{
586 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: toggle);
587
588 g_return_val_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (toggle), FALSE);
589
590 return priv->active;
591}
592
593/**
594 * gtk_cell_renderer_toggle_set_active:
595 * @toggle: a `GtkCellRendererToggle`.
596 * @setting: the value to set.
597 *
598 * Activates or deactivates a cell renderer.
599 **/
600void
601gtk_cell_renderer_toggle_set_active (GtkCellRendererToggle *toggle,
602 gboolean setting)
603{
604 g_return_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (toggle));
605
606 g_object_set (object: toggle, first_property_name: "active", setting ? TRUE : FALSE, NULL);
607}
608
609/**
610 * gtk_cell_renderer_toggle_get_activatable:
611 * @toggle: a `GtkCellRendererToggle`
612 *
613 * Returns whether the cell renderer is activatable. See
614 * gtk_cell_renderer_toggle_set_activatable().
615 *
616 * Returns: %TRUE if the cell renderer is activatable.
617 **/
618gboolean
619gtk_cell_renderer_toggle_get_activatable (GtkCellRendererToggle *toggle)
620{
621 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: toggle);
622
623 g_return_val_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (toggle), FALSE);
624
625 return priv->activatable;
626}
627
628/**
629 * gtk_cell_renderer_toggle_set_activatable:
630 * @toggle: a `GtkCellRendererToggle`.
631 * @setting: the value to set.
632 *
633 * Makes the cell renderer activatable.
634 **/
635void
636gtk_cell_renderer_toggle_set_activatable (GtkCellRendererToggle *toggle,
637 gboolean setting)
638{
639 GtkCellRendererTogglePrivate *priv = gtk_cell_renderer_toggle_get_instance_private (self: toggle);
640
641 g_return_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (toggle));
642
643 if (priv->activatable != setting)
644 {
645 priv->activatable = setting ? TRUE : FALSE;
646 g_object_notify (G_OBJECT (toggle), property_name: "activatable");
647 }
648}
649

source code of gtk/gtk/gtkcellrenderertoggle.c