GtkConstrain




Hi,

Jonathan reminded me about this old widget I had laying around, which
may be of interest.

It allows you to group widgets such that they all request the same
size. It's useful if you have two widgets you would like to line up
nicely, but the two widgets can't be in the same container.

For example, say you have a dialog, with two frames holding groups of
options. The entries (for example) in the two frames should have the
same size. It's easy to use GtkTable within a frame to achieve this,
but the table can't span the two frames. So, GtkConstrain can be used.

GtkConstrain does not guarantee two widgets will end up with the same
size, obviously, since it only affects the size request.

Havoc


#ifndef __GTK_CONSTRAIN_H__
#define __GTK_CONSTRAIN_H__


#include <gdk/gdk.h>
#include <gtk/gtkbin.h>


#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

typedef enum
{
  GTK_CONSTRAIN_FULL,
  GTK_CONSTRAIN_HORIZONTAL,
  GTK_CONSTRAIN_VERTICAL
} GtkConstrainDimension;

typedef struct _GtkConstrainGroup GtkConstrainGroup;

#define GTK_TYPE_CONSTRAIN		(gtk_constrain_get_type ())
#define GTK_CONSTRAIN(obj)		(GTK_CHECK_CAST ((obj), GTK_TYPE_CONSTRAIN, GtkConstrain))
#define GTK_CONSTRAIN_CLASS(klass)	(GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_CONSTRAIN, GtkConstrainClass))
#define GTK_IS_CONSTRAIN(obj)		(GTK_CHECK_TYPE ((obj), GTK_TYPE_CONSTRAIN))
#define GTK_IS_CONSTRAIN_CLASS(klass)	(GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CONSTRAIN))


typedef struct _GtkConstrain        GtkConstrain;
typedef struct _GtkConstrainClass   GtkConstrainClass;

struct _GtkConstrain
{
  GtkBin bin;

  /*< private >*/
  
  GtkConstrainGroup* group;
  GtkConstrainDimension dimension;
};

struct _GtkConstrainClass
{
  GtkBinClass parent_class;
};

GtkType               gtk_constrain_get_type      (void);
GtkWidget *           gtk_constrain_new           (GtkConstrainDimension  dimension,
                                                   GtkConstrainGroup     *group);

void                  gtk_constrain_set_group     (GtkConstrain          *constrain,
                                                   GtkConstrainGroup     *group);
GtkConstrainGroup*    gtk_constrain_get_group     (GtkConstrain          *constrain);
void                  gtk_constrain_set_dimension (GtkConstrain          *constrain,
                                                   GtkConstrainDimension  dimension);
GtkConstrainDimension gtk_constrain_get_dimension (GtkConstrain          *constrain);

GtkConstrainGroup*    gtk_constrain_group_new     (void);
void                  gtk_constrain_group_unref   (GtkConstrainGroup     *group);
GtkConstrainGroup*    gtk_constrain_group_ref     (GtkConstrainGroup     *group);




#ifdef __cplusplus
}
#endif /* __cplusplus */


#endif /* __GTK_CONSTRAIN_H__ */


#include "gtkconstrain.h"

static void               gtk_constrain_group_add    (GtkConstrainGroup* group,
                                                      GtkConstrain*      constrain);
static void               gtk_constrain_group_remove (GtkConstrainGroup* group,
                                                      GtkConstrain*      constrain);
static void               gtk_constrain_group_size_request (GtkConstrainGroup* group,
                                                            GtkRequisition* requisition,
                                                            GtkConstrain* in_request_for);

#if 0
static void               gtk_constrain_group_queue_resize (GtkConstrainGroup* group);
#endif

enum {
  ARG_0,
  ARG_GROUP,
  ARG_DIMENSION
};


static void gtk_constrain_class_init    (GtkConstrainClass  *klass);
static void gtk_constrain_init          (GtkConstrain       *constrain);
static void gtk_constrain_set_arg       (GtkObject      *object,
                                         GtkArg         *arg,
                                         guint           arg_id);
static void gtk_constrain_get_arg       (GtkObject      *object,
                                         GtkArg         *arg,
                                         guint           arg_id);
static void gtk_constrain_destroy       (GtkObject      *object);
static void gtk_constrain_finalize      (GtkObject      *object);
static void gtk_constrain_size_request  (GtkWidget      *widget,
                                         GtkRequisition *requisition);
static void gtk_constrain_size_allocate (GtkWidget      *widget,
                                         GtkAllocation  *allocation);

