gtkcellview.c 35.7 KB
Newer Older
1 2 3
/* gtkellview.c
 * Copyright (C) 2002, 2003  Kristian Rietveld <kris@gtk.org>
 *
4 5 6 7
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * 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 11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * 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
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17
 */

18
#include "config.h"
19
#include <string.h>
Kristian Rietveld's avatar
Kristian Rietveld committed
20 21
#include "gtkcellview.h"
#include "gtkcelllayout.h"
22
#include "gtkcellareabox.h"
Kristian Rietveld's avatar
Kristian Rietveld committed
23 24 25
#include "gtkintl.h"
#include "gtkcellrenderertext.h"
#include "gtkcellrendererpixbuf.h"
26
#include "gtkprivate.h"
27
#include "gtkorientableprivate.h"
28
#include "gtkwidgetprivate.h"
29
#include <gobject/gmarshal.h>
30
#include "gtkbuildable.h"
31

32

33 34 35 36 37
/**
 * SECTION:gtkcellview
 * @Short_description: A widget displaying a single row of a GtkTreeModel
 * @Title: GtkCellView
 *
38 39 40 41
 * A #GtkCellView displays a single row of a #GtkTreeModel using a #GtkCellArea
 * and #GtkCellAreaContext. A #GtkCellAreaContext can be provided to the 
 * #GtkCellView at construction time in order to keep the cellview in context
 * of a group of cell views, this ensures that the renderers displayed will
42 43
 * be properly aligned with eachother (like the aligned cells in the menus
 * of #GtkComboBox).
44 45
 *
 * #GtkCellView is #GtkOrientable in order to decide in which orientation
46 47
 * the underlying #GtkCellAreaContext should be allocated. Taking the #GtkComboBox
 * menu as an example, cellviews should be oriented horizontally if the menus are
48 49 50
 * listed top-to-bottom and thus all share the same width but may have separate
 * individual heights (left-to-right menus should be allocated vertically since
 * they all share the same height but may have variable widths).
51 52 53 54
 *
 * # CSS nodes
 *
 * GtkCellView has a single CSS node with name cellview.
55 56
 */

57 58
static void        gtk_cell_view_constructed              (GObject          *object);
static void        gtk_cell_view_get_property             (GObject          *object,
59 60 61 62 63 64 65 66
                                                           guint             param_id,
                                                           GValue           *value,
                                                           GParamSpec       *pspec);
static void        gtk_cell_view_set_property             (GObject          *object,
                                                           guint             param_id,
                                                           const GValue     *value,
                                                           GParamSpec       *pspec);
static void        gtk_cell_view_finalize                 (GObject          *object);
67
static void        gtk_cell_view_dispose                  (GObject          *object);
68 69
static void        gtk_cell_view_size_allocate            (GtkWidget           *widget,
                                                           const GtkAllocation *allocation,
70
                                                           int                  baseline);
71 72
static void        gtk_cell_view_snapshot                 (GtkWidget        *widget,
                                                           GtkSnapshot      *snapshot);
73 74
static void        gtk_cell_view_set_value                (GtkCellView     *cell_view,
                                                           GtkCellRenderer *renderer,
75
                                                           const char      *property,
76
                                                           GValue          *value);
77
static void        gtk_cell_view_set_cell_data            (GtkCellView      *cell_view);
78

79 80
/* celllayout */
static void        gtk_cell_view_cell_layout_init         (GtkCellLayoutIface *iface);
81
static GtkCellArea *gtk_cell_view_cell_layout_get_area         (GtkCellLayout         *layout);
82

83

84 85 86 87 88 89 90 91 92 93 94 95
/* buildable */
static void       gtk_cell_view_buildable_init                 (GtkBuildableIface     *iface);
static gboolean   gtk_cell_view_buildable_custom_tag_start     (GtkBuildable  	      *buildable,
								GtkBuilder    	      *builder,
								GObject       	      *child,
								const gchar   	      *tagname,
								GMarkupParser 	      *parser,
								gpointer      	      *data);
static void       gtk_cell_view_buildable_custom_tag_end       (GtkBuildable  	      *buildable,
								GtkBuilder    	      *builder,
								GObject       	      *child,
								const gchar   	      *tagname,
96
								gpointer      	       data);
97

98
static GtkSizeRequestMode gtk_cell_view_get_request_mode       (GtkWidget             *widget);
Timm Bäder's avatar
Timm Bäder committed
99
static void gtk_cell_view_measure (GtkWidget      *widget,
100 101 102 103 104 105
                                   GtkOrientation  orientation,
                                   int             for_size,
                                   int            *minimum,
                                   int            *natural,
                                   int            *minimum_baseline,
                                   int            *natural_baseline);
106 107 108
static void       context_size_changed_cb                      (GtkCellAreaContext   *context,
								GParamSpec           *pspec,
								GtkWidget            *view);
109 110 111 112
static void       row_changed_cb                               (GtkTreeModel         *model,
								GtkTreePath          *path,
								GtkTreeIter          *iter,
								GtkCellView          *view);
113

114 115 116 117 118 119 120 121 122 123 124
struct _GtkCellViewPrivate
{
  GtkTreeModel        *model;
  GtkTreeRowReference *displayed_row;

  GtkCellArea         *area;
  GtkCellAreaContext  *context;

  gulong               size_changed_id;
  gulong               row_changed_id;

125 126 127 128
  GtkOrientation       orientation;

  guint                draw_sensitive : 1;
  guint                fit_model      : 1;
129 130
};

