diff options
Diffstat (limited to '')
-rw-r--r-- | app/widgets/gtkhwrapbox.c | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/app/widgets/gtkhwrapbox.c b/app/widgets/gtkhwrapbox.c new file mode 100644 index 0000000..18b2ff4 --- /dev/null +++ b/app/widgets/gtkhwrapbox.c @@ -0,0 +1,607 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * GtkHWrapBox: Horizontal wrapping box widget + * Copyright (C) 1999 Tim Janik + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#undef GSEAL_ENABLE +#undef GTK_DISABLE_DEPRECATED + +#include "gtkhwrapbox.h" + +#include "libgimpmath/gimpmath.h" + + +/* --- prototypes --- */ +static void gtk_hwrap_box_class_init (GtkHWrapBoxClass *klass); +static void gtk_hwrap_box_init (GtkHWrapBox *hwbox); +static void gtk_hwrap_box_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gtk_hwrap_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static GSList* reverse_list_row_children (GtkWrapBox *wbox, + GtkWrapBoxChild **child_p, + GtkAllocation *area, + guint *max_height, + gboolean *can_vexpand); + + +/* --- variables --- */ +static gpointer parent_class = NULL; + + +/* --- functions --- */ +GType +gtk_hwrap_box_get_type (void) +{ + static GType hwrap_box_type = 0; + + if (! hwrap_box_type) + { + const GTypeInfo hwrap_box_info = + { + sizeof (GtkHWrapBoxClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_hwrap_box_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkHWrapBox), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_hwrap_box_init, + }; + + hwrap_box_type = g_type_register_static (GTK_TYPE_WRAP_BOX, "GtkHWrapBox", + &hwrap_box_info, 0); + } + + return hwrap_box_type; +} + +static void +gtk_hwrap_box_class_init (GtkHWrapBoxClass *class) +{ + GtkWidgetClass *widget_class; + GtkWrapBoxClass *wrap_box_class; + + widget_class = GTK_WIDGET_CLASS (class); + wrap_box_class = GTK_WRAP_BOX_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + widget_class->size_request = gtk_hwrap_box_size_request; + widget_class->size_allocate = gtk_hwrap_box_size_allocate; + + wrap_box_class->rlist_line_children = reverse_list_row_children; +} + +static void +gtk_hwrap_box_init (GtkHWrapBox *hwbox) +{ + hwbox->max_child_width = 0; + hwbox->max_child_height = 0; +} + +GtkWidget* +gtk_hwrap_box_new (gboolean homogeneous) +{ + return g_object_new (GTK_TYPE_HWRAP_BOX, "homogeneous", homogeneous, NULL); +} + +static inline void +get_child_requisition (GtkWrapBox *wbox, + GtkWidget *child, + GtkRequisition *child_requisition) +{ + if (wbox->homogeneous) + { + GtkHWrapBox *hwbox = GTK_HWRAP_BOX (wbox); + + child_requisition->width = hwbox->max_child_width; + child_requisition->height = hwbox->max_child_height; + } + else + gtk_widget_get_child_requisition (child, child_requisition); +} + +static gfloat +get_layout_size (GtkHWrapBox *this, + guint max_width, + guint *width_inc) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (this); + GtkWrapBoxChild *child; + guint n_rows, left_over = 0, total_height = 0; + gboolean last_row_filled = TRUE; + + *width_inc = this->max_child_width + 1; + + n_rows = 0; + for (child = wbox->children; child; child = child->next) + { + GtkWrapBoxChild *row_child; + GtkRequisition child_requisition; + guint row_width, row_height, n = 1; + + if (!GTK_WIDGET_VISIBLE (child->widget)) + continue; + + get_child_requisition (wbox, child->widget, &child_requisition); + if (!last_row_filled) + *width_inc = MIN (*width_inc, child_requisition.width - left_over); + row_width = child_requisition.width; + row_height = child_requisition.height; + for (row_child = child->next; row_child && n < wbox->child_limit; row_child = row_child->next) + { + if (GTK_WIDGET_VISIBLE (row_child->widget)) + { + get_child_requisition (wbox, row_child->widget, &child_requisition); + if (row_width + wbox->hspacing + child_requisition.width > max_width) + break; + row_width += wbox->hspacing + child_requisition.width; + row_height = MAX (row_height, child_requisition.height); + n++; + } + child = row_child; + } + last_row_filled = n >= wbox->child_limit; + left_over = last_row_filled ? 0 : max_width - (row_width + wbox->hspacing); + total_height += (n_rows ? wbox->vspacing : 0) + row_height; + n_rows++; + } + + if (*width_inc > this->max_child_width) + *width_inc = 0; + + return MAX (total_height, 1); +} + +static void +gtk_hwrap_box_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkHWrapBox *this = GTK_HWRAP_BOX (widget); + GtkWrapBox *wbox = GTK_WRAP_BOX (widget); + GtkWrapBoxChild *child; + gfloat ratio_dist, layout_width = 0; + guint row_inc = 0; + + g_return_if_fail (requisition != NULL); + + requisition->width = 0; + requisition->height = 0; + this->max_child_width = 0; + this->max_child_height = 0; + + /* size_request all children */ + for (child = wbox->children; child; child = child->next) + if (GTK_WIDGET_VISIBLE (child->widget)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (child->widget, &child_requisition); + + this->max_child_width = MAX (this->max_child_width, child_requisition.width); + this->max_child_height = MAX (this->max_child_height, child_requisition.height); + } + + /* figure all possible layouts */ + ratio_dist = 32768; + layout_width = this->max_child_width; + do + { + gfloat layout_height; + gfloat ratio, dist; + + layout_width += row_inc; + layout_height = get_layout_size (this, layout_width, &row_inc); + ratio = layout_width / layout_height; /*<h2v-skip>*/ + dist = MAX (ratio, wbox->aspect_ratio) - MIN (ratio, wbox->aspect_ratio); + if (dist < ratio_dist) + { + ratio_dist = dist; + requisition->width = layout_width; + requisition->height = layout_height; + } + + /* g_print ("ratio for width %d height %d = %f\n", + (gint) layout_width, + (gint) layout_height, + ratio); + */ + } + while (row_inc); + + requisition->width += GTK_CONTAINER (wbox)->border_width * 2; /*<h2v-skip>*/ + requisition->height += GTK_CONTAINER (wbox)->border_width * 2; /*<h2v-skip>*/ + /* g_print ("chosen: width %d, height %d\n", + requisition->width, + requisition->height); + */ +} + +static GSList* +reverse_list_row_children (GtkWrapBox *wbox, + GtkWrapBoxChild **child_p, + GtkAllocation *area, + guint *max_child_size, + gboolean *expand_line) +{ + GSList *slist = NULL; + guint width = 0, row_width = area->width; + GtkWrapBoxChild *child = *child_p; + + *max_child_size = 0; + *expand_line = FALSE; + + while (child && !GTK_WIDGET_VISIBLE (child->widget)) + { + *child_p = child->next; + child = *child_p; + } + + if (child) + { + GtkRequisition child_requisition; + guint n = 1; + + get_child_requisition (wbox, child->widget, &child_requisition); + width += child_requisition.width; + *max_child_size = MAX (*max_child_size, child_requisition.height); + *expand_line |= child->vexpand; + slist = g_slist_prepend (slist, child); + *child_p = child->next; + child = *child_p; + + while (child && n < wbox->child_limit) + { + if (GTK_WIDGET_VISIBLE (child->widget)) + { + get_child_requisition (wbox, child->widget, &child_requisition); + if (width + wbox->hspacing + child_requisition.width > row_width || + child->wrapped) + break; + width += wbox->hspacing + child_requisition.width; + *max_child_size = MAX (*max_child_size, child_requisition.height); + *expand_line |= child->vexpand; + slist = g_slist_prepend (slist, child); + n++; + } + *child_p = child->next; + child = *child_p; + } + } + + return slist; +} + +static void +layout_row (GtkWrapBox *wbox, + GtkAllocation *area, + GSList *children, + guint children_per_line, + gboolean vexpand) +{ + GSList *slist; + guint n_children = 0, n_expand_children = 0, have_expand_children = 0; + gint total_width = 0; + gfloat x, width, extra; + GtkAllocation child_allocation; + + for (slist = children; slist; slist = slist->next) + { + GtkWrapBoxChild *child = slist->data; + GtkRequisition child_requisition; + + n_children++; + if (child->hexpand) + n_expand_children++; + + get_child_requisition (wbox, child->widget, &child_requisition); + total_width += child_requisition.width; + } + + width = MAX (1, area->width - (n_children - 1) * wbox->hspacing); + if (width > total_width) + extra = width - total_width; + else + extra = 0; + have_expand_children = n_expand_children && extra; + + x = area->x; + if (wbox->homogeneous) + { + width = MAX (1, area->width - (children_per_line - 1) * wbox->hspacing); + width /= ((gdouble) children_per_line); + extra = 0; + } + else if (have_expand_children && wbox->justify != GTK_JUSTIFY_FILL) + { + width = extra; + extra /= ((gdouble) n_expand_children); + } + else + { + if (wbox->justify == GTK_JUSTIFY_FILL) + { + width = extra; + have_expand_children = TRUE; + n_expand_children = n_children; + extra /= ((gdouble) n_expand_children); + } + else if (wbox->justify == GTK_JUSTIFY_CENTER) + { + x += extra / 2; + width = 0; + extra = 0; + } + else if (wbox->justify == GTK_JUSTIFY_LEFT) + { + width = 0; + extra = 0; + } + else if (wbox->justify == GTK_JUSTIFY_RIGHT) + { + x += extra; + width = 0; + extra = 0; + } + } + + n_children = 0; + for (slist = children; slist; slist = slist->next) + { + GtkWrapBoxChild *child = slist->data; + + child_allocation.x = x; + child_allocation.y = area->y; + if (wbox->homogeneous) + { + child_allocation.height = area->height; + child_allocation.width = width; + x += child_allocation.width + wbox->hspacing; + } + else + { + GtkRequisition child_requisition; + + get_child_requisition (wbox, child->widget, &child_requisition); + + if (child_requisition.height >= area->height) + child_allocation.height = area->height; + else + { + child_allocation.height = child_requisition.height; + if (wbox->line_justify == GTK_JUSTIFY_FILL || child->vfill) + child_allocation.height = area->height; + else if (child->vexpand || wbox->line_justify == GTK_JUSTIFY_CENTER) + child_allocation.y += (area->height - child_requisition.height) / 2; + else if (wbox->line_justify == GTK_JUSTIFY_BOTTOM) + child_allocation.y += area->height - child_requisition.height; + } + + if (have_expand_children) + { + child_allocation.width = child_requisition.width; + if (child->hexpand || wbox->justify == GTK_JUSTIFY_FILL) + { + guint space; + + n_expand_children--; + space = extra * n_expand_children; + space = width - space; + width -= space; + if (child->hfill) + child_allocation.width += space; + else + { + child_allocation.x += space / 2; + x += space; + } + } + } + else + { + /* g_print ("child_allocation.x %d += %d * %f ", + child_allocation.x, n_children, extra); */ + child_allocation.x += n_children * extra; + /* g_print ("= %d\n", + child_allocation.x); */ + child_allocation.width = MIN (child_requisition.width, + area->width - child_allocation.x + area->x); + } + } + + x += child_allocation.width + wbox->hspacing; + gtk_widget_size_allocate (child->widget, &child_allocation); + n_children++; + } +} + +typedef struct _Line Line; +struct _Line +{ + GSList *children; + guint16 min_size; + guint expand : 1; + Line *next; +}; + +static void +layout_rows (GtkWrapBox *wbox, + GtkAllocation *area) +{ + GtkWrapBoxChild *next_child; + guint min_height; + gboolean vexpand; + GSList *slist; + Line *line_list = NULL; + guint total_height = 0, n_expand_lines = 0, n_lines = 0; + gfloat shrink_height; + guint children_per_line; + + next_child = wbox->children; + slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox, + &next_child, + area, + &min_height, + &vexpand); + slist = g_slist_reverse (slist); + + children_per_line = g_slist_length (slist); + while (slist) + { + Line *line = g_slice_new (Line); + + line->children = slist; + line->min_size = min_height; + total_height += min_height; + line->expand = vexpand; + if (vexpand) + n_expand_lines++; + line->next = line_list; + line_list = line; + n_lines++; + + slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox, + &next_child, + area, + &min_height, + &vexpand); + slist = g_slist_reverse (slist); + } + + if (total_height > area->height) + shrink_height = total_height - area->height; + else + shrink_height = 0; + + if (1) /* reverse lines and shrink */ + { + Line *prev = NULL, *last = NULL; + gfloat n_shrink_lines = n_lines; + + while (line_list) + { + Line *tmp = line_list->next; + + if (shrink_height) + { + Line *line = line_list; + guint shrink_fract = shrink_height / n_shrink_lines + 0.5; + + if (line->min_size > shrink_fract) + { + shrink_height -= shrink_fract; + line->min_size -= shrink_fract; + } + else + { + shrink_height -= line->min_size - 1; + line->min_size = 1; + } + } + n_shrink_lines--; + + last = line_list; + line_list->next = prev; + prev = line_list; + line_list = tmp; + } + line_list = last; + } + + if (n_lines) + { + Line *line; + gfloat y, height, extra = 0; + + height = area->height; + height = MAX (n_lines, height - (n_lines - 1) * wbox->vspacing); + + if (wbox->homogeneous) + height /= ((gdouble) n_lines); + else if (n_expand_lines) + { + height = MAX (0, height - total_height); + extra = height / ((gdouble) n_expand_lines); + } + else + height = 0; + + y = area->y; + line = line_list; + while (line) + { + GtkAllocation row_allocation; + Line *next_line = line->next; + + row_allocation.x = area->x; + row_allocation.width = area->width; + if (wbox->homogeneous) + row_allocation.height = height; + else + { + row_allocation.height = line->min_size; + + if (line->expand) + row_allocation.height += extra; + } + + row_allocation.y = y; + + y += row_allocation.height + wbox->vspacing; + layout_row (wbox, + &row_allocation, + line->children, + children_per_line, + line->expand); + + g_slist_free (line->children); + line = next_line; + } + + g_slice_free_chain (Line, line_list, next); + } +} + +static void +gtk_hwrap_box_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWrapBox *wbox = GTK_WRAP_BOX (widget); + GtkAllocation area; + gint border = GTK_CONTAINER (wbox)->border_width; /*<h2v-skip>*/ + + widget->allocation = *allocation; + area.x = allocation->x + border; + area.y = allocation->y + border; + area.width = MAX (1, (gint) allocation->width - border * 2); + area.height = MAX (1, (gint) allocation->height - border * 2); + + /*<h2v-off>*/ + /* g_print ("got: width %d, height %d\n", + allocation->width, + allocation->height); + */ + /*<h2v-on>*/ + + layout_rows (wbox, &area); +} |