static GtkBinClass *parent_class = NULL;


GtkType
gtk_constrain_get_type (void)
{
  static GtkType constrain_type = 0;

  if (!constrain_type)
    {
      static const GtkTypeInfo constrain_info =
      {
	"GtkConstrain",
	sizeof (GtkConstrain),
	sizeof (GtkConstrainClass),
	(GtkClassInitFunc) gtk_constrain_class_init,
	(GtkObjectInitFunc) gtk_constrain_init,
        /* reserved_1 */ NULL,
	/* reserved_2 */ NULL,
	(GtkClassInitFunc) NULL,
      };

      constrain_type = gtk_type_unique (gtk_bin_get_type (), &constrain_info);
    }

  return constrain_type;
}

static void
gtk_constrain_class_init (GtkConstrainClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;

  parent_class = gtk_type_class (gtk_bin_get_type ());

  gtk_object_add_arg_type ("GtkConstrain::dimension", GTK_TYPE_ENUM,
                           GTK_ARG_READWRITE, ARG_DIMENSION);
  gtk_object_add_arg_type ("GtkConstrain::group", GTK_TYPE_BOXED,
                           GTK_ARG_READWRITE, ARG_GROUP);

  object_class->set_arg = gtk_constrain_set_arg;
  object_class->get_arg = gtk_constrain_get_arg;
  object_class->finalize = gtk_constrain_finalize;
  object_class->destroy = gtk_constrain_destroy;

  widget_class->size_request = gtk_constrain_size_request;
  widget_class->size_allocate = gtk_constrain_size_allocate;
}

static void
gtk_constrain_init (GtkConstrain *constrain)
{
  constrain->dimension = GTK_CONSTRAIN_FULL;
  /* An invariant is that we always have a group. */
  constrain->group = gtk_constrain_group_new ();
  gtk_constrain_group_add (constrain->group, constrain);
}

static void
gtk_constrain_set_arg (GtkObject      *object,
                       GtkArg         *arg,
                       guint           arg_id)
{
  GtkConstrain *constrain;

  constrain = GTK_CONSTRAIN (object);

  switch (arg_id)
    {
    case ARG_DIMENSION:
      gtk_constrain_set_dimension (constrain, GTK_VALUE_ENUM (*arg));
      break;

    case ARG_GROUP:
      gtk_constrain_set_group (constrain, GTK_VALUE_BOXED (*arg));
      break;

    default:
      break;
    }
}

static void
gtk_constrain_get_arg (GtkObject      *object,
                       GtkArg         *arg,
                       guint           arg_id)
{
  GtkConstrain *constrain;

  constrain = GTK_CONSTRAIN (object);

  switch (arg_id)
    {
    case ARG_DIMENSION:
      GTK_VALUE_ENUM (*arg) = constrain->dimension;
      break;

    case ARG_GROUP:
      gtk_constrain_group_ref (constrain->group);
      GTK_VALUE_BOXED (*arg) = constrain->group;
      break;

    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
}

GtkWidget*
gtk_constrain_new (GtkConstrainDimension type,
                   GtkConstrainGroup *group)
{
  GtkConstrain *constrain;

  constrain = gtk_type_new (gtk_constrain_get_type ());

  constrain->dimension = type;

  if (group != NULL)
    gtk_constrain_set_group (constrain, group);

  return GTK_WIDGET (constrain);
}

static void
gtk_constrain_destroy (GtkObject *object)
{
  GtkConstrain *constrain;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_CONSTRAIN (object));

  constrain = GTK_CONSTRAIN (object);

  gtk_constrain_group_remove (constrain->group, constrain);
  gtk_constrain_group_unref (constrain->group);
  constrain->group = NULL;

  (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void
gtk_constrain_finalize (GtkObject *object)
{
  GtkConstrain *constrain;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_CONSTRAIN (object));

  constrain = GTK_CONSTRAIN (object);

  (* GTK_OBJECT_CLASS (parent_class)->finalize) (object);
}

static void
gtk_constrain_size_request (GtkWidget      *widget,
                            GtkRequisition *requisition)
{
  GtkConstrain *constrain;
  GtkBin *bin;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CONSTRAIN (widget));
  g_return_if_fail (requisition != NULL);

  constrain = GTK_CONSTRAIN (widget);
  bin = GTK_BIN (widget);

  requisition->width = (GTK_CONTAINER (widget)->border_width * 2);

  requisition->height = (GTK_CONTAINER (widget)->border_width * 2);

  if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
    {
      GtkRequisition child_requisition = { 0, 0 };
      GtkRequisition group_requisition = { 0, 0 };

      gtk_widget_size_request (bin->child, &child_requisition);

      gtk_constrain_group_size_request (constrain->group,
                                        &group_requisition,
                                        constrain);

      if (constrain->dimension == GTK_CONSTRAIN_FULL ||
          constrain->dimension == GTK_CONSTRAIN_HORIZONTAL)
        child_requisition.width = group_requisition.width;

      if (constrain->dimension == GTK_CONSTRAIN_FULL ||
          constrain->dimension == GTK_CONSTRAIN_VERTICAL)
        child_requisition.height = group_requisition.height;
      
      requisition->width += child_requisition.width;
      requisition->height += child_requisition.height;
    }

  /*   printf ("%p requested size %d x %d\n", constrain, requisition->width, requisition->height); */
}

