/*
* 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.Color;
import java.awt.Graphics2D;
import java.util.*;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;
import java.awt.Image;
import java.awt.AlphaComposite;
import geom.Position;
import ket.Cursor; // TODO: Check that this is the correct package location.
import ket.display.ColourScheme;
import ket.math.Equation;
import ketUI.Document;
/**
* Display information analogous to that of a scrollbar summarizing the visible
* and selected equations within the entire list. This is displayed in the
* bottom left corner in a minimalistic style of coloured icons.
*/
public class ScrollBar {
private static final int TEXT = -1;
private static final int BLANK = -2;
private static final int ALPHA = 85;
private static final int MAX_FIGS = 30; // ish
private static final int ICON_WIDTH = 50; // ish
private static final int ICON_HEIGHT = 40;
final Document document;
final Vector<Integer> selected;
final Vector<Integer> args;
final Vector<Integer> invisibleArgs;
final Vector<Integer> invisibleText;
final Vector<Integer> text;
final Vector<Integer> sizes; // The recursiveSize() of every equation.
final Vector<BufferedImage> figs;
int n;
public ScrollBar(Document document) {
this.document = document;
selected = new Vector<Integer>();
args = new Vector<Integer>();
invisibleArgs = new Vector<Integer>();
invisibleText = new Vector<Integer>();
text = new Vector<Integer>();
sizes = new Vector<Integer>();
figs = new Vector<BufferedImage>();
n = 0;
}
private ColourScheme getColourScheme() {
return document.getColourScheme();
}
private Vector<Equation> getSelectedEquations() {
return document.getSelection().getSelectedEquations();
}
private Vector<Equation> getEquations() {
return document.getCursor().getEquationList().getEquations();
}
private Vector<Integer> findVisibleEquationIndices() {
return document.getKetPanel().findVisibleEquationIndices();
}
private void update() { // text? {visible?} {selected?} -> selected args invisibleArgs invisibleText text
selected.clear();
invisibleArgs.clear();
invisibleText.clear();
args.clear();
text.clear();
sizes.clear();
Vector<Equation> equations = getEquations();
figs.clear();
ColourScheme colourScheme = getColourScheme();
n = equations.size();
Vector<Integer> visible = findVisibleEquationIndices();
for (int i=0; i<n; i++) {
Equation e = equations.get(i);
if (e.isText()) {
if (e.isBlankLine()) {
sizes.add(BLANK);
} else {
invisibleText.add(i);
text.add(i);
sizes.add(TEXT);
}
figs.add(null);
} else {
args.add(i);
invisibleArgs.add(i);
sizes.add(e.getVisibleRoot().recursiveSize());
// TODO: Expensive: Only evaluate for small numbers of equations.
//> if (equations.size()<MAX_DRAW_SIZE) {
//- figs.add(e.toBox(colourScheme).toBufferedImage(width, height, colourScheme));
figs.add(e.toBox(colourScheme).toBufferedImage(ICON_WIDTH, ICON_HEIGHT, colourScheme, false));
//> }
}
}
invisibleArgs.removeAll(visible);
invisibleText.removeAll(visible);
args.retainAll(visible);
text.retainAll(visible);
for (Equation e : getSelectedEquations()) {
if (e==null) continue; //?
int v = e.getEquationIndex();
selected.add(v);
}
invisibleArgs.removeAll(selected);
invisibleText.removeAll(selected);
args.removeAll(selected);
text.removeAll(selected);
}
public void paint(Graphics2D g2D) {
update();
Color argColour = getColourScheme().getArgColour(0);
Color hightlightColour = getColourScheme().getHighlightedColour(0);
Color plainTextColour = getColourScheme().getPlainTextColour();
Color faintArgColour = faint(argColour);
Color faintTextColour = faint(plainTextColour);
boolean big = figs.size()>MAX_FIGS;
haze(g2D, faintTextColour, invisibleText, true, big);
haze(g2D, plainTextColour, text, true, big);
haze(g2D, hightlightColour, selected, false, big);
if (big) {
haze(g2D, faintArgColour, invisibleArgs, true, big); // errors? Blame ListDisplay.findVisibleEquationIndices().
haze(g2D, argColour, args, true, big);
} else {
hazeFigs(g2D);
}
}
private Color faint(Color colour) {
int red = colour.getRed();
int green = colour.getGreen();
int blue = colour.getBlue();
return new Color(red, green, blue, ALPHA);
}
private float getFraction() {
switch (n) {
case 0: return 0.0f;
case 1: return 1.0f/32.0f;
case 2: return 1.0f/16.0f;
case 3: return 1.0f/12.0f;
case 4: return 1.0f/8.0f;
case 5: return 1.0f/6.0f;
case 6: return 1.0f/4.5f;
default: return 1.0f/3.0f;
}
}
private int maxSize() {
int max = 1;
for (int i : sizes) {
if (max<i) {
max = i;
}
}
return max;
}
private int getHeight() {
return document.getKetPanel().getHeight();
}
private float getFractionalHeight() {
int gap = KetPanel.BOTTOM_BORDER_SIZE-KetPanel.TOP_BORDER_SIZE;
return (getHeight()-gap) / (float) n;
}
private float getYStep() {
return getFraction()*getFractionalHeight();
}
private float getY0() {
return (1.0f-getFraction())*n*getFractionalHeight();
}
private int getY(int i) {
return (int) (i*getYStep() + getY0());
}
private boolean getText(int size, boolean sparse) {
if (size==TEXT) {
return true;
} else if (size==BLANK) {
return false;
} else if ( ! sparse ) {
return false;
} else {
return false;
}
}
private boolean getBlank(int size, boolean sparse) {
if (size==TEXT) {
return false;
} else if (size==BLANK) {
return true;
} else if ( ! sparse ) {
return false;
} else {
return false;
}
}
private int boundSize(int size, double top, boolean sparse) {
if (size==TEXT) {
return (int) top;
} else if (size==BLANK) {
return (int) top;
} else if ( ! sparse ) {
return (int) top;
} else {
return Math.max(size, 2); // size;
}
}
private void hazeFigs(Graphics2D g2D) { // DRY: Evaluate variables repeated in this and the next method as separate methods.
boolean sparse = false;
int stride = (int) Math.ceil(n/85.0);
double top = Math.max(maxSize(), 4.0)+1;
for (int i=0; i<figs.size(); i++) {
BufferedImage figure = figs.get(i);
if (figure==null) continue;
// x-dx .. x
// y .. y+u
int x = (int) (0.8f*KetPanel.LEFT_BORDER_SIZE);
int y = getY(i);
int size = sizes.get(i);
double stretch = Math.log10(boundSize(size, top, sparse)+1) / Math.log10(top);
int dx = (int) (0.7*KetPanel.LEFT_BORDER_SIZE * stretch);
int dy = (int) (2.5f*KetPanel.TOP_BORDER_SIZE + 0.5f*getFraction());
int u = (int) (2.0*Math.min(dx,dy)/5.0);
Image scaled = figure.getScaledInstance(dx, dy, Image.SCALE_SMOOTH);
boolean fade = invisibleArgs.contains(i);
if (fade) {
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ALPHA/256.0f);
g2D.setComposite(ac);
}
g2D.drawImage(scaled, x-dx, y, null);
if (fade) {
//-? g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f);
g2D.setComposite(ac);
}
}
}
private void haze(Graphics2D g2D, Color colour, Vector<Integer> indices, boolean sparse, boolean big) {
int stride = (int) Math.ceil(n/85.0);
double top = Math.max(maxSize(), 4.0)+1;
for (int i : indices) {
if (sparse && i%stride!=0) continue;
g2D.setColor(colour);
int x = (int) (0.8f*KetPanel.LEFT_BORDER_SIZE);
int y = getY(i);
if (i>=sizes.size()) continue; // BUG!
int size = sizes.get(i);
double stretch = Math.log10(boundSize(size, top, sparse)+1) / Math.log10(top);
int dx = (int) (0.7*KetPanel.LEFT_BORDER_SIZE * stretch);
int dy = (int) (2.5f*KetPanel.TOP_BORDER_SIZE + 0.5f*getFraction());
int u = (int) (2.0*Math.min(dx,dy)/5.0);
if (getText(size, sparse)) {
g2D.fillRect(x-dx, y, dx/2, u/4);
g2D.fillRect(x+(-dx)+dx/4, y, dx/6, (15*u)/8);
} else if (big) {
int[] xs = new int[]{x, x-dx, x-u, x, x};
int[] ys = new int[]{y, y, y+u, y+u, y};
if (getBlank(size, sparse)) {
g2D.drawPolygon(xs, ys, 4);
} else if (big) {
g2D.fillPolygon(xs, ys, 4);
}
}
}
}
public Equation pointToEquation(Position p) {
if (KetPanel.LEFT_BORDER_SIZE < p.x) return null;
float y0 = getY0();
float yStep = getYStep();
int i = (int) Math.round(( p.y - y0 - yStep/2 ) / yStep);
if (i==-1) i=0; // add a little margin for error.
if (i<0) return null;
Vector<Equation> equations = getEquations();
if (equations.isEmpty()) return null;
if (i>=n) return equations.lastElement();
return i<equations.size() ? equations.get(i) : null;
}
}