From 64ab7aee8d63de0ca81bf436f5a415de2e9ad887 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 12 Dec 2018 17:20:28 +0000 Subject: [PATCH] Hook GtkLayoutManager into GtkWidget We delegate the size request mode, the measuring, and the allocation of a widget through a GtkLayoutManager instance, if one has been attached to the widget; otherwise, we fall back to the widget's own implementation. --- docs/reference/gtk/gtk4-sections.txt | 2 + gtk/gtklayoutmanager.c | 104 +++++++++++++++++++------ gtk/gtklayoutmanager.h | 6 +- gtk/gtksizerequest.c | 111 +++++++++++++++++++-------- gtk/gtktypes.h | 1 + gtk/gtkwidget.c | 79 ++++++++++++++++--- gtk/gtkwidget.h | 6 ++ gtk/gtkwidgetprivate.h | 3 + 8 files changed, 248 insertions(+), 64 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 7b5633d5d7..ba555e4ba9 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -4535,6 +4535,8 @@ gtk_widget_get_first_child gtk_widget_get_last_child gtk_widget_insert_before gtk_widget_insert_after +gtk_widget_set_layout_manager +gtk_widget_get_layout_manager gtk_widget_get_path diff --git a/gtk/gtklayoutmanager.c b/gtk/gtklayoutmanager.c index bb23c5a598..7a5f2f08d5 100644 --- a/gtk/gtklayoutmanager.c +++ b/gtk/gtklayoutmanager.c @@ -22,12 +22,23 @@ * @Title: GtkLayoutManager * @Short_description: Base class for layout manager * - * ... + * Layout managers are delegate classes that handle the preferred size + * and the allocation of a container widget. + * + * You typically subclass #GtkLayoutManager if you want to implement a + * layout policy for the children of a widget, without necessarily + * implementing the @GtkWidgetClass.measure() and @GtkWidgetClass.size_allocate() + * virtual functions directly. + * + * Each #GtkWidget can only have a #GtkLayoutManager instance associated to it + * at any given time; it is possible, though, to replace the layout manager + * instance using gtk_widget_set_layout_manager(). */ #include "config.h" -#include "gtklayoutmanager.h" +#include "gtklayoutmanagerprivate.h" +#include "gtkwidget.h" #ifdef G_ENABLE_DEBUG #define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method) G_STMT_START { \ @@ -108,7 +119,7 @@ gtk_layout_manager_init (GtkLayoutManager *self) * @layout_manager: a #GtkLayoutManager * @widget: (nullable): a #GtkWidget * - * ... + * Sets a back pointer from @widget to @layout_manager. */ void gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager, @@ -116,22 +127,44 @@ gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager, { GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (layout_manager); + if (widget != NULL && priv->widget != NULL) + { + g_critical ("The layout manager %p of type %s is already in use " + "by widget %p '%s', and cannot be used by widget %p '%s'", + layout_manager, G_OBJECT_TYPE_NAME (layout_manager), + priv->widget, gtk_widget_get_name (priv->widget), + widget, gtk_widget_get_name (widget)); + return; + } + priv->widget = widget; } /** * gtk_layout_manager_measure: - * @manager: - * @widget: - * @orientation: - * @for_size: - * @minimum: (out): - * @natural: (out): - * @minimum_baseline: (out): - * @natural_baseline: (out): + * @manager: a #GtkLayoutManager + * @widget: the #GtkWidget using @manager + * @orientation: the orientation to measure + * @for_size: Size for the opposite of @orientation; for instance, if + * the @orientation is %GTK_ORIENTATION_HORIZONTAL, this is the height + * of the widget; if the @orientation is %GTK_ORIENTATION_VERTICAL, this + * is the width of the widget. This allows to measure the height for the + * given width, and the width for the given height. Use -1 if the size + * is not known + * @minimum: (out) (optional): the minimum size for the given size and + * orientation + * @natural: (out) (optional): the natural, or preferred size for the + * given size and orientation + * @minimum_baseline: (out) (optional): the baseline position for the + * minimum size + * @natural_baseline: (out) (optional): the baseline position for the + * natural size * - * ... + * Measures the size of the @widget using @manager, for the + * given @orientation and size. * + * See [GtkWidget's geometry management section][geometry-management] for + * more details. */ void gtk_layout_manager_measure (GtkLayoutManager *manager, @@ -158,13 +191,15 @@ gtk_layout_manager_measure (GtkLayoutManager *manager, /** * gtk_layout_manager_allocate: - * @manager: - * @widget: - * @width: - * @height: - * @baseline: + * @manager: a #GtkLayoutManager + * @widget: the #GtkWidget using @manager + * @width: the new width of the @widget + * @height: the new height of the @widget + * @baseline: the baseline position of the @widget * - * ... + * This function assigns the given @width, @height, and @baseline to + * a @widget, and computes the position and sizes of the children of + * the @widget using the layout management policy of @manager. */ void gtk_layout_manager_allocate (GtkLayoutManager *manager, @@ -185,12 +220,12 @@ gtk_layout_manager_allocate (GtkLayoutManager *manager, /** * gtk_layout_manager_get_request_mode: - * @manager: - * @widget: + * @manager: a #GtkLayoutManager + * @widget: the #GtkWidget using @manager * - * ... + * Retrieves the request mode of @manager. * - * Returns: ... + * Returns: a #GtkSizeRequestMode */ GtkSizeRequestMode gtk_layout_manager_get_request_mode (GtkLayoutManager *manager, @@ -206,17 +241,40 @@ gtk_layout_manager_get_request_mode (GtkLayoutManager *manager, return klass->get_request_mode (manager, widget); } +/** + * gtk_layout_manager_get_widget: + * @manager: a #GtkLayoutManager + * + * Retrieves the #GtkWidget using the given #GtkLayoutManager. + * + * Returns: (transfer none) (nullable): a #GtkWidget + */ +GtkWidget * +gtk_layout_manager_get_widget (GtkLayoutManager *manager) +{ + GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager); + + g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), NULL); + + return priv->widget; +} + /** * gtk_layout_manager_layout_changed: * @manager: a #GtkLayoutManager * - * ... + * Queues a resize on the #GtkWidget using @manager, if any. + * + * This function should be called by subclasses of #GtkLayoutManager in + * response to changes to their layout management policies. */ void gtk_layout_manager_layout_changed (GtkLayoutManager *manager) { GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager); + g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager)); + if (priv->widget != NULL) gtk_widget_queue_resize (priv->widget); } diff --git a/gtk/gtklayoutmanager.h b/gtk/gtklayoutmanager.h index e849e300a2..4d05132c4d 100644 --- a/gtk/gtklayoutmanager.h +++ b/gtk/gtklayoutmanager.h @@ -18,7 +18,8 @@ */ #pragma once -#include +#include +#include G_BEGIN_DECLS @@ -88,6 +89,9 @@ GDK_AVAILABLE_IN_ALL GtkSizeRequestMode gtk_layout_manager_get_request_mode (GtkLayoutManager *manager, GtkWidget *widget); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_layout_manager_get_widget (GtkLayoutManager *manager); + GDK_AVAILABLE_IN_ALL void gtk_layout_manager_layout_changed (GtkLayoutManager *manager); diff --git a/gtk/gtksizerequest.c b/gtk/gtksizerequest.c index 197d2413ea..cd25debcae 100644 --- a/gtk/gtksizerequest.c +++ b/gtk/gtksizerequest.c @@ -31,6 +31,7 @@ #include "gtkwidgetprivate.h" #include "gtkcssnodeprivate.h" #include "gtkcssnumbervalueprivate.h" +#include "gtklayoutmanagerprivate.h" #ifdef G_ENABLE_CONSISTENCY_CHECKS @@ -195,45 +196,87 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, css_min_for_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_WIDTH); } - if (for_size < 0) + GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget); + + if (layout_manager != NULL) { - push_recursion_check (widget, orientation); - widget_class->measure (widget, orientation, -1, - &reported_min_size, &reported_nat_size, - &min_baseline, &nat_baseline); - pop_recursion_check (widget, orientation); + if (for_size < 0) + { + gtk_layout_manager_measure (layout_manager, widget, + orientation, -1, + &reported_min_size, &reported_nat_size, + &min_baseline, &nat_baseline); + } + else + { + int adjusted_for_size; + int minimum_for_size = 0; + int natural_for_size = 0; + int dummy = 0; + + /* Pull the minimum for_size from the cache as it's needed to adjust + * the proposed 'for_size' */ + gtk_layout_manager_measure (layout_manager, widget, + OPPOSITE_ORIENTATION (orientation), -1, + &minimum_for_size, &natural_for_size, + NULL, NULL); + + if (for_size < MAX (minimum_for_size, css_min_for_size)) + for_size = MAX (minimum_for_size, css_min_for_size); + + adjusted_for_size = for_size; + gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation), + &for_size, &natural_for_size, + &dummy, &adjusted_for_size); + adjusted_for_size -= css_extra_for_size; + + gtk_layout_manager_measure (layout_manager, widget, + orientation, adjusted_for_size, + &reported_min_size, &reported_nat_size, + &min_baseline, &nat_baseline); + } } else { - int adjusted_for_size; - int minimum_for_size = 0; - int natural_for_size = 0; - int dummy = 0; + if (for_size < 0) + { + push_recursion_check (widget, orientation); + widget_class->measure (widget, orientation, -1, + &reported_min_size, &reported_nat_size, + &min_baseline, &nat_baseline); + pop_recursion_check (widget, orientation); + } + else + { + int adjusted_for_size; + int minimum_for_size = 0; + int natural_for_size = 0; + int dummy = 0; - /* Pull the minimum for_size from the cache as it's needed to adjust - * the proposed 'for_size' */ - gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), -1, - &minimum_for_size, &natural_for_size, NULL, NULL); + /* Pull the minimum for_size from the cache as it's needed to adjust + * the proposed 'for_size' */ + gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), -1, + &minimum_for_size, &natural_for_size, NULL, NULL); - /* TODO: Warn if the given for_size is too small? */ - if (for_size < MAX (minimum_for_size, css_min_for_size)) - for_size = MAX (minimum_for_size, css_min_for_size); + /* TODO: Warn if the given for_size is too small? */ + if (for_size < MAX (minimum_for_size, css_min_for_size)) + for_size = MAX (minimum_for_size, css_min_for_size); - adjusted_for_size = for_size; - gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation), - &for_size, &natural_for_size, - &dummy, &adjusted_for_size); + adjusted_for_size = for_size; + gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation), + &for_size, &natural_for_size, + &dummy, &adjusted_for_size); - adjusted_for_size -= css_extra_for_size; - - push_recursion_check (widget, orientation); - widget_class->measure (widget, - orientation, - adjusted_for_size, - &reported_min_size, &reported_nat_size, - &min_baseline, &nat_baseline); - pop_recursion_check (widget, orientation); + adjusted_for_size -= css_extra_for_size; + push_recursion_check (widget, orientation); + widget_class->measure (widget, + orientation, + adjusted_for_size, + &reported_min_size, &reported_nat_size, + &min_baseline, &nat_baseline); + pop_recursion_check (widget, orientation); + } } min_size = MAX (0, MAX (reported_min_size, css_min_size)) + css_extra_size; @@ -512,7 +555,13 @@ gtk_widget_get_request_mode (GtkWidget *widget) if (G_UNLIKELY (!cache->request_mode_valid)) { - cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget); + GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget); + + if (layout_manager != NULL) + cache->request_mode = gtk_layout_manager_get_request_mode (layout_manager, widget); + else + cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget); + cache->request_mode_valid = TRUE; } diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h index fc58058821..8444999778 100644 --- a/gtk/gtktypes.h +++ b/gtk/gtktypes.h @@ -38,6 +38,7 @@ typedef struct _GtkBuilder GtkBuilder; typedef struct _GtkClipboard GtkClipboard; typedef struct _GtkEventController GtkEventController; typedef struct _GtkGesture GtkGesture; +typedef struct _GtkLayoutManager GtkLayoutManager; typedef struct _GtkRequisition GtkRequisition; typedef struct _GtkSelectionData GtkSelectionData; typedef struct _GtkSettings GtkSettings; diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index be1f058555..64ce2f7537 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -47,6 +47,7 @@ #include "gtkgestureswipe.h" #include "gtkintl.h" #include "gtkinvisible.h" +#include "gtklayoutmanagerprivate.h" #include "gtkmarshalers.h" #include "gtkmain.h" #include "gtkmenu.h" @@ -4254,16 +4255,26 @@ gtk_widget_size_allocate (GtkWidget *widget, if (baseline >= 0) baseline -= margin.top + border.top + padding.top; - if (g_signal_has_handler_pending (widget, widget_signals[SIZE_ALLOCATE], 0, FALSE)) - g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0, - real_allocation.width, - real_allocation.height, - baseline); + if (priv->layout_manager != NULL) + { + gtk_layout_manager_allocate (priv->layout_manager, widget, + real_allocation.width, + real_allocation.height, + baseline); + } else - GTK_WIDGET_GET_CLASS (widget)->size_allocate (widget, - real_allocation.width, - real_allocation.height, - baseline); + { + if (g_signal_has_handler_pending (widget, widget_signals[SIZE_ALLOCATE], 0, FALSE)) + g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0, + real_allocation.width, + real_allocation.height, + baseline); + else + GTK_WIDGET_GET_CLASS (widget)->size_allocate (widget, + real_allocation.width, + real_allocation.height, + baseline); + } /* Size allocation is god... after consulting god, no further requests or allocations are needed */ #ifdef G_ENABLE_DEBUG @@ -8146,6 +8157,9 @@ gtk_widget_dispose (GObject *object) while (priv->paintables) gtk_widget_paintable_set_widget (priv->paintables->data, NULL); + gtk_widget_set_layout_manager (widget, NULL); + g_clear_object (&priv->layout_manager); + priv->visible = FALSE; if (_gtk_widget_get_realized (widget)) gtk_widget_unrealize (widget); @@ -13586,3 +13600,50 @@ gtk_widget_get_height (GtkWidget *widget) border.top - border.bottom - padding.top - padding.bottom; } + +/** + * gtk_widget_set_layout_manager: + * @widget: a #GtkWidget + * @layout_manager: (nullable) (transfer full): a #GtkLayoutManager + * + * Sets the layout manager delegate instance that provides an implementation + * for measuring and allocating the children of @widget. + * + * The @widget acquires a reference to the given @layout_manager. + */ +void +gtk_widget_set_layout_manager (GtkWidget *widget, + GtkLayoutManager *layout_manager) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (layout_manager == NULL || GTK_IS_LAYOUT_MANAGER (layout_manager)); + g_return_if_fail (layout_manager == NULL || gtk_layout_manager_get_widget (layout_manager) == NULL); + + if (g_set_object (&priv->layout_manager, layout_manager)) + { + if (priv->layout_manager != NULL) + gtk_layout_manager_set_widget (priv->layout_manager, widget); + + gtk_widget_queue_resize (widget); + } +} + +/** + * gtk_widget_get_layout_manager: + * @widget: a #GtkWidget + * + * Retrieves the layout manager set using gtk_widget_set_layout_manager(). + * + * Returns: (transfer none) (nullable): a #GtkLayoutManager + */ +GtkLayoutManager * +gtk_widget_get_layout_manager (GtkWidget *widget) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + return priv->layout_manager; +} diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index 5d6236ea8a..d99dc1aa24 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -424,6 +424,12 @@ void gtk_widget_get_preferred_size (GtkWidget *w GtkRequisition *minimum_size, GtkRequisition *natural_size); +GDK_AVAILABLE_IN_ALL +void gtk_widget_set_layout_manager (GtkWidget *widget, + GtkLayoutManager *layout_manager); +GDK_AVAILABLE_IN_ALL +GtkLayoutManager * gtk_widget_get_layout_manager (GtkWidget *widget); + GDK_AVAILABLE_IN_ALL void gtk_widget_add_accelerator (GtkWidget *widget, const gchar *accel_signal, diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index f658811358..0ac93eace4 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -149,6 +149,9 @@ struct _GtkWidgetPrivate /* The render node we draw or %NULL if not yet created.*/ GskRenderNode *render_node; + /* The layout manager, or %NULL */ + GtkLayoutManager *layout_manager; + GSList *paintables; /* The widget's surface or its parent surface if it does