HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Why Write Custom Layout Managers?
Layout manager objectifies the component layout strategy. It disentangles component layout code from the drawing code of the container of components. By isolating the implementation of a layout strategy in a separate class, a programmer can reuse it by simply assigning an instance of this layout class to the container that requires it. Without a layout manager the layout code would have to be embedded within drawing code of the container itself.

Java's AWT supports this concept in the LayoutManager interface and supplies five implementations that are sufficient in most situations. It's sometimes more efficient and effective, however, to implement one's own layout manager, designed specifically for a particular task, rather than struggling with the standard layout managers.

Let's say we need to draw components on a grid, for example, but need to specify the positions of the components arbitrarily at any time. On the first take we'd probably choose AWT's GridLayout as the layout manager - that is, until we discover that it will only position components in sequence. The programmer then would have no control over their positions. The alternative would be to use GridBagLayout, but that's way too complicated and difficult to control for such a simple task. The solution is to write our own LayoutManager. As you'll see, this isn't as difficult as it sounds and may be the best solution in many situations. This article introduces a grid layout manager with positionable components, and with flexibility and complexity somewhere between AWT's GridLayout and GridBagLayout.

Layout Manager Basics:LayoutManager Interface
AWT's LayoutManager interface declares the following methods that have to be implemented in a valid layout manager:

  • addLayoutComponent
  • removeLayoutComponent
  • layoutContainer
  • minimumLayoutSize
  • preferredLayoutSize
AddLayoutComponent and removeLayoutComponent let the Container assigned with this layout add and remove a Component to and from the layout. LayoutContainer implements the layout strategy, changing the x and y pixel position and the dimension of the Components as necessary. MinimumLayoutSize and preferredLayoutSize let the Container find out the minimum required and preferred layout size given the Components to be laid out in it.

AWT's Containers are assigned default LayoutManagers but a different one may be chosen using the Container's setLayout method. When you add a Component to a Container, the Container will in turn add it to its assigned LayoutManager. The same happens when you remove a Component. Whenever a Component is added or removed, the Container will call the layoutContainer of the assigned LayoutManager to update the position and size of all Components. If you need to lay out the Container again, you may invoke the Container's doLayout method to recompute the layout.

PositionableGridLayout Class
We call our grid layout with positionable components the PositionableGridLayout class. The minimumLayoutSize for this case should be the extent of the layout embodying the outermost component. That would be our preferredLayoutSize as well. In other words, minimumLayoutSize is the x grid position of the easternmost Component times the width of each grid and the y grid position of the southernmost Component times the height of each grid. This is shown in Listing 1.

LayoutContainer calculates the pixel location of each Component given its grid position, and relocates it with a call to the Component's setBounds method if necessary.

Note that we did not implement addLayoutComponent and removeLayoutComponent because nothing special needs to be done when a Component is added to or removed from the Container.

Listing 2 shows the implementation of PositionableGridLayout for Java 1.1.

Layout Constraints: PositionableGridConstraints Class
An automatic LayoutManager - that is, one that would not allow the programmer to control the location and size of each Component - would normally be able to work alone. For a LayoutManager that allows the programmer to individually fix the location and size of the Components, however, an assisting class is required. Such is the case with AWT's GridBagLayout, which requires GridBagConstraints to work. In our example we need to be able to position any Component at an arbitrary location at any time. Following the example of GridBagLayout, we implement an assisting class, the PositionableGridConstraints, which allows us to specify the grid position of an added component.

The PositionableGridConstraints class is fairly simple to implement. It mainly stores away the grid position of a Component.

/**
* Constructs a PositionableGridConstraints.
*/
public PositionableGridConstraints( int gridx, int gridy, ... )
{
this.gridx = gridx;
this.gridy = gridy;
:
}

We need to assign the grid constraints to the Component, of course. Again, following the example set by AWT's GridBagLayout, we implement a setConstraints method in PositionableGridLayout whose purpose is to associate the grid constraints to a Component in a hashtable using the Component as the key, as shown in Listing 3. Listing 4 shows the implementation of PositionableGridConstraints.

Using the PositionableGridLayout Class
A good example of the use of the PositionableGridLayout class would be the familiar sliding puzzle. Initially, the tiles lie in a grid. As the user shifts the tiles' position, the application has to change the coordinate of each piece. Listing 3 shows the implementation of a simple 3x3 puzzle, the core of which follows it. Note in Listing 5 the convenience of being able to position Components by grid position instead of pixel location.


Figure 1:  Puzzle program

Conclusion
We've shown the use of LayoutManager beyond the standard AWT-supplied implementation. We've shown how customized LayoutManager can be a powerful tool for implementing unique Component layout strategy for any Container.

