All Downloads are FREE. Search and download functionalities are using the official Maven repository.

stanford.karel.HVLayout Maven / Gradle / Ivy

Go to download

This the original Stanford Karel for Java, packaged for Maven. ACM Library is included. See also https://cs.stanford.edu/people/eroberts/karel-the-robot-learns-java.pdf

The newest version!
/*
 * File: HVLayout.java
 * -------------------
 * This file implements a layout manager that simplifies the
 * task of creating component assemblies.  In general, clients
 * will create HPanels and VPanels, which use an HVLayout
 * of the appropriate direction.
 *
 * To use a stack panel, the first step is to create the
 * appropriate HPanel or VPanel.  For example, you can
 * create a horizontal assemblage like this:
 *
 *      HPanel hbox = new HPanel();
 *
 * From there, you simply add components to the panel in a
 * left-to-right order (top-to-bottom for VPanels).  The
 * call to add a component looks like this:
 *
 *      hbox.add(constraints, component);
 *
 * where constraints is a string consisting of a series of
 * options.  Each option has one of the following two forms:
 *
 *     /option
 *     /option:value
 *
 * The available options and their interpretations are:
 *
 * /stretch:xxx  The /stretch option indicates that this
 *               component should stretch to fill its grid cell
 *               if the enclosing panel stretches.  The available
 *               option values are both, horizontal, vertical,
 *               and none.  If the /stretch option does not
 *               appear (or /stretch:none is used), the component
 *               will maintain its size.  If /stretch appears
 *               without an option value, it is interpreted as
 *               /stretch:both.
 *
 * /fill         The /fill option is very much like stretch, but
 *               causes the component to stretch only in the
 *               panel direction.  Thus, /fill is the same as
 *               /stretch:horizontal for HPanels and the same as
 *               /stretch:vertical for VPanels.
 *
 * /anchor:xxx   The /anchor option applies to components that do
 *               not stretch and specifies how those components
 *               should be oriented with respect to the space
 *               available for that component.  The /anchor option
 *               in the first component determines the anchoring
 *               for the entire panel in the dimension of expansion;
 *               in the opposite dimension, each component is anchored
 *               individually.  The legal option values are the names
 *               of the compass points and center.  If no /anchor
 *               option appears, the component is centered.  If
 *               /anchor appears with no argument, it is taken to
 *               be /anchor:northwest.  The /anchor option values
 *               can also be used as complete options, so that
 *               /south is a shorthand for /anchor:south.
 *
 * /space:n      The /space:n option adds n pixels of space before
 *               the component (i.e., to the left of components in
 *               an HPanel and above components in a VPanel).
 *
 * /top:n        These options add a margin of n pixels in the
 * /bottom:n     indicated direction from the component.  You may
 * /left:n       specify as many of these options as you want; if
 * /right:n      no value appears for a particular direction, no
 *               extra space is allocated.
 *
 * /width:n      These options specify the preferred width and height
 * /height:n     of their component.
 *
 * The option string is case-insensitive.  Numeric values can easily
 * be inserted into the string using Java's concatenation operator.
 * For example, if you want to add myButton to the HPanel hbox and
 * specify that it be BUTTON_WIDTH pixels wide, you could write
 *
 *          hbox.add("/width:" + BUTTON_WIDTH, myButton);
 */

package stanford.karel;

import java.awt.*;
import java.util.*;

class HVLayout implements LayoutManager {

/* Constants */

    public static final int DEFAULT_SPACE = 5;

    public static final int HORIZONTAL = GridBagConstraints.HORIZONTAL;
    public static final int VERTICAL = GridBagConstraints.VERTICAL;

/*
 * Constructor: HVLayout
 * Usage: layout = new HVLayout(orientation);
 * ------------------------------------------
 * This method creates a new HVLayout object that grows along
 * the axis specified by orientation, which must be HORIZONTAL
 * or VERTICAL.
 */

    public HVLayout(int orientation) {
        this.orientation = orientation;
        constraintTable = new HashMap();
    }

/* LayoutManager interface */

/*
 * Method: addLayoutComponent
 * Usage: (not ordinarily called by clients)
 * -----------------------------------------
 * This method adds the specified component to the layout in the
 * position specified by name.
 */

