Update .gitignore.
[aspectbin.git] / aspectbin.c
blobbc74a67d3c4558f08f26623926841854915d28d0
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.
20 #include <stdio.h>
21 #include "aspectbin.h"
23 enum {
24 PROP_0,
25 PROP_RATIO,
26 PROP_CONSTRAIN,
27 PROP_FILL,
30 enum {
31 CHILD_PROP_0,
32 CHILD_PROP_ALIGN,
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 *,
44 GParamSpec *);
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);
56 abin->body = NULL;
57 abin->side = NULL;
58 abin->ratio = 1;
59 abin->body_align = 0.5;
60 abin->side_align = 0.5;
61 abin->constrain = FALSE;
62 abin->fill = TRUE;
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,
86 PROP_RATIO,
87 g_param_spec_float("ratio",
88 "Aspect Ratio",
89 "Width:height ratio of the body.",
90 0.2, 5.0, 1.0,
91 G_PARAM_READWRITE));
92 g_object_class_install_property(object_class,
93 PROP_FILL,
94 g_param_spec_boolean("fill",
95 "Fill",
96 "Allocate all remaining space to the side.",
97 TRUE,
98 G_PARAM_READWRITE));
99 g_object_class_install_property(object_class,
100 PROP_CONSTRAIN,
101 g_param_spec_boolean("constrain",
102 "Constrain",
103 "Try not to place the side beyond the body's edges.",
104 FALSE,
105 G_PARAM_READWRITE));
106 gtk_container_class_install_child_property(container_class,
107 CHILD_PROP_ALIGN,
108 g_param_spec_float("align",
109 "Alignment",
110 "Alignment of the child within the available space.",
111 0.0, 1.0, 0.5,
112 G_PARAM_READWRITE));
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,
121 guint prop_id,
122 const GValue *value,
123 GParamSpec *pspec)
125 AspectBin *abin = ASPECT_BIN(object);
126 gfloat tmp;
128 switch (prop_id) {
129 case PROP_RATIO:
130 tmp = abin->ratio;
131 abin->ratio = g_value_get_float(value);
132 if (tmp != abin->ratio)
133 gtk_widget_queue_resize(GTK_WIDGET(abin));
134 break;
135 case PROP_CONSTRAIN:
136 abin->constrain = g_value_get_boolean(value);
137 gtk_widget_queue_resize(GTK_WIDGET(abin));
138 break;
139 case PROP_FILL:
140 abin->fill = g_value_get_boolean(value);
141 gtk_widget_queue_resize(GTK_WIDGET(abin));
142 break;
143 default:
144 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
148 static void aspect_bin_get_property(GObject *object,
149 guint prop_id,
150 GValue *value,
151 GParamSpec *pspec)
153 AspectBin *abin = ASPECT_BIN(object);
155 switch (prop_id) {
156 case PROP_RATIO:
157 g_value_set_float(value, abin->ratio);
158 break;
159 case PROP_CONSTRAIN:
160 g_value_set_boolean(value, abin->constrain);
161 break;
162 case PROP_FILL:
163 g_value_set_boolean(value, abin->fill);
164 break;
165 default:
166 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
170 static void aspect_bin_set_child_property(GtkContainer *container,
171 GtkWidget *child,
172 guint prop_id,
173 const GValue *value,
174 GParamSpec *pspec)
176 AspectBin *abin = ASPECT_BIN(container);
177 gfloat align;
179 g_assert(child == abin->body || child == abin->side);
181 switch (prop_id) {
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));
189 break;
190 default:
191 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
192 prop_id, pspec);
196 static void aspect_bin_get_child_property(GtkContainer *container,
197 GtkWidget *child,
198 guint prop_id,
199 GValue *value,
200 GParamSpec *pspec)
202 AspectBin *abin = ASPECT_BIN(container);
203 gfloat align = 0;
205 g_assert(child == abin->body || child == abin->side);
207 switch (prop_id) {
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);
214 break;
215 default:
216 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
217 prop_id, pspec);
221 static void aspect_bin_add(GtkContainer *container, GtkWidget *widget)
223 AspectBin *abin;
224 g_return_if_fail(IS_ASPECT_BIN(container));
225 abin = ASPECT_BIN(container);
227 if (!abin->body)
228 aspect_bin_set_body(abin, widget, 1);
229 else if (!abin->side)
230 aspect_bin_set_side(abin, widget);
231 else
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);
254 if (abin->body)
255 callback(abin->body, callback_data);
256 if (abin->side)
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;
264 return G_TYPE_NONE;
267 static void
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)) {
278 int wtmp, htmp;
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) {
284 areq.width = wtmp;
285 } else {
286 areq.height = htmp;
290 requisition->width = areq.width + creq.width;
291 requisition->height = MAX(areq.height, creq.height);
294 static void
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;
329 } else {
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;
342 } else {
343 csize.y = (allocation->height - csize.height)
344 * abin->side_align + 0.5;
347 if (abin->body)
348 gtk_widget_size_allocate(abin->body, &asize);
349 if (abin->side)
350 gtk_widget_size_allocate(abin->side, &csize);
353 static gboolean
354 set_widget(GtkWidget **dest, GtkWidget *parent, GtkWidget *widget)
356 gboolean need_resize = FALSE;
358 if (*dest == widget)
359 return FALSE;
361 if (*dest) {
362 need_resize |= GTK_WIDGET_VISIBLE(*dest);
363 gtk_widget_unparent(*dest);
366 *dest = widget;
367 if (widget) {
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);
398 return abin->body;
401 GtkWidget *aspect_bin_get_side(AspectBin *abin)
403 g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
404 return abin->side;