In a future article we'll make use of the PositionableGridLayout class and the Widget interface class featured in an earlier issue of JDJ (Vol. 3, Issue 6) to implement a TreeViewer widget, a component for laying out trees in either horizontal or vertical format.

Download Source Code
The program in this article requires the callback and widget code from the Implementing Callback and Widget-izing AWT articles published in JDJ (Vol. 3, Issues 4 and 6, respectively). Full source code (including a complete version of Puzzle) can be downloaded free from www.wigitek.com. A more extensive version supporting programmer-defined Component size, autoadjustment and alignment for Java versions 1.0 and 1.1 is also available from Wigitek Corporation at the same Web site.

About the Author
Daniel Dee, president of Wigitek Corporation, holds two MS degrees and has more than 10 years' working experience in the development of GUI software toolkits. Daniel has been involved with Java since its inception. He can be reached at [email protected]

	

Listing 1

/**
 * Copyright (c) 1997 Daniel Dee
 * Description:
   Package contains common classes for the Vivigraphics widget
   Toolkit - a callback-based toolkit.
   Originally, part of the Eva Toolkit - the prototype
   implementation.
 */
package com.wigitek.vivigraphics.widget.gui;


import java.awt.Component;
import java.awt.Container;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.util.Hashtable;


import com.wigitek.vivigraphics.widget.common.*;


/**
 * This class implements a layout with fixed-size grids. The dimension
   of the grids can be either specified or be constantly adjusted
   to be able to fully contain any of the components in the layout.
   PositionableGridLayout works with PositionableGridConstraints to
determine how
   to position a given Component within its Container.
 * @version $Revision$
 * @author Originally written by Daniel Dee, 2/21/96
 * @see GridConstraints
 */
public class PositionableGridLayout extends Object implements LayoutManager
{
  /**
   * Creates a PositionableGridLayout with a default grid width and height.
     Automatically adjusts size of grid to accomodate the largest component.
   */
  public PositionableGridLayout ()
  {
    this( _defaultGridWidth, _defaultGridHeight, new Insets(2,2,2,2),
AUTO_ADJUST );
  }



  /**
   * Creates a PositionableGridLayout with a specified grid width and height.
   * @param gridWidth the specified gridWidth
   * @param gridHeight the specified gridHeight
   * @param adjust specifies to auto-adjust to accomodate the largest component
   */
  public PositionableGridLayout( int gridWidth, int gridHeight, Insets
insets, int adjust )
  {
    comptable = new Hashtable();                // creates a
component-constraints hash table
    defaultConstraints = new PositionableGridConstraints(); // creates a
default PositionableGridConstraints for components whose constraints are
not specified
    this.gridWidth = gridWidth + insets.left + insets.right;
    this.gridHeight = gridHeight + insets.top + insets.bottom;
    this.insets = insets;
    this.adjust = adjust;
  }



  /**
   * Sets the constraints for the specified component.
   * @param comp the component to be modified
   * @param constraints the constraints to be applied
   */
  public void setConstraints(Component comp, PositionableGridConstraints
constraints)
  {
    comptable.put(comp, constraints.clone());
  }



  /**
   * Retrieves the constraints for the specified component.  A copy of
     the constraints is returned.
   * @param comp the component to be queried
   */
  public PositionableGridConstraints getConstraints(Component comp)
  {
    PositionableGridConstraints constraints =
(PositionableGridConstraints)comptable.get(comp);



    // If constraints have not been set for the specified component,
    // return a copy of the default one.
    if (constraints == null)
    {
      setConstraints(comp, defaultConstraints);
      constraints = (PositionableGridConstraints)comptable.get(comp);
    }


    return (PositionableGridConstraints)constraints.clone();
  }


  /**
   * Retrieves the constraints for the specified component.  The return
     value is not a copy, but is the actual constraints class used by the
     layout mechanism.
   * @param comp the component to be queried
   */
  public PositionableGridConstraints lookupConstraints(Component comp)
  {
    PositionableGridConstraints constraints =
(PositionableGridConstraints)comptable.get(comp);

    // If constraints have not been set for the specified component,
    // return a copy of the default one.
    if (constraints == null) {
      setConstraints(comp, defaultConstraints);
      constraints = (PositionableGridConstraints)comptable.get(comp);
    }

    return constraints;
  }

  /**
   * Returns the pixel coordinate identified by the grid
     location x and y.
   * @param gridx the grid x location
   * @param gridy the grid y location
   * @returns the pixel position
   */
  public Point coordinates(int gridx, int gridy)
  {
    Point loc = new Point(0,0);

    if (layoutInfo == null)
      return loc;

    loc.x = layoutInfo.startx + gridx * gridWidth;
    loc.y = layoutInfo.starty + gridy * gridHeight;
    return loc;
  }

