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 GtkType columns_get_type(void)
31 static GtkType columns_type = 0;
34 static const GtkTypeInfo columns_info = {
38 (GtkClassInitFunc) columns_class_init,
39 (GtkObjectInitFunc) columns_init,
40 /* reserved_1 */ NULL,
41 /* reserved_2 */ NULL,
42 (GtkClassInitFunc) NULL,
45 columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
51 #if !GTK_CHECK_VERSION(2,0,0)
52 static gint (*columns_inherited_focus)(GtkContainer *container,
53 GtkDirectionType direction);
56 static void columns_class_init(ColumnsClass *klass)
58 GtkObjectClass *object_class;
59 GtkWidgetClass *widget_class;
60 GtkContainerClass *container_class;
62 object_class = (GtkObjectClass *)klass;
63 widget_class = (GtkWidgetClass *)klass;
64 container_class = (GtkContainerClass *)klass;
66 parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
69 * FIXME: do we have to do all this faffing with set_arg,
70 * get_arg and child_arg_type? Ick.
73 widget_class->map = columns_map;
74 widget_class->unmap = columns_unmap;
75 #if !GTK_CHECK_VERSION(2,0,0)
76 widget_class->draw = columns_draw;
77 widget_class->expose_event = columns_expose;
79 widget_class->size_request = columns_size_request;
80 widget_class->size_allocate = columns_size_allocate;
82 container_class->add = columns_base_add;
83 container_class->remove = columns_remove;
84 container_class->forall = columns_forall;
85 container_class->child_type = columns_child_type;
86 #if !GTK_CHECK_VERSION(2,0,0)
87 /* Save the previous value of this method. */
88 if (!columns_inherited_focus)
89 columns_inherited_focus = container_class->focus;
90 container_class->focus = columns_focus;
94 static void columns_init(Columns *cols)
96 GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
98 cols->children = NULL;
103 * These appear to be thoroughly tedious functions; the only reason
104 * we have to reimplement them at all is because we defined our own
105 * format for our GList of children...
107 static void columns_map(GtkWidget *widget)
113 g_return_if_fail(widget != NULL);
114 g_return_if_fail(IS_COLUMNS(widget));
116 cols = COLUMNS(widget);
117 GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
119 for (children = cols->children;
120 children && (child = children->data);
121 children = children->next) {
123 GTK_WIDGET_VISIBLE(child->widget) &&
124 !GTK_WIDGET_MAPPED(child->widget))
125 gtk_widget_map(child->widget);
128 static void columns_unmap(GtkWidget *widget)
134 g_return_if_fail(widget != NULL);
135 g_return_if_fail(IS_COLUMNS(widget));
137 cols = COLUMNS(widget);
138 GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
140 for (children = cols->children;
141 children && (child = children->data);
142 children = children->next) {
144 GTK_WIDGET_VISIBLE(child->widget) &&
145 GTK_WIDGET_MAPPED(child->widget))
146 gtk_widget_unmap(child->widget);
149 #if !GTK_CHECK_VERSION(2,0,0)
150 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
155 GdkRectangle child_area;
157 g_return_if_fail(widget != NULL);
158 g_return_if_fail(IS_COLUMNS(widget));
160 if (GTK_WIDGET_DRAWABLE(widget)) {
161 cols = COLUMNS(widget);
163 for (children = cols->children;
164 children && (child = children->data);
165 children = children->next) {
167 GTK_WIDGET_DRAWABLE(child->widget) &&
168 gtk_widget_intersect(child->widget, area, &child_area))
169 gtk_widget_draw(child->widget, &child_area);
173 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
178 GdkEventExpose child_event;
180 g_return_val_if_fail(widget != NULL, FALSE);
181 g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
182 g_return_val_if_fail(event != NULL, FALSE);
184 if (GTK_WIDGET_DRAWABLE(widget)) {
185 cols = COLUMNS(widget);
186 child_event = *event;
188 for (children = cols->children;
189 children && (child = children->data);
190 children = children->next) {
192 GTK_WIDGET_DRAWABLE(child->widget) &&
193 GTK_WIDGET_NO_WINDOW(child->widget) &&
194 gtk_widget_intersect(child->widget, &event->area,
196 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
203 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
207 g_return_if_fail(container != NULL);
208 g_return_if_fail(IS_COLUMNS(container));
209 g_return_if_fail(widget != NULL);
211 cols = COLUMNS(container);
214 * Default is to add a new widget spanning all columns.
216 columns_add(cols, widget, 0, 0); /* 0 means ncols */
219 static void columns_remove(GtkContainer *container, GtkWidget *widget)
225 gboolean was_visible;
227 g_return_if_fail(container != NULL);
228 g_return_if_fail(IS_COLUMNS(container));
229 g_return_if_fail(widget != NULL);
231 cols = COLUMNS(container);
233 for (children = cols->children;
234 children && (child = children->data);
235 children = children->next) {
236 if (child->widget != widget)
239 was_visible = GTK_WIDGET_VISIBLE(widget);
240 gtk_widget_unparent(widget);
241 cols->children = g_list_remove_link(cols->children, children);
242 g_list_free(children);
245 gtk_widget_queue_resize(GTK_WIDGET(container));
249 for (children = cols->taborder;
250 children && (childw = children->data);
251 children = children->next) {
252 if (childw != widget)
255 cols->taborder = g_list_remove_link(cols->taborder, children);
256 g_list_free(children);
257 #if GTK_CHECK_VERSION(2,0,0)
258 gtk_container_set_focus_chain(container, cols->taborder);
264 static void columns_forall(GtkContainer *container, gboolean include_internals,
265 GtkCallback callback, gpointer callback_data)
269 GList *children, *next;
271 g_return_if_fail(container != NULL);
272 g_return_if_fail(IS_COLUMNS(container));
273 g_return_if_fail(callback != NULL);
275 cols = COLUMNS(container);
277 for (children = cols->children;
278 children && (child = children->data);
281 * We can't wait until after the callback to assign
282 * `children = children->next', because the callback might
283 * be gtk_widget_destroy, which would remove the link
284 * `children' from the list! So instead we must get our
285 * hands on the value of the `next' pointer _before_ the
288 next = children->next;
290 callback(child->widget, callback_data);
294 static GtkType columns_child_type(GtkContainer *container)
296 return GTK_TYPE_WIDGET;
299 GtkWidget *columns_new(gint spacing)
303 cols = gtk_type_new(columns_get_type());
304 cols->spacing = spacing;
306 return GTK_WIDGET(cols);
309 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
311 ColumnsChild *childdata;
314 g_return_if_fail(cols != NULL);
315 g_return_if_fail(IS_COLUMNS(cols));
316 g_return_if_fail(ncols > 0);
317 g_return_if_fail(percentages != NULL);
319 childdata = g_new(ColumnsChild, 1);
320 childdata->widget = NULL;
321 childdata->ncols = ncols;
322 childdata->percentages = g_new(gint, ncols);
323 childdata->force_left = FALSE;
324 for (i = 0; i < ncols; i++)
325 childdata->percentages[i] = percentages[i];
327 cols->children = g_list_append(cols->children, childdata);
330 void columns_add(Columns *cols, GtkWidget *child,
331 gint colstart, gint colspan)
333 ColumnsChild *childdata;
335 g_return_if_fail(cols != NULL);
336 g_return_if_fail(IS_COLUMNS(cols));
337 g_return_if_fail(child != NULL);
338 g_return_if_fail(child->parent == NULL);
340 childdata = g_new(ColumnsChild, 1);
341 childdata->widget = child;
342 childdata->colstart = colstart;
343 childdata->colspan = colspan;
344 childdata->force_left = FALSE;
346 cols->children = g_list_append(cols->children, childdata);
347 cols->taborder = g_list_append(cols->taborder, child);
349 gtk_widget_set_parent(child, GTK_WIDGET(cols));
351 #if GTK_CHECK_VERSION(2,0,0)
352 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
355 if (GTK_WIDGET_REALIZED(cols))
356 gtk_widget_realize(child);
358 if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
359 if (GTK_WIDGET_MAPPED(cols))
360 gtk_widget_map(child);
361 gtk_widget_queue_resize(child);
365 void columns_force_left_align(Columns *cols, GtkWidget *widget)
370 g_return_if_fail(cols != NULL);
371 g_return_if_fail(IS_COLUMNS(cols));
372 g_return_if_fail(widget != NULL);
374 for (children = cols->children;
375 children && (child = children->data);
376 children = children->next) {
377 if (child->widget != widget)
380 child->force_left = TRUE;
381 if (GTK_WIDGET_VISIBLE(widget))
382 gtk_widget_queue_resize(GTK_WIDGET(cols));
387 void columns_taborder_last(Columns *cols, GtkWidget *widget)
392 g_return_if_fail(cols != NULL);
393 g_return_if_fail(IS_COLUMNS(cols));
394 g_return_if_fail(widget != NULL);
396 for (children = cols->taborder;
397 children && (childw = children->data);
398 children = children->next) {
399 if (childw != widget)
402 cols->taborder = g_list_remove_link(cols->taborder, children);
403 g_list_free(children);
404 cols->taborder = g_list_append(cols->taborder, widget);
405 #if GTK_CHECK_VERSION(2,0,0)
406 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
412 #if !GTK_CHECK_VERSION(2,0,0)
414 * Override GtkContainer's focus movement so the user can
415 * explicitly specify the tab order.
417 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
421 GtkWidget *focuschild;
423 g_return_val_if_fail(container != NULL, FALSE);
424 g_return_val_if_fail(IS_COLUMNS(container), FALSE);
426 cols = COLUMNS(container);
428 if (!GTK_WIDGET_DRAWABLE(cols) ||
429 !GTK_WIDGET_IS_SENSITIVE(cols))
432 if (!GTK_WIDGET_CAN_FOCUS(container) &&
433 (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
435 focuschild = container->focus_child;
436 gtk_container_set_focus_child(container, NULL);
438 if (dir == GTK_DIR_TAB_FORWARD)
439 pos = cols->taborder;
441 pos = g_list_last(cols->taborder);
444 GtkWidget *child = pos->data;
447 if (focuschild == child) {
448 focuschild = NULL; /* now we can start looking in here */
449 if (GTK_WIDGET_DRAWABLE(child) &&
450 GTK_IS_CONTAINER(child) &&
451 !GTK_WIDGET_HAS_FOCUS(child)) {
452 if (gtk_container_focus(GTK_CONTAINER(child), dir))
456 } else if (GTK_WIDGET_DRAWABLE(child)) {
457 if (GTK_IS_CONTAINER(child)) {
458 if (gtk_container_focus(GTK_CONTAINER(child), dir))
460 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
461 gtk_widget_grab_focus(child);
466 if (dir == GTK_DIR_TAB_FORWARD)
474 return columns_inherited_focus(container, dir);
479 * Now here comes the interesting bit. The actual layout part is
480 * done in the following two functions:
482 * columns_size_request() examines the list of widgets held in the
483 * Columns, and returns a requisition stating the absolute minimum
484 * size it can bear to be.
486 * columns_size_allocate() is given an allocation telling it what
487 * size the whole container is going to be, and it calls
488 * gtk_widget_size_allocate() on all of its (visible) children to
489 * set their size and position relative to the top left of the
493 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
498 gint i, ncols, colspan, *colypos;
499 const gint *percentages;
500 static const gint onecol[] = { 100 };
502 g_return_if_fail(widget != NULL);
503 g_return_if_fail(IS_COLUMNS(widget));
504 g_return_if_fail(req != NULL);
506 cols = COLUMNS(widget);
509 req->height = cols->spacing;
512 colypos = g_new(gint, 1);
514 percentages = onecol;
516 for (children = cols->children;
517 children && (child = children->data);
518 children = children->next) {
521 if (!child->widget) {
522 /* Column reconfiguration. */
523 for (i = 1; i < ncols; i++) {
524 if (colypos[0] < colypos[i])
525 colypos[0] = colypos[i];
527 ncols = child->ncols;
528 percentages = child->percentages;
529 colypos = g_renew(gint, colypos, ncols);
530 for (i = 1; i < ncols; i++)
531 colypos[i] = colypos[0];
535 /* Only take visible widgets into account. */
536 if (!GTK_WIDGET_VISIBLE(child->widget))
539 gtk_widget_size_request(child->widget, &creq);
540 colspan = child->colspan ? child->colspan : ncols-child->colstart;
543 * To compute width: we know that creq.width plus
544 * cols->spacing needs to equal a certain percentage of the
545 * full width of the container. So we work this value out,
546 * figure out how wide the container will need to be to
547 * make that percentage of it equal to that width, and
548 * ensure our returned width is at least that much. Very
552 int percent, thiswid, fullwid;
555 for (i = 0; i < colspan; i++)
556 percent += percentages[child->colstart+i];
558 thiswid = creq.width + cols->spacing;
560 * Since creq is the _minimum_ size the child needs, we
561 * must ensure that it gets _at least_ that size.
562 * Hence, when scaling thiswid up to fullwid, we must
563 * round up, which means adding percent-1 before
564 * dividing by percent.
566 fullwid = (thiswid * 100 + percent - 1) / percent;
569 * The above calculation assumes every widget gets
570 * cols->spacing on the right. So we subtract
571 * cols->spacing here to account for the extra load of
572 * spacing on the right.
574 if (req->width < fullwid - cols->spacing)
575 req->width = fullwid - cols->spacing;
579 * To compute height: the widget's top will be positioned
580 * at the largest y value so far reached in any of the
581 * columns it crosses. Then it will go down by creq.height
582 * plus padding; and the point it reaches at the bottom is
583 * the new y value in all those columns, and minus the
584 * padding it is also a lower bound on our own size
591 for (i = 0; i < colspan; i++) {
592 if (topy < colypos[child->colstart+i])
593 topy = colypos[child->colstart+i];
595 boty = topy + creq.height + cols->spacing;
596 for (i = 0; i < colspan; i++) {
597 colypos[child->colstart+i] = boty;
600 if (req->height < boty - cols->spacing)
601 req->height = boty - cols->spacing;
605 req->width += 2*GTK_CONTAINER(cols)->border_width;
606 req->height += 2*GTK_CONTAINER(cols)->border_width;
611 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
616 gint i, ncols, colspan, border, *colxpos, *colypos;
617 const gint *percentages;
618 static const gint onecol[] = { 100 };
620 g_return_if_fail(widget != NULL);
621 g_return_if_fail(IS_COLUMNS(widget));
622 g_return_if_fail(alloc != NULL);
624 cols = COLUMNS(widget);
625 widget->allocation = *alloc;
626 border = GTK_CONTAINER(cols)->border_width;
629 percentages = onecol;
630 /* colxpos gives the starting x position of each column.
631 * We supply n+1 of them, so that we can find the RH edge easily.
632 * All ending x positions are expected to be adjusted afterwards by
633 * subtracting the spacing. */
634 colxpos = g_new(gint, 2);
636 colxpos[1] = alloc->width - 2*border + cols->spacing;
637 /* As in size_request, colypos is the lowest y reached in each column. */
638 colypos = g_new(gint, 1);
641 for (children = cols->children;
642 children && (child = children->data);
643 children = children->next) {
647 if (!child->widget) {
650 /* Column reconfiguration. */
651 for (i = 1; i < ncols; i++) {
652 if (colypos[0] < colypos[i])
653 colypos[0] = colypos[i];
655 ncols = child->ncols;
656 percentages = child->percentages;
657 colypos = g_renew(gint, colypos, ncols);
658 for (i = 1; i < ncols; i++)
659 colypos[i] = colypos[0];
660 colxpos = g_renew(gint, colxpos, ncols + 1);
663 for (i = 0; i < ncols; i++) {
664 percent += percentages[i];
665 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
671 /* Only take visible widgets into account. */
672 if (!GTK_WIDGET_VISIBLE(child->widget))
675 gtk_widget_get_child_requisition(child->widget, &creq);
676 colspan = child->colspan ? child->colspan : ncols-child->colstart;
679 * Starting x position is cols[colstart].
680 * Ending x position is cols[colstart+colspan] - spacing.
682 * Unless we're forcing left, in which case the width is
683 * exactly the requisition width.
685 call.x = alloc->x + border + colxpos[child->colstart];
686 if (child->force_left)
687 call.width = creq.width;
689 call.width = (colxpos[child->colstart+colspan] -
690 colxpos[child->colstart] - cols->spacing);
693 * To compute height: the widget's top will be positioned
694 * at the largest y value so far reached in any of the
695 * columns it crosses. Then it will go down by creq.height
696 * plus padding; and the point it reaches at the bottom is
697 * the new y value in all those columns.
703 for (i = 0; i < colspan; i++) {
704 if (topy < colypos[child->colstart+i])
705 topy = colypos[child->colstart+i];
707 call.y = alloc->y + border + topy;
708 call.height = creq.height;
709 boty = topy + creq.height + cols->spacing;
710 for (i = 0; i < colspan; i++) {
711 colypos[child->colstart+i] = boty;
715 gtk_widget_size_allocate(child->widget, &call);