static void
gtk_constrain_size_allocate (GtkWidget     *widget,
                             GtkAllocation *allocation)
{
  GtkConstrain *constrain;
  GtkBin *bin;
  GtkAllocation child_allocation;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CONSTRAIN (widget));
  g_return_if_fail (allocation != NULL);

  constrain = GTK_CONSTRAIN (widget);
  bin = GTK_BIN (widget);

  widget->allocation = *allocation;

  if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
    {
      child_allocation.x = GTK_CONTAINER (constrain)->border_width;
      child_allocation.width = MAX (1, (gint)allocation->width - child_allocation.x * 2);

      child_allocation.y = GTK_CONTAINER (constrain)->border_width;

      child_allocation.height = MAX (1, ( (gint)allocation->height - child_allocation.y -
                                          (gint)GTK_CONTAINER (constrain)->border_width));

      child_allocation.x += allocation->x;
      child_allocation.y += allocation->y;

      gtk_widget_size_allocate (bin->child, &child_allocation);
    }
}

/*
 *  Non-widget methods
 */

void
gtk_constrain_set_group (GtkConstrain* constrain,
                         GtkConstrainGroup* group)
{
  g_return_if_fail (constrain != NULL);
  g_return_if_fail (GTK_IS_CONSTRAIN (constrain));
  g_return_if_fail (group != NULL);

  if (constrain->group == group)
    return;

  gtk_constrain_group_ref (group);

  gtk_constrain_group_remove (constrain->group, constrain);

  gtk_constrain_group_unref (constrain->group);

  constrain->group = group;

  gtk_constrain_group_add (group, constrain);
}

GtkConstrainGroup*
gtk_constrain_get_group     (GtkConstrain* constrain)
{
  g_return_val_if_fail (constrain != NULL, NULL);

  return constrain->group;
}

void
gtk_constrain_set_dimension (GtkConstrain *constrain,
                             GtkConstrainDimension dimension)
{
  g_return_if_fail (constrain != NULL);
  g_return_if_fail (GTK_IS_CONSTRAIN (constrain));

  constrain->dimension = dimension;

  /* pretty sure we can resize ourselves only, rather
     than the entire group. */
  gtk_widget_queue_resize (GTK_WIDGET (constrain));
}

GtkConstrainDimension
gtk_constrain_get_dimension (GtkConstrain *constrain)
{
  g_return_val_if_fail (constrain != NULL, GTK_CONSTRAIN_FULL);
  g_return_val_if_fail (GTK_IS_CONSTRAIN (constrain), GTK_CONSTRAIN_FULL);

  return constrain->dimension;
}

/*
 * GtkConstrainGroup
 */

struct _GtkConstrainGroup {
  GSList* members;
  guint refcount;
  gint max_width;
  gint max_height;
};

GtkConstrainGroup*
gtk_constrain_group_new    (void)
{
  GtkConstrainGroup* group;

  group = g_new0 (GtkConstrainGroup, 1);

  group->refcount = 1;

  return group;
}

void
gtk_constrain_group_unref  (GtkConstrainGroup* group)
{
  g_return_if_fail (group != NULL);
  g_return_if_fail (group->refcount > 0);

  group->refcount -= 1;

  if (group->refcount == 0)
    {
      g_return_if_fail (group->members == NULL);
      g_free (group);
    }
}