  /**
   * Returns the grid position identified by the pixel
     coordinates x and y.
   * @param x the pixel x coordinate
   * @param y the pixel y coordinate
   * @returns the grid position
   */
  public Point getLocation(int x, int y)
  {
    Point loc = new Point(0,0);
    int i, d;

    if (layoutInfo == null)
      return loc;

    // Find the row position by scanning from the starting
    // pixel position of the layout, and counting the number
    // of gridWidth increments required to get the pixel
    // coordinate x. The number of increments is the grid x
    // position.
    d = layoutInfo.startx;
    for (i=0; i<layoutInfo.width; i++)
    {
      d += gridWidth;
      if (d > x)
            break;
    }
    loc.x = i;

    // Find the column position by scanning from the starting
    // pixel position of the layout, and counting the number
    // of gridHeight increments required to get the pixel
    // coordinate y. The number of increments is the grid y
    // position.
    d = layoutInfo.starty;
    for (i=0; i<layoutInfo.height; i++) {
      d += gridHeight;
      if (d > y)
            break;
    }
    loc.y = i;

    return loc;
  }

  public Component getComponent(Container parent, int gridx, int gridy)
  {
    Component comp;
    int compindex;
    PositionableGridConstraints constraints;
    Component components[] = parent.getComponents();

    /*
     * If the parent has no slaves anymore, then don't do anything
     * at all:  just leave the parent's size as-is.
     */
    if (components.length == 0)
      return null;

    /*
     * Now do the actual layout using the layout information
     * that has been collected.
     */

    for (compindex = 0 ; compindex < components.length ; compindex++)
    {
      comp = components[compindex];
      constraints = lookupConstraints(comp);
      if( constraints.gridx == gridx && constraints.gridy == gridy )
        return comp;
        }

        return null;
  }

  /**
   * Adds the specified component with the specified name to the layout.
     Does not apply.
   * @param name the name of the component
   * @param comp the component to be added
   */
  public void addLayoutComponent(String name, Component comp)
  {
  }

  /**
   * Removes the specified component from the layout. Does not apply.
   * @param comp the component to be removed
   */
  public void removeLayoutComponent(Component comp)
  {
  }

  /**
   * Returns the preferred dimensions for this layout given the components
     in the specified panel.
   * @param parent the component which needs to be laid out
   * @see #minimumLayoutSize
   */
  public Dimension preferredLayoutSize(Container parent)
  {
    return minimumLayoutSize(parent);
  }

  /**
   * Returns the minimum dimensions needed to layout the components
     contained in the specified panel.
   * @param parent the component which needs to be laid out
   * @see #preferredLayoutSize
   */
  public Dimension minimumLayoutSize(Container parent)
  {
    PositionableGridLayoutInfo layoutInfo = GetGridLayoutInfo(parent);
    Dimension d = new Dimension();
    Insets parentInsets = parent.getInsets();

    d.width = layoutInfo.width * gridWidth + parentInsets.left +
parentInsets.right;
    d.height = layoutInfo.height * gridHeight + parentInsets.top +
parentInsets.bottom;

    return d;
  }

