gtkcellarea.c 128 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* gtkcellarea.c
 *
 * Copyright (C) 2010 Openismus GmbH
 *
 * Authors:
 *      Tristan Van Berkom <tristanvb@openismus.com>
 *
 * 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.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Javier Jardón's avatar
Javier Jardón committed
19
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 21
 */

22 23
/**
 * SECTION:gtkcellarea
24
 * @Short_Description: An abstract class for laying out GtkCellRenderers
25 26
 * @Title: GtkCellArea
 *
Matthias Clasen's avatar
Matthias Clasen committed
27 28 29 30
 * The #GtkCellArea is an abstract class for #GtkCellLayout widgets
 * (also referred to as "layouting widgets") to interface with an
 * arbitrary number of #GtkCellRenderers and interact with the user
 * for a given #GtkTreeModel row.
31
 *
Matthias Clasen's avatar
Matthias Clasen committed
32
 * The cell area handles events, focus navigation, drawing and
33 34
 * size requests and allocations for a given row of data.
 *
Matthias Clasen's avatar
Matthias Clasen committed
35
 * Usually users dont have to interact with the #GtkCellArea directly
36
 * unless they are implementing a cell-layouting widget themselves.
37 38 39 40 41 42
 *
 * <refsect2 id="cell-area-geometry-management">
 * <title>Requesting area sizes</title>
 * <para>
 * As outlined in <link linkend="geometry-management">GtkWidget's
 * geometry management section</link>, GTK+ uses a height-for-width
Matthias Clasen's avatar
Matthias Clasen committed
43
 * geometry management system to compute the sizes of widgets and user
44 45 46
 * interfaces. #GtkCellArea uses the same semantics to calculate the
 * size of an area for an arbitrary number of #GtkTreeModel rows.
 *
47
 * When requesting the size of a cell area one needs to calculate
48
 * the size for a handful of rows, and this will be done differently by
49
 * different layouting widgets. For instance a #GtkTreeViewColumn
50
 * always lines up the areas from top to bottom while a #GtkIconView
51
 * on the other hand might enforce that all areas received the same
Matthias Clasen's avatar
Matthias Clasen committed
52
 * width and wrap the areas around, requesting height for more cell
53 54
 * areas when allocated less width.
 *
Matthias Clasen's avatar
Matthias Clasen committed
55 56
 * It's also important for areas to maintain some cell
 * alignments with areas rendered for adjacent rows (cells can
57 58
 * appear "columnized" inside an area even when the size of
 * cells are different in each row). For this reason the #GtkCellArea
59
 * uses a #GtkCellAreaContext object to store the alignments
60 61 62 63 64 65
 * and sizes along the way (as well as the overall largest minimum
 * and natural size for all the rows which have been calculated
 * with the said context).
 *
 * The #GtkCellAreaContext is an opaque object specific to the
 * #GtkCellArea which created it (see gtk_cell_area_create_context()).
66
 * The owning cell-layouting widget can create as many contexts as
67
 * it wishes to calculate sizes of rows which should receive the
Matthias Clasen's avatar
Matthias Clasen committed
68
 * same size in at least one orientation (horizontally or vertically),
69
 * However, it's important that the same #GtkCellAreaContext which
70 71
 * was used to request the sizes for a given #GtkTreeModel row be
 * used when rendering or processing events for that row.
72 73 74 75
 *
 * In order to request the width of all the rows at the root level
 * of a #GtkTreeModel one would do the following:
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
76
 *   <title>Requesting the width of a handful of GtkTreeModel rows</title>
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
 *   <programlisting>
 * GtkTreeIter iter;
 * gint        minimum_width;
 * gint        natural_width;
 *
 * valid = gtk_tree_model_get_iter_first (model, &iter);
 * while (valid)
 *   {
 *     gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE);
 *     gtk_cell_area_get_preferred_width (area, context, widget, NULL, NULL);
 *
 *     valid = gtk_tree_model_iter_next (model, &iter);
 *   }
 * gtk_cell_area_context_get_preferred_width (context, &minimum_width, &natural_width);
 *   </programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
93 94
 * Note that in this example it's not important to observe the
 * returned minimum and natural width of the area for each row
95
 * unless the cell-layouting object is actually interested in the
Matthias Clasen's avatar
Matthias Clasen committed
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
 * widths of individual rows. The overall width is however stored
 * in the accompanying #GtkCellAreaContext object and can be consulted
 * at any time.
 *
 * This can be useful since #GtkCellLayout widgets usually have to
 * support requesting and rendering rows in treemodels with an
 * exceedingly large amount of rows. The #GtkCellLayout widget in
 * that case would calculate the required width of the rows in an
 * idle or timeout source (see g_timeout_add()) and when the widget
 * is requested its actual width in #GtkWidgetClass.get_preferred_width()
 * it can simply consult the width accumulated so far in the
 * #GtkCellAreaContext object.
 *
 * A simple example where rows are rendered from top to bottom and
 * take up the full width of the layouting widget would look like:
111
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
112
 *   <title>A typical get_preferred_width() implementation</title>
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
 *   <programlisting>
 * static void
 * foo_get_preferred_width (GtkWidget       *widget,
 *                          gint            *minimum_size,
 *                          gint            *natural_size)
 * {
 *   Foo        *foo  = FOO (widget);
 *   FooPrivate *priv = foo->priv;
 *
 *   foo_ensure_at_least_one_handfull_of_rows_have_been_requested (foo);
 *
 *   gtk_cell_area_context_get_preferred_width (priv->context, minimum_size, natural_size);
 * }
 *   </programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
128 129 130 131 132 133 134 135 136
 * In the above example the Foo widget has to make sure that some
 * row sizes have been calculated (the amount of rows that Foo judged
 * was appropriate to request space for in a single timeout iteration)
 * before simply returning the amount of space required by the area via
 * the #GtkCellAreaContext.
 *
 * Requesting the height for width (or width for height) of an area is
 * a similar task except in this case the #GtkCellAreaContext does not
 * store the data (actually, it does not know how much space the layouting
137
 * widget plans to allocate it for every row. It's up to the layouting
Matthias Clasen's avatar
Matthias Clasen committed
138 139 140 141 142
 * widget to render each row of data with the appropriate height and
 * width which was requested by the #GtkCellArea).
 *
 * In order to request the height for width of all the rows at the
 * root level of a #GtkTreeModel one would do the following:
143
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
144
 *   <title>Requesting the height for width of a handful of GtkTreeModel rows</title>
145 146 147 148 149 150 151 152 153 154 155
 *   <programlisting>
 * GtkTreeIter iter;
 * gint        minimum_height;
 * gint        natural_height;
 * gint        full_minimum_height = 0;
 * gint        full_natural_height = 0;
 *
 * valid = gtk_tree_model_get_iter_first (model, &iter);
 * while (valid)
 *   {
 *     gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
156
 *     gtk_cell_area_get_preferred_height_for_width (area, context, widget,
157 158 159 160 161 162 163 164 165 166 167 168
 *                                                   width, &minimum_height, &natural_height);
 *
 *     if (width_is_for_allocation)
 *        cache_row_height (&iter, minimum_height, natural_height);
 *
 *     full_minimum_height += minimum_height;
 *     full_natural_height += natural_height;
 *
 *     valid = gtk_tree_model_iter_next (model, &iter);
 *   }
 *   </programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
 * Note that in the above example we would need to cache the heights
 * returned for each row so that we would know what sizes to render the
 * areas for each row. However we would only want to really cache the
 * heights if the request is intended for the layouting widgets real
 * allocation.
 *
 * In some cases the layouting widget is requested the height for an
 * arbitrary for_width, this is a special case for layouting widgets
 * who need to request size for tens of thousands  of rows. For this
 * case it's only important that the layouting widget calculate
 * one reasonably sized chunk of rows and return that height
 * synchronously. The reasoning here is that any layouting widget is
 * at least capable of synchronously calculating enough height to fill
 * the screen height (or scrolled window height) in response to a single
 * call to #GtkWidgetClass.get_preferred_height_for_width(). Returning
 * a perfect height for width that is larger than the screen area is
 * inconsequential since after the layouting receives an allocation
186
 * from a scrolled window it simply continues to drive the scrollbar
Matthias Clasen's avatar
Matthias Clasen committed
187 188
 * values while more and more height is required for the row heights
 * that are calculated in the background.
189 190 191 192 193
 * </para>
 * </refsect2>
 * <refsect2 id="cell-area-rendering">
 * <title>Rendering Areas</title>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
194 195 196
 * Once area sizes have been aquired at least for the rows in the
 * visible area of the layouting widget they can be rendered at
 * #GtkWidgetClass.draw() time.
197
 *
Matthias Clasen's avatar
Matthias Clasen committed
198 199
 * A crude example of how to render all the rows at the root level
 * runs as follows:
200
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
201
 *   <title>Requesting the width of a handful of GtkTreeModel rows</title>
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
 *   <programlisting>
 * GtkAllocation allocation;
 * GdkRectangle  cell_area = { 0, };
 * GtkTreeIter   iter;
 * gint          minimum_width;
 * gint          natural_width;
 *
 * gtk_widget_get_allocation (widget, &allocation);
 * cell_area.width = allocation.width;
 *
 * valid = gtk_tree_model_get_iter_first (model, &iter);
 * while (valid)
 *   {
 *     cell_area.height = get_cached_height_for_row (&iter);
 *
 *     gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
218
 *     gtk_cell_area_render (area, context, widget, cr,
219 220 221 222 223 224 225 226
 *                           &cell_area, &cell_area, state_flags, FALSE);
 *
 *     cell_area.y += cell_area.height;
 *
 *     valid = gtk_tree_model_iter_next (model, &iter);
 *   }
 *   </programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
227 228
 * Note that the cached height in this example really depends on how
 * the layouting widget works. The layouting widget might decide to
229
 * give every row its minimum or natural height or, if the model content
Matthias Clasen's avatar
Matthias Clasen committed
230 231
 * is expected to fit inside the layouting widget without scrolling, it
 * would make sense to calculate the allocation for each row at
232
 * #GtkWidget::size-allocate time using gtk_distribute_natural_allocation().
233 234 235 236 237
 * </para>
 * </refsect2>
 * <refsect2 id="cell-area-events-and-focus">
 * <title>Handling Events and Driving Keyboard Focus</title>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
238 239 240 241 242 243 244 245
 * Passing events to the area is as simple as handling events on any
 * normal widget and then passing them to the gtk_cell_area_event()
 * API as they come in. Usually #GtkCellArea is only interested in
 * button events, however some customized derived areas can be implemented
 * who are interested in handling other events. Handling an event can
 * trigger the #GtkCellArea::focus-changed signal to fire; as well as
 * #GtkCellArea::add-editable in the case that an editable cell was
 * clicked and needs to start editing. You can call
246 247
 * gtk_cell_area_stop_editing() at any time to cancel any cell editing
 * that is currently in progress.
248
 *
Matthias Clasen's avatar
Matthias Clasen committed
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
 * The #GtkCellArea drives keyboard focus from cell to cell in a way
 * similar to #GtkWidget. For layouting widgets that support giving
 * focus to cells it's important to remember to pass %GTK_CELL_RENDERER_FOCUSED
 * to the area functions for the row that has focus and to tell the
 * area to paint the focus at render time.
 *
 * Layouting widgets that accept focus on cells should implement the
 * #GtkWidgetClass.focus() virtual method. The layouting widget is always
 * responsible for knowing where #GtkTreeModel rows are rendered inside
 * the widget, so at #GtkWidgetClass.focus() time the layouting widget
 * should use the #GtkCellArea methods to navigate focus inside the area
 * and then observe the GtkDirectionType to pass the focus to adjacent
 * rows and areas.
 *
 * A basic example of how the #GtkWidgetClass.focus() virtual method
 * should be implemented:
265
 * <example>
Matthias Clasen's avatar
Matthias Clasen committed
266
 *   <title>Implementing keyboard focus navigation</title>
267
 *   <programlisting>
268
 * static gboolean
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
 * foo_focus (GtkWidget       *widget,
 *            GtkDirectionType direction)
 * {
 *   Foo        *foo  = FOO (widget);
 *   FooPrivate *priv = foo->priv;
 *   gint        focus_row;
 *   gboolean    have_focus = FALSE;
 *
 *   focus_row = priv->focus_row;
 *
 *   if (!gtk_widget_has_focus (widget))
 *     gtk_widget_grab_focus (widget);
 *
 *   valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, priv->focus_row);
 *   while (valid)
 *     {
 *       gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
 *
 *       if (gtk_cell_area_focus (priv->area, direction))
 *         {
 *            priv->focus_row = focus_row;
 *            have_focus = TRUE;
 *            break;
 *         }
 *       else
 *         {
295 296 297 298 299 300 301
 *           if (direction == GTK_DIR_RIGHT ||
 *               direction == GTK_DIR_LEFT)
 *             break;
 *           else if (direction == GTK_DIR_UP ||
 *                    direction == GTK_DIR_TAB_BACKWARD)
 *            {
 *               if (focus_row == 0)
302 303 304 305 306 307 308 309 310 311
 *                 break;
 *               else
 *                {
 *                   focus_row--;
 *                   valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row);
 *                }
 *             }
 *           else
 *             {
 *               if (focus_row == last_row)
312
 *                 break;
313 314 315 316 317 318 319 320 321 322 323 324
 *               else
 *                 {
 *                   focus_row++;
 *                   valid = gtk_tree_model_iter_next (priv->model, &iter);
 *                 }
 *             }
 *         }
 *     }
 *     return have_focus;
 * }
 *   </programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
325 326
 * Note that the layouting widget is responsible for matching the
 * GtkDirectionType values to the way it lays out its cells.
327 328
 * </para>
 * </refsect2>
329 330 331
 * <refsect2 id="cell-properties">
 * <title>Cell Properties</title>
 * <para>
Matthias Clasen's avatar
Matthias Clasen committed
332 333 334 335 336 337
 * The #GtkCellArea introduces <emphasis>cell properties</emphasis>
 * for #GtkCellRenderers in very much the same way that #GtkContainer
 * introduces <link linkend="child-properties">child properties</link>
 * for #GtkWidgets. This provides some general interfaces for defining
 * the relationship cell areas have with their cells. For instance in a
 * #GtkCellAreaBox a cell might "expand" and receive extra space when
338
 * the area is allocated more than its full natural request, or a cell
Matthias Clasen's avatar
Matthias Clasen committed
339 340 341 342 343 344 345
 * might be configured to "align" with adjacent rows which were requested
 * and rendered with the same #GtkCellAreaContext.
 *
 * Use gtk_cell_area_class_install_cell_property() to install cell
 * properties for a cell area class and gtk_cell_area_class_find_cell_property()
 * or gtk_cell_area_class_list_cell_properties() to get information about
 * existing cell properties.
346 347
 *
 * To set the value of a cell property, use gtk_cell_area_cell_set_property(),
Matthias Clasen's avatar
Matthias Clasen committed
348 349 350
 * gtk_cell_area_cell_set() or gtk_cell_area_cell_set_valist(). To obtain
 * the value of a cell property, use gtk_cell_area_cell_get_property(),
 * gtk_cell_area_cell_get() or gtk_cell_area_cell_get_valist().
351 352
 * </para>
 * </refsect2>
353 354
 */

