]> git.draconx.ca Git - aspectbin.git/blob - aspectbin.c
Update .gitignore.
[aspectbin.git] / aspectbin.c
1 /* AspectBin - A GTK+ container for packing with consrained aspect ratio.
2  * Copyright (C) 2009 Nick Bowler
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #include <stdio.h>
21 #include "aspectbin.h"
22
23 enum {
24         PROP_0,
25         PROP_RATIO,
26         PROP_CONSTRAIN,
27         PROP_FILL,
28 };
29
30 enum {
31         CHILD_PROP_0,
32         CHILD_PROP_ALIGN,
33 };
34
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 *);
41
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 *);
49
50 G_DEFINE_TYPE(AspectBin, aspect_bin, GTK_TYPE_CONTAINER)
51
52 static void aspect_bin_init(AspectBin *abin)
53 {
54         GTK_WIDGET_SET_FLAGS(abin, GTK_NO_WINDOW);
55
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;
63 }
64
65 static void aspect_bin_class_init(AspectBinClass *class)
66 {
67         GObjectClass      *object_class    = G_OBJECT_CLASS(class);
68         GtkWidgetClass    *widget_class    = GTK_WIDGET_CLASS(class);
69         GtkContainerClass *container_class = GTK_CONTAINER_CLASS(class);
70
71         widget_class->size_request  = aspect_bin_size_request;
72         widget_class->size_allocate = aspect_bin_size_allocate;
73
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;
78
79         container_class->set_child_property = aspect_bin_set_child_property;
80         container_class->get_child_property = aspect_bin_get_child_property;
81
82         object_class->set_property = aspect_bin_set_property;
83         object_class->get_property = aspect_bin_get_property;
84
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));
113 }
114
115 GtkWidget *aspect_bin_new(void)
116 {
117         return GTK_WIDGET(g_object_new(ASPECT_BIN_TYPE, NULL));
118 }
119
120 static void aspect_bin_set_property(GObject      *object,
121                                     guint         prop_id,
122                                     const GValue *value,
123                                     GParamSpec   *pspec)
124 {
125         AspectBin *abin = ASPECT_BIN(object);
126         gfloat tmp;
127
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);
145         }
146 }
147
148 static void aspect_bin_get_property(GObject    *object,
149                                     guint       prop_id,
150                                     GValue     *value,
151                                     GParamSpec *pspec)
152 {
153         AspectBin *abin = ASPECT_BIN(object);
154
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);
167         }
168 }
169
170 static void aspect_bin_set_child_property(GtkContainer *container,
171                                           GtkWidget    *child,
172                                           guint         prop_id,
173                                           const GValue *value,
174                                           GParamSpec   *pspec)
175 {
176         AspectBin *abin = ASPECT_BIN(container);
177         gfloat align;
178
179         g_assert(child == abin->body || child == abin->side);
180
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);
193         }
194 }
195
196 static void aspect_bin_get_child_property(GtkContainer *container,
197                                           GtkWidget    *child,
198                                           guint         prop_id,
199                                           GValue       *value,
200                                           GParamSpec   *pspec)
201 {
202         AspectBin *abin = ASPECT_BIN(container);
203         gfloat align = 0;
204
205         g_assert(child == abin->body || child == abin->side);
206
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);
218         }
219 }
220
221 static void aspect_bin_add(GtkContainer *container, GtkWidget *widget)
222 {
223         AspectBin *abin;
224         g_return_if_fail(IS_ASPECT_BIN(container));
225         abin = ASPECT_BIN(container);
226
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");
233 }
234
235 static void aspect_bin_remove(GtkContainer *container, GtkWidget *child)
236 {
237         AspectBin *abin = ASPECT_BIN(container);
238
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);
243         }
244 }
245
246 static void aspect_bin_forall(GtkContainer *container,
247                               gboolean      include_internals,
248                               GtkCallback   callback,
249                               gpointer      callback_data)
250 {
251         AspectBin *abin = ASPECT_BIN(container);
252         g_return_if_fail(callback != NULL);
253
254         if (abin->body)
255                 callback(abin->body, callback_data);
256         if (abin->side)
257                 callback(abin->side, callback_data);
258 }
259
260 static GType aspect_bin_child_type(GtkContainer *container)
261 {
262         if (!ASPECT_BIN(container)->body || !ASPECT_BIN(container)->side)
263                 return GTK_TYPE_WIDGET;
264         return G_TYPE_NONE;
265 }
266
267 static void
268 aspect_bin_size_request(GtkWidget *widget, GtkRequisition *requisition)
269 {
270         AspectBin *abin = ASPECT_BIN(widget);
271         GtkRequisition creq = {0}, areq = {0};
272
273         if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
274                 gtk_widget_size_request(abin->side, &creq);
275         }
276
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;
282
283                 if (wtmp > areq.width) {
284                         areq.width  = wtmp;
285                 } else {
286                         areq.height = htmp;
287                 }
288         }
289
290         requisition->width  = areq.width + creq.width;
291         requisition->height = MAX(areq.height, creq.height);
292 }
293
294 static void
295 aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
296 {
297         AspectBin *abin = ASPECT_BIN(widget);
298         GtkRequisition creq = {0};
299         GtkAllocation csize = {0}, asize = {0};
300
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;
305
306                 if (asize.width > allocation->width) {
307                         asize.width  = allocation->width;
308                         asize.height = asize.width / abin->ratio + 0.5;
309                 }
310         }
311
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);
315
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;
320                 }
321
322                 csize.width  = allocation->width - asize.width;
323                 csize.x = asize.width;
324
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);
331                 }
332         }
333
334         asize.y = (allocation->height - asize.height) * abin->body_align + 0.5;
335
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;
345         }
346
347         if (abin->body)
348                 gtk_widget_size_allocate(abin->body, &asize);
349         if (abin->side)
350                 gtk_widget_size_allocate(abin->side, &csize);
351 }
352
353 static gboolean
354 set_widget(GtkWidget **dest, GtkWidget *parent, GtkWidget *widget)
355 {
356         gboolean need_resize = FALSE;
357
358         if (*dest == widget)
359                 return FALSE;
360
361         if (*dest) {
362                 need_resize |= GTK_WIDGET_VISIBLE(*dest);
363                 gtk_widget_unparent(*dest);
364         }
365
366         *dest = widget;
367         if (widget) {
368                 gtk_widget_set_parent(widget, parent);
369                 need_resize |= GTK_WIDGET_VISIBLE(widget);
370         }
371
372         return GTK_WIDGET_VISIBLE(parent) && need_resize;
373 }
374
375 void aspect_bin_set_body(AspectBin *abin, GtkWidget *widget, gfloat ratio)
376 {
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);
380
381         if (set_widget(&abin->body, GTK_WIDGET(abin), widget))
382                 gtk_widget_queue_resize(GTK_WIDGET(abin));
383 }
384
385 void aspect_bin_set_side(AspectBin *abin, GtkWidget *widget)
386 {
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);
390
391         if (set_widget(&abin->side, GTK_WIDGET(abin), widget))
392                 gtk_widget_queue_resize(GTK_WIDGET(abin));
393 }
394
395 GtkWidget *aspect_bin_get_body(AspectBin *abin)
396 {
397         g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
398         return abin->body;
399 }
400
401 GtkWidget *aspect_bin_get_side(AspectBin *abin)
402 {
403         g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
404         return abin->side;
405 }