2 * gtkcols.c - implementation of the `Columns' GTK layout container.
\r
8 static void columns_init(Columns *cols);
\r
9 static void columns_class_init(ColumnsClass *klass);
\r
10 static void columns_map(GtkWidget *widget);
\r
11 static void columns_unmap(GtkWidget *widget);
\r
12 #if !GTK_CHECK_VERSION(2,0,0)
\r
13 static void columns_draw(GtkWidget *widget, GdkRectangle *area);
\r
14 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
\r
16 static void columns_base_add(GtkContainer *container, GtkWidget *widget);
\r
17 static void columns_remove(GtkContainer *container, GtkWidget *widget);
\r
18 static void columns_forall(GtkContainer *container, gboolean include_internals,
\r
19 GtkCallback callback, gpointer callback_data);
\r
20 #if !GTK_CHECK_VERSION(2,0,0)
\r
21 static gint columns_focus(GtkContainer *container, GtkDirectionType dir);
\r
23 static GtkType columns_child_type(GtkContainer *container);
\r
24 static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
\r
25 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
\r
27 static GtkContainerClass *parent_class = NULL;
\r
29 #if !GTK_CHECK_VERSION(2,0,0)
\r
30 GtkType columns_get_type(void)
\r
32 static GtkType columns_type = 0;
\r
34 if (!columns_type) {
\r
35 static const GtkTypeInfo columns_info = {
\r
38 sizeof(ColumnsClass),
\r
39 (GtkClassInitFunc) columns_class_init,
\r
40 (GtkObjectInitFunc) columns_init,
\r
41 /* reserved_1 */ NULL,
\r
42 /* reserved_2 */ NULL,
\r
43 (GtkClassInitFunc) NULL,
\r
46 columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
\r
49 return columns_type;
\r
52 GType columns_get_type(void)
\r
54 static GType columns_type = 0;
\r
56 if (!columns_type) {
\r
57 static const GTypeInfo columns_info = {
\r
58 sizeof(ColumnsClass),
\r
61 (GClassInitFunc) columns_class_init,
\r
66 (GInstanceInitFunc)columns_init,
\r
69 columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
\r
73 return columns_type;
\r
77 #if !GTK_CHECK_VERSION(2,0,0)
\r
78 static gint (*columns_inherited_focus)(GtkContainer *container,
\r
79 GtkDirectionType direction);
\r
82 static void columns_class_init(ColumnsClass *klass)
\r
84 #if !GTK_CHECK_VERSION(2,0,0)
\r
85 /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */
\r
86 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
\r
87 GtkContainerClass *container_class = (GtkContainerClass *)klass;
\r
89 /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */
\r
90 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
\r
91 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
\r
94 #if !GTK_CHECK_VERSION(2,0,0)
\r
95 parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
\r
97 parent_class = g_type_class_peek_parent(klass);
\r
100 widget_class->map = columns_map;
\r
101 widget_class->unmap = columns_unmap;
\r
102 #if !GTK_CHECK_VERSION(2,0,0)
\r
103 widget_class->draw = columns_draw;
\r
104 widget_class->expose_event = columns_expose;
\r
106 widget_class->size_request = columns_size_request;
\r
107 widget_class->size_allocate = columns_size_allocate;
\r
109 container_class->add = columns_base_add;
\r
110 container_class->remove = columns_remove;
\r
111 container_class->forall = columns_forall;
\r
112 container_class->child_type = columns_child_type;
\r
113 #if !GTK_CHECK_VERSION(2,0,0)
\r
114 /* Save the previous value of this method. */
\r
115 if (!columns_inherited_focus)
\r
116 columns_inherited_focus = container_class->focus;
\r
117 container_class->focus = columns_focus;
\r
121 static void columns_init(Columns *cols)
\r
123 GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
\r
125 cols->children = NULL;
\r
130 * These appear to be thoroughly tedious functions; the only reason
\r
131 * we have to reimplement them at all is because we defined our own
\r
132 * format for our GList of children...
\r
134 static void columns_map(GtkWidget *widget)
\r
137 ColumnsChild *child;
\r
140 g_return_if_fail(widget != NULL);
\r
141 g_return_if_fail(IS_COLUMNS(widget));
\r
143 cols = COLUMNS(widget);
\r
144 GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
\r
146 for (children = cols->children;
\r
147 children && (child = children->data);
\r
148 children = children->next) {
\r
149 if (child->widget &&
\r
150 GTK_WIDGET_VISIBLE(child->widget) &&
\r
151 !GTK_WIDGET_MAPPED(child->widget))
\r
152 gtk_widget_map(child->widget);
\r
155 static void columns_unmap(GtkWidget *widget)
\r
158 ColumnsChild *child;
\r
161 g_return_if_fail(widget != NULL);
\r
162 g_return_if_fail(IS_COLUMNS(widget));
\r
164 cols = COLUMNS(widget);
\r
165 GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
\r
167 for (children = cols->children;
\r
168 children && (child = children->data);
\r
169 children = children->next) {
\r
170 if (child->widget &&
\r
171 GTK_WIDGET_VISIBLE(child->widget) &&
\r
172 GTK_WIDGET_MAPPED(child->widget))
\r
173 gtk_widget_unmap(child->widget);
\r
176 #if !GTK_CHECK_VERSION(2,0,0)
\r
177 static void columns_draw(GtkWidget *widget, GdkRectangle *area)
\r
180 ColumnsChild *child;
\r
182 GdkRectangle child_area;
\r
184 g_return_if_fail(widget != NULL);
\r
185 g_return_if_fail(IS_COLUMNS(widget));
\r
187 if (GTK_WIDGET_DRAWABLE(widget)) {
\r
188 cols = COLUMNS(widget);
\r
190 for (children = cols->children;
\r
191 children && (child = children->data);
\r
192 children = children->next) {
\r
193 if (child->widget &&
\r
194 GTK_WIDGET_DRAWABLE(child->widget) &&
\r
195 gtk_widget_intersect(child->widget, area, &child_area))
\r
196 gtk_widget_draw(child->widget, &child_area);
\r
200 static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
\r
203 ColumnsChild *child;
\r
205 GdkEventExpose child_event;
\r
207 g_return_val_if_fail(widget != NULL, FALSE);
\r
208 g_return_val_if_fail(IS_COLUMNS(widget), FALSE);
\r
209 g_return_val_if_fail(event != NULL, FALSE);
\r
211 if (GTK_WIDGET_DRAWABLE(widget)) {
\r
212 cols = COLUMNS(widget);
\r
213 child_event = *event;
\r
215 for (children = cols->children;
\r
216 children && (child = children->data);
\r
217 children = children->next) {
\r
218 if (child->widget &&
\r
219 GTK_WIDGET_DRAWABLE(child->widget) &&
\r
220 GTK_WIDGET_NO_WINDOW(child->widget) &&
\r
221 gtk_widget_intersect(child->widget, &event->area,
\r
222 &child_event.area))
\r
223 gtk_widget_event(child->widget, (GdkEvent *)&child_event);
\r
230 static void columns_base_add(GtkContainer *container, GtkWidget *widget)
\r
234 g_return_if_fail(container != NULL);
\r
235 g_return_if_fail(IS_COLUMNS(container));
\r
236 g_return_if_fail(widget != NULL);
\r
238 cols = COLUMNS(container);
\r
241 * Default is to add a new widget spanning all columns.
\r
243 columns_add(cols, widget, 0, 0); /* 0 means ncols */
\r
246 static void columns_remove(GtkContainer *container, GtkWidget *widget)
\r
249 ColumnsChild *child;
\r
252 gboolean was_visible;
\r
254 g_return_if_fail(container != NULL);
\r
255 g_return_if_fail(IS_COLUMNS(container));
\r
256 g_return_if_fail(widget != NULL);
\r
258 cols = COLUMNS(container);
\r
260 for (children = cols->children;
\r
261 children && (child = children->data);
\r
262 children = children->next) {
\r
263 if (child->widget != widget)
\r
266 was_visible = GTK_WIDGET_VISIBLE(widget);
\r
267 gtk_widget_unparent(widget);
\r
268 cols->children = g_list_remove_link(cols->children, children);
\r
269 g_list_free(children);
\r
272 gtk_widget_queue_resize(GTK_WIDGET(container));
\r
276 for (children = cols->taborder;
\r
277 children && (childw = children->data);
\r
278 children = children->next) {
\r
279 if (childw != widget)
\r
282 cols->taborder = g_list_remove_link(cols->taborder, children);
\r
283 g_list_free(children);
\r
284 #if GTK_CHECK_VERSION(2,0,0)
\r
285 gtk_container_set_focus_chain(container, cols->taborder);
\r
291 static void columns_forall(GtkContainer *container, gboolean include_internals,
\r
292 GtkCallback callback, gpointer callback_data)
\r
295 ColumnsChild *child;
\r
296 GList *children, *next;
\r
298 g_return_if_fail(container != NULL);
\r
299 g_return_if_fail(IS_COLUMNS(container));
\r
300 g_return_if_fail(callback != NULL);
\r
302 cols = COLUMNS(container);
\r
304 for (children = cols->children;
\r
305 children && (child = children->data);
\r
308 * We can't wait until after the callback to assign
\r
309 * `children = children->next', because the callback might
\r
310 * be gtk_widget_destroy, which would remove the link
\r
311 * `children' from the list! So instead we must get our
\r
312 * hands on the value of the `next' pointer _before_ the
\r
315 next = children->next;
\r
317 callback(child->widget, callback_data);
\r
321 static GtkType columns_child_type(GtkContainer *container)
\r
323 return GTK_TYPE_WIDGET;
\r
326 GtkWidget *columns_new(gint spacing)
\r
330 #if !GTK_CHECK_VERSION(2,0,0)
\r
331 cols = gtk_type_new(columns_get_type());
\r
333 cols = g_object_new(TYPE_COLUMNS, NULL);
\r
336 cols->spacing = spacing;
\r
338 return GTK_WIDGET(cols);
\r
341 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
\r
343 ColumnsChild *childdata;
\r
346 g_return_if_fail(cols != NULL);
\r
347 g_return_if_fail(IS_COLUMNS(cols));
\r
348 g_return_if_fail(ncols > 0);
\r
349 g_return_if_fail(percentages != NULL);
\r
351 childdata = g_new(ColumnsChild, 1);
\r
352 childdata->widget = NULL;
\r
353 childdata->ncols = ncols;
\r
354 childdata->percentages = g_new(gint, ncols);
\r
355 childdata->force_left = FALSE;
\r
356 for (i = 0; i < ncols; i++)
\r
357 childdata->percentages[i] = percentages[i];
\r
359 cols->children = g_list_append(cols->children, childdata);
\r
362 void columns_add(Columns *cols, GtkWidget *child,
\r
363 gint colstart, gint colspan)
\r
365 ColumnsChild *childdata;
\r
367 g_return_if_fail(cols != NULL);
\r
368 g_return_if_fail(IS_COLUMNS(cols));
\r
369 g_return_if_fail(child != NULL);
\r
370 g_return_if_fail(child->parent == NULL);
\r
372 childdata = g_new(ColumnsChild, 1);
\r
373 childdata->widget = child;
\r
374 childdata->colstart = colstart;
\r
375 childdata->colspan = colspan;
\r
376 childdata->force_left = FALSE;
\r
378 cols->children = g_list_append(cols->children, childdata);
\r
379 cols->taborder = g_list_append(cols->taborder, child);
\r
381 gtk_widget_set_parent(child, GTK_WIDGET(cols));
\r
383 #if GTK_CHECK_VERSION(2,0,0)
\r
384 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
\r
387 if (GTK_WIDGET_REALIZED(cols))
\r
388 gtk_widget_realize(child);
\r
390 if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
\r
391 if (GTK_WIDGET_MAPPED(cols))
\r
392 gtk_widget_map(child);
\r
393 gtk_widget_queue_resize(child);
\r
397 void columns_force_left_align(Columns *cols, GtkWidget *widget)
\r
399 ColumnsChild *child;
\r
402 g_return_if_fail(cols != NULL);
\r
403 g_return_if_fail(IS_COLUMNS(cols));
\r
404 g_return_if_fail(widget != NULL);
\r
406 for (children = cols->children;
\r
407 children && (child = children->data);
\r
408 children = children->next) {
\r
409 if (child->widget != widget)
\r
412 child->force_left = TRUE;
\r
413 if (GTK_WIDGET_VISIBLE(widget))
\r
414 gtk_widget_queue_resize(GTK_WIDGET(cols));
\r
419 void columns_taborder_last(Columns *cols, GtkWidget *widget)
\r
424 g_return_if_fail(cols != NULL);
\r
425 g_return_if_fail(IS_COLUMNS(cols));
\r
426 g_return_if_fail(widget != NULL);
\r
428 for (children = cols->taborder;
\r
429 children && (childw = children->data);
\r
430 children = children->next) {
\r
431 if (childw != widget)
\r
434 cols->taborder = g_list_remove_link(cols->taborder, children);
\r
435 g_list_free(children);
\r
436 cols->taborder = g_list_append(cols->taborder, widget);
\r
437 #if GTK_CHECK_VERSION(2,0,0)
\r
438 gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
\r
444 #if !GTK_CHECK_VERSION(2,0,0)
\r
446 * Override GtkContainer's focus movement so the user can
\r
447 * explicitly specify the tab order.
\r
449 static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
\r
453 GtkWidget *focuschild;
\r
455 g_return_val_if_fail(container != NULL, FALSE);
\r
456 g_return_val_if_fail(IS_COLUMNS(container), FALSE);
\r
458 cols = COLUMNS(container);
\r
460 if (!GTK_WIDGET_DRAWABLE(cols) ||
\r
461 !GTK_WIDGET_IS_SENSITIVE(cols))
\r
464 if (!GTK_WIDGET_CAN_FOCUS(container) &&
\r
465 (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
\r
467 focuschild = container->focus_child;
\r
468 gtk_container_set_focus_child(container, NULL);
\r
470 if (dir == GTK_DIR_TAB_FORWARD)
\r
471 pos = cols->taborder;
\r
473 pos = g_list_last(cols->taborder);
\r
476 GtkWidget *child = pos->data;
\r
479 if (focuschild == child) {
\r
480 focuschild = NULL; /* now we can start looking in here */
\r
481 if (GTK_WIDGET_DRAWABLE(child) &&
\r
482 GTK_IS_CONTAINER(child) &&
\r
483 !GTK_WIDGET_HAS_FOCUS(child)) {
\r
484 if (gtk_container_focus(GTK_CONTAINER(child), dir))
\r
488 } else if (GTK_WIDGET_DRAWABLE(child)) {
\r
489 if (GTK_IS_CONTAINER(child)) {
\r
490 if (gtk_container_focus(GTK_CONTAINER(child), dir))
\r
492 } else if (GTK_WIDGET_CAN_FOCUS(child)) {
\r
493 gtk_widget_grab_focus(child);
\r
498 if (dir == GTK_DIR_TAB_FORWARD)
\r
506 return columns_inherited_focus(container, dir);
\r
511 * Now here comes the interesting bit. The actual layout part is
\r
512 * done in the following two functions:
\r
514 * columns_size_request() examines the list of widgets held in the
\r
515 * Columns, and returns a requisition stating the absolute minimum
\r
516 * size it can bear to be.
\r
518 * columns_size_allocate() is given an allocation telling it what
\r
519 * size the whole container is going to be, and it calls
\r
520 * gtk_widget_size_allocate() on all of its (visible) children to
\r
521 * set their size and position relative to the top left of the
\r
525 static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
\r
528 ColumnsChild *child;
\r
530 gint i, ncols, colspan, *colypos;
\r
531 const gint *percentages;
\r
532 static const gint onecol[] = { 100 };
\r
534 g_return_if_fail(widget != NULL);
\r
535 g_return_if_fail(IS_COLUMNS(widget));
\r
536 g_return_if_fail(req != NULL);
\r
538 cols = COLUMNS(widget);
\r
541 req->height = cols->spacing;
\r
544 colypos = g_new(gint, 1);
\r
546 percentages = onecol;
\r
548 for (children = cols->children;
\r
549 children && (child = children->data);
\r
550 children = children->next) {
\r
551 GtkRequisition creq;
\r
553 if (!child->widget) {
\r
554 /* Column reconfiguration. */
\r
555 for (i = 1; i < ncols; i++) {
\r
556 if (colypos[0] < colypos[i])
\r
557 colypos[0] = colypos[i];
\r
559 ncols = child->ncols;
\r
560 percentages = child->percentages;
\r
561 colypos = g_renew(gint, colypos, ncols);
\r
562 for (i = 1; i < ncols; i++)
\r
563 colypos[i] = colypos[0];
\r
567 /* Only take visible widgets into account. */
\r
568 if (!GTK_WIDGET_VISIBLE(child->widget))
\r
571 gtk_widget_size_request(child->widget, &creq);
\r
572 colspan = child->colspan ? child->colspan : ncols-child->colstart;
\r
575 * To compute width: we know that creq.width plus
\r
576 * cols->spacing needs to equal a certain percentage of the
\r
577 * full width of the container. So we work this value out,
\r
578 * figure out how wide the container will need to be to
\r
579 * make that percentage of it equal to that width, and
\r
580 * ensure our returned width is at least that much. Very
\r
584 int percent, thiswid, fullwid;
\r
587 for (i = 0; i < colspan; i++)
\r
588 percent += percentages[child->colstart+i];
\r
590 thiswid = creq.width + cols->spacing;
\r
592 * Since creq is the _minimum_ size the child needs, we
\r
593 * must ensure that it gets _at least_ that size.
\r
594 * Hence, when scaling thiswid up to fullwid, we must
\r
595 * round up, which means adding percent-1 before
\r
596 * dividing by percent.
\r
598 fullwid = (thiswid * 100 + percent - 1) / percent;
\r
601 * The above calculation assumes every widget gets
\r
602 * cols->spacing on the right. So we subtract
\r
603 * cols->spacing here to account for the extra load of
\r
604 * spacing on the right.
\r
606 if (req->width < fullwid - cols->spacing)
\r
607 req->width = fullwid - cols->spacing;
\r
611 * To compute height: the widget's top will be positioned
\r
612 * at the largest y value so far reached in any of the
\r
613 * columns it crosses. Then it will go down by creq.height
\r
614 * plus padding; and the point it reaches at the bottom is
\r
615 * the new y value in all those columns, and minus the
\r
616 * padding it is also a lower bound on our own size
\r
623 for (i = 0; i < colspan; i++) {
\r
624 if (topy < colypos[child->colstart+i])
\r
625 topy = colypos[child->colstart+i];
\r
627 boty = topy + creq.height + cols->spacing;
\r
628 for (i = 0; i < colspan; i++) {
\r
629 colypos[child->colstart+i] = boty;
\r
632 if (req->height < boty - cols->spacing)
\r
633 req->height = boty - cols->spacing;
\r
637 req->width += 2*GTK_CONTAINER(cols)->border_width;
\r
638 req->height += 2*GTK_CONTAINER(cols)->border_width;
\r
643 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
\r
646 ColumnsChild *child;
\r
648 gint i, ncols, colspan, border, *colxpos, *colypos;
\r
649 const gint *percentages;
\r
650 static const gint onecol[] = { 100 };
\r
652 g_return_if_fail(widget != NULL);
\r
653 g_return_if_fail(IS_COLUMNS(widget));
\r
654 g_return_if_fail(alloc != NULL);
\r
656 cols = COLUMNS(widget);
\r
657 widget->allocation = *alloc;
\r
658 border = GTK_CONTAINER(cols)->border_width;
\r
661 percentages = onecol;
\r
662 /* colxpos gives the starting x position of each column.
\r
663 * We supply n+1 of them, so that we can find the RH edge easily.
\r
664 * All ending x positions are expected to be adjusted afterwards by
\r
665 * subtracting the spacing. */
\r
666 colxpos = g_new(gint, 2);
\r
668 colxpos[1] = alloc->width - 2*border + cols->spacing;
\r
669 /* As in size_request, colypos is the lowest y reached in each column. */
\r
670 colypos = g_new(gint, 1);
\r
673 for (children = cols->children;
\r
674 children && (child = children->data);
\r
675 children = children->next) {
\r
676 GtkRequisition creq;
\r
677 GtkAllocation call;
\r
679 if (!child->widget) {
\r
682 /* Column reconfiguration. */
\r
683 for (i = 1; i < ncols; i++) {
\r
684 if (colypos[0] < colypos[i])
\r
685 colypos[0] = colypos[i];
\r
687 ncols = child->ncols;
\r
688 percentages = child->percentages;
\r
689 colypos = g_renew(gint, colypos, ncols);
\r
690 for (i = 1; i < ncols; i++)
\r
691 colypos[i] = colypos[0];
\r
692 colxpos = g_renew(gint, colxpos, ncols + 1);
\r
695 for (i = 0; i < ncols; i++) {
\r
696 percent += percentages[i];
\r
697 colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
\r
703 /* Only take visible widgets into account. */
\r
704 if (!GTK_WIDGET_VISIBLE(child->widget))
\r
707 gtk_widget_get_child_requisition(child->widget, &creq);
\r
708 colspan = child->colspan ? child->colspan : ncols-child->colstart;
\r
711 * Starting x position is cols[colstart].
\r
712 * Ending x position is cols[colstart+colspan] - spacing.
\r
714 * Unless we're forcing left, in which case the width is
\r
715 * exactly the requisition width.
\r
717 call.x = alloc->x + border + colxpos[child->colstart];
\r
718 if (child->force_left)
\r
719 call.width = creq.width;
\r
721 call.width = (colxpos[child->colstart+colspan] -
\r
722 colxpos[child->colstart] - cols->spacing);
\r
725 * To compute height: the widget's top will be positioned
\r
726 * at the largest y value so far reached in any of the
\r
727 * columns it crosses. Then it will go down by creq.height
\r
728 * plus padding; and the point it reaches at the bottom is
\r
729 * the new y value in all those columns.
\r
735 for (i = 0; i < colspan; i++) {
\r
736 if (topy < colypos[child->colstart+i])
\r
737 topy = colypos[child->colstart+i];
\r
739 call.y = alloc->y + border + topy;
\r
740 call.height = creq.height;
\r
741 boty = topy + creq.height + cols->spacing;
\r
742 for (i = 0; i < colspan; i++) {
\r
743 colypos[child->colstart+i] = boty;
\r
747 gtk_widget_size_allocate(child->widget, &call);
\r