131 132
static GtkBuildableIface *parent_buildable_iface;

133 134 135
enum
{
  PROP_0,
136
  PROP_ORIENTATION,
137 138
  PROP_MODEL,
  PROP_CELL_AREA,
139 140 141
  PROP_CELL_AREA_CONTEXT,
  PROP_DRAW_SENSITIVE,
  PROP_FIT_MODEL
142 143
};

144 145
G_DEFINE_TYPE_WITH_CODE (GtkCellView, gtk_cell_view, GTK_TYPE_WIDGET,
                         G_ADD_PRIVATE (GtkCellView)
Matthias Clasen's avatar
Matthias Clasen committed
146
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
147 148
						gtk_cell_view_cell_layout_init)
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
149
						gtk_cell_view_buildable_init)
150
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
151

152 153 154
static void
gtk_cell_view_class_init (GtkCellViewClass *klass)
{
155
  GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
156 157
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

158
  gobject_class->constructed = gtk_cell_view_constructed;
159 160 161
  gobject_class->get_property = gtk_cell_view_get_property;
  gobject_class->set_property = gtk_cell_view_set_property;
  gobject_class->finalize = gtk_cell_view_finalize;
162
  gobject_class->dispose = gtk_cell_view_dispose;
163

164
  widget_class->snapshot                       = gtk_cell_view_snapshot;
165
  widget_class->size_allocate                  = gtk_cell_view_size_allocate;
166
  widget_class->get_request_mode               = gtk_cell_view_get_request_mode;
Timm Bäder's avatar
Timm Bäder committed
167
  widget_class->measure                        = gtk_cell_view_measure;
168 169

  /* properties */
170 171
  g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");

172
  /**
173
   * GtkCellView:model:
174 175 176 177 178 179 180 181 182 183 184 185
   *
   * The model for cell view
   *
   * since 2.10
   */
  g_object_class_install_property (gobject_class,
				   PROP_MODEL,
				   g_param_spec_object  ("model",
							 P_("CellView model"),
							 P_("The model for cell view"),
							 GTK_TYPE_TREE_MODEL,
							 GTK_PARAM_READWRITE));
186 187 188


  /**
189
   * GtkCellView:cell-area:
190 191 192
   *
   * The #GtkCellArea rendering cells
   *
193 194 195
   * If no area is specified when creating the cell view with gtk_cell_view_new_with_context() 
   * a horizontally oriented #GtkCellAreaBox will be used.
   *
196 197 198 199 200 201 202 203 204 205 206
   * since 3.0
   */
   g_object_class_install_property (gobject_class,
                                    PROP_CELL_AREA,
                                    g_param_spec_object ("cell-area",
							 P_("Cell Area"),
							 P_("The GtkCellArea used to layout cells"),
							 GTK_TYPE_CELL_AREA,
							 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

  /**
207
   * GtkCellView:cell-area-context:
208 209 210
   *
   * The #GtkCellAreaContext used to compute the geometry of the cell view.
   *
211 212 213 214
   * A group of cell views can be assigned the same context in order to
   * ensure the sizes and cell alignments match across all the views with
   * the same context.
   *
215
   * #GtkComboBox menus uses this to assign the same context to all cell views
216
   * in the menu items for a single menu (each submenu creates its own
217 218 219
   * context since the size of each submenu does not depend on parent
   * or sibling menus).
   *
220 221 222
   * since 3.0
   */
   g_object_class_install_property (gobject_class,
223
                                    PROP_CELL_AREA_CONTEXT,
224 225 226 227 228 229 230
                                    g_param_spec_object ("cell-area-context",
							 P_("Cell Area Context"),
							 P_("The GtkCellAreaContext used to "
							    "compute the geometry of the cell view"),
							 GTK_TYPE_CELL_AREA_CONTEXT,
							 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

231
  /**
232
   * GtkCellView:draw-sensitive:
233 234 235 236 237 238 239 240 241 242 243 244 245 246
   *
   * Whether all cells should be draw as sensitive for this view regardless
   * of the actual cell properties (used to make menus with submenus appear
   * sensitive when the items in submenus might be insensitive).
   *
   * since 3.0
   */
   g_object_class_install_property (gobject_class,
                                    PROP_DRAW_SENSITIVE,
                                    g_param_spec_boolean ("draw-sensitive",
							  P_("Draw Sensitive"),
							  P_("Whether to force cells to be drawn in a "
							     "sensitive state"),
							  FALSE,
247
							  GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
248 249

  /**
250
   * GtkCellView:fit-model:
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
   *
   * Whether the view should request enough space to always fit
   * the size of every row in the model (used by the combo box to
   * ensure the combo box size doesnt change when different items
   * are selected).
   *
   * since 3.0
   */
   g_object_class_install_property (gobject_class,
                                    PROP_FIT_MODEL,
                                    g_param_spec_boolean ("fit-model",
							  P_("Fit Model"),
							  P_("Whether to request enough space for "
							     "every row in the model"),
							  FALSE,
266
							  GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
267

Matthias Clasen's avatar
Matthias Clasen committed
268
  gtk_widget_class_set_css_name (widget_class, I_("cellview"));
269 270
}

271 272 273 274 275 276 277 278 279 280 281 282
static void
gtk_cell_view_buildable_add_child (GtkBuildable *buildable,
                                   GtkBuilder   *builder,
                                   GObject      *child,
                                   const gchar  *type)
{
  if (GTK_IS_CELL_RENDERER (child))
    _gtk_cell_layout_buildable_add_child (buildable, builder, child, type);
  else
    parent_buildable_iface->add_child (buildable, builder, child, type);
}

283 284 285 286
static void
gtk_cell_view_buildable_init (GtkBuildableIface *iface)
{
  parent_buildable_iface = g_type_interface_peek_parent (iface);
287
  iface->add_child = gtk_cell_view_buildable_add_child;
288 289 290 291
  iface->custom_tag_start = gtk_cell_view_buildable_custom_tag_start;
  iface->custom_tag_end = gtk_cell_view_buildable_custom_tag_end;
}

292 293 294
static void
gtk_cell_view_cell_layout_init (GtkCellLayoutIface *iface)
{
295 296 297
  iface->get_area = gtk_cell_view_cell_layout_get_area;
}

298 299
static void
gtk_cell_view_constructed (GObject *object)
300
{
301 302
  GtkCellView *view = GTK_CELL_VIEW (object);
  GtkCellViewPrivate *priv = view->priv;
303

304
  G_OBJECT_CLASS (gtk_cell_view_parent_class)->constructed (object);
305

306
  if (!priv->area)
307
    {
308 309
      priv->area = gtk_cell_area_box_new ();
      g_object_ref_sink (priv->area);
310 311
    }

312 313
  if (!priv->context)
    priv->context = gtk_cell_area_create_context (priv->area);
314

315
  priv->size_changed_id =
316 317
    g_signal_connect (priv->context, "notify",
		      G_CALLBACK (context_size_changed_cb), view);
318 319
}

320 321 322 323 324 325 326 327 328 329
static void
gtk_cell_view_get_property (GObject    *object,
                            guint       param_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
  GtkCellView *view = GTK_CELL_VIEW (object);

  switch (param_id)
    {
330 331 332
    case PROP_ORIENTATION:
      g_value_set_enum (value, view->priv->orientation);
      break;
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
    case PROP_MODEL:
      g_value_set_object (value, view->priv->model);
      break;
    case PROP_CELL_AREA:
      g_value_set_object (value, view->priv->area);
      break;
    case PROP_CELL_AREA_CONTEXT:
      g_value_set_object (value, view->priv->context);
      break;
    case PROP_DRAW_SENSITIVE:
      g_value_set_boolean (value, view->priv->draw_sensitive);
      break;
    case PROP_FIT_MODEL:
      g_value_set_boolean (value, view->priv->fit_model);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
351 352 353 354 355 356 357 358 359 360
    }
}

static void
gtk_cell_view_set_property (GObject      *object,
                            guint         param_id,
                            const GValue *value,
                            GParamSpec   *pspec)
{
  GtkCellView *view = GTK_CELL_VIEW (object);
361
  GtkCellViewPrivate *priv = view->priv;
362 363
  GtkCellArea *area;
  GtkCellAreaContext *context;
364 365 366

  switch (param_id)
    {
367
    case PROP_ORIENTATION:
368 369 370 371 372 373 374 375
      if (priv->orientation != g_value_get_enum (value))
        {
          priv->orientation = g_value_get_enum (value);
          if (priv->context)
            gtk_cell_area_context_reset (priv->context);
          _gtk_orientable_set_style_classes (GTK_ORIENTABLE (object));
          g_object_notify_by_pspec (object, pspec);
        }
376
      break;
377 378 379
    case PROP_MODEL:
      gtk_cell_view_set_model (view, g_value_get_object (value));
      break;
380 381 382
    case PROP_CELL_AREA:
      /* Construct-only, can only be assigned once */
      area = g_value_get_object (value);
383

384
      if (area)
385 386 387 388 389 390 391 392 393 394
        {
          if (priv->area != NULL)
            {
              g_warning ("cell-area has already been set, ignoring construct property");
              g_object_ref_sink (area);
              g_object_unref (area);
            }
          else
            priv->area = g_object_ref_sink (area);
        }
395 396 397 398
      break;
    case PROP_CELL_AREA_CONTEXT:
      /* Construct-only, can only be assigned once */
      context = g_value_get_object (value);
399

400
      if (context)
401 402 403 404 405 406 407 408 409 410
        {
          if (priv->context != NULL)
            {
              g_warning ("cell-area-context has already been set, ignoring construct property");
              g_object_ref_sink (context);
              g_object_unref (context);
            }
          else
            priv->context = g_object_ref (context);
        }
411 412
      break;

413 414 415 416 417 418 419 420
    case PROP_DRAW_SENSITIVE:
      gtk_cell_view_set_draw_sensitive (view, g_value_get_boolean (value));
      break;

    case PROP_FIT_MODEL:
      gtk_cell_view_set_fit_model (view, g_value_get_boolean (value));
      break;

421
    default:
422 423 424 425 426 427 428 429
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
        break;
    }
}

static void
gtk_cell_view_init (GtkCellView *cellview)
{
430 431
  cellview->priv = gtk_cell_view_get_instance_private (cellview);
  cellview->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
432

433
  gtk_widget_set_has_surface (GTK_WIDGET (cellview), FALSE);
434 435 436 437 438
}

static void
gtk_cell_view_finalize (GObject *object)
{
439 440 441 442
  GtkCellView *cellview = GTK_CELL_VIEW (object);

  if (cellview->priv->displayed_row)
     gtk_tree_row_reference_free (cellview->priv->displayed_row);
443

444
  G_OBJECT_CLASS (gtk_cell_view_parent_class)->finalize (object);
445 446 447
}

static void
448
gtk_cell_view_dispose (GObject *object)
449
{
450
  GtkCellView *cellview = GTK_CELL_VIEW (object);
451

452
  gtk_cell_view_set_model (cellview, NULL);
453

454
  if (cellview->priv->area)
455
    {
456 457
      g_object_unref (cellview->priv->area);
      cellview->priv->area = NULL;
458
    }
459

460
  if (cellview->priv->context)
461
    {
462 463
      g_signal_handler_disconnect (cellview->priv->context, cellview->priv->size_changed_id);

464 465
      g_object_unref (cellview->priv->context);
      cellview->priv->context = NULL;
466
      cellview->priv->size_changed_id = 0;
467
    }
468

469 470
  G_OBJECT_CLASS (gtk_cell_view_parent_class)->dispose (object);
}
471

472
static void
473 474
gtk_cell_view_size_allocate (GtkWidget           *widget,
                             const GtkAllocation *allocation,
475
                             int                  baseline)
476
{
477 478 479
  GtkCellView *cellview;
  GtkCellViewPrivate *priv;
  gint alloc_width, alloc_height, width, height;
480

481 482
  cellview = GTK_CELL_VIEW (widget);
  priv = cellview->priv;
483

484 485
  width = allocation->width;
  height = allocation->height;
486

487
  gtk_cell_area_context_get_allocation (priv->context, &alloc_width, &alloc_height);
488

489 490 491
  /* The first cell view in context is responsible for allocating the context at
   * allocate time (or the cellview has its own context and is not grouped with
   * any other cell views)
492
   *
493 494
   * If the cellview is in "fit model" mode, we assume it's not in context and
   * needs to allocate every time.
495
   */
496
  if (priv->fit_model)
497
    gtk_cell_area_context_allocate (priv->context, width, height);
498
  else if (alloc_width != allocation->width && priv->orientation == GTK_ORIENTATION_HORIZONTAL)
499
    gtk_cell_area_context_allocate (priv->context, width, -1);
500
  else if (alloc_height != allocation->height && priv->orientation == GTK_ORIENTATION_VERTICAL)
501
    gtk_cell_area_context_allocate (priv->context, -1, height);
502 503
}

504 505 506 507 508 509 510 511 512 513 514 515
static void
gtk_cell_view_request_model (GtkCellView        *cellview,
			     GtkTreeIter        *parent,
			     GtkOrientation      orientation,
			     gint                for_size,
			     gint               *minimum_size,
			     gint               *natural_size)
{
  GtkCellViewPrivate *priv = cellview->priv;
  GtkTreeIter         iter;
  gboolean            valid;

516 517 518
  if (!priv->model)
    return;

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
  valid = gtk_tree_model_iter_children (priv->model, &iter, parent);
  while (valid)
    {
      gint min, nat;

      gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);

      if (orientation == GTK_ORIENTATION_HORIZONTAL)
	{
	  if (for_size < 0)
	    gtk_cell_area_get_preferred_width (priv->area, priv->context, 
					       GTK_WIDGET (cellview), &min, &nat);
	  else
	    gtk_cell_area_get_preferred_width_for_height (priv->area, priv->context, 
							  GTK_WIDGET (cellview), for_size, &min, &nat);
	}
      else
	{
	  if (for_size < 0)
	    gtk_cell_area_get_preferred_height (priv->area, priv->context, 
						GTK_WIDGET (cellview), &min, &nat);
	  else
	    gtk_cell_area_get_preferred_height_for_width (priv->area, priv->context, 
							  GTK_WIDGET (cellview), for_size, &min, &nat);
	}

      *minimum_size = MAX (min, *minimum_size);
      *natural_size = MAX (nat, *natural_size);

      /* Recurse into children when they exist */
      gtk_cell_view_request_model (cellview, &iter, orientation, for_size, minimum_size, natural_size);

      valid = gtk_tree_model_iter_next (priv->model, &iter);
    }
}

555 556 557 558 559 560 561 562 563
static GtkSizeRequestMode 
gtk_cell_view_get_request_mode (GtkWidget *widget)
{
  GtkCellView        *cellview = GTK_CELL_VIEW (widget);
  GtkCellViewPrivate *priv = cellview->priv;

  return gtk_cell_area_get_request_mode (priv->area);
}

564
static void
Timm Bäder's avatar
Timm Bäder committed
565
gtk_cell_view_measure (GtkWidget      *widget,
566 567 568 569 570 571
                       GtkOrientation  orientation,
                       int             for_size,
                       int            *minimum,
                       int            *natural,
                       int            *minimum_baseline,
                       int            *natural_baseline)
572 573 574
{
  GtkCellView *cellview;
  GtkCellViewPrivate *priv;
575

576 577
  cellview = GTK_CELL_VIEW (widget);
  priv = cellview->priv;
578

579
  g_signal_handler_block (priv->context, priv->size_changed_id);
580

581
  if (orientation == GTK_ORIENTATION_HORIZONTAL && for_size == -1)
582
    {
583 584 585 586 587 588 589 590 591
      if (priv->fit_model)
        {
          gint min = 0, nat = 0;
          gtk_cell_view_request_model (cellview, NULL, GTK_ORIENTATION_HORIZONTAL, -1, &min, &nat);
        }
      else
        {
          if (cellview->priv->displayed_row)
            gtk_cell_view_set_cell_data (cellview);
592

593 594
          gtk_cell_area_get_preferred_width (priv->area, priv->context, widget, NULL, NULL);
        }
595

596 597 598 599 600 601 602 603 604 605 606 607 608
      gtk_cell_area_context_get_preferred_width (priv->context, minimum, natural);
    }
  else if (orientation == GTK_ORIENTATION_VERTICAL && for_size == -1)
    {
      if (priv->fit_model)
        {
          gint min = 0, nat = 0;
          gtk_cell_view_request_model (cellview, NULL, GTK_ORIENTATION_VERTICAL, -1, &min, &nat);
        }
      else
        {
          if (cellview->priv->displayed_row)
            gtk_cell_view_set_cell_data (cellview);
609

610 611
          gtk_cell_area_get_preferred_height (priv->area, priv->context, widget, NULL, NULL);
        }
612

613 614 615
      gtk_cell_area_context_get_preferred_height (priv->context, minimum, natural);
    }
  else if (orientation == GTK_ORIENTATION_HORIZONTAL && for_size >= 0)
616
    {
617 618 619 620 621 622 623 624 625 626 627 628
      if (priv->fit_model)
        {
          gint min = 0, nat = 0;
          gtk_cell_view_request_model (cellview, NULL, GTK_ORIENTATION_HORIZONTAL, for_size, &min, &nat);

          *minimum = min;
          *natural = nat;
        }
      else
        {
          if (cellview->priv->displayed_row)
            gtk_cell_view_set_cell_data (cellview);
629

630 631 632
          gtk_cell_area_get_preferred_width_for_height (priv->area, priv->context, widget,
                                                        for_size, minimum, natural);
        }
633 634
    }
  else
635 636 637 638 639
   {
      if (priv->fit_model)
        {
          gint min = 0, nat = 0;
          gtk_cell_view_request_model (cellview, NULL, GTK_ORIENTATION_VERTICAL, for_size, &min, &nat);
640

641 642 643 644 645 646 647 648 649 650 651
          *minimum = min;
          *natural = nat;
        }
      else
        {
          if (cellview->priv->displayed_row)
            gtk_cell_view_set_cell_data (cellview);

          gtk_cell_area_get_preferred_height_for_width (priv->area, priv->context, widget,
                                                        for_size, minimum, natural);
        }
652
    }
653

654
  g_signal_handler_unblock (priv->context, priv->size_changed_id);
655 656
}

657 658 659
static void
gtk_cell_view_snapshot (GtkWidget   *widget,
                        GtkSnapshot *snapshot)
660 661 662
{
  GtkCellView *cellview;
  GdkRectangle area;
663
  GtkCellRendererState state;
664

665 666
  cellview = GTK_CELL_VIEW (widget);

667
  /* render cells */
668 669
  area.x = 0;
  area.y = 0;
670 671
  area.width = gtk_widget_get_width (widget);
  area.height = gtk_widget_get_height (widget);
672

673 674 675 676
  /* set cell data (if available) */
  if (cellview->priv->displayed_row)
    gtk_cell_view_set_cell_data (cellview);
  else if (cellview->priv->model)
Timm Bäder's avatar
Timm Bäder committed
677
    return;
678

679
  if (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_PRELIGHT)
680 681 682
    state = GTK_CELL_RENDERER_PRELIT;
  else
    state = 0;
683

684
  /* Render the cells */
685 686
  gtk_cell_area_snapshot (cellview->priv->area, cellview->priv->context,
			  widget, snapshot, &area, &area, state, FALSE);
687

Timm Bäder's avatar
Timm Bäder committed
688

689 690
}

691
static void
692
gtk_cell_view_set_cell_data (GtkCellView *cell_view)
693 694 695 696
{
  GtkTreeIter iter;
  GtkTreePath *path;

697
  g_return_if_fail (cell_view->priv->displayed_row != NULL);
698

699
  path = gtk_tree_row_reference_get_path (cell_view->priv->displayed_row);
700 701 702
  if (!path)
    return;

703
  gtk_tree_model_get_iter (cell_view->priv->model, &iter, path);
704 705
  gtk_tree_path_free (path);

706
  gtk_cell_area_apply_attributes (cell_view->priv->area, 
707 708
				  cell_view->priv->model, 
				  &iter, FALSE, FALSE);
709 710 711 712 713 714 715 716 717 718 719 720 721 722

  if (cell_view->priv->draw_sensitive)
    {
      GList *l, *cells = 
	gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (cell_view->priv->area));

      for (l = cells; l; l = l->next)
	{
	  GObject *renderer = l->data;

	  g_object_set (renderer, "sensitive", TRUE, NULL);
	}
      g_list_free (cells);
    }
723 724 725
}

/* GtkCellLayout implementation */
726 727 728 729
static GtkCellArea *
gtk_cell_view_cell_layout_get_area (GtkCellLayout   *layout)
{
  GtkCellView *cellview = GTK_CELL_VIEW (layout);
730 731 732 733 734 735 736
  GtkCellViewPrivate *priv = cellview->priv;

  if (G_UNLIKELY (!priv->area))
    {
      priv->area = gtk_cell_area_box_new ();
      g_object_ref_sink (priv->area);
    }
Kristian Rietveld's avatar
Kristian Rietveld committed
737

738
  return priv->area;
Kristian Rietveld's avatar
Kristian Rietveld committed
739
}
740

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
/* GtkBuildable implementation */
static gboolean
gtk_cell_view_buildable_custom_tag_start (GtkBuildable  *buildable,
					  GtkBuilder    *builder,
					  GObject       *child,
					  const gchar   *tagname,
					  GMarkupParser *parser,
					  gpointer      *data)
{
  if (parent_buildable_iface->custom_tag_start &&
      parent_buildable_iface->custom_tag_start (buildable, builder, child,
						tagname, parser, data))
    return TRUE;

  return _gtk_cell_layout_buildable_custom_tag_start (buildable, builder, child,
						      tagname, parser, data);
}

static void
gtk_cell_view_buildable_custom_tag_end (GtkBuildable *buildable,
					GtkBuilder   *builder,
					GObject      *child,
					const gchar  *tagname,
764
					gpointer      data)
765
{
766 767 768
  if (_gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname,
						 data))
    return;
769 770 771 772 773
  else if (parent_buildable_iface->custom_tag_end)
    parent_buildable_iface->custom_tag_end (buildable, builder, child, tagname,
					    data);
}

774 775 776 777 778 779 780 781 782 783 784 785
static void
context_size_changed_cb (GtkCellAreaContext  *context,
			 GParamSpec          *pspec,
			 GtkWidget           *view)
{
  if (!strcmp (pspec->name, "minimum-width") ||
      !strcmp (pspec->name, "natural-width") ||
      !strcmp (pspec->name, "minimum-height") ||
      !strcmp (pspec->name, "natural-height"))
    gtk_widget_queue_resize (view);
}

786 787 788 789 790 791 792 793 794 795 796 797 798
static void
row_changed_cb (GtkTreeModel         *model,
		GtkTreePath          *path,
		GtkTreeIter          *iter,
		GtkCellView          *view)
{
  GtkTreePath *row_path;

  if (view->priv->displayed_row)
    {
      row_path = 
	gtk_tree_row_reference_get_path (view->priv->displayed_row);

799 800 801 802
      if (row_path)
	{
	  /* Resize everything in our context if our row changed */
	  if (gtk_tree_path_compare (row_path, path) == 0)
803
	    gtk_cell_area_context_reset (view->priv->context);
804 805 806

	  gtk_tree_path_free (row_path);
	}
807 808
    }
}
809

Matthias Clasen's avatar
Matthias Clasen committed
810 811 812 813 814
/**
 * gtk_cell_view_new:
 *
 * Creates a new #GtkCellView widget.
 *
815
 * Returns: A newly created #GtkCellView widget.
Matthias Clasen's avatar
Matthias Clasen committed
816
 */
817 818
GtkWidget *
gtk_cell_view_new (void)
819
{
820
  GtkCellView *cellview;
821

822
  cellview = g_object_new (gtk_cell_view_get_type (), NULL);
823

824 825
  return GTK_WIDGET (cellview);
}
826

827 828 829 830 831 832 833 834 835 836 837 838 839 840

/**
 * gtk_cell_view_new_with_context:
 * @area: the #GtkCellArea to layout cells
 * @context: the #GtkCellAreaContext in which to calculate cell geometry
 *
 * Creates a new #GtkCellView widget with a specific #GtkCellArea
 * to layout cells and a specific #GtkCellAreaContext.
 *
 * Specifying the same context for a handfull of cells lets
 * the underlying area synchronize the geometry for those cells,
 * in this way alignments with cellviews for other rows are
 * possible.
 *
841
 * Returns: A newly created #GtkCellView widget.
842 843 844 845 846
 */
GtkWidget *
gtk_cell_view_new_with_context (GtkCellArea        *area,
				GtkCellAreaContext *context)
{
847 848 849
  g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
  g_return_val_if_fail (context == NULL || GTK_IS_CELL_AREA_CONTEXT (context), NULL);

850 851 852 853 854 855
  return (GtkWidget *)g_object_new (GTK_TYPE_CELL_VIEW, 
				    "cell-area", area,
				    "cell-area-context", context,
				    NULL);
}

Matthias Clasen's avatar
Matthias Clasen committed
856 857 858 859 860
/**
 * gtk_cell_view_new_with_text:
 * @text: the text to display in the cell view
 *
 * Creates a new #GtkCellView widget, adds a #GtkCellRendererText 
861
 * to it, and makes it show @text.
Matthias Clasen's avatar
Matthias Clasen committed
862
 *
863
 * Returns: A newly created #GtkCellView widget.
Matthias Clasen's avatar
Matthias Clasen committed
864
 */
865 866 867 868 869
GtkWidget *
gtk_cell_view_new_with_text (const gchar *text)
{
  GtkCellView *cellview;
  GtkCellRenderer *renderer;
Javier Jardón's avatar
Javier Jardón committed
870
  GValue value = G_VALUE_INIT;
871 872 873 874

  cellview = GTK_CELL_VIEW (gtk_cell_view_new ());

  renderer = gtk_cell_renderer_text_new ();
875 876
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cellview),
			      renderer, TRUE);
877 878 879

  g_value_init (&value, G_TYPE_STRING);
  g_value_set_string (&value, text);
880
  gtk_cell_view_set_value (cellview, renderer, "text", &value);
881 882 883
  g_value_unset (&value);

  return GTK_WIDGET (cellview);
884 885
}

Matthias Clasen's avatar
Matthias Clasen committed
886 887 888 889 890
/**
 * gtk_cell_view_new_with_markup:
 * @markup: the text to display in the cell view
 *
 * Creates a new #GtkCellView widget, adds a #GtkCellRendererText 
891
 * to it, and makes it show @markup. The text can be
892
 * marked up with the [Pango text markup language][PangoMarkupFormat].
Matthias Clasen's avatar
Matthias Clasen committed
893
 *
894
 * Returns: A newly created #GtkCellView widget.
Matthias Clasen's avatar
Matthias Clasen committed
895
 */
896 897
GtkWidget *
gtk_cell_view_new_with_markup (const gchar *markup)
898
{
899 900
  GtkCellView *cellview;
  GtkCellRenderer *renderer;
Javier Jardón's avatar
Javier Jardón committed
901
  GValue value = G_VALUE_INIT;
902

903
  cellview = GTK_CELL_VIEW (gtk_cell_view_new ());
904

905
  renderer = gtk_cell_renderer_text_new ();
906 907
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cellview),
			      renderer, TRUE);
908 909 910

  g_value_init (&value, G_TYPE_STRING);
  g_value_set_string (&value, markup);
911
  gtk_cell_view_set_value (cellview, renderer, "markup", &value);
912 913 914 915 916
  g_value_unset (&value);

  return GTK_WIDGET (cellview);
}

917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
/**
 * gtk_cell_view_new_with_texture:
 * @texture: the image to display in the cell view
 *
 * Creates a new #GtkCellView widget, adds a #GtkCellRendererPixbuf
 * to it, and makes it show @texture.
 *
 * Returns: A newly created #GtkCellView widget.
 */
GtkWidget *
gtk_cell_view_new_with_texture (GdkTexture *texture)
{
  GtkCellView *cellview;
  GtkCellRenderer *renderer;
  GValue value = G_VALUE_INIT;

  cellview = GTK_CELL_VIEW (gtk_cell_view_new ());

  renderer = gtk_cell_renderer_pixbuf_new ();
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (cellview),
			      renderer, TRUE);

  g_value_init (&value, GDK_TYPE_TEXTURE);
  g_value_set_object (&value, texture);
  gtk_cell_view_set_value (cellview, renderer, "texture", &value);
  g_value_unset (&value);

  return GTK_WIDGET (cellview);
}

