After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 729341 - [PATCH] GtkTreeView does not honour GtkScrollable policies
[PATCH] GtkTreeView does not honour GtkScrollable policies
Status: RESOLVED OBSOLETE
Product: gtk+
Classification: Platform
Component: Widget: GtkTreeView
unspecified
Other Linux
: Normal normal
: ---
Assigned To: gtktreeview-bugs
gtktreeview-bugs
Depends on:
Blocks:
 
 
Reported: 2014-05-01 16:16 UTC by Julian Ahrens
Modified: 2018-05-02 16:03 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
quick fix (6.36 KB, patch)
2014-05-01 16:16 UTC, Julian Ahrens
none Details | Review
fix with line breaks (6.94 KB, patch)
2014-05-12 15:51 UTC, Julian Ahrens
reviewed Details | Review

Description Julian Ahrens 2014-05-01 16:16:17 UTC
Created attachment 275554 [details] [review]
quick fix

GtkTreeView does not honour GtkScrollable policies

The current implementation of GtkTreeView does not honour the
GtkScrollable policies.  When gtk_scrollable_set_hscroll_policy or
gtk_scrollable_set_vscroll_policy is called, the supplied value is
stored but it is never used by the sizing logic which always seems
to assume GTK_SCROLL_MINIMUM.  The following references illustrate
the resulting behaviour:

    https://mail.gnome.org/archives/gtk-perl-list/2013-December/msg00019.html
    bug 698925

I have written a very conservative patch which fixes the issue.
It is based on a previous patch by John Lindgren

    https://git.gnome.org/browse/gtk+/commit?id=666d10ec7676a15b6861b785397563cbd4ef21e6

which changed the sizing logic to always assume GTK_SCROLL_NATURAL.
Comment 1 Michael Webster 2014-05-12 12:16:29 UTC
This patch works fine for me, gtk_scrollable_set_Xscroll_policy changes the behavior as expected.

The only nitpick I would have is line length, need some newlines in there.
Comment 2 Julian Ahrens 2014-05-12 15:51:33 UTC
Created attachment 276391 [details] [review]
fix with line breaks
Comment 3 Tristan Van Berkom 2014-05-20 21:13:39 UTC
Review of attachment 276391 [details] [review]:

Matthias informally asked me to review this patch on IRC, this is a very
difficult review and I don't have time to give it very much attention but
here is what I have written up.

First of all I should mention that this ties directly into height-for-width,
we never landed height-for-width geometry into the treeview and in order
to keep things working as much as possible like it did in GTK+2, we simply
settled on a minimum-for-minimum default for the treeview area.

That said, text renderers DO height-for-width and will unwrap dynamically
if they are given more width (thus reducing their need for height).

For height-for-width to really work in GtkTreeView, the size request pattern
needs to be split into two separate iterations.

This is approximately what is happening right now:

  o The visible area is validated syncrhonously once for the purpose
    of responding to a size request and showing something on screen
    as fast as possible

  o The remaining rows are then traversed, for each row:

    o The minimum width is requested for the row
    o The height for that width is requested
    o The row heights are cached in the RBTree
    o The overall treeview 'width' and 'height' is recorded
    o The upper bounds of the adjustments are increased to allow for
      the additional rows

What needs to happen for height-for-width to be properly supported:

  o The treeview must cache the minimum/natural overall width/height
    instead of just the minimum in order to efficiently respond to
    size requests which can occur multiple times between any
    change in the treeview's underlying content.

  o The visible area must be validated synchronously as usual, however
    it must be done in two phases - all visible rows must report their
    minimum/natural width - and the allocated width should be used to
    request the heights of each visible row.

  o The remaining (invisible) rows must have their widths requested
    asyncrhonously, for each row:

      o The overall cached treeview minimum/natural width/height needs to
        be updated

  o Once we have requested all of the rows which we intend to request
    width for, we can finally know that we have an allocated width
    which can satisfy the request of our treeview - we receive an
    allocation.

  o When we receive an allocation and we know that all of our rows
    have been requested, now we need to start requesting height for
    those invisible rows we are interested in. (currently the treeview
    requests every row, but this should ideally be only one out of
    every 1000 rows or such, for a treeview with many millions of rows).

  o When the content changes (i.e. the text which would be rendered
    by a potentially wrapping text cell renderer), we need to restart
    the process depending on some conditions (such as grow-only and
    fixed height mode where we can make some optimizations).

  o It is possible that the content changes before we have even finished
    requesting horizontally, we still need to complete the process of
    requesting horizontally before we can start to consider requesting
    row heights.


diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c
index 73c37c1..6672e61 100644
--- a/gtk/gtktreeview.c
+++ b/gtk/gtktreeview.c
@@ -2485,15 +2485,17 @@ gtk_tree_view_update_height (GtkTreeView *tree_view)
 
   for (list = tree_view->priv->columns; list; list = list->next)
     {
-      GtkRequisition     requisition;
+      GtkRequisition     minimum, natural;
       GtkTreeViewColumn *column = list->data;
       GtkWidget         *button = gtk_tree_view_column_get_button (column);
 
       if (button == NULL)
         continue;
 
-      gtk_widget_get_preferred_size (button, &requisition, NULL);
-      tree_view->priv->header_height = MAX (tree_view->priv->header_height, requisition.height);
+      gtk_widget_get_preferred_size (button, &minimum, &natural);
+      tree_view->priv->header_height = MAX (tree_view->priv->header_height,
+                                            tree_view->priv->vscroll_policy == GTK_SCROLL_MINIMUM
+                                            ? minimum.height : natural.height);

This part captures the natural height of the button, whereas before it would only capture the minimum.

It still does not do any height-for-width, however.
 
@@ -2682,7 +2684,9 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget,
   natural_width = tree_view->priv->natural_width;
   n_expand_columns = tree_view->priv->n_expand_columns;
 
-  width = MAX (widget_allocation.width, minimum_width);
+  width = MAX (widget_allocation.width,
+               tree_view->priv->hscroll_policy == GTK_SCROLL_MINIMUM
+               ? minimum_width : natural_width);

Here we are ensuring that the treeview bin window (as opposed to the
actual treeview allocation) will be at least 'natural_width' wide
in the case of GTK_SCROLL_NATURAL... the intent is correct, however
it will result in the unwrapping of text cell renderers - since the
treeview does not properly re-request for all rows asynchronously when
an allocated width change, this will result in inconsistencies in the
reported treeview height request (and probably some inconsistencies
in the stored row heights in the RBTree).

 
@@ -6602,18 +6606,22 @@ validate_visible_area (GtkTreeView *tree_view)
   /* update width/height and queue a resize */
   if (size_changed)
     {
-      GtkRequisition requisition;
+      GtkRequisition minimum, natural;
 
       /* We temporarily guess a size, under the assumption that it will be the
        * same when we get our next size_allocate.  If we don't do this, we'll be
        * in an inconsistent state if we call top_row_to_dy. */
 
       gtk_widget_get_preferred_size (GTK_WIDGET (tree_view),
-                                     &requisition, NULL);
+                                     &minimum, &natural);
       gtk_adjustment_set_upper (tree_view->priv->hadjustment,
-                                MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment), requisition.width));
+                                MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment),
+                                     tree_view->priv->hscroll_policy == GTK_SCROLL_MINIMUM
+                                     ? minimum.width : natural.width));
       gtk_adjustment_set_upper (tree_view->priv->vadjustment,
-                                MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment), requisition.height));
+                                MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment),
+                                     tree_view->priv->vscroll_policy == GTK_SCROLL_MINIMUM
+                                     ? minimum.height : natural.height));
       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
     }

Here we do much the same as when allocating columns, withing the context of requesting
the size syncrhonously for the visible area of the treeview.

