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