355 356 357 358 359 360
#include "config.h"

#include <stdarg.h>
#include <string.h>
#include <stdlib.h>

361
#include "gtkintl.h"
362 363
#include "gtkcelllayout.h"
#include "gtkcellarea.h"
364
#include "gtkcellareacontext.h"
365
#include "gtkmarshalers.h"
366
#include "gtkprivate.h"
367

368 369 370
#include <gobject/gvaluecollector.h>


371 372 373
/* GObjectClass */
static void      gtk_cell_area_dispose                             (GObject            *object);
static void      gtk_cell_area_finalize                            (GObject            *object);
374
static void      gtk_cell_area_set_property                        (GObject            *object,
Matthias Clasen's avatar
Matthias Clasen committed
375 376 377
                                                                    guint               prop_id,
                                                                    const GValue       *value,
                                                                    GParamSpec         *pspec);
378
static void      gtk_cell_area_get_property                        (GObject            *object,
Matthias Clasen's avatar
Matthias Clasen committed
379 380 381
                                                                    guint               prop_id,
                                                                    GValue             *value,
                                                                    GParamSpec         *pspec);
382

383
/* GtkCellAreaClass */
384 385 386 387 388 389 390 391 392 393 394 395 396 397
static void      gtk_cell_area_real_add                            (GtkCellArea         *area,
								    GtkCellRenderer     *renderer);