Again the intent looks correct, is the natural height also taken into accound while
asyncrhonously requesting row heights for the 1,000,000 rows that are not currently
visible ?

 
@@ -6809,8 +6817,7 @@ do_validate_rows (GtkTreeView *tree_view, gboolean queue_resize)
  done:
   if (validated_area)
     {
-      GtkRequisition requisition;
-      gint dummy;
+      GtkRequisition minimum, natural;
 
       /* We temporarily guess a size, under the assumption that it will be the
        * same when we get our next size_allocate.  If we don't do this, we'll be
@@ -6827,8 +6834,8 @@ do_validate_rows (GtkTreeView *tree_view, gboolean queue_resize)
        * untill we've recieved an allocation (never update scroll adjustments from size-requests).
        */
       prevent_recursion_hack = TRUE;
-      gtk_tree_view_get_preferred_width (GTK_WIDGET (tree_view), &requisition.width, &dummy);
-      gtk_tree_view_get_preferred_height (GTK_WIDGET (tree_view), &requisition.height, &dummy);
+      gtk_tree_view_get_preferred_width (GTK_WIDGET (tree_view), &minimum.width, &natural.width);
+      gtk_tree_view_get_preferred_height (GTK_WIDGET (tree_view), &minimum.height, &natural.height);
       prevent_recursion_hack = FALSE;
 
       /* If rows above the current position have changed height, this has
@@ -6838,9 +6845,13 @@ do_validate_rows (GtkTreeView *tree_view, gboolean queue_resize)
         gtk_widget_queue_draw (GTK_WIDGET (tree_view));
 
       gtk_adjustment_set_upper (tree_view->priv->hadjustment,
-                                MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment), requisition.width));
+                                MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment),
+                                     tree_view->priv->hscroll_policy == GTK_SCROLL_MINIMUM
+                                     ? minimum.width : natural.width));
       gtk_adjustment_set_upper (tree_view->priv->vadjustment,
-                                MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment), requisition.height));
+                                MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment),
+                                     tree_view->priv->vscroll_policy == GTK_SCROLL_MINIMUM
+                                     ? minimum.height : natural.height));


Yes to the above question... we have the same treatment in the process of
requesting row sizes asyncrhonously.


@@ -6870,15 +6881,19 @@ do_presize_handler (GtkTreeView *tree_view)
 
   if (tree_view->priv->fixed_height_mode)
     {
-      GtkRequisition requisition;
+      GtkRequisition minimum, natural;
 
       gtk_widget_get_preferred_size (GTK_WIDGET (tree_view),
-                                     &requisition, NULL);
+                                     &minimum, &natural);
 
       gtk_adjustment_set_upper (tree_view->priv->hadjustment,
-                                MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment), requisition.width));
+                                MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment),
+                                     tree_view->priv->hscroll_policy == GTK_SCROLL_MINIMUM
+                                     ? minimum.width : natural.width));
       gtk_adjustment_set_upper (tree_view->priv->vadjustment,
-                                MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment), requisition.height));
+                                MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment),
+                                     tree_view->priv->vscroll_policy == GTK_SCROLL_MINIMUM
+                                     ? minimum.height : natural.height));
       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
     }
                   
And in the presize handler (the first size request while re-requesting the whole treeview), we've considered
the natural width in the equasion for fixed height mode (this basically 'bumps' the current adjustment values
to be at least as big as they are, it acts as a grow-only functionality unless before re-validating all rows
we've reset the internal width/height variables to 0).


I think this patch needs work, certainly all of the treeview tests in the ./tests directly should be
run and manually compared with a build without this patch applied (tests such as testtreeedit are
important to ensure we get all the pixels in the right places).

My primary worry about this is that it enables height-for-width functionality in cell renderers
implicitly by allocating cells/columns sizes which are greater than their minimum requests, this
means that row heights for those contextual widths will be cached in the RBTree and reused later,
potentially after a content change or a reallocation of the treeview - when those precached values
might not be correct anymore.

Comparisons should be made with treeviews that only use the vertical scrollbar and not the
horizontal scrollbar, ideally inside a GtkPaned as this allows one to resize the treeview
manually and watch the text renderers rewrap into the newly allocated width (of course these
tests should be made with wrapping cell renderers).
Comment 4 GNOME Infrastructure Team 2018-05-02 16:03:27 UTC
-- GitLab Migration Automatic Message --

This bug has been migrated to GNOME's GitLab instance and has been closed from further activity.

You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.gnome.org/GNOME/gtk/issues/485.