Matthias Clasen's avatar
Matthias Clasen committed
947 948 949 950 951 952 953 954 955 956
/**
 * gtk_cell_view_set_value:
 * @cell_view: a #GtkCellView widget
 * @renderer: one of the renderers of @cell_view
 * @property: the name of the property of @renderer to set
 * @value: the new value to set the property to
 * 
 * Sets a property of a cell renderer of @cell_view, and
 * makes sure the display of @cell_view is updated.
 */
957
static void
958
gtk_cell_view_set_value (GtkCellView     *cell_view,
959
                         GtkCellRenderer *renderer,
960
                         const char      *property,
961 962 963 964
                         GValue          *value)
{
  g_object_set_property (G_OBJECT (renderer), property, value);

965 966 967
  /* force resize and redraw */
  gtk_widget_queue_resize (GTK_WIDGET (cell_view));
  gtk_widget_queue_draw (GTK_WIDGET (cell_view));
968 969
}

Matthias Clasen's avatar
Matthias Clasen committed
970 971 972
/**
 * gtk_cell_view_set_model:
 * @cell_view: a #GtkCellView
973
 * @model: (allow-none): a #GtkTreeModel
Matthias Clasen's avatar
Matthias Clasen committed
974 975
 *
 * Sets the model for @cell_view.  If @cell_view already has a model
976
 * set, it will remove it before setting the new model.  If @model is
Matthias Clasen's avatar
Matthias Clasen committed
977 978
 * %NULL, then it will unset the old model.
 */
