]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkcols.c
first pass
[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 #ifdef COLUMNS_WIDTH_DIAGNOSTICS
592     printf("compute_width(%p): start\n", cols);
593 #endif
594
595     retwidth = 0;
596
597     ncols = 1;
598     percentages = onecol;
599
600     for (children = cols->children;
601          children && (child = children->data);
602          children = children->next) {
603
604         if (!child->widget) {
605             /* Column reconfiguration. */
606             ncols = child->ncols;
607             percentages = child->percentages;
608             continue;
609         }
610
611         /* Only take visible widgets into account. */
612         if (!gtk_widget_get_visible(child->widget))
613             continue;
614
615         childwidth = get_width(child);
616         colspan = child->colspan ? child->colspan : ncols-child->colstart;
617
618 #ifdef COLUMNS_WIDTH_DIAGNOSTICS
619         printf("compute_width(%p): ", cols);
620         if (GTK_IS_LABEL(child->widget))
621             printf("label %p '%s' wrap=%s: ", child->widget,
622                    gtk_label_get_text(GTK_LABEL(child->widget)),
623                    (gtk_label_get_line_wrap(GTK_LABEL(child->widget))
624                     ? "TRUE" : "FALSE"));
625         else
626             printf("widget %p: ", child->widget);
627         {
628             gint min, nat;
629             gtk_widget_get_preferred_width(child->widget, &min, &nat);
630             printf("minwidth=%d natwidth=%d ", min, nat);
631         }
632         printf("thiswidth=%d span=%d\n", childwidth, colspan);
633 #endif
634
635         /*
636          * To compute width: we know that childwidth + cols->spacing
637          * needs to equal a certain percentage of the full width of
638          * the container. So we work this value out, figure out how
639          * wide the container will need to be to make that percentage
640          * of it equal to that width, and ensure our returned width is
641          * at least that much. Very simple really.
642          */
643         {
644             int percent, thiswid, fullwid;
645
646             percent = 0;
647             for (i = 0; i < colspan; i++)
648                 percent += percentages[child->colstart+i];
649
650             thiswid = childwidth + cols->spacing;
651             /*
652              * Since childwidth is (at least sometimes) the _minimum_
653              * size the child needs, we must ensure that it gets _at
654              * least_ that size. Hence, when scaling thiswid up to
655              * fullwid, we must round up, which means adding percent-1
656              * before dividing by percent.
657              */
658             fullwid = (thiswid * 100 + percent - 1) / percent;
659 #ifdef COLUMNS_WIDTH_DIAGNOSTICS
660             printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
661                    cols, child->widget, thiswid, fullwid);
662 #endif
663
664             /*
665              * The above calculation assumes every widget gets
666              * cols->spacing on the right. So we subtract
667              * cols->spacing here to account for the extra load of
668              * spacing on the right.
669              */
670             if (retwidth < fullwid - cols->spacing)
671                 retwidth = fullwid - cols->spacing;
672         }
673     }
674
675     retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
676
677 #ifdef COLUMNS_WIDTH_DIAGNOSTICS
678     printf("compute_width(%p): done, returning %d\n", cols, retwidth);
679 #endif
680
681     return retwidth;
682 }
683
684 static void columns_alloc_horiz(Columns *cols, gint ourwidth,
685                                 widget_dim_fn_t get_width)
686 {
687     ColumnsChild *child;
688     GList *children;
689     gint i, ncols, colspan, border, *colxpos, childwidth;
690     const gint *percentages;
691     static const gint onecol[] = { 100 };
692
693     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
694
695     ncols = 1;
696     percentages = onecol;
697     /* colxpos gives the starting x position of each column.
698      * We supply n+1 of them, so that we can find the RH edge easily.
699      * All ending x positions are expected to be adjusted afterwards by
700      * subtracting the spacing. */
701     colxpos = g_new(gint, 2);
702     colxpos[0] = 0;
703     colxpos[1] = ourwidth - 2*border + cols->spacing;
704
705     for (children = cols->children;
706          children && (child = children->data);
707          children = children->next) {
708
709         if (!child->widget) {
710             gint percent;
711
712             /* Column reconfiguration. */
713             ncols = child->ncols;
714             percentages = child->percentages;
715             colxpos = g_renew(gint, colxpos, ncols + 1);
716             colxpos[0] = 0;
717             percent = 0;
718             for (i = 0; i < ncols; i++) {
719                 percent += percentages[i];
720                 colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
721                                 * percent / 100);
722             }
723             continue;
724         }
725
726         /* Only take visible widgets into account. */
727         if (!gtk_widget_get_visible(child->widget))
728             continue;
729
730         childwidth = get_width(child);
731         colspan = child->colspan ? child->colspan : ncols-child->colstart;
732
733         /*
734          * Starting x position is cols[colstart].
735          * Ending x position is cols[colstart+colspan] - spacing.
736          * 
737          * Unless we're forcing left, in which case the width is
738          * exactly the requisition width.
739          */
740         child->x = colxpos[child->colstart];
741         if (child->force_left)
742             child->w = childwidth;
743         else
744             child->w = (colxpos[child->colstart+colspan] -
745                         colxpos[child->colstart] - cols->spacing);
746     }
747
748     g_free(colxpos);
749 }
750
751 static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
752 {
753     ColumnsChild *child;
754     GList *children;
755     gint i, ncols, colspan, *colypos, retheight, childheight;
756
757     retheight = cols->spacing;
758
759     ncols = 1;
760     colypos = g_new(gint, 1);
761     colypos[0] = 0;
762
763     for (children = cols->children;
764          children && (child = children->data);
765          children = children->next) {
766
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         if (child->same_height_as) {
786             gint childheight2 = get_height(child->same_height_as);
787             if (childheight < childheight2)
788                 childheight = childheight2;
789         }
790         colspan = child->colspan ? child->colspan : ncols-child->colstart;
791
792         /*
793          * To compute height: the widget's top will be positioned at
794          * the largest y value so far reached in any of the columns it
795          * crosses. Then it will go down by childheight plus padding;
796          * and the point it reaches at the bottom is the new y value
797          * in all those columns, and minus the padding it is also a
798          * lower bound on our own height.
799          */
800         {
801             int topy, boty;
802
803             topy = 0;
804             for (i = 0; i < colspan; i++) {
805                 if (topy < colypos[child->colstart+i])
806                     topy = colypos[child->colstart+i];
807             }
808             boty = topy + childheight + cols->spacing;
809             for (i = 0; i < colspan; i++) {
810                 colypos[child->colstart+i] = boty;
811             }
812
813             if (retheight < boty - cols->spacing)
814                 retheight = boty - cols->spacing;
815         }
816     }
817
818     retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
819
820     g_free(colypos);
821
822     return retheight;
823 }
824
825 static void columns_alloc_vert(Columns *cols, gint ourheight,
826                                widget_dim_fn_t get_height)
827 {
828     ColumnsChild *child;
829     GList *children;
830     gint i, ncols, colspan, *colypos, realheight, fakeheight;
831
832     ncols = 1;
833     /* As in size_request, colypos is the lowest y reached in each column. */
834     colypos = g_new(gint, 1);
835     colypos[0] = 0;
836
837     for (children = cols->children;
838          children && (child = children->data);
839          children = children->next) {
840         if (!child->widget) {
841             /* Column reconfiguration. */
842             for (i = 1; i < ncols; i++) {
843                 if (colypos[0] < colypos[i])
844                     colypos[0] = colypos[i];
845             }
846             ncols = child->ncols;
847             colypos = g_renew(gint, colypos, ncols);
848             for (i = 1; i < ncols; i++)
849                 colypos[i] = colypos[0];
850             continue;
851         }
852
853         /* Only take visible widgets into account. */
854         if (!gtk_widget_get_visible(child->widget))
855             continue;
856
857         realheight = fakeheight = get_height(child);
858         if (child->same_height_as) {
859             gint childheight2 = get_height(child->same_height_as);
860             if (fakeheight < childheight2)
861                 fakeheight = childheight2;
862         }
863         colspan = child->colspan ? child->colspan : ncols-child->colstart;
864
865         /*
866          * To compute height: the widget's top will be positioned
867          * at the largest y value so far reached in any of the
868          * columns it crosses. Then it will go down by creq.height
869          * plus padding; and the point it reaches at the bottom is
870          * the new y value in all those columns.
871          */
872         {
873             int topy, boty;
874
875             topy = 0;
876             for (i = 0; i < colspan; i++) {
877                 if (topy < colypos[child->colstart+i])
878                     topy = colypos[child->colstart+i];
879             }
880             child->y = topy + fakeheight/2 - realheight/2;
881             child->h = realheight;
882             boty = topy + fakeheight + cols->spacing;
883             for (i = 0; i < colspan; i++) {
884                 colypos[child->colstart+i] = boty;
885             }
886         }
887     }
888
889     g_free(colypos);    
890 }
891
892 /*
893  * Now here comes the interesting bit. The actual layout part is
894  * done in the following two functions:
895  *
896  * columns_size_request() examines the list of widgets held in the
897  * Columns, and returns a requisition stating the absolute minimum
898  * size it can bear to be.
899  *
900  * columns_size_allocate() is given an allocation telling it what
901  * size the whole container is going to be, and it calls
902  * gtk_widget_size_allocate() on all of its (visible) children to
903  * set their size and position relative to the top left of the
904  * container.
905  */
906
907 #if !GTK_CHECK_VERSION(3,0,0)
908
909 static gint columns_gtk2_get_width(ColumnsChild *child)
910 {
911     GtkRequisition creq;
912     gtk_widget_size_request(child->widget, &creq);
913     return creq.width;
914 }
915
916 static gint columns_gtk2_get_height(ColumnsChild *child)
917 {
918     GtkRequisition creq;
919     gtk_widget_size_request(child->widget, &creq);
920     return creq.height;
921 }
922
923 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
924 {
925     Columns *cols;
926
927     g_return_if_fail(widget != NULL);
928     g_return_if_fail(IS_COLUMNS(widget));
929     g_return_if_fail(req != NULL);
930
931     cols = COLUMNS(widget);
932
933     req->width = columns_compute_width(cols, columns_gtk2_get_width);
934     req->height = columns_compute_height(cols, columns_gtk2_get_height);
935 }
936
937 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
938 {
939     Columns *cols;
940     ColumnsChild *child;
941     GList *children;
942     gint border;
943
944     g_return_if_fail(widget != NULL);
945     g_return_if_fail(IS_COLUMNS(widget));
946     g_return_if_fail(alloc != NULL);
947
948     cols = COLUMNS(widget);
949     gtk_widget_set_allocation(widget, alloc);
950
951     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
952
953     columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
954     columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
955
956     for (children = cols->children;
957          children && (child = children->data);
958          children = children->next) {
959         if (child->widget && gtk_widget_get_visible(child->widget)) {
960             GtkAllocation call;
961             call.x = alloc->x + border + child->x;
962             call.y = alloc->y + border + child->y;
963             call.width = child->w;
964             call.height = child->h;
965             gtk_widget_size_allocate(child->widget, &call);
966         }
967     }
968 }
969
970 #else /* GTK_CHECK_VERSION(3,0,0) */
971
972 static gint columns_gtk3_get_min_width(ColumnsChild *child)
973 {
974     gint ret;
975     gtk_widget_get_preferred_width(child->widget, &ret, NULL);
976     return ret;
977 }
978
979 static gint columns_gtk3_get_nat_width(ColumnsChild *child)
980 {
981     gint ret;
982
983     if ((GTK_IS_LABEL(child->widget) &&
984          gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
985         GTK_IS_ENTRY(child->widget)) {
986         /*
987          * We treat wrapping GtkLabels as a special case in this
988          * layout class, because the whole point of those is that I
989          * _don't_ want them to take up extra horizontal space for
990          * long text, but instead to wrap it to whatever size is used
991          * by the rest of the layout.
992          *
993          * GtkEntry gets similar treatment, because in OS X GTK I've
994          * found that it requests a natural width regardless of the
995          * output of gtk_entry_set_width_chars.
996          */
997         gtk_widget_get_preferred_width(child->widget, &ret, NULL);
998     } else {
999         gtk_widget_get_preferred_width(child->widget, NULL, &ret);
1000     }
1001     return ret;
1002 }
1003
1004 static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
1005 {
1006     gint ret;
1007     gtk_widget_get_preferred_width_for_height(child->widget, child->h,
1008                                               &ret, NULL);
1009     return ret;
1010 }
1011
1012 static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
1013 {
1014     gint ret;
1015     gtk_widget_get_preferred_width_for_height(child->widget, child->h,
1016                                               NULL, &ret);
1017     return ret;
1018 }
1019
1020 static gint columns_gtk3_get_min_height(ColumnsChild *child)
1021 {
1022     gint ret;
1023     gtk_widget_get_preferred_height(child->widget, &ret, NULL);
1024     return ret;
1025 }
1026
1027 static gint columns_gtk3_get_nat_height(ColumnsChild *child)
1028 {
1029     gint ret;
1030     gtk_widget_get_preferred_height(child->widget, NULL, &ret);
1031     return ret;
1032 }
1033
1034 static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
1035 {
1036     gint ret;
1037     gtk_widget_get_preferred_height_for_width(child->widget, child->w,
1038                                               &ret, NULL);
1039     return ret;
1040 }
1041
1042 static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
1043 {
1044     gint ret;
1045     gtk_widget_get_preferred_height_for_width(child->widget, child->w,
1046                                               NULL, &ret);
1047     return ret;
1048 }
1049
1050 static void columns_get_preferred_width(GtkWidget *widget,
1051                                         gint *min, gint *nat)
1052 {
1053     Columns *cols;
1054
1055     g_return_if_fail(widget != NULL);
1056     g_return_if_fail(IS_COLUMNS(widget));
1057
1058     cols = COLUMNS(widget);
1059
1060     if (min)
1061         *min = columns_compute_width(cols, columns_gtk3_get_min_width);
1062     if (nat)
1063         *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
1064 }
1065
1066 static void columns_get_preferred_height(GtkWidget *widget,
1067                                          gint *min, gint *nat)
1068 {
1069     Columns *cols;
1070
1071     g_return_if_fail(widget != NULL);
1072     g_return_if_fail(IS_COLUMNS(widget));
1073
1074     cols = COLUMNS(widget);
1075
1076     if (min)
1077         *min = columns_compute_height(cols, columns_gtk3_get_min_height);
1078     if (nat)
1079         *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
1080 }
1081
1082 static void columns_get_preferred_width_for_height(GtkWidget *widget,
1083                                                    gint height,
1084                                                    gint *min, gint *nat)
1085 {
1086     Columns *cols;
1087
1088     g_return_if_fail(widget != NULL);
1089     g_return_if_fail(IS_COLUMNS(widget));
1090
1091     cols = COLUMNS(widget);
1092
1093     /* FIXME: which one should the get-height function here be? */
1094     columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
1095
1096     if (min)
1097         *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
1098     if (nat)
1099         *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
1100 }
1101
1102 static void columns_get_preferred_height_for_width(GtkWidget *widget,
1103                                                    gint width,
1104                                                    gint *min, gint *nat)
1105 {
1106     Columns *cols;
1107
1108     g_return_if_fail(widget != NULL);
1109     g_return_if_fail(IS_COLUMNS(widget));
1110
1111     cols = COLUMNS(widget);
1112
1113     /* FIXME: which one should the get-height function here be? */
1114     columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
1115
1116     if (min)
1117         *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
1118     if (nat)
1119         *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
1120 }
1121
1122 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
1123 {
1124     Columns *cols;
1125     ColumnsChild *child;
1126     GList *children;
1127     gint border;
1128
1129     g_return_if_fail(widget != NULL);
1130     g_return_if_fail(IS_COLUMNS(widget));
1131     g_return_if_fail(alloc != NULL);
1132
1133     cols = COLUMNS(widget);
1134     gtk_widget_set_allocation(widget, alloc);
1135
1136     border = gtk_container_get_border_width(GTK_CONTAINER(cols));
1137
1138     columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
1139     columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
1140
1141     for (children = cols->children;
1142          children && (child = children->data);
1143          children = children->next) {
1144         if (child->widget && gtk_widget_get_visible(child->widget)) {
1145             GtkAllocation call;
1146             call.x = alloc->x + border + child->x;
1147             call.y = alloc->y + border + child->y;
1148             call.width = child->w;
1149             call.height = child->h;
1150             gtk_widget_size_allocate(child->widget, &call);
1151         }
1152     }
1153 }
1154
1155 #endif