  /**
   * Lays out the container in the specified panel.
   * @param parent the specified component being laid out
   * @see Container
   */
  public void layoutContainer(Container parent)
  {
    Component comp;
    int compindex;
    PositionableGridConstraints constraints;
    Component components[] = parent.getComponents();
    Point p;
    PositionableGridLayoutInfo info;

    /*
     * If the parent has no slaves anymore, then don't do anything
     * at all:  just leave the parent's size as-is.
     */
    if (components.length == 0)
      return;

    /*
     * Start by getting basic layout information.
     */
    info = GetGridLayoutInfo(parent);
    layoutInfo = info;

    /*
     * Now do the actual layout using the layout information
     * that has been collected.
     */

    for (compindex = 0 ; compindex < components.length ; compindex++)
    {
      comp = components[compindex];
      if (!comp.isVisible())
        continue;
      constraints = lookupConstraints(comp);

      p = coordinates( constraints.gridx, constraints.gridy );

      // Reshape the component according the constraints associated with it.
      // Then set the grid dimension in the component's constraints to the
actual value.
      constraints = lookupConstraints(comp);
      if( constraints.gridwidth == 0 || constraints.gridheight == 0 )
      {
        Dimension compPreferredSize = comp.getPreferredSize();
        constraints.gridwidth = constraints.gridwidth ==
PositionableGridConstraints.PREFERRED_SIZE ?
                                    compPreferredSize.width :
constraints.gridwidth;
        constraints.gridheight = constraints.gridheight ==
PositionableGridConstraints.PREFERRED_SIZE ?
                                    compPreferredSize.height :
constraints.gridheight;
      }

      // Adjust the component's position based on its specified anchor point
      //p = AdjustForGravity(constraints, p, constraints.gridwidth,
constraints.gridheight );

      Dimension compDimension = comp.getSize();
      Point compLocation = comp.getLocation();
      if( compLocation.x != p.x || compLocation.y != p.y ||
          compDimension.width != constraints.gridwidth ||
          compDimension.height != constraints.gridheight )
      {
        // If not fill the entire grid...
        /*if( constraints.gridwidth !=
PositionableGridConstraints.FILL_SIZE &&
                constraints.gridheight !=
PositionableGridConstraints.FILL_SIZE )
            else*/
            {
                int w = constraints.gridwidth !=
PositionableGridConstraints.FILL_SIZE ? compDimension.width : gridWidth;
                int h = constraints.gridheight !=
PositionableGridConstraints.FILL_SIZE ? compDimension.height : gridHeight;
                comp.setBounds(p.x, p.y, w, h);
            }
      }



    }
  }



  /*
   * Fill in an instance of the PositionableGridLayoutInfo structure for
the current set
   * of managed children.
   */
  protected PositionableGridLayoutInfo GetGridLayoutInfo(Container parent)
  {
    PositionableGridLayoutInfo r = new PositionableGridLayoutInfo();
    PositionableGridConstraints constraints;
    Dimension d;
    Insets parentInsets = parent.getInsets();
    Component comp, components[] = parent.getComponents();

    int compindex;
    int curX, curY, curRow, curCol;

    // Adjust startx and starty to include the parent's insets.
    r.startx = parentInsets.left;
    r.starty = parentInsets.top;

    // Figure out the dimensions of the layout grid.

    // Start with zero grid dimensions.
    r.width = r.height = 0;

    // If automatic adjustment is desired, find the largest component
    // in width and height.
    //if( adjust == AUTO_ADJUST )

    // Now find the components furthest right and furthest down.
    curRow = curCol = -1;

    for (compindex = 0 ; compindex < components.length ; compindex++)
    {
      comp = components[compindex];
      if (!comp.isVisible())
            continue;
      constraints = lookupConstraints(comp);

      curX = constraints.gridx;
      curY = constraints.gridy;

      // If x or y is negative, then use relative positioning.
      if (curX < 0 )
        curX = curCol >= 0 ? curCol + 1 : 0;

      if (curY < 0)
        curY = curRow >= 0 ? curRow + 1 : 0;

      // Adjust the layout's cell width and height to include the
      // new component if necessary.
      if( r.width < curX + 1 ) r.width = curX + 1;
      if( r.height < curY + 1 ) r.height = curY + 1;

      // Save x and y coordinate of this component in case the
      // next component added is position RELATIVE to this one.
      curRow = curX;
      curCol = curY;
    }

    return r;
  }

  private static final int _defaultGridWidth = 30;   // default grid
dimension in a new layout
  private static final int _defaultGridHeight = 30;
  private int gridWidth;                             // actual grid
dimension in a layout
  private int gridHeight;
  private Insets insets;                             // insets around grid
cells
  private int adjust;                                // specifies if
auto-adjust

  protected static final int MAXGRIDSIZE = 128;

  protected Hashtable comptable;
  protected PositionableGridConstraints defaultConstraints;
  protected PositionableGridLayoutInfo layoutInfo;

  public static final int AUTO_ADJUST = 0;
  public static final int NO_ADJUST = 1;

  public static final int HORIZONTAL = 2;
  public static final int VERTICAL = 3;

}



class PositionableGridLayoutInfo
{
  int width, height;            /* number of cells horizontally, vertically */
  int startx, starty;           /* starting point for layout in pixel
coordinates */
}


Listing 2

/**
 * Copyright (c) 1997 Daniel Dee
 * Description:
   Package contains common classes for the Vivigraphics widget
   Toolkit - a callback-based toolkit.
   Originally, part of the Eva Toolkit - the prototype
   implementation.
 */
package com.wigitek.vivigraphics.widget.common;


/**
 * This class encapsulates the instance variables that tell a
   GridLayout how to position a given Component within its Container.
   This class also implements Cloneable.
 * Note: The GridLayout class supported by this class is a completely
   different implementation of the AWT version. GridConstraints will
   not work with java.awt.GridLayout.
 * @version $Revision$
 * @author Originally written by Daniel Dee, 2/21/96
 * @author Last updated by $Author$, $Date$
 * @see GridLayout
 */
