gimp/app/widgets/gtkvwrapbox.c
Kelly Lynn Martin 77c44b21e1 restructured toolbox to use a single gtk_hwrap_box with the added "forced
2000-02-07  Kelly Lynn Martin  <kelly@poverty.bloomington.in.us>

	* app/interface.c: restructured toolbox to use a single
	gtk_hwrap_box with the added "forced break" functionality to make
	the selector boxes not run in with the rest of the tool buttons.
	The toolbox should now not cut things off, although if the user
	selects a really small toolbox the buttons or selectors may be,
	um, difficult to use...

	* app/gtkwrapbox.h:
	* app/gtkwrapbox.c:
	* app/gtkvwrapbox.c (reverse_list_col_children):
	* app/gtkhwrapbox.c (reverse_list_row_children): support for
	"forced break" functionality needed for toolbox -- will forward
	patches to Tim Janik as well
2000-02-07 10:51:08 +00:00

655 lines
17 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* GtkVWrapBox: Vertical 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 2 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gtkvwrapbox.h"
#include <math.h>
/* --- prototypes --- */
static void gtk_vwrap_box_class_init (GtkVWrapBoxClass *klass);
static void gtk_vwrap_box_init (GtkVWrapBox *vwbox);
static void gtk_vwrap_box_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static void gtk_vwrap_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static GSList* reverse_list_col_children (GtkWrapBox *wbox,
GtkWrapBoxChild **child_p,
GtkAllocation *area,
guint *max_width,
gboolean *can_hexpand);
/* --- variables --- */
static gpointer parent_class = NULL;
/* --- functions --- */
GtkType
gtk_vwrap_box_get_type (void)
{
static GtkType vwrap_box_type = 0;
if (!vwrap_box_type)
{
static const GtkTypeInfo vwrap_box_info =
{
"GtkVWrapBox",
sizeof (GtkVWrapBox),
sizeof (GtkVWrapBoxClass),
(GtkClassInitFunc) gtk_vwrap_box_class_init,
(GtkObjectInitFunc) gtk_vwrap_box_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL,
};
vwrap_box_type = gtk_type_unique (GTK_TYPE_WRAP_BOX, &vwrap_box_info);
}
return vwrap_box_type;
}
static void
gtk_vwrap_box_class_init (GtkVWrapBoxClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkContainerClass *container_class;
GtkWrapBoxClass *wrap_box_class;
object_class = GTK_OBJECT_CLASS (class);
widget_class = GTK_WIDGET_CLASS (class);
container_class = GTK_CONTAINER_CLASS (class);
wrap_box_class = GTK_WRAP_BOX_CLASS (class);
parent_class = gtk_type_class (GTK_TYPE_WRAP_BOX);
widget_class->size_request = gtk_vwrap_box_size_request;
widget_class->size_allocate = gtk_vwrap_box_size_allocate;
wrap_box_class->rlist_line_children = reverse_list_col_children;
}
static void
gtk_vwrap_box_init (GtkVWrapBox *vwbox)
{
vwbox->max_child_height = 0;
vwbox->max_child_width = 0;
}
GtkWidget*
gtk_vwrap_box_new (gboolean homogeneous)
{
GtkVWrapBox *vwbox;
vwbox = GTK_VWRAP_BOX (gtk_widget_new (GTK_TYPE_VWRAP_BOX, NULL));
GTK_WRAP_BOX (vwbox)->homogeneous = homogeneous ? TRUE : FALSE;
return GTK_WIDGET (vwbox);
}
static inline void
get_child_requisition (GtkWrapBox *wbox,
GtkWidget *child,
GtkRequisition *child_requisition)
{
if (wbox->homogeneous)
{
GtkVWrapBox *vwbox = GTK_VWRAP_BOX (wbox);
child_requisition->height = vwbox->max_child_height;
child_requisition->width = vwbox->max_child_width;
}
else
gtk_widget_get_child_requisition (child, child_requisition);
}
static void
_gtk_vwrap_box_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
GtkVWrapBox *this = GTK_VWRAP_BOX (widget);
GtkWrapBox *wbox = GTK_WRAP_BOX (widget);
GtkWrapBoxChild *child;
guint area = 0;
g_return_if_fail (requisition != NULL);
/*<h2v-off>*/
requisition->width = 0;
requisition->height = 0;
this->max_child_width = 0;
this->max_child_height = 0;
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);
area += child_requisition.width * child_requisition.height;
this->max_child_width = MAX (this->max_child_width, child_requisition.width);
this->max_child_height = MAX (this->max_child_height, child_requisition.height);
}
if (wbox->homogeneous)
area = this->max_child_width * this->max_child_height * wbox->n_children;
if (area)
{
requisition->width = sqrt (area * wbox->aspect_ratio);
requisition->height = area / requisition->width;
}
else
{
requisition->width = 0;
requisition->height = 0;
}
requisition->width += GTK_CONTAINER (wbox)->border_width * 2;
requisition->height += GTK_CONTAINER (wbox)->border_width * 2;
/*<h2v-on>*/
}
static gfloat
get_layout_size (GtkVWrapBox *this,
guint max_height,
guint *height_inc)
{
GtkWrapBox *wbox = GTK_WRAP_BOX (this);
GtkWrapBoxChild *child;
guint n_cols, left_over = 0, total_width = 0;
gboolean last_col_filled = TRUE;
*height_inc = this->max_child_height + 1;
n_cols = 0;
for (child = wbox->children; child; child = child->next)
{
GtkWrapBoxChild *col_child;
GtkRequisition child_requisition;
guint col_height, col_width, n = 1;
if (!GTK_WIDGET_VISIBLE (child->widget))
continue;
get_child_requisition (wbox, child->widget, &child_requisition);
if (!last_col_filled)
*height_inc = MIN (*height_inc, child_requisition.height - left_over);
col_height = child_requisition.height;
col_width = child_requisition.width;
for (col_child = child->next; col_child && n < wbox->child_limit; col_child = col_child->next)
{
if (GTK_WIDGET_VISIBLE (col_child->widget))
{
get_child_requisition (wbox, col_child->widget, &child_requisition);
if (col_height + wbox->vspacing + child_requisition.height > max_height)
break;
col_height += wbox->vspacing + child_requisition.height;
col_width = MAX (col_width, child_requisition.width);
n++;
}
child = col_child;
}
last_col_filled = n >= wbox->child_limit;
left_over = last_col_filled ? 0 : max_height - (col_height + wbox->vspacing);
total_width += (n_cols ? wbox->hspacing : 0) + col_width;
n_cols++;
}
if (*height_inc > this->max_child_height)
*height_inc = 0;
return MAX (total_width, 1);
}
static void
gtk_vwrap_box_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
GtkVWrapBox *this = GTK_VWRAP_BOX (widget);
GtkWrapBox *wbox = GTK_WRAP_BOX (widget);
GtkWrapBoxChild *child;
gfloat ratio_dist, layout_height = 0;
guint col_inc = 0;
g_return_if_fail (requisition != NULL);
requisition->height = 0;
requisition->width = 0;
this->max_child_height = 0;
this->max_child_width = 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_height = MAX (this->max_child_height, child_requisition.height);
this->max_child_width = MAX (this->max_child_width, child_requisition.width);
}
/* figure all possible layouts */
ratio_dist = 32768;
layout_height = this->max_child_height;
do
{
gfloat layout_width;
gfloat ratio, dist;
layout_height += col_inc;
layout_width = get_layout_size (this, layout_height, &col_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->height = layout_height;
requisition->width = layout_width;
}
/* g_print ("ratio for height %d width %d = %f\n",
(gint) layout_height,
(gint) layout_width,
ratio);
*/
}
while (col_inc);
requisition->width += GTK_CONTAINER (wbox)->border_width * 2; /*<h2v-skip>*/
requisition->height += GTK_CONTAINER (wbox)->border_width * 2; /*<h2v-skip>*/
/* g_print ("choosen: height %d, width %d\n",
requisition->height,
requisition->width);
*/
}
static GSList*
reverse_list_col_children (GtkWrapBox *wbox,
GtkWrapBoxChild **child_p,
GtkAllocation *area,
guint *max_child_size,
gboolean *expand_line)
{
GSList *slist = NULL;
guint height = 0, col_height = area->height;
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);
height += child_requisition.height;
*max_child_size = MAX (*max_child_size, child_requisition.width);
*expand_line |= child->hexpand;
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 (height + wbox->vspacing + child_requisition.height > col_height ||
child->forced_break)
break;
height += wbox->vspacing + child_requisition.height;
*max_child_size = MAX (*max_child_size, child_requisition.width);
*expand_line |= child->hexpand;
slist = g_slist_prepend (slist, child);
n++;
}
*child_p = child->next;
child = *child_p;
}
}
return slist;
}
static void
layout_col (GtkWrapBox *wbox,
GtkAllocation *area,
GSList *children,
guint children_per_line,
gboolean hexpand)
{
GSList *slist;
guint n_children = 0, n_expand_children = 0, have_expand_children = 0, total_height = 0;
gfloat y, height, extra;
GtkAllocation child_allocation;
for (slist = children; slist; slist = slist->next)
{
GtkWrapBoxChild *child = slist->data;
GtkRequisition child_requisition;
n_children++;
if (child->vexpand)
n_expand_children++;
get_child_requisition (wbox, child->widget, &child_requisition);
total_height += child_requisition.height;
}
height = MAX (1, area->height - (n_children - 1) * wbox->vspacing);
if (height > total_height)
extra = height - total_height;
else
extra = 0;
have_expand_children = n_expand_children && extra;
y = area->y;
if (wbox->homogeneous)
{
height = MAX (1, area->height - (children_per_line - 1) * wbox->vspacing);
height /= ((gdouble) children_per_line);
extra = 0;
}
else if (have_expand_children && wbox->justify != GTK_JUSTIFY_FILL)
{
height = extra;
extra /= ((gdouble) n_expand_children);
}
else
{
if (wbox->justify == GTK_JUSTIFY_FILL)
{
height = extra;
have_expand_children = TRUE;
n_expand_children = n_children;
extra /= ((gdouble) n_expand_children);
}
else if (wbox->justify == GTK_JUSTIFY_CENTER)
{
y += extra / 2;
height = 0;
extra = 0;
}
else if (wbox->justify == GTK_JUSTIFY_LEFT)
{
height = 0;
extra = 0;
}
else if (wbox->justify == GTK_JUSTIFY_RIGHT)
{
y += extra;
height = 0;
extra = 0;
}
}
n_children = 0;
for (slist = children; slist; slist = slist->next)
{
GtkWrapBoxChild *child = slist->data;
child_allocation.y = y;
child_allocation.x = area->x;
if (wbox->homogeneous)
{
child_allocation.width = area->width;
child_allocation.height = height;
y += child_allocation.height + wbox->vspacing;
}
else
{
GtkRequisition child_requisition;
get_child_requisition (wbox, child->widget, &child_requisition);
if (child_requisition.width >= area->width)
child_allocation.width = area->width;
else
{
child_allocation.width = child_requisition.width;
if (wbox->line_justify == GTK_JUSTIFY_FILL || child->hfill)
child_allocation.width = area->width;
else if (child->hexpand || wbox->line_justify == GTK_JUSTIFY_CENTER)
child_allocation.x += (area->width - child_requisition.width) / 2;
else if (wbox->line_justify == GTK_JUSTIFY_BOTTOM)
child_allocation.x += area->width - child_requisition.width;
}
if (have_expand_children)
{
child_allocation.height = child_requisition.height;
if (child->vexpand || wbox->justify == GTK_JUSTIFY_FILL)
{
guint space;
n_expand_children--;
space = extra * n_expand_children;
space = height - space;
height -= space;
if (child->vfill)
child_allocation.height += space;
else
{
child_allocation.y += space / 2;
y += space;
}
}
}
else
{
/* g_print ("child_allocation.y %d += %d * %f ",
child_allocation.y, n_children, extra); */
child_allocation.y += n_children * extra;
/* g_print ("= %d\n",
child_allocation.y); */
child_allocation.height = MIN (child_requisition.height,
area->height - child_allocation.y + area->y);
}
}
y += child_allocation.height + wbox->vspacing;
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_cols (GtkWrapBox *wbox,
GtkAllocation *area)
{
GtkWrapBoxChild *next_child;
guint min_width;
gboolean hexpand;
GSList *slist;
Line *line_list = NULL;
guint total_width = 0, n_expand_lines = 0, n_lines = 0;
gfloat shrink_width;
guint children_per_line;
next_child = wbox->children;
slist = GTK_WRAP_BOX_GET_CLASS (wbox)->rlist_line_children (wbox,
&next_child,
area,
&min_width,
&hexpand);
slist = g_slist_reverse (slist);
children_per_line = g_slist_length (slist);
while (slist)
{
Line *line = g_new (Line, 1);
line->children = slist;
line->min_size = min_width;
total_width += min_width;
line->expand = hexpand;
if (hexpand)
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_width,
&hexpand);
slist = g_slist_reverse (slist);
}
if (total_width > area->width)
shrink_width = total_width - area->width;
else
shrink_width = 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_width)
{
Line *line = line_list;
guint shrink_fract = shrink_width / n_shrink_lines + 0.5;
if (line->min_size > shrink_fract)
{
shrink_width -= shrink_fract;
line->min_size -= shrink_fract;
}
else
{
shrink_width -= 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 x, width, extra = 0;
width = area->width;
width = MAX (n_lines, width - (n_lines - 1) * wbox->hspacing);
if (wbox->homogeneous)
width /= ((gdouble) n_lines);
else if (n_expand_lines)
{
width = MAX (0, width - total_width);
extra = width / ((gdouble) n_expand_lines);
}
else
width = 0;
x = area->x;
line = line_list;
while (line)
{
GtkAllocation col_allocation;
Line *next_line = line->next;
col_allocation.y = area->y;
col_allocation.height = area->height;
if (wbox->homogeneous)
col_allocation.width = width;
else
{
col_allocation.width = line->min_size;
if (line->expand)
col_allocation.width += extra;
}
col_allocation.x = x;
x += col_allocation.width + wbox->hspacing;
layout_col (wbox,
&col_allocation,
line->children,
children_per_line,
line->expand);
g_slist_free (line->children);
g_free (line);
line = next_line;
}
}
}
static void
gtk_vwrap_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkWrapBox *wbox = GTK_WRAP_BOX (widget);
GtkAllocation area;
guint border = GTK_CONTAINER (wbox)->border_width; /*<h2v-skip>*/
widget->allocation = *allocation;
area.y = allocation->y + border;
area.x = allocation->x + border;
area.height = MAX (1, (gint) allocation->height - border * 2);
area.width = MAX (1, (gint) allocation->width - border * 2);
/*<h2v-off>*/
/* g_print ("got: width %d, height %d\n",
allocation->width,
allocation->height);
*/
/*<h2v-on>*/
layout_cols (wbox, &area);
}