1 /* AspectBin - A GTK+ container for packing with consrained aspect ratio.
2 * Copyright (C) 2009 Nick Bowler
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 #include "aspectbin.h"
35 static void aspect_bin_size_request(GtkWidget *, GtkRequisition *);
36 static void aspect_bin_size_allocate(GtkWidget *, GtkAllocation *);
37 static void aspect_bin_add(GtkContainer *, GtkWidget *);
38 static void aspect_bin_remove(GtkContainer *, GtkWidget *);
39 static void aspect_bin_forall(GtkContainer *, gboolean, GtkCallback, gpointer);
40 static GType aspect_bin_child_type(GtkContainer *);
42 static void aspect_bin_get_property(GObject *, guint, GValue *, GParamSpec *);
43 static void aspect_bin_set_property(GObject *, guint, const GValue *,
45 static void aspect_bin_set_child_property(GtkContainer *, GtkWidget *, guint,
46 const GValue *, GParamSpec *);
47 static void aspect_bin_get_child_property(GtkContainer *, GtkWidget *, guint,
48 GValue *, GParamSpec *);
50 G_DEFINE_TYPE(AspectBin, aspect_bin, GTK_TYPE_CONTAINER)
52 static void aspect_bin_init(AspectBin *abin)
54 GTK_WIDGET_SET_FLAGS(abin, GTK_NO_WINDOW);
59 abin->body_align = 0.5;
60 abin->side_align = 0.5;
61 abin->constrain = FALSE;
65 static void aspect_bin_class_init(AspectBinClass *class)
67 GObjectClass *object_class = G_OBJECT_CLASS(class);
68 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
69 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(class);
71 widget_class->size_request = aspect_bin_size_request;
72 widget_class->size_allocate = aspect_bin_size_allocate;
74 container_class->add = aspect_bin_add;
75 container_class->remove = aspect_bin_remove;
76 container_class->forall = aspect_bin_forall;
77 container_class->child_type = aspect_bin_child_type;
79 container_class->set_child_property = aspect_bin_set_child_property;
80 container_class->get_child_property = aspect_bin_get_child_property;
82 object_class->set_property = aspect_bin_set_property;
83 object_class->get_property = aspect_bin_get_property;
85 g_object_class_install_property(object_class,
87 g_param_spec_float("ratio",
89 "Width:height ratio of the body.",
92 g_object_class_install_property(object_class,
94 g_param_spec_boolean("fill",
96 "Allocate all remaining space to the side.",
99 g_object_class_install_property(object_class,
101 g_param_spec_boolean("constrain",
103 "Try not to place the side beyond the body's edges.",
106 gtk_container_class_install_child_property(container_class,
108 g_param_spec_float("align",
110 "Alignment of the child within the available space.",
115 GtkWidget *aspect_bin_new(void)
117 return GTK_WIDGET(g_object_new(ASPECT_BIN_TYPE, NULL));
120 static void aspect_bin_set_property(GObject *object,
125 AspectBin *abin = ASPECT_BIN(object);
131 abin->ratio = g_value_get_float(value);
132 if (tmp != abin->ratio)
133 gtk_widget_queue_resize(GTK_WIDGET(abin));
136 abin->constrain = g_value_get_boolean(value);
137 gtk_widget_queue_resize(GTK_WIDGET(abin));
140 abin->fill = g_value_get_boolean(value);
141 gtk_widget_queue_resize(GTK_WIDGET(abin));
144 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
148 static void aspect_bin_get_property(GObject *object,
153 AspectBin *abin = ASPECT_BIN(object);
157 g_value_set_float(value, abin->ratio);
160 g_value_set_boolean(value, abin->constrain);
163 g_value_set_boolean(value, abin->fill);
166 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
170 static void aspect_bin_set_child_property(GtkContainer *container,
176 AspectBin *abin = ASPECT_BIN(container);
179 g_assert(child == abin->body || child == abin->side);
182 case CHILD_PROP_ALIGN:
183 align = g_value_get_float(value);
184 if (child == abin->body)
185 abin->body_align = align;
186 else if (child == abin->side)
187 abin->side_align = align;
188 gtk_widget_queue_resize(GTK_WIDGET(abin));
191 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
196 static void aspect_bin_get_child_property(GtkContainer *container,
202 AspectBin *abin = ASPECT_BIN(container);
205 g_assert(child == abin->body || child == abin->side);
208 case CHILD_PROP_ALIGN:
209 if (child == abin->body)
210 align = abin->body_align;
211 else if (child == abin->side)
212 align = abin->side_align;
213 g_value_set_float(value, align);
216 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
221 static void aspect_bin_add(GtkContainer *container, GtkWidget *widget)
224 g_return_if_fail(IS_ASPECT_BIN(container));
225 abin = ASPECT_BIN(container);
228 aspect_bin_set_body(abin, widget, 1);
229 else if (!abin->side)
230 aspect_bin_set_side(abin, widget);
232 g_warning("AspectBin cannot have more than 2 children.\n");
235 static void aspect_bin_remove(GtkContainer *container, GtkWidget *child)
237 AspectBin *abin = ASPECT_BIN(container);
239 if (abin->body == child) {
240 aspect_bin_set_body(abin, NULL, 1);
241 } else if (abin->side == child) {
242 aspect_bin_set_side(abin, NULL);
246 static void aspect_bin_forall(GtkContainer *container,
247 gboolean include_internals,
248 GtkCallback callback,
249 gpointer callback_data)
251 AspectBin *abin = ASPECT_BIN(container);
252 g_return_if_fail(callback != NULL);
255 callback(abin->body, callback_data);
257 callback(abin->side, callback_data);
260 static GType aspect_bin_child_type(GtkContainer *container)
262 if (!ASPECT_BIN(container)->body || !ASPECT_BIN(container)->side)
263 return GTK_TYPE_WIDGET;
268 aspect_bin_size_request(GtkWidget *widget, GtkRequisition *requisition)
270 AspectBin *abin = ASPECT_BIN(widget);
271 GtkRequisition creq = {0}, areq = {0};
273 if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
274 gtk_widget_size_request(abin->side, &creq);
277 if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) {
279 gtk_widget_size_request(abin->body, &areq);
280 wtmp = areq.height * abin->ratio + 0.5;
281 htmp = areq.width / abin->ratio + 0.5;
283 if (wtmp > areq.width) {
290 requisition->width = areq.width + creq.width;
291 requisition->height = MAX(areq.height, creq.height);
295 aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
297 AspectBin *abin = ASPECT_BIN(widget);
298 GtkRequisition creq = {0};
299 GtkAllocation csize = {0}, asize = {0};
301 /* First find the best fit for the body. */
302 if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) {
303 asize.height = allocation->height;
304 asize.width = asize.height * abin->ratio + 0.5;
306 if (asize.width > allocation->width) {
307 asize.width = allocation->width;
308 asize.height = asize.width / abin->ratio + 0.5;
312 /* Now try to fit the side. */
313 if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
314 gtk_widget_get_child_requisition(abin->side, &creq);
316 if (allocation->width - asize.width < creq.width) {
317 /* It didn't fit, squish the constrained guy. */
318 asize.width = allocation->width - creq.width;
319 asize.height = asize.width / abin->ratio + 0.5;
322 csize.width = allocation->width - asize.width;
323 csize.x = asize.width;
325 if (abin->fill && abin->constrain) {
326 csize.height = asize.height;
327 } else if (abin->fill) {
328 csize.height = allocation->height;
330 csize.height = MIN(creq.height, allocation->height);
334 asize.y = (allocation->height - asize.height) * abin->body_align + 0.5;
336 if (abin->constrain && csize.height <= asize.height) {
337 csize.y = asize.y + (asize.height-csize.height)
338 * abin->side_align + 0.5;
339 } else if (abin->constrain) {
340 csize.y = (allocation->height - csize.height)
341 * abin->body_align + 0.5;
343 csize.y = (allocation->height - csize.height)
344 * abin->side_align + 0.5;
348 gtk_widget_size_allocate(abin->body, &asize);
350 gtk_widget_size_allocate(abin->side, &csize);
354 set_widget(GtkWidget **dest, GtkWidget *parent, GtkWidget *widget)
356 gboolean need_resize = FALSE;
362 need_resize |= GTK_WIDGET_VISIBLE(*dest);
363 gtk_widget_unparent(*dest);
368 gtk_widget_set_parent(widget, parent);
369 need_resize |= GTK_WIDGET_VISIBLE(widget);
372 return GTK_WIDGET_VISIBLE(parent) && need_resize;
375 void aspect_bin_set_body(AspectBin *abin, GtkWidget *widget, gfloat ratio)
377 g_return_if_fail(IS_ASPECT_BIN(abin));
378 g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
379 g_return_if_fail(widget == NULL || widget->parent == NULL);
381 if (set_widget(&abin->body, GTK_WIDGET(abin), widget))
382 gtk_widget_queue_resize(GTK_WIDGET(abin));
385 void aspect_bin_set_side(AspectBin *abin, GtkWidget *widget)
387 g_return_if_fail(IS_ASPECT_BIN(abin));
388 g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
389 g_return_if_fail(widget == NULL || widget->parent == NULL);
391 if (set_widget(&abin->side, GTK_WIDGET(abin), widget))
392 gtk_widget_queue_resize(GTK_WIDGET(abin));
395 GtkWidget *aspect_bin_get_body(AspectBin *abin)
397 g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
401 GtkWidget *aspect_bin_get_side(AspectBin *abin)
403 g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);