GtkVBox: A Windowless Container

This section describes some aspects of the GtkVBox widget, which differs substantially from the GtkEv widget presented earlier in the chapter. To understand this section you must first understand how GtkBox works from a user's point of view; see the section called GtkBox in the chapter called GTK+ Basics. You might want to look through the files gtkvbox.h and gtkvbox.c from your GTK+ distribution as you read.

Most of GtkVBox is implemented in the GtkBox base class; GtkVBox itself implements only size request and allocation. The GtkBox instance struct looks like this:


typedef struct _GtkBox        GtkBox;

struct _GtkBox
{
  GtkContainer container;
  
  GList *children;
  gint16 spacing;
  guint homogeneous : 1;
};
    

GtkBoxClass adds nothing to GtkContainerClass, and GtkVBox adds nothing to GtkBox.

Coding a GTK_NO_WINDOW Widget

The implementation of windowless widgets is slightly different from the implementation of "normal" widgets.

Windowless widgets must set the GTK_NO_WINDOW flag, so GTK+ can treat them appropriately. This should be done in the init function:


static void
gtk_box_init (GtkBox *box)
{
  GTK_WIDGET_SET_FLAGS (box, GTK_NO_WINDOW);

  box->children = NULL;
  box->spacing = 0;
  box->homogeneous = FALSE;
}
      

GtkBox uses the default realize method described in the section called Realization; because no GdkWindow needs to be created, a GTK_NO_WINDOW widget rarely needs a realize method. Recall that the default realize implementation sets the windowless widget's window field to the parent widget's window field.

Because boxes are invisible layout containers, the GtkBox draw and expose implementations simply pass the draw or expose request on to the box's children. This is identical to GtkBin's draw and expose implementations, except that there's a list of children to iterate over.

A GTK_NO_WINDOW widget that isn't invisible, such as GtkLabel, should be careful not to draw a background; the parent widget's background is used.

Size Negotiation

GtkVBox's purpose in life is size negotiation; it passes size requests up from its children, and then divides a size allocation among them. This leads to the layout behavior described in the section called GtkBox in the chapter called GTK+ Basics.

Here is the size request implementation:


static void
gtk_vbox_size_request (GtkWidget      *widget,
                       GtkRequisition *requisition)
{
  GtkBox *box;
  GtkBoxChild *child;
  GtkRequisition child_requisition;
  GList *children;
  gint nvis_children;
  gint height;

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

  box = GTK_BOX (widget);
  requisition->width = 0;
  requisition->height = 0;
  nvis_children = 0;

  children = box->children;
  while (children)
    {
      child = children->data;
      children = children->next;

      if (GTK_WIDGET_VISIBLE (child->widget))
        {
          gtk_widget_size_request (child->widget, &child_requisition);

          if (box->homogeneous)
            {
              height = child_requisition.height + child->padding * 2;
              requisition->height = MAX (requisition->height, height);
            }
          else
            {
              requisition->height += child_requisition.height + child->padding * 2;
            }

          requisition->width = MAX (requisition->width, child_requisition.width);

          nvis_children += 1;
        }
    }

  if (nvis_children > 0)
    {
      if (box->homogeneous)
        requisition->height *= nvis_children;
      requisition->height += (nvis_children - 1) * box->spacing;
    }

  requisition->width += GTK_CONTAINER (box)->border_width * 2;
  requisition->height += GTK_CONTAINER (box)->border_width * 2;
}
      

If the box is homogenous, it multiplies the maximum child requisition times the number of children; otherwise, it sums the child requisitions. Then it adds padding, spacing, and border width, as appropriate. Recall that all containers must honor their border width, set with gtk_container_set_border_width() and available as the border_width field in the GtkContainer instance struct.

When reading this code, it may help to know that GtkBox stores a small struct for each child widget in its children field. The struct looks like this:


typedef struct _GtkBoxChild   GtkBoxChild;

struct _GtkBoxChild
{
  GtkWidget *widget;
  guint16 padding;
  guint expand : 1;
  guint fill : 1;
  guint pack : 1;
};
      

Size allocation is more complex; here, all the box-packing flags come into play. It will probably take you a while to fully understand this function, but there is no need to; the important thing is to see how layout takes place via size allocation.


