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
);