979
void
980
gtk_cell_view_set_model (GtkCellView  *cell_view,
981 982
                         GtkTreeModel *model)
{
983
  g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
Matthias Clasen's avatar
Matthias Clasen committed
984
  g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
985

986
  if (cell_view->priv->model)
987
    {
988 989 990 991
      g_signal_handler_disconnect (cell_view->priv->model, 
				   cell_view->priv->row_changed_id);
      cell_view->priv->row_changed_id = 0;

992 993 994
      if (cell_view->priv->displayed_row)
        gtk_tree_row_reference_free (cell_view->priv->displayed_row);
      cell_view->priv->displayed_row = NULL;
995

996
      g_object_unref (cell_view->priv->model);
997 998
    }

999
  cell_view->priv->model = model;
1000

1001
  if (cell_view->priv->model)
1002 1003 1004 1005 1006 1007 1008
    {
      g_object_ref (cell_view->priv->model);

      cell_view->priv->row_changed_id = 
	g_signal_connect (cell_view->priv->model, "row-changed",
			  G_CALLBACK (row_changed_cb), cell_view);
    }
1009 1010
}

1011 1012 1013 1014 1015 1016 1017
/**
 * gtk_cell_view_get_model:
 * @cell_view: a #GtkCellView
 *
 * Returns the model for @cell_view. If no model is used %NULL is
 * returned.
 *
1018
 * Returns: (nullable) (transfer none): a #GtkTreeModel used or %NULL
1019 1020 1021 1022 1023 1024 1025 1026 1027
 **/
