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