    public void addLayoutComponent(String constraints, Component comp) {
        synchronized (comp.getTreeLock()) {
            constraintTable.put(comp, new OptionTable(constraints.toLowerCase()));
        }
    }

/*
 * Method: removeLayoutComponent
 * Usage: (not ordinarily called by clients)
 * -----------------------------------------
 * This method removes the specified component from the layour.
 */

    public void removeLayoutComponent(Component comp) {
        constraintTable.remove(comp);
    }

/*
 * Method: preferredLayoutSize
 * Usage: (not ordinarily called by clients)
 * -----------------------------------------
 * This method calculates the preferred layout size for the components
 * contained in the parent.
 */

    public Dimension preferredLayoutSize(Container parent) {
        synchronized (parent.getTreeLock()) {
            return getContainerSize(parent, PREFERRED);
        }
    }

/*
 * Method: minimumLayoutSize
 * Usage: (not ordinarily called by clients)
 * -----------------------------------------
 * This method calculates the minimum layout size for the components
 * contained in the parent.
 */

    public Dimension minimumLayoutSize(Container parent) {
        synchronized (parent.getTreeLock()) {
            return getContainerSize(parent, MINIMUM);
        }
    }

/*
 * Method: layoutContainer
 * Usage: (not ordinarily called by clients)
 * -----------------------------------------
 * This method performs the actual layout operations, resizing and
 * repositioning the components in the specified parent.
 */

    public void layoutContainer(Container parent) {
        synchronized (parent.getTreeLock()) {
            int nComponents = parent.getComponentCount();
            Dimension psize = parent.getSize();
            Dimension tsize = preferredLayoutSize(parent);
            int nStretch = getStretchCount(parent);
            int extra = getExtraSpace(psize, tsize, nStretch);
            Point origin = new Point(0, 0);
            if (nStretch == 0) origin = getInitialOrigin(parent, psize, tsize);
            for (int i = 0; i < nComponents; i++) {
                Component comp = parent.getComponent(i);
                OptionTable options = constraintTable.get(comp);
                Dimension csize = getLayoutSize(comp, options, PREFERRED);
                Dimension lsize = applyStretching(getStretchOption(options), csize, psize, extra);
                Dimension vsize = applyStretching(BOTH, csize, psize, extra);
                Rectangle bounds = getLayoutBounds(options, lsize, vsize, origin);
                comp.setBounds(bounds);
                if (orientation == HORIZONTAL) {
                    origin.x += lsize.width;
                } else {
                    origin.y += lsize.height;
                }
            }
        }
    }

/*
 * Private method: getContainerSize
 * Usage: size = layout.getContainerSize(parent, type);
 * ----------------------------------------------------
 * This method unifies the calculation of the minimum and preferred
 * sizes for the parent.  The type parameter must be one of the
 * constants MINIMUM and PREFERRED.  This method assumes that the
 * tree lock has been obtained by the caller.
 */

    private Dimension getContainerSize(Container parent, int type) {
        Dimension result = new Dimension(0, 0);
        int nComponents = parent.getComponentCount();
        for (int i = 0; i < nComponents; i++) {
            Component comp = parent.getComponent(i);
            OptionTable options = constraintTable.get(comp);
            Dimension size = getLayoutSize(comp, options, type);
            if (orientation == HORIZONTAL) {
                result.width += size.width;
                result.height = Math.max(result.height, size.height);
            } else {
                result.width = Math.max(result.width, size.width);
                result.height += size.height;
            }
        }
        return result;
    }

/*
 * Private method: getLayoutSize
 * Usage: size = layout.getLayoutSize(comp, options, type);
 * --------------------------------------------------------
 * This method computes the minimum or preferred size of the
 * specified component according to the constraint specifications
 * for the layout and any size information set by the component
 * class.  The type parameter must be one of the constants MINIMUM
 * and PREFERRED.
 */

