]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkcols.c
4d0d23c33ce60a34ddf381a8013f646b77744307
[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         g_free(child);
294         if (was_visible)
295             gtk_widget_queue_resize(GTK_WIDGET(container));
296         break;
297     }
298
299     for (children = cols->taborder;
300          children && (childw = children->data);
301          children = children->next) {
302         if (childw != widget)
303             continue;
304
305         cols->taborder = g_list_remove_link(cols->taborder, children);
306         g_list_free(children);
307 #if GTK_CHECK_VERSION(2,0,0)
308         gtk_container_set_focus_chain(container, cols->taborder);
309 #endif
310         break;
311     }
312 }
313
314 static void columns_forall(GtkContainer *container, gboolean include_internals,
315                            GtkCallback callback, gpointer callback_data)
316 {
317     Columns *cols;
318     ColumnsChild *child;
319     GList *children, *next;
320
321     g_return_if_fail(container != NULL);
322     g_return_if_fail(IS_COLUMNS(container));
323     g_return_if_fail(callback != NULL);
324
325     cols = COLUMNS(container);
326
327     for (children = cols->children;
328          children && (child = children->data);
329          children = next) {
330         /*
331          * We can't wait until after the callback to assign
332          * `children = children->next', because the callback might
333          * be gtk_widget_destroy, which would remove the link
334          * `children' from the list! So instead we must get our
335          * hands on the value of the `next' pointer _before_ the
336          * callback.
337          */
338         next = children->next;
339         if (child->widget)
340             callback(child->widget, callback_data);
341     }
342 }
343
344 static GType columns_child_type(GtkContainer *container)
345 {
346     return GTK_TYPE_WIDGET;
347 }
348
349 GtkWidget *columns_new(gint spacing)
350 {
351     Columns *cols;
352
353 #if !GTK_CHECK_VERSION(2,0,0)
354     cols = gtk_type_new(columns_get_type());
355 #else
356     cols = g_object_new(TYPE_COLUMNS, NULL);
357 #endif
358
359     cols->spacing = spacing;
360
361     return GTK_WIDGET(cols);
362 }
363
364 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
365 {
366     ColumnsChild *childdata;
367     gint i;
368
369     g_return_if_fail(cols != NULL);
370     g_return_if_fail(IS_COLUMNS(cols));
371     g_return_if_fail(ncols > 0);
372     g_return_if_fail(percentages != NULL);
373
374     childdata = g_new(ColumnsChild, 1);
375     childdata->widget = NULL;
376     childdata->ncols = ncols;
377     childdata->percentages = g_new(gint, ncols);
378     childdata->force_left = FALSE;
379     for (i = 0; i < ncols; i++)
380         childdata->percentages[i] = percentages[i];
381
382     cols->children = g_list_append(cols->children, childdata);
383 }
384
385 void columns_add(Columns *cols, GtkWidget *child,
386                  gint colstart, gint colspan)
387 {
388     ColumnsChild *childdata;
389
390     g_return_if_fail(cols != NULL);
391     g_return_if_fail(IS_COLUMNS(cols));
392     g_return_if_fail(child != NULL);
393     g_return_if_fail(gtk_widget_get_parent(child) == NULL);
394
395     childdata = g_new(ColumnsChild, 1);
396     childdata->widget = child;
397     childdata->colstart = colstart;
398     childdata->colspan = colspan;
399     childdata->force_left = FALSE;
400
401     cols->children = g_list_append(cols->children, childdata);
402     cols->taborder = g_list_append(cols->taborder, child);
403
404     gtk_widget_set_parent(child, GTK_WIDGET(cols));
405
406 #if GTK_CHECK_VERSION(2,0,0)
407     gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
408 #endif
409
410     if (gtk_widget_get_realized(GTK_WIDGET(cols)))
411         gtk_widget_realize(child);
412
413     if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
414         gtk_widget_get_visible(child)) {
415         if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
416             gtk_widget_map(child);
417         gtk_widget_queue_resize(child);
418     }
419 }
420
421 static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
422 {
423     GList *children;
424     ColumnsChild *child;
425
426     for (children = cols->children;
427          children && (child = children->data);
428          children = children->next) {
429
430         if (child->widget == widget)
431             return child;
432     }
433
434     return NULL;
435 }
436
437 void columns_force_left_align(Columns *cols, GtkWidget *widget)
438 {
439     ColumnsChild *child;
440
441     g_return_if_fail(cols != NULL);
442     g_return_if_fail(IS_COLUMNS(cols));
443     g_return_if_fail(widget != NULL);
444
445     child = columns_find_child(cols, widget);
446     g_return_if_fail(child != NULL);
447
448     child->force_left = TRUE;
449     if (gtk_widget_get_visible(widget))
450         gtk_widget_queue_resize(GTK_WIDGET(cols));
451 }
452
453 void columns_taborder_last(Columns *cols, GtkWidget *widget)
454 {
455     GtkWidget *childw;
456     GList *children;
457
458     g_return_if_fail(cols != NULL);
459     g_return_if_fail(IS_COLUMNS(cols));
460     g_return_if_fail(widget != NULL);
461
462     for (children = cols->taborder;
463          children && (childw = children->data);
464          children = children->next) {
465         if (childw != widget)
466             continue;
467
468         cols->taborder = g_list_remove_link(cols->taborder, children);
469         g_list_free(children);
470         cols->taborder = g_list_append(cols->taborder, widget);
471 #if GTK_CHECK_VERSION(2,0,0)
472         gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
473 #endif
474         break;
475     }
476 }
477
478 #if !GTK_CHECK_VERSION(2,0,0)
479 /*
480  * Override GtkContainer's focus movement so the user can
481  * explicitly specify the tab order.
482  */
483 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
484 {
485     Columns *cols;
486     GList *pos;
487     GtkWidget *focuschild;
488
489     g_return_val_if_fail(container != NULL, FALSE);
490     g_return_val_if_fail(IS_COLUMNS(container), FALSE);
491
492     cols = COLUMNS(container);
493
494     if (!GTK_WIDGET_DRAWABLE(cols) ||
495         !GTK_WIDGET_IS_SENSITIVE(cols))
496         return FALSE;
497
498     if (!GTK_WIDGET_CAN_FOCUS(container) &&
499         (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
500
501         focuschild = container->focus_child;
502         gtk_container_set_focus_child(container, NULL);
503
504         if (dir == GTK_DIR_TAB_FORWARD)
505             pos = cols->taborder;
506         else
507             pos = g_list_last(cols->taborder);
508
509         while (pos) {
510             GtkWidget *child = pos->data;
511
512             if (focuschild) {
513                 if (focuschild == child) {
514                     focuschild = NULL; /* now we can start looking in here */
515                     if (GTK_WIDGET_DRAWABLE(child) &&
516                         GTK_IS_CONTAINER(child) &&
517                         !GTK_WIDGET_HAS_FOCUS(child)) {
518                         if (gtk_container_focus(GTK_CONTAINER(child), dir))
519                             return TRUE;
520                     }
521                 }
522             } else if (GTK_WIDGET_DRAWABLE(child)) {
523                 if (GTK_IS_CONTAINER(child)) {
524                     if (gtk_container_focus(GTK_CONTAINER(child), dir))
525                         return TRUE;
526                 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
527                     gtk_widget_grab_focus(child);
528                     return TRUE;
529                 }
530             }
531
532             if (dir == GTK_DIR_TAB_FORWARD)
533                 pos = pos->next;
534             else
535                 pos = pos->prev;
536         }
537
538         return FALSE;
539     } else
540         return columns_inherited_focus(container, dir);
541 }
542 #endif
543
544 /*
545  * Underlying parts of the layout algorithm, to compute the Columns
546  * container's width or height given the widths or heights of its
547  * children. These will be called in various ways with different
548  * notions of width and height in use, so we abstract them out and
549  * pass them a 'get width' or 'get height' function pointer.
550  */
551
552 typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
553
554 static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
555 {
556     ColumnsChild *child;
557     GList *children;
558     gint i, ncols, colspan, retwidth, childwidth;
559     const gint *percentages;
560     static const gint onecol[] = { 100 };
561
562     retwidth = 0;
563
564     ncols = 1;
565     percentages = onecol;
566
567     for (children = cols->children;
568          children && (child = children->data);
569          children = children->next) {
570
571         if (!child->widget) {
572             /* Column reconfiguration. */
573             ncols = child->ncols;
574             percentages = child->percentages;
575             continue;
576         }
577
578         /* Only take visible widgets into account. */
579         if (!gtk_widget_get_visible(child->widget))
580             continue;
581
582         childwidth = get_width(child);
583         colspan = child->colspan ? child->colspan : ncols-child->colstart;
584
585         /*
586          * To compute width: we know that childwidth + cols->spacing
587          * needs to equal a certain percentage of the full width of
588          * the container. So we work this value out, figure out how
589          * wide the container will need to be to make that percentage
590          * of it equal to that width, and ensure our returned width is
591          * at least that much. Very simple really.
592          */
593         {
594             int percent, thiswid, fullwid;
595
596             percent = 0;
597             for (i = 0; i < colspan; i++)
598                 percent += percentages[child->colstart+i];
599
600             thiswid = childwidth + cols->spacing;
601             /*
602              * Since childwidth is (at least sometimes) the _minimum_
603              * size the child needs, we must ensure that it gets _at
604              * least_ that size. Hence, when scaling thiswid up to
605              * fullwid, we must round up, which means adding percent-1
606              * before dividing by percent.
607              */
608             fullwid = (thiswid * 100 + percent - 1) / percent;
609
610             /*
611              * The above calculation assumes every widget gets
612              * cols->spacing on the right. So we subtract
613              * cols->spacing here to account for the extra load of
614              * spacing on the right.
615              */
616             if (retwidth < fullwid - cols->spacing)
617                 retwidth = fullwid - cols->spacing;
618         }
619     }
620
621     retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
622
623     return retwidth;
624 }
625
626 static void columns_alloc_horiz(Columns *cols, gint ourwidth,
627                                 widget_dim_fn_t get_width)
628 {
629     ColumnsChild *child;
630     GList *children;
631     gint i, ncols, colspan, border, *colxpos, childwidth;
632     const gint *percentages;
633     static const gint onecol[] = { 100 };
634
635     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
636
637     ncols = 1;
638     percentages = onecol;
639     /* colxpos gives the starting x position of each column.
640      * We supply n+1 of them, so that we can find the RH edge easily.
641      * All ending x positions are expected to be adjusted afterwards by
642      * subtracting the spacing. */
643     colxpos = g_new(gint, 2);
644     colxpos[0] = 0;
645     colxpos[1] = ourwidth - 2*border + cols->spacing;
646
647     for (children = cols->children;
648          children && (child = children->data);
649          children = children->next) {
650
651         if (!child->widget) {
652             gint percent;
653
654             /* Column reconfiguration. */
655             ncols = child->ncols;
656             percentages = child->percentages;
657             colxpos = g_renew(gint, colxpos, ncols + 1);
658             colxpos[0] = 0;
659             percent = 0;
660             for (i = 0; i < ncols; i++) {
661                 percent += percentages[i];
662                 colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
663                                 * percent / 100);
664             }
665             continue;
666         }
667
668         /* Only take visible widgets into account. */
669         if (!gtk_widget_get_visible(child->widget))
670             continue;
671
672         childwidth = get_width(child);
673         colspan = child->colspan ? child->colspan : ncols-child->colstart;
674
675         /*
676          * Starting x position is cols[colstart].
677          * Ending x position is cols[colstart+colspan] - spacing.
678          * 
679          * Unless we're forcing left, in which case the width is
680          * exactly the requisition width.
681          */
682         child->x = colxpos[child->colstart];
683         if (child->force_left)
684             child->w = childwidth;
685         else
686             child->w = (colxpos[child->colstart+colspan] -
687                         colxpos[child->colstart] - cols->spacing);
688     }
689
690     g_free(colxpos);
691 }
692
693 static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
694 {
695     ColumnsChild *child;
696     GList *children;
697     gint i, ncols, colspan, *colypos, retheight, childheight;
698
699     retheight = cols->spacing;
700
701     ncols = 1;
702     colypos = g_new(gint, 1);
703     colypos[0] = 0;
704
705     for (children = cols->children;
706          children && (child = children->data);
707          children = children->next) {
708
709         if (!child->widget) {
710             /* Column reconfiguration. */
711             for (i = 1; i < ncols; i++) {
712                 if (colypos[0] < colypos[i])
713                     colypos[0] = colypos[i];
714             }
715             ncols = child->ncols;
716             colypos = g_renew(gint, colypos, ncols);
717             for (i = 1; i < ncols; i++)
718                 colypos[i] = colypos[0];
719             continue;
720         }
721
722         /* Only take visible widgets into account. */
723         if (!gtk_widget_get_visible(child->widget))
724             continue;
725
726         childheight = get_height(child);
727         colspan = child->colspan ? child->colspan : ncols-child->colstart;
728
729         /*
730          * To compute height: the widget's top will be positioned at
731          * the largest y value so far reached in any of the columns it
732          * crosses. Then it will go down by childheight plus padding;
733          * and the point it reaches at the bottom is the new y value
734          * in all those columns, and minus the padding it is also a
735          * lower bound on our own height.
736          */
737         {
738             int topy, boty;
739
740             topy = 0;
741             for (i = 0; i < colspan; i++) {
742                 if (topy < colypos[child->colstart+i])
743                     topy = colypos[child->colstart+i];
744             }
745             boty = topy + childheight + cols->spacing;
746             for (i = 0; i < colspan; i++) {
747                 colypos[child->colstart+i] = boty;
748             }
749
750             if (retheight < boty - cols->spacing)
751                 retheight = boty - cols->spacing;
752         }
753     }
754
755     retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
756
757     g_free(colypos);
758
759     return retheight;
760 }
761
762 static void columns_alloc_vert(Columns *cols, gint ourheight,
763                                widget_dim_fn_t get_height)
764 {
765     ColumnsChild *child;
766     GList *children;
767     gint i, ncols, colspan, *colypos, childheight;
768
769     ncols = 1;
770     /* As in size_request, colypos is the lowest y reached in each column. */
771     colypos = g_new(gint, 1);
772     colypos[0] = 0;
773
774     for (children = cols->children;
775          children && (child = children->data);
776          children = children->next) {
777         if (!child->widget) {
778             /* Column reconfiguration. */
779             for (i = 1; i < ncols; i++) {
780                 if (colypos[0] < colypos[i])
781                     colypos[0] = colypos[i];
782             }
783             ncols = child->ncols;
784             colypos = g_renew(gint, colypos, ncols);
785             for (i = 1; i < ncols; i++)
786                 colypos[i] = colypos[0];
787             continue;
788         }
789
790         /* Only take visible widgets into account. */
791         if (!gtk_widget_get_visible(child->widget))
792             continue;
793
794         childheight = get_height(child);
795         colspan = child->colspan ? child->colspan : ncols-child->colstart;
796
797         /*
798          * To compute height: the widget's top will be positioned
799          * at the largest y value so far reached in any of the
800          * columns it crosses. Then it will go down by creq.height
801          * plus padding; and the point it reaches at the bottom is
802          * the new y value in all those columns.
803          */
804         {
805             int topy, boty;
806
807             topy = 0;
808             for (i = 0; i < colspan; i++) {
809                 if (topy < colypos[child->colstart+i])
810                     topy = colypos[child->colstart+i];
811             }
812             child->y = topy;
813             child->h = childheight;
814             boty = topy + childheight + cols->spacing;
815             for (i = 0; i < colspan; i++) {
816                 colypos[child->colstart+i] = boty;
817             }
818         }
819     }
820
821     g_free(colypos);    
822 }
823
824 /*
825  * Now here comes the interesting bit. The actual layout part is
826  * done in the following two functions:
827  *
828  * columns_size_request() examines the list of widgets held in the
829  * Columns, and returns a requisition stating the absolute minimum
830  * size it can bear to be.
831  *
832  * columns_size_allocate() is given an allocation telling it what
833  * size the whole container is going to be, and it calls
834  * gtk_widget_size_allocate() on all of its (visible) children to
835  * set their size and position relative to the top left of the
836  * container.
837  */
838
839 #if !GTK_CHECK_VERSION(3,0,0)
840
841 static gint columns_gtk2_get_width(ColumnsChild *child)
842 {
843     GtkRequisition creq;
844     gtk_widget_size_request(child->widget, &creq);
845     return creq.width;
846 }
847
848 static gint columns_gtk2_get_height(ColumnsChild *child)
849 {
850     GtkRequisition creq;
851     gtk_widget_size_request(child->widget, &creq);
852     return creq.height;
853 }
854
855 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
856 {
857     Columns *cols;
858
859     g_return_if_fail(widget != NULL);
860     g_return_if_fail(IS_COLUMNS(widget));
861     g_return_if_fail(req != NULL);
862
863     cols = COLUMNS(widget);
864
865     req->width = columns_compute_width(cols, columns_gtk2_get_width);
866     req->height = columns_compute_height(cols, columns_gtk2_get_height);
867 }
868
869 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
870 {
871     Columns *cols;
872     ColumnsChild *child;
873     GList *children;
874     gint border;
875
876     g_return_if_fail(widget != NULL);
877     g_return_if_fail(IS_COLUMNS(widget));
878     g_return_if_fail(alloc != NULL);
879
880     cols = COLUMNS(widget);
881     gtk_widget_set_allocation(widget, alloc);
882
883     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
884
885     columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
886     columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
887
888     for (children = cols->children;
889          children && (child = children->data);
890          children = children->next) {
891         if (child->widget && gtk_widget_get_visible(child->widget)) {
892             GtkAllocation call;
893             call.x = alloc->x + border + child->x;
894             call.y = alloc->y + border + child->y;
895             call.width = child->w;
896             call.height = child->h;
897             gtk_widget_size_allocate(child->widget, &call);
898         }
899     }
900 }
901
902 #else /* GTK_CHECK_VERSION(3,0,0) */
903
904 static gint columns_gtk3_get_min_width(ColumnsChild *child)
905 {
906     gint ret;
907     gtk_widget_get_preferred_width(child->widget, &ret, NULL);
908     return ret;
909 }
910
911 static gint columns_gtk3_get_nat_width(ColumnsChild *child)
912 {
913     gint ret;
914
915     if (GTK_IS_LABEL(child->widget) &&
916         gtk_label_get_line_wrap(GTK_LABEL(child->widget))) {
917         /*
918          * We treat wrapping GtkLabels as a special case in this
919          * layout class, because the whole point of those is that I
920          * _don't_ want them to take up extra horizontal space for
921          * long text, but instead to wrap it to whatever size is used
922          * by the rest of the layout.
923          */
924         gtk_widget_get_preferred_width(child->widget, &ret, NULL);
925     } else {
926         gtk_widget_get_preferred_width(child->widget, NULL, &ret);
927     }
928     return ret;
929 }
930
931 static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
932 {
933     gint ret;
934     gtk_widget_get_preferred_width_for_height(child->widget, child->h,
935                                               &ret, NULL);
936     return ret;
937 }
938
939 static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
940 {
941     gint ret;
942     gtk_widget_get_preferred_width_for_height(child->widget, child->h,
943                                               NULL, &ret);
944     return ret;
945 }
946
947 static gint columns_gtk3_get_min_height(ColumnsChild *child)
948 {
949     gint ret;
950     gtk_widget_get_preferred_height(child->widget, &ret, NULL);
951     return ret;
952 }
953
954 static gint columns_gtk3_get_nat_height(ColumnsChild *child)
955 {
956     gint ret;
957     gtk_widget_get_preferred_height(child->widget, NULL, &ret);
958     return ret;
959 }
960
961 static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
962 {
963     gint ret;
964     gtk_widget_get_preferred_height_for_width(child->widget, child->w,
965                                               &ret, NULL);
966     return ret;
967 }
968
969 static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
970 {
971     gint ret;
972     gtk_widget_get_preferred_height_for_width(child->widget, child->w,
973                                               NULL, &ret);
974     return ret;
975 }
976
977 static void columns_get_preferred_width(GtkWidget *widget,
978                                         gint *min, gint *nat)
979 {
980     Columns *cols;
981
982     g_return_if_fail(widget != NULL);
983     g_return_if_fail(IS_COLUMNS(widget));
984
985     cols = COLUMNS(widget);
986
987     if (min)
988         *min = columns_compute_width(cols, columns_gtk3_get_min_width);
989     if (nat)
990         *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
991 }
992
993 static void columns_get_preferred_height(GtkWidget *widget,
994                                          gint *min, gint *nat)
995 {
996     Columns *cols;
997
998     g_return_if_fail(widget != NULL);
999     g_return_if_fail(IS_COLUMNS(widget));
1000
1001     cols = COLUMNS(widget);
1002
1003     if (min)
1004         *min = columns_compute_height(cols, columns_gtk3_get_min_height);
1005     if (nat)
1006         *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
1007 }
1008
1009 static void columns_get_preferred_width_for_height(GtkWidget *widget,
1010                                                    gint height,
1011                                                    gint *min, gint *nat)
1012 {
1013     Columns *cols;
1014
1015     g_return_if_fail(widget != NULL);
1016     g_return_if_fail(IS_COLUMNS(widget));
1017
1018     cols = COLUMNS(widget);
1019
1020     /* FIXME: which one should the get-height function here be? */
1021     columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
1022
1023     if (min)
1024         *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
1025     if (nat)
1026         *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
1027 }
1028
1029 static void columns_get_preferred_height_for_width(GtkWidget *widget,
1030                                                    gint width,
1031                                                    gint *min, gint *nat)
1032 {
1033     Columns *cols;
1034
1035     g_return_if_fail(widget != NULL);
1036     g_return_if_fail(IS_COLUMNS(widget));
1037
1038     cols = COLUMNS(widget);
1039
1040     /* FIXME: which one should the get-height function here be? */
1041     columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
1042
1043     if (min)
1044         *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
1045     if (nat)
1046         *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
1047 }
1048
1049 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
1050 {
1051     Columns *cols;
1052     ColumnsChild *child;
1053     GList *children;
1054     gint border;
1055
1056     g_return_if_fail(widget != NULL);
1057     g_return_if_fail(IS_COLUMNS(widget));
1058     g_return_if_fail(alloc != NULL);
1059
1060     cols = COLUMNS(widget);
1061     gtk_widget_set_allocation(widget, alloc);
1062
1063     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
1064
1065     columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
1066     columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
1067
1068     for (children = cols->children;
1069          children && (child = children->data);
1070          children = children->next) {
1071         if (child->widget && gtk_widget_get_visible(child->widget)) {
1072             GtkAllocation call;
1073             call.x = alloc->x + border + child->x;
1074             call.y = alloc->y + border + child->y;
1075             call.width = child->w;
1076             call.height = child->h;
1077             gtk_widget_size_allocate(child->widget, &call);
1078         }
1079     }
1080 }
1081
1082 #endif