public class PositionableGridConstraints extends Object implements Cloneable
{
  /**
   * Constructs a PositionableGridConstraints specifying RELATIVE
positioning and
     selecting PREFERRED_SIZE as the values for
     gridx, gridy, gridwidth and gridheight.
   */
  public PositionableGridConstraints ()
  {
    this( RELATIVE, RELATIVE, PREFERRED_SIZE, PREFERRED_SIZE );
  }


  /**
   * Constructs a GridConstraints CENTERed on the specified grid position and
     selecting PREFERRED_SIZE as the values for gridwidth and gridheight.
   * @param gridx the x position in a GridLayout
   * @param gridy the y position in a GridLayout
   */
  public PositionableGridConstraints ( int gridx, int gridy )
  {
    this( gridx, gridy, PREFERRED_SIZE, PREFERRED_SIZE, CENTER );
  }


  /**
   * Constructs a GridConstraints with specified grid position at the
     specified anchor, and selecting PREFERRED_SIZE as the values for
gridwidth
     and gridheight.
   * @param gridx the x position in a GridLayout
   * @param gridy the y position in a GridLayout
   * @param anchor the specified anchor
   */
  public PositionableGridConstraints ( int gridx, int gridy, int anchor )
  {
    this( gridx, gridy, PREFERRED_SIZE, PREFERRED_SIZE, anchor );
  }


  /**
   * Constructs a GridConstraints with specified grid position and
dimension and
     CENTERed anchor.
   */
  public PositionableGridConstraints( int gridx, int gridy, int gridwidth,
int gridheight )
  {
    this( gridx, gridy, gridwidth, gridheight, CENTER );
  }


  /**
   * Constructs a GridConstraints with specified grid position and
dimension and anchor.
   */
  public PositionableGridConstraints( int gridx, int gridy, int gridwidth,
int gridheight, int anchor )
  {
    this.gridx = gridx;
    this.gridy = gridy;
    this.gridwidth = gridwidth;
    this.gridheight = gridheight;
    this.anchor = anchor;
  }


  /**
   * Implements the clone() method required by the Cloneable interface.
   */
  public Object clone ()
  {
      try
      {
              PositionableGridConstraints c =
(PositionableGridConstraints)super.clone();
              return c;
      }
      catch (CloneNotSupportedException e)
      {
              // this shouldn't happen, since we are Cloneable
              throw new InternalError();
      }
  }


  public int gridx, gridy;           // grid position of a component in a
GridLayout
  public int gridwidth, gridheight;  // dimension of a component in a
GridLayout
  public int anchor;                 // determines gravity of the component
in a
// GridLayout


  public static final int RELATIVE = -1;      // specifies a position to
the right
 // or below the previous component
  public static final int PREFERRED_SIZE = 0; // specifies the use of a
component's
 // preferred size
  public static final int FILL_SIZE = -1;     // specifies the use of grid
size as
 // the component's size


  public static final int CENTER    = 10; // centers component in grid
  public static final int NORTH     = 11; // position component towards top
and center
  public static final int NORTHEAST = 12; // position component towards top
and right
  public static final int EAST      = 13; // position component towards
center and right
  public static final int SOUTHEAST = 14; // position component towards
bottom and right
  public static final int SOUTH     = 15; // position component towards
bottom and center
  public static final int SOUTHWEST = 16; // position component towards
bottom and left
  public static final int WEST      = 17; // position component towards
center and left
  public static final int NORTHWEST = 18; // position component towards top
and left
}

Listing 3

/**
 * Copyright (c) 1998 Daniel Dee
 * Description:
   Puzzle game. Objective is to shift tiles around until you
   arrange them in numerical order. Click on the tile to shift, and
   the empty space to shift it to. A tile can be shifted only if
   a space is adjacent to it.
*/
import java.applet.Applet;
import java.awt.Event;
import java.awt.Frame;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;


import com.wigitek.vivigraphics.widget.common.*;
import com.wigitek.vivigraphics.widget.gui.*;


/**
 * This class extends the java.awt.Button class to support
   the callback mechanism by implementing the Widget interface.
 * @version $Revision$
 * @author Originally written by Daniel Dee, 6/4/98
 * @author Last updated by $Author$, $Date$
 */
public class Puzzle extends Applet
{
        private Panel panel;
        private PositionableGridLayout layout;


        public void init()
        {
                panel = new Panel();
                layout = new PositionableGridLayout();
                panel.addCallback( Panel.ACTIVATE_CALLBACK,
new PuzzleCallbackable(null,layout,panel,this) );
                panel.setLayout( layout );
                createPuzzle(this,panel,layout);
                CardLayout clayout = new CardLayout();
                setLayout( clayout );
                add( "Puzzle", panel );
        }