    private Dimension getLayoutSize(Component comp, OptionTable options, int type) {
        Dimension size = new Dimension(0, 0);;
        if (type == PREFERRED) size = new Dimension(comp.getPreferredSize());
        size.width = options.getIntOption("width", size.width);
        size.height = options.getIntOption("height", size.height);
        size = limitSize(size, comp);
        Insets insets = getInsetOption(options);
        size.width += insets.left + insets.right;
        size.height += insets.top + insets.bottom;
        return size;
    }

/*
 * Private method: limitSize
 * Usage: size = limitSize(size, comp);
 * ------------------------------------
 * This method returns a new Dimension value that is within the
 * limits set by the minimum and maximum sizes for the component.
 */

    private Dimension limitSize(Dimension size, Component comp) {
        Dimension minSize = comp.getMinimumSize();
        Dimension maxSize = comp.getMaximumSize();
        int width = Math.max(minSize.width, Math.min(size.width, maxSize.width));
        int height = Math.max(minSize.height, Math.min(size.height, maxSize.height));
        return new Dimension(width, height);
    }

/*
 * Private method: getInsetOption
 * Usage: insets = layout.getInsetOption(options);
 * -----------------------------------------------
 * This method returns the Insets object specified by the options table,
 * which comes from the /top, /bottom, /left, /right, and /space options.
 */

    private Insets getInsetOption(OptionTable options) {
        Insets insets = new Insets(0, 0, 0, 0);
        if (options.isSpecified("space")) {
            switch (orientation) {
              case HORIZONTAL: insets.left = options.getIntOption("space"); break;
              case VERTICAL: insets.top = options.getIntOption("space"); break;
            }
        }
        if (options.isSpecified("left")) insets.left = options.getIntOption("left");
        if (options.isSpecified("right")) insets.right = options.getIntOption("right");
        if (options.isSpecified("top")) insets.top = options.getIntOption("top");
        if (options.isSpecified("bottom")) insets.bottom = options.getIntOption("bottom");
        return insets;
    }

/*
 * Private method: getStretchOption
 * Usage: stretch = layout.getStretchOption(options);
 * --------------------------------------------------
 * This method returns the stretching style specified by the options table,
 * which comes from the /stretch and /fill options.
 */

    private int getStretchOption(OptionTable options) {
        if (options.isSpecified("fill")) return orientation;
        if (options.isSpecified("stretch")) {
            String value = options.getOption("stretch", "both");
            if (value.equals("none")) return NONE;
            if (value.equals("horizontal")) return HORIZONTAL;
            if (value.equals("vertical")) return VERTICAL;
            if (value.equals("both")) return BOTH;
        }
        return NONE;
    }

/*
 * Private method: getAnchorOption
 * Usage: anchor = layout.getAnchorOption(options);
 * ------------------------------------------------
 * This method returns the anchoring style specified by the options table,
 * which comes from the /anchor option, if specified, or from any of the
 * anchoring constants.
 */

    private int getAnchorOption(OptionTable options) {
        int anchor = CENTER;
        if (options.isSpecified("anchor")) {
            String value = options.getOption("anchor", "northwest");
            if (value.equals("center")) {
                anchor = CENTER;
            } else if (value.equals("north")) {
                anchor = NORTH;
            } else if (value.equals("northeast") || value.equals("ne")) {
                anchor = NORTHEAST;
            } else if (value.equals("east")) {
                anchor = EAST;
            } else if (value.equals("southeast") || value.equals("se")) {
                anchor = SOUTHEAST;
            } else if (value.equals("south")) {
                anchor = SOUTH;
            } else if (value.equals("southwest") || value.equals("sw")) {
                anchor = SOUTHWEST;
            } else if (value.equals("west")) {
                anchor = WEST;
            } else if (value.equals("northwest") || value.equals("nw")) {
                anchor = NORTHWEST;
            }
        } else {
            if (options.isSpecified("center")) {
                anchor = CENTER;
            } else if (options.isSpecified("north")) {
                anchor = NORTH;
            } else if (options.isSpecified("northeast") || options.isSpecified("ne")) {
                anchor = NORTHEAST;
            } else if (options.isSpecified("east")) {
                anchor = EAST;
            } else if (options.isSpecified("southeast") || options.isSpecified("se")) {
                anchor = SOUTHEAST;
            } else if (options.isSpecified("south")) {
                anchor = SOUTH;
            } else if (options.isSpecified("southwest") || options.isSpecified("sw")) {
                anchor = SOUTHWEST;
            } else if (options.isSpecified("west")) {
                anchor = WEST;
            } else if (options.isSpecified("northwest") || options.isSpecified("nw")) {
                anchor = NORTHWEST;
            }
        }
        return anchor;
    }

/*
 * Private method: getStretchCount
 * Usage: nStretch = layout.getStretchCount(parent);
 * -------------------------------------------------
 * This method counts the number of stretchable components in the
 * parent, considering only the axis along which new components are
 * added.
 */

