2 * gtkcols.c - implementation of the `Columns' GTK layout container.
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);
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);
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);
27 static GtkContainerClass *parent_class = NULL;
29 #if !GTK_CHECK_VERSION(2,0,0)
30 GtkType columns_get_type(void)
32 static GtkType columns_type = 0;
35 static const GtkTypeInfo columns_info = {
39 (GtkClassInitFunc) columns_class_init,
40 (GtkObjectInitFunc) columns_init,
41 /* reserved_1 */ NULL,
42 /* reserved_2 */ NULL,
43 (GtkClassInitFunc) NULL,
46 columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
52 GType columns_get_type(void)
54 static GType columns_type = 0;
57 static const GTypeInfo columns_info = {
61 (GClassInitFunc) columns_class_init,
66 (GInstanceInitFunc)columns_init,
69 columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
77 #if !GTK_CHECK_VERSION(2,0,0)
78 static gint (*columns_inherited_focus)(GtkContainer *container,
79 GtkDirectionType direction);
82 static void columns_class_init(ColumnsClass *klass)
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;
89 /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */
90 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
91 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
94 #if !GTK_CHECK_VERSION(2,0,0)
95 parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
97 parent_class = g_type_class_peek_parent(klass);
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;
106 widget_class->size_request = columns_size_request;
107 widget_class->size_allocate = columns_size_allocate;
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;
121 static void columns_init(Columns *cols)
123 GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
125 cols->children = NULL;
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...
134 static void columns_map(GtkWidget *widget)
140 g_return_if_fail(widget != NULL);
141 g_return_if_fail(IS_COLUMNS(widget));
143 cols = COLUMNS(widget);
144 GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
146 for (children = cols->children;
147 children && (child = children->data);
148 children = children->next) {
150 GTK_WIDGET_VISIBLE(child->widget) &&
151 !GTK_WIDGET_MAPPED(child->widget))
152 gtk_widget_map(child->widget);
155 static void columns_unmap(GtkWidget *widget)
161 g_return_if_fail(widget != NULL);
162 g_return_if_fail(IS_COLUMNS(widget));
164 cols = COLUMNS(widget);
165 GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
167 for (children = cols->children;
168 children && (child = children->data);
169 children = children->next) {
171 GTK_WIDGET_VISIBLE(child->widget) &&
172 GTK_WIDGET_MAPPED(child->widget))
173 gtk_widget_unmap(child->widget);
176 #if !GTK_CHECK_VERSION(2,0,0)
177 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
182 GdkRectangle child_area;
184 g_return_if_fail(widget != NULL);
185 g_return_if_fail(IS_COLUMNS(widget));
187 if (GTK_WIDGET_DRAWABLE(widget)) {
188 cols = COLUMNS(widget);
190 for (children = cols->children;
191 children && (child = children->data);
192 children = children->next) {
194 GTK_WIDGET_DRAWABLE(child->widget) &&
195 gtk_widget_intersect(child->widget, area, &child_area))
196 gtk_widget_draw(child->widget, &child_area);
200 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
205 GdkEventExpose child_event;
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);
211 if (GTK_WIDGET_DRAWABLE(widget)) {
212 cols = COLUMNS(widget);
213 child_event = *event;
215 for (children = cols->children;
216 children && (child = children->data);
217 children = children->next) {
219 GTK_WIDGET_DRAWABLE(child->widget) &&
220 GTK_WIDGET_NO_WINDOW(child->widget) &&
221 gtk_widget_intersect(child->widget, &event->area,
223 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
230 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
234 g_return_if_fail(container != NULL);
235 g_return_if_fail(IS_COLUMNS(container));
236 g_return_if_fail(widget != NULL);
238 cols = COLUMNS(container);
241 * Default is to add a new widget spanning all columns.
243 columns_add(cols, widget, 0, 0); /* 0 means ncols */
246 static void columns_remove(GtkContainer *container, GtkWidget *widget)
252 gboolean was_visible;
254 g_return_if_fail(container != NULL);
255 g_return_if_fail(IS_COLUMNS(container));
256 g_return_if_fail(widget != NULL);
258 cols = COLUMNS(container);
260 for (children = cols->children;
261 children && (child = children->data);
262 children = children->next) {
263 if (child->widget != widget)
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);
272 gtk_widget_queue_resize(GTK_WIDGET(container));
276 for (children = cols->taborder;
277 children && (childw = children->data);
278 children = children->next) {
279 if (childw != widget)
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);
291 static void columns_forall(GtkContainer *container, gboolean include_internals,
292 GtkCallback callback, gpointer callback_data)
296 GList *children, *next;
298 g_return_if_fail(container != NULL);
299 g_return_if_fail(IS_COLUMNS(container));
300 g_return_if_fail(callback != NULL);
302 cols = COLUMNS(container);
304 for (children = cols->children;
305 children && (child = children->data);
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
315 next = children->next;
317 callback(child->widget, callback_data);
321 static GtkType columns_child_type(GtkContainer *container)
323 return GTK_TYPE_WIDGET;
326 GtkWidget *columns_new(gint spacing)
330 #if !GTK_CHECK_VERSION(2,0,0)
331 cols = gtk_type_new(columns_get_type());
333 cols = g_object_new(TYPE_COLUMNS, NULL);
336 cols->spacing = spacing;
338 return GTK_WIDGET(cols);
341 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
343 ColumnsChild *childdata;
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);
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];
359 cols->children = g_list_append(cols->children, childdata);
362 void columns_add(Columns *cols, GtkWidget *child,
363 gint colstart, gint colspan)
365 ColumnsChild *childdata;
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);
372 childdata = g_new(ColumnsChild, 1);
373 childdata->widget = child;
374 childdata->colstart = colstart;
375 childdata->colspan = colspan;
376 childdata->force_left = FALSE;
378 cols->children = g_list_append(cols->children, childdata);
379 cols->taborder = g_list_append(cols->taborder, child);
381 gtk_widget_set_parent(child, GTK_WIDGET(cols));
383 #if GTK_CHECK_VERSION(2,0,0)
384 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
387 if (GTK_WIDGET_REALIZED(cols))
388 gtk_widget_realize(child);
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);
397 void columns_force_left_align(Columns *cols, GtkWidget *widget)
402 g_return_if_fail(cols != NULL);
403 g_return_if_fail(IS_COLUMNS(cols));
404 g_return_if_fail(widget != NULL);
406 for (children = cols->children;
407 children && (child = children->data);
408 children = children->next) {
409 if (child->widget != widget)
412 child->force_left = TRUE;
413 if (GTK_WIDGET_VISIBLE(widget))
414 gtk_widget_queue_resize(GTK_WIDGET(cols));
419 void columns_taborder_last(Columns *cols, GtkWidget *widget)
424 g_return_if_fail(cols != NULL);
425 g_return_if_fail(IS_COLUMNS(cols));
426 g_return_if_fail(widget != NULL);
428 for (children = cols->taborder;
429 children && (childw = children->data);
430 children = children->next) {
431 if (childw != widget)
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);
444 #if !GTK_CHECK_VERSION(2,0,0)
446 * Override GtkContainer's focus movement so the user can
447 * explicitly specify the tab order.
449 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
453 GtkWidget *focuschild;
455 g_return_val_if_fail(container != NULL, FALSE);
456 g_return_val_if_fail(IS_COLUMNS(container), FALSE);
458 cols = COLUMNS(container);
460 if (!GTK_WIDGET_DRAWABLE(cols) ||
461 !GTK_WIDGET_IS_SENSITIVE(cols))
464 if (!GTK_WIDGET_CAN_FOCUS(container) &&
465 (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
467 focuschild = container->focus_child;
468 gtk_container_set_focus_child(container, NULL);
470 if (dir == GTK_DIR_TAB_FORWARD)
471 pos = cols->taborder;
473 pos = g_list_last(cols->taborder);
476 GtkWidget *child = pos->data;
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))
488 } else if (GTK_WIDGET_DRAWABLE(child)) {
489 if (GTK_IS_CONTAINER(child)) {
490 if (gtk_container_focus(GTK_CONTAINER(child), dir))
492 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
493 gtk_widget_grab_focus(child);
498 if (dir == GTK_DIR_TAB_FORWARD)
506 return columns_inherited_focus(container, dir);
511 * Now here comes the interesting bit. The actual layout part is
512 * done in the following two functions:
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.
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
525 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
530 gint i, ncols, colspan, *colypos;
531 const gint *percentages;
532 static const gint onecol[] = { 100 };
534 g_return_if_fail(widget != NULL);
535 g_return_if_fail(IS_COLUMNS(widget));
536 g_return_if_fail(req != NULL);
538 cols = COLUMNS(widget);
541 req->height = cols->spacing;
544 colypos = g_new(gint, 1);
546 percentages = onecol;
548 for (children = cols->children;
549 children && (child = children->data);
550 children = children->next) {
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];
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];
567 /* Only take visible widgets into account. */
568 if (!GTK_WIDGET_VISIBLE(child->widget))
571 gtk_widget_size_request(child->widget, &creq);
572 colspan = child->colspan ? child->colspan : ncols-child->colstart;
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
584 int percent, thiswid, fullwid;
587 for (i = 0; i < colspan; i++)
588 percent += percentages[child->colstart+i];
590 thiswid = creq.width + cols->spacing;
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.
598 fullwid = (thiswid * 100 + percent - 1) / percent;
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.
606 if (req->width < fullwid - cols->spacing)
607 req->width = fullwid - cols->spacing;
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
623 for (i = 0; i < colspan; i++) {
624 if (topy < colypos[child->colstart+i])
625 topy = colypos[child->colstart+i];
627 boty = topy + creq.height + cols->spacing;
628 for (i = 0; i < colspan; i++) {
629 colypos[child->colstart+i] = boty;
632 if (req->height < boty - cols->spacing)
633 req->height = boty - cols->spacing;
637 req->width += 2*GTK_CONTAINER(cols)->border_width;
638 req->height += 2*GTK_CONTAINER(cols)->border_width;
643 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
648 gint i, ncols, colspan, border, *colxpos, *colypos;
649 const gint *percentages;
650 static const gint onecol[] = { 100 };
652 g_return_if_fail(widget != NULL);
653 g_return_if_fail(IS_COLUMNS(widget));
654 g_return_if_fail(alloc != NULL);
656 cols = COLUMNS(widget);
657 widget->allocation = *alloc;
658 border = GTK_CONTAINER(cols)->border_width;
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);
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);
673 for (children = cols->children;
674 children && (child = children->data);
675 children = children->next) {
679 if (!child->widget) {
682 /* Column reconfiguration. */
683 for (i = 1; i < ncols; i++) {
684 if (colypos[0] < colypos[i])
685 colypos[0] = colypos[i];
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);
695 for (i = 0; i < ncols; i++) {
696 percent += percentages[i];
697 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
703 /* Only take visible widgets into account. */
704 if (!GTK_WIDGET_VISIBLE(child->widget))
707 gtk_widget_get_child_requisition(child->widget, &creq);
708 colspan = child->colspan ? child->colspan : ncols-child->colstart;
711 * Starting x position is cols[colstart].
712 * Ending x position is cols[colstart+colspan] - spacing.
714 * Unless we're forcing left, in which case the width is
715 * exactly the requisition width.
717 call.x = alloc->x + border + colxpos[child->colstart];
718 if (child->force_left)
719 call.width = creq.width;
721 call.width = (colxpos[child->colstart+colspan] -
722 colxpos[child->colstart] - cols->spacing);
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.
735 for (i = 0; i < colspan; i++) {
736 if (topy < colypos[child->colstart+i])
737 topy = colypos[child->colstart+i];
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;
747 gtk_widget_size_allocate(child->widget, &call);