stanford.karel.HVLayout Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javakarel Show documentation
Show all versions of javakarel Show documentation
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;
}