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