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"
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
*);
41 static void aspect_bin_get_property(GObject
*, guint
, GValue
*, GParamSpec
*);
42 static void aspect_bin_set_property(GObject
*, guint
, const GValue
*,
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
*);
49 G_DEFINE_TYPE(AspectBin
, aspect_bin
, GTK_TYPE_CONTAINER
)
51 static void aspect_bin_init(AspectBin
*abin
)
53 GTK_WIDGET_SET_FLAGS(abin
, GTK_NO_WINDOW
);
58 abin
->body_align
= 0.5;
59 abin
->side_align
= 0.5;
60 abin
->constrain
= FALSE
;
64 static void aspect_bin_class_init(AspectBinClass
*class)
66 GObjectClass
*object_class
= G_OBJECT_CLASS(class);
67 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(class);
68 GtkContainerClass
*container_class
= GTK_CONTAINER_CLASS(class);
70 widget_class
->size_request
= aspect_bin_size_request
;
71 widget_class
->size_allocate
= aspect_bin_size_allocate
;
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
;
78 container_class
->set_child_property
= aspect_bin_set_child_property
;
79 container_class
->get_child_property
= aspect_bin_get_child_property
;
81 object_class
->set_property
= aspect_bin_set_property
;
82 object_class
->get_property
= aspect_bin_get_property
;
84 g_object_class_install_property(object_class
,
86 g_param_spec_boolean("fill",
88 "Allocate all remaining space to the side.",
91 g_object_class_install_property(object_class
,
93 g_param_spec_boolean("constrain",
95 "Try not to place the side beyond the body's edges.",
98 gtk_container_class_install_child_property(container_class
,
100 g_param_spec_float("align",
102 "Alignment of the child within the available space.",
107 GtkWidget
*aspect_bin_new(void)
109 return GTK_WIDGET(g_object_new(ASPECT_BIN_TYPE
, NULL
));
112 static void aspect_bin_set_property(GObject
*object
,
117 AspectBin
*abin
= ASPECT_BIN(object
);
121 abin
->constrain
= g_value_get_boolean(value
);
122 gtk_widget_queue_resize(GTK_WIDGET(abin
));
125 abin
->fill
= g_value_get_boolean(value
);
126 gtk_widget_queue_resize(GTK_WIDGET(abin
));
129 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
133 static void aspect_bin_get_property(GObject
*object
,
138 AspectBin
*abin
= ASPECT_BIN(object
);
142 g_value_set_boolean(value
, abin
->constrain
);
145 g_value_set_boolean(value
, abin
->fill
);
148 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
152 static void aspect_bin_set_child_property(GtkContainer
*container
,
158 AspectBin
*abin
= ASPECT_BIN(container
);
161 g_assert(child
== abin
->body
|| child
== abin
->side
);
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
));
173 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container
,
178 static void aspect_bin_get_child_property(GtkContainer
*container
,
184 AspectBin
*abin
= ASPECT_BIN(container
);
187 g_assert(child
== abin
->body
|| child
== abin
->side
);
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
);
198 GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container
,
203 static void aspect_bin_add(GtkContainer
*container
, GtkWidget
*widget
)
206 g_return_if_fail(IS_ASPECT_BIN(container
));
207 abin
= ASPECT_BIN(container
);
210 aspect_bin_set_body(abin
, widget
, 1);
211 else if (!abin
->side
)
212 aspect_bin_set_side(abin
, widget
);
214 g_warning("AspectBin cannot have more than 2 children.\n");
217 static void aspect_bin_remove(GtkContainer
*container
, GtkWidget
*child
)
219 AspectBin
*abin
= ASPECT_BIN(container
);
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
);
228 static void aspect_bin_forall(GtkContainer
*container
,
229 gboolean include_internals
,
230 GtkCallback callback
,
231 gpointer callback_data
)
233 AspectBin
*abin
= ASPECT_BIN(container
);
234 g_return_if_fail(callback
!= NULL
);
237 callback(abin
->body
, callback_data
);
239 callback(abin
->side
, callback_data
);
242 static GType
aspect_bin_child_type(GtkContainer
*container
)
244 if (!ASPECT_BIN(container
)->body
|| !ASPECT_BIN(container
)->side
)
245 return GTK_TYPE_WIDGET
;
250 aspect_bin_size_request(GtkWidget
*widget
, GtkRequisition
*requisition
)
252 AspectBin
*abin
= ASPECT_BIN(widget
);
253 GtkRequisition creq
= {0}, areq
= {0};
255 if (abin
->side
&& GTK_WIDGET_VISIBLE(abin
->side
)) {
256 gtk_widget_size_request(abin
->side
, &creq
);
259 if (abin
->body
&& GTK_WIDGET_VISIBLE(abin
->body
)) {
261 gtk_widget_size_request(abin
->body
, &areq
);
262 wtmp
= areq
.height
* abin
->ratio
+ 0.5;
263 htmp
= areq
.width
/ abin
->ratio
+ 0.5;
265 if (wtmp
> areq
.width
) {
272 requisition
->width
= areq
.width
+ creq
.width
;
273 requisition
->height
= MAX(areq
.height
, creq
.height
);
277 aspect_bin_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
279 AspectBin
*abin
= ASPECT_BIN(widget
);
280 GtkRequisition creq
= {0};
281 GtkAllocation csize
= {0}, asize
= {0};
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;
288 if (asize
.width
> allocation
->width
) {
289 asize
.width
= allocation
->width
;
290 asize
.height
= asize
.width
/ abin
->ratio
+ 0.5;
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
);
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;
304 csize
.width
= allocation
->width
- asize
.width
;
305 csize
.x
= asize
.width
;
307 if (abin
->fill
&& abin
->constrain
) {
308 csize
.height
= asize
.height
;
309 } else if (abin
->fill
) {
310 csize
.height
= allocation
->height
;
312 csize
.height
= MIN(creq
.height
, allocation
->height
);
316 asize
.y
= (allocation
->height
- asize
.height
) * abin
->body_align
+ 0.5;
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;
325 csize
.y
= (allocation
->height
- csize
.height
)
326 * abin
->side_align
+ 0.5;
330 gtk_widget_size_allocate(abin
->body
, &asize
);
332 gtk_widget_size_allocate(abin
->side
, &csize
);
336 set_widget(GtkWidget
**dest
, GtkWidget
*parent
, GtkWidget
*widget
)
338 gboolean need_resize
= FALSE
;
344 need_resize
|= GTK_WIDGET_VISIBLE(*dest
);
345 gtk_widget_unparent(*dest
);
350 gtk_widget_set_parent(widget
, parent
);
351 need_resize
|= GTK_WIDGET_VISIBLE(widget
);
354 return GTK_WIDGET_VISIBLE(parent
) && need_resize
;
357 void aspect_bin_set_body(AspectBin
*abin
, GtkWidget
*widget
, gfloat ratio
)
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
);
363 if (set_widget(&abin
->body
, GTK_WIDGET(abin
), widget
))
364 gtk_widget_queue_resize(GTK_WIDGET(abin
));
367 void aspect_bin_set_side(AspectBin
*abin
, GtkWidget
*widget
)
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
);
373 if (set_widget(&abin
->side
, GTK_WIDGET(abin
), widget
))
374 gtk_widget_queue_resize(GTK_WIDGET(abin
));
377 GtkWidget
*aspect_bin_get_body(AspectBin
*abin
)
379 g_return_val_if_fail(IS_ASPECT_BIN(abin
), NULL
);
383 GtkWidget
*aspect_bin_get_side(AspectBin
*abin
)
385 g_return_val_if_fail(IS_ASPECT_BIN(abin
), NULL
);