/* 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 *); 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) { 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->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->body == child) { aspect_bin_set_body(abin, NULL, 1); } else if (abin->side == child) { aspect_bin_set_side(abin, NULL); } } static void aspect_bin_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { AspectBin *abin = ASPECT_BIN(container); g_return_if_fail(callback != NULL); 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); GtkRequisition creq = {0}, areq = {0}; if (abin->side && GTK_WIDGET_VISIBLE(abin->side)) { gtk_widget_size_request(abin->side, &creq); } if (abin->body && GTK_WIDGET_VISIBLE(abin->body)) { int wtmp, htmp; gtk_widget_size_request(abin->body, &areq); wtmp = areq.height * abin->ratio + 0.5; htmp = areq.width / abin->ratio + 0.5; if (wtmp > areq.width) { areq.width = wtmp; } else { areq.height = htmp; } } requisition->width = areq.width + creq.width; requisition->height = MAX(areq.height, creq.height); } static void aspect_bin_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { AspectBin *abin = ASPECT_BIN(widget); GtkRequisition creq = {0}; GtkAllocation csize = {0}, asize = {0}; /* 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; if (asize.width > allocation->width) { asize.width = allocation->width; asize.height = asize.width / abin->ratio + 0.5; } } /* 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. */ asize.width = allocation->width - creq.width; asize.height = asize.width / abin->ratio + 0.5; } csize.width = allocation->width - asize.width; 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); } } 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 (abin->body) gtk_widget_size_allocate(abin->body, &asize); if (abin->side) gtk_widget_size_allocate(abin->side, &csize); } static gboolean set_widget(GtkWidget **dest, GtkWidget *parent, GtkWidget *widget) { gboolean need_resize = FALSE; if (*dest == widget) return FALSE; if (*dest) { need_resize |= GTK_WIDGET_VISIBLE(*dest); gtk_widget_unparent(*dest); } *dest = widget; if (widget) { gtk_widget_set_parent(widget, parent); need_resize |= GTK_WIDGET_VISIBLE(widget); } 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; }