GtkConstrainGroup*
gtk_constrain_group_ref    (GtkConstrainGroup* group)
{
  g_return_val_if_fail (group != NULL, NULL);
  g_return_val_if_fail (group->refcount > 0, NULL);

  group->refcount += 1;

  return group;
}

static void
gtk_constrain_group_add    (GtkConstrainGroup* group,
                            GtkConstrain*      constrain)
{
  g_return_if_fail (group != NULL);
  g_return_if_fail (group->refcount != 0);

  group->members = g_slist_prepend (group->members, constrain);

  /* if this widget changes the max width/height,
     gtk_constrain_group_size_request() will queue
     resize on the other widgets in the group. */
  gtk_widget_queue_resize (GTK_WIDGET (constrain));
}

static void
gtk_constrain_group_remove (GtkConstrainGroup* group,
                            GtkConstrain*      constrain)
{
  g_return_if_fail (group != NULL);
  g_return_if_fail (group->refcount != 0);

  group->members = g_slist_remove (group->members, constrain);

  /* the constrain is no longer constrained. */
  gtk_widget_queue_resize (GTK_WIDGET (constrain));
}

static void
gtk_constrain_group_size_request (GtkConstrainGroup* group,
                                  GtkRequisition*    requisition,
                                  GtkConstrain*      in_request_for)
{
  GSList* tmp;

  g_return_if_fail (group != NULL);
  g_return_if_fail (group->refcount != 0);
  g_return_if_fail (group->members != NULL);

  requisition->width = 0;
  requisition->height = 0;

  tmp = group->members;

  while (tmp != NULL)
    {
      GtkBin *bin;
      GtkRequisition this_req = { 0, 0 };

      bin = GTK_BIN (tmp->data);

      g_assert (bin != NULL);
      g_assert (GTK_IS_CONSTRAIN (bin));

      if (bin->child != NULL && GTK_WIDGET_VISIBLE (bin->child))
        gtk_widget_size_request (bin->child, &this_req);

      /* 
      printf ("%p child would like %d x %d\n",
              bin, this_req.width, this_req.height);
      */
      
      requisition->width = MAX ((gint)this_req.width, (gint)requisition->width);
      requisition->height = MAX ((gint)this_req.height, (gint)requisition->height);

      tmp = g_slist_next (tmp);
    }

  /*

    If any constrains in the group have the wrong size request,
    we need to queue a resize on them. This is a badly broken hack,
    but otherwise changes to GtkContainer would be required.
    
    Clearly we rely on the invariant that a GtkConstrain always sets
    its size request to the group max width/height plus
    border width. This keeps us out of infinite loops.
     
  */

  group->max_width = (gint)requisition->width;
  group->max_height = (gint)requisition->height;
  
  tmp = group->members;
  while (tmp != NULL)
    {
      GtkConstrain *constrain;

      constrain = tmp->data;

      g_assert (constrain != NULL);
      g_assert (GTK_IS_CONSTRAIN (constrain));

      if (constrain != in_request_for)
        {
          gint base_width;
          gint base_height;

          base_width = GTK_WIDGET (constrain)->requisition.width -
            GTK_CONTAINER (constrain)->border_width * 2;

          base_height = GTK_WIDGET (constrain)->requisition.height -
            GTK_CONTAINER (constrain)->border_width * 2;

          if (constrain->dimension == GTK_CONSTRAIN_FULL ||
              constrain->dimension == GTK_CONSTRAIN_HORIZONTAL)
            {
              
              if (base_width != group->max_width)
                {
                  if (GTK_BIN (constrain)->child)
                    gtk_widget_queue_resize (GTK_BIN (constrain)->child);
                }
            }
          
          if (constrain->dimension == GTK_CONSTRAIN_FULL ||
              constrain->dimension == GTK_CONSTRAIN_VERTICAL)
            {
              if (base_height != group->max_height)
                {
                  if (GTK_BIN (constrain)->child)
                    gtk_widget_queue_resize (GTK_BIN (constrain)->child);
                }
            }
        }
      
      tmp = g_slist_next (tmp);
    }
}

#if 0
static void
gtk_constrain_group_queue_resize (GtkConstrainGroup* group)
{
  GSList* tmp;

  g_return_if_fail (group != NULL);
  g_return_if_fail (group->refcount != 0);

  tmp = group->members;

  while (tmp != NULL)
    {
      gtk_widget_queue_resize (GTK_WIDGET (tmp->data));

      tmp = g_slist_next (tmp);
    }
}

#endif






[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]