2 * gtkcols.c - implementation of the `Columns' GTK layout container.
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);
22 static GtkContainerClass *parent_class = NULL;
24 GtkType columns_get_type(void)
26 static GtkType columns_type = 0;
29 static const GtkTypeInfo columns_info = {
33 (GtkClassInitFunc) columns_class_init,
34 (GtkObjectInitFunc) columns_init,
35 /* reserved_1 */ NULL,
36 /* reserved_2 */ NULL,
37 (GtkClassInitFunc) NULL,
40 columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
46 static gint (*columns_inherited_focus)(GtkContainer *container,
47 GtkDirectionType direction);
49 static void columns_class_init(ColumnsClass *klass)
51 GtkObjectClass *object_class;
52 GtkWidgetClass *widget_class;
53 GtkContainerClass *container_class;
55 object_class = (GtkObjectClass *)klass;
56 widget_class = (GtkWidgetClass *)klass;
57 container_class = (GtkContainerClass *)klass;
59 parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
62 * FIXME: do we have to do all this faffing with set_arg,
63 * get_arg and child_arg_type? Ick.
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;
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;
83 static void columns_init(Columns *cols)
85 GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
87 cols->children = NULL;
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...
96 static void columns_map(GtkWidget *widget)
102 g_return_if_fail(widget != NULL);
103 g_return_if_fail(IS_COLUMNS(widget));
105 cols = COLUMNS(widget);
106 GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
108 for (children = cols->children;
109 children && (child = children->data);
110 children = children->next) {
112 GTK_WIDGET_VISIBLE(child->widget) &&
113 !GTK_WIDGET_MAPPED(child->widget))
114 gtk_widget_map(child->widget);
117 static void columns_unmap(GtkWidget *widget)
123 g_return_if_fail(widget != NULL);
124 g_return_if_fail(IS_COLUMNS(widget));
126 cols = COLUMNS(widget);
127 GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
129 for (children = cols->children;
130 children && (child = children->data);
131 children = children->next) {
133 GTK_WIDGET_VISIBLE(child->widget) &&
134 GTK_WIDGET_MAPPED(child->widget))
135 gtk_widget_unmap(child->widget);
138 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
143 GdkRectangle child_area;
145 g_return_if_fail(widget != NULL);
146 g_return_if_fail(IS_COLUMNS(widget));
148 if (GTK_WIDGET_DRAWABLE(widget)) {
149 cols = COLUMNS(widget);
151 for (children = cols->children;
152 children && (child = children->data);
153 children = children->next) {
155 GTK_WIDGET_DRAWABLE(child->widget) &&
156 gtk_widget_intersect(child->widget, area, &child_area))
157 gtk_widget_draw(child->widget, &child_area);
161 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
166 GdkEventExpose child_event;
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);
172 if (GTK_WIDGET_DRAWABLE(widget)) {
173 cols = COLUMNS(widget);
174 child_event = *event;
176 for (children = cols->children;
177 children && (child = children->data);
178 children = children->next) {
180 GTK_WIDGET_DRAWABLE(child->widget) &&
181 GTK_WIDGET_NO_WINDOW(child->widget) &&
182 gtk_widget_intersect(child->widget, &event->area,
184 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
190 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
194 g_return_if_fail(container != NULL);
195 g_return_if_fail(IS_COLUMNS(container));
196 g_return_if_fail(widget != NULL);
198 cols = COLUMNS(container);
201 * Default is to add a new widget spanning all columns.
203 columns_add(cols, widget, 0, 0); /* 0 means ncols */
206 static void columns_remove(GtkContainer *container, GtkWidget *widget)
212 gboolean was_visible;
214 g_return_if_fail(container != NULL);
215 g_return_if_fail(IS_COLUMNS(container));
216 g_return_if_fail(widget != NULL);
218 cols = COLUMNS(container);
220 for (children = cols->children;
221 children && (child = children->data);
222 children = children->next) {
223 if (child->widget != widget)
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);
232 gtk_widget_queue_resize(GTK_WIDGET(container));
236 for (children = cols->taborder;
237 children && (childw = children->data);
238 children = children->next) {
239 if (childw != widget)
242 cols->taborder = g_list_remove_link(cols->taborder, children);
243 g_list_free(children);
248 static void columns_forall(GtkContainer *container, gboolean include_internals,
249 GtkCallback callback, gpointer callback_data)
255 g_return_if_fail(container != NULL);
256 g_return_if_fail(IS_COLUMNS(container));
257 g_return_if_fail(callback != NULL);
259 cols = COLUMNS(container);
261 for (children = cols->children;
262 children && (child = children->data);
263 children = children->next)
265 callback(child->widget, callback_data);
268 static GtkType columns_child_type(GtkContainer *container)
270 return GTK_TYPE_WIDGET;
273 GtkWidget *columns_new(gint spacing)
277 cols = gtk_type_new(columns_get_type());
278 cols->spacing = spacing;
280 return GTK_WIDGET(cols);
283 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
285 ColumnsChild *childdata;
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);
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];
301 cols->children = g_list_append(cols->children, childdata);
304 void columns_add(Columns *cols, GtkWidget *child,
305 gint colstart, gint colspan)
307 ColumnsChild *childdata;
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);
314 childdata = g_new(ColumnsChild, 1);
315 childdata->widget = child;
316 childdata->colstart = colstart;
317 childdata->colspan = colspan;
318 childdata->force_left = FALSE;
320 cols->children = g_list_append(cols->children, childdata);
321 cols->taborder = g_list_append(cols->taborder, child);
323 gtk_widget_set_parent(child, GTK_WIDGET(cols));
325 if (GTK_WIDGET_REALIZED(cols))
326 gtk_widget_realize(child);
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);
335 void columns_force_left_align(Columns *cols, GtkWidget *widget)
340 g_return_if_fail(cols != NULL);
341 g_return_if_fail(IS_COLUMNS(cols));
342 g_return_if_fail(widget != NULL);
344 for (children = cols->children;
345 children && (child = children->data);
346 children = children->next) {
347 if (child->widget != widget)
350 child->force_left = TRUE;
351 if (GTK_WIDGET_VISIBLE(widget))
352 gtk_widget_queue_resize(GTK_WIDGET(cols));
357 void columns_taborder_last(Columns *cols, GtkWidget *widget)
362 g_return_if_fail(cols != NULL);
363 g_return_if_fail(IS_COLUMNS(cols));
364 g_return_if_fail(widget != NULL);
366 for (children = cols->taborder;
367 children && (childw = children->data);
368 children = children->next) {
369 if (childw != widget)
372 cols->taborder = g_list_remove_link(cols->taborder, children);
373 g_list_free(children);
374 cols->taborder = g_list_append(cols->taborder, widget);
380 * Override GtkContainer's focus movement so the user can
381 * explicitly specify the tab order.
383 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
387 GtkWidget *focuschild;
389 g_return_val_if_fail(container != NULL, FALSE);
390 g_return_val_if_fail(IS_COLUMNS(container), FALSE);
392 cols = COLUMNS(container);
394 if (!GTK_WIDGET_DRAWABLE(cols) ||
395 !GTK_WIDGET_IS_SENSITIVE(cols))
398 if (!GTK_WIDGET_CAN_FOCUS(container) &&
399 (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
401 focuschild = container->focus_child;
402 gtk_container_set_focus_child(container, NULL);
404 if (dir == GTK_DIR_TAB_FORWARD)
405 pos = cols->taborder;
407 pos = g_list_last(cols->taborder);
410 GtkWidget *child = pos->data;
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))
422 } else if (GTK_WIDGET_DRAWABLE(child)) {
423 if (GTK_IS_CONTAINER(child)) {
424 if (gtk_container_focus(GTK_CONTAINER(child), dir))
426 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
427 gtk_widget_grab_focus(child);
432 if (dir == GTK_DIR_TAB_FORWARD)
440 return columns_inherited_focus(container, dir);
444 * Now here comes the interesting bit. The actual layout part is
445 * done in the following two functions:
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.
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
458 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
463 gint i, ncols, colspan, *colypos;
464 const gint *percentages;
465 static const gint onecol[] = { 100 };
467 g_return_if_fail(widget != NULL);
468 g_return_if_fail(IS_COLUMNS(widget));
469 g_return_if_fail(req != NULL);
471 cols = COLUMNS(widget);
474 req->height = cols->spacing;
477 colypos = g_new(gint, 1);
479 percentages = onecol;
481 for (children = cols->children;
482 children && (child = children->data);
483 children = children->next) {
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];
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];
500 /* Only take visible widgets into account. */
501 if (!GTK_WIDGET_VISIBLE(child->widget))
504 gtk_widget_size_request(child->widget, &creq);
505 colspan = child->colspan ? child->colspan : ncols-child->colstart;
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
517 int percent, thiswid, fullwid;
520 for (i = 0; i < colspan; i++)
521 percent += percentages[child->colstart+i];
523 thiswid = creq.width + cols->spacing;
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.
531 fullwid = (thiswid * 100 + percent - 1) / percent;
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.
539 if (req->width < fullwid - cols->spacing)
540 req->width = fullwid - cols->spacing;
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
556 for (i = 0; i < colspan; i++) {
557 if (topy < colypos[child->colstart+i])
558 topy = colypos[child->colstart+i];
560 boty = topy + creq.height + cols->spacing;
561 for (i = 0; i < colspan; i++) {
562 colypos[child->colstart+i] = boty;
565 if (req->height < boty - cols->spacing)
566 req->height = boty - cols->spacing;
570 req->width += 2*GTK_CONTAINER(cols)->border_width;
571 req->height += 2*GTK_CONTAINER(cols)->border_width;
576 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
581 gint i, ncols, colspan, border, *colxpos, *colypos;
582 const gint *percentages;
583 static const gint onecol[] = { 100 };
585 g_return_if_fail(widget != NULL);
586 g_return_if_fail(IS_COLUMNS(widget));
587 g_return_if_fail(alloc != NULL);
589 cols = COLUMNS(widget);
590 widget->allocation = *alloc;
591 border = GTK_CONTAINER(cols)->border_width;
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);
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);
606 for (children = cols->children;
607 children && (child = children->data);
608 children = children->next) {
612 if (!child->widget) {
615 /* Column reconfiguration. */
616 for (i = 1; i < ncols; i++) {
617 if (colypos[0] < colypos[i])
618 colypos[0] = colypos[i];
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);
628 for (i = 0; i < ncols; i++) {
629 percent += percentages[i];
630 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
636 /* Only take visible widgets into account. */
637 if (!GTK_WIDGET_VISIBLE(child->widget))
640 gtk_widget_get_child_requisition(child->widget, &creq);
641 colspan = child->colspan ? child->colspan : ncols-child->colstart;
644 * Starting x position is cols[colstart].
645 * Ending x position is cols[colstart+colspan] - spacing.
647 * Unless we're forcing left, in which case the width is
648 * exactly the requisition width.
650 call.x = alloc->x + border + colxpos[child->colstart];
651 if (child->force_left)
652 call.width = creq.width;
654 call.width = (colxpos[child->colstart+colspan] -
655 colxpos[child->colstart] - cols->spacing);
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.
668 for (i = 0; i < colspan; i++) {
669 if (topy < colypos[child->colstart+i])
670 topy = colypos[child->colstart+i];
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;
680 gtk_widget_size_allocate(child->widget, &call);