static void      gtk_cell_area_real_remove                         (GtkCellArea         *area,
								    GtkCellRenderer     *renderer);
static void      gtk_cell_area_real_foreach                        (GtkCellArea         *area,
								    GtkCellCallback      callback,
								    gpointer             callback_data);
static void      gtk_cell_area_real_foreach_alloc                  (GtkCellArea         *area,
								    GtkCellAreaContext  *context,
								    GtkWidget           *widget,
								    const GdkRectangle  *cell_area,
								    const GdkRectangle  *background_area,
								    GtkCellAllocCallback callback,
								    gpointer             callback_data);
398
static gint      gtk_cell_area_real_event                          (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
399 400 401 402 403
                                                                    GtkCellAreaContext   *context,
                                                                    GtkWidget            *widget,
                                                                    GdkEvent             *event,
                                                                    const GdkRectangle   *cell_area,
                                                                    GtkCellRendererState  flags);
404
static void      gtk_cell_area_real_render                         (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
405 406 407 408 409 410 411
                                                                    GtkCellAreaContext   *context,
                                                                    GtkWidget            *widget,
                                                                    cairo_t              *cr,
                                                                    const GdkRectangle   *background_area,
                                                                    const GdkRectangle   *cell_area,
                                                                    GtkCellRendererState  flags,
                                                                    gboolean              paint_focus);
412
static void      gtk_cell_area_real_apply_attributes               (GtkCellArea           *area,
Matthias Clasen's avatar
Matthias Clasen committed
413 414 415 416
                                                                    GtkTreeModel          *tree_model,
                                                                    GtkTreeIter           *iter,
                                                                    gboolean               is_expander,
                                                                    gboolean               is_expanded);
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431

static GtkCellAreaContext *gtk_cell_area_real_create_context       (GtkCellArea           *area);
static GtkCellAreaContext *gtk_cell_area_real_copy_context         (GtkCellArea           *area,
								    GtkCellAreaContext    *context);
static GtkSizeRequestMode  gtk_cell_area_real_get_request_mode     (GtkCellArea           *area);
static void      gtk_cell_area_real_get_preferred_width            (GtkCellArea           *area,
								    GtkCellAreaContext    *context,
								    GtkWidget             *widget,
								    gint                  *minimum_width,
								    gint                  *natural_width);
static void      gtk_cell_area_real_get_preferred_height           (GtkCellArea           *area,
								    GtkCellAreaContext    *context,
								    GtkWidget             *widget,
								    gint                  *minimum_height,
								    gint                  *natural_height);
432
static void      gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea           *area,
Matthias Clasen's avatar
Matthias Clasen committed
433 434 435 436 437
                                                                    GtkCellAreaContext    *context,
                                                                    GtkWidget             *widget,
                                                                    gint                   width,
                                                                    gint                  *minimum_height,
                                                                    gint                  *natural_height);
438
static void      gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea           *area,
Matthias Clasen's avatar
Matthias Clasen committed
439 440 441 442 443
                                                                    GtkCellAreaContext    *context,
                                                                    GtkWidget             *widget,
                                                                    gint                   height,
                                                                    gint                  *minimum_width,
                                                                    gint                  *natural_width);
444
static gboolean  gtk_cell_area_real_is_activatable                 (GtkCellArea           *area);
445
static gboolean  gtk_cell_area_real_activate                       (GtkCellArea           *area,
Matthias Clasen's avatar
Matthias Clasen committed
446 447 448 449 450
                                                                    GtkCellAreaContext    *context,
                                                                    GtkWidget             *widget,
                                                                    const GdkRectangle    *cell_area,
                                                                    GtkCellRendererState   flags,
                                                                    gboolean               edit_only);
451 452
static gboolean  gtk_cell_area_real_focus                          (GtkCellArea           *area,
								    GtkDirectionType       direction);
453 454 455 456

/* GtkCellLayoutIface */
static void      gtk_cell_area_cell_layout_init              (GtkCellLayoutIface    *iface);
static void      gtk_cell_area_pack_default                  (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
457 458
                                                              GtkCellRenderer       *renderer,
                                                              gboolean               expand);
459 460
static void      gtk_cell_area_clear                         (GtkCellLayout         *cell_layout);
static void      gtk_cell_area_add_attribute                 (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
461 462 463
                                                              GtkCellRenderer       *renderer,
                                                              const gchar           *attribute,
                                                              gint                   column);
464
static void      gtk_cell_area_set_cell_data_func            (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
465 466 467 468
                                                              GtkCellRenderer       *cell,
                                                              GtkCellLayoutDataFunc  func,
                                                              gpointer               func_data,
                                                              GDestroyNotify         destroy);
469
static void      gtk_cell_area_clear_attributes              (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
470
                                                              GtkCellRenderer       *renderer);
471
static void      gtk_cell_area_reorder                       (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
472 473
                                                              GtkCellRenderer       *cell,
                                                              gint                   position);
474
static GList    *gtk_cell_area_get_cells                     (GtkCellLayout         *cell_layout);
475
static GtkCellArea *gtk_cell_area_get_area                   (GtkCellLayout         *cell_layout);
476

477 478 479
/* GtkBuildableIface */
static void      gtk_cell_area_buildable_init                (GtkBuildableIface     *iface);
static void      gtk_cell_area_buildable_custom_tag_end      (GtkBuildable          *buildable,
Matthias Clasen's avatar
Matthias Clasen committed
480 481 482 483
                                                              GtkBuilder            *builder,
                                                              GObject               *child,
                                                              const gchar           *tagname,
                                                              gpointer              *data);
484

485
/* Used in foreach loop to check if a child renderer is present */
486 487 488 489 490
typedef struct {
  GtkCellRenderer *renderer;
  gboolean         has_renderer;
} HasRendererCheck;

491 492 493 494 495 496
/* Used in foreach loop to get a cell's allocation */
typedef struct {
  GtkCellRenderer *renderer;
  GdkRectangle     allocation;
} RendererAllocationData;

497 498 499 500 501 502 503 504 505 506 507 508
/* Used in foreach loop to render cells */
typedef struct {
  GtkCellArea         *area;
  GtkWidget           *widget;
  cairo_t             *cr;
  GdkRectangle         focus_rect;
  GtkCellRendererState render_flags;
  guint                paint_focus : 1;
  guint                focus_all   : 1;
  guint                first_focus : 1;
} CellRenderData;

509 510 511 512 513 514 515 516
/* Used in foreach loop to get a cell by position */
typedef struct {
  gint             x;
  gint             y;
  GtkCellRenderer *renderer;
  GdkRectangle     cell_area;
} CellByPositionData;

517
/* Attribute/Cell metadata */
518
typedef struct {
519 520 521 522 523
  const gchar *attribute;
  gint         column;
} CellAttribute;

typedef struct {
524
  GSList          *attributes;
525 526 527 528

  GtkCellLayoutDataFunc  func;
  gpointer               data;
  GDestroyNotify         destroy;
529
  GtkCellLayout         *proxy;
530 531 532
} CellInfo;

static CellInfo       *cell_info_new       (GtkCellLayoutDataFunc  func,
Matthias Clasen's avatar
Matthias Clasen committed
533 534
                                            gpointer               data,
                                            GDestroyNotify         destroy);
