]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkcols.c
New Columns method, columns_force_same_height().
[PuTTY.git] / unix / gtkcols.c
1 /*
2  * gtkcols.c - implementation of the `Columns' GTK layout container.
3  */
4
5 #include <gtk/gtk.h>
6 #include "gtkcompat.h"
7 #include "gtkcols.h"
8
9 static void columns_init(Columns *cols);
10 static void columns_class_init(ColumnsClass *klass);
11 static void columns_map(GtkWidget *widget);
12 static void columns_unmap(GtkWidget *widget);
13 #if !GTK_CHECK_VERSION(2,0,0)
14 static void columns_draw(GtkWidget *widget, GdkRectangle *area);
15 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
16 #endif
17 static void columns_base_add(GtkContainer *container, GtkWidget *widget);
18 static void columns_remove(GtkContainer *container, GtkWidget *widget);
19 static void columns_forall(GtkContainer *container, gboolean include_internals,
20                            GtkCallback callback, gpointer callback_data);
21 #if !GTK_CHECK_VERSION(2,0,0)
22 static gint columns_focus(GtkContainer *container, GtkDirectionType dir);
23 #endif
24 static GType columns_child_type(GtkContainer *container);
25 #if GTK_CHECK_VERSION(3,0,0)
26 static void columns_get_preferred_width(GtkWidget *widget,
27                                         gint *min, gint *nat);
28 static void columns_get_preferred_height(GtkWidget *widget,
29                                          gint *min, gint *nat);
30 static void columns_get_preferred_width_for_height(GtkWidget *widget,
31                                                    gint height,
32                                                    gint *min, gint *nat);
33 static void columns_get_preferred_height_for_width(GtkWidget *widget,
34                                                    gint width,
35                                                    gint *min, gint *nat);
36 #else
37 static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
38 #endif
39 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
40
41 static GtkContainerClass *parent_class = NULL;
42
43 #if !GTK_CHECK_VERSION(2,0,0)
44 GType columns_get_type(void)
45 {
46     static GType columns_type = 0;
47
48     if (!columns_type) {
49         static const GtkTypeInfo columns_info = {
50             "Columns",
51             sizeof(Columns),
52             sizeof(ColumnsClass),
53             (GtkClassInitFunc) columns_class_init,
54             (GtkObjectInitFunc) columns_init,
55             /* reserved_1 */ NULL,
56             /* reserved_2 */ NULL,
57             (GtkClassInitFunc) NULL,
58         };
59
60         columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
61     }
62
63     return columns_type;
64 }
65 #else
66 GType columns_get_type(void)
67 {
68     static GType columns_type = 0;
69
70     if (!columns_type) {
71         static const GTypeInfo columns_info = {
72             sizeof(ColumnsClass),
73             NULL,
74             NULL,
75             (GClassInitFunc) columns_class_init,
76             NULL,
77             NULL,
78             sizeof(Columns),
79             0,
80             (GInstanceInitFunc)columns_init,
81         };
82
83         columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
84                                               &columns_info, 0);
85     }
86
87     return columns_type;
88 }
89 #endif
90
91 #if !GTK_CHECK_VERSION(2,0,0)
92 static gint (*columns_inherited_focus)(GtkContainer *container,
93                                        GtkDirectionType direction);
94 #endif
95
96 static void columns_class_init(ColumnsClass *klass)
97 {
98 #if !GTK_CHECK_VERSION(2,0,0)
99     /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */
100     GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
101     GtkContainerClass *container_class = (GtkContainerClass *)klass;
102 #else
103     /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */
104     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
105     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
106 #endif
107
108 #if !GTK_CHECK_VERSION(2,0,0)
109     parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
110 #else
111     parent_class = g_type_class_peek_parent(klass);
112 #endif
113
114     widget_class->map = columns_map;
115     widget_class->unmap = columns_unmap;
116 #if !GTK_CHECK_VERSION(2,0,0)
117     widget_class->draw = columns_draw;
118     widget_class->expose_event = columns_expose;
119 #endif
120 #if GTK_CHECK_VERSION(3,0,0)
121     widget_class->get_preferred_width = columns_get_preferred_width;
122     widget_class->get_preferred_height = columns_get_preferred_height;
123     widget_class->get_preferred_width_for_height =
124         columns_get_preferred_width_for_height;
125     widget_class->get_preferred_height_for_width =
126         columns_get_preferred_height_for_width;
127 #else
128     widget_class->size_request = columns_size_request;
129 #endif
130     widget_class->size_allocate = columns_size_allocate;
131
132     container_class->add = columns_base_add;
133     container_class->remove = columns_remove;
134     container_class->forall = columns_forall;
135     container_class->child_type = columns_child_type;
136 #if !GTK_CHECK_VERSION(2,0,0)
137     /* Save the previous value of this method. */
138     if (!columns_inherited_focus)
139         columns_inherited_focus = container_class->focus;
140     container_class->focus = columns_focus;
141 #endif
142 }
143
144 static void columns_init(Columns *cols)
145 {
146     gtk_widget_set_has_window(GTK_WIDGET(cols), FALSE);
147
148     cols->children = NULL;
149     cols->spacing = 0;
150 }
151
152 /*
153  * These appear to be thoroughly tedious functions; the only reason
154  * we have to reimplement them at all is because we defined our own
155  * format for our GList of children...
156  */
157 static void columns_map(GtkWidget *widget)
158 {
159     Columns *cols;
160     ColumnsChild *child;
161     GList *children;
162
163     g_return_if_fail(widget != NULL);
164     g_return_if_fail(IS_COLUMNS(widget));
165
166     cols = COLUMNS(widget);
167     gtk_widget_set_mapped(GTK_WIDGET(cols), TRUE);
168
169     for (children = cols->children;
170          children && (child = children->data);
171          children = children->next) {
172         if (child->widget &&
173             gtk_widget_get_visible(child->widget) &&
174             !gtk_widget_get_mapped(child->widget))
175             gtk_widget_map(child->widget);
176     }
177 }
178 static void columns_unmap(GtkWidget *widget)
179 {
180     Columns *cols;
181     ColumnsChild *child;
182     GList *children;
183
184     g_return_if_fail(widget != NULL);
185     g_return_if_fail(IS_COLUMNS(widget));
186
187     cols = COLUMNS(widget);
188     gtk_widget_set_mapped(GTK_WIDGET(cols), FALSE);
189
190     for (children = cols->children;
191          children && (child = children->data);
192          children = children->next) {
193         if (child->widget &&
194             gtk_widget_get_visible(child->widget) &&
195             gtk_widget_get_mapped(child->widget))
196             gtk_widget_unmap(child->widget);
197     }
198 }
199 #if !GTK_CHECK_VERSION(2,0,0)
200 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
201 {
202     Columns *cols;
203     ColumnsChild *child;
204     GList *children;
205     GdkRectangle child_area;
206
207     g_return_if_fail(widget != NULL);
208     g_return_if_fail(IS_COLUMNS(widget));
209
210     if (GTK_WIDGET_DRAWABLE(widget)) {
211         cols = COLUMNS(widget);
212
213         for (children = cols->children;
214              children && (child = children->data);
215              children = children->next) {
216             if (child->widget &&
217                 GTK_WIDGET_DRAWABLE(child->widget) &&
218                 gtk_widget_intersect(child->widget, area, &child_area))
219                 gtk_widget_draw(child->widget, &child_area);
220         }
221     }
222 }
223 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
224 {
225     Columns *cols;
226     ColumnsChild *child;
227     GList *children;
228     GdkEventExpose child_event;
229
230     g_return_val_if_fail(widget != NULL, FALSE);
231     g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
232     g_return_val_if_fail(event != NULL, FALSE);
233
234     if (GTK_WIDGET_DRAWABLE(widget)) {
235         cols = COLUMNS(widget);
236         child_event = *event;
237
238         for (children = cols->children;
239              children && (child = children->data);
240              children = children->next) {
241             if (child->widget &&
242                 GTK_WIDGET_DRAWABLE(child->widget) &&
243                 GTK_WIDGET_NO_WINDOW(child->widget) &&
244                 gtk_widget_intersect(child->widget, &event->area,
245                                      &child_event.area))
246                 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
247         }
248     }
249     return FALSE;
250 }
251 #endif
252
253 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
254 {
255     Columns *cols;
256
257     g_return_if_fail(container != NULL);
258     g_return_if_fail(IS_COLUMNS(container));
259     g_return_if_fail(widget != NULL);
260
261     cols = COLUMNS(container);
262
263     /*
264      * Default is to add a new widget spanning all columns.
265      */
266     columns_add(cols, widget, 0, 0);   /* 0 means ncols */
267 }
268
269 static void columns_remove(GtkContainer *container, GtkWidget *widget)
270 {
271     Columns *cols;
272     ColumnsChild *child;
273     GtkWidget *childw;
274     GList *children;
275     gboolean was_visible;
276
277     g_return_if_fail(container != NULL);
278     g_return_if_fail(IS_COLUMNS(container));
279     g_return_if_fail(widget != NULL);
280
281     cols = COLUMNS(container);
282
283     for (children = cols->children;
284          children && (child = children->data);
285          children = children->next) {
286         if (child->widget != widget)
287             continue;
288
289         was_visible = gtk_widget_get_visible(widget);
290         gtk_widget_unparent(widget);
291         cols->children = g_list_remove_link(cols->children, children);
292         g_list_free(children);
293
294         if (child->same_height_as) {
295             g_return_if_fail(child->same_height_as->same_height_as == child);
296             child->same_height_as->same_height_as = NULL;
297             if (gtk_widget_get_visible(child->same_height_as->widget))
298                 gtk_widget_queue_resize(GTK_WIDGET(container));
299         }
300
301         g_free(child);
302         if (was_visible)
303             gtk_widget_queue_resize(GTK_WIDGET(container));
304         break;
305     }
306
307     for (children = cols->taborder;
308          children && (childw = children->data);
309          children = children->next) {
310         if (childw != widget)
311             continue;
312
313         cols->taborder = g_list_remove_link(cols->taborder, children);
314         g_list_free(children);
315 #if GTK_CHECK_VERSION(2,0,0)
316         gtk_container_set_focus_chain(container, cols->taborder);
317 #endif
318         break;
319     }
320 }
321
322 static void columns_forall(GtkContainer *container, gboolean include_internals,
323                            GtkCallback callback, gpointer callback_data)
324 {
325     Columns *cols;
326     ColumnsChild *child;
327     GList *children, *next;
328
329     g_return_if_fail(container != NULL);
330     g_return_if_fail(IS_COLUMNS(container));
331     g_return_if_fail(callback != NULL);
332
333     cols = COLUMNS(container);
334
335     for (children = cols->children;
336          children && (child = children->data);
337          children = next) {
338         /*
339          * We can't wait until after the callback to assign
340          * `children = children->next', because the callback might
341          * be gtk_widget_destroy, which would remove the link
342          * `children' from the list! So instead we must get our
343          * hands on the value of the `next' pointer _before_ the
344          * callback.
345          */
346         next = children->next;
347         if (child->widget)
348             callback(child->widget, callback_data);
349     }
350 }
351
352 static GType columns_child_type(GtkContainer *container)
353 {
354     return GTK_TYPE_WIDGET;
355 }
356
357 GtkWidget *columns_new(gint spacing)
358 {
359     Columns *cols;
360
361 #if !GTK_CHECK_VERSION(2,0,0)
362     cols = gtk_type_new(columns_get_type());
363 #else
364     cols = g_object_new(TYPE_COLUMNS, NULL);
365 #endif
366
367     cols->spacing = spacing;
368
369     return GTK_WIDGET(cols);
370 }
371
372 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
373 {
374     ColumnsChild *childdata;
375     gint i;
376
377     g_return_if_fail(cols != NULL);
378     g_return_if_fail(IS_COLUMNS(cols));
379     g_return_if_fail(ncols > 0);
380     g_return_if_fail(percentages != NULL);
381
382     childdata = g_new(ColumnsChild, 1);
383     childdata->widget = NULL;
384     childdata->ncols = ncols;
385     childdata->percentages = g_new(gint, ncols);
386     childdata->force_left = FALSE;
387     for (i = 0; i < ncols; i++)
388         childdata->percentages[i] = percentages[i];
389
390     cols->children = g_list_append(cols->children, childdata);
391 }
392
393 void columns_add(Columns *cols, GtkWidget *child,
394                  gint colstart, gint colspan)
395 {
396     ColumnsChild *childdata;
397
398     g_return_if_fail(cols != NULL);
399     g_return_if_fail(IS_COLUMNS(cols));
400     g_return_if_fail(child != NULL);
401     g_return_if_fail(gtk_widget_get_parent(child) == NULL);
402
403     childdata = g_new(ColumnsChild, 1);
404     childdata->widget = child;
405     childdata->colstart = colstart;
406     childdata->colspan = colspan;
407     childdata->force_left = FALSE;
408     childdata->same_height_as = NULL;
409
410     cols->children = g_list_append(cols->children, childdata);
411     cols->taborder = g_list_append(cols->taborder, child);
412
413     gtk_widget_set_parent(child, GTK_WIDGET(cols));
414
415 #if GTK_CHECK_VERSION(2,0,0)
416     gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
417 #endif
418
419     if (gtk_widget_get_realized(GTK_WIDGET(cols)))
420         gtk_widget_realize(child);
421
422     if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
423         gtk_widget_get_visible(child)) {
424         if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
425             gtk_widget_map(child);
426         gtk_widget_queue_resize(child);
427     }
428 }
429
430 static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
431 {
432     GList *children;
433     ColumnsChild *child;
434
435     for (children = cols->children;
436          children && (child = children->data);
437          children = children->next) {
438
439         if (child->widget == widget)
440             return child;
441     }
442
443     return NULL;
444 }
445
446 void columns_force_left_align(Columns *cols, GtkWidget *widget)
447 {
448     ColumnsChild *child;
449
450     g_return_if_fail(cols != NULL);
451     g_return_if_fail(IS_COLUMNS(cols));
452     g_return_if_fail(widget != NULL);
453
454     child = columns_find_child(cols, widget);
455     g_return_if_fail(child != NULL);
456
457     child->force_left = TRUE;
458     if (gtk_widget_get_visible(widget))
459         gtk_widget_queue_resize(GTK_WIDGET(cols));
460 }
461
462 void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
463 {
464     ColumnsChild *child1, *child2;
465
466     g_return_if_fail(cols != NULL);
467     g_return_if_fail(IS_COLUMNS(cols));
468     g_return_if_fail(cw1 != NULL);
469     g_return_if_fail(cw2 != NULL);
470
471     child1 = columns_find_child(cols, cw1);
472     g_return_if_fail(child1 != NULL);
473     child2 = columns_find_child(cols, cw2);
474     g_return_if_fail(child2 != NULL);
475
476     child1->same_height_as = child2;
477     child2->same_height_as = child1;
478     if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
479         gtk_widget_queue_resize(GTK_WIDGET(cols));
480 }
481
482 void columns_taborder_last(Columns *cols, GtkWidget *widget)
483 {
484     GtkWidget *childw;
485     GList *children;
486
487     g_return_if_fail(cols != NULL);
488     g_return_if_fail(IS_COLUMNS(cols));
489     g_return_if_fail(widget != NULL);
490
491     for (children = cols->taborder;
492          children && (childw = children->data);
493          children = children->next) {
494         if (childw != widget)
495             continue;
496
497         cols->taborder = g_list_remove_link(cols->taborder, children);
498         g_list_free(children);
499         cols->taborder = g_list_append(cols->taborder, widget);
500 #if GTK_CHECK_VERSION(2,0,0)
501         gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
502 #endif
503         break;
504     }
505 }
506
507 #if !GTK_CHECK_VERSION(2,0,0)
508 /*
509  * Override GtkContainer's focus movement so the user can
510  * explicitly specify the tab order.
511  */
512 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
513 {
514     Columns *cols;
515     GList *pos;
516     GtkWidget *focuschild;
517
518     g_return_val_if_fail(container != NULL, FALSE);
519     g_return_val_if_fail(IS_COLUMNS(container), FALSE);
520
521     cols = COLUMNS(container);
522
523     if (!GTK_WIDGET_DRAWABLE(cols) ||
524         !GTK_WIDGET_IS_SENSITIVE(cols))
525         return FALSE;
526
527     if (!GTK_WIDGET_CAN_FOCUS(container) &&
528         (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
529
530         focuschild = container->focus_child;
531         gtk_container_set_focus_child(container, NULL);
532
533         if (dir == GTK_DIR_TAB_FORWARD)
534             pos = cols->taborder;
535         else
536             pos = g_list_last(cols->taborder);
537
538         while (pos) {
539             GtkWidget *child = pos->data;
540
541             if (focuschild) {
542                 if (focuschild == child) {
543                     focuschild = NULL; /* now we can start looking in here */
544                     if (GTK_WIDGET_DRAWABLE(child) &&
545                         GTK_IS_CONTAINER(child) &&
546                         !GTK_WIDGET_HAS_FOCUS(child)) {
547                         if (gtk_container_focus(GTK_CONTAINER(child), dir))
548                             return TRUE;
549                     }
550                 }
551             } else if (GTK_WIDGET_DRAWABLE(child)) {
552                 if (GTK_IS_CONTAINER(child)) {
553                     if (gtk_container_focus(GTK_CONTAINER(child), dir))
554                         return TRUE;
555                 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
556                     gtk_widget_grab_focus(child);
557                     return TRUE;
558                 }
559             }
560
561             if (dir == GTK_DIR_TAB_FORWARD)
562                 pos = pos->next;
563             else
564                 pos = pos->prev;
565         }
566
567         return FALSE;
568     } else
569         return columns_inherited_focus(container, dir);
570 }
571 #endif
572
573 /*
574  * Underlying parts of the layout algorithm, to compute the Columns
575  * container's width or height given the widths or heights of its
576  * children. These will be called in various ways with different
577  * notions of width and height in use, so we abstract them out and
578  * pass them a 'get width' or 'get height' function pointer.
579  */
580
581 typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
582
583 static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
584 {
585     ColumnsChild *child;
586     GList *children;
587     gint i, ncols, colspan, retwidth, childwidth;
588     const gint *percentages;
589     static const gint onecol[] = { 100 };
590
591     retwidth = 0;
592
593     ncols = 1;
594     percentages = onecol;
595
596     for (children = cols->children;
597          children && (child = children->data);
598          children = children->next) {
599
600         if (!child->widget) {
601             /* Column reconfiguration. */
602             ncols = child->ncols;
603             percentages = child->percentages;
604             continue;
605         }
606
607         /* Only take visible widgets into account. */
608         if (!gtk_widget_get_visible(child->widget))
609             continue;
610
611         childwidth = get_width(child);
612         colspan = child->colspan ? child->colspan : ncols-child->colstart;
613
614         /*
615          * To compute width: we know that childwidth + cols->spacing
616          * needs to equal a certain percentage of the full width of
617          * the container. So we work this value out, figure out how
618          * wide the container will need to be to make that percentage
619          * of it equal to that width, and ensure our returned width is
620          * at least that much. Very simple really.
621          */
622         {
623             int percent, thiswid, fullwid;
624
625             percent = 0;
626             for (i = 0; i < colspan; i++)
627                 percent += percentages[child->colstart+i];
628
629             thiswid = childwidth + cols->spacing;
630             /*
631              * Since childwidth is (at least sometimes) the _minimum_
632              * size the child needs, we must ensure that it gets _at
633              * least_ that size. Hence, when scaling thiswid up to
634              * fullwid, we must round up, which means adding percent-1
635              * before dividing by percent.
636              */
637             fullwid = (thiswid * 100 + percent - 1) / percent;
638
639             /*
640              * The above calculation assumes every widget gets
641              * cols->spacing on the right. So we subtract
642              * cols->spacing here to account for the extra load of
643              * spacing on the right.
644              */
645             if (retwidth < fullwid - cols->spacing)
646                 retwidth = fullwid - cols->spacing;
647         }
648     }
649
650     retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
651
652     return retwidth;
653 }
654
655 static void columns_alloc_horiz(Columns *cols, gint ourwidth,
656                                 widget_dim_fn_t get_width)
657 {
658     ColumnsChild *child;
659     GList *children;
660     gint i, ncols, colspan, border, *colxpos, childwidth;
661     const gint *percentages;
662     static const gint onecol[] = { 100 };
663
664     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
665
666     ncols = 1;
667     percentages = onecol;
668     /* colxpos gives the starting x position of each column.
669      * We supply n+1 of them, so that we can find the RH edge easily.
670      * All ending x positions are expected to be adjusted afterwards by
671      * subtracting the spacing. */
672     colxpos = g_new(gint, 2);
673     colxpos[0] = 0;
674     colxpos[1] = ourwidth - 2*border + cols->spacing;
675
676     for (children = cols->children;
677          children && (child = children->data);
678          children = children->next) {
679
680         if (!child->widget) {
681             gint percent;
682
683             /* Column reconfiguration. */
684             ncols = child->ncols;
685             percentages = child->percentages;
686             colxpos = g_renew(gint, colxpos, ncols + 1);
687             colxpos[0] = 0;
688             percent = 0;
689             for (i = 0; i < ncols; i++) {
690                 percent += percentages[i];
691                 colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
692                                 * percent / 100);
693             }
694             continue;
695         }
696
697         /* Only take visible widgets into account. */
698         if (!gtk_widget_get_visible(child->widget))
699             continue;
700
701         childwidth = get_width(child);
702         colspan = child->colspan ? child->colspan : ncols-child->colstart;
703
704         /*
705          * Starting x position is cols[colstart].
706          * Ending x position is cols[colstart+colspan] - spacing.
707          * 
708          * Unless we're forcing left, in which case the width is
709          * exactly the requisition width.
710          */
711         child->x = colxpos[child->colstart];
712         if (child->force_left)
713             child->w = childwidth;
714         else
715             child->w = (colxpos[child->colstart+colspan] -
716                         colxpos[child->colstart] - cols->spacing);
717     }
718
719     g_free(colxpos);
720 }
721
722 static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
723 {
724     ColumnsChild *child;
725     GList *children;
726     gint i, ncols, colspan, *colypos, retheight, childheight;
727
728     retheight = cols->spacing;
729
730     ncols = 1;
731     colypos = g_new(gint, 1);
732     colypos[0] = 0;
733
734     for (children = cols->children;
735          children && (child = children->data);
736          children = children->next) {
737
738         if (!child->widget) {
739             /* Column reconfiguration. */
740             for (i = 1; i < ncols; i++) {
741                 if (colypos[0] < colypos[i])
742                     colypos[0] = colypos[i];
743             }
744             ncols = child->ncols;
745             colypos = g_renew(gint, colypos, ncols);
746             for (i = 1; i < ncols; i++)
747                 colypos[i] = colypos[0];
748             continue;
749         }
750
751         /* Only take visible widgets into account. */
752         if (!gtk_widget_get_visible(child->widget))
753             continue;
754
755         childheight = get_height(child);
756         if (child->same_height_as) {
757             gint childheight2 = get_height(child->same_height_as);
758             if (childheight < childheight2)
759                 childheight = childheight2;
760         }
761         colspan = child->colspan ? child->colspan : ncols-child->colstart;
762
763         /*
764          * To compute height: the widget's top will be positioned at
765          * the largest y value so far reached in any of the columns it
766          * crosses. Then it will go down by childheight plus padding;
767          * and the point it reaches at the bottom is the new y value
768          * in all those columns, and minus the padding it is also a
769          * lower bound on our own height.
770          */
771         {
772             int topy, boty;
773
774             topy = 0;
775             for (i = 0; i < colspan; i++) {
776                 if (topy < colypos[child->colstart+i])
777                     topy = colypos[child->colstart+i];
778             }
779             boty = topy + childheight + cols->spacing;
780             for (i = 0; i < colspan; i++) {
781                 colypos[child->colstart+i] = boty;
782             }
783
784             if (retheight < boty - cols->spacing)
785                 retheight = boty - cols->spacing;
786         }
787     }
788
789     retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
790
791     g_free(colypos);
792
793     return retheight;
794 }
795
796 static void columns_alloc_vert(Columns *cols, gint ourheight,
797                                widget_dim_fn_t get_height)
798 {
799     ColumnsChild *child;
800     GList *children;
801     gint i, ncols, colspan, *colypos, realheight, fakeheight;
802
803     ncols = 1;
804     /* As in size_request, colypos is the lowest y reached in each column. */
805     colypos = g_new(gint, 1);
806     colypos[0] = 0;
807
808     for (children = cols->children;
809          children && (child = children->data);
810          children = children->next) {
811         if (!child->widget) {
812             /* Column reconfiguration. */
813             for (i = 1; i < ncols; i++) {
814                 if (colypos[0] < colypos[i])
815                     colypos[0] = colypos[i];
816             }
817             ncols = child->ncols;
818             colypos = g_renew(gint, colypos, ncols);
819             for (i = 1; i < ncols; i++)
820                 colypos[i] = colypos[0];
821             continue;
822         }
823
824         /* Only take visible widgets into account. */
825         if (!gtk_widget_get_visible(child->widget))
826             continue;
827
828         realheight = fakeheight = get_height(child);
829         if (child->same_height_as) {
830             gint childheight2 = get_height(child->same_height_as);
831             if (fakeheight < childheight2)
832                 fakeheight = childheight2;
833         }
834         colspan = child->colspan ? child->colspan : ncols-child->colstart;
835
836         /*
837          * To compute height: the widget's top will be positioned
838          * at the largest y value so far reached in any of the
839          * columns it crosses. Then it will go down by creq.height
840          * plus padding; and the point it reaches at the bottom is
841          * the new y value in all those columns.
842          */
843         {
844             int topy, boty;
845
846             topy = 0;
847             for (i = 0; i < colspan; i++) {
848                 if (topy < colypos[child->colstart+i])
849                     topy = colypos[child->colstart+i];
850             }
851             child->y = topy + fakeheight/2 - realheight/2;
852             child->h = realheight;
853             boty = topy + fakeheight + cols->spacing;
854             for (i = 0; i < colspan; i++) {
855                 colypos[child->colstart+i] = boty;
856             }
857         }
858     }
859
860     g_free(colypos);    
861 }
862
863 /*
864  * Now here comes the interesting bit. The actual layout part is
865  * done in the following two functions:
866  *
867  * columns_size_request() examines the list of widgets held in the
868  * Columns, and returns a requisition stating the absolute minimum
869  * size it can bear to be.
870  *
871  * columns_size_allocate() is given an allocation telling it what
872  * size the whole container is going to be, and it calls
873  * gtk_widget_size_allocate() on all of its (visible) children to
874  * set their size and position relative to the top left of the
875  * container.
876  */
877
878 #if !GTK_CHECK_VERSION(3,0,0)
879
880 static gint columns_gtk2_get_width(ColumnsChild *child)
881 {
882     GtkRequisition creq;
883     gtk_widget_size_request(child->widget, &creq);
884     return creq.width;
885 }
886
887 static gint columns_gtk2_get_height(ColumnsChild *child)
888 {
889     GtkRequisition creq;
890     gtk_widget_size_request(child->widget, &creq);
891     return creq.height;
892 }
893
894 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
895 {
896     Columns *cols;
897
898     g_return_if_fail(widget != NULL);
899     g_return_if_fail(IS_COLUMNS(widget));
900     g_return_if_fail(req != NULL);
901
902     cols = COLUMNS(widget);
903
904     req->width = columns_compute_width(cols, columns_gtk2_get_width);
905     req->height = columns_compute_height(cols, columns_gtk2_get_height);
906 }
907
908 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
909 {
910     Columns *cols;
911     ColumnsChild *child;
912     GList *children;
913     gint border;
914
915     g_return_if_fail(widget != NULL);
916     g_return_if_fail(IS_COLUMNS(widget));
917     g_return_if_fail(alloc != NULL);
918
919     cols = COLUMNS(widget);
920     gtk_widget_set_allocation(widget, alloc);
921
922     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
923
924     columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
925     columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
926
927     for (children = cols->children;
928          children && (child = children->data);
929          children = children->next) {
930         if (child->widget && gtk_widget_get_visible(child->widget)) {
931             GtkAllocation call;
932             call.x = alloc->x + border + child->x;
933             call.y = alloc->y + border + child->y;
934             call.width = child->w;
935             call.height = child->h;
936             gtk_widget_size_allocate(child->widget, &call);
937         }
938     }
939 }
940
941 #else /* GTK_CHECK_VERSION(3,0,0) */
942
943 static gint columns_gtk3_get_min_width(ColumnsChild *child)
944 {
945     gint ret;
946     gtk_widget_get_preferred_width(child->widget, &ret, NULL);
947     return ret;
948 }
949
950 static gint columns_gtk3_get_nat_width(ColumnsChild *child)
951 {
952     gint ret;
953
954     if (GTK_IS_LABEL(child->widget) &&
955         gtk_label_get_line_wrap(GTK_LABEL(child->widget))) {
956         /*
957          * We treat wrapping GtkLabels as a special case in this
958          * layout class, because the whole point of those is that I
959          * _don't_ want them to take up extra horizontal space for
960          * long text, but instead to wrap it to whatever size is used
961          * by the rest of the layout.
962          */
963         gtk_widget_get_preferred_width(child->widget, &ret, NULL);
964     } else {
965         gtk_widget_get_preferred_width(child->widget, NULL, &ret);
966     }
967     return ret;
968 }
969
970 static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
971 {
972     gint ret;
973     gtk_widget_get_preferred_width_for_height(child->widget, child->h,
974                                               &ret, NULL);
975     return ret;
976 }
977
978 static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
979 {
980     gint ret;
981     gtk_widget_get_preferred_width_for_height(child->widget, child->h,
982                                               NULL, &ret);
983     return ret;
984 }
985
986 static gint columns_gtk3_get_min_height(ColumnsChild *child)
987 {
988     gint ret;
989     gtk_widget_get_preferred_height(child->widget, &ret, NULL);
990     return ret;
991 }
992
993 static gint columns_gtk3_get_nat_height(ColumnsChild *child)
994 {
995     gint ret;
996     gtk_widget_get_preferred_height(child->widget, NULL, &ret);
997     return ret;
998 }
999
1000 static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
1001 {
1002     gint ret;
1003     gtk_widget_get_preferred_height_for_width(child->widget, child->w,
1004                                               &ret, NULL);
1005     return ret;
1006 }
1007
1008 static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
1009 {
1010     gint ret;
1011     gtk_widget_get_preferred_height_for_width(child->widget, child->w,
1012                                               NULL, &ret);
1013     return ret;
1014 }
1015
1016 static void columns_get_preferred_width(GtkWidget *widget,
1017                                         gint *min, gint *nat)
1018 {
1019     Columns *cols;
1020
1021     g_return_if_fail(widget != NULL);
1022     g_return_if_fail(IS_COLUMNS(widget));
1023
1024     cols = COLUMNS(widget);
1025
1026     if (min)
1027         *min = columns_compute_width(cols, columns_gtk3_get_min_width);
1028     if (nat)
1029         *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
1030 }
1031
1032 static void columns_get_preferred_height(GtkWidget *widget,
1033                                          gint *min, gint *nat)
1034 {
1035     Columns *cols;
1036
1037     g_return_if_fail(widget != NULL);
1038     g_return_if_fail(IS_COLUMNS(widget));
1039
1040     cols = COLUMNS(widget);
1041
1042     if (min)
1043         *min = columns_compute_height(cols, columns_gtk3_get_min_height);
1044     if (nat)
1045         *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
1046 }
1047
1048 static void columns_get_preferred_width_for_height(GtkWidget *widget,
1049                                                    gint height,
1050                                                    gint *min, gint *nat)
1051 {
1052     Columns *cols;
1053
1054     g_return_if_fail(widget != NULL);
1055     g_return_if_fail(IS_COLUMNS(widget));
1056
1057     cols = COLUMNS(widget);
1058
1059     /* FIXME: which one should the get-height function here be? */
1060     columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
1061
1062     if (min)
1063         *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
1064     if (nat)
1065         *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
1066 }
1067
1068 static void columns_get_preferred_height_for_width(GtkWidget *widget,
1069                                                    gint width,
1070                                                    gint *min, gint *nat)
1071 {
1072     Columns *cols;
1073
1074     g_return_if_fail(widget != NULL);
1075     g_return_if_fail(IS_COLUMNS(widget));
1076
1077     cols = COLUMNS(widget);
1078
1079     /* FIXME: which one should the get-height function here be? */
1080     columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
1081
1082     if (min)
1083         *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
1084     if (nat)
1085         *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
1086 }
1087
1088 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
1089 {
1090     Columns *cols;
1091     ColumnsChild *child;
1092     GList *children;
1093     gint border;
1094
1095     g_return_if_fail(widget != NULL);
1096     g_return_if_fail(IS_COLUMNS(widget));
1097     g_return_if_fail(alloc != NULL);
1098
1099     cols = COLUMNS(widget);
1100     gtk_widget_set_allocation(widget, alloc);
1101
1102     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
1103
1104     columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
1105     columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
1106
1107     for (children = cols->children;
1108          children && (child = children->data);
1109          children = children->next) {
1110         if (child->widget && gtk_widget_get_visible(child->widget)) {
1111             GtkAllocation call;
1112             call.x = alloc->x + border + child->x;
1113             call.y = alloc->y + border + child->y;
1114             call.width = child->w;
1115             call.height = child->h;
1116             gtk_widget_size_allocate(child->widget, &call);
1117         }
1118     }
1119 }
1120
1121 #endif