GtkTreeModel *
gtk_cell_view_get_model (GtkCellView *cell_view)
{
  g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), NULL);

  return cell_view->priv->model;
}

1028 1029 1030
/**
 * gtk_cell_view_set_displayed_row:
 * @cell_view: a #GtkCellView
1031 1032
 * @path: (allow-none): a #GtkTreePath or %NULL to unset.
 *
1033 1034
 * Sets the row of the model that is currently displayed
 * by the #GtkCellView. If the path is unset, then the
1035
 * contents of the cellview “stick” at their last value;
1036 1037 1038 1039
 * this is not normally a desired result, but may be
 * a needed intermediate state if say, the model for
 * the #GtkCellView becomes temporarily empty.
 **/
1040
void
1041
gtk_cell_view_set_displayed_row (GtkCellView *cell_view,
1042 1043
                                 GtkTreePath *path)
{
1044 1045
  g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
  g_return_if_fail (GTK_IS_TREE_MODEL (cell_view->priv->model));
1046

1047 1048
  if (cell_view->priv->displayed_row)
    gtk_tree_row_reference_free (cell_view->priv->displayed_row);
1049

1050 1051 1052 1053 1054 1055 1056
  if (path)
    {
      cell_view->priv->displayed_row =
	gtk_tree_row_reference_new (cell_view->priv->model, path);
    }
  else
    cell_view->priv->displayed_row = NULL;
1057

1058 1059 1060
  /* force resize and redraw */
  gtk_widget_queue_resize (GTK_WIDGET (cell_view));
  gtk_widget_queue_draw (GTK_WIDGET (cell_view));
1061 1062
}

