2 * gtkcols.c - implementation of the `Columns' GTK layout container.
9 static void columns_init(Columns *cols);
10 static void columns_class_init(ColumnsClass *klass);
11 static void columns_map(GtkWidget *widget);
12 static void columns_unmap(GtkWidget *widget);
13 #if !GTK_CHECK_VERSION(2,0,0)
14 static void columns_draw(GtkWidget *widget, GdkRectangle *area);
15 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
17 static void columns_base_add(GtkContainer *container, GtkWidget *widget);
18 static void columns_remove(GtkContainer *container, GtkWidget *widget);
19 static void columns_forall(GtkContainer *container, gboolean include_internals,
20 GtkCallback callback, gpointer callback_data);
21 #if !GTK_CHECK_VERSION(2,0,0)
22 static gint columns_focus(GtkContainer *container, GtkDirectionType dir);
24 static GType columns_child_type(GtkContainer *container);
25 #if GTK_CHECK_VERSION(3,0,0)
26 static void columns_get_preferred_width(GtkWidget *widget,
27 gint *min, gint *nat);
28 static void columns_get_preferred_height(GtkWidget *widget,
29 gint *min, gint *nat);
31 static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
32 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
34 static GtkContainerClass *parent_class = NULL;
36 #if !GTK_CHECK_VERSION(2,0,0)
37 GType columns_get_type(void)
39 static GType columns_type = 0;
42 static const GtkTypeInfo columns_info = {
46 (GtkClassInitFunc) columns_class_init,
47 (GtkObjectInitFunc) columns_init,
48 /* reserved_1 */ NULL,
49 /* reserved_2 */ NULL,
50 (GtkClassInitFunc) NULL,
53 columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
59 GType columns_get_type(void)
61 static GType columns_type = 0;
64 static const GTypeInfo columns_info = {
68 (GClassInitFunc) columns_class_init,
73 (GInstanceInitFunc)columns_init,
76 columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
84 #if !GTK_CHECK_VERSION(2,0,0)
85 static gint (*columns_inherited_focus)(GtkContainer *container,
86 GtkDirectionType direction);
89 static void columns_class_init(ColumnsClass *klass)
91 #if !GTK_CHECK_VERSION(2,0,0)
92 /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */
93 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
94 GtkContainerClass *container_class = (GtkContainerClass *)klass;
96 /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */
97 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
98 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
101 #if !GTK_CHECK_VERSION(2,0,0)
102 parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
104 parent_class = g_type_class_peek_parent(klass);
107 widget_class->map = columns_map;
108 widget_class->unmap = columns_unmap;
109 #if !GTK_CHECK_VERSION(2,0,0)
110 widget_class->draw = columns_draw;
111 widget_class->expose_event = columns_expose;
113 #if GTK_CHECK_VERSION(3,0,0)
114 widget_class->get_preferred_width = columns_get_preferred_width;
115 widget_class->get_preferred_height = columns_get_preferred_height;
117 widget_class->size_request = columns_size_request;
119 widget_class->size_allocate = columns_size_allocate;
121 container_class->add = columns_base_add;
122 container_class->remove = columns_remove;
123 container_class->forall = columns_forall;
124 container_class->child_type = columns_child_type;
125 #if !GTK_CHECK_VERSION(2,0,0)
126 /* Save the previous value of this method. */
127 if (!columns_inherited_focus)
128 columns_inherited_focus = container_class->focus;
129 container_class->focus = columns_focus;
133 static void columns_init(Columns *cols)
135 gtk_widget_set_has_window(GTK_WIDGET(cols), FALSE);
137 cols->children = NULL;
142 * These appear to be thoroughly tedious functions; the only reason
143 * we have to reimplement them at all is because we defined our own
144 * format for our GList of children...
146 static void columns_map(GtkWidget *widget)
152 g_return_if_fail(widget != NULL);
153 g_return_if_fail(IS_COLUMNS(widget));
155 cols = COLUMNS(widget);
156 gtk_widget_set_mapped(GTK_WIDGET(cols), TRUE);
158 for (children = cols->children;
159 children && (child = children->data);
160 children = children->next) {
162 gtk_widget_get_visible(child->widget) &&
163 !gtk_widget_get_mapped(child->widget))
164 gtk_widget_map(child->widget);
167 static void columns_unmap(GtkWidget *widget)
173 g_return_if_fail(widget != NULL);
174 g_return_if_fail(IS_COLUMNS(widget));
176 cols = COLUMNS(widget);
177 gtk_widget_set_mapped(GTK_WIDGET(cols), FALSE);
179 for (children = cols->children;
180 children && (child = children->data);
181 children = children->next) {
183 gtk_widget_get_visible(child->widget) &&
184 gtk_widget_get_mapped(child->widget))
185 gtk_widget_unmap(child->widget);
188 #if !GTK_CHECK_VERSION(2,0,0)
189 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
194 GdkRectangle child_area;
196 g_return_if_fail(widget != NULL);
197 g_return_if_fail(IS_COLUMNS(widget));
199 if (GTK_WIDGET_DRAWABLE(widget)) {
200 cols = COLUMNS(widget);
202 for (children = cols->children;
203 children && (child = children->data);
204 children = children->next) {
206 GTK_WIDGET_DRAWABLE(child->widget) &&
207 gtk_widget_intersect(child->widget, area, &child_area))
208 gtk_widget_draw(child->widget, &child_area);
212 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
217 GdkEventExpose child_event;
219 g_return_val_if_fail(widget != NULL, FALSE);
220 g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
221 g_return_val_if_fail(event != NULL, FALSE);
223 if (GTK_WIDGET_DRAWABLE(widget)) {
224 cols = COLUMNS(widget);
225 child_event = *event;
227 for (children = cols->children;
228 children && (child = children->data);
229 children = children->next) {
231 GTK_WIDGET_DRAWABLE(child->widget) &&
232 GTK_WIDGET_NO_WINDOW(child->widget) &&
233 gtk_widget_intersect(child->widget, &event->area,
235 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
242 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
246 g_return_if_fail(container != NULL);
247 g_return_if_fail(IS_COLUMNS(container));
248 g_return_if_fail(widget != NULL);
250 cols = COLUMNS(container);
253 * Default is to add a new widget spanning all columns.
255 columns_add(cols, widget, 0, 0); /* 0 means ncols */
258 static void columns_remove(GtkContainer *container, GtkWidget *widget)
264 gboolean was_visible;
266 g_return_if_fail(container != NULL);
267 g_return_if_fail(IS_COLUMNS(container));
268 g_return_if_fail(widget != NULL);
270 cols = COLUMNS(container);
272 for (children = cols->children;
273 children && (child = children->data);
274 children = children->next) {
275 if (child->widget != widget)
278 was_visible = gtk_widget_get_visible(widget);
279 gtk_widget_unparent(widget);
280 cols->children = g_list_remove_link(cols->children, children);
281 g_list_free(children);
284 gtk_widget_queue_resize(GTK_WIDGET(container));
288 for (children = cols->taborder;
289 children && (childw = children->data);
290 children = children->next) {
291 if (childw != widget)
294 cols->taborder = g_list_remove_link(cols->taborder, children);
295 g_list_free(children);
296 #if GTK_CHECK_VERSION(2,0,0)
297 gtk_container_set_focus_chain(container, cols->taborder);
303 static void columns_forall(GtkContainer *container, gboolean include_internals,
304 GtkCallback callback, gpointer callback_data)
308 GList *children, *next;
310 g_return_if_fail(container != NULL);
311 g_return_if_fail(IS_COLUMNS(container));
312 g_return_if_fail(callback != NULL);
314 cols = COLUMNS(container);
316 for (children = cols->children;
317 children && (child = children->data);
320 * We can't wait until after the callback to assign
321 * `children = children->next', because the callback might
322 * be gtk_widget_destroy, which would remove the link
323 * `children' from the list! So instead we must get our
324 * hands on the value of the `next' pointer _before_ the
327 next = children->next;
329 callback(child->widget, callback_data);
333 static GType columns_child_type(GtkContainer *container)
335 return GTK_TYPE_WIDGET;
338 GtkWidget *columns_new(gint spacing)
342 #if !GTK_CHECK_VERSION(2,0,0)
343 cols = gtk_type_new(columns_get_type());
345 cols = g_object_new(TYPE_COLUMNS, NULL);
348 cols->spacing = spacing;
350 return GTK_WIDGET(cols);
353 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
355 ColumnsChild *childdata;
358 g_return_if_fail(cols != NULL);
359 g_return_if_fail(IS_COLUMNS(cols));
360 g_return_if_fail(ncols > 0);
361 g_return_if_fail(percentages != NULL);
363 childdata = g_new(ColumnsChild, 1);
364 childdata->widget = NULL;
365 childdata->ncols = ncols;
366 childdata->percentages = g_new(gint, ncols);
367 childdata->force_left = FALSE;
368 for (i = 0; i < ncols; i++)
369 childdata->percentages[i] = percentages[i];
371 cols->children = g_list_append(cols->children, childdata);
374 void columns_add(Columns *cols, GtkWidget *child,
375 gint colstart, gint colspan)
377 ColumnsChild *childdata;
379 g_return_if_fail(cols != NULL);
380 g_return_if_fail(IS_COLUMNS(cols));
381 g_return_if_fail(child != NULL);
382 g_return_if_fail(gtk_widget_get_parent(child) == NULL);
384 childdata = g_new(ColumnsChild, 1);
385 childdata->widget = child;
386 childdata->colstart = colstart;
387 childdata->colspan = colspan;
388 childdata->force_left = FALSE;
390 cols->children = g_list_append(cols->children, childdata);
391 cols->taborder = g_list_append(cols->taborder, child);
393 gtk_widget_set_parent(child, GTK_WIDGET(cols));
395 #if GTK_CHECK_VERSION(2,0,0)
396 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
399 if (gtk_widget_get_realized(GTK_WIDGET(cols)))
400 gtk_widget_realize(child);
402 if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
403 gtk_widget_get_visible(child)) {
404 if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
405 gtk_widget_map(child);
406 gtk_widget_queue_resize(child);
410 void columns_force_left_align(Columns *cols, GtkWidget *widget)
415 g_return_if_fail(cols != NULL);
416 g_return_if_fail(IS_COLUMNS(cols));
417 g_return_if_fail(widget != NULL);
419 for (children = cols->children;
420 children && (child = children->data);
421 children = children->next) {
422 if (child->widget != widget)
425 child->force_left = TRUE;
426 if (gtk_widget_get_visible(widget))
427 gtk_widget_queue_resize(GTK_WIDGET(cols));
432 void columns_taborder_last(Columns *cols, GtkWidget *widget)
437 g_return_if_fail(cols != NULL);
438 g_return_if_fail(IS_COLUMNS(cols));
439 g_return_if_fail(widget != NULL);
441 for (children = cols->taborder;
442 children && (childw = children->data);
443 children = children->next) {
444 if (childw != widget)
447 cols->taborder = g_list_remove_link(cols->taborder, children);
448 g_list_free(children);
449 cols->taborder = g_list_append(cols->taborder, widget);
450 #if GTK_CHECK_VERSION(2,0,0)
451 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
457 #if !GTK_CHECK_VERSION(2,0,0)
459 * Override GtkContainer's focus movement so the user can
460 * explicitly specify the tab order.
462 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
466 GtkWidget *focuschild;
468 g_return_val_if_fail(container != NULL, FALSE);
469 g_return_val_if_fail(IS_COLUMNS(container), FALSE);
471 cols = COLUMNS(container);
473 if (!GTK_WIDGET_DRAWABLE(cols) ||
474 !GTK_WIDGET_IS_SENSITIVE(cols))
477 if (!GTK_WIDGET_CAN_FOCUS(container) &&
478 (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
480 focuschild = container->focus_child;
481 gtk_container_set_focus_child(container, NULL);
483 if (dir == GTK_DIR_TAB_FORWARD)
484 pos = cols->taborder;
486 pos = g_list_last(cols->taborder);
489 GtkWidget *child = pos->data;
492 if (focuschild == child) {
493 focuschild = NULL; /* now we can start looking in here */
494 if (GTK_WIDGET_DRAWABLE(child) &&
495 GTK_IS_CONTAINER(child) &&
496 !GTK_WIDGET_HAS_FOCUS(child)) {
497 if (gtk_container_focus(GTK_CONTAINER(child), dir))
501 } else if (GTK_WIDGET_DRAWABLE(child)) {
502 if (GTK_IS_CONTAINER(child)) {
503 if (gtk_container_focus(GTK_CONTAINER(child), dir))
505 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
506 gtk_widget_grab_focus(child);
511 if (dir == GTK_DIR_TAB_FORWARD)
519 return columns_inherited_focus(container, dir);
524 * Underlying parts of the layout algorithm, to compute the Columns
525 * container's width or height given the widths or heights of its
526 * children. These will be called in various ways with different
527 * notions of width and height in use, so we abstract them out and
528 * pass them a 'get width' or 'get height' function pointer.
531 typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
533 static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
537 gint i, ncols, colspan, retwidth, childwidth;
538 const gint *percentages;
539 static const gint onecol[] = { 100 };
544 percentages = onecol;
546 for (children = cols->children;
547 children && (child = children->data);
548 children = children->next) {
550 if (!child->widget) {
551 /* Column reconfiguration. */
552 ncols = child->ncols;
553 percentages = child->percentages;
557 /* Only take visible widgets into account. */
558 if (!gtk_widget_get_visible(child->widget))
561 childwidth = get_width(child);
562 colspan = child->colspan ? child->colspan : ncols-child->colstart;
565 * To compute width: we know that childwidth + cols->spacing
566 * needs to equal a certain percentage of the full width of
567 * the container. So we work this value out, figure out how
568 * wide the container will need to be to make that percentage
569 * of it equal to that width, and ensure our returned width is
570 * at least that much. Very simple really.
573 int percent, thiswid, fullwid;
576 for (i = 0; i < colspan; i++)
577 percent += percentages[child->colstart+i];
579 thiswid = childwidth + cols->spacing;
581 * Since childwidth is (at least sometimes) the _minimum_
582 * size the child needs, we must ensure that it gets _at
583 * least_ that size. Hence, when scaling thiswid up to
584 * fullwid, we must round up, which means adding percent-1
585 * before dividing by percent.
587 fullwid = (thiswid * 100 + percent - 1) / percent;
590 * The above calculation assumes every widget gets
591 * cols->spacing on the right. So we subtract
592 * cols->spacing here to account for the extra load of
593 * spacing on the right.
595 if (retwidth < fullwid - cols->spacing)
596 retwidth = fullwid - cols->spacing;
600 retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
605 static void columns_alloc_horiz(Columns *cols, gint ourwidth,
606 widget_dim_fn_t get_width)
610 gint i, ncols, colspan, border, *colxpos, childwidth;
611 const gint *percentages;
612 static const gint onecol[] = { 100 };
614 border = gtk_container_get_border_width(GTK_CONTAINER(cols));
617 percentages = onecol;
618 /* colxpos gives the starting x position of each column.
619 * We supply n+1 of them, so that we can find the RH edge easily.
620 * All ending x positions are expected to be adjusted afterwards by
621 * subtracting the spacing. */
622 colxpos = g_new(gint, 2);
624 colxpos[1] = ourwidth - 2*border + cols->spacing;
626 for (children = cols->children;
627 children && (child = children->data);
628 children = children->next) {
630 if (!child->widget) {
633 /* Column reconfiguration. */
634 ncols = child->ncols;
635 percentages = child->percentages;
636 colxpos = g_renew(gint, colxpos, ncols + 1);
639 for (i = 0; i < ncols; i++) {
640 percent += percentages[i];
641 colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
647 /* Only take visible widgets into account. */
648 if (!gtk_widget_get_visible(child->widget))
651 childwidth = get_width(child);
652 colspan = child->colspan ? child->colspan : ncols-child->colstart;
655 * Starting x position is cols[colstart].
656 * Ending x position is cols[colstart+colspan] - spacing.
658 * Unless we're forcing left, in which case the width is
659 * exactly the requisition width.
661 child->x = colxpos[child->colstart];
662 if (child->force_left)
663 child->w = childwidth;
665 child->w = (colxpos[child->colstart+colspan] -
666 colxpos[child->colstart] - cols->spacing);
672 static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
676 gint i, ncols, colspan, *colypos, retheight, childheight;
678 retheight = cols->spacing;
681 colypos = g_new(gint, 1);
684 for (children = cols->children;
685 children && (child = children->data);
686 children = children->next) {
688 if (!child->widget) {
689 /* Column reconfiguration. */
690 for (i = 1; i < ncols; i++) {
691 if (colypos[0] < colypos[i])
692 colypos[0] = colypos[i];
694 ncols = child->ncols;
695 colypos = g_renew(gint, colypos, ncols);
696 for (i = 1; i < ncols; i++)
697 colypos[i] = colypos[0];
701 /* Only take visible widgets into account. */
702 if (!gtk_widget_get_visible(child->widget))
705 childheight = get_height(child);
706 colspan = child->colspan ? child->colspan : ncols-child->colstart;
709 * To compute height: the widget's top will be positioned at
710 * the largest y value so far reached in any of the columns it
711 * crosses. Then it will go down by childheight plus padding;
712 * and the point it reaches at the bottom is the new y value
713 * in all those columns, and minus the padding it is also a
714 * lower bound on our own height.
720 for (i = 0; i < colspan; i++) {
721 if (topy < colypos[child->colstart+i])
722 topy = colypos[child->colstart+i];
724 boty = topy + childheight + cols->spacing;
725 for (i = 0; i < colspan; i++) {
726 colypos[child->colstart+i] = boty;
729 if (retheight < boty - cols->spacing)
730 retheight = boty - cols->spacing;
734 retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
741 static void columns_alloc_vert(Columns *cols, gint ourheight,
742 widget_dim_fn_t get_height)
746 gint i, ncols, colspan, *colypos, childheight;
749 /* As in size_request, colypos is the lowest y reached in each column. */
750 colypos = g_new(gint, 1);
753 for (children = cols->children;
754 children && (child = children->data);
755 children = children->next) {
756 if (!child->widget) {
757 /* Column reconfiguration. */
758 for (i = 1; i < ncols; i++) {
759 if (colypos[0] < colypos[i])
760 colypos[0] = colypos[i];
762 ncols = child->ncols;
763 colypos = g_renew(gint, colypos, ncols);
764 for (i = 1; i < ncols; i++)
765 colypos[i] = colypos[0];
769 /* Only take visible widgets into account. */
770 if (!gtk_widget_get_visible(child->widget))
773 childheight = get_height(child);
774 colspan = child->colspan ? child->colspan : ncols-child->colstart;
777 * To compute height: the widget's top will be positioned
778 * at the largest y value so far reached in any of the
779 * columns it crosses. Then it will go down by creq.height
780 * plus padding; and the point it reaches at the bottom is
781 * the new y value in all those columns.
787 for (i = 0; i < colspan; i++) {
788 if (topy < colypos[child->colstart+i])
789 topy = colypos[child->colstart+i];
792 child->h = childheight;
793 boty = topy + childheight + cols->spacing;
794 for (i = 0; i < colspan; i++) {
795 colypos[child->colstart+i] = boty;
804 * Now here comes the interesting bit. The actual layout part is
805 * done in the following two functions:
807 * columns_size_request() examines the list of widgets held in the
808 * Columns, and returns a requisition stating the absolute minimum
809 * size it can bear to be.
811 * columns_size_allocate() is given an allocation telling it what
812 * size the whole container is going to be, and it calls
813 * gtk_widget_size_allocate() on all of its (visible) children to
814 * set their size and position relative to the top left of the
818 static gint columns_gtk2_get_width(ColumnsChild *child)
821 gtk_widget_size_request(child->widget, &creq);
825 static gint columns_gtk2_get_height(ColumnsChild *child)
828 gtk_widget_size_request(child->widget, &creq);
832 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
836 g_return_if_fail(widget != NULL);
837 g_return_if_fail(IS_COLUMNS(widget));
838 g_return_if_fail(req != NULL);
840 cols = COLUMNS(widget);
842 req->width = columns_compute_width(cols, columns_gtk2_get_width);
843 req->height = columns_compute_height(cols, columns_gtk2_get_height);
846 #if GTK_CHECK_VERSION(3,0,0)
847 static void columns_get_preferred_width(GtkWidget *widget,
848 gint *min, gint *nat)
851 columns_size_request(widget, &req);
852 *min = *nat = req.width;
855 static void columns_get_preferred_height(GtkWidget *widget,
856 gint *min, gint *nat)
859 columns_size_request(widget, &req);
860 *min = *nat = req.height;
864 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
871 g_return_if_fail(widget != NULL);
872 g_return_if_fail(IS_COLUMNS(widget));
873 g_return_if_fail(alloc != NULL);
875 cols = COLUMNS(widget);
876 gtk_widget_set_allocation(widget, alloc);
878 border = gtk_container_get_border_width(GTK_CONTAINER(cols));
880 columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
881 columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
883 for (children = cols->children;
884 children && (child = children->data);
885 children = children->next) {
886 if (child->widget && gtk_widget_get_visible(child->widget)) {
888 call.x = alloc->x + border + child->x;
889 call.y = alloc->y + border + child->y;
890 call.width = child->w;
891 call.height = child->h;
892 gtk_widget_size_allocate(child->widget, &call);