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