1063 1064 1065 1066 1067 1068 1069 1070
/**
 * gtk_cell_view_get_displayed_row:
 * @cell_view: a #GtkCellView
 *
 * Returns a #GtkTreePath referring to the currently 
 * displayed row. If no row is currently displayed, 
 * %NULL is returned.
 *
1071
 * Returns: (nullable) (transfer full): the currently displayed row or %NULL
1072
 */
1073
GtkTreePath *
1074
gtk_cell_view_get_displayed_row (GtkCellView *cell_view)
1075
{
1076
  g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), NULL);
1077

1078
  if (!cell_view->priv->displayed_row)
1079 1080
    return NULL;

1081 1082 1083
  return gtk_tree_row_reference_get_path (cell_view->priv->displayed_row);
}

1084 1085 1086 1087
/**
 * gtk_cell_view_get_draw_sensitive:
 * @cell_view: a #GtkCellView
 *
1088
 * Gets whether @cell_view is configured to draw all of its
1089 1090
 * cells in a sensitive state.
 *
1091
 * Returns: whether @cell_view draws all of its
1092 1093
 * cells in a sensitive state
 */
1094 1095
gboolean
gtk_cell_view_get_draw_sensitive (GtkCellView     *cell_view)
1096
{
1097
  GtkCellViewPrivate *priv;
1098

1099
  g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), FALSE);
