+static gint
+gtk_icon_view_get_n_items (GtkIconView *icon_view)
+{
+ GtkIconViewPrivate *priv = icon_view->priv;
+
+ if (priv->model == NULL)
+ return 0;
+
+ return gtk_tree_model_iter_n_children (priv->model, NULL);
+}
+
+static void
+adjust_wrap_width (GtkIconView *icon_view)
+{
+ if (icon_view->priv->text_cell)
+ {
+ gint wrap_width = 50;
+
+ /* Here we go with the same old guess, try the icon size and set double
+ * the size of the first icon found in the list, naive but works much
+ * of the time */
+ if (icon_view->priv->items && icon_view->priv->pixbuf_cell)
+ {
+ gtk_cell_renderer_get_preferred_width (icon_view->priv->pixbuf_cell,
+ GTK_WIDGET (icon_view),
+ &wrap_width, NULL);
+
+ wrap_width = MAX (wrap_width * 2, 50);
+ }
+
+ g_object_set (icon_view->priv->text_cell, "wrap-width", wrap_width, NULL);
+ g_object_set (icon_view->priv->text_cell, "width", wrap_width, NULL);
+ }
+}
+
+/* General notes about layout
+ *
+ * The icon view is layouted like this:
+ *
+ * +----------+ s +----------+
+ * | padding | p | padding |
+ * | +------+ | a | +------+ |
+ * | | cell | | c | | cell | |
+ * | +------+ | i | +------+ |
+ * | | n | |
+ * +----------+ g +----------+
+ *
+ * In size request and allocation code, there are 3 sizes that are used:
+ * * cell size
+ * This is the size returned by gtk_cell_area_get_preferred_foo(). In places
+ * where code is interacting with the cell area and renderers this is useful.
+ * * padded size
+ * This is the cell size plus the item padding on each side.
+ * * spaced size
+ * This is the padded size plus the spacing. This is what's used for most
+ * calculations because it can (ab)use the following formula:
+ * iconview_size = 2 * margin + n_items * spaced_size - spacing
+ * So when reading this code and fixing my bugs where I confuse these two, be
+ * aware of this distinction.
+ */
+static void
+cell_area_get_preferred_size (GtkIconView *icon_view,
+ GtkCellAreaContext *context,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum,
+ gint *natural)
+{
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (for_size > 0)
+ gtk_cell_area_get_preferred_width_for_height (icon_view->priv->cell_area,
+ context,
+ GTK_WIDGET (icon_view),
+ for_size,
+ minimum, natural);
+ else
+ gtk_cell_area_get_preferred_width (icon_view->priv->cell_area,
+ context,
+ GTK_WIDGET (icon_view),
+ minimum, natural);
+ }
+ else
+ {
+ if (for_size > 0)
+ gtk_cell_area_get_preferred_height_for_width (icon_view->priv->cell_area,
+ context,
+ GTK_WIDGET (icon_view),
+ for_size,
+ minimum, natural);
+ else
+ gtk_cell_area_get_preferred_height (icon_view->priv->cell_area,
+ context,
+ GTK_WIDGET (icon_view),
+ minimum, natural);
+ }
+}
+
+static gboolean
+gtk_icon_view_is_empty (GtkIconView *icon_view)
+{
+ return icon_view->priv->items == NULL;
+}
+
+static void
+gtk_icon_view_get_preferred_item_size (GtkIconView *icon_view,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum,
+ gint *natural)
+{
+ GtkIconViewPrivate *priv = icon_view->priv;
+ GtkCellAreaContext *context;
+ GList *items;
+
+ g_assert (!gtk_icon_view_is_empty (icon_view));
+
+ context = gtk_cell_area_create_context (priv->cell_area);
+
+ for_size -= 2 * priv->item_padding;
+
+ if (for_size > 0)
+ {
+ /* This is necessary for the context to work properly */
+ for (items = priv->items; items; items = items->next)
+ {
+ GtkIconViewItem *item = items->data;
+
+ _gtk_icon_view_set_cell_data (icon_view, item);
+ cell_area_get_preferred_size (icon_view, context, 1 - orientation, -1, NULL, NULL);
+ }
+ }
+
+ for (items = priv->items; items; items = items->next)
+ {
+ GtkIconViewItem *item = items->data;
+
+ _gtk_icon_view_set_cell_data (icon_view, item);
+ if (items == priv->items)
+ adjust_wrap_width (icon_view);
+ cell_area_get_preferred_size (icon_view, context, orientation, for_size, NULL, NULL);
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (for_size > 0)
+ gtk_cell_area_context_get_preferred_width_for_height (context,
+ for_size,
+ minimum, natural);
+ else
+ gtk_cell_area_context_get_preferred_width (context,
+ minimum, natural);
+ }
+ else
+ {
+ if (for_size > 0)
+ gtk_cell_area_context_get_preferred_height_for_width (context,
+ for_size,
+ minimum, natural);
+ else
+ gtk_cell_area_context_get_preferred_height (context,
+ minimum, natural);
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL && priv->item_width >= 0)
+ {
+ if (minimum)
+ *minimum = MAX (*minimum, priv->item_width);
+ if (natural)
+ *natural = *minimum;
+ }
+
+ if (minimum)
+ *minimum = MAX (1, *minimum + 2 * priv->item_padding);
+ if (natural)
+ *natural = MAX (1, *natural + 2 * priv->item_padding);
+
+ g_object_unref (context);
+}
+
+static void
+gtk_icon_view_compute_n_items_for_size (GtkIconView *icon_view,
+ GtkOrientation orientation,
+ gint size,
+ gint *min_items,
+ gint *min_item_size,
+ gint *max_items,
+ gint *max_item_size)
+{
+ GtkIconViewPrivate *priv = icon_view->priv;
+ int minimum, natural, spacing;
+
+ g_return_if_fail (min_item_size == NULL || min_items != NULL);
+ g_return_if_fail (max_item_size == NULL || max_items != NULL);
+ g_return_if_fail (!gtk_icon_view_is_empty (icon_view));
+
+ gtk_icon_view_get_preferred_item_size (icon_view, orientation, -1, &minimum, &natural);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ spacing = priv->column_spacing;
+ else
+ spacing = priv->row_spacing;
+
+ size -= 2 * priv->margin;
+ size += spacing;
+ minimum += spacing;
+ natural += spacing;
+
+ if (priv->columns > 0)
+ {
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (min_items)
+ *min_items = priv->columns;
+ if (max_items)
+ *max_items = priv->columns;
+ }
+ else
+ {
+ int n_items = gtk_icon_view_get_n_items (icon_view);
+
+ if (min_items)
+ *min_items = (n_items + priv->columns - 1) / priv->columns;
+ if (max_items)
+ *max_items = (n_items + priv->columns - 1) / priv->columns;
+ }
+ }
+ else
+ {
+ if (max_items)
+ {
+ if (size <= minimum)
+ *max_items = 1;
+ else
+ *max_items = size / minimum;
+ }
+
+ if (min_items)
+ {
+ if (size <= natural)
+ *min_items = 1;
+ else
+ *min_items = size / natural;
+ }
+ }
+
+ if (min_item_size)
+ {
+ *min_item_size = size / *min_items;
+ *min_item_size = CLAMP (*min_item_size, minimum, natural);
+ *min_item_size -= spacing;
+ *min_item_size -= 2 * priv->item_padding;
+ }
+
+ if (max_item_size)
+ {
+ *max_item_size = size / *max_items;
+ *max_item_size = CLAMP (*max_item_size, minimum, natural);
+ *max_item_size -= spacing;
+ *max_item_size -= 2 * priv->item_padding;
+ }
+}
+
+static GtkSizeRequestMode
+gtk_icon_view_get_request_mode (GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+gtk_icon_view_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ GtkIconView *icon_view = GTK_ICON_VIEW (widget);
+ GtkIconViewPrivate *priv = icon_view->priv;
+ int item_min, item_nat;
+
+ if (gtk_icon_view_is_empty (icon_view))
+ {
+ *minimum = *natural = 2 * priv->margin;
+ return;
+ }
+
+ gtk_icon_view_get_preferred_item_size (icon_view, GTK_ORIENTATION_HORIZONTAL, -1, &item_min, &item_nat);
+
+ if (priv->columns > 0)
+ {
+ *minimum = item_min * priv->columns + priv->column_spacing * (priv->columns - 1);
+ *natural = item_nat * priv->columns + priv->column_spacing * (priv->columns - 1);
+ }
+ else
+ {
+ int n_items = gtk_icon_view_get_n_items (icon_view);
+
+ *minimum = item_min;
+ *natural = item_nat * n_items + priv->column_spacing * (n_items - 1);
+ }
+
+ *minimum += 2 * priv->margin;
+ *natural += 2 * priv->margin;
+}
+
+static void
+gtk_icon_view_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum,
+ gint *natural)
+{
+ GtkIconView *icon_view = GTK_ICON_VIEW (widget);
+ GtkIconViewPrivate *priv = icon_view->priv;
+ int item_min, item_nat, rows, row_height, n_items;
+
+ if (gtk_icon_view_is_empty (icon_view))
+ {
+ *minimum = *natural = 2 * priv->margin;
+ return;
+ }
+
+ gtk_icon_view_compute_n_items_for_size (icon_view, GTK_ORIENTATION_VERTICAL, height, &rows, &row_height, NULL, NULL);
+ n_items = gtk_icon_view_get_n_items (icon_view);
+
+ gtk_icon_view_get_preferred_item_size (icon_view, GTK_ORIENTATION_HORIZONTAL, row_height, &item_min, &item_nat);
+ *minimum = (item_min + priv->column_spacing) * ((n_items + rows - 1) / rows) - priv->column_spacing;
+ *natural = (item_nat + priv->column_spacing) * ((n_items + rows - 1) / rows) - priv->column_spacing;
+
+ *minimum += 2 * priv->margin;
+ *natural += 2 * priv->margin;
+}
+