        public void destroy()
        {
                panel = null;
        }


        /**
        * Create the Puzzle.
        * @param c the Container for this Puzzle
        * @param l the PositionableGridLayout manager for this Puzzle
        */
        private static void createPuzzle( Container f, Container c,
PositionableGridLayout l )
        {
                PositionableGridConstraints constraints =
new PositionableGridConstraints();
constraints.gridwidth = constraints.gridheight =
PositionableGridConstraints.FILL_SIZE;
                // Create 8 tiles in order.
                for( int i=0; i < 3; i++ )
                        for( int j=0; j < 2 || (j == 2 && i != 2); j++ )
                        {
                                constraints.gridx = j;
                                constraints.gridy = i;
                               Button button = new Button(
new Integer(i*3+(j+1)).toString() );
                               button.addCallback( Button.ACTIVATE_CALLBACK,
new PuzzleCallbackable(button,l,c,f) );
                                l.setConstraints( button, constraints );
                                c.add( button );
                        }
        }


        /**
        * This is the main program to test the
PositionableGridLayout/Constraints class.
        * @param args unused
        */
        public static void main( String args[] )
        {
               Frame applic = new Frame( "Eva Puzzle" );
               Panel panel = new Panel();
                PositionableGridLayout layout = new PositionableGridLayout();
                panel.addCallback( Panel.ACTIVATE_CALLBACK,
new PuzzleCallbackable(null,layout,panel,applic) );
                panel.setLayout( layout );
                Puzzle.createPuzzle(applic,panel,layout);
                CardLayout clayout = new CardLayout();
                applic.setLayout( clayout );
                applic.add( "Puzzle", panel );


               Dimension d = layout.minimumLayoutSize(panel);
               applic.setSize(d.width,d.height);
               applic.setVisible(true);
               Insets i = applic.getInsets();
               applic.setSize(d.width+i.left+i.right,d.height+i.top+i.bottom);
               applic.setVisible(true);
        }
}


/**
 * Extension of Callbackable class to handle tile selection. If an empty
   space is selected, that means the previously selected tile should be
   moved to the empty location. Does nothing if that is not possible, i.e.,
   the empty space is not adjacent to the previously selected tile.
 */
class PuzzleCallbackable extends Callbackable
{
        /**
        * Constructs a PuzzleCallbackable.
        * @param b the button for which this Callback is created
        * @param i the PositionableGridLayout for this Puzzle's Container
        * @param f the Container for this Puzzle
        */
        public PuzzleCallbackable( Button b, PositionableGridLayout l,
Container f, Container c )
        {
                this.b = b;
                this.l = l;
                this.f = f;
                this.c = c;
        }


        /**
        * Activates this Callbackable.
        * @param e the event that triggers this callback
        */
        public boolean activate( Event e )
        {
                if( b == null ) // an empty space is selected, probably a
tile move
                {
                    if( selected != null ) // a tile was selected for moving
                    {
                        // Check if tile move is valid
                       PositionableGridConstraints c =
l.lookupConstraints(selected);
                       Point p = l.getLocation( e.x, e.y );
                       if( (c.gridx == p.x+1 || c.gridx == p.x-1) &&
c.gridy == p.y )
                                c.gridx = p.x;
               else if((c.gridy == p.y+1 || c.gridy == p.y-1) && c.gridx ==
p.x)
                                c.gridy = p.y;
                        f.doLayout();
                        f.repaint();
                    }
                }
                else // a selection
                    selected = b;
                return true;
        }


        private Container c, f;
        private PositionableGridLayout l;
        private Button b;
        private static Button selected;
}

Listing 4

/**
 * Copyright (c) 1997 Daniel Dee
 * Description:
   Package contains common classes for the ViviGraphics Widget
   Toolkit - a callback-based toolkit.
   Originally, part of the Eva Toolkit - the prototype
   implementation.
 */
package com.wigitek.vivigraphics.widget.gui;


//import java.awt.Event;


import com.wigitek.vivigraphics.widget.gui.*;
import com.wigitek.vivigraphics.widget.common.*;


/**
 * This class extends the java.awt.Button class to support
   the callback mechanism by implementing the Widget interface.
 * @version $Revision$
 * @author Originally written by Daniel Dee, 3/18/97
 * @author Last updated by $Author$, $Date$
 * @see Widget
 */
public class Panel extends java.awt.Panel implements Widget
{
    /**
     * Constructs an "unnamed" Panel.
     */
    public Panel()
    {
        this( "unnamed" );
    }


    /**
     * Constructs a new Panel with given name.
     * @param name the specified name
     */
    public Panel( String name )
    {
        super();
        this.name = name;
        addMouseListener(activateCallbackList);
    }