static void
gtk_vbox_size_allocate (GtkWidget     *widget,
                        GtkAllocation *allocation)
{
  GtkBox *box;
  GtkBoxChild *child;
  GList *children;
  GtkAllocation child_allocation;
  gint nvis_children;
  gint nexpand_children;
  gint child_height;
  gint height;
  gint extra;
  gint y;

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

  box = GTK_BOX (widget);
  widget->allocation = *allocation;

  nvis_children = 0;
  nexpand_children = 0;
  children = box->children;

  while (children)
    {
      child = children->data;
      children = children->next;

      if (GTK_WIDGET_VISIBLE (child->widget))
        {
          nvis_children += 1;
          if (child->expand)
            nexpand_children += 1;
        }
    }

  if (nvis_children > 0)
    {
      if (box->homogeneous)
        {
          height = (allocation->height -
                   GTK_CONTAINER (box)->border_width * 2 -
                   (nvis_children - 1) * box->spacing);
          extra = height / nvis_children;
        }
      else if (nexpand_children > 0)
        {
          height = (gint) allocation->height - (gint) widget->requisition.height;
          extra = height / nexpand_children;
        }
      else
        {
          height = 0;
          extra = 0;
        }

      y = allocation->y + GTK_CONTAINER (box)->border_width;
      child_allocation.x = allocation->x + GTK_CONTAINER (box)->border_width;
      child_allocation.width = MAX (1, (gint) allocation->width - (gint) GTK_CONTAINER (box)->border_width * 2);

      children = box->children;
      while (children)
        {
          child = children->data;
          children = children->next;

          if ((child->pack == GTK_PACK_START) && GTK_WIDGET_VISIBLE (child->widget))
            {
              if (box->homogeneous)
                {
                  if (nvis_children == 1)
                    child_height = height;
                  else
                    child_height = extra;

                  nvis_children -= 1;
                  height -= extra;
                }
              else
                {
                  GtkRequisition child_requisition;

                  gtk_widget_get_child_requisition (child->widget, &child_requisition);
                  child_height = child_requisition.height + child->padding * 2;

                  if (child->expand)
                    {
                      if (nexpand_children == 1)
                        child_height += height;
                      else
                        child_height += extra;

                      nexpand_children -= 1;
                      height -= extra;
                    }
                }

              if (child->fill)
                {
                  child_allocation.height = MAX (1, child_height - (gint)child->padding * 2);
                  child_allocation.y = y + child->padding;
                }
              else
                {
                  GtkRequisition child_requisition;

                  gtk_widget_get_child_requisition (child->widget, &child_requisition);
                  child_allocation.height = child_requisition.height;
                  child_allocation.y = y + (child_height - child_allocation.height) / 2;
                }

              gtk_widget_size_allocate (child->widget, &child_allocation);

              y += child_height + box->spacing;
            }
        }

      y = allocation->y + allocation->height - GTK_CONTAINER (box)->border_width;

      children = box->children;
      while (children)
        {
          child = children->data;
          children = children->next;

          if ((child->pack == GTK_PACK_END) && GTK_WIDGET_VISIBLE (child->widget))
            {
              GtkRequisition child_requisition;
              gtk_widget_get_child_requisition (child->widget, &child_requisition);

              if (box->homogeneous)
                {
                  if (nvis_children == 1)
                    child_height = height;
                  else
                    child_height = extra;

                  nvis_children -= 1;
                  height -= extra;
                }
              else
                {
                  child_height = child_requisition.height + child->padding * 2;

                  if (child->expand)
                    {
                      if (nexpand_children == 1)
                        child_height += height;
                      else
                        child_height += extra;

                      nexpand_children -= 1;
                      height -= extra;
                    }
                }

              if (child->fill)
                {
                  child_allocation.height = MAX (1, child_height - (gint)child->padding * 2);
                  child_allocation.y = y + child->padding - child_height;
                }
              else
                {
                  child_allocation.height = child_requisition.height;
                  child_allocation.y = y + (child_height - child_allocation.height) / 2 - child_height;
                }

              gtk_widget_size_allocate (child->widget, &child_allocation);

              y -= (child_height + box->spacing);
            }
        }
    }
}
      

Child Arguments

GtkBox implements child arguments, which were briefly described in the section called Using Object Arguments in Your Own GtkObject Subclass in the chapter called The GTK+ Object and Type System. Child arguments represent a property of a pair of objects. In this case, the box-packing flags for each child can be read and written using the object argument system.

Here's how GtkBox registers its child arguments, in gtk_box_class_init():


  gtk_container_add_child_arg_type ("GtkBox::expand", GTK_TYPE_BOOL, GTK_ARG_READWRITE, CHILD_ARG_EXPAND);
  gtk_container_add_child_arg_type ("GtkBox::fill", GTK_TYPE_BOOL, GTK_ARG_READWRITE, CHILD_ARG_FILL);
  gtk_container_add_child_arg_type ("GtkBox::padding", GTK_TYPE_ULONG, GTK_ARG_READWRITE, CHILD_ARG_PADDING);
  gtk_container_add_child_arg_type ("GtkBox::pack_type", GTK_TYPE_PACK_TYPE, GTK_ARG_READWRITE, CHILD_ARG_PACK_TYPE);
  gtk_container_add_child_arg_type ("GtkBox::position", GTK_TYPE_LONG, GTK_ARG_READWRITE, CHILD_ARG_POSITION);

      

GtkBox then implements the get_child_arg and set_child_arg methods from GtkContainerClass. Here's gtk_box_get_child_arg(); the gtk_box_set_child_arg() is analagous.


static void
gtk_box_get_child_arg (GtkContainer   *container,
                       GtkWidget      *child,
                       GtkArg         *arg,
                       guint           arg_id)
{
  gboolean expand = 0;
  gboolean fill = 0;
  guint padding = 0;
  GtkPackType pack_type = 0;
  GList *list;

  if (arg_id != CHILD_ARG_POSITION)
    gtk_box_query_child_packing (GTK_BOX (container),
                                 child,
                                 &expand,
                                 &fill,
                                 &padding,
                                 &pack_type);
  
  switch (arg_id)
    {
    case CHILD_ARG_EXPAND:
      GTK_VALUE_BOOL (*arg) = expand;
      break;
    case CHILD_ARG_FILL:
      GTK_VALUE_BOOL (*arg) = fill;
      break;
    case CHILD_ARG_PADDING:
      GTK_VALUE_ULONG (*arg) = padding;
      break;
    case CHILD_ARG_PACK_TYPE:
      GTK_VALUE_ENUM (*arg) = pack_type;
      break;
    case CHILD_ARG_POSITION:
      GTK_VALUE_LONG (*arg) = 0;
      for (list = GTK_BOX (container)->children; list; list = list->next)
        {
          GtkBoxChild *child_entry;

          child_entry = list->data;
          if (child_entry->widget == child)
            break;
          GTK_VALUE_LONG (*arg)++;
        }
      if (!list)
        GTK_VALUE_LONG (*arg) = -1;
      break;
    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
}