First stab at a GTK layout engine. It's missing all sorts of stuff
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 13 Mar 2003 19:52:28 +0000 (19:52 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 13 Mar 2003 19:52:28 +0000 (19:52 +0000)
(list boxes are particularly conspicuously absent), it has no event
handling at all, and it isn't in any way integrated into pterm - you
have to build it specially using the test stubs in gtkdlg.c. But
what there is so far seems to work plausibly well, so it's a start.
Rather than browbeat the existing GTK container/layout widgets into
doing what I wanted, I decided to implement two subclasses of
GtkContainer myself, which implement precisely the layout model
assumed by the config box specification; this has the rather cool
consequence that the box can be resized and will maintain the same
layout at all times that it would have had if initially created at
that size.

git-svn-id: svn://svn.tartarus.org/sgt/putty@2931 cda61777-01e9-0310-a592-d414129be87e

config.c
unix/gtkcols.c [new file with mode: 0644]
unix/gtkcols.h [new file with mode: 0644]
unix/gtkdlg.c [new file with mode: 0644]
unix/gtkpanel.c [new file with mode: 0644]
unix/gtkpanel.h [new file with mode: 0644]
unix/unix.h

index 961dc7f..6aac6ce 100644 (file)
--- a/config.c
+++ b/config.c
@@ -723,7 +723,7 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
                         I(offsetof(Config,port)), I(-1));
        c->generic.column = 1;
        ctrl_columns(s, 1, 100);