535 536
static void            cell_info_free      (CellInfo              *info);
static CellAttribute  *cell_attribute_new  (GtkCellRenderer       *renderer,
Matthias Clasen's avatar
Matthias Clasen committed
537 538
                                            const gchar           *attribute,
                                            gint                   column);
539
static void            cell_attribute_free (CellAttribute         *attribute);
540
static gint            cell_attribute_find (CellAttribute         *cell_attribute,
Matthias Clasen's avatar
Matthias Clasen committed
541
                                            const gchar           *attribute);
542

543 544
/* Internal functions/signal emissions */
static void            gtk_cell_area_add_editable     (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
545 546 547
                                                       GtkCellRenderer    *renderer,
                                                       GtkCellEditable    *editable,
                                                       const GdkRectangle *cell_area);
548
static void            gtk_cell_area_remove_editable  (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
549 550
                                                       GtkCellRenderer    *renderer,
                                                       GtkCellEditable    *editable);
551
static void            gtk_cell_area_set_edit_widget  (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
552
                                                       GtkCellEditable    *editable);
553
static void            gtk_cell_area_set_edited_cell  (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
554
                                                       GtkCellRenderer    *renderer);
555 556


Matthias Clasen's avatar
Matthias Clasen committed
557
/* Struct to pass data along while looping over
558
 * cell renderers to apply attributes
559 560 561 562 563
 */
typedef struct {
  GtkCellArea  *area;
  GtkTreeModel *model;
  GtkTreeIter  *iter;
564 565
  gboolean      is_expander;
  gboolean      is_expanded;
566 567 568 569
} AttributeData;

struct _GtkCellAreaPrivate
{
Matthias Clasen's avatar
Matthias Clasen committed
570
  /* The GtkCellArea bookkeeps any connected
571 572
   * attributes in this hash table.
   */
573
  GHashTable      *cell_info;
574

575
  /* Current path is saved as a side-effect
Matthias Clasen's avatar
Matthias Clasen committed
576 577
   * of gtk_cell_area_apply_attributes()
   */
578 579
  gchar           *current_path;

580 581
  /* Current cell being edited and editable widget used */
  GtkCellEditable *edit_widget;
582
  GtkCellRenderer *edited_cell;
583 584 585 586 587

  /* Signal connections to the editable widget */
  gulong           remove_widget_id;

  /* Currently focused cell */
588
  GtkCellRenderer *focus_cell;
589 590 591

  /* Tracking which cells are focus siblings of focusable cells */
  GHashTable      *focus_siblings;
592
};
593

594 595
enum {
  PROP_0,
596
  PROP_FOCUS_CELL,
597 598
  PROP_EDITED_CELL,
  PROP_EDIT_WIDGET
599
};
600

601
enum {
602
  SIGNAL_APPLY_ATTRIBUTES,
603
  SIGNAL_ADD_EDITABLE,
604
  SIGNAL_REMOVE_EDITABLE,
605
  SIGNAL_FOCUS_CHANGED,
606 607 608 609
  LAST_SIGNAL
};

/* Keep the paramspec pool internal, no need to deliver notifications
Matthias Clasen's avatar
Matthias Clasen committed
610 611
 * on cells. at least no perceived need for now
 */
612 613 614 615 616 617
static GParamSpecPool *cell_property_pool = NULL;
static guint           cell_area_signals[LAST_SIGNAL] = { 0 };

#define PARAM_SPEC_PARAM_ID(pspec)              ((pspec)->param_id)
#define PARAM_SPEC_SET_PARAM_ID(pspec, id)      ((pspec)->param_id = (id))

618
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkCellArea, gtk_cell_area, G_TYPE_INITIALLY_UNOWNED,
Matthias Clasen's avatar
Matthias Clasen committed
619 620 621 622
                                  G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
                                                         gtk_cell_area_cell_layout_init)
                                  G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                         gtk_cell_area_buildable_init))
623 624 625 626

static void
gtk_cell_area_init (GtkCellArea *area)
{
627
  GtkCellAreaPrivate *priv;
628

629
  area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area,
Matthias Clasen's avatar
Matthias Clasen committed
630 631
                                            GTK_TYPE_CELL_AREA,
                                            GtkCellAreaPrivate);
632 633
  priv = area->priv;

Matthias Clasen's avatar
Matthias Clasen committed
634 635 636 637
  priv->cell_info = g_hash_table_new_full (g_direct_hash,
                                           g_direct_equal,
                                           NULL,
                                           (GDestroyNotify)cell_info_free);
638

Matthias Clasen's avatar
Matthias Clasen committed
639 640 641 642
  priv->focus_siblings = g_hash_table_new_full (g_direct_hash,
                                                g_direct_equal,
                                                NULL,
                                                (GDestroyNotify)g_list_free);
643

644
  priv->focus_cell         = NULL;
645 646 647 648
  priv->edited_cell        = NULL;
  priv->edit_widget        = NULL;

  priv->remove_widget_id   = 0;
649
}
650

Matthias Clasen's avatar
Matthias Clasen committed
651
static void
652
gtk_cell_area_class_init (GtkCellAreaClass *class)
653
{
654
  GObjectClass *object_class = G_OBJECT_CLASS (class);
Matthias Clasen's avatar
Matthias Clasen committed
655

656
  /* GObjectClass */
657 658 659 660
  object_class->dispose      = gtk_cell_area_dispose;
  object_class->finalize     = gtk_cell_area_finalize;
  object_class->get_property = gtk_cell_area_get_property;
  object_class->set_property = gtk_cell_area_set_property;
661

662
  /* general */
663 664 665 666
  class->add              = gtk_cell_area_real_add;
  class->remove           = gtk_cell_area_real_remove;
  class->foreach          = gtk_cell_area_real_foreach;
  class->foreach_alloc    = gtk_cell_area_real_foreach_alloc;
667
  class->event            = gtk_cell_area_real_event;
668
  class->render           = gtk_cell_area_real_render;
669
  class->apply_attributes = gtk_cell_area_real_apply_attributes;
670 671

  /* geometry */
672 673 674 675 676
  class->create_context                 = gtk_cell_area_real_create_context;
  class->copy_context                   = gtk_cell_area_real_copy_context;
  class->get_request_mode               = gtk_cell_area_real_get_request_mode;
  class->get_preferred_width            = gtk_cell_area_real_get_preferred_width;
  class->get_preferred_height           = gtk_cell_area_real_get_preferred_height;
677 678
  class->get_preferred_height_for_width = gtk_cell_area_real_get_preferred_height_for_width;
  class->get_preferred_width_for_height = gtk_cell_area_real_get_preferred_width_for_height;
679

680
  /* focus */
681 682
  class->is_activatable = gtk_cell_area_real_is_activatable;
  class->activate       = gtk_cell_area_real_activate;
683
  class->focus          = gtk_cell_area_real_focus;
684 685

  /* Signals */
686 687 688 689 690 691 692 693 694
  /**
   * GtkCellArea::apply-attributes:
   * @area: the #GtkCellArea to apply the attributes to
   * @model: the #GtkTreeModel to apply the attributes from
   * @iter: the #GtkTreeIter indicating which row to apply the attributes of
   * @is_expander: whether the view shows children for this row
   * @is_expanded: whether the view is currently showing the children of this row
   *
   * This signal is emitted whenever applying attributes to @area from @model
695 696
   *
   * Since: 3.0
697 698 699
   */
  cell_area_signals[SIGNAL_APPLY_ATTRIBUTES] =
    g_signal_new (I_("apply-attributes"),
Matthias Clasen's avatar
Matthias Clasen committed
700 701 702 703 704 705 706 707 708 709
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCellAreaClass, apply_attributes),
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT_BOXED_BOOLEAN_BOOLEAN,
                  G_TYPE_NONE, 4,
                  GTK_TYPE_TREE_MODEL,
                  GTK_TYPE_TREE_ITER,
                  G_TYPE_BOOLEAN,
                  G_TYPE_BOOLEAN);
