]> git.draconx.ca Git - aspectbin.git/blob - aspectbin.c
Add a short readme.
[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_CONSTRAIN,
26         PROP_FILL,
27 };
28
29 enum {
30         CHILD_PROP_0,
31         CHILD_PROP_ALIGN,
32 };
33
34 static void aspect_bin_size_request(GtkWidget *, GtkRequisition *);
35 static void aspect_bin_size_allocate(GtkWidget *, GtkAllocation *);
36 static void aspect_bin_add(GtkContainer *, GtkWidget *);
37 static void aspect_bin_remove(GtkContainer *, GtkWidget *);
38 static void aspect_bin_forall(GtkContainer *, gboolean, GtkCallback, gpointer);
39 static GType aspect_bin_child_type(GtkContainer *);
40
41 static void aspect_bin_get_property(GObject *, guint, GValue *, GParamSpec *);
42 static void aspect_bin_set_property(GObject *, guint, const GValue *,
43                                                           GParamSpec *);
44 static void aspect_bin_set_child_property(GtkContainer *, GtkWidget *, guint,
45                                                 const GValue *, GParamSpec *);
46 static void aspect_bin_get_child_property(GtkContainer *, GtkWidget *, guint,
47                                                       GValue *, GParamSpec *);
48
49 G_DEFINE_TYPE(AspectBin, aspect_bin, GTK_TYPE_CONTAINER)
50
51 static void aspect_bin_init(AspectBin *abin)
52 {
53         GTK_WIDGET_SET_FLAGS(abin, GTK_NO_WINDOW);
54
55         abin->body       = NULL;
56         abin->side       = NULL;
57         abin->ratio      = 1;
58         abin->body_align = 0.5;
59         abin->side_align = 0.5;
60         abin->constrain  = FALSE;
61         abin->fill       = TRUE;
62 }
63
64 static void aspect_bin_class_init(AspectBinClass *class)
65 {
66         GObjectClass      *object_class    = G_OBJECT_CLASS(class);
67         GtkWidgetClass    *widget_class    = GTK_WIDGET_CLASS(class);
68         GtkContainerClass *container_class = GTK_CONTAINER_CLASS(class);
69
70         widget_class->size_request  = aspect_bin_size_request;
71         widget_class->size_allocate = aspect_bin_size_allocate;
72
73         container_class->add        = aspect_bin_add;
74         container_class->remove     = aspect_bin_remove;
75         container_class->forall     = aspect_bin_forall;
76         container_class->child_type = aspect_bin_child_type;
77
78         container_class->set_child_property = aspect_bin_set_child_property;
79         container_class->get_child_property = aspect_bin_get_child_property;
80
81         object_class->set_property = aspect_bin_set_property;
82         object_class->get_property = aspect_bin_get_property;
83
84         g_object_class_install_property(object_class,
85                 PROP_FILL,
86                 g_param_spec_boolean("fill",
87                         "Fill",
88                         "Allocate all remaining space to the side.",
89                         TRUE,
90                         G_PARAM_READWRITE));
91         g_object_class_install_property(object_class,
92                 PROP_CONSTRAIN,
93                 g_param_spec_boolean("constrain",
94                         "Constrain",
95                         "Try not to place the side beyond the body's edges.",
96                         FALSE,
97                         G_PARAM_READWRITE));
98         gtk_container_class_install_child_property(container_class,
99                 CHILD_PROP_ALIGN,
100                 g_param_spec_float("align",
101                         "Alignment",
102                         "Alignment of the child within the available space.",
103                         0.0, 1.0, 0.5,
104                         G_PARAM_READWRITE));
105 }
106
107 GtkWidget *aspect_bin_new(void)
108 {
109         return GTK_WIDGET(g_object_new(ASPECT_BIN_TYPE, NULL));
110 }
111
112 static void aspect_bin_set_property(GObject      *object,
113                                     guint         prop_id,
114                                     const GValue *value,
115                                     GParamSpec   *pspec)
116 {
117         AspectBin *abin = ASPECT_BIN(object);
118
119         switch (prop_id) {
120         case PROP_CONSTRAIN:
121                 abin->constrain = g_value_get_boolean(value);
122                 gtk_widget_queue_resize(GTK_WIDGET(abin));
123                 break;
124         case PROP_FILL:
125                 abin->fill = g_value_get_boolean(value);
126                 gtk_widget_queue_resize(GTK_WIDGET(abin));
127                 break;
128         default:
129                 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
130         }
131 }
132
133 static void aspect_bin_get_property(GObject    *object,
134                                     guint       prop_id,
135                                     GValue     *value,
136                                     GParamSpec *pspec)
137 {
138         AspectBin *abin = ASPECT_BIN(object);
139
140         switch (prop_id) {
141         case PROP_CONSTRAIN:
142                 g_value_set_boolean(value, abin->constrain);
143                 break;
144         case PROP_FILL:
145                 g_value_set_boolean(value, abin->fill);
146                 break;
147         default:
148                 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
149         }
150 }
151
152 static void aspect_bin_set_child_property(GtkContainer *container,
153                                           GtkWidget    *child,
154                                           guint         prop_id,
155                                           const GValue *value,
156                                           GParamSpec   *pspec)
157 {
158         AspectBin *abin = ASPECT_BIN(container);
159         gfloat align;
160
161         g_assert(child == abin->body || child == abin->side);
162
163         switch (prop_id) {
164         case CHILD_PROP_ALIGN:
165                 align = g_value_get_float(value);
166                 if (child == abin->body)
167                         abin->body_align = align;
168                 else if (child == abin->side)
169                         abin->side_align = align;
170                 gtk_widget_queue_resize(GTK_WIDGET(abin));
171                 break;
172         default:
173                 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
174                                                              prop_id, pspec);
175         }
176 }
177
178 static void aspect_bin_get_child_property(GtkContainer *container,
179                                           GtkWidget    *child,
180                                           guint         prop_id,
181                                           GValue       *value,
182                                           GParamSpec   *pspec)
183 {
184         AspectBin *abin = ASPECT_BIN(container);
185         gfloat align = 0;
186
187         g_assert(child == abin->body || child == abin->side);
188
189         switch (prop_id) {
190         case CHILD_PROP_ALIGN:
191                 if (child == abin->body)
192                         align = abin->body_align;
193                 else if (child == abin->side)
194                         align = abin->side_align;
195                 g_value_set_float(value, align);
196                 break;
197         default:
198                 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
199                                                              prop_id, pspec);
200         }
201 }
202
203 static void aspect_bin_add(GtkContainer *container, GtkWidget *widget)
204 {
205         AspectBin *abin;
206         g_return_if_fail(IS_ASPECT_BIN(container));
207         abin = ASPECT_BIN(container);
208
209         if (!abin->body)
210                 aspect_bin_set_body(abin, widget, 1);
211         else if (!abin->side)
212                 aspect_bin_set_side(abin, widget);
213         else
214                 g_warning("AspectBin cannot have more than 2 children.\n");
215 }
216
217 static void aspect_bin_remove(GtkContainer *container, GtkWidget *child)
218 {
219         AspectBin *abin = ASPECT_BIN(container);
220
221         if (abin->body == child) {
222                 aspect_bin_set_body(abin, NULL, 1);
223         } else if (abin->side == child) {
224                 aspect_bin_set_side(abin, NULL);
225         }
226 }
227
228 static void aspect_bin_forall(GtkContainer *container,
229                               gboolean      include_internals,
230                               GtkCallback   callback,
231                               gpointer      callback_data)
232 {
233         AspectBin *abin = ASPECT_BIN(container);
234         g_return_if_fail(callback != NULL);
235
236         if (abin->body)
237                 callback(abin->body, callback_data);
238         if (abin->side)
239                 callback(abin->side, callback_data);
240 }
241
242 static GType aspect_bin_child_type(GtkContainer *container)
243 {
244         if (!ASPECT_BIN(container)->body || !ASPECT_BIN(container)->side)
245                 return GTK_TYPE_WIDGET;
246         return G_TYPE_NONE;
247 }
248
249 static void
250 aspect_bin_size_request(GtkWidget *widget, GtkRequisition *requisition)
251 {
252         AspectBin *abin = ASPECT_BIN(widget);
253         GtkRequisition creq = {0}, areq = {0};
254
255         if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
256                 gtk_widget_size_request(abin->side, &creq);
257         }
258
259         if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) {
260                 int wtmp, htmp;
261                 gtk_widget_size_request(abin->body, &areq);
262                 wtmp = areq.height * abin->ratio + 0.5;
263                 htmp = areq.width  / abin->ratio + 0.5;
264
265                 if (wtmp > areq.width) {
266                         areq.width  = wtmp;
267                 } else {
268                         areq.height = htmp;
269                 }
270         }
271
272         requisition->width  = areq.width + creq.width;
273         requisition->height = MAX(areq.height, creq.height);
274 }
275
276 static void
277 aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
278 {
279         AspectBin *abin = ASPECT_BIN(widget);
280         GtkRequisition creq = {0};
281         GtkAllocation csize = {0}, asize = {0};
282
283         /* First find the best fit for the body. */
284         if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) {
285                 asize.height = allocation->height;
286                 asize.width  = asize.height * abin->ratio + 0.5;
287
288                 if (asize.width > allocation->width) {
289                         asize.width  = allocation->width;
290                         asize.height = asize.width / abin->ratio + 0.5;
291                 }
292         }
293
294         /* Now try to fit the side. */
295         if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
296                 gtk_widget_get_child_requisition(abin->side, &creq);
297
298                 if (allocation->width - asize.width < creq.width) {
299                         /* It didn't fit, squish the constrained guy. */
300                         asize.width  = allocation->width - creq.width;
301                         asize.height = asize.width / abin->ratio + 0.5;
302                 }
303
304                 csize.width  = allocation->width - asize.width;
305                 csize.x = asize.width;
306
307                 if (abin->fill && abin->constrain) {
308                         csize.height = asize.height;
309                 } else if (abin->fill) {
310                         csize.height = allocation->height;
311                 } else {
312                         csize.height = MIN(creq.height, allocation->height);
313                 }
314         }
315
316         asize.y = (allocation->height - asize.height) * abin->body_align + 0.5;
317
318         if (abin->constrain && csize.height <= asize.height) {
319                 csize.y = asize.y + (asize.height-csize.height)
320                         * abin->side_align + 0.5;
321         } else if (abin->constrain) {
322                 csize.y = (allocation->height - csize.height)
323                         * abin->body_align + 0.5;
324         } else {
325                 csize.y = (allocation->height - csize.height)
326                         * abin->side_align + 0.5;
327         }
328
329         if (abin->body)
330                 gtk_widget_size_allocate(abin->body, &asize);
331         if (abin->side)
332                 gtk_widget_size_allocate(abin->side, &csize);
333 }
334
335 static gboolean
336 set_widget(GtkWidget **dest, GtkWidget *parent, GtkWidget *widget)
337 {
338         gboolean need_resize = FALSE;
339
340         if (*dest == widget)
341                 return FALSE;
342
343         if (*dest) {
344                 need_resize |= GTK_WIDGET_VISIBLE(*dest);
345                 gtk_widget_unparent(*dest);
346         }
347
348         *dest = widget;
349         if (widget) {
350                 gtk_widget_set_parent(widget, parent);
351                 need_resize |= GTK_WIDGET_VISIBLE(widget);
352         }
353
354         return GTK_WIDGET_VISIBLE(parent) && need_resize;
355 }
356
357 void aspect_bin_set_body(AspectBin *abin, GtkWidget *widget, gfloat ratio)
358 {
359         g_return_if_fail(IS_ASPECT_BIN(abin));
360         g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
361         g_return_if_fail(widget == NULL || widget->parent == NULL);
362
363         if (set_widget(&abin->body, GTK_WIDGET(abin), widget))
364                 gtk_widget_queue_resize(GTK_WIDGET(abin));
365 }
366
367 void aspect_bin_set_side(AspectBin *abin, GtkWidget *widget)
368 {
369         g_return_if_fail(IS_ASPECT_BIN(abin));
370         g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
371         g_return_if_fail(widget == NULL || widget->parent == NULL);
372
373         if (set_widget(&abin->side, GTK_WIDGET(abin), widget))
374                 gtk_widget_queue_resize(GTK_WIDGET(abin));
375 }
376
377 GtkWidget *aspect_bin_get_body(AspectBin *abin)
378 {
379         g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
380         return abin->body;
381 }
382
383 GtkWidget *aspect_bin_get_side(AspectBin *abin)
384 {
385         g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
386         return abin->side;
387 }