/*
* 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 ketUI.panel;
import geom.Offset;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.util.*;
import geom.Position;
import ket.*;
import ket.display.*;
import ket.display.Transition;
import ket.display.box.*;
import ket.math.*;
import ket.treeDiff.Step;
import ket.treeDiff.TreeDiff;
import ketUI.MouseEventHandler;
/**
* A perspective effect based on argument depth within the equation hierarchy.
*/
public class PerspectiveDisplay implements Display {
static final double ANIMATION_TIME = 250.0; // ms
static final int TOP = 0x1;
static final int CENTRE = 0x2;
static final int BOTTOM = 0x3;
final MathCollection mathCollection;
final MouseEventHandler mouseEventHandler;
/**
* When the equation is drawn, record its box from which the arguments
* associated with a mouse click may be determined.
*/
Box box;
Box labelBox;
Position topLeft;
boolean repaint;
int view;
TreeMap<Integer, Vector<Box.Band>> bands;
public PerspectiveDisplay(MathCollection mathCollection, MouseEventHandler mouseEventHandler) {
this.mathCollection = mathCollection;
this.mouseEventHandler = mouseEventHandler;
repaint = false;
view = CENTRE;
bands = null;
}
public void fromTop() {
view = TOP;
}
public void fromMiddle() {
view = CENTRE;
}
public void fromBottom() {
view = BOTTOM;
}
@Override
public Box findDeepestBox(Position p) {
if (bands==null) return null;
Box deepest = null;
for (Map.Entry<Integer,Vector<Box.Band>> entry : bands.entrySet()) {
for (Box.Band band : entry.getValue()) {
Position q = band.position;
Offset r = band.box.getInnerRectangle();
double dx = p.x - q.x;
double dy = p.y - q.y;
if (dx>0 && dy>0 && dx<r.width && dy<r.height) {
deepest = band.box;
}
}
}
return deepest;
}
@Override
public Argument findDeepestArgument(Position p) {
if (bands==null) return null;
Argument deepest = null;
for (Map.Entry<Integer,Vector<Box.Band>> entry : bands.entrySet()) {
for (Box.Band band : entry.getValue()) {
Position q = band.position;
Offset r = band.box.getInnerRectangle();
double dx = p.x - q.x;
double dy = p.y - q.y;
Argument a = band.box.getArgument();
if (a!=null && dx>0 && dy>0 && dx<r.width && dy<r.height) {
deepest = a;
}
}
}
return deepest;
}
@Override
public Vector<Integer> findVisibleEquationIndices() {
Vector<Integer> indices = new Vector<Integer>();
if (box==null) return indices;
Argument a = box.getArgument();
if (a==null) return indices;
Integer index = a.getEquationIndex();
if (index==null) return indices;
indices.add(index);
return indices;
}
@Override
public Equation pointToEquation(Position p) {
Argument argument = box.getArgument();
return argument!=null ? argument.getEquation() : null;
}
@Override
public void noteChange(Graphics2D g2D, ColourScheme colourScheme, Argument before, Equation afterEquation, int fontSize, Offset panelRectangle) {
// Do nothing.
}
/**
* This is called every time the user interacts with the box model, either changing or selecting.
*/
@Override
public boolean generateBoxes(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
//- bands = null;
bands = new TreeMap<Integer, Vector<Box.Band>>();
Equation equation = mathCollection.getCursor().getEquation();
box = equation.toBox(colourScheme);
labelBox = equation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
box.setupInnerRectangle(fontSize);
Offset equationRectangle = calcEquationRectangle(panelRectangle);
labelBox.setup(fontSize, equationRectangle);
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
box.setupOuterRectangle(windowWithoutLabel);
double boxHeight = getBoxHeight(box, panelRectangle);
topLeft = new Position(KetPanel.LEFT_BORDER_SIZE, boxHeight);
box.boxesByDepth(null, bands, topLeft, colourScheme);
return true;
}
/**
* Draw a single equation in the centre of the screen or text to the
* top left. In either case a label is also displayed.
*/
@Override
public void paint(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
if (bands==null || box==null) {
return;
}
labelBox.paint(g2D, topLeft, colourScheme);
Offset shift = getShift(panelRectangle);
double scale = 100.0;
for (Map.Entry<Integer,Vector<Box.Band>> entry : new Vector<Map.Entry<Integer,Vector<Box.Band>>>(bands.entrySet())) {
int depth = entry.getKey();
for (Box.Band band : entry.getValue()) {
double x = band.position.x;
double y = band.position.y;
x += shift.width * depth / scale;
y += shift.height * depth / scale;
AffineTransform t = AffineTransform.getTranslateInstance(x, y);
g2D.drawImage(band.image, t, null);
}
}
}
private Offset getShift(Offset panelRectangle) {
Position p = mouseEventHandler.getCurrentPosition();
if (p==null) {
return new Offset(0.0, 0.0);
} else {
Position oldPosition = new Position(
KetPanel.LEFT_BORDER_SIZE + panelRectangle.width/2.0,
KetPanel.TOP_BORDER_SIZE + panelRectangle.height/2.0);
return Offset.difference(p, oldPosition);
// return Offset.difference(oldPosition, p);
}
}
private Offset calcEquationRectangle(Offset panelRectangle) {
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
double minimumHeight = box.getInnerRectangle().height;
return new Offset(shapeWidth, minimumHeight);
}
private Offset calcWindowWithoutLabel(Offset equationRectangle) {
return new Offset(equationRectangle.width-labelBox.getInnerRectangle().width, equationRectangle.height);
}
@Override
public boolean requiresRepaint() {
return repaint;
}
private boolean animate(double fractionalTime) {
return true; // TODO: mouse moved?
}
private double getBoxHeight(Box box, Offset panelRectangle) {
switch (view) {
case TOP:
return KetPanel.TOP_BORDER_SIZE;
case CENTRE:
double centre = (panelRectangle.height-KetPanel.BORDER_OFFSET.height) / 2.0;
double heightAboveCentre = box.getOuterRectangle().height / 2.0;
return centre - heightAboveCentre;
case BOTTOM:
double bottom = panelRectangle.height - KetPanel.BORDER_OFFSET.height;
double heightAboveBottom = box.getOuterRectangle().height;
return bottom - heightAboveBottom;
default:
throw new RuntimeException("Illegal display state.");
}
}
@Override
public boolean isArgumentVisible(Argument argument) {
return box.containsArgument(argument);
}
}