Package ketUI.panel

Source Code of ketUI.panel.GridDisplay

/*
* 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 java.awt.Color;

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;

/**
* A treemap is fills a box with smaller boxes.  Usually this is nested, but
* this is currently only a subset of the current list.
*/
public class GridDisplay implements Display {
  Set<BoxEquation> boxSet;
  Vector<Equation> equations = null; //? Used?

  public static final double GAP = 10.0; // Horizontal gap

  static final double ANIMATION_TIME = 250.0; // ms
  static final boolean animate = false;

  final MathCollection mathCollection;

  /**
   * When the equation is drawn, record its box from which the arguments
   * associated with a mouse click may be determined.
   */
  Box afterRootBox;
  Box equationBox;
  Box labelBox;

  Position equationTopLeft;

  Vector<Transition> transitions;
  boolean repaint;
  long initialTime;
  int index;


  public GridDisplay(MathCollection mathCollection) {
    this.mathCollection = mathCollection;
    boxSet = null;
    initialTime = 0L;
    transitions = null;
    index = 0;
  }

  /**
   * Find the equation that is closest to the given point on the
   * ketPanel.
   */
  @Override
  public Equation pointToEquation(Position p) {
    // Find the closest equation to the mouse click where part of it is above and to the left of the click.
    Equation e = null;
    Double bestLength2 = null; // Skip unnecessary and expensive square roots.
    for (BoxEquation next : boxSet) {
      Box box = next.getEquationBox();
      if (box==null) continue; //HACK
      if (box.getTopLeft()==null) continue; // HACK
      double left   = box.getTopLeft().x;
      double top    = box.getTopLeft().y;
      double dx = p.x - left;
      double dy = p.y - top;
      if (dx>0 && dy>0) {
        double length2 = dx*dx + dy*dy;
        if (bestLength2==null || length2<bestLength2) {
          bestLength2 = length2;
          e = box.getArgument().getEquation();
        }
      }
    }
    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); // used
      if (match!=null) {
        return match;
      }
    }
    return null;
  }

  @Override
  public Vector<Integer> findVisibleEquationIndices() {
    Vector<Integer> indices = new Vector<Integer>();
    if (boxSet==null) 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) continue;
      Argument a = equationPart.getArgument();
      if (a==null) continue;
      int index = a.getEquationIndex();
      indices.add(index);
    }
    return indices;
  }

  /**
   * 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) {
    generateList(g2D, colourScheme, fontSize, panelRectangle);
    return generateAnimation(g2D, colourScheme, fontSize, panelRectangle);
  }

  private void generateList(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
    Vector<BoxEquation> source = getValidBoxEquations(g2D, fontSize, colourScheme);
    stuff(g2D, colourScheme, fontSize, panelRectangle, source);
    HashSet<BoxEquation> boxSet = new HashSet<BoxEquation>();
    for (BoxEquation boxEquation : source) {
      if (boxEquation!=null && boxEquation.isValid()) {
        boxSet.add(boxEquation);
      }
    }
    this.boxSet = boxSet;
  }

  private void stuff(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle, Vector<BoxEquation> source) {
    double maxWidth = panelRectangle.width-KetPanel.LEFT_BORDER_SIZE-KetPanel.RIGHT_BORDER_SIZE;
    double maxHeight = panelRectangle.height-KetPanel.TOP_BORDER_SIZE-KetPanel.BOTTOM_BORDER_SIZE;
    double width = 0.0;
    double height = 0.0;
    double widest = 0.0;
    int ithColumn=0;
    Vector<BoxEquation> column = new Vector<BoxEquation>();
    for (BoxEquation boxEquation : source) {
      if (maxHeight<height+boxEquation.getHeight()) {
        if (ithColumn>=index) {
          setupColumn(g2D, column, width, widest, true);
          width += GAP + widest;
          if (maxWidth < width) {
            // Boxes are wider than the window so there is nothing left to draw.
            return;
          }
        }
        ithColumn += 1;
        height = 0.0;
        column = new Vector<BoxEquation>();
        widest = 0.0;
      }
      column.add(boxEquation);
      widest = boxWrap(boxEquation, widest, maxWidth, g2D, true);
      height += boxEquation.getHeight();
    }
    if (ithColumn<index) {
      index = ithColumn;
    }
    if (!column.isEmpty()) { // There remains a few colun elements.
      if (width==0.0) { // Single column: Use the full width of the window.
        setupColumn(g2D, column, 0.0, maxWidth, false);
      } else {
        setupColumn(g2D, column, width, widest, true);
        width += GAP + widest;
      }
    }
    // Spread equations out evenly across the horizontal.
    if (width>0.0 && width<maxWidth) { // i.e. multiple lines with gaps.
      double scale = maxWidth/width; // final 'widest' has already been added.
      for (BoxEquation boxEquation : source) {
        if (boxEquation==null || !boxEquation.isValid()) continue;
        Position topLeft = boxEquation.getTopLeft();
        topLeft.x *= scale;
      }
    }
  }

  private double boxWrap(BoxEquation boxEquation, double widest, double maxWidth, Graphics2D g2D, boolean scale) {
    assert boxEquation!=null;
    Equation e = boxEquation.getEquation();
    assert e!=null;
    // Don't measure the width of text and instead have it wrap to multiple lines.
    if (!e.isText()) {
      return Math.max(widest, boxEquation.getFullWidth());
    } // otherwise:
    double wrappedWidth = scale ? Math.min(boxEquation.getWidth(), maxWidth/2.0) : maxWidth;
    Offset leftRectangle = new Offset(wrappedWidth, Double.MAX_VALUE);
    boxEquation.getEquationBox().setupOuterRectangle(leftRectangle);
    return Math.max(widest, boxEquation.getFullWidth());
  }

  private Vector<Equation> getValidEquations() {
    Vector<Equation> es = new Vector<Equation>();
    Selection selection = mathCollection.getSelection();
    for (int boxIndex=0; boxIndex<getNumberOfEquations(); boxIndex++) {
      Equation equation = selection.getEquationList().getEquation(boxIndex);
      boolean valid = true;
      if (equation==null) continue;
      if (equations!=null && !equation.isIn(equations)) continue;
      es.add(equation);
    }
    return es;
  }

  private Vector<BoxEquation> getValidBoxEquations(Graphics2D g2D, int fontSize, ColourScheme colourScheme) {
    Vector<BoxEquation> source = new Vector<BoxEquation>();
    for (Equation equation : getValidEquations()) {
      Box box = equation.toBox(colourScheme);
      box.setupInnerRectangle(fontSize);
      Box labelBox = equation.getLabelBox(colourScheme);
      labelBox.setupInnerRectangle(fontSize);
      BoxEquation boxEquation = new BoxEquation(box, labelBox);
      source.add(boxEquation);
    }
    return source;
  }

  private void setupColumn(Graphics2D g2D, Vector<BoxEquation> column, double width, double widest, boolean multiple) {
    double sum = 0.0;
    for (BoxEquation b : column) {
      assert b!=null; // HACK
      double step = b.getHeight();
      Box box = b.getEquationBox();
      Box labelBox = b.getLabel();
      if (b.getEquation().isText() ) {
        box.setProperties(Box.LEFT_ALIGN, Box.TOP_ALIGN, Box.TOP_ALIGN);
      } else if (multiple) { // TODO: Indent rather than centre?
        box.setProperties(Box.LEFT_ALIGN, Box.Y_CENTRE_ALIGN, Box.Y_CENTRE_ALIGN);
      } else {
        box.setProperties(Box.X_CENTRE_ALIGN, Box.Y_CENTRE_ALIGN, Box.Y_CENTRE_ALIGN);
      }
      labelBox.setProperties(Box.RIGHT_ALIGN, Box.SMALL_FONT, Box.BOLD_FONT);
      Offset equationRectangle = new Offset(widest, step);
      labelBox.setupOuterRectangle(equationRectangle);
      Offset leftRectangle = new Offset(equationRectangle);
      leftRectangle.width -= labelBox.getInnerRectangle().width;
      box.setupOuterRectangle(leftRectangle);
      double left = KetPanel.LEFT_BORDER_SIZE + width;
      double top = KetPanel.TOP_BORDER_SIZE + sum;
      Position topLeft = new Position(left, top);
      b.setTopLeft(topLeft);
      sum += step;
    }
  }

  private synchronized boolean generateAnimation(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
    Equation equation = mathCollection.getCursor().getEquation();
    if (equation==null) { System.out.println(" @134 "); return false; } // BUG: Why is this required?
    equationBox = equation.toBox(colourScheme);
    if (equationBox==null) { System.out.println(" @321 "); return false; }
    labelBox = equation.getLabelBox(colourScheme);
    labelBox.setProperty(Box.RIGHT_ALIGN);
    equationBox.setupInnerRectangle(fontSize);
    Offset equationRectangle = calcEquationRectangle(panelRectangle);
    if (equationRectangle==null) { System.out.println(" @498 "); return false; }
    labelBox.setup(fontSize, equationRectangle);
    Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
    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) {
      position = paintBoxes(g2D, colourScheme);
    }
    if (equationBox!=null && position!=null) {
      paintAnimation(g2D, colourScheme, position);
    }
  }

  private Position paintBoxes(Graphics2D g2D, ColourScheme colourScheme) {
    Position position = null;
    for (BoxEquation boxEquation : boxSet) {
      if (animate && boxEquation.getEquation()==equationBox.getArgument().getEquation()) {
        position = boxEquation.getTopLeft();
      } else {
        g2D.setColor(Color.BLACK); // <-- use colourscheme.
        Box eb = boxEquation.getEquationBox();
        boxEquation.paint(g2D, colourScheme);
      }
    }
    return position;
  }

  private void paintAnimation(Graphics2D g2D, ColourScheme colourScheme, Position position) {
    double fractionalTime = (System.currentTimeMillis()-initialTime) / ANIMATION_TIME;
    if (transitions!=null && fractionalTime<1.0) {
      repaint = true;
      labelBox.paint(g2D, position, colourScheme);
      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;
    }
  }

  @Override
  public boolean isArgumentVisible(Argument argument) {
    if (boxSet==null) 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() {
    return index;
  }

  public void viewEquation(int index) {
    this.index = bound(index, 0, getNumberOfEquations());
  }

  @Override
  public boolean requiresRepaint() {
    return animate && repaint;
  }

  /**
   * 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();
    return windowWithoutLabel;
  }

  private synchronized Offset calcEquationRectangle(Offset panelRectangle) {
    double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
    if (equationBox==null) { System.out.println(" @291 "); return null; }
    if (equationBox.getInnerRectangle()==null) { System.out.println(" @743 "); return null; }
    double minimumHeight = equationBox.getInnerRectangle().height;
    return new Offset(shapeWidth, minimumHeight);
  }

  private Offset calcWindowWithoutLabel(Offset equationRectangle) {
    return new Offset(equationRectangle.width-labelBox.getInnerRectangle().width, equationRectangle.height);
  }

  /*-
  private double getBoxHeight(Box box, Offset panelRectangle) {
    return KetPanel.TOP_BORDER_SIZE;
  }
  */

  public void moveViewUp() {
    //- setInitialBoxIndex(getInitialBoxIndex()-1);
    this.index = bound(index-1, 0, getNumberOfEquations());
  }

  public void moveViewDown() {
    //- setInitialBoxIndex(getInitialBoxIndex()+1);
    this.index = bound(index+1, 0, getNumberOfEquations());

  }

  private int getNumberOfEquations() {
    Selection selection = mathCollection.getSelection();
    return selection.getEquationList().size();
  }

  /**
   * 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 boolean isSingleSelectionSet() {
    return mathCollection.isSingleSelectionSet();
  }
}
TOP

Related Classes of ketUI.panel.GridDisplay

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.