710 711
  g_signal_set_va_marshaller (cell_area_signals[SIGNAL_APPLY_ATTRIBUTES], G_TYPE_FROM_CLASS (class),
                              _gtk_marshal_VOID__OBJECT_BOXED_BOOLEAN_BOOLEANv);
712

713 714 715 716 717 718 719
  /**
   * GtkCellArea::add-editable:
   * @area: the #GtkCellArea where editing started
   * @renderer: the #GtkCellRenderer that started the edited
   * @editable: the #GtkCellEditable widget to add
   * @cell_area: the #GtkWidget relative #GdkRectangle coordinates
   *             where @editable should be added
720
   * @path: the #GtkTreePath string this edit was initiated for
721 722
   *
   * Indicates that editing has started on @renderer and that @editable
723
   * should be added to the owning cell-layouting widget at @cell_area.
724 725
   *
   * Since: 3.0
726
   */
727 728
  cell_area_signals[SIGNAL_ADD_EDITABLE] =
    g_signal_new (I_("add-editable"),
Matthias Clasen's avatar
Matthias Clasen committed
729 730 731 732 733 734 735 736 737 738
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0, /* No class closure here */
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT_OBJECT_BOXED_STRING,
                  G_TYPE_NONE, 4,
                  GTK_TYPE_CELL_RENDERER,
                  GTK_TYPE_CELL_EDITABLE,
                  GDK_TYPE_RECTANGLE,
                  G_TYPE_STRING);
739

740 741 742 743 744 745 746 747

  /**
   * GtkCellArea::remove-editable:
   * @area: the #GtkCellArea where editing finished
   * @renderer: the #GtkCellRenderer that finished editeding
   * @editable: the #GtkCellEditable widget to remove
   *
   * Indicates that editing finished on @renderer and that @editable
748
   * should be removed from the owning cell-layouting widget.
749 750
   *
   * Since: 3.0
751
   */
752 753
  cell_area_signals[SIGNAL_REMOVE_EDITABLE] =
    g_signal_new (I_("remove-editable"),
Matthias Clasen's avatar
Matthias Clasen committed
754 755 756 757 758 759 760 761
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0, /* No class closure here */
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT_OBJECT,
                  G_TYPE_NONE, 2,
                  GTK_TYPE_CELL_RENDERER,
                  GTK_TYPE_CELL_EDITABLE);
762

763 764 765 766 767 768 769 770 771 772 773 774 775 776
  /**
   * GtkCellArea::focus-changed:
   * @area: the #GtkCellArea where focus changed
   * @renderer: the #GtkCellRenderer that has focus
   * @path: the current #GtkTreePath string set for @area
   *
   * Indicates that focus changed on this @area. This signal
   * is emitted either as a result of focus handling or event
   * handling.
   *
   * It's possible that the signal is emitted even if the
   * currently focused renderer did not change, this is
   * because focus may change to the same renderer in the
   * same cell area for a different row of data.
777 778
   *
   * Since: 3.0
779
   */
780 781
  cell_area_signals[SIGNAL_FOCUS_CHANGED] =
    g_signal_new (I_("focus-changed"),
Matthias Clasen's avatar
Matthias Clasen committed
782 783 784 785 786 787 788 789
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_FIRST,
                  0, /* No class closure here */
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT_STRING,
                  G_TYPE_NONE, 2,
                  GTK_TYPE_CELL_RENDERER,
                  G_TYPE_STRING);
790

791
  /* Properties */
792 793 794 795
  /**
   * GtkCellArea:focus-cell:
   *
   * The cell in the area that currently has focus
796 797
   *
   * Since: 3.0
798
   */
799 800 801
  g_object_class_install_property (object_class,
                                   PROP_FOCUS_CELL,
                                   g_param_spec_object
Matthias Clasen's avatar
Matthias Clasen committed
802 803 804 805 806
                                   ("focus-cell",
                                    P_("Focus Cell"),
                                    P_("The cell which currently has focus"),
                                    GTK_TYPE_CELL_RENDERER,
                                    GTK_PARAM_READWRITE));
807

808 809 810 811 812 813 814
  /**
   * GtkCellArea:edited-cell:
   *
   * The cell in the area that is currently edited
   *
   * This property is read-only and only changes as
   * a result of a call gtk_cell_area_activate_cell().
815 816
   *
   * Since: 3.0
817
   */
818 819 820
  g_object_class_install_property (object_class,
                                   PROP_EDITED_CELL,
                                   g_param_spec_object
Matthias Clasen's avatar
Matthias Clasen committed
821 822 823 824 825
                                   ("edited-cell",
                                    P_("Edited Cell"),
                                    P_("The cell which is currently being edited"),
                                    GTK_TYPE_CELL_RENDERER,
                                    G_PARAM_READABLE));
826

827 828 829 830 831 832 833
  /**
   * GtkCellArea:edit-widget:
   *
   * The widget currently editing the edited cell
   *
   * This property is read-only and only changes as
   * a result of a call gtk_cell_area_activate_cell().
834 835
   *
   * Since: 3.0
836
   */
837 838 839
  g_object_class_install_property (object_class,
                                   PROP_EDIT_WIDGET,
                                   g_param_spec_object
Matthias Clasen's avatar
Matthias Clasen committed
840 841 842
                                   ("edit-widget",
                                    P_("Edit Widget"),
                                    P_("The widget currently editing the edited cell"),
843
                                    GTK_TYPE_CELL_EDITABLE,
Matthias Clasen's avatar
Matthias Clasen committed
844
                                    G_PARAM_READABLE));
845

846
  /* Pool for Cell Properties */
847 848 849
  if (!cell_property_pool)
    cell_property_pool = g_param_spec_pool_new (FALSE);

850 851 852 853
  g_type_class_add_private (object_class, sizeof (GtkCellAreaPrivate));
}

/*************************************************************
854
 *                    CellInfo Basics                        *
855
 *************************************************************/
856 857
static CellInfo *
cell_info_new (GtkCellLayoutDataFunc  func,
Matthias Clasen's avatar
Matthias Clasen committed
858 859
               gpointer               data,
               GDestroyNotify         destroy)
860
{
861 862 863 864 865
  CellInfo *info = g_slice_new0 (CellInfo);

  info->func     = func;
  info->data     = data;
  info->destroy  = destroy;
866

867
  return info;
868 869 870
}

static void
871 872 873 874 875 876 877 878 879 880 881 882 883
cell_info_free (CellInfo *info)
{
  if (info->destroy)
    info->destroy (info->data);

  g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL);
  g_slist_free (info->attributes);

  g_slice_free (CellInfo, info);
}

static CellAttribute  *
cell_attribute_new  (GtkCellRenderer       *renderer,
Matthias Clasen's avatar
Matthias Clasen committed
884 885
                     const gchar           *attribute,
                     gint                   column)
886
{
887 888 889 890
  GParamSpec *pspec;

  /* Check if the attribute really exists and point to
   * the property string installed on the cell renderer
Matthias Clasen's avatar
Matthias Clasen committed
891
   * class (dont dup the string)
892 893 894 895 896 897
   */
  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (renderer), attribute);

  if (pspec)
    {
      CellAttribute *cell_attribute = g_slice_new (CellAttribute);
898

899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
      cell_attribute->attribute = pspec->name;
      cell_attribute->column    = column;

      return cell_attribute;
    }

  return NULL;
}

static void
cell_attribute_free (CellAttribute *attribute)
{
  g_slice_free (CellAttribute, attribute);
}

914
/* GCompareFunc for g_slist_find_custom() */
915 916
static gint
cell_attribute_find (CellAttribute *cell_attribute,
Matthias Clasen's avatar
Matthias Clasen committed
917
                     const gchar   *attribute)
918 919
{
  return g_strcmp0 (cell_attribute->attribute, attribute);
920 921
}

922 923 924 925 926 927 928 929 930 931
/*************************************************************
 *                      GObjectClass                         *
 *************************************************************/
