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