package org.basex.gui.layout;
import static org.basex.gui.GUIConstants.*;
import static org.basex.gui.layout.BaseXKeys.*;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
/**
* DoubleSlider implementation.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class BaseXDSlider extends BaseXPanel {
/** Slider width. */
private static final int ARROW = 17;
/** Label space. */
public static final int LABELW = 300;
/** Minimum slider value. */
public final double totMin;
/** Maximum slider value. */
public final double totMax;
/** Current slider value. */
public double min;
/** Current slider value. */
public double max;
/** Integer flag. */
public boolean itr;
/** Listener. */
private final ActionListener listener;
/** Cached slider value. */
private double oldMin;
/** Cached slider value. */
private double oldMax;
/** Mouse position for dragging operations. */
private int mouX;
/** Left button flag. */
private boolean left;
/** Right button flag. */
private boolean right;
/** Right button flag. */
private boolean center;
/** Logarithmic scale. */
private final boolean log;
/**
* Constructor.
* @param main reference to the main window
* @param mn min value
* @param mx max value
* @param list listener
*/
public BaseXDSlider(final Window main, final double mn, final double mx,
final ActionListener list) {
super(main);
listener = list;
totMin = mn;
totMax = mx;
min = mn;
max = mx;
// choose logarithmic scaling for larger ranges
log = Math.log(totMax) - Math.log(totMin) > 5 && totMax - totMin > 100;
mode(Fill.NONE).setFocusable(true);
BaseXLayout.setHeight(this, getFont().getSize() + 9);
BaseXLayout.setWidth(this, 200 + LABELW);
addFocusListener(new FocusAdapter() {
@Override
public void focusGained(final FocusEvent e) {
repaint();
}
@Override
public void focusLost(final FocusEvent e) {
repaint();
}
});
addKeyListener(this);
addMouseListener(this);
addMouseMotionListener(this);
setToolTip();
}
@Override
public void mouseMoved(final MouseEvent e) {
mouX = e.getX();
final Range r = new Range(this);
left = mouX >= r.xs && mouX <= r.xs + ARROW;
right = mouX >= r.xe && mouX <= r.xe + ARROW;
center = mouX + ARROW > r.xs && mouX < r.xe;
oldMin = encode(min);
oldMax = encode(max);
}
@Override
public void mousePressed(final MouseEvent e) {
mouseMoved(e);
}
@Override
public void mouseDragged(final MouseEvent e) {
if(!left && !right && !center) return;
final Range r = new Range(this);
final double prop = r.dist * (mouX - e.getX()) / r.w;
if(left) {
min = limit(totMin, max, decode(oldMin - prop) - 1);
} else if(right) {
max = limit(min, totMax, decode(oldMax - prop) - 1);
} else {
min = limit(totMin, totMax, decode(oldMin - prop) - 1);
max = limit(totMin, totMax, decode(oldMax - prop) - 1);
}
if(itr) {
min = (long) min;
max = (long) max;
}
listener.actionPerformed(null);
setToolTip();
repaint();
}
/**
* Sets a new tooltip.
*/
private void setToolTip() {
final double mn = (long) (min * 100) / 100.0;
final double mx = (long) (max * 100) / 100.0;
setToolTipText(BaseXLayout.value(mn) + " - " + BaseXLayout.value(mx));
}
@Override
public void mouseReleased(final MouseEvent e) {
left = false;
right = false;
center = false;
}
@Override
public void keyPressed(final KeyEvent e) {
oldMin = min;
oldMax = min;
double diffMin = 0;
double diffMax = 0;
if(PREV.is(e)) {
diffMin = -1;
diffMax = -1;
} else if(NEXT.is(e)) {
diffMin = 1;
diffMax = 1;
} else if(PREVLINE.is(e)) {
diffMin = -1;
diffMax = 1;
} else if(NEXTLINE.is(e)) {
diffMin = 1;
diffMax = -1;
} else if(LINESTART.is(e)) {
min = totMin;
} else if(LINEEND.is(e)) {
max = totMax;
}
if(e.isShiftDown()) {
diffMin /= 10;
diffMax /= 10;
}
final double dist = encode(totMax) - encode(totMin);
diffMin = dist / 20 * diffMin;
diffMax = dist / 20 * diffMax;
if(diffMin != 0) {
min = limit(totMin, max, decode(Math.max(0, encode(min) + diffMin)));
}
if(diffMax != 0) {
max = limit(min, totMax, decode(Math.max(0, encode(max) + diffMax)));
}
if(min != oldMin || max != oldMax) {
if(itr) {
if(min != oldMin) min = min > oldMin ? Math.max(oldMin + 1,
(long) min) : Math.min(oldMin - 1, (long) min);
if(max != oldMax) max = max > oldMax ? Math.max(oldMax + 1,
(long) max) : Math.min(oldMax - 1, (long) max);
}
listener.actionPerformed(null);
repaint();
}
}
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
final int w = getWidth() - LABELW;
final int h = getHeight();
final int hh = h / 2;
final boolean focus = hasFocus();
g.setColor(focus ? Color.white : WHITE);
g.fillRect(0, hh - 4, w, 8);
g.setColor(Color.black);
g.drawLine(0, hh - 4, w - 1, hh - 4);
g.drawLine(0, hh - 4, 0, hh + 4);
g.setColor(color2);
g.drawLine(w - 1, hh - 4, w - 1, hh + 4);
g.drawLine(0, hh + 4, w, hh + 4);
final Range r = new Range(this);
BaseXLayout.drawCell(g, r.xs, r.xe + ARROW, 2, h - 2, false);
if(r.xs + ARROW < r.xe) {
g.setColor(color4);
g.drawLine(r.xs + ARROW, 3, r.xs + ARROW, h - 4);
g.drawLine(r.xe - 1, 3, r.xe - 1, h - 4);
g.setColor(Color.white);
if(r.xs + ARROW + 2 < r.xe) {
g.drawLine(r.xs + ARROW + 1, 4, r.xs + ARROW + 1, h - 5);
g.drawLine(r.xe, 4, r.xe, h - 5);
}
g.drawLine(r.xs + ARROW - 1, 4, r.xs + ARROW - 1, h - 5);
g.drawLine(r.xe - 2, 4, r.xe - 2, h - 5);
}
// draw arrows
final Polygon pol = new Polygon(
new int[] { r.xs + 11, r.xs + 5, r.xs + 5, r.xs + 11 },
new int[] { hh - 5, hh - 1, hh, hh + 5 }, 4);
g.setColor(focus ? color4 : GRAY);
g.fillPolygon(pol);
pol.xpoints = new int[] { r.xe + 5, r.xe + 12, r.xe + 12, r.xe + 5 };
g.fillPolygon(pol);
g.setColor(focus ? Color.black : DGRAY);
g.drawLine(r.xs + 11, hh - 5, r.xs + 11, hh + 4);
g.drawLine(r.xs + 11, hh - 5, r.xs + 6, hh - 1);
g.drawLine(r.xe + 5, hh - 5, r.xe + 5, hh + 4);
g.drawLine(r.xe + 5, hh - 5, r.xe + 11, hh - 1);
g.setColor(Color.white);
g.drawLine(r.xs + 10, hh + 4, r.xs + 6, hh + 1);
g.drawLine(r.xe + 6, hh + 4, r.xe + 11, hh + 1);
// draw range info
g.setColor(Color.black);
final double mn = (long) (min * 100) / 100.0;
final double mx = (long) (max * 100) / 100.0;
g.drawString(BaseXLayout.value(mn) + " - " + BaseXLayout.value(mx),
w + 15, h - (h - getFont().getSize()) / 2);
}
/**
* Encodes the specified value.
* @param v value to be normalized
* @return new value
*/
double encode(final double v) {
return log ? Math.log(v + 1) : v;
}
/**
* Decodes the specified value.
* @param v value to be normalized
* @return new value
*/
private double decode(final double v) {
return log ? Math.exp(v) - 1 : v;
}
/**
* Returns a double in the specified minimum and maximum range.
* @param mn minimum value
* @param mx maximum value
* @param val value
* @return new value
*/
private static double limit(final double mn, final double mx,
final double val) {
return Math.max(mn, Math.min(mx, val));
}
/** Range class. */
private static class Range {
/** Range distance. */
final double dist;
/** Start position. */
final int xs;
/** End position. */
final int xe;
/** Slider width. */
final int w;
/**
* Constructor.
* @param s slider reference
*/
Range(final BaseXDSlider s) {
w = s.getWidth() - LABELW - ARROW * 2;
dist = s.encode(s.totMax - s.totMin);
xs = (int) (s.encode(s.min - s.totMin) * w / dist);
xe = (s.totMin == s.totMax ? w :
(int) (s.encode(s.max - s.totMin) * w / dist)) + ARROW;
}
}
}