/*
* 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 java.awt.image.*;
import java.util.Vector;
import java.util.*;
import java.awt.Graphics2D;
import geom.Position;
import geom.Offset;
import ket.*;
import ket.display.*;
import ket.display.Transition;
import ket.display.box.Box;
import ket.display.box.BoxText;
import ket.math.*;
import ket.treeDiff.Step;
import ket.treeDiff.TreeDiff;
import ketUI.Ket;
/**
* A list of equations may be displayed by this class which organizes and draws
* them relative to the top, centre or bottom of the screen.
*/
public class ListDisplay implements Display {
static final double ANIMATION_TIME = 250.0; // ms
final MathCollection mathCollection;
final Top top;
final Middle middle;
final Bottom bottom;
Align align;
Box afterRootBox;
Box equationBox;
Box labelBox;
Position equationTopLeft;
volatile Set<BoxEquation> boxSet;
Vector<Transition> transitions;
boolean animate;
boolean repaint;
int bottomBoxIndex;
int initialBoxIndex;
int middleBoxIndex;
long initialTime;
public ListDisplay(MathCollection mathCollection) {
this.mathCollection = mathCollection;
top = new Top(this);
middle = new Middle(this);
bottom = new Bottom(this);
align = top;
boxSet = null;
initialTime = 0L;
transitions = null;
repaint = true;
animate = false;
initialBoxIndex = 0;
middleBoxIndex = 0;
bottomBoxIndex = 0;
}
/**
* Find the equation that is closest to the given point on the
* ketPanel.
*/
@Override
public Equation pointToEquation(Position p) {
Equation e = null;
Double bestTop = null;
for (BoxEquation next : boxSet) {
Box box = next.getEquationBox();
if (box==null) continue;
if (box.getTopLeft()==null) continue;
double top = box.getTopLeft().y;
if (top<p.y && (bestTop==null || bestTop<top)) {
e = box.getArgument().getEquation();
Ket.out.println(e);
bestTop = top;
}
}
return e;
}
/*
* Look through boxSet and while a box contains the location within its
* inner rectangle, return that box.
*/
@Override
public Box findDeepestBox(Position p) {
for (BoxEquation next : boxSet) {
Box match = next.findDeepestBox(p);
if (match!=null) {
return match;
}
}
return null;
}
/*
* Look through boxSet and while a box contains the location within its
* inner rectangle, return that box.
*/
@Override
public Argument findDeepestArgument(Position p) {
for (BoxEquation next : boxSet) {
Argument match = next.findDeepestArgument(p);
if (match!=null) {
return match;
}
}
return null;
}
@Override
public Vector<Integer> findVisibleEquationIndices() {
Vector<Integer> indices = new Vector<Integer>();
if (boxSet==null) {
Ket.out.println("[findVisibleEquationIndices::null box-set]");
return indices;
}
for (BoxEquation next : boxSet) {
//?- if (animate) continue;
//? if (equationBox.getArgument()!=null && next.getEquation()==equationBox.getArgument().getEquation()) continue;
Box equationPart = next.getEquationBox();
if (equationPart==null) {
Ket.out.println("[pointToEquation::null box-set element]");
continue;
}
Argument argument = equationPart.getArgument();
if (argument==null) {
Ket.out.println("[pointToEquation::null box-set argument]");
continue;
}
Integer index = argument.getEquationIndex(); //NOTE: Integer, not int
if (index==null) {
Ket.out.println("[pointToEquation::null box-set index]");
continue;
}
indices.add(index);
}
return indices;
}
/**
* This is called every time the user interacts with the box model, either changing or selecting.
*/
@Override
public synchronized boolean generateBoxes(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
synchronized (mathCollection.getSelection()) {
boxSet = align.paint(g2D, colourScheme, fontSize, panelRectangle);
Equation equation = mathCollection.getCursor().getEquation(); //q
if (equation==null) { System.out.print(" #2187 "); return false; } // BUG?
equationBox = equation.toBox(colourScheme);
labelBox = equation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
equationBox.setupInnerRectangle(fontSize);
Offset equationRectangle = calcEquationRectangle(panelRectangle);
if (equationRectangle==null) { System.out.print(" #9862 "); return false; } // BUG? // <-- (1)
labelBox.setup(fontSize, equationRectangle);
if (equationBox.getInnerRectangle()==null) { System.out.print(" #0391 "); return false; } // BUG? // <-- (2)
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
if (windowWithoutLabel==null) { System.out.print(" #9104 "); return false; } // BUG?
equationBox.setupOuterRectangle(windowWithoutLabel);
equationBox.calcRootOffset();
}
return true;
}
@Override
public void paint(
Graphics2D g2D,
ColourScheme colourScheme,
int fontSize,
Offset panelRectangle) {
Position position = null; // <--- Locally repurpose (q) version.
if (boxSet!=null) {
for (BoxEquation boxEquation : boxSet) {
if (animate && boxEquation.getEquation()==equationBox.getArgument().getEquation()) {
position = boxEquation.getTopLeft();
} else {
boxEquation.paint(g2D, colourScheme);
}
}
}
if (equationBox!=null && position!=null) {
double fractionalTime = (System.currentTimeMillis()-initialTime) / ANIMATION_TIME;
if (transitions!=null && fractionalTime<1.0) {
repaint = true;
labelBox.paint(g2D, position, colourScheme);
// Draw the next frame.
for (Transition t : transitions) {
t.animate(g2D, colourScheme, fractionalTime, position);
}
} else {
repaint = false;
initialTime = 0L;
labelBox.paint(g2D, position, colourScheme);
equationBox.paint(g2D, position, colourScheme);
transitions = null;
}
}
}
public Align getAlign() {
return align;
}
public void fromTop(int initialBoxIndex) {
align = top;
this.initialBoxIndex = initialBoxIndex;
}
public void fromMiddle(int middleBoxIndex) {
align = middle;
this.middleBoxIndex = middleBoxIndex;
}
public void fromBottom(int bottomBoxIndex) {
align = bottom;
this.bottomBoxIndex = bottomBoxIndex;
}
public void moveViewUp() {
align.moveViewUp();
}
public void moveViewDown() {
align.moveViewDown();
}
@Override
public boolean isArgumentVisible(Argument argument) {
if (boxSet==null) {
System.out.println("[null box set]");
return false;
}
for (BoxEquation boxEquation : boxSet) {
Box box = boxEquation.getEquationBox();
if (box.containsArgument(argument)) {
return true;
}
}
return false;
}
/**
* Set the current argument from the logically significant index of
* visible equations.
*/
public void selectByVisible(Cursor cursor, EquationList equationList) {
int currentIndex = getLogicalIndex();
if (currentIndex<0) {
currentIndex = 0;
}
if (currentIndex>=equationList.size()) {
currentIndex = equationList.size() - 1;
}
Equation e = equationList.getEquations().get(currentIndex);
cursor.setCurrent(e.getVisibleRoot());
}
private int getLogicalIndex() {
if (align==top) {
return initialBoxIndex;
} else if (align==middle) {
return middleBoxIndex;
} else {
return bottomBoxIndex;
}
}
public void viewEquation(int index) {
if (align==top) {
this.initialBoxIndex = bound(index, 0, getNumberOfEquations());
} else if (align==middle) {
this.middleBoxIndex = bound(index, 0, getNumberOfEquations());
} else if (align==bottom) {
this.bottomBoxIndex = bound(index, 0, getNumberOfEquations());
}
}
@Override
public boolean requiresRepaint() {
return animate && repaint;
}
// --------------------
// --- SIFTING, 'V' ---
// --------------------
Vector<Equation> equations = null;
/**
* This provides a list of equations that are to be displayed (or all
* if equations is null).
*/
public void setVisibleEquations(Vector<Equation> equations) {
this.equations = equations;
}
public Vector<Equation> getVisibleEquations() {
return equations;
}
@Override
public void noteChange(Graphics2D g2D, ColourScheme colourScheme, Argument before, Equation afterEquation, int fontSize, Offset panelRectangle) {
Argument after = afterEquation.getRoot();
if ( !animate || !isSingleSelectionSet() || before.subBranchEquals(after) || afterEquation.isText()) { //+ Did the equation change?
transitions = null;
afterRootBox = null;
return;
}
afterRootBox = after.toBox(0L, colourScheme);
labelSetup(afterEquation, colourScheme);
Offset windowWithoutLabel = afterBoxSetup(fontSize, panelRectangle);
Box beforeRootBox = beforeBoxSetup(g2D, colourScheme, before, fontSize, windowWithoutLabel);
initialTime = System.currentTimeMillis();
TreeDiff treeDiff = new TreeDiff(before, after);
transitions = treeDiff.getTransitions(beforeRootBox, afterRootBox);
}
private void labelSetup(Equation afterEquation, ColourScheme colourScheme) {
labelBox = afterEquation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
labelBox.setProperty(Box.Y_CENTRE_ALIGN);
}
/**
* Setup the box that represents the previous state.
*/
private Box beforeBoxSetup(Graphics2D g2D, ColourScheme colourScheme, Argument before, int fontSize, Offset windowWithoutLabel) {
Box beforeRootBox = before.toBox(0L, colourScheme);
beforeRootBox.setupInnerRectangle(fontSize);
beforeRootBox.setupOuterRectangle(windowWithoutLabel);
beforeRootBox.calcRootOffset();
return beforeRootBox;
}
/**
* Animate the box that represents the new state.
*/
private Offset afterBoxSetup(int fontSize, Offset panelRectangle) {
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
afterRootBox.setupInnerRectangle(fontSize);
double minimumHeight = afterRootBox.getInnerRectangle().height;
Offset equationRectangle = new Offset(shapeWidth, minimumHeight);
labelBox.setup(fontSize, equationRectangle);
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
afterRootBox.setupOuterRectangle(windowWithoutLabel);
afterRootBox.calcRootOffset();
//- topLeft = new Position(KetPanel.LEFT_BORDER_SIZE, getBoxHeight(afterRootBox, panelRectangle));
return windowWithoutLabel;
}
private Offset calcEquationRectangle(Offset panelRectangle) {
if (equationBox==null) { System.out.print(" #0291 "); return null; }
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
if (equationBox.getInnerRectangle()==null) { System.out.print(" #4921 "); return null; } // <-- (1)
double minimumHeight = equationBox.getInnerRectangle().height; // BUG: innterRectangle can be null!
return new Offset(shapeWidth, minimumHeight);
}
private Offset calcWindowWithoutLabel(Offset equationRectangle) {
if ( equationRectangle==null ) { System.out.print(" #1342 "); return null; } // BUG?
if ( labelBox==null ) { System.out.print(" #4382 "); return null; } // BUG?
if ( labelBox.getInnerRectangle()==null ) { System.out.print(" #9889 "); return null; } // BUG?
return new Offset(equationRectangle.width-labelBox.getInnerRectangle().width, equationRectangle.height);
}
private double getBoxHeight(Box box, Offset panelRectangle) {
if (align==top) {
return KetPanel.TOP_BORDER_SIZE;
} else if (align==middle) {
double centre = (panelRectangle.height-KetPanel.BORDER_OFFSET.height) / 2.0;
double heightAboveCentre = box.getOuterRectangle().height / 2.0;
return centre - heightAboveCentre;
} else {
double bottom = panelRectangle.height - KetPanel.BORDER_OFFSET.height;
double heightAboveBottom = box.getOuterRectangle().height;
return bottom - heightAboveBottom;
}
}
/**
* Restrict the value of index within [min, max).
*/
private int bound(int index, int min, int max) {
if (index <= min) {
return min;
} else if (max <= index) {
return max;
} else {
return index;
}
}
private int getNumberOfEquations() {
Selection selection = mathCollection.getSelection();
return selection.getEquationList().size();
}
private Selection getSelection() {
return mathCollection.getSelection();
}
private boolean isSingleSelectionSet() {
return mathCollection.isSingleSelectionSet();
}
public int getInitialBoxIndex() {
return initialBoxIndex;
}
public int getMiddleBoxIndex() {
return middleBoxIndex;
}
public int getFinalBoxIndex() {
return bottomBoxIndex;
}
public MathCollection getMathCollection() {
return mathCollection;
}
}