1/* gtkellview.c
2 * Copyright (C) 2002, 2003 Kristian Rietveld <kris@gtk.org>
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 "gtkcellview.h"
21
22#include "gtkbuildable.h"
23#include "gtkcelllayout.h"
24#include "gtkcellareabox.h"
25#include "gtkcellrendererpixbuf.h"
26#include "gtkcellrenderertext.h"
27#include "gtkintl.h"
28#include "gtkorientable.h"
29#include "gtkprivate.h"
30#include "gtkwidgetprivate.h"
31
32#include <gobject/gmarshal.h>
33
34#include <string.h>
35
36/**
37 * GtkCellView:
38 *
39 * A widget displaying a single row of a GtkTreeModel
40 *
41 * A `GtkCellView` displays a single row of a `GtkTreeModel` using a `GtkCellArea`
42 * and `GtkCellAreaContext`. A `GtkCellAreaContext` can be provided to the
43 * `GtkCellView` at construction time in order to keep the cellview in context
44 * of a group of cell views, this ensures that the renderers displayed will
45 * be properly aligned with each other (like the aligned cells in the menus
46 * of `GtkComboBox`).
47 *
48 * `GtkCellView` is `GtkOrientable` in order to decide in which orientation
49 * the underlying `GtkCellAreaContext` should be allocated. Taking the `GtkComboBox`
50 * menu as an example, cellviews should be oriented horizontally if the menus are
51 * listed top-to-bottom and thus all share the same width but may have separate
52 * individual heights (left-to-right menus should be allocated vertically since
53 * they all share the same height but may have variable widths).
54 *
55 * # CSS nodes
56 *
57 * GtkCellView has a single CSS node with name cellview.
58 */
59
60static void gtk_cell_view_constructed (GObject *object);
61static void gtk_cell_view_get_property (GObject *object,
62 guint param_id,
63 GValue *value,
64 GParamSpec *pspec);
65static void gtk_cell_view_set_property (GObject *object,
66 guint param_id,
67 const GValue *value,
68 GParamSpec *pspec);
69static void gtk_cell_view_finalize (GObject *object);
70static void gtk_cell_view_dispose (GObject *object);
71static void gtk_cell_view_size_allocate (GtkWidget *widget,
72 int width,
73 int height,
74 int baseline);
75static void gtk_cell_view_snapshot (GtkWidget *widget,
76 GtkSnapshot *snapshot);
77static void gtk_cell_view_set_value (GtkCellView *cell_view,
78 GtkCellRenderer *renderer,
79 const char *property,
80 GValue *value);
81static void gtk_cell_view_set_cell_data (GtkCellView *cell_view);
82
83/* celllayout */
84static void gtk_cell_view_cell_layout_init (GtkCellLayoutIface *iface);
85static GtkCellArea *gtk_cell_view_cell_layout_get_area (GtkCellLayout *layout);
86
87
88/* buildable */
89static void gtk_cell_view_buildable_init (GtkBuildableIface *iface);
90static gboolean gtk_cell_view_buildable_custom_tag_start (GtkBuildable *buildable,
91 GtkBuilder *builder,
92 GObject *child,
93 const char *tagname,
94 GtkBuildableParser *parser,
95 gpointer *data);
96static void gtk_cell_view_buildable_custom_tag_end (GtkBuildable *buildable,
97 GtkBuilder *builder,
98 GObject *child,
99 const char *tagname,
100 gpointer data);
101
102static GtkSizeRequestMode gtk_cell_view_get_request_mode (GtkWidget *widget);
103static void gtk_cell_view_measure (GtkWidget *widget,
104 GtkOrientation orientation,
105 int for_size,
106 int *minimum,
107 int *natural,
108 int *minimum_baseline,
109 int *natural_baseline);
110static void context_size_changed_cb (GtkCellAreaContext *context,
111 GParamSpec *pspec,
112 GtkWidget *view);
113static void row_changed_cb (GtkTreeModel *model,
114 GtkTreePath *path,
115 GtkTreeIter *iter,
116 GtkCellView *view);
117
118typedef struct _GtkCellViewClass GtkCellViewClass;
119typedef struct _GtkCellViewPrivate GtkCellViewPrivate;
120
121struct _GtkCellView
122{
123 GtkWidget parent_instance;
124};
125
126struct _GtkCellViewClass
127{
128 GtkWidgetClass parent_class;
129};
130
131struct _GtkCellViewPrivate
132{
133 GtkTreeModel *model;
134 GtkTreeRowReference *displayed_row;
135
136 GtkCellArea *area;
137 GtkCellAreaContext *context;
138
139 gulong size_changed_id;
140 gulong row_changed_id;
141
142 GtkOrientation orientation;
143
144 guint draw_sensitive : 1;
145 guint fit_model : 1;
146};
147
148static GtkBuildableIface *parent_buildable_iface;
149
150enum
151{
152 PROP_0,
153 PROP_ORIENTATION,
154 PROP_MODEL,
155 PROP_CELL_AREA,
156 PROP_CELL_AREA_CONTEXT,
157 PROP_DRAW_SENSITIVE,
158 PROP_FIT_MODEL
159};
160
161G_DEFINE_TYPE_WITH_CODE (GtkCellView, gtk_cell_view, GTK_TYPE_WIDGET,
162 G_ADD_PRIVATE (GtkCellView)
163 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
164 gtk_cell_view_cell_layout_init)
165 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
166 gtk_cell_view_buildable_init)
167 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
168
169static void
170gtk_cell_view_class_init (GtkCellViewClass *klass)
171{
172 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
173 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
174
175 gobject_class->constructed = gtk_cell_view_constructed;
176 gobject_class->get_property = gtk_cell_view_get_property;
177 gobject_class->set_property = gtk_cell_view_set_property;
178 gobject_class->finalize = gtk_cell_view_finalize;
179 gobject_class->dispose = gtk_cell_view_dispose;
180
181 widget_class->snapshot = gtk_cell_view_snapshot;
182 widget_class->size_allocate = gtk_cell_view_size_allocate;
183 widget_class->get_request_mode = gtk_cell_view_get_request_mode;
184 widget_class->measure = gtk_cell_view_measure;
185
186 /* properties */
187 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "orientation");
188
189 /**
190 * GtkCellView:model:
191 *
192 * The model for cell view
193 *
194 * since 2.10
195 */
196 g_object_class_install_property (oclass: gobject_class,
197 property_id: PROP_MODEL,
198 pspec: g_param_spec_object (name: "model",
199 P_("CellView model"),
200 P_("The model for cell view"),
201 GTK_TYPE_TREE_MODEL,
202 GTK_PARAM_READWRITE));
203
204
205 /**
206 * GtkCellView:cell-area:
207 *
208 * The `GtkCellArea` rendering cells
209 *
210 * If no area is specified when creating the cell view with gtk_cell_view_new_with_context()
211 * a horizontally oriented `GtkCellArea`Box will be used.
212 *
213 * since 3.0
214 */
215 g_object_class_install_property (oclass: gobject_class,
216 property_id: PROP_CELL_AREA,
217 pspec: g_param_spec_object (name: "cell-area",
218 P_("Cell Area"),
219 P_("The GtkCellArea used to layout cells"),
220 GTK_TYPE_CELL_AREA,
221 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
222
223 /**
224 * GtkCellView:cell-area-context:
225 *
226 * The `GtkCellAreaContext` used to compute the geometry of the cell view.
227 *
228 * A group of cell views can be assigned the same context in order to
229 * ensure the sizes and cell alignments match across all the views with
230 * the same context.
231 *
232 * `GtkComboBox` menus uses this to assign the same context to all cell views
233 * in the menu items for a single menu (each submenu creates its own
234 * context since the size of each submenu does not depend on parent
235 * or sibling menus).
236 *
237 * since 3.0
238 */
239 g_object_class_install_property (oclass: gobject_class,
240 property_id: PROP_CELL_AREA_CONTEXT,
241 pspec: g_param_spec_object (name: "cell-area-context",
242 P_("Cell Area Context"),
243 P_("The GtkCellAreaContext used to "
244 "compute the geometry of the cell view"),
245 GTK_TYPE_CELL_AREA_CONTEXT,
246 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
247
248 /**
249 * GtkCellView:draw-sensitive:
250 *
251 * Whether all cells should be draw as sensitive for this view regardless
252 * of the actual cell properties (used to make menus with submenus appear
253 * sensitive when the items in submenus might be insensitive).
254 *
255 * since 3.0
256 */
257 g_object_class_install_property (oclass: gobject_class,
258 property_id: PROP_DRAW_SENSITIVE,
259 pspec: g_param_spec_boolean (name: "draw-sensitive",
260 P_("Draw Sensitive"),
261 P_("Whether to force cells to be drawn in a "
262 "sensitive state"),
263 FALSE,
264 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
265
266 /**
267 * GtkCellView:fit-model:
268 *
269 * Whether the view should request enough space to always fit
270 * the size of every row in the model (used by the combo box to
271 * ensure the combo box size doesn't change when different items
272 * are selected).
273 *
274 * since 3.0
275 */
276 g_object_class_install_property (oclass: gobject_class,
277 property_id: PROP_FIT_MODEL,
278 pspec: g_param_spec_boolean (name: "fit-model",
279 P_("Fit Model"),
280 P_("Whether to request enough space for "
281 "every row in the model"),
282 FALSE,
283 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
284
285 gtk_widget_class_set_css_name (widget_class, I_("cellview"));
286}
287
288static void
289gtk_cell_view_buildable_add_child (GtkBuildable *buildable,
290 GtkBuilder *builder,
291 GObject *child,
292 const char *type)
293{
294 if (GTK_IS_CELL_RENDERER (child))
295 _gtk_cell_layout_buildable_add_child (buildable, builder, child, type);
296 else
297 parent_buildable_iface->add_child (buildable, builder, child, type);
298}
299
300static void
301gtk_cell_view_buildable_init (GtkBuildableIface *iface)
302{
303 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
304 iface->add_child = gtk_cell_view_buildable_add_child;
305 iface->custom_tag_start = gtk_cell_view_buildable_custom_tag_start;
306 iface->custom_tag_end = gtk_cell_view_buildable_custom_tag_end;
307}
308
309static void
310gtk_cell_view_cell_layout_init (GtkCellLayoutIface *iface)
311{
312 iface->get_area = gtk_cell_view_cell_layout_get_area;
313}
314
315static void
316gtk_cell_view_constructed (GObject *object)
317{
318 GtkCellView *view = GTK_CELL_VIEW (object);
319 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: view);
320
321 G_OBJECT_CLASS (gtk_cell_view_parent_class)->constructed (object);
322
323 if (!priv->area)
324 {
325 priv->area = gtk_cell_area_box_new ();
326 g_object_ref_sink (priv->area);
327 }
328
329 if (!priv->context)
330 priv->context = gtk_cell_area_create_context (area: priv->area);
331
332 priv->size_changed_id =
333 g_signal_connect (priv->context, "notify",
334 G_CALLBACK (context_size_changed_cb), view);
335}
336
337static void
338gtk_cell_view_get_property (GObject *object,
339 guint param_id,
340 GValue *value,
341 GParamSpec *pspec)
342{
343 GtkCellView *view = GTK_CELL_VIEW (object);
344 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: view);
345
346 switch (param_id)
347 {
348 case PROP_ORIENTATION:
349 g_value_set_enum (value, v_enum: priv->orientation);
350 break;
351 case PROP_MODEL:
352 g_value_set_object (value, v_object: priv->model);
353 break;
354 case PROP_CELL_AREA:
355 g_value_set_object (value, v_object: priv->area);
356 break;
357 case PROP_CELL_AREA_CONTEXT:
358 g_value_set_object (value, v_object: priv->context);
359 break;
360 case PROP_DRAW_SENSITIVE:
361 g_value_set_boolean (value, v_boolean: priv->draw_sensitive);
362 break;
363 case PROP_FIT_MODEL:
364 g_value_set_boolean (value, v_boolean: priv->fit_model);
365 break;
366 default:
367 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
368 break;
369 }
370}
371
372static void
373gtk_cell_view_set_property (GObject *object,
374 guint param_id,
375 const GValue *value,
376 GParamSpec *pspec)
377{
378 GtkCellView *view = GTK_CELL_VIEW (object);
379 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: view);
380 GtkCellArea *area;
381 GtkCellAreaContext *context;
382
383 switch (param_id)
384 {
385 case PROP_ORIENTATION:
386 if (priv->orientation != g_value_get_enum (value))
387 {
388 priv->orientation = g_value_get_enum (value);
389 if (priv->context)
390 gtk_cell_area_context_reset (context: priv->context);
391 gtk_widget_update_orientation (GTK_WIDGET (object), orientation: priv->orientation);
392 g_object_notify_by_pspec (object, pspec);
393 }
394 break;
395 case PROP_MODEL:
396 gtk_cell_view_set_model (cell_view: view, model: g_value_get_object (value));
397 break;
398 case PROP_CELL_AREA:
399 /* Construct-only, can only be assigned once */
400 area = g_value_get_object (value);
401
402 if (area)
403 {
404 if (priv->area != NULL)
405 {
406 g_warning ("cell-area has already been set, ignoring construct property");
407 g_object_ref_sink (area);
408 g_object_unref (object: area);
409 }
410 else
411 priv->area = g_object_ref_sink (area);
412 }
413 break;
414 case PROP_CELL_AREA_CONTEXT:
415 /* Construct-only, can only be assigned once */
416 context = g_value_get_object (value);
417
418 if (context)
419 {
420 if (priv->context != NULL)
421 {
422 g_warning ("cell-area-context has already been set, ignoring construct property");
423 g_object_ref_sink (context);
424 g_object_unref (object: context);
425 }
426 else
427 priv->context = g_object_ref (context);
428 }
429 break;
430
431 case PROP_DRAW_SENSITIVE:
432 gtk_cell_view_set_draw_sensitive (cell_view: view, draw_sensitive: g_value_get_boolean (value));
433 break;
434
435 case PROP_FIT_MODEL:
436 gtk_cell_view_set_fit_model (cell_view: view, fit_model: g_value_get_boolean (value));
437 break;
438
439 default:
440 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
441 break;
442 }
443}
444
445static void
446gtk_cell_view_init (GtkCellView *cellview)
447{
448 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
449
450 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
451}
452
453static void
454gtk_cell_view_finalize (GObject *object)
455{
456 GtkCellView *cellview = GTK_CELL_VIEW (object);
457 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
458
459 if (priv->displayed_row)
460 gtk_tree_row_reference_free (reference: priv->displayed_row);
461
462 G_OBJECT_CLASS (gtk_cell_view_parent_class)->finalize (object);
463}
464
465static void
466gtk_cell_view_dispose (GObject *object)
467{
468 GtkCellView *cellview = GTK_CELL_VIEW (object);
469 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
470
471 gtk_cell_view_set_model (cell_view: cellview, NULL);
472
473 g_clear_object (&priv->area);
474
475 if (priv->context)
476 {
477 g_signal_handler_disconnect (instance: priv->context, handler_id: priv->size_changed_id);
478
479 g_object_unref (object: priv->context);
480 priv->context = NULL;
481 priv->size_changed_id = 0;
482 }
483
484 G_OBJECT_CLASS (gtk_cell_view_parent_class)->dispose (object);
485}
486
487static void
488gtk_cell_view_size_allocate (GtkWidget *widget,
489 int width,
490 int height,
491 int baseline)
492{
493 GtkCellView *cellview = GTK_CELL_VIEW (widget);
494 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
495 int alloc_width, alloc_height;
496
497 gtk_cell_area_context_get_allocation (context: priv->context, width: &alloc_width, height: &alloc_height);
498
499 /* The first cell view in context is responsible for allocating the context at
500 * allocate time (or the cellview has its own context and is not grouped with
501 * any other cell views)
502 *
503 * If the cellview is in "fit model" mode, we assume it's not in context and
504 * needs to allocate every time.
505 */
506 if (priv->fit_model)
507 gtk_cell_area_context_allocate (context: priv->context, width, height);
508 else if (alloc_width != width && priv->orientation == GTK_ORIENTATION_HORIZONTAL)
509 gtk_cell_area_context_allocate (context: priv->context, width, height: -1);
510 else if (alloc_height != height && priv->orientation == GTK_ORIENTATION_VERTICAL)
511 gtk_cell_area_context_allocate (context: priv->context, width: -1, height);
512}
513
514static void
515gtk_cell_view_request_model (GtkCellView *cellview,
516 GtkTreeIter *parent,
517 GtkOrientation orientation,
518 int for_size,
519 int *minimum_size,
520 int *natural_size)
521{
522 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
523 GtkTreeIter iter;
524 gboolean valid;
525
526 if (!priv->model)
527 return;
528
529 valid = gtk_tree_model_iter_children (tree_model: priv->model, iter: &iter, parent);
530 while (valid)
531 {
532 int min, nat;
533
534 gtk_cell_area_apply_attributes (area: priv->area, tree_model: priv->model, iter: &iter, FALSE, FALSE);
535
536 if (orientation == GTK_ORIENTATION_HORIZONTAL)
537 {
538 if (for_size < 0)
539 gtk_cell_area_get_preferred_width (area: priv->area, context: priv->context,
540 GTK_WIDGET (cellview), minimum_width: &min, natural_width: &nat);
541 else
542 gtk_cell_area_get_preferred_width_for_height (area: priv->area, context: priv->context,
543 GTK_WIDGET (cellview), height: for_size, minimum_width: &min, natural_width: &nat);
544 }
545 else
546 {
547 if (for_size < 0)
548 gtk_cell_area_get_preferred_height (area: priv->area, context: priv->context,
549 GTK_WIDGET (cellview), minimum_height: &min, natural_height: &nat);
550 else
551 gtk_cell_area_get_preferred_height_for_width (area: priv->area, context: priv->context,
552 GTK_WIDGET (cellview), width: for_size, minimum_height: &min, natural_height: &nat);
553 }
554
555 *minimum_size = MAX (min, *minimum_size);
556 *natural_size = MAX (nat, *natural_size);
557
558 /* Recurse into children when they exist */
559 gtk_cell_view_request_model (cellview, parent: &iter, orientation, for_size, minimum_size, natural_size);
560
561 valid = gtk_tree_model_iter_next (tree_model: priv->model, iter: &iter);
562 }
563}
564
565static GtkSizeRequestMode
566gtk_cell_view_get_request_mode (GtkWidget *widget)
567{
568 GtkCellView *cellview = GTK_CELL_VIEW (widget);
569 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
570
571 return gtk_cell_area_get_request_mode (area: priv->area);
572}
573
574static void
575gtk_cell_view_measure (GtkWidget *widget,
576 GtkOrientation orientation,
577 int for_size,
578 int *minimum,
579 int *natural,
580 int *minimum_baseline,
581 int *natural_baseline)
582{
583 GtkCellView *cellview = GTK_CELL_VIEW (widget);
584 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
585
586 g_signal_handler_block (instance: priv->context, handler_id: priv->size_changed_id);
587
588 if (orientation == GTK_ORIENTATION_HORIZONTAL && for_size == -1)
589 {
590 if (priv->fit_model)
591 {
592 int min = 0, nat = 0;
593 gtk_cell_view_request_model (cellview, NULL, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, minimum_size: &min, natural_size: &nat);
594 }
595 else
596 {
597 if (priv->displayed_row)
598 gtk_cell_view_set_cell_data (cell_view: cellview);
599
600 gtk_cell_area_get_preferred_width (area: priv->area, context: priv->context, widget, NULL, NULL);
601 }
602
603 gtk_cell_area_context_get_preferred_width (context: priv->context, minimum_width: minimum, natural_width: natural);
604 }
605 else if (orientation == GTK_ORIENTATION_VERTICAL && for_size == -1)
606 {
607 if (priv->fit_model)
608 {
609 int min = 0, nat = 0;
610 gtk_cell_view_request_model (cellview, NULL, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, minimum_size: &min, natural_size: &nat);
611 }
612 else
613 {
614 if (priv->displayed_row)
615 gtk_cell_view_set_cell_data (cell_view: cellview);
616
617 gtk_cell_area_get_preferred_height (area: priv->area, context: priv->context, widget, NULL, NULL);
618 }
619
620 gtk_cell_area_context_get_preferred_height (context: priv->context, minimum_height: minimum, natural_height: natural);
621 }
622 else if (orientation == GTK_ORIENTATION_HORIZONTAL && for_size >= 0)
623 {
624 if (priv->fit_model)
625 {
626 int min = 0, nat = 0;
627 gtk_cell_view_request_model (cellview, NULL, orientation: GTK_ORIENTATION_HORIZONTAL, for_size, minimum_size: &min, natural_size: &nat);
628
629 *minimum = min;
630 *natural = nat;
631 }
632 else
633 {
634 if (priv->displayed_row)
635 gtk_cell_view_set_cell_data (cell_view: cellview);
636
637 gtk_cell_area_get_preferred_width_for_height (area: priv->area, context: priv->context, widget,
638 height: for_size, minimum_width: minimum, natural_width: natural);
639 }
640 }
641 else
642 {
643 if (priv->fit_model)
644 {
645 int min = 0, nat = 0;
646 gtk_cell_view_request_model (cellview, NULL, orientation: GTK_ORIENTATION_VERTICAL, for_size, minimum_size: &min, natural_size: &nat);
647
648 *minimum = min;
649 *natural = nat;
650 }
651 else
652 {
653 if (priv->displayed_row)
654 gtk_cell_view_set_cell_data (cell_view: cellview);
655
656 gtk_cell_area_get_preferred_height_for_width (area: priv->area, context: priv->context, widget,
657 width: for_size, minimum_height: minimum, natural_height: natural);
658 }
659 }
660
661 g_signal_handler_unblock (instance: priv->context, handler_id: priv->size_changed_id);
662}
663
664static void
665gtk_cell_view_snapshot (GtkWidget *widget,
666 GtkSnapshot *snapshot)
667{
668 GtkCellView *cellview = GTK_CELL_VIEW (widget);
669 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
670 GdkRectangle area;
671 GtkCellRendererState state;
672
673 /* render cells */
674 area.x = 0;
675 area.y = 0;
676 area.width = gtk_widget_get_width (widget);
677 area.height = gtk_widget_get_height (widget);
678
679 /* set cell data (if available) */
680 if (priv->displayed_row)
681 gtk_cell_view_set_cell_data (cell_view: cellview);
682 else if (priv->model)
683 return;
684
685 if (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_PRELIGHT)
686 state = GTK_CELL_RENDERER_PRELIT;
687 else
688 state = 0;
689
690 /* Render the cells */
691 gtk_cell_area_snapshot (area: priv->area, context: priv->context,
692 widget, snapshot, background_area: &area, cell_area: &area, flags: state, FALSE);
693
694
695}
696
697static void
698gtk_cell_view_set_cell_data (GtkCellView *cell_view)
699{
700 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
701 GtkTreeIter iter;
702 GtkTreePath *path;
703
704 g_return_if_fail (priv->displayed_row != NULL);
705
706 path = gtk_tree_row_reference_get_path (reference: priv->displayed_row);
707 if (!path)
708 return;
709
710 gtk_tree_model_get_iter (tree_model: priv->model, iter: &iter, path);
711 gtk_tree_path_free (path);
712
713 gtk_cell_area_apply_attributes (area: priv->area,
714 tree_model: priv->model,
715 iter: &iter, FALSE, FALSE);
716
717 if (priv->draw_sensitive)
718 {
719 GList *l, *cells =
720 gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->area));
721
722 for (l = cells; l; l = l->next)
723 {
724 GObject *renderer = l->data;
725
726 g_object_set (object: renderer, first_property_name: "sensitive", TRUE, NULL);
727 }
728 g_list_free (list: cells);
729 }
730}
731
732/* GtkCellLayout implementation */
733static GtkCellArea *
734gtk_cell_view_cell_layout_get_area (GtkCellLayout *layout)
735{
736 GtkCellView *cellview = GTK_CELL_VIEW (layout);
737 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cellview);
738
739 if (G_UNLIKELY (!priv->area))
740 {
741 priv->area = gtk_cell_area_box_new ();
742 g_object_ref_sink (priv->area);
743 }
744
745 return priv->area;
746}
747
748/* GtkBuildable implementation */
749static gboolean
750gtk_cell_view_buildable_custom_tag_start (GtkBuildable *buildable,
751 GtkBuilder *builder,
752 GObject *child,
753 const char *tagname,
754 GtkBuildableParser *parser,
755 gpointer *data)
756{
757 if (parent_buildable_iface->custom_tag_start &&
758 parent_buildable_iface->custom_tag_start (buildable, builder, child,
759 tagname, parser, data))
760 return TRUE;
761
762 return _gtk_cell_layout_buildable_custom_tag_start (buildable, builder, child,
763 tagname, parser, data);
764}
765
766static void
767gtk_cell_view_buildable_custom_tag_end (GtkBuildable *buildable,
768 GtkBuilder *builder,
769 GObject *child,
770 const char *tagname,
771 gpointer data)
772{
773 if (_gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname,
774 data))
775 return;
776 else if (parent_buildable_iface->custom_tag_end)
777 parent_buildable_iface->custom_tag_end (buildable, builder, child, tagname,
778 data);
779}
780
781static void
782context_size_changed_cb (GtkCellAreaContext *context,
783 GParamSpec *pspec,
784 GtkWidget *view)
785{
786 if (!strcmp (s1: pspec->name, s2: "minimum-width") ||
787 !strcmp (s1: pspec->name, s2: "natural-width") ||
788 !strcmp (s1: pspec->name, s2: "minimum-height") ||
789 !strcmp (s1: pspec->name, s2: "natural-height"))
790 gtk_widget_queue_resize (widget: view);
791}
792
793static void
794row_changed_cb (GtkTreeModel *model,
795 GtkTreePath *path,
796 GtkTreeIter *iter,
797 GtkCellView *view)
798{
799 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: view);
800 GtkTreePath *row_path;
801
802 if (priv->displayed_row)
803 {
804 row_path = gtk_tree_row_reference_get_path (reference: priv->displayed_row);
805
806 if (row_path)
807 {
808 /* Resize everything in our context if our row changed */
809 if (gtk_tree_path_compare (a: row_path, b: path) == 0)
810 gtk_cell_area_context_reset (context: priv->context);
811
812 gtk_tree_path_free (path: row_path);
813 }
814 }
815}
816
817/**
818 * gtk_cell_view_new:
819 *
820 * Creates a new `GtkCellView` widget.
821 *
822 * Returns: A newly created `GtkCellView` widget.
823 */
824GtkWidget *
825gtk_cell_view_new (void)
826{
827 GtkCellView *cellview;
828
829 cellview = g_object_new (GTK_TYPE_CELL_VIEW, NULL);
830
831 return GTK_WIDGET (cellview);
832}
833
834
835/**
836 * gtk_cell_view_new_with_context:
837 * @area: the `GtkCellArea` to layout cells
838 * @context: the `GtkCellAreaContext` in which to calculate cell geometry
839 *
840 * Creates a new `GtkCellView` widget with a specific `GtkCellArea`
841 * to layout cells and a specific `GtkCellAreaContext`.
842 *
843 * Specifying the same context for a handful of cells lets
844 * the underlying area synchronize the geometry for those cells,
845 * in this way alignments with cellviews for other rows are
846 * possible.
847 *
848 * Returns: A newly created `GtkCellView` widget.
849 */
850GtkWidget *
851gtk_cell_view_new_with_context (GtkCellArea *area,
852 GtkCellAreaContext *context)
853{
854 g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
855 g_return_val_if_fail (context == NULL || GTK_IS_CELL_AREA_CONTEXT (context), NULL);
856
857 return (GtkWidget *)g_object_new (GTK_TYPE_CELL_VIEW,
858 first_property_name: "cell-area", area,
859 "cell-area-context", context,
860 NULL);
861}
862
863/**
864 * gtk_cell_view_new_with_text:
865 * @text: the text to display in the cell view
866 *
867 * Creates a new `GtkCellView` widget, adds a `GtkCellRendererText`
868 * to it, and makes it show @text.
869 *
870 * Returns: A newly created `GtkCellView` widget.
871 */
872GtkWidget *
873gtk_cell_view_new_with_text (const char *text)
874{
875 GtkCellView *cellview;
876 GtkCellRenderer *renderer;
877 GValue value = G_VALUE_INIT;
878
879 cellview = GTK_CELL_VIEW (gtk_cell_view_new ());
880
881 renderer = gtk_cell_renderer_text_new ();
882 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cellview),
883 cell: renderer, TRUE);
884
885 g_value_init (value: &value, G_TYPE_STRING);
886 g_value_set_string (value: &value, v_string: text);
887 gtk_cell_view_set_value (cell_view: cellview, renderer, property: "text", value: &value);
888 g_value_unset (value: &value);
889
890 return GTK_WIDGET (cellview);
891}
892
893/**
894 * gtk_cell_view_new_with_markup:
895 * @markup: the text to display in the cell view
896 *
897 * Creates a new `GtkCellView` widget, adds a `GtkCellRendererText`
898 * to it, and makes it show @markup. The text can be marked up with
899 * the [Pango text markup language](https://docs.gtk.org/Pango/pango_markup.html).
900 *
901 * Returns: A newly created `GtkCellView` widget.
902 */
903GtkWidget *
904gtk_cell_view_new_with_markup (const char *markup)
905{
906 GtkCellView *cellview;
907 GtkCellRenderer *renderer;
908 GValue value = G_VALUE_INIT;
909
910 cellview = GTK_CELL_VIEW (gtk_cell_view_new ());
911
912 renderer = gtk_cell_renderer_text_new ();
913 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cellview),
914 cell: renderer, TRUE);
915
916 g_value_init (value: &value, G_TYPE_STRING);
917 g_value_set_string (value: &value, v_string: markup);
918 gtk_cell_view_set_value (cell_view: cellview, renderer, property: "markup", value: &value);
919 g_value_unset (value: &value);
920
921 return GTK_WIDGET (cellview);
922}
923
924/**
925 * gtk_cell_view_new_with_texture:
926 * @texture: the image to display in the cell view
927 *
928 * Creates a new `GtkCellView` widget, adds a `GtkCellRendererPixbuf`
929 * to it, and makes it show @texture.
930 *
931 * Returns: A newly created `GtkCellView` widget.
932 */
933GtkWidget *
934gtk_cell_view_new_with_texture (GdkTexture *texture)
935{
936 GtkCellView *cellview;
937 GtkCellRenderer *renderer;
938 GValue value = G_VALUE_INIT;
939
940 cellview = GTK_CELL_VIEW (gtk_cell_view_new ());
941
942 renderer = gtk_cell_renderer_pixbuf_new ();
943 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cellview),
944 cell: renderer, TRUE);
945
946 g_value_init (value: &value, GDK_TYPE_TEXTURE);
947 g_value_set_object (value: &value, v_object: texture);
948 gtk_cell_view_set_value (cell_view: cellview, renderer, property: "texture", value: &value);
949 g_value_unset (value: &value);
950
951 return GTK_WIDGET (cellview);
952}
953
954/**
955 * gtk_cell_view_set_value:
956 * @cell_view: a `GtkCellView` widget
957 * @renderer: one of the renderers of @cell_view
958 * @property: the name of the property of @renderer to set
959 * @value: the new value to set the property to
960 *
961 * Sets a property of a cell renderer of @cell_view, and
962 * makes sure the display of @cell_view is updated.
963 */
964static void
965gtk_cell_view_set_value (GtkCellView *cell_view,
966 GtkCellRenderer *renderer,
967 const char *property,
968 GValue *value)
969{
970 g_object_set_property (G_OBJECT (renderer), property_name: property, value);
971
972 /* force resize and redraw */
973 gtk_widget_queue_resize (GTK_WIDGET (cell_view));
974 gtk_widget_queue_draw (GTK_WIDGET (cell_view));
975}
976
977/**
978 * gtk_cell_view_set_model:
979 * @cell_view: a `GtkCellView`
980 * @model: (nullable): a `GtkTreeModel`
981 *
982 * Sets the model for @cell_view. If @cell_view already has a model
983 * set, it will remove it before setting the new model. If @model is
984 * %NULL, then it will unset the old model.
985 */
986void
987gtk_cell_view_set_model (GtkCellView *cell_view,
988 GtkTreeModel *model)
989{
990 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
991
992 g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
993 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
994
995 if (priv->model)
996 {
997 g_signal_handler_disconnect (instance: priv->model, handler_id: priv->row_changed_id);
998 priv->row_changed_id = 0;
999
1000 if (priv->displayed_row)
1001 gtk_tree_row_reference_free (reference: priv->displayed_row);
1002 priv->displayed_row = NULL;
1003
1004 g_object_unref (object: priv->model);
1005 }
1006
1007 priv->model = model;
1008
1009 if (priv->model)
1010 {
1011 g_object_ref (priv->model);
1012
1013 priv->row_changed_id =
1014 g_signal_connect (priv->model, "row-changed",
1015 G_CALLBACK (row_changed_cb), cell_view);
1016 }
1017}
1018
1019/**
1020 * gtk_cell_view_get_model:
1021 * @cell_view: a `GtkCellView`
1022 *
1023 * Returns the model for @cell_view. If no model is used %NULL is
1024 * returned.
1025 *
1026 * Returns: (nullable) (transfer none): a `GtkTreeModel` used
1027 */
1028GtkTreeModel *
1029gtk_cell_view_get_model (GtkCellView *cell_view)
1030{
1031 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1032
1033 g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), NULL);
1034
1035 return priv->model;
1036}
1037
1038/**
1039 * gtk_cell_view_set_displayed_row:
1040 * @cell_view: a `GtkCellView`
1041 * @path: (nullable): a `GtkTreePath` or %NULL to unset.
1042 *
1043 * Sets the row of the model that is currently displayed
1044 * by the `GtkCellView`. If the path is unset, then the
1045 * contents of the cellview “stick” at their last value;
1046 * this is not normally a desired result, but may be
1047 * a needed intermediate state if say, the model for
1048 * the `GtkCellView` becomes temporarily empty.
1049 **/
1050void
1051gtk_cell_view_set_displayed_row (GtkCellView *cell_view,
1052 GtkTreePath *path)
1053{
1054 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1055
1056 g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
1057 g_return_if_fail (GTK_IS_TREE_MODEL (priv->model));
1058
1059 if (priv->displayed_row)
1060 gtk_tree_row_reference_free (reference: priv->displayed_row);
1061
1062 if (path)
1063 priv->displayed_row = gtk_tree_row_reference_new (model: priv->model, path);
1064 else
1065 priv->displayed_row = NULL;
1066
1067 /* force resize and redraw */
1068 gtk_widget_queue_resize (GTK_WIDGET (cell_view));
1069 gtk_widget_queue_draw (GTK_WIDGET (cell_view));
1070}
1071
1072/**
1073 * gtk_cell_view_get_displayed_row:
1074 * @cell_view: a `GtkCellView`
1075 *
1076 * Returns a `GtkTreePath` referring to the currently
1077 * displayed row. If no row is currently displayed,
1078 * %NULL is returned.
1079 *
1080 * Returns: (nullable) (transfer full): the currently displayed row
1081 */
1082GtkTreePath *
1083gtk_cell_view_get_displayed_row (GtkCellView *cell_view)
1084{
1085 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1086
1087 g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), NULL);
1088
1089 if (!priv->displayed_row)
1090 return NULL;
1091
1092 return gtk_tree_row_reference_get_path (reference: priv->displayed_row);
1093}
1094
1095/**
1096 * gtk_cell_view_get_draw_sensitive:
1097 * @cell_view: a `GtkCellView`
1098 *
1099 * Gets whether @cell_view is configured to draw all of its
1100 * cells in a sensitive state.
1101 *
1102 * Returns: whether @cell_view draws all of its
1103 * cells in a sensitive state
1104 */
1105gboolean
1106gtk_cell_view_get_draw_sensitive (GtkCellView *cell_view)
1107{
1108 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1109
1110 g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), FALSE);
1111
1112 return priv->draw_sensitive;
1113}
1114
1115/**
1116 * gtk_cell_view_set_draw_sensitive:
1117 * @cell_view: a `GtkCellView`
1118 * @draw_sensitive: whether to draw all cells in a sensitive state.
1119 *
1120 * Sets whether @cell_view should draw all of its
1121 * cells in a sensitive state, this is used by `GtkComboBox` menus
1122 * to ensure that rows with insensitive cells that contain
1123 * children appear sensitive in the parent menu item.
1124 */
1125void
1126gtk_cell_view_set_draw_sensitive (GtkCellView *cell_view,
1127 gboolean draw_sensitive)
1128{
1129 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1130
1131 g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
1132
1133 if (priv->draw_sensitive != draw_sensitive)
1134 {
1135 priv->draw_sensitive = draw_sensitive;
1136
1137 g_object_notify (G_OBJECT (cell_view), property_name: "draw-sensitive");
1138 }
1139}
1140
1141/**
1142 * gtk_cell_view_get_fit_model:
1143 * @cell_view: a `GtkCellView`
1144 *
1145 * Gets whether @cell_view is configured to request space
1146 * to fit the entire `GtkTreeModel`.
1147 *
1148 * Returns: whether @cell_view requests space to fit
1149 * the entire `GtkTreeModel`.
1150 */
1151gboolean
1152gtk_cell_view_get_fit_model (GtkCellView *cell_view)
1153{
1154 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1155
1156 g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), FALSE);
1157
1158 return priv->fit_model;
1159}
1160
1161/**
1162 * gtk_cell_view_set_fit_model:
1163 * @cell_view: a `GtkCellView`
1164 * @fit_model: whether @cell_view should request space for the whole model.
1165 *
1166 * Sets whether @cell_view should request space to fit the entire `GtkTreeModel`.
1167 *
1168 * This is used by `GtkComboBox` to ensure that the cell view displayed on
1169 * the combo box’s button always gets enough space and does not resize
1170 * when selection changes.
1171 */
1172void
1173gtk_cell_view_set_fit_model (GtkCellView *cell_view,
1174 gboolean fit_model)
1175{
1176 GtkCellViewPrivate *priv = gtk_cell_view_get_instance_private (self: cell_view);
1177
1178 g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
1179
1180 if (priv->fit_model != fit_model)
1181 {
1182 priv->fit_model = fit_model;
1183
1184 gtk_cell_area_context_reset (context: priv->context);
1185
1186 g_object_notify (G_OBJECT (cell_view), property_name: "fit-model");
1187 }
1188}
1189

source code of gtk/gtk/gtkcellview.c