]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkcols.c
Better not forget to make sure GTK1 doesn't break.
[PuTTY.git] / unix / gtkcols.c
1 /*
2  * gtkcols.c - implementation of the `Columns' GTK layout container.
3  */
4
5 #include "gtkcols.h"
6 #include <gtk/gtk.h>
7
8 static void columns_init(Columns *cols);
9 static void columns_class_init(ColumnsClass *klass);
10 static void columns_map(GtkWidget *widget);
11 static void columns_unmap(GtkWidget *widget);
12 #if !GTK_CHECK_VERSION(2,0,0)
13 static void columns_draw(GtkWidget *widget, GdkRectangle *area);
14 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
15 #endif
16 static void columns_base_add(GtkContainer *container, GtkWidget *widget);
17 static void columns_remove(GtkContainer *container, GtkWidget *widget);
18 static void columns_forall(GtkContainer *container, gboolean include_internals,
19                            GtkCallback callback, gpointer callback_data);
20 #if !GTK_CHECK_VERSION(2,0,0)
21 static gint columns_focus(GtkContainer *container, GtkDirectionType dir);
22 #endif
23 static GtkType columns_child_type(GtkContainer *container);
24 static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
25 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
26
27 static GtkContainerClass *parent_class = NULL;
28
29 GtkType columns_get_type(void)
30 {
31     static GtkType columns_type = 0;
32
33     if (!columns_type) {
34         static const GtkTypeInfo columns_info = {
35             "Columns",
36             sizeof(Columns),
37             sizeof(ColumnsClass),
38             (GtkClassInitFunc) columns_class_init,
39             (GtkObjectInitFunc) columns_init,
40             /* reserved_1 */ NULL,
41             /* reserved_2 */ NULL,
42             (GtkClassInitFunc) NULL,
43         };
44
45         columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
46     }
47
48     return columns_type;
49 }
50
51 #if !GTK_CHECK_VERSION(2,0,0)
52 static gint (*columns_inherited_focus)(GtkContainer *container,
53                                        GtkDirectionType direction);
54 #endif
55
56 static void columns_class_init(ColumnsClass *klass)
57 {
58     GtkObjectClass *object_class;
59     GtkWidgetClass *widget_class;
60     GtkContainerClass *container_class;
61
62     object_class = (GtkObjectClass *)klass;
63     widget_class = (GtkWidgetClass *)klass;
64     container_class = (GtkContainerClass *)klass;
65
66     parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
67
68     /*
69      * FIXME: do we have to do all this faffing with set_arg,
70      * get_arg and child_arg_type? Ick.
71      */
72
73     widget_class->map = columns_map;
74     widget_class->unmap = columns_unmap;
75 #if !GTK_CHECK_VERSION(2,0,0)
76     widget_class->draw = columns_draw;
77     widget_class->expose_event = columns_expose;
78 #endif
79     widget_class->size_request = columns_size_request;
80     widget_class->size_allocate = columns_size_allocate;
81
82     container_class->add = columns_base_add;
83     container_class->remove = columns_remove;
84     container_class->forall = columns_forall;
85     container_class->child_type = columns_child_type;
86 #if !GTK_CHECK_VERSION(2,0,0)
87     /* Save the previous value of this method. */
88     if (!columns_inherited_focus)
89         columns_inherited_focus = container_class->focus;
90     container_class->focus = columns_focus;
91 #endif
92 }
93
94 static void columns_init(Columns *cols)
95 {
96     GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
97
98     cols->children = NULL;
99     cols->spacing = 0;
100 }
101
102 /*
103  * These appear to be thoroughly tedious functions; the only reason
104  * we have to reimplement them at all is because we defined our own
105  * format for our GList of children...
106  */
107 static void columns_map(GtkWidget *widget)
108 {
109     Columns *cols;
110     ColumnsChild *child;
111     GList *children;
112
113     g_return_if_fail(widget != NULL);
114     g_return_if_fail(IS_COLUMNS(widget));
115
116     cols = COLUMNS(widget);
117     GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
118
119     for (children = cols->children;
120          children && (child = children->data);
121          children = children->next) {
122         if (child->widget &&
123             GTK_WIDGET_VISIBLE(child->widget) &&
124             !GTK_WIDGET_MAPPED(child->widget))
125             gtk_widget_map(child->widget);
126     }
127 }
128 static void columns_unmap(GtkWidget *widget)
129 {
130     Columns *cols;
131     ColumnsChild *child;
132     GList *children;
133
134     g_return_if_fail(widget != NULL);
135     g_return_if_fail(IS_COLUMNS(widget));
136
137     cols = COLUMNS(widget);
138     GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
139
140     for (children = cols->children;
141          children && (child = children->data);
142          children = children->next) {
143         if (child->widget &&
144             GTK_WIDGET_VISIBLE(child->widget) &&
145             GTK_WIDGET_MAPPED(child->widget))
146             gtk_widget_unmap(child->widget);
147     }
148 }
149 #if !GTK_CHECK_VERSION(2,0,0)
150 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
151 {
152     Columns *cols;
153     ColumnsChild *child;
154     GList *children;
155     GdkRectangle child_area;
156
157     g_return_if_fail(widget != NULL);
158     g_return_if_fail(IS_COLUMNS(widget));
159
160     if (GTK_WIDGET_DRAWABLE(widget)) {
161         cols = COLUMNS(widget);
162
163         for (children = cols->children;
164              children && (child = children->data);
165              children = children->next) {
166             if (child->widget &&
167                 GTK_WIDGET_DRAWABLE(child->widget) &&
168                 gtk_widget_intersect(child->widget, area, &child_area))
169                 gtk_widget_draw(child->widget, &child_area);
170         }
171     }
172 }
173 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
174 {
175     Columns *cols;
176     ColumnsChild *child;
177     GList *children;
178     GdkEventExpose child_event;
179
180     g_return_val_if_fail(widget != NULL, FALSE);
181     g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
182     g_return_val_if_fail(event != NULL, FALSE);
183
184     if (GTK_WIDGET_DRAWABLE(widget)) {
185         cols = COLUMNS(widget);
186         child_event = *event;
187
188         for (children = cols->children;
189              children && (child = children->data);
190              children = children->next) {
191             if (child->widget &&
192                 GTK_WIDGET_DRAWABLE(child->widget) &&
193                 GTK_WIDGET_NO_WINDOW(child->widget) &&
194                 gtk_widget_intersect(child->widget, &event->area,
195                                      &child_event.area))
196                 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
197         }
198     }
199     return FALSE;
200 }
201 #endif
202
203 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
204 {
205     Columns *cols;
206
207     g_return_if_fail(container != NULL);
208     g_return_if_fail(IS_COLUMNS(container));
209     g_return_if_fail(widget != NULL);
210
211     cols = COLUMNS(container);
212
213     /*
214      * Default is to add a new widget spanning all columns.
215      */
216     columns_add(cols, widget, 0, 0);   /* 0 means ncols */
217 }
218
219 static void columns_remove(GtkContainer *container, GtkWidget *widget)
220 {
221     Columns *cols;
222     ColumnsChild *child;
223     GtkWidget *childw;
224     GList *children;
225     gboolean was_visible;
226
227     g_return_if_fail(container != NULL);
228     g_return_if_fail(IS_COLUMNS(container));
229     g_return_if_fail(widget != NULL);
230
231     cols = COLUMNS(container);
232
233     for (children = cols->children;
234          children && (child = children->data);
235          children = children->next) {
236         if (child->widget != widget)
237             continue;
238
239         was_visible = GTK_WIDGET_VISIBLE(widget);
240         gtk_widget_unparent(widget);
241         cols->children = g_list_remove_link(cols->children, children);
242         g_list_free(children);
243         g_free(child);
244         if (was_visible)
245             gtk_widget_queue_resize(GTK_WIDGET(container));
246         break;
247     }
248
249     for (children = cols->taborder;
250          children && (childw = children->data);
251          children = children->next) {
252         if (childw != widget)
253             continue;
254
255         cols->taborder = g_list_remove_link(cols->taborder, children);
256         g_list_free(children);
257 #if GTK_CHECK_VERSION(2,0,0)
258         gtk_container_set_focus_chain(container, cols->taborder);
259 #endif
260         break;
261     }
262 }
263
264 static void columns_forall(GtkContainer *container, gboolean include_internals,
265                            GtkCallback callback, gpointer callback_data)
266 {
267     Columns *cols;
268     ColumnsChild *child;
269     GList *children, *next;
270
271     g_return_if_fail(container != NULL);
272     g_return_if_fail(IS_COLUMNS(container));
273     g_return_if_fail(callback != NULL);
274
275     cols = COLUMNS(container);
276
277     for (children = cols->children;
278          children && (child = children->data);
279          children = next) {
280         /*
281          * We can't wait until after the callback to assign
282          * `children = children->next', because the callback might
283          * be gtk_widget_destroy, which would remove the link
284          * `children' from the list! So instead we must get our
285          * hands on the value of the `next' pointer _before_ the
286          * callback.
287          */
288         next = children->next;
289         if (child->widget)
290             callback(child->widget, callback_data);
291     }
292 }
293
294 static GtkType columns_child_type(GtkContainer *container)
295 {
296     return GTK_TYPE_WIDGET;
297 }
298
299 GtkWidget *columns_new(gint spacing)
300 {
301     Columns *cols;
302
303     cols = gtk_type_new(columns_get_type());
304     cols->spacing = spacing;
305
306     return GTK_WIDGET(cols);
307 }
308
309 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
310 {
311     ColumnsChild *childdata;
312     gint i;
313
314     g_return_if_fail(cols != NULL);
315     g_return_if_fail(IS_COLUMNS(cols));
316     g_return_if_fail(ncols > 0);
317     g_return_if_fail(percentages != NULL);
318
319     childdata = g_new(ColumnsChild, 1);
320     childdata->widget = NULL;
321     childdata->ncols = ncols;
322     childdata->percentages = g_new(gint, ncols);
323     childdata->force_left = FALSE;
324     for (i = 0; i < ncols; i++)
325         childdata->percentages[i] = percentages[i];
326
327     cols->children = g_list_append(cols->children, childdata);
328 }
329
330 void columns_add(Columns *cols, GtkWidget *child,
331                  gint colstart, gint colspan)
332 {
333     ColumnsChild *childdata;
334
335     g_return_if_fail(cols != NULL);
336     g_return_if_fail(IS_COLUMNS(cols));
337     g_return_if_fail(child != NULL);
338     g_return_if_fail(child->parent == NULL);
339
340     childdata = g_new(ColumnsChild, 1);
341     childdata->widget = child;
342     childdata->colstart = colstart;
343     childdata->colspan = colspan;
344     childdata->force_left = FALSE;
345
346     cols->children = g_list_append(cols->children, childdata);
347     cols->taborder = g_list_append(cols->taborder, child);
348
349     gtk_widget_set_parent(child, GTK_WIDGET(cols));
350
351 #if GTK_CHECK_VERSION(2,0,0)
352     gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
353 #endif
354
355     if (GTK_WIDGET_REALIZED(cols))
356         gtk_widget_realize(child);
357
358     if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
359         if (GTK_WIDGET_MAPPED(cols))
360             gtk_widget_map(child);
361         gtk_widget_queue_resize(child);
362     }
363 }
364
365 void columns_force_left_align(Columns *cols, GtkWidget *widget)
366 {
367     ColumnsChild *child;
368     GList *children;
369
370     g_return_if_fail(cols != NULL);
371     g_return_if_fail(IS_COLUMNS(cols));
372     g_return_if_fail(widget != NULL);
373
374     for (children = cols->children;
375          children && (child = children->data);
376          children = children->next) {
377         if (child->widget != widget)
378             continue;
379
380         child->force_left = TRUE;
381         if (GTK_WIDGET_VISIBLE(widget))
382             gtk_widget_queue_resize(GTK_WIDGET(cols));
383         break;
384     }
385 }
386
387 void columns_taborder_last(Columns *cols, GtkWidget *widget)
388 {
389     GtkWidget *childw;
390     GList *children;
391
392     g_return_if_fail(cols != NULL);
393     g_return_if_fail(IS_COLUMNS(cols));
394     g_return_if_fail(widget != NULL);
395
396     for (children = cols->taborder;
397          children && (childw = children->data);
398          children = children->next) {
399         if (childw != widget)
400             continue;
401
402         cols->taborder = g_list_remove_link(cols->taborder, children);
403         g_list_free(children);
404         cols->taborder = g_list_append(cols->taborder, widget);
405 #if GTK_CHECK_VERSION(2,0,0)
406         gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
407 #endif
408         break;
409     }
410 }
411
412 #if !GTK_CHECK_VERSION(2,0,0)
413 /*
414  * Override GtkContainer's focus movement so the user can
415  * explicitly specify the tab order.
416  */
417 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
418 {
419     Columns *cols;
420     GList *pos;
421     GtkWidget *focuschild;
422
423     g_return_val_if_fail(container != NULL, FALSE);
424     g_return_val_if_fail(IS_COLUMNS(container), FALSE);
425
426     cols = COLUMNS(container);
427
428     if (!GTK_WIDGET_DRAWABLE(cols) ||
429         !GTK_WIDGET_IS_SENSITIVE(cols))
430         return FALSE;
431
432     if (!GTK_WIDGET_CAN_FOCUS(container) &&
433         (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
434
435         focuschild = container->focus_child;
436         gtk_container_set_focus_child(container, NULL);
437
438         if (dir == GTK_DIR_TAB_FORWARD)
439             pos = cols->taborder;
440         else
441             pos = g_list_last(cols->taborder);
442
443         while (pos) {
444             GtkWidget *child = pos->data;
445
446             if (focuschild) {
447                 if (focuschild == child) {
448                     focuschild = NULL; /* now we can start looking in here */
449                     if (GTK_WIDGET_DRAWABLE(child) &&
450                         GTK_IS_CONTAINER(child) &&
451                         !GTK_WIDGET_HAS_FOCUS(child)) {
452                         if (gtk_container_focus(GTK_CONTAINER(child), dir))
453                             return TRUE;
454                     }
455                 }
456             } else if (GTK_WIDGET_DRAWABLE(child)) {
457                 if (GTK_IS_CONTAINER(child)) {
458                     if (gtk_container_focus(GTK_CONTAINER(child), dir))
459                         return TRUE;
460                 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
461                     gtk_widget_grab_focus(child);
462                     return TRUE;
463                 }
464             }
465
466             if (dir == GTK_DIR_TAB_FORWARD)
467                 pos = pos->next;
468             else
469                 pos = pos->prev;
470         }
471
472         return FALSE;
473     } else
474         return columns_inherited_focus(container, dir);
475 }
476 #endif
477
478 /*
479  * Now here comes the interesting bit. The actual layout part is
480  * done in the following two functions:
481  * 
482  * columns_size_request() examines the list of widgets held in the
483  * Columns, and returns a requisition stating the absolute minimum
484  * size it can bear to be.
485  * 
486  * columns_size_allocate() is given an allocation telling it what
487  * size the whole container is going to be, and it calls
488  * gtk_widget_size_allocate() on all of its (visible) children to
489  * set their size and position relative to the top left of the
490  * container.
491  */
492
493 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
494 {
495     Columns *cols;
496     ColumnsChild *child;
497     GList *children;
498     gint i, ncols, colspan, *colypos;
499     const gint *percentages;
500     static const gint onecol[] = { 100 };
501
502     g_return_if_fail(widget != NULL);
503     g_return_if_fail(IS_COLUMNS(widget));
504     g_return_if_fail(req != NULL);
505
506     cols = COLUMNS(widget);
507
508     req->width = 0;
509     req->height = cols->spacing;
510
511     ncols = 1;
512     colypos = g_new(gint, 1);
513     colypos[0] = 0;
514     percentages = onecol;
515
516     for (children = cols->children;
517          children && (child = children->data);
518          children = children->next) {
519         GtkRequisition creq;
520
521         if (!child->widget) {
522             /* Column reconfiguration. */
523             for (i = 1; i < ncols; i++) {
524                 if (colypos[0] < colypos[i])
525                     colypos[0] = colypos[i];
526             }
527             ncols = child->ncols;
528             percentages = child->percentages;
529             colypos = g_renew(gint, colypos, ncols);
530             for (i = 1; i < ncols; i++)
531                 colypos[i] = colypos[0];
532             continue;
533         }
534
535         /* Only take visible widgets into account. */
536         if (!GTK_WIDGET_VISIBLE(child->widget))
537             continue;
538
539         gtk_widget_size_request(child->widget, &creq);
540         colspan = child->colspan ? child->colspan : ncols-child->colstart;
541
542         /*
543          * To compute width: we know that creq.width plus
544          * cols->spacing needs to equal a certain percentage of the
545          * full width of the container. So we work this value out,
546          * figure out how wide the container will need to be to
547          * make that percentage of it equal to that width, and
548          * ensure our returned width is at least that much. Very
549          * simple really.
550          */
551         {
552             int percent, thiswid, fullwid;
553
554             percent = 0;
555             for (i = 0; i < colspan; i++)
556                 percent += percentages[child->colstart+i];
557
558             thiswid = creq.width + cols->spacing;
559             /*
560              * Since creq is the _minimum_ size the child needs, we
561              * must ensure that it gets _at least_ that size.
562              * Hence, when scaling thiswid up to fullwid, we must
563              * round up, which means adding percent-1 before
564              * dividing by percent.
565              */
566             fullwid = (thiswid * 100 + percent - 1) / percent;
567
568             /*
569              * The above calculation assumes every widget gets
570              * cols->spacing on the right. So we subtract
571              * cols->spacing here to account for the extra load of
572              * spacing on the right.
573              */
574             if (req->width < fullwid - cols->spacing)
575                 req->width = fullwid - cols->spacing;
576         }
577
578         /*
579          * To compute height: the widget's top will be positioned
580          * at the largest y value so far reached in any of the
581          * columns it crosses. Then it will go down by creq.height
582          * plus padding; and the point it reaches at the bottom is
583          * the new y value in all those columns, and minus the
584          * padding it is also a lower bound on our own size
585          * request.
586          */
587         {
588             int topy, boty;
589
590             topy = 0;
591             for (i = 0; i < colspan; i++) {
592                 if (topy < colypos[child->colstart+i])
593                     topy = colypos[child->colstart+i];
594             }
595             boty = topy + creq.height + cols->spacing;
596             for (i = 0; i < colspan; i++) {
597                 colypos[child->colstart+i] = boty;
598             }
599
600             if (req->height < boty - cols->spacing)
601                 req->height = boty - cols->spacing;
602         }
603     }
604
605     req->width += 2*GTK_CONTAINER(cols)->border_width;
606     req->height += 2*GTK_CONTAINER(cols)->border_width;
607
608     g_free(colypos);
609 }
610
611 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
612 {
613     Columns *cols;
614     ColumnsChild *child;
615     GList *children;
616     gint i, ncols, colspan, border, *colxpos, *colypos;
617     const gint *percentages;
618     static const gint onecol[] = { 100 };
619
620     g_return_if_fail(widget != NULL);
621     g_return_if_fail(IS_COLUMNS(widget));
622     g_return_if_fail(alloc != NULL);
623
624     cols = COLUMNS(widget);
625     widget->allocation = *alloc;
626     border = GTK_CONTAINER(cols)->border_width;
627
628     ncols = 1;
629     percentages = onecol;
630     /* colxpos gives the starting x position of each column.
631      * We supply n+1 of them, so that we can find the RH edge easily.
632      * All ending x positions are expected to be adjusted afterwards by
633      * subtracting the spacing. */
634     colxpos = g_new(gint, 2);
635     colxpos[0] = 0;
636     colxpos[1] = alloc->width - 2*border + cols->spacing;
637     /* As in size_request, colypos is the lowest y reached in each column. */
638     colypos = g_new(gint, 1);
639     colypos[0] = 0;
640
641     for (children = cols->children;
642          children && (child = children->data);
643          children = children->next) {
644         GtkRequisition creq;
645         GtkAllocation call;
646
647         if (!child->widget) {
648             gint percent;
649
650             /* Column reconfiguration. */
651             for (i = 1; i < ncols; i++) {
652                 if (colypos[0] < colypos[i])
653                     colypos[0] = colypos[i];
654             }
655             ncols = child->ncols;
656             percentages = child->percentages;
657             colypos = g_renew(gint, colypos, ncols);
658             for (i = 1; i < ncols; i++)
659                 colypos[i] = colypos[0];
660             colxpos = g_renew(gint, colxpos, ncols + 1);
661             colxpos[0] = 0;
662             percent = 0;
663             for (i = 0; i < ncols; i++) {
664                 percent += percentages[i];
665                 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
666                                 * percent / 100);
667             }
668             continue;
669         }
670
671         /* Only take visible widgets into account. */
672         if (!GTK_WIDGET_VISIBLE(child->widget))
673             continue;
674
675         gtk_widget_get_child_requisition(child->widget, &creq);
676         colspan = child->colspan ? child->colspan : ncols-child->colstart;
677
678         /*
679          * Starting x position is cols[colstart].
680          * Ending x position is cols[colstart+colspan] - spacing.
681          * 
682          * Unless we're forcing left, in which case the width is
683          * exactly the requisition width.
684          */
685         call.x = alloc->x + border + colxpos[child->colstart];
686         if (child->force_left)
687             call.width = creq.width;
688         else
689             call.width = (colxpos[child->colstart+colspan] -
690                           colxpos[child->colstart] - cols->spacing);
691
692         /*
693          * To compute height: the widget's top will be positioned
694          * at the largest y value so far reached in any of the
695          * columns it crosses. Then it will go down by creq.height
696          * plus padding; and the point it reaches at the bottom is
697          * the new y value in all those columns.
698          */
699         {
700             int topy, boty;
701
702             topy = 0;
703             for (i = 0; i < colspan; i++) {
704                 if (topy < colypos[child->colstart+i])
705                     topy = colypos[child->colstart+i];
706             }
707             call.y = alloc->y + border + topy;
708             call.height = creq.height;
709             boty = topy + creq.height + cols->spacing;
710             for (i = 0; i < colspan; i++) {
711                 colypos[child->colstart+i] = boty;
712             }
713         }
714
715         gtk_widget_size_allocate(child->widget, &call);
716     }
717
718     g_free(colxpos);
719     g_free(colypos);    
720 }