+/* AspectBin - A GTK+ container for packing with consrained aspect ratio.
+ * Copyright (C) 2009 Nick Bowler
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
#include <stdio.h>
#include "aspectbin.h"
+enum {
+ PROP_0,
+ PROP_RATIO,
+ PROP_CONSTRAIN,
+ PROP_FILL,
+};
+
+enum {
+ CHILD_PROP_0,
+ CHILD_PROP_ALIGN,
+};
+
static void aspect_bin_size_request(GtkWidget *, GtkRequisition *);
static void aspect_bin_size_allocate(GtkWidget *, GtkAllocation *);
+static void aspect_bin_add(GtkContainer *, GtkWidget *);
static void aspect_bin_remove(GtkContainer *, GtkWidget *);
static void aspect_bin_forall(GtkContainer *, gboolean, GtkCallback, gpointer);
+static GType aspect_bin_child_type(GtkContainer *);
-G_DEFINE_TYPE(AspectBin, aspect_bin, GTK_TYPE_BIN)
+static void aspect_bin_get_property(GObject *, guint, GValue *, GParamSpec *);
+static void aspect_bin_set_property(GObject *, guint, const GValue *,
+ GParamSpec *);
+static void aspect_bin_set_child_property(GtkContainer *, GtkWidget *, guint,
+ const GValue *, GParamSpec *);
+static void aspect_bin_get_child_property(GtkContainer *, GtkWidget *, guint,
+ GValue *, GParamSpec *);
-static void aspect_bin_init(AspectBin *ab)
+G_DEFINE_TYPE(AspectBin, aspect_bin, GTK_TYPE_CONTAINER)
+
+static void aspect_bin_init(AspectBin *abin)
{
- ab->child = NULL;
- ab->ratio = 1;
- ab->constrain = FALSE;
- ab->align = 0;
+ GTK_WIDGET_SET_FLAGS(abin, GTK_NO_WINDOW);
+
+ abin->body = NULL;
+ abin->side = NULL;
+ abin->ratio = 1;
+ abin->body_align = 0.5;
+ abin->side_align = 0.5;
+ abin->constrain = FALSE;
+ abin->fill = TRUE;
}
-static void aspect_bin_class_init(AspectBinClass *abc)
+static void aspect_bin_class_init(AspectBinClass *class)
{
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(abc);
- GtkContainerClass *container_class = GTK_CONTAINER_CLASS(abc);
+ GObjectClass *object_class = G_OBJECT_CLASS(class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS(class);
widget_class->size_request = aspect_bin_size_request;
widget_class->size_allocate = aspect_bin_size_allocate;
- container_class->remove = aspect_bin_remove;
- container_class->forall = aspect_bin_forall;
+ container_class->add = aspect_bin_add;
+ container_class->remove = aspect_bin_remove;
+ container_class->forall = aspect_bin_forall;
+ container_class->child_type = aspect_bin_child_type;
+
+ container_class->set_child_property = aspect_bin_set_child_property;
+ container_class->get_child_property = aspect_bin_get_child_property;
+
+ object_class->set_property = aspect_bin_set_property;
+ object_class->get_property = aspect_bin_get_property;
+
+ g_object_class_install_property(object_class,
+ PROP_RATIO,
+ g_param_spec_float("ratio",
+ "Aspect Ratio",
+ "Width:height ratio of the body.",
+ 0.2, 5.0, 1.0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_FILL,
+ g_param_spec_boolean("fill",
+ "Fill",
+ "Allocate all remaining space to the side.",
+ TRUE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property(object_class,
+ PROP_CONSTRAIN,
+ g_param_spec_boolean("constrain",
+ "Constrain",
+ "Try not to place the side beyond the body's edges.",
+ FALSE,
+ G_PARAM_READWRITE));
+ gtk_container_class_install_child_property(container_class,
+ CHILD_PROP_ALIGN,
+ g_param_spec_float("align",
+ "Alignment",
+ "Alignment of the child within the available space.",
+ 0.0, 1.0, 0.5,
+ G_PARAM_READWRITE));
}
GtkWidget *aspect_bin_new(void)
return GTK_WIDGET(g_object_new(ASPECT_BIN_TYPE, NULL));
}
+static void aspect_bin_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AspectBin *abin = ASPECT_BIN(object);
+ gfloat tmp;
+
+ switch (prop_id) {
+ case PROP_RATIO:
+ tmp = abin->ratio;
+ abin->ratio = g_value_get_float(value);
+ if (tmp != abin->ratio)
+ gtk_widget_queue_resize(GTK_WIDGET(abin));
+ break;
+ case PROP_CONSTRAIN:
+ abin->constrain = g_value_get_boolean(value);
+ gtk_widget_queue_resize(GTK_WIDGET(abin));
+ break;
+ case PROP_FILL:
+ abin->fill = g_value_get_boolean(value);
+ gtk_widget_queue_resize(GTK_WIDGET(abin));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void aspect_bin_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AspectBin *abin = ASPECT_BIN(object);
+
+ switch (prop_id) {
+ case PROP_RATIO:
+ g_value_set_float(value, abin->ratio);
+ break;
+ case PROP_CONSTRAIN:
+ g_value_set_boolean(value, abin->constrain);
+ break;
+ case PROP_FILL:
+ g_value_set_boolean(value, abin->fill);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void aspect_bin_set_child_property(GtkContainer *container,
+ GtkWidget *child,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AspectBin *abin = ASPECT_BIN(container);
+ gfloat align;
+
+ g_assert(child == abin->body || child == abin->side);
+
+ switch (prop_id) {
+ case CHILD_PROP_ALIGN:
+ align = g_value_get_float(value);
+ if (child == abin->body)
+ abin->body_align = align;
+ else if (child == abin->side)
+ abin->side_align = align;
+ gtk_widget_queue_resize(GTK_WIDGET(abin));
+ break;
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
+ prop_id, pspec);
+ }
+}
+
+static void aspect_bin_get_child_property(GtkContainer *container,
+ GtkWidget *child,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AspectBin *abin = ASPECT_BIN(container);
+ gfloat align = 0;
+
+ g_assert(child == abin->body || child == abin->side);
+
+ switch (prop_id) {
+ case CHILD_PROP_ALIGN:
+ if (child == abin->body)
+ align = abin->body_align;
+ else if (child == abin->side)
+ align = abin->side_align;
+ g_value_set_float(value, align);
+ break;
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID(container,
+ prop_id, pspec);
+ }
+}
+
+static void aspect_bin_add(GtkContainer *container, GtkWidget *widget)
+{
+ AspectBin *abin;
+ g_return_if_fail(IS_ASPECT_BIN(container));
+ abin = ASPECT_BIN(container);
+
+ if (!abin->body)
+ aspect_bin_set_body(abin, widget, 1);
+ else if (!abin->side)
+ aspect_bin_set_side(abin, widget);
+ else
+ g_warning("AspectBin cannot have more than 2 children.\n");
+}
+
static void aspect_bin_remove(GtkContainer *container, GtkWidget *child)
{
AspectBin *abin = ASPECT_BIN(container);
- if (abin->child == child) {
- aspect_bin_set_body_widget(abin, NULL, 1);
- } else {
- GTK_CONTAINER_CLASS(aspect_bin_parent_class)->remove
- (container, child);
+ if (abin->body == child) {
+ aspect_bin_set_body(abin, NULL, 1);
+ } else if (abin->side == child) {
+ aspect_bin_set_side(abin, NULL);
}
}
gpointer callback_data)
{
AspectBin *abin = ASPECT_BIN(container);
- GtkBin *bin = GTK_BIN(container);
+ g_return_if_fail(callback != NULL);
- if (bin->child)
- callback(bin->child, callback_data);
-
- if (abin->child)
- callback(abin->child, callback_data);
+ if (abin->body)
+ callback(abin->body, callback_data);
+ if (abin->side)
+ callback(abin->side, callback_data);
+}
+
+static GType aspect_bin_child_type(GtkContainer *container)
+{
+ if (!ASPECT_BIN(container)->body || !ASPECT_BIN(container)->side)
+ return GTK_TYPE_WIDGET;
+ return G_TYPE_NONE;
}
static void
aspect_bin_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
AspectBin *abin = ASPECT_BIN(widget);
- GtkBin *bin = GTK_BIN(widget);
GtkRequisition creq = {0}, areq = {0};
- if (bin->child && GTK_WIDGET_VISIBLE(bin->child)) {
- gtk_widget_size_request(bin->child, &creq);
+ if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
+ gtk_widget_size_request(abin->side, &creq);
}
- if (abin->child && GTK_WIDGET_VISIBLE(abin->child)) {
+ if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) {
int wtmp, htmp;
- gtk_widget_size_request(abin->child, &areq);
+ gtk_widget_size_request(abin->body, &areq);
wtmp = areq.height * abin->ratio + 0.5;
htmp = areq.width / abin->ratio + 0.5;
aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
AspectBin *abin = ASPECT_BIN(widget);
- GtkBin *bin = GTK_BIN(widget);
GtkRequisition creq = {0};
GtkAllocation csize = {0}, asize = {0};
-
- /* First find the best fit for the constrained child. */
- if (abin->child && GTK_WIDGET_VISIBLE(abin->child)) {
+
+ /* First find the best fit for the body. */
+ if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) {
asize.height = allocation->height;
asize.width = asize.height * abin->ratio + 0.5;
}
}
- /* Now try to fit the other child. */
- if (bin->child && GTK_WIDGET_VISIBLE(bin->child)) {
- gtk_widget_get_child_requisition(bin->child, &creq);
+ /* Now try to fit the side. */
+ if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) {
+ gtk_widget_get_child_requisition(abin->side, &creq);
if (allocation->width - asize.width < creq.width) {
/* It didn't fit, squish the constrained guy. */
}
csize.width = allocation->width - asize.width;
- csize.height = MIN(allocation->height, creq.height);
- if (abin->constrain) {
- csize.height = MAX(csize.height, asize.height);
- }
csize.x = asize.width;
+
+ if (abin->fill && abin->constrain) {
+ csize.height = asize.height;
+ } else if (abin->fill) {
+ csize.height = allocation->height;
+ } else {
+ csize.height = MIN(creq.height, allocation->height);
+ }
}
- csize.y = (allocation->height - csize.height) * abin->align + 0.5;
- asize.y = (allocation->height - asize.height) * abin->align + 0.5;
+ asize.y = (allocation->height - asize.height) * abin->body_align + 0.5;
+
+ if (abin->constrain && csize.height <= asize.height) {
+ csize.y = asize.y + (asize.height-csize.height)
+ * abin->side_align + 0.5;
+ } else if (abin->constrain) {
+ csize.y = (allocation->height - csize.height)
+ * abin->body_align + 0.5;
+ } else {
+ csize.y = (allocation->height - csize.height)
+ * abin->side_align + 0.5;
+ }
- if (bin->child)
- gtk_widget_size_allocate(bin->child, &csize);
- if (abin->child)
- gtk_widget_size_allocate(abin->child, &asize);
+ if (abin->body)
+ gtk_widget_size_allocate(abin->body, &asize);
+ if (abin->side)
+ gtk_widget_size_allocate(abin->side, &csize);
}
-void
-aspect_bin_set_body_widget(AspectBin *ab, GtkWidget *widget, gfloat ratio)
+static gboolean
+set_widget(GtkWidget **dest, GtkWidget *parent, GtkWidget *widget)
{
gboolean need_resize = FALSE;
- g_return_if_fail(IS_ASPECT_BIN(ab));
- g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
- g_return_if_fail(widget == NULL || widget->parent == NULL);
+ if (*dest == widget)
+ return FALSE;
- if (ab->child == widget)
- return;
-
- if (ab->child) {
- need_resize |= GTK_WIDGET_VISIBLE(ab->child);
- gtk_widget_unparent(ab->child);
+ if (*dest) {
+ need_resize |= GTK_WIDGET_VISIBLE(*dest);
+ gtk_widget_unparent(*dest);
}
- ab->child = widget;
+ *dest = widget;
if (widget) {
- gtk_widget_set_parent(widget, GTK_WIDGET(ab));
+ gtk_widget_set_parent(widget, parent);
need_resize |= GTK_WIDGET_VISIBLE(widget);
}
- if (GTK_WIDGET_VISIBLE(ab) && need_resize)
- gtk_widget_queue_resize(GTK_WIDGET(ab));
+ return GTK_WIDGET_VISIBLE(parent) && need_resize;
+}
+
+void aspect_bin_set_body(AspectBin *abin, GtkWidget *widget, gfloat ratio)
+{
+ g_return_if_fail(IS_ASPECT_BIN(abin));
+ g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
+ g_return_if_fail(widget == NULL || widget->parent == NULL);
+
+ if (set_widget(&abin->body, GTK_WIDGET(abin), widget))
+ gtk_widget_queue_resize(GTK_WIDGET(abin));
+}
+
+void aspect_bin_set_side(AspectBin *abin, GtkWidget *widget)
+{
+ g_return_if_fail(IS_ASPECT_BIN(abin));
+ g_return_if_fail(widget == NULL || GTK_IS_WIDGET(widget));
+ g_return_if_fail(widget == NULL || widget->parent == NULL);
+
+ if (set_widget(&abin->side, GTK_WIDGET(abin), widget))
+ gtk_widget_queue_resize(GTK_WIDGET(abin));
+}
+
+GtkWidget *aspect_bin_get_body(AspectBin *abin)
+{
+ g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
+ return abin->body;
+}
+
+GtkWidget *aspect_bin_get_side(AspectBin *abin)
+{
+ g_return_val_if_fail(IS_ASPECT_BIN(abin), NULL);
+ return abin->side;
}