1 /* AspectBin - A GTK+ container for packing with consrained aspect ratio.
2 * Copyright (C) 2009 Nick Bowler
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.
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.
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.
21 #include "aspectbin.h"
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
*);
42 static void aspect_bin_get_property(GObject
*, guint
, GValue
*, GParamSpec
*);
43 static void aspect_bin_set_property(GObject
*, guint
, const GValue
*,
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
*);
50 G_DEFINE_TYPE(AspectBin
, aspect_bin
, GTK_TYPE_CONTAINER
)
52 static void aspect_bin_init(AspectBin
*abin
)
54 GTK_WIDGET_SET_FLAGS(abin
, GTK_NO_WINDOW
);
59 abin
->body_align
= 0.5;
60 abin
->side_align
= 0.5;
61 abin
->constrain
= FALSE
;
65 static void aspect_bin_class_init(AspectBinClass
*class)
67 GObjectClass
*object_class
= G_OBJECT_CLASS(class);
68 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(class);
69 GtkContainerClass
*container_class
= GTK_CONTAINER_CLASS(class);
71 widget_class
->size_request
= aspect_bin_size_request
;
72 widget_class
->size_allocate
= aspect_bin_size_allocate
;
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
;
79 container_class
->set_child_property
= aspect_bin_set_child_property
;
80 container_class
->get_child_property
= aspect_bin_get_child_property
;
82 object_class
->set_property
= aspect_bin_set_property
;
83 object_class
->get_property
= aspect_bin_get_property
;
85 g_object_class_install_property(object_class
,
87 g_param_spec_float("ratio",
89 "Width:height ratio of the body.",
92 g_object_class_install_property(object_class
,
94 g_param_spec_boolean("fill",
96 "Allocate all remaining space to the side.",
99 g_object_class_install_property(object_class
,
101 g_param_spec_boolean("constrain",
103 "Try not to place the side beyond the body's edges.",
106 gtk_container_class_install_child_property(container_class
,
108 g_param_spec_float("align",
110 "Alignment of the child within the available space.",
115 GtkWidget
*aspect_bin_new(void)
117 return GTK_WIDGET(g_object_new(ASPECT_BIN_TYPE
, NULL
));
120 static void aspect_bin_set_property(GObject
*object
,
125 AspectBin
*abin
= ASPECT_BIN(object
);
131 abin
->ratio
= g_value_get_float(value
);
132 if (tmp
!= abin
->ratio
)
133 gtk_widget_queue_resize(GTK_WIDGET(abin
));
136 abin
->constrain
= g_value_get_boolean(value
);
137 gtk_widget_queue_resize(GTK_WIDGET(abin
));
140 abin
->fill
= g_value_get_boolean(value
);
141 gtk_widget_queue_resize(GTK_WIDGET(abin
));
144 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
148 static void aspect_bin_get_property(GObject
*object
,
153 AspectBin
*abin
= ASPECT_BIN(object
);
157 g_value_set_float(value
, abin
->ratio
);
160 g_value_set_boolean(value
, abin
->constrain
);
163 g_value_set_boolean(value
, abin
->fill
);
166 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
170 static void aspect_bin_set_child_property(GtkContainer
*container
,
176 AspectBin
*abin
= ASPECT_BIN(container
);
179 g_assert(child
== abin
->body
|| child
== abin
->side
);
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
));
191 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container
,
196 static void aspect_bin_get_child_property(GtkContainer
*container
,
202 AspectBin
*abin
= ASPECT_BIN(container
);
205 g_assert(child
== abin
->body
|| child
== abin
->side
);
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
);
216 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container
,
221 static void aspect_bin_add(GtkContainer
*container
, GtkWidget
*widget
)
224 g_return_if_fail(IS_ASPECT_BIN(container
));
225 abin
= ASPECT_BIN(container
);
228 aspect_bin_set_body(abin
, widget
, 1);
229 else if (!abin
->side
)
230 aspect_bin_set_side(abin
, widget
);
232 g_warning("AspectBin cannot have more than 2 children.\n");
235 static void aspect_bin_remove(GtkContainer
*container
, GtkWidget
*child
)
237 AspectBin
*abin
= ASPECT_BIN(container
);
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
);
246 static void aspect_bin_forall(GtkContainer
*container
,
247 gboolean include_internals
,
248 GtkCallback callback
,
249 gpointer callback_data
)
251 AspectBin
*abin
= ASPECT_BIN(container
);
252 g_return_if_fail(callback
!= NULL
);
255 callback(abin
->body
, callback_data
);
257 callback(abin
->side
, callback_data
);
260 static GType
aspect_bin_child_type(GtkContainer
*container
)
262 if (!ASPECT_BIN(container
)->body
|| !ASPECT_BIN(container
)->side
)
263 return GTK_TYPE_WIDGET
;
268 aspect_bin_size_request(GtkWidget
*widget
, GtkRequisition
*requisition
)
270 AspectBin
*abin
= ASPECT_BIN(widget
);
271 GtkRequisition creq
= {0}, areq
= {0};
273 if (abin
->side
&& GTK_WIDGET_VISIBLE(abin
->side
)) {
274 gtk_widget_size_request(abin
->side
, &creq
);
277 if (abin
->body
&& GTK_WIDGET_VISIBLE(abin
->body
)) {
279 gtk_widget_size_request(abin
->body
, &areq
);
280 wtmp
= areq
.height
* abin
->ratio
+ 0.5;
281 htmp
= areq
.width
/ abin
->ratio
+ 0.5;
283 if (wtmp
> areq
.width
) {
290 requisition
->width
= areq
.width
+ creq
.width
;
291 requisition
->height
= MAX(areq
.height
, creq
.height
);
295 aspect_bin_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
297 AspectBin
*abin
= ASPECT_BIN(widget
);
298 GtkRequisition creq
= {0};
299 GtkAllocation csize
= {0}, asize
= {0};
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;
306 if (asize
.width
> allocation
->width
) {
307 asize
.width
= allocation
->width
;
308 asize
.height
= asize
.width
/ abin
->ratio
+ 0.5;
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
);
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;
322 csize
.width
= allocation
->width
- asize
.width
;
323 csize
.x
= asize
.width
;
325 if (abin
->fill
&& abin
->constrain
) {
326 csize
.height
= asize
.height
;
327 } else if (abin
->fill
) {
328 csize
.height
= allocation
->height
;
330 csize
.height
= MIN(creq
.height
, allocation
->height
);
334 asize
.y
= (allocation
->height
- asize
.height
) * abin
->body_align
+ 0.5;
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;
343 csize
.y
= (allocation
->height
- csize
.height
)
344 * abin
->side_align
+ 0.5;
348 gtk_widget_size_allocate(abin
->body
, &asize
);
350 gtk_widget_size_allocate(abin
->side
, &csize
);
354 set_widget(GtkWidget
**dest
, GtkWidget
*parent
, GtkWidget
*widget
)
356 gboolean need_resize
= FALSE
;
362 need_resize
|= GTK_WIDGET_VISIBLE(*dest
);
363 gtk_widget_unparent(*dest
);
368 gtk_widget_set_parent(widget
, parent
);
369 need_resize
|= GTK_WIDGET_VISIBLE(widget
);
372 return GTK_WIDGET_VISIBLE(parent
) && need_resize
;
375 void aspect_bin_set_body(AspectBin
*abin
, GtkWidget
*widget
, gfloat ratio
)
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
);
381 if (set_widget(&abin
->body
, GTK_WIDGET(abin
), widget
))
382 gtk_widget_queue_resize(GTK_WIDGET(abin
));
385 void aspect_bin_set_side(AspectBin
*abin
, GtkWidget
*widget
)
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
);
391 if (set_widget(&abin
->side
, GTK_WIDGET(abin
), widget
))
392 gtk_widget_queue_resize(GTK_WIDGET(abin
));
395 GtkWidget
*aspect_bin_get_body(AspectBin
*abin
)
397 g_return_val_if_fail(IS_ASPECT_BIN(abin
), NULL
);
401 GtkWidget
*aspect_bin_get_side(AspectBin
*abin
)
403 g_return_val_if_fail(IS_ASPECT_BIN(abin
), NULL
);