-       if (backends[3].backend == NULL) {
+       if (backends[3].name == NULL) {
            ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 3,
                              HELPCTX(session_hostname),
                              protocolbuttons_handler, P(c),
@@ -1366,7 +1366,7 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
      * All the SSH stuff is omitted in PuTTYtel.
      */
 
-    if (!midsession && backends[3].backend != NULL) {
+    if (!midsession && backends[3].name != NULL) {
 
        /*
         * The Connection/SSH panel.
diff --git a/unix/gtkcols.c b/unix/gtkcols.c
new file mode 100644 (file)
index 0000000..d4c9367
--- /dev/null
@@ -0,0 +1,685 @@
+/*
+ * gtkcols.c - implementation of the `Columns' GTK layout container.
+ */
+
+#include "gtkcols.h"
+
+static void columns_init(Columns *cols);
+static void columns_class_init(ColumnsClass *klass);
+static void columns_map(GtkWidget *widget);
+static void columns_unmap(GtkWidget *widget);
+static void columns_draw(GtkWidget *widget, GdkRectangle *area);
+static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
+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(GtkContainer *container, GtkDirectionType dir);
+static GtkType columns_child_type(GtkContainer *container);
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
+
+static GtkContainerClass *parent_class = NULL;
+
+GtkType columns_get_type(void)
+{
+    static GtkType 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;
+}
+
+static gint (*columns_inherited_focus)(GtkContainer *container,
+                                      GtkDirectionType direction);
+
+static void columns_class_init(ColumnsClass *klass)
+{
+    GtkObjectClass *object_class;
+    GtkWidgetClass *widget_class;
+    GtkContainerClass *container_class;
+
+    object_class = (GtkObjectClass *)klass;
+    widget_class = (GtkWidgetClass *)klass;
+    container_class = (GtkContainerClass *)klass;
+
+    parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
+
+    /*
+     * FIXME: do we have to do all this faffing with set_arg,
+     * get_arg and child_arg_type? Ick.
+     */
+
+    widget_class->map = columns_map;
+    widget_class->unmap = columns_unmap;
+    widget_class->draw = columns_draw;
+    widget_class->expose_event = columns_expose;
+    widget_class->size_request = columns_size_request;
+    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 = container_class->focus;
+    container_class->focus = columns_focus;
+}
+
+static void columns_init(Columns *cols)
+{
+    GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
+
+    cols->children = NULL;
+    cols->spacing = 0;
+}
+
+/*
+ * 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_FLAGS(cols, GTK_MAPPED);
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child->widget &&
+           GTK_WIDGET_VISIBLE(child->widget) &&
+            !GTK_WIDGET_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_UNSET_FLAGS(cols, GTK_MAPPED);
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child->widget &&
+           GTK_WIDGET_VISIBLE(child->widget) &&
+            GTK_WIDGET_MAPPED(child->widget))
+            gtk_widget_unmap(child->widget);
+    }
+}
+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;
+}
+
+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;
+    gboolean 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_VISIBLE(widget);
+        gtk_widget_unparent(widget);
+        cols->children = g_list_remove_link(cols->children, children);
+        g_list_free(children);
+        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;
+
+    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 = children->next)
+       if (child->widget)
+           callback(child->widget, callback_data);
+}
+
+static GtkType columns_child_type(GtkContainer *container)
+{
+    return GTK_TYPE_WIDGET;
+}
+
+GtkWidget *columns_new(gint spacing)
+{
+    Columns *cols;
+
+    cols = gtk_type_new(columns_get_type());
+    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(child->parent == NULL);
+
+    childdata = g_new(ColumnsChild, 1);
+    childdata->widget = child;
+    childdata->colstart = colstart;
+    childdata->colspan = colspan;
+    childdata->force_left = FALSE;
+
+    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_REALIZED(cols))
+        gtk_widget_realize(child);
+
+    if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
+        if (GTK_WIDGET_MAPPED(cols))
+            gtk_widget_map(child);
+        gtk_widget_queue_resize(child);
+    }
+}
+
+void columns_force_left_align(Columns *cols, GtkWidget *widget)
+{
+    ColumnsChild *child;
+    GList *children;
+
+    g_return_if_fail(cols != NULL);
+    g_return_if_fail(IS_COLUMNS(cols));
+    g_return_if_fail(widget != NULL);
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child->widget != widget)
+            continue;
+
+       child->force_left = TRUE;
+        if (GTK_WIDGET_VISIBLE(widget))
+            gtk_widget_queue_resize(GTK_WIDGET(cols));
+        break;
+    }
+}
+
+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(GtkContainer *container, GtkDirectionType dir)
+{
+    Columns *cols;
+    GList *pos;
+    GtkWidget *focuschild;
+
+    g_return_val_if_fail(container != NULL, FALSE);
+    g_return_val_if_fail(IS_COLUMNS(container), FALSE);
+
+    cols = COLUMNS(container);
+
+    if (!GTK_WIDGET_DRAWABLE(cols) ||
+       !GTK_WIDGET_IS_SENSITIVE(cols))
+       return FALSE;
+
+    if (!GTK_WIDGET_CAN_FOCUS(container) &&
+       (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
+
+       focuschild = container->focus_child;
+       gtk_container_set_focus_child(container, 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_DRAWABLE(child) &&
+                       GTK_IS_CONTAINER(child) &&
+                       !GTK_WIDGET_HAS_FOCUS(child)) {
+                       if (gtk_container_focus(GTK_CONTAINER(child), dir))
+                           return TRUE;
+                   }
+               }
+           } else if (GTK_WIDGET_DRAWABLE(child)) {
+               if (GTK_IS_CONTAINER(child)) {
+                   if (gtk_container_focus(GTK_CONTAINER(child), dir))
+                       return TRUE;
+               } else if (GTK_WIDGET_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(container, dir);
+}
+
+/*
+ * 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.
+ */
+
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
+{
+    Columns *cols;
+    ColumnsChild *child;
+    GList *children;
+    gint i, ncols, colspan, *colypos;
+    const gint *percentages;
+    static const gint onecol[] = { 100 };
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+    g_return_if_fail(req != NULL);
+
+    cols = COLUMNS(widget);
+
+    req->width = 0;
+    req->height = cols->spacing;
+
+    ncols = 1;
+    colypos = g_new(gint, 1);
+    colypos[0] = 0;
+    percentages = onecol;
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+        GtkRequisition creq;
+
+       if (!child->widget) {
+           /* Column reconfiguration. */
+           for (i = 1; i < ncols; i++) {
+               if (colypos[0] < colypos[i])
+                   colypos[0] = colypos[i];
+           }
+           ncols = child->ncols;
+           percentages = child->percentages;
+           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_VISIBLE(child->widget))
+            continue;
+
+        gtk_widget_size_request(child->widget, &creq);
+       colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+        /*
+         * To compute width: we know that creq.width plus
+         * 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 = creq.width + cols->spacing;
+            /*
+             * Since creq is 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;
+
+            /*
+             * 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 (req->width < fullwid - cols->spacing)
+                req->width = fullwid - cols->spacing;
+        }
+
+        /*
+         * 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, and minus the
+         * padding it is also a lower bound on our own size
+         * request.
+         */
+        {
+            int topy, boty;
+
+            topy = 0;
+            for (i = 0; i < colspan; i++) {
+                if (topy < colypos[child->colstart+i])
+                    topy = colypos[child->colstart+i];
+            }
+            boty = topy + creq.height + cols->spacing;
+            for (i = 0; i < colspan; i++) {
+                colypos[child->colstart+i] = boty;
+            }
+
+            if (req->height < boty - cols->spacing)
+                req->height = boty - cols->spacing;
+        }
+    }
+
+    req->width += 2*GTK_CONTAINER(cols)->border_width;
+    req->height += 2*GTK_CONTAINER(cols)->border_width;
+
+    g_free(colypos);
+}
+
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+    Columns *cols;
+    ColumnsChild *child;
+    GList *children;
+    gint i, ncols, colspan, border, *colxpos, *colypos;
+    const gint *percentages;
+    static const gint onecol[] = { 100 };
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+    g_return_if_fail(alloc != NULL);
+
+    cols = COLUMNS(widget);
+    widget->allocation = *alloc;
+    border = GTK_CONTAINER(cols)->border_width;
+
+    ncols = 1;
+    percentages = onecol;
+    /* 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] = alloc->width - 2*border + cols->spacing;
+    /* 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) {
+        GtkRequisition creq;
+        GtkAllocation call;
+
+       if (!child->widget) {
+           gint percent;
+
+           /* Column reconfiguration. */
+           for (i = 1; i < ncols; i++) {
+               if (colypos[0] < colypos[i])
+                   colypos[0] = colypos[i];
+           }
+           ncols = child->ncols;
+           percentages = child->percentages;
+           colypos = g_renew(gint, colypos, ncols);
+           for (i = 1; i < ncols; i++)
+               colypos[i] = colypos[0];
+           colxpos = g_renew(gint, colxpos, ncols + 1);
+           colxpos[0] = 0;
+           percent = 0;
+           for (i = 0; i < ncols; i++) {
+               percent += percentages[i];
+               colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
+                               * percent / 100);
+           }
+           continue;
+       }
+
+        /* Only take visible widgets into account. */
+        if (!GTK_WIDGET_VISIBLE(child->widget))
+            continue;
+
+        gtk_widget_get_child_requisition(child->widget, &creq);
+       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.
+         */
+        call.x = alloc->x + border + colxpos[child->colstart];
+       if (child->force_left)
+           call.width = creq.width;
+       else
+           call.width = (colxpos[child->colstart+colspan] -
+                         colxpos[child->colstart] - cols->spacing);
+
+        /*
+         * 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];
+            }
+            call.y = alloc->y + border + topy;
+            call.height = creq.height;
+            boty = topy + creq.height + cols->spacing;
+            for (i = 0; i < colspan; i++) {
+                colypos[child->colstart+i] = boty;
+            }
+        }
+
+        gtk_widget_size_allocate(child->widget, &call);
+    }
+
+    g_free(colxpos);
+    g_free(colypos);    
+}
diff --git a/unix/gtkcols.h b/unix/gtkcols.h
new file mode 100644 (file)
index 0000000..5bcc627
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * gtkcols.h - header file for a columns-based widget container
+ * capable of supporting the PuTTY portable dialog box layout
+ * mechanism.
+ */
+
+#ifndef COLUMNS_H
+#define COLUMNS_H
+
+#include <gdk/gdk.h>
+#include <gtk/gtkcontainer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define TYPE_COLUMNS (columns_get_type())
+#define COLUMNS(obj) (GTK_CHECK_CAST((obj), TYPE_COLUMNS, Columns))
+#define COLUMNS_CLASS(klass) \
+                (GTK_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
+#define IS_COLUMNS(obj) (GTK_CHECK_TYPE((obj), TYPE_COLUMNS))
+#define IS_COLUMNS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
+
+typedef struct Columns_tag Columns;
+typedef struct ColumnsClass_tag ColumnsClass;
+typedef struct ColumnsChild_tag ColumnsChild;
+
+struct Columns_tag {
+    GtkContainer container;
+    /* private after here */
+    GList *children;                  /* this holds ColumnsChild structures */
+    GList *taborder;                  /* this just holds GtkWidgets */
+    gint spacing;
+};
+
+struct ColumnsClass_tag {
+    GtkContainerClass parent_class;
+};
+
+struct ColumnsChild_tag {
+    /* If `widget' is non-NULL, this entry represents an actual widget. */
+    GtkWidget *widget;
+    gint colstart, colspan;
+    gboolean force_left;              /* for recalcitrant GtkLabels */
+    /* Otherwise, this entry represents a change in the column setup. */
+    gint ncols;
+    gint *percentages;
+};
+
+GtkType columns_get_type(void);
+GtkWidget *columns_new(gint spacing);
+void columns_set_cols(Columns *cols, gint ncols, const gint *percentages);
+void columns_add(Columns *cols, GtkWidget *child,
+                 gint colstart, gint colspan);
+void columns_taborder_last(Columns *cols, GtkWidget *child);
+void columns_force_left_align(Columns *cols, GtkWidget *child);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* COLUMNS_H */
diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c
new file mode 100644 (file)
index 0000000..85a2d7b
--- /dev/null
@@ -0,0 +1,633 @@
+/*
+ * gtkdlg.c - GTK implementation of the PuTTY configuration box.
+ */
+
+#include <assert.h>
+#include <gtk/gtk.h>
+
+#include "gtkcols.h"
+#include "gtkpanel.h"
+
+#ifdef TESTMODE
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
+#endif
+
+#include "putty.h"
+#include "dialog.h"
+
+
+void *dlg_get_privdata(union control *ctrl, void *dlg)
+{
+    return NULL;                      /* FIXME */
+}
+
+void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr)
+{
+    /* FIXME */
+}
+
+void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size)
+{
+    return NULL;                      /* FIXME */
+}
+
+union control *dlg_last_focused(void *dlg)
+{
+    return NULL;                       /* FIXME */
+}
+
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton)
+{
+    /* FIXME */
+}
+
+int dlg_radiobutton_get(union control *ctrl, void *dlg)
+{
+    return 0;                          /* FIXME */
+}
+
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
+{
+    /* FIXME */
+}
+
+int dlg_checkbox_get(union control *ctrl, void *dlg)
+{
+    return 0;                          /* FIXME */
+}
+
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
+{
+    /* FIXME */
+}
+
+void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length)
+{
+    /* FIXME */
+}
+
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(union control *ctrl, void *dlg)
+{
+    /* FIXME */
+}
+
+void dlg_listbox_del(union control *ctrl, void *dlg, int index)
+{
+    /* FIXME */
+}
+
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
+{
+    /* FIXME */
+}
+
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithindex(union control *ctrl, void *dlg,
+                             char const *text, int id)
+{
+    /* FIXME */
+}
+
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
+{
+    return -1;                         /* FIXME */
+}
+
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(union control *ctrl, void *dlg)
+{
+    return -1;                         /* FIXME */
+}
+
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
+{
+    return 0;                          /* FIXME */
+}
+
+void dlg_listbox_select(union control *ctrl, void *dlg, int index)
+{
+    /* FIXME */
+}
+
+void dlg_text_set(union control *ctrl, void *dlg, char const *text)
+{
+    /* FIXME */
+}
+
+void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
+{
+    /* FIXME */
+}
+
+void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn)
+{
+    /* FIXME */
+}
+
+void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
+{
+    /* FIXME */
+}
+
+void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs)
+{
+    /* FIXME */
+}
+
+/*
+ * Bracketing a large set of updates in these two functions will
+ * cause the front end (if possible) to delay updating the screen
+ * until it's all complete, thus avoiding flicker.
+ */
+void dlg_update_start(union control *ctrl, void *dlg)
+{
+    /* FIXME */
+}
+
+void dlg_update_done(union control *ctrl, void *dlg)
+{
+    /* FIXME */
+}
+
+void dlg_set_focus(union control *ctrl, void *dlg)
+{
+    /* FIXME */
+}
+
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(void *dlg)
+{
+    /* FIXME */
+}
+
+void dlg_error_msg(void *dlg, char *msg)
+{
+    /* FIXME */
+}
+
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(void *dlg, int value)
+{
+    /* FIXME */
+}
+
+void dlg_refresh(union control *ctrl, void *dlg)
+{
+    /* FIXME */
+}
+
+void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
+{
+    /* FIXME */
+}
+
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+                         int *r, int *g, int *b)
+{
+    return 0;                          /* FIXME */
+}
+
+/*
+ * This function does the main layout work: it reads a controlset,
+ * it creates the relevant GTK controls, and returns a GtkWidget
+ * containing the result. (This widget might be a title of some
+ * sort, it might be a Columns containing many controls, or it
+ * might be a GtkFrame containing a Columns; whatever it is, it's
+ * definitely a GtkWidget and should probably be added to a
+ * GtkVbox.)
+ */
+GtkWidget *layout_ctrls(struct controlset *s)
+{
+    Columns *cols;
+    GtkWidget *ret;
+    int i;
+
+    if (!s->boxname && s->boxtitle) {
+        /* This controlset is a panel title. */
+        return gtk_label_new(s->boxtitle);
+    }
+
+    /*
+     * Otherwise, we expect to be laying out actual controls, so
+     * we'll start by creating a Columns for the purpose.
+     */
+    cols = COLUMNS(columns_new(4));
+    ret = GTK_WIDGET(cols);
+    gtk_widget_show(ret);
+
+    /*
+     * Create a containing frame if we have a box name.
+     */
+    if (*s->boxname) {
+        ret = gtk_frame_new(s->boxtitle);   /* NULL is valid here */
+        gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
+        gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
+        gtk_widget_show(ret);
+    }
+
+    /*
+     * Now iterate through the controls themselves, create them,
+     * and add them to the Columns.
+     */
+    for (i = 0; i < s->ncontrols; i++) {
+       union control *ctrl = s->ctrls[i];
+        GtkWidget *w = NULL;
+
+        switch (ctrl->generic.type) {
+          case CTRL_COLUMNS:
+            {
+                static const int simplecols[1] = { 100 };
+                columns_set_cols(cols, ctrl->columns.ncols,
+                                 (ctrl->columns.percentages ?
+                                  ctrl->columns.percentages : simplecols));
+            }
+            continue;                  /* no actual control created */
+          case CTRL_TABDELAY:
+            /* FIXME: we can do columns_taborder_last easily enough, but
+             * we need to be able to remember which GtkWidget(s) correspond
+             * to ctrl->tabdelay.ctrl. */
+            continue;                  /* no actual control created */
+          case CTRL_BUTTON:
+            w = gtk_button_new_with_label(ctrl->generic.label);
+            break;
+          case CTRL_CHECKBOX:
+            w = gtk_check_button_new_with_label(ctrl->generic.label);
+            break;
+          case CTRL_RADIO:
+            /*
+             * Radio buttons get to go inside their own Columns, no
+             * matter what.
+             */
+            {
+                gint i, *percentages;
+                GSList *group;
+
+                w = columns_new(1);
+                if (ctrl->generic.label) {
+                    GtkWidget *label = gtk_label_new(ctrl->generic.label);
+                    columns_add(COLUMNS(w), label, 0, 1);
+                   columns_force_left_align(COLUMNS(w), label);
+                    gtk_widget_show(label);
+                }
+                percentages = g_new(gint, ctrl->radio.ncolumns);
+                for (i = 0; i < ctrl->radio.ncolumns; i++) {
+                    percentages[i] =
+                        ((100 * (i+1) / ctrl->radio.ncolumns) -
+                         100 * i / ctrl->radio.ncolumns);
+                }
+                columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
+                                 percentages);
+                g_free(percentages);
+                group = NULL;
+                for (i = 0; i < ctrl->radio.nbuttons; i++) {
+                    GtkWidget *b;
+                    gint colstart;
+
+                    b = (gtk_radio_button_new_with_label
+                         (group, ctrl->radio.buttons[i]));
+                    group = gtk_radio_button_group(GTK_RADIO_BUTTON(b));
+                    colstart = i % ctrl->radio.ncolumns;
+                    columns_add(COLUMNS(w), b, colstart,
+                                (i == ctrl->radio.nbuttons-1 ?
+                                 ctrl->radio.ncolumns - colstart : 1));
+                    gtk_widget_show(b);
+                }
+            }
+            break;
+          case CTRL_EDITBOX:
+            if (ctrl->editbox.has_list) {
+                w = gtk_combo_new();
+            } else {
+                w = gtk_entry_new();
+                if (ctrl->editbox.password)
+                    gtk_entry_set_visibility(GTK_ENTRY(w), FALSE);
+            }
+            /*
+             * Edit boxes, for some strange reason, have a minimum
+             * width of 150 in GTK 1.2. We don't want this - we'd
+             * rather the edit boxes acquired their natural width
+             * from the column layout of the rest of the box.
+             */
+            {
+                GtkRequisition req;
+                gtk_widget_size_request(w, &req);
+                gtk_widget_set_usize(w, 10, req.height);
+            }
+            if (ctrl->generic.label) {
+                GtkWidget *label, *container;
+
+                label = gtk_label_new(ctrl->generic.label);
+
+               container = columns_new(4);
+                if (ctrl->editbox.percentwidth == 100) {
+                    columns_add(COLUMNS(container), label, 0, 1);
+                   columns_force_left_align(COLUMNS(container), label);
+                    columns_add(COLUMNS(container), w, 0, 1);
+                } else {
+                    gint percentages[2];
+                    percentages[1] = ctrl->editbox.percentwidth;
+                    percentages[0] = 100 - ctrl->editbox.percentwidth;
+                    columns_set_cols(COLUMNS(container), 2, percentages);
+                    columns_add(COLUMNS(container), label, 0, 1);
+                   columns_force_left_align(COLUMNS(container), label);
+                    columns_add(COLUMNS(container), w, 1, 1);
+                }
+                gtk_widget_show(label);
+                gtk_widget_show(w);
+
+                w = container;
+            }
+            break;
+          case CTRL_TEXT:
+            w = gtk_label_new(ctrl->generic.label);
+            gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
+           gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_FILL);
+            break;
+        }
+        if (w) {
+            columns_add(cols, w,
+                        COLUMN_START(ctrl->generic.column),
+                        COLUMN_SPAN(ctrl->generic.column));
+            gtk_widget_show(w);
+        }
+    }
+
+    return ret;
+}
+
+struct selparam {
+    Panels *panels;
+    GtkWidget *panel, *treeitem;
+};
+
+static void treeitem_sel(GtkItem *item, gpointer data)
+{
+    struct selparam *sp = (struct selparam *)data;
+
+    panels_switch_to(sp->panels, sp->panel);
+}
+
+void destroy(GtkWidget *widget, gpointer data)
+{
+    gtk_main_quit();
+}
+
+void do_config_box(void)
+{
+    GtkWidget *window, *hbox, *vbox, *cols, *label,
+       *tree, *treescroll, *panels, *panelvbox;
+    int index, level;
+    struct controlbox *ctrlbox;
+    char *path;
+    GtkTreeItem *treeitemlevels[8];
+    GtkTree *treelevels[8];
+    Config cfg;
+
+    struct selparam *selparams = NULL;
+    int nselparams = 0, selparamsize = 0;
+
+    do_defaults(NULL, &cfg);
+
+    ctrlbox = ctrl_new_box();
+    setup_config_box(ctrlbox, NULL, FALSE, 0);
+
+    window = gtk_dialog_new();
+    gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(window)->vbox), 4);
+    hbox = gtk_hbox_new(FALSE, 4);
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0);
+    gtk_widget_show(hbox);
+    vbox = gtk_vbox_new(FALSE, 4);
+    gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+    gtk_widget_show(vbox);
+    cols = columns_new(4);
+    gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0);
+    gtk_widget_show(cols);
+    label = gtk_label_new("Category:");
+    columns_add(COLUMNS(cols), label, 0, 1);
+    columns_force_left_align(COLUMNS(cols), label);
+    gtk_widget_show(label);
+    treescroll = gtk_scrolled_window_new(NULL, NULL);
+    tree = gtk_tree_new();
+    gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
+    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
+                                         tree);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
+                                  GTK_POLICY_NEVER,
+                                  GTK_POLICY_AUTOMATIC);
+    gtk_widget_show(tree);
+    gtk_widget_show(treescroll);
+    gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0);
+    panels = panels_new();
+    gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0);
+    gtk_widget_show(panels);
+
+    panelvbox = NULL;
+    path = NULL;
+    level = 0;
+    for (index = 0; index < ctrlbox->nctrlsets; index++) {
+       struct controlset *s = ctrlbox->ctrlsets[index];
+       GtkWidget *w = layout_ctrls(s);
+
+       if (!*s->pathname) {
+           gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area),
+                              w, TRUE, TRUE, 0);
+       } else {
+           int j = path ? ctrl_path_compare(s->pathname, path) : 0;
+           if (j != INT_MAX) {        /* add to treeview, start new panel */
+               char *c;
+               GtkWidget *treeitem;
+               int first;
+
+               /*
+                * We expect never to find an implicit path
+                * component. For example, we expect never to see
+                * A/B/C followed by A/D/E, because that would
+                * _implicitly_ create A/D. All our path prefixes
+                * are expected to contain actual controls and be
+                * selectable in the treeview; so we would expect
+                * to see A/D _explicitly_ before encountering
+                * A/D/E.
+                */
+               assert(j == ctrl_path_elements(s->pathname) - 1);
+
+               c = strrchr(s->pathname, '/');
+               if (!c)
+                   c = s->pathname;
+               else
+                   c++;
+
+               treeitem = gtk_tree_item_new_with_label(c);
+               assert(j-1 < level);
+               if (j > 0) {
+                   if (!treelevels[j-1]) {
+                       treelevels[j-1] = GTK_TREE(gtk_tree_new());
+                       gtk_tree_item_set_subtree
+                           (treeitemlevels[j-1],
+                            GTK_WIDGET(treelevels[j-1]));
+                       gtk_tree_item_expand(treeitemlevels[j-1]);
+                   }
+                   gtk_tree_append(treelevels[j-1], treeitem);
+               } else {
+                   gtk_tree_append(GTK_TREE(tree), treeitem);
+               }
+               treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
+               treelevels[j] = NULL;
+               level = j+1;
+
+               gtk_widget_show(treeitem);
+
+               path = s->pathname;
+
+               first = (panelvbox == NULL);
+
+               panelvbox = gtk_vbox_new(FALSE, 4);
+               gtk_container_add(GTK_CONTAINER(panels), panelvbox);
+               if (first) {
+                   panels_switch_to(PANELS(panels), panelvbox);
+                   gtk_tree_select_child(GTK_TREE(tree), treeitem);
+               }
+
+               if (nselparams >= selparamsize) {
+                   selparamsize += 16;
+                   selparams = srealloc(selparams,
+                                        selparamsize * sizeof(*selparams));
+               }
+               selparams[nselparams].panels = PANELS(panels);
+               selparams[nselparams].panel = panelvbox;
+               selparams[nselparams].treeitem = treeitem;
+               nselparams++;
+
+           }
+
+           gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0);
+       }
+    }
+
+    for (index = 0; index < nselparams; index++) {
+       gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select",
+                          GTK_SIGNAL_FUNC(treeitem_sel),
+                          &selparams[index]);
+    }
+
+    gtk_widget_show(window);
+
+    gtk_signal_connect(GTK_OBJECT(window), "destroy",
+                      GTK_SIGNAL_FUNC(destroy), NULL);
+
+    gtk_main();
+
+    sfree(selparams);
+}
+
+/* ======================================================================
+ * Below here is a stub main program which allows the dialog box
+ * code to be compiled and tested with a minimal amount of the rest
+ * of PuTTY.
+ */
+
+#ifdef TESTMODE
+
+/* Compile command for testing:
+
+   gcc -o gtkdlg gtk{dlg,cols,panel}.c ../{config,dialog,settings}.c \
+                 ../{misc,tree234,be_none}.c ux{store,misc,print}.c \
+                 -I. -I.. -I../charset -DTESTMODE `gtk-config --cflags --libs`
+ */
+
+void modalfatalbox(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+char *cp_name(int codepage)
+{
+    return (codepage == 123 ? "testing123" :
+            codepage == 234 ? "testing234" :
+            codepage == 345 ? "testing345" :
+            "unknown");
+}
+
+char *cp_enumerate(int index)
+{
+    return (index == 0 ? "testing123" :
+            index == 1 ? "testing234" :
+            NULL);
+}
+
+int decode_codepage(char *cp_name)
+{
+    return (!strcmp(cp_name, "testing123") ? 123 :
+            !strcmp(cp_name, "testing234") ? 234 :
+            !strcmp(cp_name, "testing345") ? 345 :
+            -2);
+}
+
+struct printer_enum_tag { int dummy; } printer_test;
+
+printer_enum *printer_start_enum(int *nprinters_ptr) {
+    *nprinters_ptr = 2;
+    return &printer_test;
+}
+char *printer_get_name(printer_enum *pe, int i) {
+    return (i==0 ? "lpr" : i==1 ? "lpr -Pfoobar" : NULL);
+}
+void printer_finish_enum(printer_enum *pe) { }
+
+char *platform_default_s(const char *name)
+{
+    return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+    return def;
+}
+
+FontSpec platform_default_fontspec(const char *name)
+{
+    FontSpec ret;
+    *ret.name = '\0';
+    return ret;
+}
+
+Filename platform_default_filename(const char *name)
+{
+    Filename ret;
+    *ret.path = '\0';
+    return ret;
+}
+
+char *x_get_default(const char *key)
+{
+    return NULL;
+}
+
+int main(int argc, char **argv)
+{
+    gtk_init(&argc, &argv);
+    do_config_box();
+    return 0;
+}
+
+#endif
diff --git a/unix/gtkpanel.c b/unix/gtkpanel.c
new file mode 100644 (file)
index 0000000..5167ead
--- /dev/null
@@ -0,0 +1,389 @@
+/*
+ * gtkpanel.c - implementation of the `Panels' GTK layout container.
+ */
+
+#include "gtkpanel.h"
+
+static void panels_init(Panels *panels);
+static void panels_class_init(PanelsClass *klass);
+static void panels_map(GtkWidget *widget);
+static void panels_unmap(GtkWidget *widget);
+static void panels_draw(GtkWidget *widget, GdkRectangle *area);
+static gint panels_expose(GtkWidget *widget, GdkEventExpose *event);
+static void panels_base_add(GtkContainer *container, GtkWidget *widget);
+static void panels_remove(GtkContainer *container, GtkWidget *widget);
+static void panels_forall(GtkContainer *container, gboolean include_internals,
+                           GtkCallback callback, gpointer callback_data);
+static GtkType panels_child_type(GtkContainer *container);
+static void panels_size_request(GtkWidget *widget, GtkRequisition *req);
+static void panels_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
+
+static GtkContainerClass *parent_class = NULL;
+
+GtkType panels_get_type(void)
+{
+    static GtkType panels_type = 0;
+
+    if (!panels_type) {
+        static const GtkTypeInfo panels_info = {
+            "Panels",
+            sizeof(Panels),
+            sizeof(PanelsClass),
+            (GtkClassInitFunc) panels_class_init,
+            (GtkObjectInitFunc) panels_init,
+            /* reserved_1 */ NULL,
+            /* reserved_2 */ NULL,
+            (GtkClassInitFunc) NULL,
+        };
+
+        panels_type = gtk_type_unique(GTK_TYPE_CONTAINER, &panels_info);
+    }
+
+    return panels_type;
+}
+
+static void panels_class_init(PanelsClass *klass)
+{
+    GtkObjectClass *object_class;
+    GtkWidgetClass *widget_class;
+    GtkContainerClass *container_class;
+
+    object_class = (GtkObjectClass *)klass;
+    widget_class = (GtkWidgetClass *)klass;
+    container_class = (GtkContainerClass *)klass;
+
+    parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
+
+    /*
+     * FIXME: do we have to do all this faffing with set_arg,
+     * get_arg and child_arg_type? Ick.
+     */
+
+    widget_class->map = panels_map;
+    widget_class->unmap = panels_unmap;
+    widget_class->draw = panels_draw;
+    widget_class->expose_event = panels_expose;
+    widget_class->size_request = panels_size_request;
+    widget_class->size_allocate = panels_size_allocate;
+
+    container_class->add = panels_base_add;
+    container_class->remove = panels_remove;
+    container_class->forall = panels_forall;
+    container_class->child_type = panels_child_type;
+}
+
+static void panels_init(Panels *panels)
+{
+    GTK_WIDGET_SET_FLAGS(panels, GTK_NO_WINDOW);
+
+    panels->children = NULL;
+}
+
+/*
+ * 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 panels_map(GtkWidget *widget)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_PANELS(widget));
+
+    panels = PANELS(widget);
+    GTK_WIDGET_SET_FLAGS(panels, GTK_MAPPED);
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child &&
+           GTK_WIDGET_VISIBLE(child) &&
+            !GTK_WIDGET_MAPPED(child))
+            gtk_widget_map(child);
+    }
+}
+static void panels_unmap(GtkWidget *widget)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_PANELS(widget));
+
+    panels = PANELS(widget);
+    GTK_WIDGET_UNSET_FLAGS(panels, GTK_MAPPED);
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child &&
+           GTK_WIDGET_VISIBLE(child) &&
+            GTK_WIDGET_MAPPED(child))
+            gtk_widget_unmap(child);
+    }
+}
+static void panels_draw(GtkWidget *widget, GdkRectangle *area)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+    GdkRectangle child_area;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_PANELS(widget));
+
+    if (GTK_WIDGET_DRAWABLE(widget)) {
+        panels = PANELS(widget);
+
+        for (children = panels->children;
+             children && (child = children->data);
+             children = children->next) {
+            if (child &&
+               GTK_WIDGET_DRAWABLE(child) &&
+                gtk_widget_intersect(child, area, &child_area))
+                gtk_widget_draw(child, &child_area);
+        }
+    }
+}
+static gint panels_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+    GdkEventExpose child_event;
+
+    g_return_val_if_fail(widget != NULL, FALSE);
+    g_return_val_if_fail(IS_PANELS(widget), FALSE);
+    g_return_val_if_fail(event != NULL, FALSE);
+
+    if (GTK_WIDGET_DRAWABLE(widget)) {
+        panels = PANELS(widget);
+        child_event = *event;
+
+        for (children = panels->children;
+             children && (child = children->data);
+             children = children->next) {
+            if (child &&
+               GTK_WIDGET_DRAWABLE(child) &&
+                GTK_WIDGET_NO_WINDOW(child) &&
+                gtk_widget_intersect(child, &event->area,
+                                     &child_event.area))
+                gtk_widget_event(child, (GdkEvent *)&child_event);
+        }
+    }
+    return FALSE;
+}
+
+static void panels_base_add(GtkContainer *container, GtkWidget *widget)
+{
+    Panels *panels;
+
+    g_return_if_fail(container != NULL);
+    g_return_if_fail(IS_PANELS(container));
+    g_return_if_fail(widget != NULL);
+
+    panels = PANELS(container);
+
+    panels_add(panels, widget);
+}
+
+static void panels_remove(GtkContainer *container, GtkWidget *widget)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+    gboolean was_visible;
+
+    g_return_if_fail(container != NULL);
+    g_return_if_fail(IS_PANELS(container));
+    g_return_if_fail(widget != NULL);
+
+    panels = PANELS(container);
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child != widget)
+            continue;
+
+        was_visible = GTK_WIDGET_VISIBLE(widget);
+        gtk_widget_unparent(widget);
+        panels->children = g_list_remove_link(panels->children, children);
+        g_list_free(children);
+        if (was_visible)
+            gtk_widget_queue_resize(GTK_WIDGET(container));
+        break;
+    }
+}
+
+static void panels_forall(GtkContainer *container, gboolean include_internals,
+                           GtkCallback callback, gpointer callback_data)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+
+    g_return_if_fail(container != NULL);
+    g_return_if_fail(IS_PANELS(container));
+    g_return_if_fail(callback != NULL);
+
+    panels = PANELS(container);
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next)
+       if (child)
+           callback(child, callback_data);
+}
+
+static GtkType panels_child_type(GtkContainer *container)
+{
+    return GTK_TYPE_WIDGET;
+}
+
+GtkWidget *panels_new(void)
+{
+    Panels *panels;
+
+    panels = gtk_type_new(panels_get_type());
+
+    return GTK_WIDGET(panels);
+}
+
+void panels_add(Panels *panels, GtkWidget *child)
+{
+    g_return_if_fail(panels != NULL);
+    g_return_if_fail(IS_PANELS(panels));
+    g_return_if_fail(child != NULL);
+    g_return_if_fail(child->parent == NULL);
+
+    panels->children = g_list_append(panels->children, child);
+
+    gtk_widget_set_parent(child, GTK_WIDGET(panels));
+
+    if (GTK_WIDGET_REALIZED(panels))
+        gtk_widget_realize(child);
+
+    if (GTK_WIDGET_VISIBLE(panels)) {
+        if (GTK_WIDGET_MAPPED(panels))
+            gtk_widget_map(child);
+        gtk_widget_queue_resize(child);
+    }
+}
+
+void panels_switch_to(Panels *panels, GtkWidget *target)
+{
+    GtkWidget *child;
+    GList *children;
+    gboolean changed;
+
+    g_return_if_fail(panels != NULL);
+    g_return_if_fail(IS_PANELS(panels));
+    g_return_if_fail(target != NULL);
+    g_return_if_fail(target->parent == GTK_WIDGET(panels));
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next) {
+
+       if (!child)
+            continue;
+
+        if (child == target) {
+            if (!GTK_WIDGET_VISIBLE(child)) {
+                gtk_widget_show(child);
+                changed = TRUE;
+            }
+        } else {
+            if (GTK_WIDGET_VISIBLE(child)) {
+                gtk_widget_hide(child);
+                changed = TRUE;
+            }
+        }
+    }
+    if (changed)
+        gtk_widget_queue_resize(child);
+}
+
+/*
+ * Now here comes the interesting bit. The actual layout part is
+ * done in the following two functions:
+ * 
+ * panels_size_request() examines the list of widgets held in the
+ * Panels, and returns a requisition stating the absolute minimum
+ * size it can bear to be.
+ * 
+ * panels_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.
+ */
+
+static void panels_size_request(GtkWidget *widget, GtkRequisition *req)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_PANELS(widget));
+    g_return_if_fail(req != NULL);
+
+    panels = PANELS(widget);
+
+    req->width = 0;
+    req->height = 0;
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next) {
+        GtkRequisition creq;
+
+        gtk_widget_size_request(child, &creq);
+        if (req->width < creq.width)
+            req->width = creq.width;
+        if (req->height < creq.height)
+            req->height = creq.height;
+    }
+
+    req->width += 2*GTK_CONTAINER(panels)->border_width;
+    req->height += 2*GTK_CONTAINER(panels)->border_width;
+}
+
+static void panels_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+    Panels *panels;
+    GtkWidget *child;
+    GList *children;
+    gint border;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_PANELS(widget));
+    g_return_if_fail(alloc != NULL);
+
+    panels = PANELS(widget);
+    widget->allocation = *alloc;
+    border = GTK_CONTAINER(panels)->border_width;
+
+    for (children = panels->children;
+         children && (child = children->data);
+         children = children->next) {
+        GtkAllocation call;
+
+        /* Only take visible widgets into account. */
+        if (!GTK_WIDGET_VISIBLE(child))
+            continue;
+
+        call.x = alloc->x + border;
+        call.width = alloc->width - 2*border;
+        call.y = alloc->y + border;
+        call.height = alloc->height - 2*border;
+
+        gtk_widget_size_allocate(child, &call);
+    }
+}
diff --git a/unix/gtkpanel.h b/unix/gtkpanel.h
new file mode 100644 (file)
index 0000000..2b7b2c5
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * gtkpanel.h - header file for a panel-based widget container,
+ * which holds a number of widgets of which at most one is ever
+ * visible at a time, and sizes itself to the maximum of its
+ * children's potential size requests.
+ */
+
+#ifndef PANELS_H
+#define PANELS_H
+
+#include <gdk/gdk.h>
+#include <gtk/gtkcontainer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define TYPE_PANELS (panels_get_type())
+#define PANELS(obj) (GTK_CHECK_CAST((obj), TYPE_PANELS, Panels))
+#define PANELS_CLASS(klass) \
+                (GTK_CHECK_CLASS_CAST((klass), TYPE_PANELS, PanelsClass))
+#define IS_PANELS(obj) (GTK_CHECK_TYPE((obj), TYPE_PANELS))
+#define IS_PANELS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), TYPE_PANELS))
+
+typedef struct Panels_tag Panels;
+typedef struct PanelsClass_tag PanelsClass;
+typedef struct PanelsChild_tag PanelsChild;
+
+struct Panels_tag {
+    GtkContainer container;
+    /* private after here */
+    GList *children;                  /* this just holds GtkWidgets */
+};
+
+struct PanelsClass_tag {
+    GtkContainerClass parent_class;
+};
+
+GtkType panels_get_type(void);
+GtkWidget *panels_new(void);
+void panels_add(Panels *panels, GtkWidget *child);
+void panels_switch_to(Panels *panels, GtkWidget *child);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PANELS_H */
index c7f2470..01c7a9b 100644 (file)
@@ -24,6 +24,12 @@ extern Backend pty_backend;
 #define MULTICLICK_ONLY_EVENT 0
 
 /*
+ * Under GTK, there is no context help available.
+ */
+#define HELPCTX(x) P(NULL)
+#define FILTER_KEY_FILES NULL          /* FIXME */
+
+/*
  * Under X, selection data must not be NUL-terminated.
  */
 #define SELECTION_NUL_TERMINATED 0