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 GtkType columns_child_type(GtkContainer *container);
25 static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
26 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
28 static GtkContainerClass *parent_class = NULL;
30 #if !GTK_CHECK_VERSION(2,0,0)
31 GtkType columns_get_type(void)
33 static GtkType columns_type = 0;
36 static const GtkTypeInfo columns_info = {
40 (GtkClassInitFunc) columns_class_init,
41 (GtkObjectInitFunc) columns_init,
42 /* reserved_1 */ NULL,
43 /* reserved_2 */ NULL,
44 (GtkClassInitFunc) NULL,
47 columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
53 GType columns_get_type(void)
55 static GType columns_type = 0;
58 static const GTypeInfo columns_info = {
62 (GClassInitFunc) columns_class_init,
67 (GInstanceInitFunc)columns_init,
70 columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
78 #if !GTK_CHECK_VERSION(2,0,0)
79 static gint (*columns_inherited_focus)(GtkContainer *container,
80 GtkDirectionType direction);
83 static void columns_class_init(ColumnsClass *klass)
85 #if !GTK_CHECK_VERSION(2,0,0)
86 /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */
87 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
88 GtkContainerClass *container_class = (GtkContainerClass *)klass;
90 /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */
91 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
92 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
95 #if !GTK_CHECK_VERSION(2,0,0)
96 parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
98 parent_class = g_type_class_peek_parent(klass);
101 widget_class->map = columns_map;
102 widget_class->unmap = columns_unmap;
103 #if !GTK_CHECK_VERSION(2,0,0)
104 widget_class->draw = columns_draw;
105 widget_class->expose_event = columns_expose;
107 widget_class->size_request = columns_size_request;
108 widget_class->size_allocate = columns_size_allocate;
110 container_class->add = columns_base_add;
111 container_class->remove = columns_remove;
112 container_class->forall = columns_forall;
113 container_class->child_type = columns_child_type;
114 #if !GTK_CHECK_VERSION(2,0,0)
115 /* Save the previous value of this method. */
116 if (!columns_inherited_focus)
117 columns_inherited_focus = container_class->focus;
118 container_class->focus = columns_focus;
122 static void columns_init(Columns *cols)
124 gtk_widget_set_has_window(GTK_WIDGET(cols), FALSE);
126 cols->children = NULL;
131 * These appear to be thoroughly tedious functions; the only reason
132 * we have to reimplement them at all is because we defined our own
133 * format for our GList of children...
135 static void columns_map(GtkWidget *widget)
141 g_return_if_fail(widget != NULL);
142 g_return_if_fail(IS_COLUMNS(widget));
144 cols = COLUMNS(widget);
145 gtk_widget_set_mapped(GTK_WIDGET(cols), TRUE);
147 for (children = cols->children;
148 children && (child = children->data);
149 children = children->next) {
151 gtk_widget_get_visible(child->widget) &&
152 !gtk_widget_get_mapped(child->widget))
153 gtk_widget_map(child->widget);
156 static void columns_unmap(GtkWidget *widget)
162 g_return_if_fail(widget != NULL);
163 g_return_if_fail(IS_COLUMNS(widget));
165 cols = COLUMNS(widget);
166 gtk_widget_set_mapped(GTK_WIDGET(cols), FALSE);
168 for (children = cols->children;
169 children && (child = children->data);
170 children = children->next) {
172 gtk_widget_get_visible(child->widget) &&
173 gtk_widget_get_mapped(child->widget))
174 gtk_widget_unmap(child->widget);
177 #if !GTK_CHECK_VERSION(2,0,0)
178 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
183 GdkRectangle child_area;
185 g_return_if_fail(widget != NULL);
186 g_return_if_fail(IS_COLUMNS(widget));
188 if (GTK_WIDGET_DRAWABLE(widget)) {
189 cols = COLUMNS(widget);
191 for (children = cols->children;
192 children && (child = children->data);
193 children = children->next) {
195 GTK_WIDGET_DRAWABLE(child->widget) &&
196 gtk_widget_intersect(child->widget, area, &child_area))
197 gtk_widget_draw(child->widget, &child_area);
201 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
206 GdkEventExpose child_event;
208 g_return_val_if_fail(widget != NULL, FALSE);
209 g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
210 g_return_val_if_fail(event != NULL, FALSE);
212 if (GTK_WIDGET_DRAWABLE(widget)) {
213 cols = COLUMNS(widget);
214 child_event = *event;
216 for (children = cols->children;
217 children && (child = children->data);
218 children = children->next) {
220 GTK_WIDGET_DRAWABLE(child->widget) &&
221 GTK_WIDGET_NO_WINDOW(child->widget) &&
222 gtk_widget_intersect(child->widget, &event->area,
224 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
231 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
235 g_return_if_fail(container != NULL);
236 g_return_if_fail(IS_COLUMNS(container));
237 g_return_if_fail(widget != NULL);
239 cols = COLUMNS(container);
242 * Default is to add a new widget spanning all columns.
244 columns_add(cols, widget, 0, 0); /* 0 means ncols */
247 static void columns_remove(GtkContainer *container, GtkWidget *widget)
253 gboolean was_visible;
255 g_return_if_fail(container != NULL);
256 g_return_if_fail(IS_COLUMNS(container));
257 g_return_if_fail(widget != NULL);
259 cols = COLUMNS(container);
261 for (children = cols->children;
262 children && (child = children->data);
263 children = children->next) {
264 if (child->widget != widget)
267 was_visible = gtk_widget_get_visible(widget);
268 gtk_widget_unparent(widget);
269 cols->children = g_list_remove_link(cols->children, children);
270 g_list_free(children);
273 gtk_widget_queue_resize(GTK_WIDGET(container));
277 for (children = cols->taborder;
278 children && (childw = children->data);
279 children = children->next) {
280 if (childw != widget)
283 cols->taborder = g_list_remove_link(cols->taborder, children);
284 g_list_free(children);
285 #if GTK_CHECK_VERSION(2,0,0)
286 gtk_container_set_focus_chain(container, cols->taborder);
292 static void columns_forall(GtkContainer *container, gboolean include_internals,
293 GtkCallback callback, gpointer callback_data)
297 GList *children, *next;
299 g_return_if_fail(container != NULL);
300 g_return_if_fail(IS_COLUMNS(container));
301 g_return_if_fail(callback != NULL);
303 cols = COLUMNS(container);
305 for (children = cols->children;
306 children && (child = children->data);
309 * We can't wait until after the callback to assign
310 * `children = children->next', because the callback might
311 * be gtk_widget_destroy, which would remove the link
312 * `children' from the list! So instead we must get our
313 * hands on the value of the `next' pointer _before_ the
316 next = children->next;
318 callback(child->widget, callback_data);
322 static GtkType columns_child_type(GtkContainer *container)
324 return GTK_TYPE_WIDGET;
327 GtkWidget *columns_new(gint spacing)
331 #if !GTK_CHECK_VERSION(2,0,0)
332 cols = gtk_type_new(columns_get_type());
334 cols = g_object_new(TYPE_COLUMNS, NULL);
337 cols->spacing = spacing;
339 return GTK_WIDGET(cols);
342 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
344 ColumnsChild *childdata;
347 g_return_if_fail(cols != NULL);
348 g_return_if_fail(IS_COLUMNS(cols));
349 g_return_if_fail(ncols > 0);
350 g_return_if_fail(percentages != NULL);
352 childdata = g_new(ColumnsChild, 1);
353 childdata->widget = NULL;
354 childdata->ncols = ncols;
355 childdata->percentages = g_new(gint, ncols);
356 childdata->force_left = FALSE;
357 for (i = 0; i < ncols; i++)
358 childdata->percentages[i] = percentages[i];
360 cols->children = g_list_append(cols->children, childdata);
363 void columns_add(Columns *cols, GtkWidget *child,
364 gint colstart, gint colspan)
366 ColumnsChild *childdata;
368 g_return_if_fail(cols != NULL);
369 g_return_if_fail(IS_COLUMNS(cols));
370 g_return_if_fail(child != NULL);
371 g_return_if_fail(gtk_widget_get_parent(child) == NULL);
373 childdata = g_new(ColumnsChild, 1);
374 childdata->widget = child;
375 childdata->colstart = colstart;
376 childdata->colspan = colspan;
377 childdata->force_left = FALSE;
379 cols->children = g_list_append(cols->children, childdata);
380 cols->taborder = g_list_append(cols->taborder, child);
382 gtk_widget_set_parent(child, GTK_WIDGET(cols));
384 #if GTK_CHECK_VERSION(2,0,0)
385 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
388 if (gtk_widget_get_realized(GTK_WIDGET(cols)))
389 gtk_widget_realize(child);
391 if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
392 gtk_widget_get_visible(child)) {
393 if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
394 gtk_widget_map(child);
395 gtk_widget_queue_resize(child);
399 void columns_force_left_align(Columns *cols, GtkWidget *widget)
404 g_return_if_fail(cols != NULL);
405 g_return_if_fail(IS_COLUMNS(cols));
406 g_return_if_fail(widget != NULL);
408 for (children = cols->children;
409 children && (child = children->data);
410 children = children->next) {
411 if (child->widget != widget)
414 child->force_left = TRUE;
415 if (gtk_widget_get_visible(widget))
416 gtk_widget_queue_resize(GTK_WIDGET(cols));
421 void columns_taborder_last(Columns *cols, GtkWidget *widget)
426 g_return_if_fail(cols != NULL);
427 g_return_if_fail(IS_COLUMNS(cols));
428 g_return_if_fail(widget != NULL);
430 for (children = cols->taborder;
431 children && (childw = children->data);
432 children = children->next) {
433 if (childw != widget)
436 cols->taborder = g_list_remove_link(cols->taborder, children);
437 g_list_free(children);
438 cols->taborder = g_list_append(cols->taborder, widget);
439 #if GTK_CHECK_VERSION(2,0,0)
440 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
446 #if !GTK_CHECK_VERSION(2,0,0)
448 * Override GtkContainer's focus movement so the user can
449 * explicitly specify the tab order.
451 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
455 GtkWidget *focuschild;
457 g_return_val_if_fail(container != NULL, FALSE);
458 g_return_val_if_fail(IS_COLUMNS(container), FALSE);
460 cols = COLUMNS(container);
462 if (!GTK_WIDGET_DRAWABLE(cols) ||
463 !GTK_WIDGET_IS_SENSITIVE(cols))
466 if (!GTK_WIDGET_CAN_FOCUS(container) &&
467 (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
469 focuschild = container->focus_child;
470 gtk_container_set_focus_child(container, NULL);
472 if (dir == GTK_DIR_TAB_FORWARD)
473 pos = cols->taborder;
475 pos = g_list_last(cols->taborder);
478 GtkWidget *child = pos->data;
481 if (focuschild == child) {
482 focuschild = NULL; /* now we can start looking in here */
483 if (GTK_WIDGET_DRAWABLE(child) &&
484 GTK_IS_CONTAINER(child) &&
485 !GTK_WIDGET_HAS_FOCUS(child)) {
486 if (gtk_container_focus(GTK_CONTAINER(child), dir))
490 } else if (GTK_WIDGET_DRAWABLE(child)) {
491 if (GTK_IS_CONTAINER(child)) {
492 if (gtk_container_focus(GTK_CONTAINER(child), dir))
494 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
495 gtk_widget_grab_focus(child);
500 if (dir == GTK_DIR_TAB_FORWARD)
508 return columns_inherited_focus(container, dir);
513 * Now here comes the interesting bit. The actual layout part is
514 * done in the following two functions:
516 * columns_size_request() examines the list of widgets held in the
517 * Columns, and returns a requisition stating the absolute minimum
518 * size it can bear to be.
520 * columns_size_allocate() is given an allocation telling it what
521 * size the whole container is going to be, and it calls
522 * gtk_widget_size_allocate() on all of its (visible) children to
523 * set their size and position relative to the top left of the
527 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
532 gint i, ncols, colspan, *colypos;
533 const gint *percentages;
534 static const gint onecol[] = { 100 };
536 g_return_if_fail(widget != NULL);
537 g_return_if_fail(IS_COLUMNS(widget));
538 g_return_if_fail(req != NULL);
540 cols = COLUMNS(widget);
543 req->height = cols->spacing;
546 colypos = g_new(gint, 1);
548 percentages = onecol;
550 for (children = cols->children;
551 children && (child = children->data);
552 children = children->next) {
555 if (!child->widget) {
556 /* Column reconfiguration. */
557 for (i = 1; i < ncols; i++) {
558 if (colypos[0] < colypos[i])
559 colypos[0] = colypos[i];
561 ncols = child->ncols;
562 percentages = child->percentages;
563 colypos = g_renew(gint, colypos, ncols);
564 for (i = 1; i < ncols; i++)
565 colypos[i] = colypos[0];
569 /* Only take visible widgets into account. */
570 if (!gtk_widget_get_visible(child->widget))
573 gtk_widget_size_request(child->widget, &creq);
574 colspan = child->colspan ? child->colspan : ncols-child->colstart;
577 * To compute width: we know that creq.width plus
578 * cols->spacing needs to equal a certain percentage of the
579 * full width of the container. So we work this value out,
580 * figure out how wide the container will need to be to
581 * make that percentage of it equal to that width, and
582 * ensure our returned width is at least that much. Very
586 int percent, thiswid, fullwid;
589 for (i = 0; i < colspan; i++)
590 percent += percentages[child->colstart+i];
592 thiswid = creq.width + cols->spacing;
594 * Since creq is the _minimum_ size the child needs, we
595 * must ensure that it gets _at least_ that size.
596 * Hence, when scaling thiswid up to fullwid, we must
597 * round up, which means adding percent-1 before
598 * dividing by percent.
600 fullwid = (thiswid * 100 + percent - 1) / percent;
603 * The above calculation assumes every widget gets
604 * cols->spacing on the right. So we subtract
605 * cols->spacing here to account for the extra load of
606 * spacing on the right.
608 if (req->width < fullwid - cols->spacing)
609 req->width = fullwid - cols->spacing;
613 * To compute height: the widget's top will be positioned
614 * at the largest y value so far reached in any of the
615 * columns it crosses. Then it will go down by creq.height
616 * plus padding; and the point it reaches at the bottom is
617 * the new y value in all those columns, and minus the
618 * padding it is also a lower bound on our own size
625 for (i = 0; i < colspan; i++) {
626 if (topy < colypos[child->colstart+i])
627 topy = colypos[child->colstart+i];
629 boty = topy + creq.height + cols->spacing;
630 for (i = 0; i < colspan; i++) {
631 colypos[child->colstart+i] = boty;
634 if (req->height < boty - cols->spacing)
635 req->height = boty - cols->spacing;
639 req->width += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
640 req->height += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
645 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
650 gint i, ncols, colspan, border, *colxpos, *colypos;
651 const gint *percentages;
652 static const gint onecol[] = { 100 };
654 g_return_if_fail(widget != NULL);
655 g_return_if_fail(IS_COLUMNS(widget));
656 g_return_if_fail(alloc != NULL);
658 cols = COLUMNS(widget);
659 gtk_widget_set_allocation(widget, alloc);
660 border = gtk_container_get_border_width(GTK_CONTAINER(cols));
663 percentages = onecol;
664 /* colxpos gives the starting x position of each column.
665 * We supply n+1 of them, so that we can find the RH edge easily.
666 * All ending x positions are expected to be adjusted afterwards by
667 * subtracting the spacing. */
668 colxpos = g_new(gint, 2);
670 colxpos[1] = alloc->width - 2*border + cols->spacing;
671 /* As in size_request, colypos is the lowest y reached in each column. */
672 colypos = g_new(gint, 1);
675 for (children = cols->children;
676 children && (child = children->data);
677 children = children->next) {
681 if (!child->widget) {
684 /* Column reconfiguration. */
685 for (i = 1; i < ncols; i++) {
686 if (colypos[0] < colypos[i])
687 colypos[0] = colypos[i];
689 ncols = child->ncols;
690 percentages = child->percentages;
691 colypos = g_renew(gint, colypos, ncols);
692 for (i = 1; i < ncols; i++)
693 colypos[i] = colypos[0];
694 colxpos = g_renew(gint, colxpos, ncols + 1);
697 for (i = 0; i < ncols; i++) {
698 percent += percentages[i];
699 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
705 /* Only take visible widgets into account. */
706 if (!gtk_widget_get_visible(child->widget))
709 gtk_widget_get_child_requisition(child->widget, &creq);
710 colspan = child->colspan ? child->colspan : ncols-child->colstart;
713 * Starting x position is cols[colstart].
714 * Ending x position is cols[colstart+colspan] - spacing.
716 * Unless we're forcing left, in which case the width is
717 * exactly the requisition width.
719 call.x = alloc->x + border + colxpos[child->colstart];
720 if (child->force_left)
721 call.width = creq.width;
723 call.width = (colxpos[child->colstart+colspan] -
724 colxpos[child->colstart] - cols->spacing);
727 * To compute height: the widget's top will be positioned
728 * at the largest y value so far reached in any of the
729 * columns it crosses. Then it will go down by creq.height
730 * plus padding; and the point it reaches at the bottom is
731 * the new y value in all those columns.
737 for (i = 0; i < colspan; i++) {
738 if (topy < colypos[child->colstart+i])
739 topy = colypos[child->colstart+i];
741 call.y = alloc->y + border + topy;
742 call.height = creq.height;
743 boty = topy + creq.height + cols->spacing;
744 for (i = 0; i < colspan; i++) {
745 colypos[child->colstart+i] = boty;
749 gtk_widget_size_allocate(child->widget, &call);