1/* gtklayoutmanager.c: Layout manager base class
2 * Copyright 2018 The GNOME Foundation
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author: Emmanuele Bassi
18 */
19
20/**
21 * GtkLayoutManager:
22 *
23 * Layout managers are delegate classes that handle the preferred size
24 * and the allocation of a widget.
25 *
26 * You typically subclass `GtkLayoutManager` if you want to implement a
27 * layout policy for the children of a widget, or if you want to determine
28 * the size of a widget depending on its contents.
29 *
30 * Each `GtkWidget` can only have a `GtkLayoutManager` instance associated
31 * to it at any given time; it is possible, though, to replace the layout
32 * manager instance using [method@Gtk.Widget.set_layout_manager].
33 *
34 * ## Layout properties
35 *
36 * A layout manager can expose properties for controlling the layout of
37 * each child, by creating an object type derived from [class@Gtk.LayoutChild]
38 * and installing the properties on it as normal `GObject` properties.
39 *
40 * Each `GtkLayoutChild` instance storing the layout properties for a
41 * specific child is created through the [method@Gtk.LayoutManager.get_layout_child]
42 * method; a `GtkLayoutManager` controls the creation of its `GtkLayoutChild`
43 * instances by overriding the GtkLayoutManagerClass.create_layout_child()
44 * virtual function. The typical implementation should look like:
45 *
46 * ```c
47 * static GtkLayoutChild *
48 * create_layout_child (GtkLayoutManager *manager,
49 * GtkWidget *container,
50 * GtkWidget *child)
51 * {
52 * return g_object_new (your_layout_child_get_type (),
53 * "layout-manager", manager,
54 * "child-widget", child,
55 * NULL);
56 * }
57 * ```
58 *
59 * The [property@Gtk.LayoutChild:layout-manager] and
60 * [property@Gtk.LayoutChild:child-widget] properties
61 * on the newly created `GtkLayoutChild` instance are mandatory. The
62 * `GtkLayoutManager` will cache the newly created `GtkLayoutChild` instance
63 * until the widget is removed from its parent, or the parent removes the
64 * layout manager.
65 *
66 * Each `GtkLayoutManager` instance creating a `GtkLayoutChild` should use
67 * [method@Gtk.LayoutManager.get_layout_child] every time it needs to query
68 * the layout properties; each `GtkLayoutChild` instance should call
69 * [method@Gtk.LayoutManager.layout_changed] every time a property is
70 * updated, in order to queue a new size measuring and allocation.
71 */
72
73#include "config.h"
74
75#include "gtklayoutmanagerprivate.h"
76#include "gtklayoutchild.h"
77#include "gtkwidgetprivate.h"
78#include "gtknative.h"
79#include "gtkpopover.h"
80#include "gtktexthandleprivate.h"
81#include "gtktooltipwindowprivate.h"
82
83#ifdef G_ENABLE_DEBUG
84#define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method) G_STMT_START { \
85 GObject *_obj = G_OBJECT (m); \
86 g_warning ("Layout managers of type %s do not implement " \
87 "the GtkLayoutManager::%s method", \
88 G_OBJECT_TYPE_NAME (_obj), \
89 #method); } G_STMT_END
90#else
91#define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method)
92#endif
93
94typedef struct {
95 GtkWidget *widget;
96 GtkRoot *root;
97
98 /* HashTable<Widget, LayoutChild> */
99 GHashTable *layout_children;
100} GtkLayoutManagerPrivate;
101
102G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkLayoutManager, gtk_layout_manager, G_TYPE_OBJECT)
103
104static void
105gtk_layout_manager_real_root (GtkLayoutManager *manager)
106{
107}
108
109static void
110gtk_layout_manager_real_unroot (GtkLayoutManager *manager)
111{
112}
113
114static GtkSizeRequestMode
115gtk_layout_manager_real_get_request_mode (GtkLayoutManager *manager,
116 GtkWidget *widget)
117{
118 int hfw = 0, wfh = 0;
119 GtkWidget *child;
120
121 for (child = _gtk_widget_get_first_child (widget);
122 child != NULL;
123 child = _gtk_widget_get_next_sibling (widget: child))
124 {
125 GtkSizeRequestMode res = gtk_widget_get_request_mode (widget: child);
126
127 switch (res)
128 {
129 case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
130 hfw += 1;
131 break;
132
133 case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
134 wfh += 1;
135 break;
136
137 case GTK_SIZE_REQUEST_CONSTANT_SIZE:
138 default:
139 break;
140 }
141 }
142
143 if (hfw == 0 && wfh == 0)
144 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
145
146 return hfw > wfh ? GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH
147 : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
148}
149
150static void
151gtk_layout_manager_real_measure (GtkLayoutManager *manager,
152 GtkWidget *widget,
153 GtkOrientation orientation,
154 int for_size,
155 int *minimum,
156 int *natural,
157 int *baseline_minimum,
158 int *baseline_natural)
159{
160 LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, measure);
161
162 if (minimum != NULL)
163 *minimum = 0;
164
165 if (natural != NULL)
166 *natural = 0;
167
168 if (baseline_minimum != NULL)
169 *baseline_minimum = 0;
170
171 if (baseline_natural != NULL)
172 *baseline_natural = 0;
173}
174
175static void
176gtk_layout_manager_real_allocate (GtkLayoutManager *manager,
177 GtkWidget *widget,
178 int width,
179 int height,
180 int baseline)
181{
182 LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, allocate);
183}
184
185static GtkLayoutChild *
186gtk_layout_manager_real_create_layout_child (GtkLayoutManager *manager,
187 GtkWidget *widget,
188 GtkWidget *child)
189{
190 GtkLayoutManagerClass *manager_class = GTK_LAYOUT_MANAGER_GET_CLASS (ptr: manager);
191
192 if (manager_class->layout_child_type == G_TYPE_INVALID)
193 {
194 LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, create_layout_child);
195 return NULL;
196 }
197
198 return g_object_new (object_type: manager_class->layout_child_type,
199 first_property_name: "layout-manager", manager,
200 "child-widget", child,
201 NULL);
202}
203
204static void
205gtk_layout_manager_finalize (GObject *gobject)
206{
207 GtkLayoutManager *self = GTK_LAYOUT_MANAGER (ptr: gobject);
208 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self);
209
210 g_clear_pointer (&priv->layout_children, g_hash_table_unref);
211
212 G_OBJECT_CLASS (gtk_layout_manager_parent_class)->finalize (gobject);
213}
214
215static void
216gtk_layout_manager_class_init (GtkLayoutManagerClass *klass)
217{
218 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
219
220 gobject_class->finalize = gtk_layout_manager_finalize;
221
222 klass->get_request_mode = gtk_layout_manager_real_get_request_mode;
223 klass->measure = gtk_layout_manager_real_measure;
224 klass->allocate = gtk_layout_manager_real_allocate;
225 klass->create_layout_child = gtk_layout_manager_real_create_layout_child;
226 klass->root = gtk_layout_manager_real_root;
227 klass->unroot = gtk_layout_manager_real_unroot;
228}
229
230static void
231gtk_layout_manager_init (GtkLayoutManager *self)
232{
233}
234
235/*< private >
236 * gtk_layout_manager_set_widget:
237 * @layout_manager: a `GtkLayoutManager`
238 * @widget: (nullable): a `GtkWidget`
239 *
240 * Sets a back pointer from @widget to @layout_manager.
241 */
242void
243gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager,
244 GtkWidget *widget)
245{
246 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: layout_manager);
247
248 if (widget != NULL && priv->widget != NULL)
249 {
250 g_critical ("The layout manager %p of type %s is already in use "
251 "by widget %p '%s', and cannot be used by widget %p '%s'",
252 layout_manager, G_OBJECT_TYPE_NAME (layout_manager),
253 priv->widget, gtk_widget_get_name (priv->widget),
254 widget, gtk_widget_get_name (widget));
255 return;
256 }
257
258 priv->widget = widget;
259
260 if (widget != NULL)
261 gtk_layout_manager_set_root (manager: layout_manager, root: gtk_widget_get_root (widget));
262}
263
264/*< private >
265 * gtk_layout_manager_set_root:
266 * @layout_manager: a i`GtkLayoutManager`
267 * @root: (nullable): a `GtkWidget` implementing `GtkRoot`
268 *
269 * Sets a back pointer from @root to @layout_manager.
270 *
271 * This function is called by `GtkWidget` when getting rooted and unrooted,
272 * and will call `GtkLayoutManagerClass.root()` or `GtkLayoutManagerClass.unroot()`
273 * depending on whether @root is a `GtkWidget` or %NULL.
274 */
275void
276gtk_layout_manager_set_root (GtkLayoutManager *layout_manager,
277 GtkRoot *root)
278{
279 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: layout_manager);
280 GtkRoot *old_root = priv->root;
281
282 priv->root = root;
283
284 if (old_root != root)
285 {
286 if (priv->root != NULL)
287 GTK_LAYOUT_MANAGER_GET_CLASS (ptr: layout_manager)->root (layout_manager);
288 else
289 GTK_LAYOUT_MANAGER_GET_CLASS (ptr: layout_manager)->unroot (layout_manager);
290 }
291}
292
293/**
294 * gtk_layout_manager_measure:
295 * @manager: a `GtkLayoutManager`
296 * @widget: the `GtkWidget` using @manager
297 * @orientation: the orientation to measure
298 * @for_size: Size for the opposite of @orientation; for instance, if
299 * the @orientation is %GTK_ORIENTATION_HORIZONTAL, this is the height
300 * of the widget; if the @orientation is %GTK_ORIENTATION_VERTICAL, this
301 * is the width of the widget. This allows to measure the height for the
302 * given width, and the width for the given height. Use -1 if the size
303 * is not known
304 * @minimum: (out) (optional): the minimum size for the given size and
305 * orientation
306 * @natural: (out) (optional): the natural, or preferred size for the
307 * given size and orientation
308 * @minimum_baseline: (out) (optional): the baseline position for the
309 * minimum size
310 * @natural_baseline: (out) (optional): the baseline position for the
311 * natural size
312 *
313 * Measures the size of the @widget using @manager, for the
314 * given @orientation and size.
315 *
316 * See the [class@Gtk.Widget] documentation on layout management for
317 * more details.
318 */
319void
320gtk_layout_manager_measure (GtkLayoutManager *manager,
321 GtkWidget *widget,
322 GtkOrientation orientation,
323 int for_size,
324 int *minimum,
325 int *natural,
326 int *minimum_baseline,
327 int *natural_baseline)
328{
329 GtkLayoutManagerClass *klass;
330 int min_size = 0;
331 int nat_size = 0;
332 int min_baseline = -1;
333 int nat_baseline = -1;
334
335
336 g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
337 g_return_if_fail (GTK_IS_WIDGET (widget));
338
339 klass = GTK_LAYOUT_MANAGER_GET_CLASS (ptr: manager);
340
341 klass->measure (manager, widget, orientation,
342 for_size,
343 &min_size, &nat_size,
344 &min_baseline, &nat_baseline);
345
346 if (minimum)
347 *minimum = min_size;
348
349 if (natural)
350 *natural = nat_size;
351
352 if (minimum_baseline)
353 *minimum_baseline = min_baseline;
354
355 if (natural_baseline)
356 *natural_baseline = nat_baseline;
357}
358
359static void
360allocate_native_children (GtkWidget *widget)
361{
362 GtkWidget *child;
363
364 for (child = _gtk_widget_get_first_child (widget);
365 child != NULL;
366 child = _gtk_widget_get_next_sibling (widget: child))
367 {
368 if (GTK_IS_POPOVER (child))
369 gtk_popover_present (GTK_POPOVER (child));
370 else if (GTK_IS_TEXT_HANDLE (ptr: child))
371 gtk_text_handle_present (handle: GTK_TEXT_HANDLE (ptr: child));
372 else if (GTK_IS_TOOLTIP_WINDOW (ptr: child))
373 gtk_tooltip_window_present (window: GTK_TOOLTIP_WINDOW (ptr: child));
374 else if (GTK_IS_NATIVE (ptr: child))
375 g_warning ("Unable to present a to the layout manager unknown auxiliary child surface widget type %s",
376 G_OBJECT_TYPE_NAME (child));
377 }
378}
379
380/**
381 * gtk_layout_manager_allocate:
382 * @manager: a `GtkLayoutManager`
383 * @widget: the `GtkWidget` using @manager
384 * @width: the new width of the @widget
385 * @height: the new height of the @widget
386 * @baseline: the baseline position of the @widget, or -1
387 *
388 * Assigns the given @width, @height, and @baseline to
389 * a @widget, and computes the position and sizes of the children of
390 * the @widget using the layout management policy of @manager.
391 */
392void
393gtk_layout_manager_allocate (GtkLayoutManager *manager,
394 GtkWidget *widget,
395 int width,
396 int height,
397 int baseline)
398{
399 GtkLayoutManagerClass *klass;
400
401 g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
402 g_return_if_fail (GTK_IS_WIDGET (widget));
403 g_return_if_fail (baseline >= -1);
404
405 allocate_native_children (widget);
406
407 klass = GTK_LAYOUT_MANAGER_GET_CLASS (ptr: manager);
408
409 klass->allocate (manager, widget, width, height, baseline);
410}
411
412/**
413 * gtk_layout_manager_get_request_mode:
414 * @manager: a `GtkLayoutManager`
415 *
416 * Retrieves the request mode of @manager.
417 *
418 * Returns: a `GtkSizeRequestMode`
419 */
420GtkSizeRequestMode
421gtk_layout_manager_get_request_mode (GtkLayoutManager *manager)
422{
423 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: manager);
424 GtkLayoutManagerClass *klass;
425
426 g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), GTK_SIZE_REQUEST_CONSTANT_SIZE);
427 g_return_val_if_fail (priv->widget != NULL, GTK_SIZE_REQUEST_CONSTANT_SIZE);
428
429 klass = GTK_LAYOUT_MANAGER_GET_CLASS (ptr: manager);
430
431 return klass->get_request_mode (manager, priv->widget);
432}
433
434/**
435 * gtk_layout_manager_get_widget:
436 * @manager: a `GtkLayoutManager`
437 *
438 * Retrieves the `GtkWidget` using the given `GtkLayoutManager`.
439 *
440 * Returns: (transfer none) (nullable): a `GtkWidget`
441 */
442GtkWidget *
443gtk_layout_manager_get_widget (GtkLayoutManager *manager)
444{
445 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: manager);
446
447 g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), NULL);
448
449 return priv->widget;
450}
451
452/**
453 * gtk_layout_manager_layout_changed:
454 * @manager: a `GtkLayoutManager`
455 *
456 * Queues a resize on the `GtkWidget` using @manager, if any.
457 *
458 * This function should be called by subclasses of `GtkLayoutManager`
459 * in response to changes to their layout management policies.
460 */
461void
462gtk_layout_manager_layout_changed (GtkLayoutManager *manager)
463{
464 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: manager);
465
466 g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
467
468 if (priv->widget != NULL)
469 gtk_widget_queue_resize (widget: priv->widget);
470}
471
472/*< private >
473 * gtk_layout_manager_remove_layout_child:
474 * @manager: a `GtkLayoutManager`
475 * @widget: a `GtkWidget`
476 *
477 * Removes the `GtkLayoutChild` associated with @widget from the
478 * given `GtkLayoutManager`, if any is set.
479 */
480void
481gtk_layout_manager_remove_layout_child (GtkLayoutManager *manager,
482 GtkWidget *widget)
483{
484 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: manager);
485
486 if (priv->layout_children != NULL)
487 {
488 g_hash_table_remove (hash_table: priv->layout_children, key: widget);
489 if (g_hash_table_size (hash_table: priv->layout_children) == 0)
490 g_clear_pointer (&priv->layout_children, g_hash_table_unref);
491 }
492}
493
494/**
495 * gtk_layout_manager_get_layout_child:
496 * @manager: a `GtkLayoutManager`
497 * @child: a `GtkWidget`
498 *
499 * Retrieves a `GtkLayoutChild` instance for the `GtkLayoutManager`,
500 * creating one if necessary.
501 *
502 * The @child widget must be a child of the widget using @manager.
503 *
504 * The `GtkLayoutChild` instance is owned by the `GtkLayoutManager`,
505 * and is guaranteed to exist as long as @child is a child of the
506 * `GtkWidget` using the given `GtkLayoutManager`.
507 *
508 * Returns: (transfer none): a `GtkLayoutChild`
509 */
510GtkLayoutChild *
511gtk_layout_manager_get_layout_child (GtkLayoutManager *manager,
512 GtkWidget *child)
513{
514 GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self: manager);
515 GtkLayoutChild *res;
516 GtkWidget *parent;
517
518 g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), NULL);
519 g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
520
521 parent = _gtk_widget_get_parent (widget: child);
522 g_return_val_if_fail (parent != NULL, NULL);
523
524 if (priv->widget != parent)
525 {
526 g_critical ("The parent %s %p of the widget %s %p does not "
527 "use the given layout manager of type %s %p",
528 gtk_widget_get_name (parent), parent,
529 gtk_widget_get_name (child), child,
530 G_OBJECT_TYPE_NAME (manager), manager);
531 return NULL;
532 }
533
534 if (priv->layout_children == NULL)
535 {
536 priv->layout_children = g_hash_table_new_full (NULL, NULL,
537 NULL,
538 value_destroy_func: (GDestroyNotify) g_object_unref);
539 }
540
541 res = g_hash_table_lookup (hash_table: priv->layout_children, key: child);
542 if (res != NULL)
543 {
544 /* If the LayoutChild instance is stale, and refers to another
545 * layout manager, then we simply ask the LayoutManager to
546 * replace it, as it means the layout manager for the parent
547 * widget was replaced
548 */
549 if (gtk_layout_child_get_layout_manager (layout_child: res) == manager)
550 return res;
551 }
552
553 res = GTK_LAYOUT_MANAGER_GET_CLASS (ptr: manager)->create_layout_child (manager, parent, child);
554 if (res == NULL)
555 {
556 g_critical ("The layout manager of type %s %p does not create "
557 "GtkLayoutChild instances",
558 G_OBJECT_TYPE_NAME (manager), manager);
559 return NULL;
560 }
561
562 g_assert (g_type_is_a (G_OBJECT_TYPE (res), GTK_TYPE_LAYOUT_CHILD));
563 g_hash_table_insert (hash_table: priv->layout_children, key: child, value: res);
564
565 return res;
566}
567

source code of gtk/gtk/gtklayoutmanager.c