    private int getStretchCount(Container parent) {
        int nComponents = parent.getComponentCount();
        int nStretch = 0;
        for (int i = 0; i < nComponents; i++) {
            Component comp = parent.getComponent(i);
            OptionTable options = constraintTable.get(comp);
            int stretch = getStretchOption(options);
            if (orientation == HORIZONTAL) {
                if (stretch == HORIZONTAL || stretch == BOTH) nStretch++;
            } else {
                if (stretch == VERTICAL || stretch == BOTH) nStretch++;
            }
        }
        return nStretch;
    }

/*
 * Private method: getExtraSpace
 * Usage: extra = layout.getExtraSpace(psize, tsize, nStretch);
 * ------------------------------------------------------------
 * This method returns the amount of extra space that must be added
 * to each stretchable component along the main axis.  The psize
 * parameter gives the size of the parent, tsize is the total size
 * of the components, and nStretch is the number of components that
 * stretch.
 */

    private int getExtraSpace(Dimension psize, Dimension tsize, int nStretch) {
        if (nStretch == 0) {
            return 0;
        } else {
            if (orientation == HORIZONTAL) {
                return (psize.width - tsize.width) / nStretch;
            } else {
                return (psize.height - tsize.height) / nStretch;
            }
        }
    }

/*
 * Private method: applyStretching
 * Usage: lsize = layout.applyStretching(stretch, csize, psize, extra);
 * --------------------------------------------------------------------
 * This method computes the dimensions of the layout box to account for
 * the stretching of this component to fit into the available space.
 */

    private Dimension applyStretching(int stretch, Dimension csize, Dimension psize, int extra) {
        int width = csize.width;
        int height = csize.height;
        if (stretch == HORIZONTAL || stretch == BOTH) {
            if (orientation == HORIZONTAL) {
                width += extra;
            } else {
                width = psize.width;
            }
        }
        if (stretch == VERTICAL || stretch == BOTH) {
            if (orientation == VERTICAL) {
                height += extra;
            } else {
                height = psize.height;
            }
        }
        return new Dimension(width, height);
    }

/*
 * Private method: getLayoutBounds
 * Usage: layout.getLayoutBounds(options, lsize, vsize, origin);
 * -------------------------------------------------------------
 * This method returns the appropriate bounding box for the
 * component that appears in the specified layout area
 * beginning at the specified origin.
 */

    private Rectangle getLayoutBounds(OptionTable options, Dimension lsize, Dimension vsize, Point origin) {
        Insets insets = getInsetOption(options);
        int anchor = getAnchorOption(options);
        int dx = insets.left;
        int dy = insets.top;
        int width = lsize.width - insets.left - insets.right;
        int height = lsize.height - insets.top - insets.bottom;
        if (orientation == HORIZONTAL) {
            switch (anchor) {
              case NORTH: case NORTHWEST: case NORTHEAST:
                dy = insets.top;
                break;
              case CENTER: case WEST: case EAST:
                dy = insets.top + (vsize.height - lsize.height) / 2;
                break;
              case SOUTH: case SOUTHWEST: case SOUTHEAST:
                dy = vsize.height - insets.bottom - lsize.height;
                break;
            }
        } else {
            switch (anchor) {
              case WEST: case NORTHWEST: case SOUTHWEST:
                dx = insets.left;
                break;
              case CENTER: case NORTH: case SOUTH:
                dx = insets.left + (vsize.width - lsize.width) / 2;
                break;
              case EAST: case NORTHEAST: case SOUTHEAST:
                dx = vsize.width - insets.right - lsize.width;
                break;
            }
        }
        return new Rectangle(origin.x + dx, origin.y + dy, width, height);
    }

/*
 * Private method: getInitialOrigin
 * Usage: origin = layout.getInitialOrigin(parent, psize, tsize);
 * --------------------------------------------------------------
 * This method returns the initial origin at which the layout begins
 * and is called only if no components are stretchable along the main
 * axis.  The anchor property of the first component is used to determine
 * the alignment.
 */