static void
gtk_cell_area_finalize (GObject *object)
{
  GtkCellArea        *area   = GTK_CELL_AREA (object);
  GtkCellAreaPrivate *priv   = area->priv;

  /* All cell renderers should already be removed at this point,
Matthias Clasen's avatar
Matthias Clasen committed
932
   * just kill our (empty) hash tables here.
933 934
   */
  g_hash_table_destroy (priv->cell_info);
935
  g_hash_table_destroy (priv->focus_siblings);
936

937 938
  g_free (priv->current_path);

939 940 941 942 943 944 945 946
  G_OBJECT_CLASS (gtk_cell_area_parent_class)->finalize (object);
}


static void
gtk_cell_area_dispose (GObject *object)
{
  /* This removes every cell renderer that may be added to the GtkCellArea,
Matthias Clasen's avatar
Matthias Clasen committed
947
   * subclasses should be breaking references to the GtkCellRenderers
948 949 950 951
   * at this point.
   */
  gtk_cell_layout_clear (GTK_CELL_LAYOUT (object));

952
  /* Remove any ref to a focused/edited cell */
953
  gtk_cell_area_set_focus_cell (GTK_CELL_AREA (object), NULL);
954
  gtk_cell_area_set_edited_cell (GTK_CELL_AREA (object), NULL);
955
  gtk_cell_area_set_edit_widget (GTK_CELL_AREA (object), NULL);
956

957 958 959
  G_OBJECT_CLASS (gtk_cell_area_parent_class)->dispose (object);
}

960 961
static void
gtk_cell_area_set_property (GObject       *object,
Matthias Clasen's avatar
Matthias Clasen committed
962 963 964
                            guint          prop_id,
                            const GValue  *value,
                            GParamSpec    *pspec)
