1 /* gtkcellareacontext.c
2  *
3  * Copyright (C) 2010 Openismus GmbH
4  *
5  * Authors:
6  *      Tristan Van Berkom <tristanvb@openismus.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 /**
23  * GtkCellAreaContext:
24  *
25  * Stores geometrical information for a series of rows in a GtkCellArea
26  *
27  * The `GtkCellAreaContext` object is created by a given `GtkCellArea`
28  * implementation via its `GtkCellAreaClass.create_context()` virtual
29  * method and is used to store cell sizes and alignments for a series of
30  * `GtkTreeModel` rows that are requested and rendered in the same context.
31  *
32  * `GtkCellLayout` widgets can create any number of contexts in which to
33  * request and render groups of data rows. However, it’s important that the
34  * same context which was used to request sizes for a given `GtkTreeModel`
35  * row also be used for the same row when calling other `GtkCellArea` APIs
36  * such as gtk_cell_area_render() and gtk_cell_area_event().
37  */
38 
39 #include "config.h"
40 #include "gtkintl.h"
41 #include "gtkmarshalers.h"
42 #include "gtkcellareacontext.h"
43 #include "gtkprivate.h"
44 
45 /* GObjectClass */
46 static void gtk_cell_area_context_dispose       (GObject            *object);
47 static void gtk_cell_area_context_get_property  (GObject            *object,
48                                                  guint               prop_id,
49                                                  GValue             *value,
50                                                  GParamSpec         *pspec);
51 static void gtk_cell_area_context_set_property  (GObject            *object,
52                                                  guint               prop_id,
53                                                  const GValue       *value,
54                                                  GParamSpec         *pspec);
55 
56 /* GtkCellAreaContextClass */
57 static void gtk_cell_area_context_real_reset    (GtkCellAreaContext *context);
58 static void gtk_cell_area_context_real_allocate (GtkCellAreaContext *context,
59                                                  int                 width,
60                                                  int                 height);
61 
62 typedef struct _GtkCellAreaContextPrivate GtkCellAreaContextPrivate;
63 struct _GtkCellAreaContextPrivate
64 {
65   GtkCellArea *cell_area;
66 
67   int          min_width;
68   int          nat_width;
69   int          min_height;
70   int          nat_height;
71   int          alloc_width;
72   int          alloc_height;
73 };
74 
75 enum {
76   PROP_0,
77   PROP_CELL_AREA,
78   PROP_MIN_WIDTH,
79   PROP_NAT_WIDTH,
80   PROP_MIN_HEIGHT,
81   PROP_NAT_HEIGHT
82 };
83 
G_DEFINE_TYPE_WITH_PRIVATE(GtkCellAreaContext,gtk_cell_area_context,G_TYPE_OBJECT)84 G_DEFINE_TYPE_WITH_PRIVATE (GtkCellAreaContext, gtk_cell_area_context, G_TYPE_OBJECT)
85 
86 static void
87 gtk_cell_area_context_init (GtkCellAreaContext *context)
88 {
89 }
90 
91 static void
gtk_cell_area_context_class_init(GtkCellAreaContextClass * class)92 gtk_cell_area_context_class_init (GtkCellAreaContextClass *class)
93 {
94   GObjectClass     *object_class = G_OBJECT_CLASS (class);
95 
96   /* GObjectClass */
97   object_class->dispose      = gtk_cell_area_context_dispose;
98   object_class->get_property = gtk_cell_area_context_get_property;
99   object_class->set_property = gtk_cell_area_context_set_property;
100 
101   /* GtkCellAreaContextClass */
102   class->reset    = gtk_cell_area_context_real_reset;
103   class->allocate = gtk_cell_area_context_real_allocate;
104 
105   /**
106    * GtkCellAreaContext:area:
107    *
108    * The `GtkCellArea` this context was created by
109    */
110   g_object_class_install_property (object_class,
111                                    PROP_CELL_AREA,
112                                    g_param_spec_object ("area",
113                                                         P_("Area"),
114                                                         P_("The Cell Area this context was created for"),
115                                                         GTK_TYPE_CELL_AREA,
116                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
117 
118   /**
119    * GtkCellAreaContext:minimum-width:
120    *
121    * The minimum width for the `GtkCellArea` in this context
122    * for all `GtkTreeModel` rows that this context was requested
123    * for using gtk_cell_area_get_preferred_width().
124    */
125   g_object_class_install_property (object_class,
126                                    PROP_MIN_WIDTH,
127                                    g_param_spec_int ("minimum-width",
128                                                      P_("Minimum Width"),
129                                                      P_("Minimum cached width"),
130                                                      -1, G_MAXINT, -1,
131                                                      GTK_PARAM_READABLE));
132 
133   /**
134    * GtkCellAreaContext:natural-width:
135    *
136    * The natural width for the `GtkCellArea` in this context
137    * for all `GtkTreeModel` rows that this context was requested
138    * for using gtk_cell_area_get_preferred_width().
139    */
140   g_object_class_install_property (object_class,
141                                    PROP_NAT_WIDTH,
142                                    g_param_spec_int ("natural-width",
143                                                      P_("Minimum Width"),
144                                                      P_("Minimum cached width"),
145                                                      -1, G_MAXINT, -1,
146                                                      GTK_PARAM_READABLE));
147 
148   /**
149    * GtkCellAreaContext:minimum-height:
150    *
151    * The minimum height for the `GtkCellArea` in this context
152    * for all `GtkTreeModel` rows that this context was requested
153    * for using gtk_cell_area_get_preferred_height().
154    */
155   g_object_class_install_property (object_class,
156                                    PROP_MIN_HEIGHT,
157                                    g_param_spec_int ("minimum-height",
158                                                      P_("Minimum Height"),
159                                                      P_("Minimum cached height"),
160                                                      -1, G_MAXINT, -1,
161                                                      GTK_PARAM_READABLE));
162 
163   /**
164    * GtkCellAreaContext:natural-height:
165    *
166    * The natural height for the `GtkCellArea` in this context
167    * for all `GtkTreeModel` rows that this context was requested
168    * for using gtk_cell_area_get_preferred_height().
169    */
170   g_object_class_install_property (object_class,
171                                    PROP_NAT_HEIGHT,
172                                    g_param_spec_int ("natural-height",
173                                                      P_("Minimum Height"),
174                                                      P_("Minimum cached height"),
175                                                      -1, G_MAXINT, -1,
176                                                      GTK_PARAM_READABLE));
177 }
178 
179 /*************************************************************
180  *                      GObjectClass                         *
181  *************************************************************/
182 static void
gtk_cell_area_context_dispose(GObject * object)183 gtk_cell_area_context_dispose (GObject *object)
184 {
185   GtkCellAreaContext        *context = GTK_CELL_AREA_CONTEXT (object);
186   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
187 
188   if (priv->cell_area)
189     {
190       g_object_unref (priv->cell_area);
191 
192       priv->cell_area = NULL;
193     }
194 
195   G_OBJECT_CLASS (gtk_cell_area_context_parent_class)->dispose (object);
196 }
197 
198 static void
gtk_cell_area_context_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)199 gtk_cell_area_context_set_property (GObject      *object,
200                                     guint         prop_id,
201                                     const GValue *value,
202                                     GParamSpec   *pspec)
203 {
204   GtkCellAreaContext        *context = GTK_CELL_AREA_CONTEXT (object);
205   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
206 
207   switch (prop_id)
208     {
209     case PROP_CELL_AREA:
210       priv->cell_area = g_value_dup_object (value);
211       break;
212     default:
213       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214       break;
215     }
216 }
217 
218 static void
gtk_cell_area_context_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)219 gtk_cell_area_context_get_property (GObject     *object,
220                                     guint        prop_id,
221                                     GValue      *value,
222                                     GParamSpec  *pspec)
223 {
224   GtkCellAreaContext        *context = GTK_CELL_AREA_CONTEXT (object);
225   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
226 
227   switch (prop_id)
228     {
229     case PROP_CELL_AREA:
230       g_value_set_object (value, priv->cell_area);
231       break;
232     case PROP_MIN_WIDTH:
233       g_value_set_int (value, priv->min_width);
234       break;
235     case PROP_NAT_WIDTH:
236       g_value_set_int (value, priv->nat_width);
237       break;
238     case PROP_MIN_HEIGHT:
239       g_value_set_int (value, priv->min_height);
240       break;
241     case PROP_NAT_HEIGHT:
242       g_value_set_int (value, priv->nat_height);
243       break;
244     default:
245       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
246       break;
247     }
248 }
249 
250 /*************************************************************
251  *                    GtkCellAreaContextClass                *
252  *************************************************************/
253 static void
gtk_cell_area_context_real_reset(GtkCellAreaContext * context)254 gtk_cell_area_context_real_reset (GtkCellAreaContext *context)
255 {
256   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
257 
258   g_object_freeze_notify (G_OBJECT (context));
259 
260   if (priv->min_width != 0)
261     {
262       priv->min_width = 0;
263       g_object_notify (G_OBJECT (context), "minimum-width");
264     }
265 
266   if (priv->nat_width != 0)
267     {
268       priv->nat_width = 0;
269       g_object_notify (G_OBJECT (context), "natural-width");
270     }
271 
272   if (priv->min_height != 0)
273     {
274       priv->min_height = 0;
275       g_object_notify (G_OBJECT (context), "minimum-height");
276     }
277 
278   if (priv->nat_height != 0)
279     {
280       priv->nat_height = 0;
281       g_object_notify (G_OBJECT (context), "natural-height");
282     }
283 
284   priv->alloc_width  = 0;
285   priv->alloc_height = 0;
286 
287   g_object_thaw_notify (G_OBJECT (context));
288 }
289 
290 static void
gtk_cell_area_context_real_allocate(GtkCellAreaContext * context,int width,int height)291 gtk_cell_area_context_real_allocate (GtkCellAreaContext *context,
292                                      int                 width,
293                                      int                 height)
294 {
295   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
296 
297   priv->alloc_width  = width;
298   priv->alloc_height = height;
299 }
300 
301 /*************************************************************
302  *                            API                            *
303  *************************************************************/
304 /**
305  * gtk_cell_area_context_get_area:
306  * @context: a `GtkCellAreaContext`
307  *
308  * Fetches the `GtkCellArea` this @context was created by.
309  *
310  * This is generally unneeded by layouting widgets; however,
311  * it is important for the context implementation itself to
312  * fetch information about the area it is being used for.
313  *
314  * For instance at `GtkCellAreaContextClass.allocate()` time
315  * it’s important to know details about any cell spacing
316  * that the `GtkCellArea` is configured with in order to
317  * compute a proper allocation.
318  *
319  * Returns: (transfer none): the `GtkCellArea` this context was created by.
320  */
321 GtkCellArea *
gtk_cell_area_context_get_area(GtkCellAreaContext * context)322 gtk_cell_area_context_get_area (GtkCellAreaContext *context)
323 {
324   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
325 
326   g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL);
327 
328   return priv->cell_area;
329 }
330 
331 /**
332  * gtk_cell_area_context_reset:
333  * @context: a `GtkCellAreaContext`
334  *
335  * Resets any previously cached request and allocation
336  * data.
337  *
338  * When underlying `GtkTreeModel` data changes its
339  * important to reset the context if the content
340  * size is allowed to shrink. If the content size
341  * is only allowed to grow (this is usually an option
342  * for views rendering large data stores as a measure
343  * of optimization), then only the row that changed
344  * or was inserted needs to be (re)requested with
345  * gtk_cell_area_get_preferred_width().
346  *
347  * When the new overall size of the context requires
348  * that the allocated size changes (or whenever this
349  * allocation changes at all), the variable row
350  * sizes need to be re-requested for every row.
351  *
352  * For instance, if the rows are displayed all with
353  * the same width from top to bottom then a change
354  * in the allocated width necessitates a recalculation
355  * of all the displayed row heights using
356  * gtk_cell_area_get_preferred_height_for_width().
357  */
358 void
gtk_cell_area_context_reset(GtkCellAreaContext * context)359 gtk_cell_area_context_reset (GtkCellAreaContext *context)
360 {
361   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
362 
363   GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->reset (context);
364 }
365 
366 /**
367  * gtk_cell_area_context_allocate:
368  * @context: a `GtkCellAreaContext`
369  * @width: the allocated width for all `GtkTreeModel` rows rendered
370  *   with @context, or -1
371  * @height: the allocated height for all `GtkTreeModel` rows rendered
372  *   with @context, or -1
373  *
374  * Allocates a width and/or a height for all rows which are to be
375  * rendered with @context.
376  *
377  * Usually allocation is performed only horizontally or sometimes
378  * vertically since a group of rows are usually rendered side by
379  * side vertically or horizontally and share either the same width
380  * or the same height. Sometimes they are allocated in both horizontal
381  * and vertical orientations producing a homogeneous effect of the
382  * rows. This is generally the case for `GtkTreeView` when
383  * `GtkTreeView:fixed-height-mode` is enabled.
384  */
385 void
gtk_cell_area_context_allocate(GtkCellAreaContext * context,int width,int height)386 gtk_cell_area_context_allocate (GtkCellAreaContext *context,
387                                 int                 width,
388                                 int                 height)
389 {
390   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
391 
392   GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->allocate (context, width, height);
393 }
394 
395 /**
396  * gtk_cell_area_context_get_preferred_width:
397  * @context: a `GtkCellAreaContext`
398  * @minimum_width: (out) (optional): location to store the minimum width
399  * @natural_width: (out) (optional): location to store the natural width
400  *
401  * Gets the accumulative preferred width for all rows which have been
402  * requested with this context.
403  *
404  * After gtk_cell_area_context_reset() is called and/or before ever
405  * requesting the size of a `GtkCellArea`, the returned values are 0.
406  */
407 void
gtk_cell_area_context_get_preferred_width(GtkCellAreaContext * context,int * minimum_width,int * natural_width)408 gtk_cell_area_context_get_preferred_width (GtkCellAreaContext *context,
409                                            int                *minimum_width,
410                                            int                *natural_width)
411 {
412   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
413 
414   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
415 
416   if (minimum_width)
417     *minimum_width = priv->min_width;
418 
419   if (natural_width)
420     *natural_width = priv->nat_width;
421 }
422 
423 /**
424  * gtk_cell_area_context_get_preferred_height:
425  * @context: a `GtkCellAreaContext`
426  * @minimum_height: (out) (optional): location to store the minimum height
427  * @natural_height: (out) (optional): location to store the natural height
428  *
429  * Gets the accumulative preferred height for all rows which have been
430  * requested with this context.
431  *
432  * After gtk_cell_area_context_reset() is called and/or before ever
433  * requesting the size of a `GtkCellArea`, the returned values are 0.
434  */
435 void
gtk_cell_area_context_get_preferred_height(GtkCellAreaContext * context,int * minimum_height,int * natural_height)436 gtk_cell_area_context_get_preferred_height (GtkCellAreaContext *context,
437                                             int                *minimum_height,
438                                             int                *natural_height)
439 {
440   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
441 
442   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
443 
444   if (minimum_height)
445     *minimum_height = priv->min_height;
446 
447   if (natural_height)
448     *natural_height = priv->nat_height;
449 }
450 
451 /**
452  * gtk_cell_area_context_get_preferred_height_for_width:
453  * @context: a `GtkCellAreaContext`
454  * @width: a proposed width for allocation
455  * @minimum_height: (out) (optional): location to store the minimum height
456  * @natural_height: (out) (optional): location to store the natural height
457  *
458  * Gets the accumulative preferred height for @width for all rows
459  * which have been requested for the same said @width with this context.
460  *
461  * After gtk_cell_area_context_reset() is called and/or before ever
462  * requesting the size of a `GtkCellArea`, the returned values are -1.
463  */
464 void
gtk_cell_area_context_get_preferred_height_for_width(GtkCellAreaContext * context,int width,int * minimum_height,int * natural_height)465 gtk_cell_area_context_get_preferred_height_for_width (GtkCellAreaContext *context,
466                                                       int                 width,
467                                                       int                *minimum_height,
468                                                       int                *natural_height)
469 {
470   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
471 
472   if (GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_height_for_width)
473     GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_height_for_width (context,
474                                                                                width,
475                                                                                minimum_height,
476                                                                                natural_height);
477 }
478 
479 /**
480  * gtk_cell_area_context_get_preferred_width_for_height:
481  * @context: a `GtkCellAreaContext`
482  * @height: a proposed height for allocation
483  * @minimum_width: (out) (optional): location to store the minimum width
484  * @natural_width: (out) (optional): location to store the natural width
485  *
486  * Gets the accumulative preferred width for @height for all rows which
487  * have been requested for the same said @height with this context.
488  *
489  * After gtk_cell_area_context_reset() is called and/or before ever
490  * requesting the size of a `GtkCellArea`, the returned values are -1.
491  */
492 void
gtk_cell_area_context_get_preferred_width_for_height(GtkCellAreaContext * context,int height,int * minimum_width,int * natural_width)493 gtk_cell_area_context_get_preferred_width_for_height (GtkCellAreaContext *context,
494                                                       int                 height,
495                                                       int                *minimum_width,
496                                                       int                *natural_width)
497 {
498   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
499 
500   if (GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_width_for_height)
501     GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_width_for_height (context,
502                                                                                height,
503                                                                                minimum_width,
504                                                                                natural_width);
505 }
506 
507 /**
508  * gtk_cell_area_context_get_allocation:
509  * @context: a `GtkCellAreaContext`
510  * @width: (out) (optional): location to store the allocated width
511  * @height: (out) (optional): location to store the allocated height
512  *
513  * Fetches the current allocation size for @context.
514  *
515  * If the context was not allocated in width or height, or if the
516  * context was recently reset with gtk_cell_area_context_reset(),
517  * the returned value will be -1.
518  */
519 void
gtk_cell_area_context_get_allocation(GtkCellAreaContext * context,int * width,int * height)520 gtk_cell_area_context_get_allocation (GtkCellAreaContext *context,
521                                       int                *width,
522                                       int                *height)
523 {
524   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
525 
526   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
527 
528   if (width)
529     *width = priv->alloc_width;
530 
531   if (height)
532     *height = priv->alloc_height;
533 }
534 
535 /**
536  * gtk_cell_area_context_push_preferred_width:
537  * @context: a `GtkCellAreaContext`
538  * @minimum_width: the proposed new minimum width for @context
539  * @natural_width: the proposed new natural width for @context
540  *
541  * Causes the minimum and/or natural width to grow if the new
542  * proposed sizes exceed the current minimum and natural width.
543  *
544  * This is used by `GtkCellAreaContext` implementations during
545  * the request process over a series of `GtkTreeModel` rows to
546  * progressively push the requested width over a series of
547  * gtk_cell_area_get_preferred_width() requests.
548  */
549 void
gtk_cell_area_context_push_preferred_width(GtkCellAreaContext * context,int minimum_width,int natural_width)550 gtk_cell_area_context_push_preferred_width (GtkCellAreaContext *context,
551                                             int                 minimum_width,
552                                             int                 natural_width)
553 {
554   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
555 
556   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
557 
558   g_object_freeze_notify (G_OBJECT (context));
559 
560   if (minimum_width > priv->min_width)
561     {
562       priv->min_width = minimum_width;
563 
564       g_object_notify (G_OBJECT (context), "minimum-width");
565     }
566 
567   if (natural_width > priv->nat_width)
568     {
569       priv->nat_width = natural_width;
570 
571       g_object_notify (G_OBJECT (context), "natural-width");
572     }
573 
574   g_object_thaw_notify (G_OBJECT (context));
575 }
576 
577 /**
578  * gtk_cell_area_context_push_preferred_height:
579  * @context: a `GtkCellAreaContext`
580  * @minimum_height: the proposed new minimum height for @context
581  * @natural_height: the proposed new natural height for @context
582  *
583  * Causes the minimum and/or natural height to grow if the new
584  * proposed sizes exceed the current minimum and natural height.
585  *
586  * This is used by `GtkCellAreaContext` implementations during
587  * the request process over a series of `GtkTreeModel` rows to
588  * progressively push the requested height over a series of
589  * gtk_cell_area_get_preferred_height() requests.
590  */
591 void
gtk_cell_area_context_push_preferred_height(GtkCellAreaContext * context,int minimum_height,int natural_height)592 gtk_cell_area_context_push_preferred_height (GtkCellAreaContext *context,
593                                              int                 minimum_height,
594                                              int                 natural_height)
595 {
596   GtkCellAreaContextPrivate *priv = gtk_cell_area_context_get_instance_private (context);
597 
598   g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
599 
600   g_object_freeze_notify (G_OBJECT (context));
601 
602   if (minimum_height > priv->min_height)
603     {
604       priv->min_height = minimum_height;
605 
606       g_object_notify (G_OBJECT (context), "minimum-height");
607     }
608 
609   if (natural_height > priv->nat_height)
610     {
611       priv->nat_height = natural_height;
612 
613       g_object_notify (G_OBJECT (context), "natural-height");
614     }
615 
616   g_object_thaw_notify (G_OBJECT (context));
617 }
618