    private Point getInitialOrigin(Container parent, Dimension psize, Dimension tsize) {
        int x = 0;
        int y = 0;
        if (parent.getComponentCount() > 0) {
            OptionTable options = constraintTable.get(parent.getComponent(0));
            int anchor = getAnchorOption(options);
            if (orientation == HORIZONTAL) {
                switch (anchor) {
                  case WEST: case NORTHWEST: case SOUTHWEST:
                    x = 0;
                    break;
                  case CENTER: case NORTH: case SOUTH:
                    x = (psize.width - tsize.width) / 2;
                    break;
                  case EAST: case NORTHEAST: case SOUTHEAST:
                    x = psize.width - tsize.width;
                    break;
                }
            } else {
                switch (anchor) {
                  case NORTH: case NORTHWEST: case NORTHEAST:
                    y = 0;
                    break;
                  case CENTER: case WEST: case EAST:
                    y = (psize.height - tsize.height) / 2;
                    break;
                  case SOUTH: case SOUTHWEST: case SOUTHEAST:
                    y = psize.height - tsize.height;
                    break;
                }
            }
        }
        return new Point(x, y);
    }

/*
 * Debugging method: anchorName
 * Usage: name = anchorName(anchor);
 * ---------------------------------
 * This method returns a readable name for the anchor constant.
 */

    public String anchorName(int anchor) {
        switch (anchor) {
          case CENTER: return "CENTER";
          case NORTH: return "NORTH";
          case NORTHEAST: return "NORTHEAST";
          case EAST: return "EAST";
          case SOUTHEAST: return "SOUTHEAST";
          case SOUTH: return "SOUTH";
          case SOUTHWEST: return "SOUTHWEST";
          case WEST: return "WEST";
          case NORTHWEST: return "NORTHWEST";
          default: return "undefined";
        }
    }

/* Private constants */

    private static final int MINIMUM = 0;
    private static final int PREFERRED = 1;

    private static final int CENTER = GridBagConstraints.CENTER;
    private static final int NORTH = GridBagConstraints.NORTH;
    private static final int NORTHEAST = GridBagConstraints.NORTHEAST;
    private static final int EAST = GridBagConstraints.EAST;
    private static final int SOUTHEAST = GridBagConstraints.SOUTHEAST;
    private static final int SOUTH = GridBagConstraints.SOUTH;
    private static final int SOUTHWEST = GridBagConstraints.SOUTHWEST;
    private static final int WEST = GridBagConstraints.WEST;
    private static final int NORTHWEST = GridBagConstraints.NORTHWEST;

    private static final int NONE = GridBagConstraints.NONE;
    private static final int BOTH = GridBagConstraints.BOTH;

/* Private variables */

    private int orientation;
    private HashMap constraintTable;

}

class OptionTable {

/*
 * Constructor: OptionTable
 * Usage: options = new OptionTable();
 *        options = new OptionTable(options);
 * ------------------------------------------
 * This method creates an empty option table and initializes it
 * from the specified string, if any.
 */

    public OptionTable() {
        table = new HashMap();
    }

    public OptionTable(String options) {
        this();
        parseOptions(options);
    }

/*
 * Method: parseOptions
 * Usage: options.parseOptions(options);
 * -------------------------------------
 * This method parses a string consisting of option specifications
 * in one of the two following forms:
 *
 *     /key
 *     /key:value
 */

    public void parseOptions(String options) {
        parseOptions(options, table);
    }

/*
 * Method: isSpecified
 * Usage: if (options.isSpecified(key)) . . .
 * ------------------------------------------
 * This method returns true if the key has been specified in the
 * option table.
 */

    public boolean isSpecified(String key) {
        return table.containsKey(key);
    }

/*
 * Method: getOption
 * Usage: value = options.getOption(key);
 *        value = options.getOption(key, defValue);
 * ------------------------------------------------
 * This method looks up an option key and returns the corresponding
 * value.  If the key is not defined, getOption returns null unless
 * a defValue parameter is used to specify a different default.
 */