1100

1101 1102 1103
  priv = cell_view->priv;

  return priv->draw_sensitive;
1104
}
1105

1106 1107 1108 1109 1110
/**
 * gtk_cell_view_set_draw_sensitive:
 * @cell_view: a #GtkCellView
 * @draw_sensitive: whether to draw all cells in a sensitive state.
 *
1111
 * Sets whether @cell_view should draw all of its
1112
 * cells in a sensitive state, this is used by #GtkComboBox menus
1113 1114 1115
 * to ensure that rows with insensitive cells that contain
 * children appear sensitive in the parent menu item.
 */
1116 1117 1118
void
gtk_cell_view_set_draw_sensitive (GtkCellView     *cell_view,
				  gboolean         draw_sensitive)
1119
{
1120
  GtkCellViewPrivate *priv;
1121

1122
  g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
1123

1124
  priv = cell_view->priv;
1125

1126 1127 1128
  if (priv->draw_sensitive != draw_sensitive)
    {
      priv->draw_sensitive = draw_sensitive;
1129

1130 1131
      g_object_notify (G_OBJECT (cell_view), "draw-sensitive");
    }
1132 1133
}

1134 1135 1136 1137 1138 1139 1140
/**
 * gtk_cell_view_get_fit_model:
 * @cell_view: a #GtkCellView
 *
 * Gets whether @cell_view is configured to request space
 * to fit the entire #GtkTreeModel.
 *
1141
 * Returns: whether @cell_view requests space to fit
1142 1143
 * the entire #GtkTreeModel.
 */