    /**
     * Registers a callback for the Panel. Panel currently
       recognizes RESIZE_CALLBACK. Unknown
       callback names are ignored.
     * @param callbackName the type of callback to register
     * @param callback     the object of the Callbackable class that will
activated
                           when trigger by the callback type given by
callbackName
     * @param clientData   the object of the ClientData class through which
the registering
                           object wants to pass data when the callback is
triggered.
     * @see Callbackable
     * @see ClientData
     */
    public void addCallback( String callbackName, Callbackable callback )
    {
        //if( callbackName.compareTo(RESIZE_CALLBACK) == 0 )
        //    resizeCallbackList.add( callback );
        if( callbackName.compareTo(ACTIVATE_CALLBACK) == 0 )
            activateCallbackList.add( callback );
    }


    /**
     * Sets the new size of this Panel widget.
     * Note: this is specifically written to support Eva.ScrollPane
     * for JDK 1.0. It calls the resizeCallback.
     * @param width the width
     * @param height the height
     */
    /*static private int prevW = 0, prevH = 0;
    public void setSize(int width, int height)
    {
        if( width != prevW || height != prevH )
        {
            prevW = width;
            prevH = height;
            Event evt = new Event(this,-1,null);
            resizeCallbackList.activate(evt);
        }
        super.setSize(width,height);
    }*/


    /**
     * Gets the name of the button.
       Implements getName() method of the Widget interface.
     */
    public String getName()
    {
        return name;
    }


    /**
     * Returns a String that represents the value of this Object.
       Overrides the method in java.lang.Object.
     * @return a String
     * @see Object
     */
    public String toString()
    {
        String string = super.toString();
        int length = string.length();
        return string.substring( 0, length-1 ) + ",name=" + name + "]";
    }


    /**
     * This is the main program to test the Panel class.
     * @param args unused
     */
    /*public static void main( String args[] )
    {
        TopLevelFrame toplevel = new TopLevelFrame( "Eva Toolkit: Panel
Test" );
        toplevel.addCallback( TopLevelFrame.DESTROY_CALLBACK, new
_TestCallbackable(), new ClientData() );
        Panel panel = new Panel();
        toplevel.add(panel);
        toplevel.setSize(350,200);
        toplevel.setVisible(true);
    }*/


    // The name of an instance of this class
    private String name;


