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