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