]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkcols.c
Fiddly things involving pruning .svn directories, not mentioning
[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, *next;
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 = next) {
264         /*
265          * We can't wait until after the callback to assign
266          * `children = children->next', because the callback might
267          * be gtk_widget_destroy, which would remove the link
268          * `children' from the list! So instead we must get our
269          * hands on the value of the `next' pointer _before_ the
270          * callback.
271          */
272         next = children->next;
273         if (child->widget)
274             callback(child->widget, callback_data);
275     }
276 }
277
278 static GtkType columns_child_type(GtkContainer *container)
279 {
280     return GTK_TYPE_WIDGET;
281 }
282
283 GtkWidget *columns_new(gint spacing)
284 {
285     Columns *cols;
286
287     cols = gtk_type_new(columns_get_type());
288     cols->spacing = spacing;
289
290     return GTK_WIDGET(cols);
291 }
292
293 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
294 {
295     ColumnsChild *childdata;
296     gint i;
297
298     g_return_if_fail(cols != NULL);
299     g_return_if_fail(IS_COLUMNS(cols));
300     g_return_if_fail(ncols > 0);
301     g_return_if_fail(percentages != NULL);
302
303     childdata = g_new(ColumnsChild, 1);
304     childdata->widget = NULL;
305     childdata->ncols = ncols;
306     childdata->percentages = g_new(gint, ncols);
307     childdata->force_left = FALSE;
308     for (i = 0; i < ncols; i++)
309         childdata->percentages[i] = percentages[i];
310
311     cols->children = g_list_append(cols->children, childdata);
312 }
313
314 void columns_add(Columns *cols, GtkWidget *child,
315                  gint colstart, gint colspan)
316 {
317     ColumnsChild *childdata;
318
319     g_return_if_fail(cols != NULL);
320     g_return_if_fail(IS_COLUMNS(cols));
321     g_return_if_fail(child != NULL);
322     g_return_if_fail(child->parent == NULL);
323
324     childdata = g_new(ColumnsChild, 1);
325     childdata->widget = child;
326     childdata->colstart = colstart;
327     childdata->colspan = colspan;
328     childdata->force_left = FALSE;
329
330     cols->children = g_list_append(cols->children, childdata);
331     cols->taborder = g_list_append(cols->taborder, child);
332
333     gtk_widget_set_parent(child, GTK_WIDGET(cols));
334
335     if (GTK_WIDGET_REALIZED(cols))
336         gtk_widget_realize(child);
337
338     if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
339         if (GTK_WIDGET_MAPPED(cols))
340             gtk_widget_map(child);
341         gtk_widget_queue_resize(child);
342     }
343 }
344
345 void columns_force_left_align(Columns *cols, GtkWidget *widget)
346 {
347     ColumnsChild *child;
348     GList *children;
349
350     g_return_if_fail(cols != NULL);
351     g_return_if_fail(IS_COLUMNS(cols));
352     g_return_if_fail(widget != NULL);
353
354     for (children = cols->children;
355          children && (child = children->data);
356          children = children->next) {
357         if (child->widget != widget)
358             continue;
359
360         child->force_left = TRUE;
361         if (GTK_WIDGET_VISIBLE(widget))
362             gtk_widget_queue_resize(GTK_WIDGET(cols));
363         break;
364     }
365 }
366
367 void columns_taborder_last(Columns *cols, GtkWidget *widget)
368 {
369     GtkWidget *childw;
370     GList *children;
371
372     g_return_if_fail(cols != NULL);
373     g_return_if_fail(IS_COLUMNS(cols));
374     g_return_if_fail(widget != NULL);
375
376     for (children = cols->taborder;
377          children && (childw = children->data);
378          children = children->next) {
379         if (childw != widget)
380             continue;
381
382         cols->taborder = g_list_remove_link(cols->taborder, children);
383         g_list_free(children);
384         cols->taborder = g_list_append(cols->taborder, widget);
385         break;
386     }
387 }
388
389 /*
390  * Override GtkContainer's focus movement so the user can
391  * explicitly specify the tab order.
392  */
393 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
394 {
395     Columns *cols;
396     GList *pos;
397     GtkWidget *focuschild;
398
399     g_return_val_if_fail(container != NULL, FALSE);
400     g_return_val_if_fail(IS_COLUMNS(container), FALSE);
401
402     cols = COLUMNS(container);
403
404     if (!GTK_WIDGET_DRAWABLE(cols) ||
405         !GTK_WIDGET_IS_SENSITIVE(cols))
406         return FALSE;
407
408     if (!GTK_WIDGET_CAN_FOCUS(container) &&
409         (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
410
411         focuschild = container->focus_child;
412         gtk_container_set_focus_child(container, NULL);
413
414         if (dir == GTK_DIR_TAB_FORWARD)
415             pos = cols->taborder;
416         else
417             pos = g_list_last(cols->taborder);
418
419         while (pos) {
420             GtkWidget *child = pos->data;
421
422             if (focuschild) {
423                 if (focuschild == child) {
424                     focuschild = NULL; /* now we can start looking in here */
425                     if (GTK_WIDGET_DRAWABLE(child) &&
426                         GTK_IS_CONTAINER(child) &&
427                         !GTK_WIDGET_HAS_FOCUS(child)) {
428                         if (gtk_container_focus(GTK_CONTAINER(child), dir))
429                             return TRUE;
430                     }
431                 }
432             } else if (GTK_WIDGET_DRAWABLE(child)) {
433                 if (GTK_IS_CONTAINER(child)) {
434                     if (gtk_container_focus(GTK_CONTAINER(child), dir))
435                         return TRUE;
436                 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
437                     gtk_widget_grab_focus(child);
438                     return TRUE;
439                 }
440             }
441
442             if (dir == GTK_DIR_TAB_FORWARD)
443                 pos = pos->next;
444             else
445                 pos = pos->prev;
446         }
447
448         return FALSE;
449     } else
450         return columns_inherited_focus(container, dir);
451 }
452
453 /*
454  * Now here comes the interesting bit. The actual layout part is
455  * done in the following two functions:
456  * 
457  * columns_size_request() examines the list of widgets held in the
458  * Columns, and returns a requisition stating the absolute minimum
459  * size it can bear to be.
460  * 
461  * columns_size_allocate() is given an allocation telling it what
462  * size the whole container is going to be, and it calls
463  * gtk_widget_size_allocate() on all of its (visible) children to
464  * set their size and position relative to the top left of the
465  * container.
466  */
467
468 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
469 {
470     Columns *cols;
471     ColumnsChild *child;
472     GList *children;
473     gint i, ncols, colspan, *colypos;
474     const gint *percentages;
475     static const gint onecol[] = { 100 };
476
477     g_return_if_fail(widget != NULL);
478     g_return_if_fail(IS_COLUMNS(widget));
479     g_return_if_fail(req != NULL);
480
481     cols = COLUMNS(widget);
482
483     req->width = 0;
484     req->height = cols->spacing;
485
486     ncols = 1;
487     colypos = g_new(gint, 1);
488     colypos[0] = 0;
489     percentages = onecol;
490
491     for (children = cols->children;
492          children && (child = children->data);
493          children = children->next) {
494         GtkRequisition creq;
495
496         if (!child->widget) {
497             /* Column reconfiguration. */
498             for (i = 1; i < ncols; i++) {
499                 if (colypos[0] < colypos[i])
500                     colypos[0] = colypos[i];
501             }
502             ncols = child->ncols;
503             percentages = child->percentages;
504             colypos = g_renew(gint, colypos, ncols);
505             for (i = 1; i < ncols; i++)
506                 colypos[i] = colypos[0];
507             continue;
508         }
509
510         /* Only take visible widgets into account. */
511         if (!GTK_WIDGET_VISIBLE(child->widget))
512             continue;
513
514         gtk_widget_size_request(child->widget, &creq);
515         colspan = child->colspan ? child->colspan : ncols-child->colstart;
516
517         /*
518          * To compute width: we know that creq.width plus
519          * cols->spacing needs to equal a certain percentage of the
520          * full width of the container. So we work this value out,
521          * figure out how wide the container will need to be to
522          * make that percentage of it equal to that width, and
523          * ensure our returned width is at least that much. Very
524          * simple really.
525          */
526         {
527             int percent, thiswid, fullwid;
528
529             percent = 0;
530             for (i = 0; i < colspan; i++)
531                 percent += percentages[child->colstart+i];
532
533             thiswid = creq.width + cols->spacing;
534             /*
535              * Since creq is the _minimum_ size the child needs, we
536              * must ensure that it gets _at least_ that size.
537              * Hence, when scaling thiswid up to fullwid, we must
538              * round up, which means adding percent-1 before
539              * dividing by percent.
540              */
541             fullwid = (thiswid * 100 + percent - 1) / percent;
542
543             /*
544              * The above calculation assumes every widget gets
545              * cols->spacing on the right. So we subtract
546              * cols->spacing here to account for the extra load of
547              * spacing on the right.
548              */
549             if (req->width < fullwid - cols->spacing)
550                 req->width = fullwid - cols->spacing;
551         }
552
553         /*
554          * To compute height: the widget's top will be positioned
555          * at the largest y value so far reached in any of the
556          * columns it crosses. Then it will go down by creq.height
557          * plus padding; and the point it reaches at the bottom is
558          * the new y value in all those columns, and minus the
559          * padding it is also a lower bound on our own size
560          * request.
561          */
562         {
563             int topy, boty;
564
565             topy = 0;
566             for (i = 0; i < colspan; i++) {
567                 if (topy < colypos[child->colstart+i])
568                     topy = colypos[child->colstart+i];
569             }
570             boty = topy + creq.height + cols->spacing;
571             for (i = 0; i < colspan; i++) {
572                 colypos[child->colstart+i] = boty;
573             }
574
575             if (req->height < boty - cols->spacing)
576                 req->height = boty - cols->spacing;
577         }
578     }
579
580     req->width += 2*GTK_CONTAINER(cols)->border_width;
581     req->height += 2*GTK_CONTAINER(cols)->border_width;
582
583     g_free(colypos);
584 }
585
586 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
587 {
588     Columns *cols;
589     ColumnsChild *child;
590     GList *children;
591     gint i, ncols, colspan, border, *colxpos, *colypos;
592     const gint *percentages;
593     static const gint onecol[] = { 100 };
594
595     g_return_if_fail(widget != NULL);
596     g_return_if_fail(IS_COLUMNS(widget));
597     g_return_if_fail(alloc != NULL);
598
599     cols = COLUMNS(widget);
600     widget->allocation = *alloc;
601     border = GTK_CONTAINER(cols)->border_width;
602
603     ncols = 1;
604     percentages = onecol;
605     /* colxpos gives the starting x position of each column.
606      * We supply n+1 of them, so that we can find the RH edge easily.
607      * All ending x positions are expected to be adjusted afterwards by
608      * subtracting the spacing. */
609     colxpos = g_new(gint, 2);
610     colxpos[0] = 0;
611     colxpos[1] = alloc->width - 2*border + cols->spacing;
612     /* As in size_request, colypos is the lowest y reached in each column. */
613     colypos = g_new(gint, 1);
614     colypos[0] = 0;
615
616     for (children = cols->children;
617          children && (child = children->data);
618          children = children->next) {
619         GtkRequisition creq;
620         GtkAllocation call;
621
622         if (!child->widget) {
623             gint percent;
624
625             /* Column reconfiguration. */
626             for (i = 1; i < ncols; i++) {
627                 if (colypos[0] < colypos[i])
628                     colypos[0] = colypos[i];
629             }
630             ncols = child->ncols;
631             percentages = child->percentages;
632             colypos = g_renew(gint, colypos, ncols);
633             for (i = 1; i < ncols; i++)
634                 colypos[i] = colypos[0];
635             colxpos = g_renew(gint, colxpos, ncols + 1);
636             colxpos[0] = 0;
637             percent = 0;
638             for (i = 0; i < ncols; i++) {
639                 percent += percentages[i];
640                 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
641                                 * percent / 100);
642             }
643             continue;
644         }
645
646         /* Only take visible widgets into account. */
647         if (!GTK_WIDGET_VISIBLE(child->widget))
648             continue;
649
650         gtk_widget_get_child_requisition(child->widget, &creq);
651         colspan = child->colspan ? child->colspan : ncols-child->colstart;
652
653         /*
654          * Starting x position is cols[colstart].
655          * Ending x position is cols[colstart+colspan] - spacing.
656          * 
657          * Unless we're forcing left, in which case the width is
658          * exactly the requisition width.
659          */
660         call.x = alloc->x + border + colxpos[child->colstart];
661         if (child->force_left)
662             call.width = creq.width;
663         else
664             call.width = (colxpos[child->colstart+colspan] -
665                           colxpos[child->colstart] - cols->spacing);
666
667         /*
668          * To compute height: the widget's top will be positioned
669          * at the largest y value so far reached in any of the
670          * columns it crosses. Then it will go down by creq.height
671          * plus padding; and the point it reaches at the bottom is
672          * the new y value in all those columns.
673          */
674         {
675             int topy, boty;
676
677             topy = 0;
678             for (i = 0; i < colspan; i++) {
679                 if (topy < colypos[child->colstart+i])
680                     topy = colypos[child->colstart+i];
681             }
682             call.y = alloc->y + border + topy;
683             call.height = creq.height;
684             boty = topy + creq.height + cols->spacing;
685             for (i = 0; i < colspan; i++) {
686                 colypos[child->colstart+i] = boty;
687             }
688         }
689
690         gtk_widget_size_allocate(child->widget, &call);
691     }
692
693     g_free(colxpos);
694     g_free(colypos);    
695 }