2 * gtkcols.c - implementation of the `Columns' GTK layout container.
7 static void columns_init(Columns
*cols
);
8 static void columns_class_init(ColumnsClass
*klass
);
9 static void columns_map(GtkWidget
*widget
);
10 static void columns_unmap(GtkWidget
*widget
);
11 static void columns_draw(GtkWidget
*widget
, GdkRectangle
*area
);
12 static gint
columns_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
13 static void columns_base_add(GtkContainer
*container
, GtkWidget
*widget
);
14 static void columns_remove(GtkContainer
*container
, GtkWidget
*widget
);
15 static void columns_forall(GtkContainer
*container
, gboolean include_internals
,
16 GtkCallback callback
, gpointer callback_data
);
17 static gint
columns_focus(GtkContainer
*container
, GtkDirectionType dir
);
18 static GtkType
columns_child_type(GtkContainer
*container
);
19 static void columns_size_request(GtkWidget
*widget
, GtkRequisition
*req
);
20 static void columns_size_allocate(GtkWidget
*widget
, GtkAllocation
*alloc
);
22 static GtkContainerClass
*parent_class
= NULL
;
24 GtkType
columns_get_type(void)
26 static GtkType columns_type
= 0;
29 static const GtkTypeInfo columns_info
= {
33 (GtkClassInitFunc
) columns_class_init
,
34 (GtkObjectInitFunc
) columns_init
,
35 /* reserved_1 */ NULL
,
36 /* reserved_2 */ NULL
,
37 (GtkClassInitFunc
) NULL
,
40 columns_type
= gtk_type_unique(GTK_TYPE_CONTAINER
, &columns_info
);
46 static gint (*columns_inherited_focus
)(GtkContainer
*container
,
47 GtkDirectionType direction
);
49 static void columns_class_init(ColumnsClass
*klass
)
51 GtkObjectClass
*object_class
;
52 GtkWidgetClass
*widget_class
;
53 GtkContainerClass
*container_class
;
55 object_class
= (GtkObjectClass
*)klass
;
56 widget_class
= (GtkWidgetClass
*)klass
;
57 container_class
= (GtkContainerClass
*)klass
;
59 parent_class
= gtk_type_class(GTK_TYPE_CONTAINER
);
62 * FIXME: do we have to do all this faffing with set_arg,
63 * get_arg and child_arg_type? Ick.
66 widget_class
->map
= columns_map
;
67 widget_class
->unmap
= columns_unmap
;
68 widget_class
->draw
= columns_draw
;
69 widget_class
->expose_event
= columns_expose
;
70 widget_class
->size_request
= columns_size_request
;
71 widget_class
->size_allocate
= columns_size_allocate
;
73 container_class
->add
= columns_base_add
;
74 container_class
->remove
= columns_remove
;
75 container_class
->forall
= columns_forall
;
76 container_class
->child_type
= columns_child_type
;
77 /* Save the previous value of this method. */
78 if (!columns_inherited_focus
)
79 columns_inherited_focus
= container_class
->focus
;
80 container_class
->focus
= columns_focus
;
83 static void columns_init(Columns
*cols
)
85 GTK_WIDGET_SET_FLAGS(cols
, GTK_NO_WINDOW
);
87 cols
->children
= NULL
;
92 * These appear to be thoroughly tedious functions; the only reason
93 * we have to reimplement them at all is because we defined our own
94 * format for our GList of children...
96 static void columns_map(GtkWidget
*widget
)
102 g_return_if_fail(widget
!= NULL
);
103 g_return_if_fail(IS_COLUMNS(widget
));
105 cols
= COLUMNS(widget
);
106 GTK_WIDGET_SET_FLAGS(cols
, GTK_MAPPED
);
108 for (children
= cols
->children
;
109 children
&& (child
= children
->data
);
110 children
= children
->next
) {
112 GTK_WIDGET_VISIBLE(child
->widget
) &&
113 !GTK_WIDGET_MAPPED(child
->widget
))
114 gtk_widget_map(child
->widget
);
117 static void columns_unmap(GtkWidget
*widget
)
123 g_return_if_fail(widget
!= NULL
);
124 g_return_if_fail(IS_COLUMNS(widget
));
126 cols
= COLUMNS(widget
);
127 GTK_WIDGET_UNSET_FLAGS(cols
, GTK_MAPPED
);
129 for (children
= cols
->children
;
130 children
&& (child
= children
->data
);
131 children
= children
->next
) {
133 GTK_WIDGET_VISIBLE(child
->widget
) &&
134 GTK_WIDGET_MAPPED(child
->widget
))
135 gtk_widget_unmap(child
->widget
);
138 static void columns_draw(GtkWidget
*widget
, GdkRectangle
*area
)
143 GdkRectangle child_area
;
145 g_return_if_fail(widget
!= NULL
);
146 g_return_if_fail(IS_COLUMNS(widget
));
148 if (GTK_WIDGET_DRAWABLE(widget
)) {
149 cols
= COLUMNS(widget
);
151 for (children
= cols
->children
;
152 children
&& (child
= children
->data
);
153 children
= children
->next
) {
155 GTK_WIDGET_DRAWABLE(child
->widget
) &&
156 gtk_widget_intersect(child
->widget
, area
, &child_area
))
157 gtk_widget_draw(child
->widget
, &child_area
);
161 static gint
columns_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
166 GdkEventExpose child_event
;
168 g_return_val_if_fail(widget
!= NULL
, FALSE
);
169 g_return_val_if_fail(IS_COLUMNS(widget
), FALSE
);
170 g_return_val_if_fail(event
!= NULL
, FALSE
);
172 if (GTK_WIDGET_DRAWABLE(widget
)) {
173 cols
= COLUMNS(widget
);
174 child_event
= *event
;
176 for (children
= cols
->children
;
177 children
&& (child
= children
->data
);
178 children
= children
->next
) {
180 GTK_WIDGET_DRAWABLE(child
->widget
) &&
181 GTK_WIDGET_NO_WINDOW(child
->widget
) &&
182 gtk_widget_intersect(child
->widget
, &event
->area
,
184 gtk_widget_event(child
->widget
, (GdkEvent
*)&child_event
);
190 static void columns_base_add(GtkContainer
*container
, GtkWidget
*widget
)
194 g_return_if_fail(container
!= NULL
);
195 g_return_if_fail(IS_COLUMNS(container
));
196 g_return_if_fail(widget
!= NULL
);
198 cols
= COLUMNS(container
);
201 * Default is to add a new widget spanning all columns.
203 columns_add(cols
, widget
, 0, 0); /* 0 means ncols */
206 static void columns_remove(GtkContainer
*container
, GtkWidget
*widget
)
212 gboolean was_visible
;
214 g_return_if_fail(container
!= NULL
);
215 g_return_if_fail(IS_COLUMNS(container
));
216 g_return_if_fail(widget
!= NULL
);
218 cols
= COLUMNS(container
);
220 for (children
= cols
->children
;
221 children
&& (child
= children
->data
);
222 children
= children
->next
) {
223 if (child
->widget
!= widget
)
226 was_visible
= GTK_WIDGET_VISIBLE(widget
);
227 gtk_widget_unparent(widget
);
228 cols
->children
= g_list_remove_link(cols
->children
, children
);
229 g_list_free(children
);
232 gtk_widget_queue_resize(GTK_WIDGET(container
));
236 for (children
= cols
->taborder
;
237 children
&& (childw
= children
->data
);
238 children
= children
->next
) {
239 if (childw
!= widget
)
242 cols
->taborder
= g_list_remove_link(cols
->taborder
, children
);
243 g_list_free(children
);
248 static void columns_forall(GtkContainer
*container
, gboolean include_internals
,
249 GtkCallback callback
, gpointer callback_data
)
253 GList
*children
, *next
;
255 g_return_if_fail(container
!= NULL
);
256 g_return_if_fail(IS_COLUMNS(container
));
257 g_return_if_fail(callback
!= NULL
);
259 cols
= COLUMNS(container
);
261 for (children
= cols
->children
;
262 children
&& (child
= children
->data
);
265 * We can't wait until after the callback to assign
266 * `children = children->next', because the callback might
267 * be gtk_widget_destroy, which would remove the link
268 * `children' from the list! So instead we must get our
269 * hands on the value of the `next' pointer _before_ the
272 next
= children
->next
;
274 callback(child
->widget
, callback_data
);
278 static GtkType
columns_child_type(GtkContainer
*container
)
280 return GTK_TYPE_WIDGET
;
283 GtkWidget
*columns_new(gint spacing
)
287 cols
= gtk_type_new(columns_get_type());
288 cols
->spacing
= spacing
;
290 return GTK_WIDGET(cols
);
293 void columns_set_cols(Columns
*cols
, gint ncols
, const gint
*percentages
)
295 ColumnsChild
*childdata
;
298 g_return_if_fail(cols
!= NULL
);
299 g_return_if_fail(IS_COLUMNS(cols
));
300 g_return_if_fail(ncols
> 0);
301 g_return_if_fail(percentages
!= NULL
);
303 childdata
= g_new(ColumnsChild
, 1);
304 childdata
->widget
= NULL
;
305 childdata
->ncols
= ncols
;
306 childdata
->percentages
= g_new(gint
, ncols
);
307 childdata
->force_left
= FALSE
;
308 for (i
= 0; i
< ncols
; i
++)
309 childdata
->percentages
[i
] = percentages
[i
];
311 cols
->children
= g_list_append(cols
->children
, childdata
);
314 void columns_add(Columns
*cols
, GtkWidget
*child
,
315 gint colstart
, gint colspan
)
317 ColumnsChild
*childdata
;
319 g_return_if_fail(cols
!= NULL
);
320 g_return_if_fail(IS_COLUMNS(cols
));
321 g_return_if_fail(child
!= NULL
);
322 g_return_if_fail(child
->parent
== NULL
);
324 childdata
= g_new(ColumnsChild
, 1);
325 childdata
->widget
= child
;
326 childdata
->colstart
= colstart
;
327 childdata
->colspan
= colspan
;
328 childdata
->force_left
= FALSE
;
330 cols
->children
= g_list_append(cols
->children
, childdata
);
331 cols
->taborder
= g_list_append(cols
->taborder
, child
);
333 gtk_widget_set_parent(child
, GTK_WIDGET(cols
));
335 if (GTK_WIDGET_REALIZED(cols
))
336 gtk_widget_realize(child
);
338 if (GTK_WIDGET_VISIBLE(cols
) && GTK_WIDGET_VISIBLE(child
)) {
339 if (GTK_WIDGET_MAPPED(cols
))
340 gtk_widget_map(child
);
341 gtk_widget_queue_resize(child
);
345 void columns_force_left_align(Columns
*cols
, GtkWidget
*widget
)
350 g_return_if_fail(cols
!= NULL
);
351 g_return_if_fail(IS_COLUMNS(cols
));
352 g_return_if_fail(widget
!= NULL
);
354 for (children
= cols
->children
;
355 children
&& (child
= children
->data
);
356 children
= children
->next
) {
357 if (child
->widget
!= widget
)
360 child
->force_left
= TRUE
;
361 if (GTK_WIDGET_VISIBLE(widget
))
362 gtk_widget_queue_resize(GTK_WIDGET(cols
));
367 void columns_taborder_last(Columns
*cols
, GtkWidget
*widget
)
372 g_return_if_fail(cols
!= NULL
);
373 g_return_if_fail(IS_COLUMNS(cols
));
374 g_return_if_fail(widget
!= NULL
);
376 for (children
= cols
->taborder
;
377 children
&& (childw
= children
->data
);
378 children
= children
->next
) {
379 if (childw
!= widget
)
382 cols
->taborder
= g_list_remove_link(cols
->taborder
, children
);
383 g_list_free(children
);
384 cols
->taborder
= g_list_append(cols
->taborder
, widget
);
390 * Override GtkContainer's focus movement so the user can
391 * explicitly specify the tab order.
393 static gint
columns_focus(GtkContainer
*container
, GtkDirectionType dir
)
397 GtkWidget
*focuschild
;
399 g_return_val_if_fail(container
!= NULL
, FALSE
);
400 g_return_val_if_fail(IS_COLUMNS(container
), FALSE
);
402 cols
= COLUMNS(container
);
404 if (!GTK_WIDGET_DRAWABLE(cols
) ||
405 !GTK_WIDGET_IS_SENSITIVE(cols
))
408 if (!GTK_WIDGET_CAN_FOCUS(container
) &&
409 (dir
== GTK_DIR_TAB_FORWARD
|| dir
== GTK_DIR_TAB_BACKWARD
)) {
411 focuschild
= container
->focus_child
;
412 gtk_container_set_focus_child(container
, NULL
);
414 if (dir
== GTK_DIR_TAB_FORWARD
)
415 pos
= cols
->taborder
;
417 pos
= g_list_last(cols
->taborder
);
420 GtkWidget
*child
= pos
->data
;
423 if (focuschild
== child
) {
424 focuschild
= NULL
; /* now we can start looking in here */
425 if (GTK_WIDGET_DRAWABLE(child
) &&
426 GTK_IS_CONTAINER(child
) &&
427 !GTK_WIDGET_HAS_FOCUS(child
)) {
428 if (gtk_container_focus(GTK_CONTAINER(child
), dir
))
432 } else if (GTK_WIDGET_DRAWABLE(child
)) {
433 if (GTK_IS_CONTAINER(child
)) {
434 if (gtk_container_focus(GTK_CONTAINER(child
), dir
))
436 } else if (GTK_WIDGET_CAN_FOCUS(child
)) {
437 gtk_widget_grab_focus(child
);
442 if (dir
== GTK_DIR_TAB_FORWARD
)
450 return columns_inherited_focus(container
, dir
);
454 * Now here comes the interesting bit. The actual layout part is
455 * done in the following two functions:
457 * columns_size_request() examines the list of widgets held in the
458 * Columns, and returns a requisition stating the absolute minimum
459 * size it can bear to be.
461 * columns_size_allocate() is given an allocation telling it what
462 * size the whole container is going to be, and it calls
463 * gtk_widget_size_allocate() on all of its (visible) children to
464 * set their size and position relative to the top left of the
468 static void columns_size_request(GtkWidget
*widget
, GtkRequisition
*req
)
473 gint i
, ncols
, colspan
, *colypos
;
474 const gint
*percentages
;
475 static const gint onecol
[] = { 100 };
477 g_return_if_fail(widget
!= NULL
);
478 g_return_if_fail(IS_COLUMNS(widget
));
479 g_return_if_fail(req
!= NULL
);
481 cols
= COLUMNS(widget
);
484 req
->height
= cols
->spacing
;
487 colypos
= g_new(gint
, 1);
489 percentages
= onecol
;
491 for (children
= cols
->children
;
492 children
&& (child
= children
->data
);
493 children
= children
->next
) {
496 if (!child
->widget
) {
497 /* Column reconfiguration. */
498 for (i
= 1; i
< ncols
; i
++) {
499 if (colypos
[0] < colypos
[i
])
500 colypos
[0] = colypos
[i
];
502 ncols
= child
->ncols
;
503 percentages
= child
->percentages
;
504 colypos
= g_renew(gint
, colypos
, ncols
);
505 for (i
= 1; i
< ncols
; i
++)
506 colypos
[i
] = colypos
[0];
510 /* Only take visible widgets into account. */
511 if (!GTK_WIDGET_VISIBLE(child
->widget
))
514 gtk_widget_size_request(child
->widget
, &creq
);
515 colspan
= child
->colspan ? child
->colspan
: ncols
-child
->colstart
;
518 * To compute width: we know that creq.width plus
519 * cols->spacing needs to equal a certain percentage of the
520 * full width of the container. So we work this value out,
521 * figure out how wide the container will need to be to
522 * make that percentage of it equal to that width, and
523 * ensure our returned width is at least that much. Very
527 int percent
, thiswid
, fullwid
;
530 for (i
= 0; i
< colspan
; i
++)
531 percent
+= percentages
[child
->colstart
+i
];
533 thiswid
= creq
.width
+ cols
->spacing
;
535 * Since creq is the _minimum_ size the child needs, we
536 * must ensure that it gets _at least_ that size.
537 * Hence, when scaling thiswid up to fullwid, we must
538 * round up, which means adding percent-1 before
539 * dividing by percent.
541 fullwid
= (thiswid
* 100 + percent
- 1) / percent
;
544 * The above calculation assumes every widget gets
545 * cols->spacing on the right. So we subtract
546 * cols->spacing here to account for the extra load of
547 * spacing on the right.
549 if (req
->width
< fullwid
- cols
->spacing
)
550 req
->width
= fullwid
- cols
->spacing
;
554 * To compute height: the widget's top will be positioned
555 * at the largest y value so far reached in any of the
556 * columns it crosses. Then it will go down by creq.height
557 * plus padding; and the point it reaches at the bottom is
558 * the new y value in all those columns, and minus the
559 * padding it is also a lower bound on our own size
566 for (i
= 0; i
< colspan
; i
++) {
567 if (topy
< colypos
[child
->colstart
+i
])
568 topy
= colypos
[child
->colstart
+i
];
570 boty
= topy
+ creq
.height
+ cols
->spacing
;
571 for (i
= 0; i
< colspan
; i
++) {
572 colypos
[child
->colstart
+i
] = boty
;
575 if (req
->height
< boty
- cols
->spacing
)
576 req
->height
= boty
- cols
->spacing
;
580 req
->width
+= 2*GTK_CONTAINER(cols
)->border_width
;
581 req
->height
+= 2*GTK_CONTAINER(cols
)->border_width
;
586 static void columns_size_allocate(GtkWidget
*widget
, GtkAllocation
*alloc
)
591 gint i
, ncols
, colspan
, border
, *colxpos
, *colypos
;
592 const gint
*percentages
;
593 static const gint onecol
[] = { 100 };
595 g_return_if_fail(widget
!= NULL
);
596 g_return_if_fail(IS_COLUMNS(widget
));
597 g_return_if_fail(alloc
!= NULL
);
599 cols
= COLUMNS(widget
);
600 widget
->allocation
= *alloc
;
601 border
= GTK_CONTAINER(cols
)->border_width
;
604 percentages
= onecol
;
605 /* colxpos gives the starting x position of each column.
606 * We supply n+1 of them, so that we can find the RH edge easily.
607 * All ending x positions are expected to be adjusted afterwards by
608 * subtracting the spacing. */
609 colxpos
= g_new(gint
, 2);
611 colxpos
[1] = alloc
->width
- 2*border
+ cols
->spacing
;
612 /* As in size_request, colypos is the lowest y reached in each column. */
613 colypos
= g_new(gint
, 1);
616 for (children
= cols
->children
;
617 children
&& (child
= children
->data
);
618 children
= children
->next
) {
622 if (!child
->widget
) {
625 /* Column reconfiguration. */
626 for (i
= 1; i
< ncols
; i
++) {
627 if (colypos
[0] < colypos
[i
])
628 colypos
[0] = colypos
[i
];
630 ncols
= child
->ncols
;
631 percentages
= child
->percentages
;
632 colypos
= g_renew(gint
, colypos
, ncols
);
633 for (i
= 1; i
< ncols
; i
++)
634 colypos
[i
] = colypos
[0];
635 colxpos
= g_renew(gint
, colxpos
, ncols
+ 1);
638 for (i
= 0; i
< ncols
; i
++) {
639 percent
+= percentages
[i
];
640 colxpos
[i
+1] = (((alloc
->width
- 2*border
) + cols
->spacing
)
646 /* Only take visible widgets into account. */
647 if (!GTK_WIDGET_VISIBLE(child
->widget
))
650 gtk_widget_get_child_requisition(child
->widget
, &creq
);
651 colspan
= child
->colspan ? child
->colspan
: ncols
-child
->colstart
;
654 * Starting x position is cols[colstart].
655 * Ending x position is cols[colstart+colspan] - spacing.
657 * Unless we're forcing left, in which case the width is
658 * exactly the requisition width.
660 call
.x
= alloc
->x
+ border
+ colxpos
[child
->colstart
];
661 if (child
->force_left
)
662 call
.width
= creq
.width
;
664 call
.width
= (colxpos
[child
->colstart
+colspan
] -
665 colxpos
[child
->colstart
] - cols
->spacing
);
668 * To compute height: the widget's top will be positioned
669 * at the largest y value so far reached in any of the
670 * columns it crosses. Then it will go down by creq.height
671 * plus padding; and the point it reaches at the bottom is
672 * the new y value in all those columns.
678 for (i
= 0; i
< colspan
; i
++) {
679 if (topy
< colypos
[child
->colstart
+i
])
680 topy
= colypos
[child
->colstart
+i
];
682 call
.y
= alloc
->y
+ border
+ topy
;
683 call
.height
= creq
.height
;
684 boty
= topy
+ creq
.height
+ cols
->spacing
;
685 for (i
= 0; i
< colspan
; i
++) {
686 colypos
[child
->colstart
+i
] = boty
;
690 gtk_widget_size_allocate(child
->widget
, &call
);