    // Callbacks for scrollbar decrement.
    //private CallbackList resizeCallbackList = new CallbackList();
    //public static final String RESIZE_CALLBACK = "resizeCallback";
    private CallbackList activateCallbackList = new CallbackList();
    public static final String ACTIVATE_CALLBACK = "activateCallback";


Listing 5

/**
 * Copyright (c) 1997 Daniel Dee
 * Description:
   Package contains common classes for the Vivigraphics Widget
   Toolkit - a callback-based callback-based toolkit.
   Originally, part of the Eva Toolkit - the prototype
   implementation.
 */
package com.wigitek.vivigraphics.widget.gui;


import java.awt.Event;
import java.awt.AWTEvent;
import java.util.Vector;
import java.io.IOException;


import java.awt.event.*;


import com.wigitek.vivigraphics.widget.common.Callbackable;


/**
 * This class chains callbacks associated with a single callback type
   together.
 * @version $Revision: 2.1 $
 * @author Originally written by Daniel Dee, 3/17/97
 * @author Last updated by $Author: daniel $, $Date: 1998/05/27 23:06:41 $
 */
public class CallbackList extends Vector
                     implements ActionListener,
                                AdjustmentListener,
                                ComponentListener,
                                ContainerListener,
                                FocusListener,
                                ItemListener,
                                KeyListener,
                                MouseListener,
                                MouseMotionListener,
                                TextListener,
                                WindowListener
{
    /**
     * Constructs a CallbackList. Creates a Vector with 100 initial elements
       and a increment size of 100.
     */
    public CallbackList()
    {
        super(100, 100);
    }


    /**
     * This method is called when an action event occurs inside the
       source object. The corresponding callbacks added
       by the user are activated.
     * @param evt the event
     */
    public void actionPerformed(ActionEvent evt)
    {
        Event e = new Event( evt.getSource(), evt.getID(), evt );
        activate(e);
    }


   /**
     * This method is called when an itemStateChanged event occurs inside
       this Component. However, Java AWT does not seem to be able
       to detect disarm condition, and hence, unable to determine
       itemStateChanged correctly. The corresponding callbacks added
       by the user are activated.
     * @param evt the event
     */
    public void itemStateChanged(ItemEvent evt)
    {
        Event e = new Event( evt.getSource(), evt.getID(), evt );
        if( evt.getStateChange() == ItemEvent.SELECTED )
            activate(e);
    }


    /**
     * This method is called when a keyDown event occurs inside this
       TextArea. It allows preprocessing of input before display.
     * @param evt the event
     */
    public void keyPressed(KeyEvent evt)
    {
        int keychar;
        int keycode = (int)evt.getKeyCode();
        switch(keycode)
        {
            case KeyEvent.VK_UP:
                keychar = Event.UP;
                break;
            case KeyEvent.VK_DOWN:
                keychar = Event.DOWN;
                break;
            default:
                keychar = (int)evt.getKeyChar();
                break;
        }


        Event e = new Event( evt.getSource(), evt.getWhen(), evt.getID(),
                                0, 0, keychar, 0 );
        activate(e);
        evt.setKeyChar( (char)e.key );
        evt.setKeyCode( e.key );
    }


    public void textValueChanged(TextEvent evt)
    {
        Event e = new Event( evt.getSource(), evt.getID(), evt );
        activate(e);
    }


    public void mouseClicked(MouseEvent evt)
    {
        Event e = new Event( evt.getSource(), evt.getWhen(), evt.getID(),
                                evt.getX(), evt.getY(), 0, 0 );
        activate(e);
    }


    public void windowClosing(WindowEvent evt)
    {
    }


        // TO BE IMPLEMENTED
    public void adjustmentValueChanged(AdjustmentEvent evt) {}
    public void componentResized(ComponentEvent evt) {}
    public void componentMoved(ComponentEvent evt) {}
    public void componentShown(ComponentEvent evt) {}
    public void componentHidden(ComponentEvent evt) {}
    public void componentAdded(ContainerEvent evt) {}
    public void componentRemoved(ContainerEvent evt) {}
    public void focusGained(FocusEvent evt) {}
    public void focusLost(FocusEvent evt) {}
    public void keyTyped(KeyEvent evt) {}
    public void keyReleased(KeyEvent evt) {}
    public void mousePressed(MouseEvent evt) {}
    public void mouseReleased(MouseEvent evt) {}
    public void mouseEntered(MouseEvent evt) {}
    public void mouseExited(MouseEvent evt) {}
    public void mouseDragged(MouseEvent evt) {}
    public void mouseMoved(MouseEvent evt) {}
    public void windowOpened(WindowEvent evt) {}
    public void windowClosed(WindowEvent evt) {}
    public void windowIconified(WindowEvent evt) {}
    public void windowDeiconified(WindowEvent evt) {}
    public void windowActivated(WindowEvent evt) {}
    public void windowDeactivated(WindowEvent evt) {}


    /**
     * Removes a Callbackable from the CallbackList.
     * @param cb the callback
     */
    public boolean remove( Callbackable cb )
    {
        boolean returnValue = false;


        for( int i=0; i < size(); i++ )
        {
            Callbackable _cb = (Callbackable)elementAt(i);
            if( _cb == cb )
            {
                removeElementAt(i);
                returnValue = true;
            }
        }
        return returnValue;
    }


    /**
     * Removes a CallbackClientPair at the indexed position.
     * @param index the position of the CallbackClientPair in the
                    CallbackList.
     */
    public boolean removeAt( int index )
    {
        try
        {
            removeElementAt( index );
            return true;
        }
        catch( ArrayIndexOutOfBoundsException e )
        {
            return false;
        }
    }


    /**
     * Adds a callback to a chain.
     * @param callback     the callback
     * @return the Callbackable
     */
    public Callbackable add( Callbackable cb )
    {
        if( cb == null )
                cb = defaultCB;
        addElement( cb );
        return cb;
    }


    /**
     * Calls all the callbacks in this chain.
     * @param evt the event that triggers this callbacklist
     * @return true if event has been processed by the callback
               and no further processing is necessary; false
               if further processing from the activating object
               is required.
     */
    public boolean activate( Event e )
    {
        boolean returnValue = true;


        for( int i=0; i < size(); i++ )
        {
            Callbackable cb = (Callbackable)elementAt(i);
            returnValue = cb.activate( e );
        }


        return returnValue;
    }


    private CallbackListDefaultCallbackable defaultCB =
                        new CallbackListDefaultCallbackable();
}


/**
 * Default Callbackable.
 */
class CallbackListDefaultCallbackable extends Callbackable
{
    /**
     * Prints information about the activating
       object.
     * @param evt        the event that triggers this callback
     */
    public boolean activate( Event evt )
    {
        System.out.println( "Calling object is " + evt.target.toString() +
"." );
        System.out.println( "Activating event is " + evt.toString() + "." );
        return true;
    }
}
    

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.