[gtk+] Fixed GtkWrapBox to allocate variable row AND column heights when in ALIGNED mode



commit 153bfacde0d9c7a533c0936f3078db58cf672aac
Author: Tristan Van Berkom <tristan van berkom gmail com>
Date:   Tue Sep 14 02:28:23 2010 +0900

    Fixed GtkWrapBox to allocate variable row AND column heights when in ALIGNED mode
    
    With this new approach at request and allocate time, the average child size
    is used to determine a good guess at how many columns will fit the box
    width; afterwards extra columns are appended and checked to fit.
    Then the row heights are calculated based on height-for-width of each
    child in the row which now may have individual widths.

 gtk/gtkwrapbox.c |  369 ++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 290 insertions(+), 79 deletions(-)
---
diff --git a/gtk/gtkwrapbox.c b/gtk/gtkwrapbox.c
index 8287870..2aac13f 100644
--- a/gtk/gtkwrapbox.c
+++ b/gtk/gtkwrapbox.c
@@ -574,14 +574,14 @@ get_largest_size_for_opposing_orientation (GtkWrapBox         *box,
  * (used to get the largest line heights for a fixed item width and the opposite
  * while itterating over a list of children, note the new index is returned) */
 static GList *
-get_largest_size_for_line_in_opposing_orientation (GtkWrapBox      *box,
-                                                   GtkOrientation   orientation,
-                                                   GList           *cursor,
-                                                   gint             line_length,
-                                                   gint             item_size,
-                                                   gint             extra_pixels,
-                                                   gint            *min_item_size,
-                                                   gint            *nat_item_size)
+get_largest_size_for_line_in_opposing_orientation (GtkWrapBox       *box,
+                                                   GtkOrientation    orientation,
+                                                   GList            *cursor,
+                                                   gint              line_length,
+                                                   GtkRequestedSize *item_sizes,
+                                                   gint              extra_pixels,
+                                                   gint             *min_item_size,
+                                                   gint             *nat_item_size)
 {
   GtkWrapBoxPrivate *priv   = box->priv;
   GList             *list;
@@ -599,7 +599,7 @@ get_largest_size_for_line_in_opposing_orientation (GtkWrapBox      *box,
 
       /* Distribute the extra pixels to the first children in the line
        * (could be fancier and spread them out more evenly) */
-      this_item_size = item_size;
+      this_item_size = item_sizes[i].minimum_size;
       if (extra_pixels > 0 &&
           priv->spreading == GTK_WRAP_BOX_SPREAD_EXPAND)
         {
@@ -909,6 +909,111 @@ allocate_child (GtkWrapBox      *box,
   gtk_widget_size_allocate (child->widget, &child_allocation);
 }
 
+/* fit_aligned_item_requests() helper */
+static gint
+gather_aligned_item_requests (GtkWrapBox       *box, 
+			      GtkOrientation    orientation, 
+			      gint              line_length,
+			      gint              item_spacing,
+			      gint              n_children,
+			      GtkRequestedSize *item_sizes)
+{
+  GtkWrapBoxPrivate *priv   = box->priv;
+  GList             *list;
+  gint               i;
+  gint               extra_items, natural_line_size = 0;
+
+  extra_items = n_children % line_length;
+
+  for (list = priv->children, i = 0; list; list = list->next, i++)
+    {
+      GtkWrapBoxChild *child = list->data;
+      gint             child_min, child_nat;
+      gint             position;
+
+      if (!gtk_widget_get_visible (child->widget))
+        continue;
+
+      if (orientation == GTK_ORIENTATION_HORIZONTAL)
+        {
+          gtk_size_request_get_width (GTK_SIZE_REQUEST (child->widget),
+				      &child_min, &child_nat);
+          child_min += child->hpadding * 2;
+          child_nat += child->hpadding * 2;
+        }
+      else
+        {
+          gtk_size_request_get_height (GTK_SIZE_REQUEST (child->widget),
+				       &child_min, &child_nat);
+          child_min += child->vpadding * 2;
+          child_nat += child->vpadding * 2;
+        }
+
+
+      /* Get the index and push it over for the last line when spreading to the end */
+      position = i % line_length;
+
+      if (priv->spreading == GTK_WRAP_BOX_SPREAD_END && i >= n_children - extra_items)
+	position += line_length - extra_items;
+
+      /* Round up the size of every column/row */
+      item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min);
+      item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat);
+    }
+
+  for (i = 0; i < line_length; i++)
+    natural_line_size += item_sizes[i].natural_size;
+
+  natural_line_size += (line_length - 1) * item_spacing;
+
+  return natural_line_size;
+}
+
+static GtkRequestedSize *
+fit_aligned_item_requests (GtkWrapBox       *box, 
+			   GtkOrientation    orientation, 
+			   gint              avail_size,
+			   gint              item_spacing,
+			   gint             *line_length, /* in-out */
+			   gint              n_children)
+{
+  GtkRequestedSize  *sizes, *try_sizes;
+  gint               natural_line_size, try_line_size, try_length;
+
+  sizes = g_new0 (GtkRequestedSize, *line_length);
+
+  /* get the sizes for the initial guess */
+  try_line_size = natural_line_size = 
+    gather_aligned_item_requests (box, orientation, *line_length, item_spacing, n_children, sizes);
+
+  /* Try columnizing the whole thing and adding an item to the end of the line;
+   * try to fit as many columns into the available size as possible */
+  for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
+    {
+      try_sizes     = g_new0 (GtkRequestedSize, try_length);
+      try_line_size = gather_aligned_item_requests (box, orientation, try_length, item_spacing, 
+						    n_children, try_sizes);
+
+      if (try_line_size < avail_size)
+	{
+	  natural_line_size = try_line_size;
+	  
+	  *line_length = try_length;
+
+	  g_free (sizes);
+	  sizes = try_sizes;
+	}
+      else
+	{
+	  /* oops, this one failed; stick to the last size that fit and then return */
+	  g_free (try_sizes);
+	  break;
+	}
+    }
+
+  return sizes;
+}
+
 
 typedef struct {
   GArray *requested;
@@ -948,14 +1053,15 @@ gtk_wrap_box_size_allocate (GtkWidget     *widget,
   if (priv->mode == GTK_WRAP_ALLOCATE_ALIGNED ||
       priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
     {
-      GtkRequestedSize *sizes = NULL;
+      GtkRequestedSize *line_sizes = NULL;
+      GtkRequestedSize *item_sizes = NULL;
       GList *list;
       gint   min_item_size, nat_item_size;
       gint   line_length;
-      gint   item_size;
+      gint   item_size = 0;
       gint   line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0;
-      gint   line_offset, n_children, n_lines, line_count;
-      gint   extra_pixels, extra_per_item, extra_extra;
+      gint   line_offset, item_offset, n_children, n_lines, line_count;
+      gint   extra_pixels, extra_per_item = 0, extra_extra = 0;
       gint   i;
 
       get_average_item_size (box, priv->orientation, &min_item_size, &nat_item_size);
@@ -971,30 +1077,26 @@ gtk_wrap_box_size_allocate (GtkWidget     *widget,
        * minimum item wrap length */
       line_length = MAX (min_items, line_length);
 
-      /* Now we need the real item allocation size */
-      item_size = (avail_size - (line_length - 1) * item_spacing) / line_length;
-
-      /* Cut out the expand space if we're not distributing any */
-      if (priv->spreading != GTK_WRAP_BOX_SPREAD_EXPAND)
-        item_size = MIN (item_size, nat_item_size);
-
-      /* Get the real extra pixels incase of GTK_WRAP_BOX_SPREAD_START lines */
-      extra_pixels   = avail_size - (line_length - 1) * item_spacing - item_size * line_length;
-      extra_per_item = extra_pixels / MAX (line_length -1, 1);
-      extra_extra    = extra_pixels % MAX (line_length -1, 1);
-
-      /* Get how many lines that wraps to */
+      /* Get how many lines we'll be needing to wrap */
       n_children = get_visible_children (box);
-      n_lines    = n_children / line_length;
-      if ((n_children % line_length) > 0)
-        n_lines++;
-
-      n_lines = MAX (n_lines, 1);
 
       /* Here we just use the largest height-for-width and use that for the height
        * of all lines */
       if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
         {
+	  n_lines    = n_children / line_length;
+	  if ((n_children % line_length) > 0)
+	    n_lines++;
+	  
+	  n_lines = MAX (n_lines, 1);
+
+	  /* Now we need the real item allocation size */
+	  item_size = (avail_size - (line_length - 1) * item_spacing) / line_length;
+	  
+	  /* Cut out the expand space if we're not distributing any */
+	  if (priv->spreading != GTK_WRAP_BOX_SPREAD_EXPAND)
+	    item_size = MIN (item_size, nat_item_size);
+	  
           get_largest_size_for_opposing_orientation (box, priv->orientation, item_size,
                                                      &min_fixed_line_size,
                                                      &nat_fixed_line_size);
@@ -1002,53 +1104,91 @@ gtk_wrap_box_size_allocate (GtkWidget     *widget,
           /* resolve a fixed 'line_size' */
           line_size = (avail_other_size - (n_lines - 1) * line_spacing) / n_lines;
           line_size = MIN (line_size, nat_fixed_line_size);
+
+	  /* Get the real extra pixels incase of GTK_WRAP_BOX_SPREAD_START lines */
+	  extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length;
         }
       else /* GTK_WRAP_ALLOCATE_ALIGNED */
         {
-          /* spread out the available size in the opposing orientation into an array of
-           * lines (and then allocate those lines naturally) */
           GList            *list;
           gboolean          first_line = TRUE;
 
-	  sizes = g_new0 (GtkRequestedSize, n_lines);
-
-          /* In ALIGNED mode, all items have the same size in the box's orientation except
-           * individual lines may have a different size */
+	  /* Find the amount of columns that can fit aligned into the available space
+	   * and collect their requests.
+	   */
+	  item_sizes = fit_aligned_item_requests (box, priv->orientation, avail_size,
+						  item_spacing, &line_length, n_children);
+
+	  /* Calculate the number of lines after determining the final line_length */
+	  n_lines    = n_children / line_length;
+	  if ((n_children % line_length) > 0)
+	    n_lines++;
+	  
+	  n_lines = MAX (n_lines, 1);
+	  line_sizes = g_new0 (GtkRequestedSize, n_lines);
+
+	  /* Get the available remaining size */
+	  avail_size -= (line_length - 1) * item_spacing;
+	  for (i = 0; i < line_length; i++)
+	    avail_size -= item_sizes[i].minimum_size;
+
+	  /* Perform a natural allocation on the columnized items and get the remaining pixels */
+          extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
+
+	  /* Now that we have the size of each column of items find the size of each individual 
+	   * line based on the aligned item sizes.
+	   */
           for (i = 0, list = priv->children; list != NULL; i++)
             {
 
               list =
                 get_largest_size_for_line_in_opposing_orientation (box, priv->orientation,
                                                                    list, line_length,
-                                                                   item_size, extra_pixels,
-                                                                   &sizes[i].minimum_size,
-                                                                   &sizes[i].natural_size);
+                                                                   item_sizes, extra_pixels,
+                                                                   &line_sizes[i].minimum_size,
+                                                                   &line_sizes[i].natural_size);
 
 
               /* Its possible a line is made of completely invisible children */
-              if (sizes[i].natural_size > 0)
+              if (line_sizes[i].natural_size > 0)
                 {
                   if (first_line)
                     first_line = FALSE;
                   else
                     avail_other_size -= line_spacing;
 
-                  avail_other_size -= sizes[i].minimum_size;
+                  avail_other_size -= line_sizes[i].minimum_size;
 
-                  sizes[i].data = GINT_TO_POINTER (i);
+                  line_sizes[i].data = GINT_TO_POINTER (i);
                 }
             }
 
           /* Distribute space among lines naturally */
-          avail_other_size = gtk_distribute_natural_allocation (avail_other_size, n_lines, sizes);
+          avail_other_size = gtk_distribute_natural_allocation (avail_other_size, n_lines, line_sizes);
         }
 
-      line_offset = border_width;
+      /* Calculate expand space per item (used diferently depending on spreading mode) */
+      if (priv->spreading == GTK_WRAP_BOX_SPREAD_EVEN)
+	{
+	  extra_per_item = extra_pixels / MAX (line_length -1, 1);
+	  extra_extra    = extra_pixels % MAX (line_length -1, 1);
+	}
+      else if (priv->spreading == GTK_WRAP_BOX_SPREAD_EXPAND)
+	{
+	  extra_per_item = extra_pixels / line_length;
+	  extra_extra    = extra_pixels % line_length;
+	}
+
+      line_offset = item_offset = border_width;
+
+      /* prepend extra space to item_offset for SPREAD_END */
+      if (priv->spreading == GTK_WRAP_BOX_SPREAD_END)
+	item_offset += extra_pixels;
 
       for (i = 0, line_count = 0, list = priv->children; list; list = list->next)
         {
           GtkWrapBoxChild *child = list->data;
-          gint             position, this_line_size, item_offset;
+          gint             position, this_line_size;
           gint             this_item_size;
 
           if (!gtk_widget_get_visible (child->widget))
@@ -1063,17 +1203,40 @@ gtk_wrap_box_size_allocate (GtkWidget     *widget,
               if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
                 line_offset += line_size + line_spacing;
               else /* aligned mode */
-                line_offset += sizes[line_count].minimum_size + line_spacing;
+                line_offset += line_sizes[line_count].minimum_size + line_spacing;
 
               line_count++;
-            }
 
-          /* We could be smarter here and distribute the extra pixels more
-           * evenly across the children */
-          if (position < extra_pixels && priv->spreading == GTK_WRAP_BOX_SPREAD_EXPAND)
-            this_item_size = item_size + 1;
-          else
-            this_item_size = item_size;
+	      item_offset = border_width;
+
+	      if (priv->spreading == GTK_WRAP_BOX_SPREAD_END)
+		{
+		  item_offset += extra_pixels;
+
+		  /* If we're on the last line, prepend the space for
+		   * any leading items */
+		  if (line_count == n_lines -1)
+		    {
+		      gint extra_items = n_children % line_length;
+
+		      if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
+			{
+			  item_offset += item_size * (line_length - extra_items);
+			  item_offset += item_spacing * (line_length - extra_items);
+			}
+		      else
+			{
+			  gint j;
+
+			  for (j = 0; j < (line_length - extra_items); j++)
+			    {
+			      item_offset += item_sizes[j].minimum_size;
+			      item_offset += item_spacing;
+			    }
+			}
+		    }
+		}
+            }
 
           /* Push the index along for the last line when spreading to the end */
           if (priv->spreading == GTK_WRAP_BOX_SPREAD_END &&
@@ -1084,29 +1247,47 @@ gtk_wrap_box_size_allocate (GtkWidget     *widget,
               position += line_length - extra_items;
             }
 
-          item_offset = border_width + (position * item_size) + (position * item_spacing);
+	  if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
+	    this_item_size = item_size;
+	  else /* aligned mode */
+	    this_item_size = item_sizes[position].minimum_size;
 
-          if (priv->spreading == GTK_WRAP_BOX_SPREAD_EVEN)
-            {
-              item_offset += position * extra_per_item;
-              item_offset += MIN (position, extra_extra);
-            }
-          else if (priv->spreading == GTK_WRAP_BOX_SPREAD_END)
-            item_offset += extra_pixels;
-          else if (priv->spreading == GTK_WRAP_BOX_SPREAD_EXPAND)
-            item_offset += MIN (position, extra_pixels);
+	  if (priv->spreading == GTK_WRAP_BOX_SPREAD_EXPAND)
+	    {
+	      this_item_size += extra_per_item;
+
+	      /* We could be smarter here and distribute the extra pixels more
+	       * evenly across the children */
+	      if (position < extra_extra)
+		this_item_size++;
+	    }
 
           /* Get the allocation size for this line */
           if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
             this_line_size = line_size;
           else
-            this_line_size = sizes[line_count].minimum_size;
+            this_line_size = line_sizes[line_count].minimum_size;
 
           /* Do the actual allocation */
           allocate_child (box, child, item_offset, line_offset, this_item_size, this_line_size);
 
+          item_offset += this_item_size;
+	  item_offset += item_spacing;
+
+	  /* deal with extra spacing here */
+          if (priv->spreading == GTK_WRAP_BOX_SPREAD_EVEN)
+            {
+              item_offset += extra_per_item;
+
+	      if (position < extra_extra)
+		item_offset++;
+            }
+
           i++;
         }
+
+      g_free (item_sizes);
+      g_free (line_sizes);
     }
   else /* GTK_WRAP_ALLOCATE_FREE */
     {
@@ -1623,7 +1804,7 @@ gtk_wrap_box_get_height_for_width (GtkSizeRequest      *widget,
   gint               min_item_width, nat_item_width;
   gint               min_items;
   gint               min_height, nat_height;
-  gint               avail_size;
+  gint               avail_size, n_children;
 
   min_items = MAX (1, priv->minimum_line_children);
 
@@ -1634,6 +1815,8 @@ gtk_wrap_box_get_height_for_width (GtkSizeRequest      *widget,
     {
       gint min_width;
 
+      n_children = get_visible_children (box);
+
       /* Make sure its no smaller than the minimum */
       GTK_SIZE_REQUEST_GET_IFACE (widget)->get_width (widget, &min_width, NULL);
 
@@ -1677,7 +1860,7 @@ gtk_wrap_box_get_height_for_width (GtkSizeRequest      *widget,
           if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
             {
               gint min_item_height, nat_item_height;
-              gint lines, n_children;
+              gint lines;
 
               /* Here we just use the largest height-for-width and
                * add up the size accordingly */
@@ -1685,7 +1868,6 @@ gtk_wrap_box_get_height_for_width (GtkSizeRequest      *widget,
                                                          &min_item_height, &nat_item_height);
 
               /* Round up how many lines we need to allocate for */
-              n_children = get_visible_children (box);
               lines      = n_children / line_length;
               if ((n_children % line_length) > 0)
                 lines++;
@@ -1699,20 +1881,32 @@ gtk_wrap_box_get_height_for_width (GtkSizeRequest      *widget,
           else /* GTK_WRAP_ALLOCATE_ALIGNED */
             {
               GList *list = priv->children;
-              gint min_line_height, nat_line_height;
+              gint min_line_height, nat_line_height, i;
               gboolean first_line = TRUE;
+	      GtkRequestedSize *item_sizes;
+
+	      /* First get the size each set of items take to span the line
+	       * when aligning the items above and below after wrapping.
+	       */
+	      item_sizes = fit_aligned_item_requests (box, priv->orientation, avail_size,
+						      priv->horizontal_spacing, &line_length, n_children);
+
+
+	      /* Get the available remaining size */
+	      avail_size -= (line_length - 1) * priv->horizontal_spacing;
+	      for (i = 0; i < line_length; i++)
+		avail_size -= item_sizes[i].minimum_size;
+
+	      extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
 
-              /* In ALIGNED mode, all items have the same size in the box's orientation except
-               * individual rows may have a different size */
               while (list != NULL)
                 {
                   list =
                     get_largest_size_for_line_in_opposing_orientation (box, GTK_ORIENTATION_HORIZONTAL,
                                                                        list, line_length,
-                                                                       item_size, extra_pixels,
+                                                                       item_sizes, extra_pixels,
                                                                        &min_line_height, &nat_line_height);
 
-
                   /* Its possible the line only had invisible widgets */
                   if (nat_line_height > 0)
                     {
@@ -1728,6 +1922,8 @@ gtk_wrap_box_get_height_for_width (GtkSizeRequest      *widget,
                       nat_height += nat_line_height;
                     }
                 }
+
+	      g_free (item_sizes);
             }
         }
       else /* GTK_WRAP_ALLOCATE_FREE */
@@ -1791,7 +1987,7 @@ gtk_wrap_box_get_width_for_height (GtkSizeRequest      *widget,
   gint               min_item_height, nat_item_height;
   gint               min_items;
   gint               min_width, nat_width;
-  gint               avail_size;
+  gint               avail_size, n_children;
 
   min_items = MAX (1, priv->minimum_line_children);
 
@@ -1807,6 +2003,8 @@ gtk_wrap_box_get_width_for_height (GtkSizeRequest      *widget,
     {
       gint min_height;
 
+      n_children = get_visible_children (box);
+
       /* Make sure its no smaller than the minimum */
       GTK_SIZE_REQUEST_GET_IFACE (widget)->get_height (widget, &min_height, NULL);
 
@@ -1850,7 +2048,7 @@ gtk_wrap_box_get_width_for_height (GtkSizeRequest      *widget,
           if (priv->mode == GTK_WRAP_ALLOCATE_HOMOGENEOUS)
             {
               gint min_item_width, nat_item_width;
-              gint lines, n_children;
+              gint lines;
 
               /* Here we just use the largest height-for-width and
                * add up the size accordingly */
@@ -1872,17 +2070,29 @@ gtk_wrap_box_get_width_for_height (GtkSizeRequest      *widget,
           else /* GTK_WRAP_ALLOCATE_ALIGNED */
             {
               GList *list = priv->children;
-              gint min_line_width, nat_line_width;
+              gint min_line_width, nat_line_width, i;
               gboolean first_line = TRUE;
+	      GtkRequestedSize *item_sizes;
+
+	      /* First get the size each set of items take to span the line
+	       * when aligning the items above and below after wrapping.
+	       */
+	      item_sizes = fit_aligned_item_requests (box, priv->orientation, avail_size,
+						      priv->vertical_spacing, &line_length, n_children);
+
+	      /* Get the available remaining size */
+	      avail_size -= (line_length - 1) * priv->horizontal_spacing;
+	      for (i = 0; i < line_length; i++)
+		avail_size -= item_sizes[i].minimum_size;
+
+	      extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
 
-              /* In ALIGNED mode, all items have the same size in the box's orientation except
-               * individual rows may have a different size */
               while (list != NULL)
                 {
                   list =
                     get_largest_size_for_line_in_opposing_orientation (box, GTK_ORIENTATION_VERTICAL,
                                                                        list, line_length,
-                                                                       item_size, extra_pixels,
+                                                                       item_sizes, extra_pixels,
                                                                        &min_line_width, &nat_line_width);
 
                   /* Its possible the last line only had invisible widgets */
@@ -1900,6 +2110,7 @@ gtk_wrap_box_get_width_for_height (GtkSizeRequest      *widget,
                       nat_width += nat_line_width;
                     }
                 }
+	      g_free (item_sizes);
             }
         }
       else /* GTK_WRAP_ALLOCATE_FREE */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]