1144 1145
gboolean
gtk_cell_view_get_fit_model (GtkCellView     *cell_view)
1146
{
1147
  GtkCellViewPrivate *priv;
1148

1149
  g_return_val_if_fail (GTK_IS_CELL_VIEW (cell_view), FALSE);
1150

1151
  priv = cell_view->priv;
1152

1153
  return priv->fit_model;
1154 1155
}

1156 1157 1158 1159 1160 1161 1162 1163
/**
 * gtk_cell_view_set_fit_model:
 * @cell_view: a #GtkCellView
 * @fit_model: whether @cell_view should request space for the whole model.
 *
 * Sets whether @cell_view should request space to fit the entire #GtkTreeModel.
 *
 * This is used by #GtkComboBox to ensure that the cell view displayed on
1164
 * the combo box’s button always gets enough space and does not resize
1165 1166
 * when selection changes.
 */
1167
void
1168 1169
gtk_cell_view_set_fit_model (GtkCellView *cell_view,
                             gboolean     fit_model)
1170
{
1171
  GtkCellViewPrivate *priv;
1172

1173
  g_return_if_fail (GTK_IS_CELL_VIEW (cell_view));
1174

1175
  priv = cell_view->priv;
1176

1177 1178 1179
  if (priv->fit_model != fit_model)
    {
      priv->fit_model = fit_model;
1180

1181
      gtk_cell_area_context_reset (cell_view->priv->context);
1182

1183 1184
      g_object_notify (G_OBJECT (cell_view), "fit-model");
    }
1185
}