/*
* 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 geom.Offset;
import geom.Position;
import java.awt.*;
import java.util.*;
import ket.display.ColourScheme;
import ket.math.*;
import ketUI.Ket;
/*
* Boxes can be organized within other boxes. This class allows such
* hierarchical constructions by providing a parent class in which other Boxes
* can be added and aligned.
*/
public class BoxList extends Box {
Vector<Box> children;
Vector<Vector<Box>> xAxis;
Vector<Vector<Box>> yAxis;
@Override
public Box cloneBox() {
BoxList clone = new BoxList(getArgument(), getSettings());
IdentityHashMap<Box, Box> map = new IdentityHashMap<Box, Box>();
for (Box child : children) {
Box childClone = child.cloneBox();
map.put(child, childClone);
}
for (Vector<Box> path : xAxis) {
Vector<Box> pathClone = new Vector<Box>();
for (Box step : path) {
pathClone.add(map.get(step));
}
clone.addHorizontalPath(pathClone);
}
for (Vector<Box> path : yAxis) {
Vector<Box> pathClone = new Vector<Box>();
for (Box step : path) {
pathClone.add(map.get(step));
}
clone.addVerticalPath(pathClone);
}
return clone;
}
public BoxList(Argument argument, long settings) {
super(argument, settings);
children = new Vector<Box>();
xAxis = new Vector<Vector<Box>>();
yAxis = new Vector<Vector<Box>>();
}
public Vector<Box> getChildren() {
return new Vector<Box>(children);
}
public void addHorizontalPath(Vector<Box> path) {
xAxis.add(path);
for (Box child : path) {
assert child!=null : "Can't add a null child.";
if ( ! children.contains(child) ) {
children.add(child);
}
}
}
public void addVerticalPath(Vector<Box> path) {
yAxis.add(path);
for (Box child : path) {
assert child!=null : "Can't add a null child.";
if ( ! children.contains(child) ) {
children.add(child);
}
}
}
@Override
protected void fontSetup(int parentFontSize) {
// Insure that fontSize has been appropriately set.
super.fontSetup(parentFontSize);
for (Box child : children) {
child.fontSetup(fontSize);
}
}
/**
* Determine the smallest size of this component from the largest
* horizontal and vertical paths through the bounds of its child
* components. In order to do so, the minimal sizes of the child
* components is also evaluated. The innerRectangle component
* shouldn't change after this.
*/
@Override
protected void calcMinimumSize() {
for (Box box : children) {
box.calcMinimumSize();
}
setupActualChildBounds();
alignChildren();
}
/**
* Determine the actual sizes of child components.
*/
private void setupActualChildBounds() {
for (Vector<Box> path : yAxis) {
double width = 0.0;
for (Box child : path) {
width = Math.max(child.innerRectangle.width, width);
}
for (Box child : path) {
child.setActualWidth(width);
}
}
for (Vector<Box> path : xAxis) {
double height = 0.0;
for (Box child : path) {
height = Math.max(child.innerRectangle.height, height);
}
for (Box child : path) {
child.setActualHeight(height);
}
}
}
private void alignChildren() {
for (Box child : children) {
child.setupOuterRectangle(child.outerRectangle);
child.parentalShift = new Offset(0.0, 0.0);
}
innerRectangle = new Offset(0, 0);
for (Vector<Box> path : yAxis) {
double x = 0;
for (Box child : path) {
x = Math.max(x, child.outerRectangle.width);
child.parentalShift.width = innerRectangle.width;
}
innerRectangle.width += x;
}
for (Vector<Box> path : xAxis) {
double y = 0;
for (Box child : path) {
y = Math.max(y, child.outerRectangle.height);
child.parentalShift.height = innerRectangle.height;
}
innerRectangle.height += y;
}
}
/**
* Recursively set the current offset relative to that of its parent.
*/
@Override
protected void calcRootOffset(Offset parentOffset) {
super.calcRootOffset(parentOffset);
for (Box child: children) {
child.calcRootOffset(getRootOffset());
}
}
protected boolean isBand(Box child) {
return child.getArgument()==this.getArgument() || child.getArgument()==null;
}
@Override
public void addAllDescendants(Vector<Box> descendents) {
descendents.add(this);
for (Box child: children) {
child.addAllDescendants(descendents);
}
}
@Override
public void drawBand(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
ColourScheme localCS = getLocalColourScheme(colourScheme);
for (Box child: children) {
if (isBand(child)) {
child.paint(g2D, getPosition(topLeft), localCS);
}
}
}
@Override
public void draw(Graphics2D g2D, Position topLeft, ColourScheme colourScheme) {
ColourScheme localCS = getLocalColourScheme(colourScheme);
//- drawCross(topLeft, g2D);
for (Box child: children) {
child.paint(g2D, getPosition(topLeft), localCS);
}
}
/*-
public void drawCross(Position topLeft, Graphics2D g2D) {
int x = (int) getXPosition(topLeft);
int y = (int) getYPosition(topLeft);
g2D.drawLine(x, y, x+(int) innerRectangle.width, y+(int) innerRectangle.height);
g2D.drawLine(x+(int) innerRectangle.width, y, x, y+(int) innerRectangle.height);
}
*/
public Band boxesByDepth(Argument a, TreeMap<Integer, Vector<Band>> map, Position topLeft, ColourScheme colourScheme) {
Band band = super.boxesByDepth(a, map, topLeft, colourScheme);
ColourScheme localCS = getLocalColourScheme(colourScheme);
Argument local = getArgument();
if (local==null) {
local = a;
}
Position childTopLeft = getPosition(topLeft);
for (Box child : children) {
if (!isBand(child)) {
Band b = child.boxesByDepth(local, map, childTopLeft, localCS);
}
}
return band;
}
////////////////////
// HELPER METHODS //
////////////////////
public void nextHorizontalPath(Vector<Box> path) {
Vector<Box> pathVector = new Vector<Box>(path);
this.addHorizontalPath(pathVector);
}
public void nextHorizontalPath(Box[] path) {
Vector<Box> pathVector = new Vector<Box>(Arrays.asList(path));
this.addHorizontalPath(pathVector);
}
public void nextVerticalPath(Vector<Box> path) {
Vector<Box> pathVector = new Vector<Box>(path);
this.addVerticalPath(pathVector);
}
public void nextVerticalPath(Box[] path) {
Vector<Box> pathVector = new Vector<Box>(Arrays.asList(path));
this.addVerticalPath(pathVector);
}
public String toString() {
String string = "\nBoxList:\n";
string += "\tchildren("+children.size()+"):\n";
if (children!=null && children.size()>0) {
for (Box child : children) {
string += "\t\t" + child.toString() + "\n";
}
} else {
string += "[null children]";
}
string += "x axis:\n";
if (xAxis!=null) {
for (Vector<Box> path : xAxis) {
string += "\tPath:\n";
for (Box child : path) {
string += "\t\t" + child.toString() + "\n";
}
}
} else {
Ket.out.println("[null x-axis]");
}
string += "y axis:\n";
if (yAxis!=null) {
for (Vector<Box> path : yAxis) {
string += "\tPath:\n";
for (Box child : path) {
string += "\t\t" + child.toString() + "\n";
}
}
} else {
Ket.out.println("[null y-axis]");
}
string += "box attributes:\n";
string += super.toString();
string += "\n";
return string;
}
public Vector<Box> getRecursiveBoxVector() {
Vector<Box> boxVector = new Vector<Box>();
boxVector.add(this);
appendChildrenToBoxVector(boxVector);
return boxVector;
}
private void appendChildrenToBoxVector(Vector<Box> boxVector) {
for (Box box : children) {
boxVector.add(box);
if (box instanceof BoxList) {
BoxList boxList = (BoxList) box;
boxList.appendChildrenToBoxVector(boxVector);
}
}
}
@Override
public double getNetArea() {
double area = getArea();
for (Box child : children) {
area -= child.getArea();
}
return area;
}
@Override
public Argument findDeepestArgument(Position p) {
if (!this.withinInnerRectangle(p)) return null;
for (Box child : children) {
Argument childArgument = child.findDeepestArgument(p);
if (childArgument!=null) {
return childArgument;
}
}
return this.getArgument();
}
@Override
public Box findDeepestBox(Position p) {
if (!this.withinInnerRectangle(p)) return null;
for (Box child : children) {
Box box = child.findDeepestBox(p);
if (box==null) continue;
Argument childArgument = box.getArgument();
if (childArgument==null) continue;
if (getArgument()==childArgument) continue; // <--- Deeper boxes with the same argument are considered part of this one.
return box;
}
return this;
}
@Override
protected void addAllArguments(Vector<Argument> args) {
if (getArgument()!=null) {
args.add(getArgument());
}
for (Box child : children) {
child.addAllArguments(args);
}
}
/**
* Find the box that contains the given argument.
*/
@Override
public Box findBoxByArgument(Argument target) {
if (target==getArgument()) {
return this;
}
for (Box child : children) {
Box match = child.findBoxByArgument(target);
if (match!=null) {
return match;
}
}
return null;
}
/**
* Step through the current box's argument and its descendants to find
* the given argument.
*/
@Override
public boolean containsArgument(Argument argument) {
if (getArgument()==argument) {
return true;
}
for (Box child : children) {
boolean visible = child.containsArgument(argument);
if (visible) {
return true;
}
}
return false;
}
@Override
public void setBackground(boolean background) {
this.background = background;
for (Box box : children) {
box.setBackground(background);
}
}
boolean wrap = false;
public void setWrap() {
wrap = true;
}
private int sign(double x) {
if (x>0) {
return +1;
} else if (x==0) {
return 0;
} else {
return -1;
}
}
/*
// Comparator methods:
@Override
public int compare(Box a, Box b) { // wrong: if the top or bottom of a box is in the range of a box, and to the left of it then it.
double heightA = a.getParentalShift().height + a.getAlignmentShift().height;
double heightB = b.getParentalShift().height + b.getAlignmentShift().height;
double dh = heightA - heightB;
if (dh != 0) {
return sign(dh);
}
double widthA = a.getParentalShift().width + a.getAlignmentShift().width;
double widthB = b.getParentalShift().width + b.getAlignmentShift().width;
return sign(heightA - heightB);
}
@Override
public boolean equals(Object obj) {
return false;
}
*/
@Override
public void setupOuterRectangle(Offset actualSize) {
// Find where everything would be.
super.setupOuterRectangle(actualSize);
if (!wrap) return;
for (int i=0; i<children.size(); i++) {
Box box = children.get(i);
double width = box.getParentalShift().width + box.getAlignmentShift().width;
double height = box.getParentalShift().height + box.getAlignmentShift().height;
double rightWidth = width + box.getInnerRectangle().width;
if (rightWidth < actualSize.width) continue;
double widthShift = width;
double heightShift = box.getInnerRectangle().height; // The maximum height of this and all boxes to its left.
// Move the whole mass down by heightShift and to the left by widthShift
for (int j=i; j<children.size(); j++) { // only move down?
Box child = children.get(j);
child.getAlignmentShift().width -= widthShift;
child.getAlignmentShift().height += heightShift;
}
}
// The actual outer box size is to the bottom right point on the last child box.
Box last = children.lastElement();
double horizontalOffset = last.getParentalShift().width + last.getAlignmentShift().width + last.getInnerRectangle().width;
double verticalOffset = last.getParentalShift().height + last.getAlignmentShift().height + last.getInnerRectangle().height;
this.outerRectangle = new Offset(horizontalOffset, verticalOffset);
}
/**
* Recursively remove all arguments.
*/
@Override
public void clearArgument() {
super.clearArgument();
for (Box box : children) {
box.clearArgument();
}
}
@Override
public Vector<Pair> getPairs(Position topLeft) {
Vector<Pair> pairs = super.getPairs(topLeft);
Position recursiveTopLeft = getPosition(topLeft);
for (Box b : children) {
pairs.addAll(b.getPairs(recursiveTopLeft));
}
return pairs;
}
}