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 | |
94 | typedef struct { |
95 | GtkWidget *widget; |
96 | GtkRoot *root; |
97 | |
98 | /* HashTable<Widget, LayoutChild> */ |
99 | GHashTable *layout_children; |
100 | } GtkLayoutManagerPrivate; |
101 | |
102 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkLayoutManager, gtk_layout_manager, G_TYPE_OBJECT) |
103 | |
104 | static void |
105 | gtk_layout_manager_real_root (GtkLayoutManager *manager) |
106 | { |
107 | } |
108 | |
109 | static void |
110 | gtk_layout_manager_real_unroot (GtkLayoutManager *manager) |
111 | { |
112 | } |
113 | |
114 | static GtkSizeRequestMode |
115 | gtk_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 | |
150 | static void |
151 | gtk_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 | |
175 | static void |
176 | gtk_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 | |
185 | static GtkLayoutChild * |
186 | gtk_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 | |
204 | static void |
205 | gtk_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 | |
215 | static void |
216 | gtk_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 | |
230 | static void |
231 | gtk_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 | */ |
242 | void |
243 | gtk_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 | */ |
275 | void |
276 | gtk_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 | */ |
319 | void |
320 | gtk_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 | |
359 | static void |
360 | allocate_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 | */ |
392 | void |
393 | gtk_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 | */ |
420 | GtkSizeRequestMode |
421 | gtk_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 | */ |
442 | GtkWidget * |
443 | gtk_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 | */ |
461 | void |
462 | gtk_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 | */ |
480 | void |
481 | gtk_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 | */ |
510 | GtkLayoutChild * |
511 | gtk_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 | |