/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ket.display.box;
import java.util.*;
import java.awt.image.BufferedImage;
import geom.Offset;
import geom.Position;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import javax.swing.*;
import ket.math.*;
import ketUI.Document;
import ket.display.ImageTools;
import ket.display.ColourScheme;
import ket.display.ColourSchemeDecorator;
import ketUI.panel.KetPanel; // Hack!
import ketUI.Ket;
/**
* In order to draw an equation to a Graphics object, it may be represented by
* a hierarchy of subclasses of Box. These provide a framework to describe the
* layout of an equation in terms of rectangles (hence box) in which each
* sub-component will be drawn. The box superclass provides standard
* mechanisms by which a box may be interrogated for its dimensions, aligned and
* drawn to a specified graphics object.
*/
public abstract class Box {
///////////////////////////////////////////////////////////////
// SETTINGS FLAGS //
///////////////////////////////////////////////////////////////
// TODO: Change to an enum set?:
public static final int NONE = 0x00000;
public static final int HORIZONTAL_STRETCH = 0x00001;
public static final int LEFT_ALIGN = 0x00002;
public static final int X_CENTRE_ALIGN = 0x00004;
public static final int RIGHT_ALIGN = 0x00008;
public static final int VERTICAL_STRETCH = 0x00010;
public static final int TOP_ALIGN = 0x00020;
public static final int Y_CENTRE_ALIGN = 0x00040;
public static final int BOTTOM_ALIGN = 0x00080;
public static final int LARGE_FONT = 0x00100;
public static final int NORMAL_FONT = 0x00200;
public static final int TEXT_FONT = 0x00400;
public static final int SMALL_FONT = 0x00800;
public static final int PRESERVE_ASPECT_RATIO = 0x01000;
public static final int ITALIC_FONT = 0x02000;
public static final int PLAIN_FONT = 0x04000;
public static final int BOLD_FONT = 0x08000;
public static final int SUPPRESS_BRACKETS = 0x10000;
public static final int PAD = 0x20000;
///////////////////////////////////////////////////////////////
// OTHER CONSTANTS //
///////////////////////////////////////////////////////////////
/**
* The ratio between LARGE_FONT and NORMAL_FONT sizes as well as the
* ratio between NORMAL_FONT and SMALL_FONT sizes.
*/
public static final int FONT_SIZE_FACTOR = 2;
/**
* In practice, a mouse click by a user expresses a location with a
* lower precision than that of the pixel coordinate system in which it
* is expressed.
*/
public static final int MOUSE_PRECISION = 1; // pixels
/**
* The user interface allows font sizes to be changed; this provides
* the lower bound to stop the displayed equations from being too
* small.
*/
public static final int SMALLEST_FONT_SIZE = 5;
/**
* The largest allowed font size for the user interface to stop
* unreasonably large equations from being displayed.
*/
public static final int LARGEST_FONT_SIZE = 150;
/**
* The default size of a box's font.
*/
public static final int DEFAULT_BOX_FONT_SIZE = 25;
/**
* The size of text.
*/
public static final double TEXT_SCALE_FACTOR = 2.0 / 3.0;
/**
* In order to debug the alignment and display of equations, box
* outlines can be drawn around them. This option switches this on.
*/
public static boolean displayBorders = false;
///////////////////////////////////////////////////////////////
// ATTRIBUTES //
///////////////////////////////////////////////////////////////
/**
* The way the box is aligned horizontally (including stretching).
*/
int horizontalAlignment;
/**
* The way the box is aligned vertically (including stretching).
*/
int verticalAlignment;
/**
* How should this box's font be scaled relative to that of its
* container?
*/
int relativeFontSize;
/**
* What font style should any text contain?
*/
int style;
/**
* When drawing, should the contents be scaled to fill the x and y
* dimensions of the box?
*/
boolean preserveAspectRatio;
/**
* Make certain boxes larger to make click-and-drag algebra smoother.
*/
boolean pad;
/**
* The size of this box.
*/
protected int fontSize;
/**
* The minimum rectangle size required to display this box.
*/
protected Offset innerRectangle;
/**
* Normally a box will be drawn within a larger region than the minimum
* size, and the actual size of the box is stored here.
*/
protected Offset outerRectangle;
/**
* Boxes are often stored within other boxes and in order for their
* parent to align each component, the child boxes each store a
* separate shift relative to their parent.
*/
protected Offset parentalShift;
/**
* Each box is drawn relative to its minimum size, but boxes are in
* general allocated a larger space in which to be drawn so an shift
* must be specified to align the actual (minimum) box within the
* larger, actual size.
*/
protected Offset alignmentShift;
/**
* In order to associate mouse clicks with a particular argument, a box
* must associate an enclosed region with the argument that created it.
*/
private Argument argument;
/**
* Only record the top left coordinate when actually painting the box.
* It is used to determine whether a mouse click is within its inner
* rectangle.
*/
protected Position topLeft; // TODO: Remove.
/**
* The relative displacement from the root of the box hierarchy.
*/
protected Offset rootOffset;
public void setProperties(long...settings) {
for (long s : settings) {
setProperty(s);
}
}
/**
* Set an additional settings flag.
*/
public void setProperty(long settings) {
if ((settings&HORIZONTAL_STRETCH)==HORIZONTAL_STRETCH) {
horizontalAlignment = HORIZONTAL_STRETCH;
} else if ((settings&LEFT_ALIGN)==LEFT_ALIGN) {
horizontalAlignment = LEFT_ALIGN;
} else if ((settings&RIGHT_ALIGN)==RIGHT_ALIGN) {
horizontalAlignment = RIGHT_ALIGN;
} else if ((settings&X_CENTRE_ALIGN)==X_CENTRE_ALIGN) { // default
horizontalAlignment = X_CENTRE_ALIGN;
}
if ((settings&VERTICAL_STRETCH)==VERTICAL_STRETCH) {
verticalAlignment = VERTICAL_STRETCH;
} else if ((settings&TOP_ALIGN)==TOP_ALIGN) {
verticalAlignment = TOP_ALIGN;
} else if ((settings&Y_CENTRE_ALIGN)==Y_CENTRE_ALIGN) {
verticalAlignment = Y_CENTRE_ALIGN;
} else if ((settings&BOTTOM_ALIGN)==BOTTOM_ALIGN) {
verticalAlignment = BOTTOM_ALIGN;
}
if ((settings&PRESERVE_ASPECT_RATIO)==PRESERVE_ASPECT_RATIO) {
preserveAspectRatio = true;
}
if ((settings&PAD)==PAD) {
pad = true;
}
if ((settings&LARGE_FONT)==LARGE_FONT) {
relativeFontSize = LARGE_FONT;
} else if ((settings&SMALL_FONT)==SMALL_FONT) {
relativeFontSize = SMALL_FONT;
} else if ((settings&TEXT_FONT)==TEXT_FONT) {
relativeFontSize = TEXT_FONT;
} else if ((settings&NORMAL_FONT)==NORMAL_FONT) {
relativeFontSize = NORMAL_FONT;
}
if ((settings&PLAIN_FONT)==PLAIN_FONT) {
style = PLAIN_FONT;
} else if ((settings&BOLD_FONT)==BOLD_FONT) {
style = BOLD_FONT;
} else if ((settings&ITALIC_FONT)==ITALIC_FONT) {
style = ITALIC_FONT;
}
}
// TODO: Is this method required, or can you refactor it away?
public void setArgument(Argument argument) {
this.argument = argument;
}
/**
* Return the alignment, stretching and font size and style flags
* associated with this box.
*/
public int getSettings() {
return horizontalAlignment|
verticalAlignment|
(preserveAspectRatio?PRESERVE_ASPECT_RATIO:0)|
relativeFontSize|
style|
(pad?PAD:0);
}
public Box findBoxByArgument(Argument target) {
if (target==getArgument()) {
return this;
} else {
return null;
}
}
public Vector<Box> allDescendents() {
Vector<Box> descendents = new Vector<Box>();
addAllDescendants(descendents);
for (Box b : descendents) {
Ket.out.println("\t" + b.getClass() + "\t" + b.getArgument());
}
return descendents;
}
public void addAllDescendants(Vector<Box> descendents) {
descendents.add(this);
}
/**
* Diagnostic: list all arguments that this box and its children contain.
*/
public Vector<Argument> getAllArguments() {
Vector<Argument> args = new Vector<Argument>();
addAllArguments(args);
return args;
}
protected void addAllArguments(Vector<Argument> args) {
if (argument!=null) {
args.add(argument);
}
}
public Argument getArgument() {
return argument;
}
public Position getTopLeft() {
return topLeft;
}
/**
* Get the area of the inner box except for that of its children.
*/
public double getNetArea() {
return getArea();
}
/**
* The area of the inner rectangle (including that covered by child boxes).
*/
public double getArea() {
if (innerRectangle!=null) {
return innerRectangle.width * innerRectangle.height;
} else {
return 0;
}
}
/**
* Recursively remove all arguments.
*/
public void clearArgument() {
argument = null;
}
public void setTopLeft(Position topLeft) {
this.topLeft = topLeft;
}
/**
* An abstract constructor to be overridden in order to initialize
* alignment and other settings with which this box will be drawn.
*/
protected Box(Argument argument, long settings) {
this.argument = argument;
this.topLeft = null;
initDefaultSettings(settings);
setProperty(settings);
this.fontSize = -1;
// TODO: Decay border size with increasing box depth
this.innerRectangle = null;
this.outerRectangle = null;
// If the parent does not move the box, or there is no parent,
// this defaults to zero shift.
this.parentalShift = new Offset(0.0, 0.0);
this.alignmentShift = null;
}
protected void initDefaultSettings(long settings) {
horizontalAlignment = X_CENTRE_ALIGN;
verticalAlignment = Y_CENTRE_ALIGN;
preserveAspectRatio = false;
relativeFontSize = NORMAL_FONT;
style = ITALIC_FONT;
pad = false;
}
/**
* Test if a particular property has been set.
*/
public boolean hasProperty(long property) {
return (getSettings() & property) == property;
}
/**
* The font size of a box is scaled from that of its parent and
* requires the Graphics object to which the box is to be drawn.
*/
protected void fontSetup(int parentFontSize) {
switch (relativeFontSize) {
case LARGE_FONT:
this.fontSize = parentFontSize * FONT_SIZE_FACTOR;
break;
case NORMAL_FONT:
this.fontSize = parentFontSize;
break;
case TEXT_FONT:
this.fontSize = (int) (parentFontSize * TEXT_SCALE_FACTOR);
break; //?
case SMALL_FONT:
this.fontSize = parentFontSize / FONT_SIZE_FACTOR;
break;
}
}
/**
* In order to draw boxes within another box, their minimum sizes must
* be determined which in turn requires additional information such as
* font size which is also calculated.
*/
public void setupInnerRectangle(int parentFontSize) {
fontSetup(parentFontSize);
calcMinimumSize();
}
/**
* In order to draw boxes within another box, their minimum sizes must
* be determined which in turn requires additional information such as
* font size which is also calculated. Additionally, if the actual size
* of the region in which it will be drawn is specified, then the
* alignment of the inner box (innerRectangle) relative to its actual size
* may be calculated. In this way, the same method may be called twice
* by a container box firstly to determine each component's minimum
* size and from that information to specify its actual size.
*/
public void setup(int fontSize, Offset outerRectangle) {
setupInnerRectangle(fontSize);
setupOuterRectangle(outerRectangle);
}
public void setupAndPaint(
Graphics2D g2D,
ColourScheme colourScheme,
int fontSize,
Position topLeft,
Offset rectangle,
boolean clearBackground) {
setup(fontSize, rectangle);
if (clearBackground) {
clear(g2D, colourScheme, topLeft);
}
paint(g2D, topLeft, colourScheme);
}
/**
* Return the horizontal position of the box's inner rectangle's left edge. The actual
* drawing is restricted to a smaller rectangle within a larger one and
* this method takes the top left coordinate of the outer box to
* determine this position.
*/
public final double getXPosition(Position topLeft) {
if (topLeft==null || parentalShift==null || alignmentShift==null) {
return 0.0;
}
return topLeft.x + parentalShift.width + alignmentShift.width;
}
/**
* Return the vertical position of this box's inner rectangle's top edge. The actual
* drawing is restricted to a smaller rectangle which is shifted
* relative to the larger one.
*/
public final double getYPosition(Position topLeft) {
if (topLeft==null || parentalShift==null || alignmentShift==null) {
return 0.0;
}
return topLeft.y + parentalShift.height + alignmentShift.height;
}
/**
* Determine the position of the box's inner rectangle's top left
* corner given the outer box's top left corner position. The shift
* takes into account offsets due to alignment by a parent box,
* internal alignment between the minimum sized box and the rectangular
* region reserved for it.
*/
public Position getPosition(Position topLeft) {
return new Position(getXPosition(topLeft), getYPosition(topLeft));
}
/**
* There is no grantee that the available space in which the box is to
* be drawn will be larger than its minimum size, and this method may
* be used to test that this does not take place.
*/
public boolean isLargeEnough() {
boolean wideEnough = innerRectangle.width<=outerRectangle.width;
boolean tallEnough = innerRectangle.height<=outerRectangle.height;
return wideEnough && tallEnough;
}
/**
* Accessor method to determine the minimum size that this box can be
* drawn within. In general boxes consist of a rectangle in which
* symbols are drawn which is padded by white space. This method
* ignores the white space padding and returns only the inner box size.
*/
public Offset getInnerRectangle() {
return innerRectangle;
}
public Offset getOuterRectangle() {
return outerRectangle;
}
protected void setActualWidth(double width) {
if (outerRectangle==null) {
outerRectangle = new Offset(width, Double.NaN);
} else {
outerRectangle.width = width;
}
}
protected void setActualHeight(double height) {
if (outerRectangle==null) {
outerRectangle = new Offset(Double.NaN, height);
} else {
outerRectangle.height = height;
}
}
public Offset getParentalShift() {
return parentalShift;
}
public Offset getAlignmentShift() {
return alignmentShift;
}
/**
* One of the three shifts of the inner rectangle relative to the
* available region which aligns according to the settings.
*/
public void setupOuterRectangle(Offset outerRectangle) {
this.outerRectangle = outerRectangle;
alignmentShift = new Offset(0.0, 0.0);
switch (horizontalAlignment) {
case HORIZONTAL_STRETCH:
alignmentShift.width = 0.0;
break;
case LEFT_ALIGN:
alignmentShift.width = 0.0;
break;
case X_CENTRE_ALIGN:
assert outerRectangle!=null;
assert innerRectangle!=null;
alignmentShift.width = outerRectangle.width/2 - innerRectangle.width/2;
break;
case RIGHT_ALIGN:
assert outerRectangle!=null;
assert innerRectangle!=null;
alignmentShift.width = outerRectangle.width - innerRectangle.width;
break;
}
switch (verticalAlignment) {
case VERTICAL_STRETCH:
alignmentShift.height = 0.0;
break;
case TOP_ALIGN:
alignmentShift.height = 0.0;
break;
case Y_CENTRE_ALIGN:
alignmentShift.height = outerRectangle.height/2 - innerRectangle.height/2;
break;
case BOTTOM_ALIGN:
alignmentShift.height = outerRectangle.height - innerRectangle.height;
break;
}
}
/**
* Draw the background colour over the region in which this box would be
* drawn, clearing it.
*/
public void clear(Graphics2D g2D, ColourScheme colourScheme, Position topLeft) {
int x = (int) getXPosition(topLeft);
int y = (int) getYPosition(topLeft);
int width = (int) innerRectangle.width;
int height = (int) innerRectangle.height;
g2D.setColor(colourScheme.getBackgroundColour());
g2D.fillRect(x, y, width, height);
}
boolean background = false;
public void setBackground(boolean background) {
this.background = background;
}
public boolean getBackground() {
return background;
}
private Color getBGColour() {
int depth = getDepth();
//T int depth = (int) (10.0 * Math.random());
double r = 255.0 - 255.0/(2.0+depth);
double g = 255.0/(2.0+depth);
double b = 255.0/(2.0+depth);
return new Color((int) r, (int) g, (int) b);
}
/**
* Draw part of an equation which is bound from the depth of the
* current argument through null descendants.
*/
public void paintBand(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
this.topLeft = topLeft;
if (displayBorders) {
drawBorder(g2D, topLeft, colourScheme);
}
colourSetup(g2D, colourScheme);
drawBand(g2D, topLeft, colourScheme);
}
/**
* Draw the equation to the given graphics object at the requested
* location.
*/
public void paint(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
this.topLeft = topLeft;
if (background) {
g2D.setColor(getBGColour());
int innerX = (int) getXPosition(topLeft);
int innerY = (int) getYPosition(topLeft);
int innerWidth = (int) innerRectangle.width;
int innerHeight = (int) innerRectangle.height;
g2D.fillRect(innerX, innerY, innerWidth, innerHeight);
}
if (displayBorders) {
drawBorder(g2D, topLeft, colourScheme);
}
colourSetup(g2D, colourScheme);
draw(g2D, topLeft, colourScheme);
}
public ColourScheme getLocalColourScheme(ColourScheme colourScheme) {
ColourScheme localCS = getColourScheme();
return localCS!=null ? localCS : colourScheme;
}
public void colourSetup(Graphics2D g2D, ColourScheme colourScheme) {
ColourScheme cs = getLocalColourScheme(colourScheme);
if (argument!=null) {
Color boxColour = cs.getBoxColour(argument);
g2D.setColor(boxColour);
} else {
g2D.setColor(new Color(0, 0, 0)) ; // !ERROR
}
}
public static class Band {
public Box box;
public BufferedImage image;
public Position position;
public Band(Box box, Position position, ColourScheme colourScheme) {
this.box = box;
this.image = ImageTools.paintInnerBoxToImage(box, true, colourScheme);
this.position = position;
}
}
public Band boxesByDepth(Argument a, TreeMap<Integer, Vector<Band>> map, Position topLeft, ColourScheme colourScheme) {
Argument b = getArgument();
if (b==null) {
b = a;
}
if (b==null) return null;
int depth = b.getDepth();
//- BufferedImage image = ImageTools.paintInnerBoxToImage(this, true);
Band band = new Band(this, getPosition(topLeft), colourScheme);
if (map.containsKey(depth)) {
map.get(depth).add(band);
} else {
Vector<Band> v = new Vector<Band>();
v.add(band);
map.put(depth, v);
}
return band;
}
public int getDepth() {
if (argument==null) {
return 0;
}
return argument.getDepth();
}
// Only overridden within boxList.
protected void drawBand(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
draw(g2D, topLeft, colourScheme);
}
/**
* Draw the outline of the both the region reserved for drawing the box
* and for the smaller rectangle within which all content is restricted
* to.
*/
public void drawBorder(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
drawOuterBorder(g2D, topLeft, colourScheme);
drawInnerBorder(g2D, topLeft, colourScheme, true);
}
/**
* Draw the outline of this box's outer rectangle.
*/
public void drawOuterBorder(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
int outerX = (int) (topLeft.x + parentalShift.width);
int outerY = (int) (topLeft.y + parentalShift.height);
int outerWidth = (int) outerRectangle.width;
int outerHeight = (int) outerRectangle.height;
//- g2D.setColor(Color.GREEN);
Color colour = colourScheme.getArgColour(getDepth());
g2D.setColor(colour);
g2D.drawRect(outerX, outerY, outerWidth, outerHeight);
}
/**
* Draw the outline of this box's inner rectangle.
*/
public void drawInnerBorder(Graphics2D g2D, Position topLeft, ColourScheme colourScheme, boolean highlight) {
int innerX = (int) getXPosition(topLeft);
int innerY = (int) getYPosition(topLeft);
int innerWidth = (int) innerRectangle.width;
int innerHeight = (int) innerRectangle.height;
//- g2D.setColor(new Color(255, 0, 255));
//- colourSetup(g2D, colourScheme);
Color colour;
if (highlight) {
colour = colourScheme.getHighlightedColour(getDepth());
} else {
colour = colourScheme.getArgColour(getDepth());
}
g2D.setColor(colour);
g2D.drawRect(innerX, innerY, innerWidth, innerHeight);
}
public boolean innerBoxesOverlap(Box that) { // BUG: In scatter plot, topLeft is box-specific: fix.
// TopLeft doesn't matter because it is common to both boxes.
Position topLeft = this.topLeft!=null ? this.topLeft : new Position(0, 0);
//- if (this.getTopLeft()==null) return false; //D false
//- if (that.getTopLeft()==null) return false; //D false
Position rA = this.getPosition(topLeft);
Position rB = that.getPosition(topLeft);
Offset iA = this.getInnerRectangle();
Offset iB = that.getInnerRectangle();
return rA.x<(rB.x + iB.width) && (rA.x+iA.width)>rB.x && rA.y<(rB.y+iB.height) && (rA.y+iA.height)>rB.y;
}
/**
* Does this box, relative to topLeft, contain a given point within the INNER rectangle?
*/
public boolean withinInnerRectangle(Position p) {
// The box has yet to be displayed and so its location is not
// yet unknown.
if (topLeft==null) {
// The box has yet to be displayed and so its location
// is not yet unknown.
return false;
} else if (getXPosition(topLeft)>p.x) {
return false;
} else if (p.x>getXPosition(topLeft)+innerRectangle.width) {
return false;
}
if (getYPosition(topLeft)>p.y) {
return false;
} else if (p.y>getYPosition(topLeft)+innerRectangle.height) {
return false;
}
return true;
}
/**
* Create a buffered image that displays this object.
*/
public BufferedImage toBufferedImage(int width, int height, ColourScheme colourScheme) {
return ImageTools.boxToBufferedImage(this, width, height, colourScheme);
}
/**
* Create a buffered image that displays this object.
*/
public BufferedImage toBufferedImage(int width, int height, ColourScheme colourScheme, boolean background) {
return ImageTools.boxToBufferedImage(this, width, height, colourScheme, background);
}
/**
* Return the smallest possible BufferedImage with this box drawn on it.
*/
public BufferedImage toBufferedImage(ColourScheme colourScheme) {
return ImageTools.boxToBufferedImage(this, colourScheme);
}
/**
* Move recursively through a box's children while one of the children
* contains that box, then return the deepest child or the box itself
* if only it contains a match. Null is returned if the point is
* outside of box.
*/
public Argument findDeepestArgument(Position p) {
// Note: Extended by boxList.
return this.withinInnerRectangle(p) ? this.getArgument() : null;
}
public Box findDeepestBox(Position p) {
return this.withinInnerRectangle(p) ? this : null;
}
/**
* Step through the current box's argument and its descendents to find the given argument.
*/
public boolean containsArgument(Argument argument) {
return getArgument()==argument;
}
public int getFontSize() {
return fontSize;
}
/**
* Paint a box to the given graphics object after first converting it
* to a buffered image. This is just like painting to a graphics
* object except that aliassing effects from painting idividual symbols
* to an integer grid are consistent accross the entire image, and
* additional image processing is performed.
*/
public void paintAt(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
BufferedImage image = ImageTools.paintInnerBoxToImage(this, false, colourScheme);
//- Offset rootOffset = this.getRootOffset();
int boxXPos = (int) ( topLeft.x + getRootOffset().width );
int boxYPos = (int) ( topLeft.y + getRootOffset().height );
g2D.drawImage(image, boxXPos, boxYPos, null);
}
/**
* Paint images at a given location (without any internal offset) with
* the given scale factor.
*/
public void paintScaled(Graphics2D g2D, Position topLeft, int width, int height, ColourScheme colourScheme, boolean band) {
if (width==0 || height==0) {
return;
}
BufferedImage image = ImageTools.paintInnerBoxToImage(this, band, colourScheme);
if (image==null) {
return;
}
Image scaledImage = image.getScaledInstance(width, height, Image.SCALE_FAST);
//- Offset rootOffset = this.getRootOffset();
int boxXPos = (int) ( topLeft.x );
int boxYPos = (int) ( topLeft.y );
g2D.drawImage(scaledImage, boxXPos, boxYPos, null);
}
/**
* Assign an offset to the top of the box hierarchy.
*/
public void calcRootOffset() {
calcRootOffset(new Offset(0.0, 0.0));
}
/**
* Recursively set the current offset relative to that of its parent.
*/
protected void calcRootOffset(Offset parentOffset) {
Position rootPosition = getPosition(new Position(parentOffset));
rootOffset = new Offset(rootPosition);
}
/**
* Return the displacement from the root of this box hierarchy to this box.
*/
public Offset getRootOffset() {
return rootOffset;
}
static final BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); // HACK II
public FontMetrics getFontMetrics(Font font) {
assert font!=null : "Null font.";
Graphics2D g2D = (Graphics2D) image.getGraphics();
ImageTools.setHints(g2D);
return g2D.getFontMetrics(font);
}
public ColourScheme getColourScheme() {
return null;
}
public static class Pair { // TODO: Rename to Tripple or something more specific.
public final Position position; // TODO: Rename to position
public final Argument argument;
public final Offset innerRectangle;
public final Box box;
public Pair(Box box, Position topLeft) {
this.position = box.getPosition(topLeft);
this.argument = box.argument;
this.innerRectangle = box.innerRectangle;
this.box = box; // Only keep this if you want custom images.
}
/*-
public Pair(Position position, Argument argument, Offset innerRectangle) {
this.position = position;
this.argument = argument;
this.innerRectangle = innerRectangle;
}*/
}
public Vector<Pair> getPairs(Position topLeft) { // TODO: Rename.
Vector<Pair> pairs = new Vector<Pair>();
//- Pair p = new Pair(getPosition(topLeft), argument, innerRectangle);
Pair p = new Pair(this, topLeft);
pairs.add(p);
return pairs;
}
//////////////////////
// ABSTRACT METHODS //
//////////////////////
/**
* A single infix separator may be cloned into as many instances as
* required.
*
* Conceptually, it should not be possible to clone boxes as they are
* immutable beyond adjusting their alignment (and event that has yet
* to be implemented or required).
*/
public abstract Box cloneBox();
/**
* All child classes must be able to determine a minimum size in order
* for the box it is within to organize all the boxes that it contains
* and allocate appropriate space for each.
*/
protected abstract void calcMinimumSize();
protected abstract void draw(Graphics2D g2D, Position topLeft, ColourScheme colourScheme);
public Position getCentre(Position topLeft) {
Position corner = getPosition(topLeft);
Offset innerCentre = getInnerRectangle().getCentre();
return corner.plus(innerCentre);
}
}