putty/unix/gtkcols.c

1202 строки
35 KiB
C

/*
* gtkcols.c - implementation of the `Columns' GTK layout container.
*/
#include <assert.h>
#include <gtk/gtk.h>
#include "defs.h"
#include "gtkcompat.h"
#include "gtkcols.h"
#if GTK_CHECK_VERSION(2,0,0)
/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it
* was in GtkContainer in GTK 1 */
#define FOCUS_METHOD_SUPERCLASS GtkWidget
#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */
#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir)
#else
#define FOCUS_METHOD_SUPERCLASS GtkContainer
#define FOCUS_METHOD_LOCATION container_class
#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir)
#endif
static void columns_init(Columns *cols);
static void columns_class_init(ColumnsClass *klass);
#if !GTK_CHECK_VERSION(2,0,0)
static void columns_finalize(GtkObject *object);
#else
static void columns_finalize(GObject *object);
#endif
static void columns_map(GtkWidget *widget);
static void columns_unmap(GtkWidget *widget);
#if !GTK_CHECK_VERSION(2,0,0)
static void columns_draw(GtkWidget *widget, GdkRectangle *area);
static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
#endif
static void columns_base_add(GtkContainer *container, GtkWidget *widget);
static void columns_remove(GtkContainer *container, GtkWidget *widget);
static void columns_forall(GtkContainer *container, gboolean include_internals,
GtkCallback callback, gpointer callback_data);
static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container,
GtkDirectionType dir);
static GType columns_child_type(GtkContainer *container);
#if GTK_CHECK_VERSION(3,0,0)
static void columns_get_preferred_width(GtkWidget *widget,
gint *min, gint *nat);
static void columns_get_preferred_height(GtkWidget *widget,
gint *min, gint *nat);
static void columns_get_preferred_width_for_height(GtkWidget *widget,
gint height,
gint *min, gint *nat);
static void columns_get_preferred_height_for_width(GtkWidget *widget,
gint width,
gint *min, gint *nat);
#else
static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
#endif
static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
static GtkContainerClass *parent_class = NULL;
#if !GTK_CHECK_VERSION(2,0,0)
GType columns_get_type(void)
{
static GType columns_type = 0;
if (!columns_type) {
static const GtkTypeInfo columns_info = {
"Columns",
sizeof(Columns),
sizeof(ColumnsClass),
(GtkClassInitFunc) columns_class_init,
(GtkObjectInitFunc) columns_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL,
};
columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
}
return columns_type;
}
#else
GType columns_get_type(void)
{
static GType columns_type = 0;
if (!columns_type) {
static const GTypeInfo columns_info = {
sizeof(ColumnsClass),
NULL,
NULL,
(GClassInitFunc) columns_class_init,
NULL,
NULL,
sizeof(Columns),
0,
(GInstanceInitFunc)columns_init,
};
columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
&columns_info, 0);
}
return columns_type;
}
#endif
static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container,
GtkDirectionType direction);
static void columns_class_init(ColumnsClass *klass)
{
#if !GTK_CHECK_VERSION(2,0,0)
GtkObjectClass *object_class = (GtkObjectClass *)klass;
GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
GtkContainerClass *container_class = (GtkContainerClass *)klass;
#else
GObjectClass *object_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
#endif
#if !GTK_CHECK_VERSION(2,0,0)
parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
#else
parent_class = g_type_class_peek_parent(klass);
#endif
object_class->finalize = columns_finalize;
widget_class->map = columns_map;
widget_class->unmap = columns_unmap;
#if !GTK_CHECK_VERSION(2,0,0)
widget_class->draw = columns_draw;
widget_class->expose_event = columns_expose;
#endif
#if GTK_CHECK_VERSION(3,0,0)
widget_class->get_preferred_width = columns_get_preferred_width;
widget_class->get_preferred_height = columns_get_preferred_height;
widget_class->get_preferred_width_for_height =
columns_get_preferred_width_for_height;
widget_class->get_preferred_height_for_width =
columns_get_preferred_height_for_width;
#else
widget_class->size_request = columns_size_request;
#endif
widget_class->size_allocate = columns_size_allocate;
container_class->add = columns_base_add;
container_class->remove = columns_remove;
container_class->forall = columns_forall;
container_class->child_type = columns_child_type;
/* Save the previous value of this method. */
if (!columns_inherited_focus)
columns_inherited_focus = FOCUS_METHOD_LOCATION->focus;
FOCUS_METHOD_LOCATION->focus = columns_focus;
}
static void columns_init(Columns *cols)
{
gtk_widget_set_has_window(GTK_WIDGET(cols), false);
cols->children = NULL;
cols->spacing = 0;
}
static void columns_child_free(gpointer vchild)
{
ColumnsChild *child = (ColumnsChild *)vchild;
if (child->percentages)
g_free(child->percentages);
g_free(child);
}
static void columns_finalize(
#if !GTK_CHECK_VERSION(2,0,0)
GtkObject *object
#else
GObject *object
#endif
)
{
Columns *cols;
g_return_if_fail(object != NULL);
g_return_if_fail(IS_COLUMNS(object));
cols = COLUMNS(object);
#if !GTK_CHECK_VERSION(2,0,0)
{
GList *node;
for (node = cols->children; node; node = node->next)
if (node->data)
columns_child_free(node->data);
}
g_list_free(cols->children);
#else
g_list_free_full(cols->children, columns_child_free);
#endif
cols->children = NULL;
#if !GTK_CHECK_VERSION(2,0,0)
GTK_OBJECT_CLASS(parent_class)->finalize(object);
#else
G_OBJECT_CLASS(parent_class)->finalize(object);
#endif
}
/*
* These appear to be thoroughly tedious functions; the only reason
* we have to reimplement them at all is because we defined our own
* format for our GList of children...
*/
static void columns_map(GtkWidget *widget)
{
Columns *cols;
ColumnsChild *child;
GList *children;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
cols = COLUMNS(widget);
gtk_widget_set_mapped(GTK_WIDGET(cols), true);
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget &&
gtk_widget_get_visible(child->widget) &&
!gtk_widget_get_mapped(child->widget))
gtk_widget_map(child->widget);
}
}
static void columns_unmap(GtkWidget *widget)
{
Columns *cols;
ColumnsChild *child;
GList *children;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
cols = COLUMNS(widget);
gtk_widget_set_mapped(GTK_WIDGET(cols), false);
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget &&
gtk_widget_get_visible(child->widget) &&
gtk_widget_get_mapped(child->widget))
gtk_widget_unmap(child->widget);
}
}
#if !GTK_CHECK_VERSION(2,0,0)
static void columns_draw(GtkWidget *widget, GdkRectangle *area)
{
Columns *cols;
ColumnsChild *child;
GList *children;
GdkRectangle child_area;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
if (GTK_WIDGET_DRAWABLE(widget)) {
cols = COLUMNS(widget);
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget &&
GTK_WIDGET_DRAWABLE(child->widget) &&
gtk_widget_intersect(child->widget, area, &child_area))
gtk_widget_draw(child->widget, &child_area);
}
}
}
static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
{
Columns *cols;
ColumnsChild *child;
GList *children;
GdkEventExpose child_event;
g_return_val_if_fail(widget != NULL, false);
g_return_val_if_fail(IS_COLUMNS(widget), false);
g_return_val_if_fail(event != NULL, false);
if (GTK_WIDGET_DRAWABLE(widget)) {
cols = COLUMNS(widget);
child_event = *event;
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget &&
GTK_WIDGET_DRAWABLE(child->widget) &&
GTK_WIDGET_NO_WINDOW(child->widget) &&
gtk_widget_intersect(child->widget, &event->area,
&child_event.area))
gtk_widget_event(child->widget, (GdkEvent *)&child_event);
}
}
return false;
}
#endif
static void columns_base_add(GtkContainer *container, GtkWidget *widget)
{
Columns *cols;
g_return_if_fail(container != NULL);
g_return_if_fail(IS_COLUMNS(container));
g_return_if_fail(widget != NULL);
cols = COLUMNS(container);
/*
* Default is to add a new widget spanning all columns.
*/
columns_add(cols, widget, 0, 0); /* 0 means ncols */
}
static void columns_remove(GtkContainer *container, GtkWidget *widget)
{
Columns *cols;
ColumnsChild *child;
GtkWidget *childw;
GList *children;
bool was_visible;
g_return_if_fail(container != NULL);
g_return_if_fail(IS_COLUMNS(container));
g_return_if_fail(widget != NULL);
cols = COLUMNS(container);
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget != widget)
continue;
was_visible = gtk_widget_get_visible(widget);
gtk_widget_unparent(widget);
cols->children = g_list_remove_link(cols->children, children);
g_list_free(children);
if (child->same_height_as) {
g_return_if_fail(child->same_height_as->same_height_as == child);
child->same_height_as->same_height_as = NULL;
if (gtk_widget_get_visible(child->same_height_as->widget))
gtk_widget_queue_resize(GTK_WIDGET(container));
}
g_free(child);
if (was_visible)
gtk_widget_queue_resize(GTK_WIDGET(container));
break;
}
for (children = cols->taborder;
children && (childw = children->data);
children = children->next) {
if (childw != widget)
continue;
cols->taborder = g_list_remove_link(cols->taborder, children);
g_list_free(children);
break;
}
}
static void columns_forall(GtkContainer *container, gboolean include_internals,
GtkCallback callback, gpointer callback_data)
{
Columns *cols;
ColumnsChild *child;
GList *children, *next;
g_return_if_fail(container != NULL);
g_return_if_fail(IS_COLUMNS(container));
g_return_if_fail(callback != NULL);
cols = COLUMNS(container);
for (children = cols->children;
children && (child = children->data);
children = next) {
/*
* We can't wait until after the callback to assign
* `children = children->next', because the callback might
* be gtk_widget_destroy, which would remove the link
* `children' from the list! So instead we must get our
* hands on the value of the `next' pointer _before_ the
* callback.
*/
next = children->next;
if (child->widget)
callback(child->widget, callback_data);
}
}
static GType columns_child_type(GtkContainer *container)
{
return GTK_TYPE_WIDGET;
}
GtkWidget *columns_new(gint spacing)
{
Columns *cols;
#if !GTK_CHECK_VERSION(2,0,0)
cols = gtk_type_new(columns_get_type());
#else
cols = g_object_new(TYPE_COLUMNS, NULL);
#endif
cols->spacing = spacing;
return GTK_WIDGET(cols);
}
void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
{
ColumnsChild *childdata;
gint i;
g_return_if_fail(cols != NULL);
g_return_if_fail(IS_COLUMNS(cols));
g_return_if_fail(ncols > 0);
g_return_if_fail(percentages != NULL);
childdata = g_new(ColumnsChild, 1);
childdata->widget = NULL;
childdata->ncols = ncols;
childdata->percentages = g_new(gint, ncols);
childdata->force_left = false;
for (i = 0; i < ncols; i++)
childdata->percentages[i] = percentages[i];
cols->children = g_list_append(cols->children, childdata);
}
void columns_add(Columns *cols, GtkWidget *child,
gint colstart, gint colspan)
{
ColumnsChild *childdata;
g_return_if_fail(cols != NULL);
g_return_if_fail(IS_COLUMNS(cols));
g_return_if_fail(child != NULL);
g_return_if_fail(gtk_widget_get_parent(child) == NULL);
childdata = g_new(ColumnsChild, 1);
childdata->widget = child;
childdata->colstart = colstart;
childdata->colspan = colspan;
childdata->force_left = false;
childdata->same_height_as = NULL;
childdata->percentages = NULL;
cols->children = g_list_append(cols->children, childdata);
cols->taborder = g_list_append(cols->taborder, child);
gtk_widget_set_parent(child, GTK_WIDGET(cols));
if (gtk_widget_get_realized(GTK_WIDGET(cols)))
gtk_widget_realize(child);
if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
gtk_widget_get_visible(child)) {
if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
gtk_widget_map(child);
gtk_widget_queue_resize(child);
}
}
static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
{
GList *children;
ColumnsChild *child;
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget == widget)
return child;
}
return NULL;
}
void columns_force_left_align(Columns *cols, GtkWidget *widget)
{
ColumnsChild *child;
g_return_if_fail(cols != NULL);
g_return_if_fail(IS_COLUMNS(cols));
g_return_if_fail(widget != NULL);
child = columns_find_child(cols, widget);
g_return_if_fail(child != NULL);
child->force_left = true;
if (gtk_widget_get_visible(widget))
gtk_widget_queue_resize(GTK_WIDGET(cols));
}
void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
{
ColumnsChild *child1, *child2;
g_return_if_fail(cols != NULL);
g_return_if_fail(IS_COLUMNS(cols));
g_return_if_fail(cw1 != NULL);
g_return_if_fail(cw2 != NULL);
child1 = columns_find_child(cols, cw1);
g_return_if_fail(child1 != NULL);
child2 = columns_find_child(cols, cw2);
g_return_if_fail(child2 != NULL);
child1->same_height_as = child2;
child2->same_height_as = child1;
if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
gtk_widget_queue_resize(GTK_WIDGET(cols));
}
void columns_taborder_last(Columns *cols, GtkWidget *widget)
{
GtkWidget *childw;
GList *children;
g_return_if_fail(cols != NULL);
g_return_if_fail(IS_COLUMNS(cols));
g_return_if_fail(widget != NULL);
for (children = cols->taborder;
children && (childw = children->data);
children = children->next) {
if (childw != widget)
continue;
cols->taborder = g_list_remove_link(cols->taborder, children);
g_list_free(children);
cols->taborder = g_list_append(cols->taborder, widget);
break;
}
}
/*
* Override GtkContainer's focus movement so the user can
* explicitly specify the tab order.
*/
static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir)
{
Columns *cols;
GList *pos;
GtkWidget *focuschild;
g_return_val_if_fail(super != NULL, false);
g_return_val_if_fail(IS_COLUMNS(super), false);
cols = COLUMNS(super);
if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) ||
!gtk_widget_is_sensitive(GTK_WIDGET(cols)))
return false;
if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) &&
(dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols));
gtk_container_set_focus_child(GTK_CONTAINER(cols), NULL);
if (dir == GTK_DIR_TAB_FORWARD)
pos = cols->taborder;
else
pos = g_list_last(cols->taborder);
while (pos) {
GtkWidget *child = pos->data;
if (focuschild) {
if (focuschild == child) {
focuschild = NULL; /* now we can start looking in here */
if (gtk_widget_is_drawable(child) &&
GTK_IS_CONTAINER(child) &&
!gtk_widget_has_focus(child)) {
if (CHILD_FOCUS(child, dir))
return true;
}
}
} else if (gtk_widget_is_drawable(child)) {
if (GTK_IS_CONTAINER(child)) {
if (CHILD_FOCUS(child, dir))
return true;
} else if (gtk_widget_get_can_focus(child)) {
gtk_widget_grab_focus(child);
return true;
}
}
if (dir == GTK_DIR_TAB_FORWARD)
pos = pos->next;
else
pos = pos->prev;
}
return false;
} else
return columns_inherited_focus(super, dir);
}
/*
* Underlying parts of the layout algorithm, to compute the Columns
* container's width or height given the widths or heights of its
* children. These will be called in various ways with different
* notions of width and height in use, so we abstract them out and
* pass them a 'get width' or 'get height' function pointer.
*/
typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
{
ColumnsChild *child;
GList *children;
gint i, ncols, colspan, retwidth, childwidth;
const gint *percentages;
static const gint onecol[] = { 100 };
#ifdef COLUMNS_WIDTH_DIAGNOSTICS
printf("compute_width(%p): start\n", cols);
#endif
retwidth = 0;
ncols = 1;
percentages = onecol;
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (!child->widget) {
/* Column reconfiguration. */
ncols = child->ncols;
percentages = child->percentages;
continue;
}
/* Only take visible widgets into account. */
if (!gtk_widget_get_visible(child->widget))
continue;
childwidth = get_width(child);
colspan = child->colspan ? child->colspan : ncols-child->colstart;
assert(colspan > 0);
#ifdef COLUMNS_WIDTH_DIAGNOSTICS
printf("compute_width(%p): ", cols);
if (GTK_IS_LABEL(child->widget))
printf("label %p '%s' wrap=%s: ", child->widget,
gtk_label_get_text(GTK_LABEL(child->widget)),
(gtk_label_get_line_wrap(GTK_LABEL(child->widget))
? "true" : "false"));
else
printf("widget %p: ", child->widget);
{
gint min, nat;
gtk_widget_get_preferred_width(child->widget, &min, &nat);
printf("minwidth=%d natwidth=%d ", min, nat);
}
printf("thiswidth=%d span=%d\n", childwidth, colspan);
#endif
/*
* To compute width: we know that childwidth + cols->spacing
* needs to equal a certain percentage of the full width of
* the container. So we work this value out, figure out how
* wide the container will need to be to make that percentage
* of it equal to that width, and ensure our returned width is
* at least that much. Very simple really.
*/
{
int percent, thiswid, fullwid;
percent = 0;
for (i = 0; i < colspan; i++)
percent += percentages[child->colstart+i];
thiswid = childwidth + cols->spacing;
/*
* Since childwidth is (at least sometimes) the _minimum_
* size the child needs, we must ensure that it gets _at
* least_ that size. Hence, when scaling thiswid up to
* fullwid, we must round up, which means adding percent-1
* before dividing by percent.
*/
fullwid = (thiswid * 100 + percent - 1) / percent;
#ifdef COLUMNS_WIDTH_DIAGNOSTICS
printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
cols, child->widget, thiswid, fullwid);
#endif
/*
* The above calculation assumes every widget gets
* cols->spacing on the right. So we subtract
* cols->spacing here to account for the extra load of
* spacing on the right.
*/
if (retwidth < fullwid - cols->spacing)
retwidth = fullwid - cols->spacing;
}
}
retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
#ifdef COLUMNS_WIDTH_DIAGNOSTICS
printf("compute_width(%p): done, returning %d\n", cols, retwidth);
#endif
return retwidth;
}
static void columns_alloc_horiz(Columns *cols, gint ourwidth,
widget_dim_fn_t get_width)
{
ColumnsChild *child;
GList *children;
gint i, ncols, colspan, border, *colxpos, childwidth;
border = gtk_container_get_border_width(GTK_CONTAINER(cols));
ncols = 1;
/* colxpos gives the starting x position of each column.
* We supply n+1 of them, so that we can find the RH edge easily.
* All ending x positions are expected to be adjusted afterwards by
* subtracting the spacing. */
colxpos = g_new(gint, 2);
colxpos[0] = 0;
colxpos[1] = ourwidth - 2*border + cols->spacing;
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (!child->widget) {
gint percent;
/* Column reconfiguration. */
ncols = child->ncols;
colxpos = g_renew(gint, colxpos, ncols + 1);
colxpos[0] = 0;
percent = 0;
for (i = 0; i < ncols; i++) {
percent += child->percentages[i];
colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
* percent / 100);
}
continue;
}
/* Only take visible widgets into account. */
if (!gtk_widget_get_visible(child->widget))
continue;
childwidth = get_width(child);
colspan = child->colspan ? child->colspan : ncols-child->colstart;
/*
* Starting x position is cols[colstart].
* Ending x position is cols[colstart+colspan] - spacing.
*
* Unless we're forcing left, in which case the width is
* exactly the requisition width.
*/
child->x = colxpos[child->colstart];
if (child->force_left)
child->w = childwidth;
else
child->w = (colxpos[child->colstart+colspan] -
colxpos[child->colstart] - cols->spacing);
}
g_free(colxpos);
}
static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
{
ColumnsChild *child;
GList *children;
gint i, ncols, colspan, *colypos, retheight, childheight;
retheight = cols->spacing;
ncols = 1;
colypos = g_new(gint, 1);
colypos[0] = 0;
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (!child->widget) {
/* Column reconfiguration. */
for (i = 1; i < ncols; i++) {
if (colypos[0] < colypos[i])
colypos[0] = colypos[i];
}
ncols = child->ncols;
colypos = g_renew(gint, colypos, ncols);
for (i = 1; i < ncols; i++)
colypos[i] = colypos[0];
continue;
}
/* Only take visible widgets into account. */
if (!gtk_widget_get_visible(child->widget))
continue;
childheight = get_height(child);
if (child->same_height_as) {
gint childheight2 = get_height(child->same_height_as);
if (childheight < childheight2)
childheight = childheight2;
}
colspan = child->colspan ? child->colspan : ncols-child->colstart;
/*
* To compute height: the widget's top will be positioned at
* the largest y value so far reached in any of the columns it
* crosses. Then it will go down by childheight plus padding;
* and the point it reaches at the bottom is the new y value
* in all those columns, and minus the padding it is also a
* lower bound on our own height.
*/
{
int topy, boty;
topy = 0;
for (i = 0; i < colspan; i++) {
if (topy < colypos[child->colstart+i])
topy = colypos[child->colstart+i];
}
boty = topy + childheight + cols->spacing;
for (i = 0; i < colspan; i++) {
colypos[child->colstart+i] = boty;
}
if (retheight < boty - cols->spacing)
retheight = boty - cols->spacing;
}
}
retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
g_free(colypos);
return retheight;
}
static void columns_alloc_vert(Columns *cols, gint ourheight,
widget_dim_fn_t get_height)
{
ColumnsChild *child;
GList *children;
gint i, ncols, colspan, *colypos, realheight, fakeheight;
ncols = 1;
/* As in size_request, colypos is the lowest y reached in each column. */
colypos = g_new(gint, 1);
colypos[0] = 0;
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (!child->widget) {
/* Column reconfiguration. */
for (i = 1; i < ncols; i++) {
if (colypos[0] < colypos[i])
colypos[0] = colypos[i];
}
ncols = child->ncols;
colypos = g_renew(gint, colypos, ncols);
for (i = 1; i < ncols; i++)
colypos[i] = colypos[0];
continue;
}
/* Only take visible widgets into account. */
if (!gtk_widget_get_visible(child->widget))
continue;
realheight = fakeheight = get_height(child);
if (child->same_height_as) {
gint childheight2 = get_height(child->same_height_as);
if (fakeheight < childheight2)
fakeheight = childheight2;
}
colspan = child->colspan ? child->colspan : ncols-child->colstart;
/*
* To compute height: the widget's top will be positioned
* at the largest y value so far reached in any of the
* columns it crosses. Then it will go down by creq.height
* plus padding; and the point it reaches at the bottom is
* the new y value in all those columns.
*/
{
int topy, boty;
topy = 0;
for (i = 0; i < colspan; i++) {
if (topy < colypos[child->colstart+i])
topy = colypos[child->colstart+i];
}
child->y = topy + fakeheight/2 - realheight/2;
child->h = realheight;
boty = topy + fakeheight + cols->spacing;
for (i = 0; i < colspan; i++) {
colypos[child->colstart+i] = boty;
}
}
}
g_free(colypos);
}
/*
* Now here comes the interesting bit. The actual layout part is
* done in the following two functions:
*
* columns_size_request() examines the list of widgets held in the
* Columns, and returns a requisition stating the absolute minimum
* size it can bear to be.
*
* columns_size_allocate() is given an allocation telling it what
* size the whole container is going to be, and it calls
* gtk_widget_size_allocate() on all of its (visible) children to
* set their size and position relative to the top left of the
* container.
*/
#if !GTK_CHECK_VERSION(3,0,0)
static gint columns_gtk2_get_width(ColumnsChild *child)
{
GtkRequisition creq;
gtk_widget_size_request(child->widget, &creq);
return creq.width;
}
static gint columns_gtk2_get_height(ColumnsChild *child)
{
GtkRequisition creq;
gtk_widget_size_request(child->widget, &creq);
return creq.height;
}
static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
{
Columns *cols;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
g_return_if_fail(req != NULL);
cols = COLUMNS(widget);
req->width = columns_compute_width(cols, columns_gtk2_get_width);
req->height = columns_compute_height(cols, columns_gtk2_get_height);
}
static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
{
Columns *cols;
ColumnsChild *child;
GList *children;
gint border;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
g_return_if_fail(alloc != NULL);
cols = COLUMNS(widget);
gtk_widget_set_allocation(widget, alloc);
border = gtk_container_get_border_width(GTK_CONTAINER(cols));
columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget && gtk_widget_get_visible(child->widget)) {
GtkAllocation call;
call.x = alloc->x + border + child->x;
call.y = alloc->y + border + child->y;
call.width = child->w;
call.height = child->h;
gtk_widget_size_allocate(child->widget, &call);
}
}
}
#else /* GTK_CHECK_VERSION(3,0,0) */
static gint columns_gtk3_get_min_width(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_width(child->widget, &ret, NULL);
return ret;
}
static gint columns_gtk3_get_nat_width(ColumnsChild *child)
{
gint ret;
if ((GTK_IS_LABEL(child->widget) &&
gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
GTK_IS_ENTRY(child->widget)) {
/*
* We treat wrapping GtkLabels as a special case in this
* layout class, because the whole point of those is that I
* _don't_ want them to take up extra horizontal space for
* long text, but instead to wrap it to whatever size is used
* by the rest of the layout.
*
* GtkEntry gets similar treatment, because in OS X GTK I've
* found that it requests a natural width regardless of the
* output of gtk_entry_set_width_chars.
*/
gtk_widget_get_preferred_width(child->widget, &ret, NULL);
} else {
gtk_widget_get_preferred_width(child->widget, NULL, &ret);
}
return ret;
}
static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_width_for_height(child->widget, child->h,
&ret, NULL);
return ret;
}
static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_width_for_height(child->widget, child->h,
NULL, &ret);
return ret;
}
static gint columns_gtk3_get_min_height(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_height(child->widget, &ret, NULL);
return ret;
}
static gint columns_gtk3_get_nat_height(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_height(child->widget, NULL, &ret);
return ret;
}
static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_height_for_width(child->widget, child->w,
&ret, NULL);
return ret;
}
static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
{
gint ret;
gtk_widget_get_preferred_height_for_width(child->widget, child->w,
NULL, &ret);
return ret;
}
static void columns_get_preferred_width(GtkWidget *widget,
gint *min, gint *nat)
{
Columns *cols;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
cols = COLUMNS(widget);
if (min)
*min = columns_compute_width(cols, columns_gtk3_get_min_width);
if (nat)
*nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
}
static void columns_get_preferred_height(GtkWidget *widget,
gint *min, gint *nat)
{
Columns *cols;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
cols = COLUMNS(widget);
if (min)
*min = columns_compute_height(cols, columns_gtk3_get_min_height);
if (nat)
*nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
}
static void columns_get_preferred_width_for_height(GtkWidget *widget,
gint height,
gint *min, gint *nat)
{
Columns *cols;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
cols = COLUMNS(widget);
/* FIXME: which one should the get-height function here be? */
columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
if (min)
*min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
if (nat)
*nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
}
static void columns_get_preferred_height_for_width(GtkWidget *widget,
gint width,
gint *min, gint *nat)
{
Columns *cols;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
cols = COLUMNS(widget);
/* FIXME: which one should the get-height function here be? */
columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
if (min)
*min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
if (nat)
*nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
}
static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
{
Columns *cols;
ColumnsChild *child;
GList *children;
gint border;
g_return_if_fail(widget != NULL);
g_return_if_fail(IS_COLUMNS(widget));
g_return_if_fail(alloc != NULL);
cols = COLUMNS(widget);
gtk_widget_set_allocation(widget, alloc);
border = gtk_container_get_border_width(GTK_CONTAINER(cols));
columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
for (children = cols->children;
children && (child = children->data);
children = children->next) {
if (child->widget && gtk_widget_get_visible(child->widget)) {
GtkAllocation call;
call.x = alloc->x + border + child->x;
call.y = alloc->y + border + child->y;
call.width = child->w;
call.height = child->h;
gtk_widget_size_allocate(child->widget, &call);
}
}
}
#endif