X-Git-Url: https://git.draconx.ca/gitweb/aspectbin.git/blobdiff_plain/8e40543a4d1d56de06f8ab28eb110439f18ff06e..HEAD:/aspectbin.c diff --git a/aspectbin.c b/aspectbin.c index 77fa519..bc74a67 100644 --- a/aspectbin.c +++ b/aspectbin.c @@ -1,31 +1,115 @@ +/* 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 #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 *); + +G_DEFINE_TYPE(AspectBin, aspect_bin, GTK_TYPE_CONTAINER) static void aspect_bin_init(AspectBin *abin) { - abin->body = NULL; - abin->ratio = 1; - abin->constrain = FALSE; - abin->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 *class) { + 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) @@ -33,15 +117,129 @@ 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->body == child) { - aspect_bin_set_body_widget(abin, NULL, 1); - } else { - GTK_CONTAINER_CLASS(aspect_bin_parent_class)->remove - (container, child); + aspect_bin_set_body(abin, NULL, 1); + } else if (abin->side == child) { + aspect_bin_set_side(abin, NULL); } } @@ -51,24 +249,29 @@ static void aspect_bin_forall(GtkContainer *container, 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->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->body && GTK_WIDGET_VISIBLE(abin->body)) { @@ -92,11 +295,10 @@ static void 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. */ + + /* 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; @@ -107,9 +309,9 @@ aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation) } } - /* 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. */ @@ -118,45 +320,86 @@ aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation) } 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->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 *abin, 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(abin)); - 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 (abin->body == widget) - return; - - if (abin->body) { - need_resize |= GTK_WIDGET_VISIBLE(abin->body); - gtk_widget_unparent(abin->body); + if (*dest) { + need_resize |= GTK_WIDGET_VISIBLE(*dest); + gtk_widget_unparent(*dest); } - abin->body = widget; + *dest = widget; if (widget) { - gtk_widget_set_parent(widget, GTK_WIDGET(abin)); + gtk_widget_set_parent(widget, parent); need_resize |= GTK_WIDGET_VISIBLE(widget); } - if (GTK_WIDGET_VISIBLE(abin) && need_resize) + 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; +}