c0a0ed8df35351e5b33cd6aeda5749c19e7fcb00
[u/mdw/putty] / 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 }