965 966 967 968 969
{
  GtkCellArea *area = GTK_CELL_AREA (object);

  switch (prop_id)
    {
970 971 972
    case PROP_FOCUS_CELL:
      gtk_cell_area_set_focus_cell (area, (GtkCellRenderer *)g_value_get_object (value));
      break;
973 974 975 976 977 978 979 980
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_cell_area_get_property (GObject     *object,
Matthias Clasen's avatar
Matthias Clasen committed
981 982 983
                            guint        prop_id,
                            GValue      *value,
                            GParamSpec  *pspec)
984 985 986 987 988 989
{
  GtkCellArea        *area = GTK_CELL_AREA (object);
  GtkCellAreaPrivate *priv = area->priv;

  switch (prop_id)
    {
990 991 992
    case PROP_FOCUS_CELL:
      g_value_set_object (value, priv->focus_cell);
      break;
993 994 995 996 997 998
    case PROP_EDITED_CELL:
      g_value_set_object (value, priv->edited_cell);
      break;
    case PROP_EDIT_WIDGET:
      g_value_set_object (value, priv->edit_widget);
      break;
999 1000 1001 1002 1003
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}
1004 1005 1006 1007

/*************************************************************
 *                    GtkCellAreaClass                       *
 *************************************************************/
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
static void
gtk_cell_area_real_add (GtkCellArea         *area,
			GtkCellRenderer     *renderer)
{
    g_warning ("GtkCellAreaClass::add not implemented for `%s'",
               g_type_name (G_TYPE_FROM_INSTANCE (area)));
}

static void      
gtk_cell_area_real_remove (GtkCellArea         *area,
			   GtkCellRenderer     *renderer)
{
    g_warning ("GtkCellAreaClass::remove not implemented for `%s'",
               g_type_name (G_TYPE_FROM_INSTANCE (area)));
}

static void
gtk_cell_area_real_foreach (GtkCellArea         *area,
			    GtkCellCallback      callback,
			    gpointer             callback_data)
{
    g_warning ("GtkCellAreaClass::foreach not implemented for `%s'",
               g_type_name (G_TYPE_FROM_INSTANCE (area)));
}

static void
gtk_cell_area_real_foreach_alloc (GtkCellArea         *area,
				  GtkCellAreaContext  *context,
				  GtkWidget           *widget,
				  const GdkRectangle  *cell_area,
				  const GdkRectangle  *background_area,
				  GtkCellAllocCallback callback,
				  gpointer             callback_data)
{
    g_warning ("GtkCellAreaClass::foreach_alloc not implemented for `%s'",
               g_type_name (G_TYPE_FROM_INSTANCE (area)));
}

1046 1047
static gint
gtk_cell_area_real_event (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
1048 1049 1050 1051 1052
                          GtkCellAreaContext   *context,
                          GtkWidget            *widget,
                          GdkEvent             *event,
                          const GdkRectangle   *cell_area,
                          GtkCellRendererState  flags)
1053
{
1054
  GtkCellAreaPrivate *priv = area->priv;
1055
  gboolean            retval = FALSE;
1056

1057
  if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0)
1058
    {
1059
      GdkEventKey *key_event = (GdkEventKey *)event;
1060

1061 1062
      /* Cancel any edits in progress */
      if (priv->edited_cell && (key_event->keyval == GDK_KEY_Escape))
Matthias Clasen's avatar
Matthias Clasen committed
1063 1064 1065 1066
        {
          gtk_cell_area_stop_editing (area, TRUE);
          retval = TRUE;
        }
1067
    }
1068 1069 1070
  else if (event->type == GDK_BUTTON_PRESS)
    {
      GdkEventButton *button_event = (GdkEventButton *)event;
1071

1072
      if (button_event->button == GDK_BUTTON_PRIMARY)
Matthias Clasen's avatar
Matthias Clasen committed
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
        {
          GtkCellRenderer *renderer = NULL;
          GtkCellRenderer *focus_renderer;
          GdkRectangle     alloc_area;
          gint             event_x, event_y;

          /* We may need some semantics to tell us the offset of the event
           * window we are handling events for (i.e. GtkTreeView has a bin_window) */
          event_x = button_event->x;
          event_y = button_event->y;

          /* Dont try to search for an event coordinate that is not in the area, that will
           * trigger a runtime warning.
           */
          if (event_x >= cell_area->x && event_x <= cell_area->x + cell_area->width &&
              event_y >= cell_area->y && event_y <= cell_area->y + cell_area->height)
            renderer =
              gtk_cell_area_get_cell_at_position (area, context, widget,
                                                  cell_area, event_x, event_y,
                                                  &alloc_area);

          if (renderer)
            {
              focus_renderer = gtk_cell_area_get_focus_from_sibling (area, renderer);
              if (!focus_renderer)
                focus_renderer = renderer;

              /* If we're already editing, cancel it and set focus */
              if (gtk_cell_area_get_edited_cell (area))
                {
                  /* XXX Was it really canceled in this case ? */
                  gtk_cell_area_stop_editing (area, TRUE);
                  gtk_cell_area_set_focus_cell (area, focus_renderer);
                  retval = TRUE;
                }
              else
                {
                  /* If we are activating via a focus sibling,
                   * we need to fetch the right cell area for the real event renderer */
                  if (focus_renderer != renderer)
                    gtk_cell_area_get_cell_allocation (area, context, widget, focus_renderer,
                                                       cell_area, &alloc_area);

                  gtk_cell_area_set_focus_cell (area, focus_renderer);
                  retval = gtk_cell_area_activate_cell (area, widget, focus_renderer,
                                                        event, &alloc_area, flags);
                }
            }
        }
1122 1123 1124
    }

  return retval;
1125 1126
}

1127 1128
static gboolean
render_cell (GtkCellRenderer        *renderer,
Matthias Clasen's avatar
Matthias Clasen committed
1129 1130 1131
             const GdkRectangle     *cell_area,
             const GdkRectangle     *cell_background,
             CellRenderData         *data)
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
{
  GtkCellRenderer      *focus_cell;
  GtkCellRendererState  flags;
  GdkRectangle          inner_area;

  focus_cell = gtk_cell_area_get_focus_cell (data->area);
  flags      = data->render_flags;

  gtk_cell_area_inner_cell_area (data->area, data->widget, cell_area, &inner_area);

  if ((flags & GTK_CELL_RENDERER_FOCUSED) &&
Matthias Clasen's avatar
Matthias Clasen committed
1143 1144 1145 1146
      (data->focus_all ||
       (focus_cell &&
        (renderer == focus_cell ||
         gtk_cell_area_is_focus_sibling (data->area, focus_cell, renderer)))))
1147
    {
1148
      gint focus_line_width;
1149 1150 1151 1152
      GdkRectangle cell_focus;

      gtk_cell_renderer_get_aligned_area (renderer, data->widget, flags, &inner_area, &cell_focus);

1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
      gtk_widget_style_get (data->widget,
                            "focus-line-width", &focus_line_width,
                            NULL);

      /* The focus rectangle is located around the aligned area of the cell */
      cell_focus.x -= focus_line_width;
      cell_focus.y -= focus_line_width;
      cell_focus.width += 2 * focus_line_width;
      cell_focus.height += 2 * focus_line_width;

1163
      if (data->first_focus)
Matthias Clasen's avatar
Matthias Clasen committed
1164 1165 1166 1167
        {
          data->first_focus = FALSE;
          data->focus_rect  = cell_focus;
        }
1168
      else
Matthias Clasen's avatar
Matthias Clasen committed
1169 1170 1171
        {
          gdk_rectangle_union (&data->focus_rect, &cell_focus, &data->focus_rect);
        }
1172 1173 1174
    }

  gtk_cell_renderer_render (renderer, data->cr, data->widget,
Matthias Clasen's avatar
Matthias Clasen committed
1175
                            cell_background, &inner_area, flags);
1176 1177 1178 1179 1180 1181

  return FALSE;
}

static void
gtk_cell_area_real_render (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197
                           GtkCellAreaContext   *context,
                           GtkWidget            *widget,
                           cairo_t              *cr,
                           const GdkRectangle   *background_area,
                           const GdkRectangle   *cell_area,
                           GtkCellRendererState  flags,
                           gboolean              paint_focus)
{
  CellRenderData render_data =
    {
      area,
      widget,
      cr,
      { 0, },
      flags,
      paint_focus,
1198 1199 1200 1201
      FALSE, TRUE
    };

  /* Make sure we dont paint a focus rectangle while there
Matthias Clasen's avatar
Matthias Clasen committed
1202
   * is an editable widget in play
1203 1204 1205 1206
   */
  if (gtk_cell_area_get_edited_cell (area))
    render_data.paint_focus = FALSE;

1207 1208 1209
  if (!gtk_widget_has_visible_focus (widget))
    render_data.paint_focus = FALSE;

1210 1211
  /* If no cell can activate but the caller wants focus painted,
   * then we paint focus around all cells */
Matthias Clasen's avatar
Matthias Clasen committed
1212
  if ((flags & GTK_CELL_RENDERER_FOCUSED) != 0 && paint_focus &&
1213 1214 1215
      !gtk_cell_area_is_activatable (area))
    render_data.focus_all = TRUE;

Matthias Clasen's avatar
Matthias Clasen committed
1216 1217
  gtk_cell_area_foreach_alloc (area, context, widget, cell_area, background_area,
                               (GtkCellAllocCallback)render_cell, &render_data);
1218

Matthias Clasen's avatar
Matthias Clasen committed
1219 1220
  if (render_data.paint_focus &&
      render_data.focus_rect.width != 0 &&
1221 1222
      render_data.focus_rect.height != 0)
    {
1223 1224 1225 1226 1227 1228 1229 1230
      GtkStyleContext *style_context;
      GtkStateFlags renderer_state = 0;

      style_context = gtk_widget_get_style_context (widget);
      gtk_style_context_save (style_context);

      renderer_state = gtk_cell_renderer_get_state (NULL, widget, flags);
      gtk_style_context_set_state (style_context, renderer_state);
1231

1232 1233 1234 1235 1236
      cairo_save (cr);

      gdk_cairo_rectangle (cr, background_area);
      cairo_clip (cr);

1237 1238 1239
      gtk_render_focus (style_context, cr,
                        render_data.focus_rect.x,     render_data.focus_rect.y,
                        render_data.focus_rect.width, render_data.focus_rect.height);
1240

1241
      gtk_style_context_restore (style_context);
1242
      cairo_restore (cr);
1243 1244 1245
    }
}

1246 1247
static void
apply_cell_attributes (GtkCellRenderer *renderer,
Matthias Clasen's avatar
Matthias Clasen committed
1248 1249
                       CellInfo        *info,
                       AttributeData   *data)
1250 1251 1252
{
  CellAttribute *attribute;
  GSList        *list;
Javier Jardón's avatar
Javier Jardón committed
1253
  GValue         value = G_VALUE_INIT;
1254 1255 1256 1257 1258
  gboolean       is_expander;
  gboolean       is_expanded;

  g_object_freeze_notify (G_OBJECT (renderer));

Matthias Clasen's avatar
Matthias Clasen committed
1259
  /* Whether a row expands or is presently expanded can only be
1260 1261 1262 1263 1264 1265
   * provided by the view (as these states can vary across views
   * accessing the same model).
   */
  g_object_get (renderer, "is-expander", &is_expander, NULL);
  if (is_expander != data->is_expander)
    g_object_set (renderer, "is-expander", data->is_expander, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
1266

1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283
  g_object_get (renderer, "is-expanded", &is_expanded, NULL);
  if (is_expanded != data->is_expanded)
    g_object_set (renderer, "is-expanded", data->is_expanded, NULL);

  /* Apply the attributes directly to the renderer */
  for (list = info->attributes; list; list = list->next)
    {
      attribute = list->data;

      gtk_tree_model_get_value (data->model, data->iter, attribute->column, &value);
      g_object_set_property (G_OBJECT (renderer), attribute->attribute, &value);
      g_value_unset (&value);
    }

  /* Call any GtkCellLayoutDataFunc that may have been set by the user
   */
  if (info->func)
1284 1285
    info->func (info->proxy ? info->proxy : GTK_CELL_LAYOUT (data->area), renderer,
		data->model, data->iter, info->data);
1286 1287 1288 1289 1290 1291

  g_object_thaw_notify (G_OBJECT (renderer));
}

static void
gtk_cell_area_real_apply_attributes (GtkCellArea           *area,
Matthias Clasen's avatar
Matthias Clasen committed
1292 1293 1294 1295
                                     GtkTreeModel          *tree_model,
                                     GtkTreeIter           *iter,
                                     gboolean               is_expander,
                                     gboolean               is_expanded)
1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
{

  GtkCellAreaPrivate *priv;
  AttributeData       data;
  GtkTreePath        *path;

  priv = area->priv;

  /* Feed in data needed to apply to every renderer */
  data.area        = area;
  data.model       = tree_model;
  data.iter        = iter;
  data.is_expander = is_expander;
  data.is_expanded = is_expanded;

  /* Go over any cells that have attributes or custom GtkCellLayoutDataFuncs and
   * apply the data from the treemodel */
  g_hash_table_foreach (priv->cell_info, (GHFunc)apply_cell_attributes, &data);

  /* Update the currently applied path */
  g_free (priv->current_path);
  path               = gtk_tree_model_get_path (tree_model, iter);
  priv->current_path = gtk_tree_path_to_string (path);
  gtk_tree_path_free (path);
}

1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369
static GtkCellAreaContext *
gtk_cell_area_real_create_context (GtkCellArea *area)
{
  g_warning ("GtkCellAreaClass::create_context not implemented for `%s'",
             g_type_name (G_TYPE_FROM_INSTANCE (area)));

  return NULL;
}

static GtkCellAreaContext *
gtk_cell_area_real_copy_context (GtkCellArea        *area,
				 GtkCellAreaContext *context)
{
  g_warning ("GtkCellAreaClass::copy_context not implemented for `%s'",
             g_type_name (G_TYPE_FROM_INSTANCE (area)));

  return NULL;
}

static GtkSizeRequestMode
gtk_cell_area_real_get_request_mode (GtkCellArea *area)
{
  /* By default cell areas are height-for-width. */
  return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

static void
gtk_cell_area_real_get_preferred_width (GtkCellArea        *area,
					GtkCellAreaContext *context,
					GtkWidget          *widget,
					gint               *minimum_width,
					gint               *natural_width)
{
  g_warning ("GtkCellAreaClass::get_preferred_width not implemented for `%s'",
	     g_type_name (G_TYPE_FROM_INSTANCE (area)));
}

static void
gtk_cell_area_real_get_preferred_height (GtkCellArea        *area,
					 GtkCellAreaContext *context,
					 GtkWidget          *widget,
					 gint               *minimum_height,
					 gint               *natural_height)
{
  g_warning ("GtkCellAreaClass::get_preferred_height not implemented for `%s'",
	     g_type_name (G_TYPE_FROM_INSTANCE (area)));
}

1370 1371
static void
gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
1372 1373
                                                   GtkCellAreaContext *context,
                                                   GtkWidget          *widget,
1374
						   gint                width,
Matthias Clasen's avatar
Matthias Clasen committed
1375 1376
                                                   gint               *minimum_height,
                                                   gint               *natural_height)
1377 1378
{
  /* If the area doesnt do height-for-width, fallback on base preferred height */
1379
  GTK_CELL_AREA_GET_CLASS (area)->get_preferred_height (area, context, widget, minimum_height, natural_height);
1380 1381 1382 1383
}

static void
gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
1384 1385 1386 1387 1388
                                                   GtkCellAreaContext *context,
                                                   GtkWidget          *widget,
                                                   gint                height,
                                                   gint               *minimum_width,
                                                   gint               *natural_width)
1389 1390
{
  /* If the area doesnt do width-for-height, fallback on base preferred width */
1391
  GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, context, widget, minimum_width, natural_width);
1392 1393
}

1394
static gboolean
1395
get_is_activatable (GtkCellRenderer *renderer,
Matthias Clasen's avatar
Matthias Clasen committed
1396
                    gboolean        *activatable)
1397 1398
{

1399 1400
  if (gtk_cell_renderer_is_activatable (renderer))
    *activatable = TRUE;
1401 1402

  return *activatable;
1403 1404
}

1405
static gboolean
1406
gtk_cell_area_real_is_activatable (GtkCellArea *area)
1407
{
1408
  gboolean activatable = FALSE;
1409

1410 1411
  /* Checks if any renderer can focus for the currently applied
   * attributes.
1412 1413 1414 1415
   *
   * Subclasses can override this in the case that they are also
   * rendering widgets as well as renderers.
   */
1416
  gtk_cell_area_foreach (area, (GtkCellCallback)get_is_activatable, &activatable);
1417

1418
  return activatable;
1419 1420 1421 1422
}

static gboolean
gtk_cell_area_real_activate (GtkCellArea         *area,
Matthias Clasen's avatar
Matthias Clasen committed
1423 1424 1425 1426 1427
                             GtkCellAreaContext  *context,
                             GtkWidget           *widget,
                             const GdkRectangle  *cell_area,
                             GtkCellRendererState flags,
                             gboolean             edit_only)
1428 1429
{
  GtkCellAreaPrivate *priv = area->priv;
1430
  GdkRectangle        renderer_area;
1431
  GtkCellRenderer    *activate_cell = NULL;
1432
  GtkCellRendererMode mode;
1433 1434

  if (priv->focus_cell)
1435 1436 1437 1438
    {
      g_object_get (priv->focus_cell, "mode", &mode, NULL);

      if (gtk_cell_renderer_get_visible (priv->focus_cell) &&
Matthias Clasen's avatar
Matthias Clasen committed
1439 1440 1441
          (edit_only ? mode == GTK_CELL_RENDERER_MODE_EDITABLE :
           mode != GTK_CELL_RENDERER_MODE_INERT))
        activate_cell = priv->focus_cell;
1442
    }
1443 1444 1445 1446 1447 1448 1449 1450 1451
  else
    {
      GList *cells, *l;

      /* GtkTreeView sometimes wants to activate a cell when no
       * cells are in focus.
       */
      cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
      for (l = cells; l && !activate_cell; l = l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1452 1453
        {
          GtkCellRenderer *renderer = l->data;
1454

Matthias Clasen's avatar
Matthias Clasen committed
1455
          g_object_get (renderer, "mode", &mode, NULL);
1456

Matthias Clasen's avatar
Matthias Clasen committed
1457 1458 1459 1460 1461
          if (gtk_cell_renderer_get_visible (renderer) &&
              (edit_only ? mode == GTK_CELL_RENDERER_MODE_EDITABLE :
               mode != GTK_CELL_RENDERER_MODE_INERT))
            activate_cell = renderer;
        }
1462 1463 1464 1465
      g_list_free (cells);
    }

  if (activate_cell)
1466 1467 1468
    {
      /* Get the allocation of the focused cell.
       */
1469
      gtk_cell_area_get_cell_allocation (area, context, widget, activate_cell,
Matthias Clasen's avatar
Matthias Clasen committed
1470 1471
                                         cell_area, &renderer_area);

1472
      /* Activate or Edit the cell
1473 1474 1475 1476
       *
       * Currently just not sending an event, renderers afaics dont use
       * the event argument anyway, worst case is we can synthesize one.
       */
1477
      if (gtk_cell_area_activate_cell (area, widget, activate_cell, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
1478 1479
                                       &renderer_area, flags))
        return TRUE;
1480
    }
1481

1482
  return FALSE;
1483 1484
}

1485 1486 1487 1488 1489 1490 1491 1492 1493
static gboolean
gtk_cell_area_real_focus (GtkCellArea           *area,
			  GtkDirectionType       direction)
{
  g_warning ("GtkCellAreaClass::focus not implemented for `%s'",
             g_type_name (G_TYPE_FROM_INSTANCE (area)));
  return FALSE;
}

1494 1495 1496
/*************************************************************
 *                   GtkCellLayoutIface                      *
 *************************************************************/
1497 1498 1499
static void
gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface)
{
1500 1501 1502 1503 1504 1505 1506 1507
  iface->pack_start         = gtk_cell_area_pack_default;
  iface->pack_end           = gtk_cell_area_pack_default;
  iface->clear              = gtk_cell_area_clear;
  iface->add_attribute      = gtk_cell_area_add_attribute;
  iface->set_cell_data_func = gtk_cell_area_set_cell_data_func;
  iface->clear_attributes   = gtk_cell_area_clear_attributes;
  iface->reorder            = gtk_cell_area_reorder;
  iface->get_cells          = gtk_cell_area_get_cells;
1508
  iface->get_area           = gtk_cell_area_get_area;
1509
}
1510

1511 1512
static void
gtk_cell_area_pack_default (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
1513 1514
                            GtkCellRenderer       *renderer,
                            gboolean               expand)
1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530
{
  gtk_cell_area_add (GTK_CELL_AREA (cell_layout), renderer);
}

static void
gtk_cell_area_clear (GtkCellLayout *cell_layout)
{
  GtkCellArea *area = GTK_CELL_AREA (cell_layout);
  GList *l, *cells  =
    gtk_cell_layout_get_cells (cell_layout);

  for (l = cells; l; l = l->next)
    {
      GtkCellRenderer *renderer = l->data;
      gtk_cell_area_remove (area, renderer);
    }
1531

1532 1533 1534 1535 1536
  g_list_free (cells);
}

static void
gtk_cell_area_add_attribute (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
1537 1538 1539
                             GtkCellRenderer       *renderer,
                             const gchar           *attribute,
                             gint                   column)
1540 1541
{
  gtk_cell_area_attribute_connect (GTK_CELL_AREA (cell_layout),
Matthias Clasen's avatar
Matthias Clasen committed
1542
                                   renderer, attribute, column);
1543 1544
}

1545 1546
static void
gtk_cell_area_set_cell_data_func (GtkCellLayout         *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
1547 1548 1549 1550
                                  GtkCellRenderer       *renderer,
                                  GtkCellLayoutDataFunc  func,
                                  gpointer               func_data,
                                  GDestroyNotify         destroy)
1551
{
1552
  GtkCellArea *area   = GTK_CELL_AREA (cell_layout);
1553