    public String getOption(String key) {
        return getOption(key, null);
    }

    public String getOption(String key, String defValue) {
        String value = table.get(key.toLowerCase());
        return (value == null || value.equals("")) ? defValue : value;
    }

/*
 * Method: getIntOption
 * Usage: value = options.getIntOption(key);
 *        value = options.getIntOption(key, defValue);
 * ---------------------------------------------------
 * This method looks up an option key and returns the corresponding
 * value, parsed as an integer.  If the key is not defined, the
 * getIntOption method returns 0 unless a defValue parameter is
 * used to specify a different default.
 */

    public int getIntOption(String key) {
        return getIntOption(key, 0);
    }

    public int getIntOption(String key, int defValue) {
        String value = getOption(key, null);
        if (value == null || value.equals("")) return defValue;
        return (Integer.decode(value).intValue());
    }

/*
 * Method: getDoubleOption
 * Usage: value = options.getDoubleOption(key);
 *        value = options.getDoubleOption(key, defValue);
 * ------------------------------------------------------
 * This method looks up an option key and returns the corresponding
 * value, parsed as a double.  If the key is not defined, the
 * getDoubleOption method returns 0.0 unless a defValue parameter
 * is used to specify a different default.
 */

    public double getDoubleOption(String key) {
        return getDoubleOption(key, 0.0);
    }

    public double getDoubleOption(String key, double defValue) {
        String value = getOption(key, null);
        if (value == null || value.equals("")) return defValue;
        return (Double.valueOf(value).doubleValue());
    }

/*
 * Method: getFlagOption
 * Usage: flag = options.getFlagOption(key);
 *        flag = options.getFlagOption(key, defValue);
 * ---------------------------------------------------
 * This method looks up an option key and returns a boolean
 * whose value is true if the option maps to "true", "t", or "on"
 * and false if it maps to "false", "f", or "off".  A missing
 * option defaults to false unless the option is specified and
 * empty, in which case it defaults to true.
 */

    public boolean getFlagOption(String key) {
        return getFlagOption(key, false);
    }

    public boolean getFlagOption(String key, boolean def) {
        String value = table.get(key.toLowerCase());
        if (value == null) return def;
        value = value.toLowerCase();
        if (value.equals("") || value.equals("true") || value.equals("t") || value.equals("on")) return true;
        if (value.equals("false") || value.equals("f") || value.equals("off")) return false;
        throw new IllegalArgumentException("parseOptions: Illegal flag value");
    }

/* Private methods */

/*
 * Method: parseOptions
 * Usage: CSOptions.parseOptions(options, table);
 * ----------------------------------------------
 * This method parses a string consisting of option specifications
 * in one of the two following forms:
 *
 *     /key
 *     /key:value
 */
    private static void parseOptions(String options, HashMap map) {
        StringTokenizer scanner = new StringTokenizer(options + "/", "/:", true);
        String key = null;
        String value = null;
        int state = INITIAL_STATE;
        while (scanner.hasMoreTokens()) {
            String token = scanner.nextToken();
            switch (state) {
              case INITIAL_STATE:
                if (!token.equals("/")) {
                    key = token.toLowerCase();
                    value = "";
                    state = KEY_SEEN;
                }
                break;
              case KEY_SEEN:
                if (token.equals("/")) {
                    map.put(key, value);
                    state = INITIAL_STATE;
                } else if (token.equals(":")) {
                    state = COLON_SEEN;
                }
                break;
              case COLON_SEEN:
                value = token;
                state = VALUE_SEEN;
                break;
              case VALUE_SEEN:
                if (token.equals("/")) {
                    map.put(key, value);
                    state = INITIAL_STATE;
                } else {
                    throw new IllegalArgumentException("parseOptions: Illegal option string");
                }
                break;
            }
        }
    }

/* Private instance variables */
    private HashMap table;

/* Constants used in the parseOptions finite-state machine */

    private static final int INITIAL_STATE = 0;
    private static final int KEY_SEEN = 1;
    private static final int COLON_SEEN = 2;
    private static final int VALUE_SEEN = 3;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy