Package ket.display.box

Source Code of ket.display.box.Box

/*
* 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);
  }
}
TOP

Related Classes of ket.display.box.Box

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.