]> git.draconx.ca Git - aspectbin.git/blobdiff - aspectbin.c
Update .gitignore.
[aspectbin.git] / aspectbin.c
index 77fa5196ae3820a4e1e31fb1096ba0a04ddf85e2..bc74a67d3c4558f08f26623926841854915d28d0 100644 (file)
+/* 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 *);
+
+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;
+}