/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package de.cismet.commons.gui.equalizer;
import org.openide.util.WeakListeners;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D.Double;
import java.awt.geom.Rectangle2D;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Comparator;
import java.util.TreeMap;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import javax.swing.plaf.SliderUI;
import javax.swing.plaf.basic.BasicSliderUI;
/**
* This component is a view to an {@link EqualizerModel}. It displays the different categories of the model using
* sliders which can be used to adjust the value of the respective category. Optionally, the range of the model can be
* visualised right next to the slider group and a spline graph can be drawn to connect all the slider knobs of the
* categories to visually indicate their togetherness.
*
* @author martin.scholl@cismet.de
* @version 1.0
*/
public class EqualizerPanel extends javax.swing.JPanel {
//~ Static fields/initializers ---------------------------------------------
/** The default paint used for the spline, a pastel greenish tone. */
public static final Paint DEFAULT_PAINT;
/** The default stroke used for the spline, a dashed line, 3 pixels wide. */
public static final Stroke DEFAULT_STROKE;
private static final String PROP_MODEL_INDEX;
static {
// cannot use single integer because java only supports 31-bit integers (32nd bit is for sign indication)
DEFAULT_PAINT = new Color(Integer.decode("0x17"), // NOI18N
Integer.decode("0xA8"), // NOI18N
Integer.decode("0x14"), // NOI18N
Integer.decode("0xDD")); // NOI18N
DEFAULT_STROKE = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] { 7, 3 }, 0);
PROP_MODEL_INDEX = "__prop_model_index__"; // NOI18N
}
//~ Instance fields --------------------------------------------------------
private final EqualizerModelListener equalizerModelL;
private final ChangeListener sliderChangeL;
private EqualizerModel model;
private TreeMap<Integer, JSlider> sliderMap;
private boolean updateInProgress;
private Paint splinePaint;
private Stroke splineStroke;
private boolean rangeAxisPainted;
private boolean splinePainted;
private boolean updateSplineWhileAdjusting;
private boolean updateModelWhileAdjusting;
private JLabel lblRangeAxis;
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel pnlEqualizer;
// End of variables declaration//GEN-END:variables
//~ Constructors -----------------------------------------------------------
/**
* Creates a new EqualizerPanel object using:
*
* <ul>
* <li>a single category model</li>
* <li>splinePaint=<code>DEFAULT_PAINT</code></li>
* <li>splineStroke=<code>DEFAULT_STROKE</code></li>
* <li>splinePainted=<code>true</code></li>
* <li>rangeAxisName= <code>null</code></li>
* <li>rangeAxisPainted= <code>true</code></li>
* <li>updateModelWhileAdjusting= <code>false</code></li>
* <li>updateSplineWhileAdjusting= <code>true</code></li>
* </ul>
*
* @throws IllegalArgumentException DOCUMENT ME!
*
* @see EqualizerPanel(de.cismet.commons.gui.equalizer.EqualizerModel, java.awt.Paint, java.awt.Stroke, boolean,
* java.lang.String, boolean, boolean, boolean)
*/
public EqualizerPanel() {
this(
new EqualizerModel() {
private final EventListenerList list = new EventListenerList();
private final int[] value = new int[1];
@Override
public Range getRange() {
return new Range(0, 10);
}
@Override
public String getEqualizerCategory(final int index) {
return "EQ1"; // NOI18N
}
@Override
public int getEqualizerCategoryCount() {
return 1;
}
@Override
public int getValueAt(final int index) {
return value[index];
}
@Override
public void setValueAt(final int index, final int value) {
if ((value < 0) || (value > 10)) {
throw new IllegalArgumentException("value out of range: " + value); // NOI18N
}
final int oldValue = this.value[index];
this.value[index] = value;
for (final Object o : list.getListeners(EqualizerModelListener.class)) {
((EqualizerModelListener)o).equalizerChanged(
new EqualizerModelEvent(this, index, oldValue, value));
}
}
@Override
public void addEqualizerModelListener(final EqualizerModelListener eml) {
list.add(EqualizerModelListener.class, eml);
}
@Override
public void removeEqualizerModelListener(final EqualizerModelListener eml) {
list.remove(EqualizerModelListener.class, eml);
}
},
DEFAULT_PAINT,
DEFAULT_STROKE,
true,
null,
true,
false,
true);
}
/**
* Creates a new EqualizerPanel object from the provided <code>EqualizerModel</code> using:
*
* <ul>
* <li>splinePaint=<code>DEFAULT_PAINT</code></li>
* <li>splineStroke=<code>DEFAULT_STROKE</code></li>
* <li>splinePainted=<code>true</code></li>
* <li>rangeAxisName= <code>null</code></li>
* <li>rangeAxisPainted= <code>true</code></li>
* <li>updateModelWhileAdjusting= <code>false</code></li>
* <li>updateSplineWhileAdjusting= <code>true</code></li>
* </ul>
*
* @param model the underlying <code>EqualizerModel</code>
*
* @see EqualizerPanel(de.cismet.commons.gui.equalizer.EqualizerModel, java.awt.Paint, java.awt.Stroke, boolean,
* java.lang.String, boolean, boolean, boolean)
*/
public EqualizerPanel(final EqualizerModel model) {
this(model, DEFAULT_PAINT, DEFAULT_STROKE, true, null, true, false, true);
}
/**
* Creates a new EqualizerPanel object.
*
* @param model the underlying <code>EqualizerModel</code>
* @param splinePaint the paint for the spline
* @param splineStroke the stroke for the spline
* @param splinePainted if the spline is painted at all
* @param rangeAxisName the name of the range axis
* @param rangeAxisPainted if the range axis is painted at all
* @param updateModelWhileAdjusting if updates are sent to the model while the user is adjusting the value
* @param updateSplineWhileAdjusting if the spline is updated while the user is adjusting the value
*/
public EqualizerPanel(final EqualizerModel model,
final Paint splinePaint,
final Stroke splineStroke,
final boolean splinePainted,
final String rangeAxisName,
final boolean rangeAxisPainted,
final boolean updateModelWhileAdjusting,
final boolean updateSplineWhileAdjusting) {
this.equalizerModelL = new EqualizerModelL();
this.sliderChangeL = new SliderChangeL();
this.updateInProgress = false;
initComponents();
// we don't use the setter since there is nothing to check and we don't want to createComponents twice
this.updateModelWhileAdjusting = updateModelWhileAdjusting;
this.updateSplineWhileAdjusting = updateSplineWhileAdjusting;
this.splinePainted = splinePainted;
this.rangeAxisPainted = rangeAxisPainted;
this.lblRangeAxis = new JLabel(rangeAxisName);
this.lblRangeAxis.setOpaque(false);
setModel(model);
setSplinePaint(splinePaint);
setSplineStroke(splineStroke);
}
//~ Methods ----------------------------------------------------------------
@Override
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
for (final JSlider s : sliderMap.values()) {
s.setEnabled(enabled);
}
}
/**
* Getter for the current <code>EqualizerModel.</code>
*
* @return the current <code>EqualizerModel</code>
*/
public EqualizerModel getModel() {
return model;
}
/**
* Sets a new <code>EqualizerModel.</code>
*
* @param model the new model
*
* @throws IllegalArgumentException if the model is <code>null</code>
*/
public final void setModel(final EqualizerModel model) {
if (model == null) {
throw new IllegalArgumentException("model must not be null"); // NOI18N
}
if (this.model != model) {
this.model = model;
this.model.addEqualizerModelListener(WeakListeners.create(
EqualizerModelListener.class,
equalizerModelL,
this.model));
recreateComponents();
}
}
/**
* Indicates if the spline is painted or not.
*
* @return if the spline is painted or not
*/
public boolean isSplinePainted() {
return splinePainted;
}
/**
* Sets if the spline is painted or not.
*
* @param splinePainted if the spline is painted or not
*/
public void setSplinePainted(final boolean splinePainted) {
this.splinePainted = splinePainted;
repaint();
}
/**
* Getter for the current spline paint.
*
* @return the current spline paint
*/
public Paint getSplinePaint() {
return splinePaint;
}
/**
* Sets the new spline paint.
*
* @param splinePaint the new spline paint
*
* @throws IllegalArgumentException if the spline paint is <code>null</code>
*/
public final void setSplinePaint(final Paint splinePaint) {
if (splinePaint == null) {
throw new IllegalArgumentException("splinePaint must not be null"); // NOI18N
}
this.splinePaint = splinePaint;
repaint();
}
/**
* Getter for the current spline stroke.
*
* @return the current spline stroke
*/
public Stroke getSplineStroke() {
return splineStroke;
}
/**
* Sets the new spline stroke.
*
* @param splineStroke the new spline stroke
*
* @throws IllegalArgumentException if the spline stroke is <code>null</code>
*/
public final void setSplineStroke(final Stroke splineStroke) {
if (splineStroke == null) {
throw new IllegalArgumentException("splineStroke must not be null"); // NOI18N
}
this.splineStroke = splineStroke;
repaint();
}
/**
* Indicates if the range axis is painted or not.
*
* @return if the range axis is painted or not.
*/
public boolean isRangeAxisPainted() {
return rangeAxisPainted;
}
/**
* Sets if the range axis is painted or not.
*
* @param rangeAxisPainted paint the range axis of not
*/
public final void setRangeAxisPainted(final boolean rangeAxisPainted) {
if (this.rangeAxisPainted != rangeAxisPainted) {
this.rangeAxisPainted = rangeAxisPainted;
recreateComponents();
}
}
/**
* Getter for the current range axis name.
*
* @return the current range axis name
*/
public String getRangeAxisName() {
return lblRangeAxis.getText();
}
/**
* Sets the new range axis name.
*
* @param rangeAxisName the new range axis name
*/
public final void setRangeAxisName(final String rangeAxisName) {
this.lblRangeAxis.setText(rangeAxisName);
}
/**
* Whether or not the model is updated while the user is adjusting a slider value.
*
* @return whether or not the model is updated while the user is adjusting a slider value
*
* @see JSlider#getValueIsAdjusting()
*/
public boolean isUpdateModelWhileAdjusting() {
return updateModelWhileAdjusting;
}
/**
* Through this setter one can control if user interaction with a slider will constantly update the model while the
* user is still choosing the new slider value.
*
* @param updateModelWhileAdjusting whether or not update the model while adjusting
*
* @see JSlider#getValueIsAdjusting()
*/
public void setUpdateModelWhileAdjusting(final boolean updateModelWhileAdjusting) {
this.updateModelWhileAdjusting = updateModelWhileAdjusting;
}
/**
* Whether or not the spline curve will reflect the user's adjusting activity.
*
* @return whether or not the spline curve will reflect the user's adjusting activity
*/
public boolean isUpdateSplineWhileAdjusting() {
return updateSplineWhileAdjusting;
}
/**
* Through this setter one can control if the user interaction with a slider will constantly update the spline curve
* while the user is still choosing the new slider value.
*
* @param updateSplineWhileAdjusting whether or not update the spline curve while adjusting
*/
public void setUpdateSplineWhileAdjusting(final boolean updateSplineWhileAdjusting) {
this.updateSplineWhileAdjusting = updateSplineWhileAdjusting;
}
/**
* DOCUMENT ME!
*/
private void recreateComponents() {
createComponents();
invalidate();
validate();
// strangely the spline is not correctly painted after these steps and calling repaint directly does not help
// only if the repaint is invoked later than the commands above it renders correctly
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
repaint();
}
});
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private JPanel createRangeComponent() {
final JPanel panel = new JPanel(new GridBagLayout());
panel.setOpaque(false);
// TODO: support for rotating the label text
final JPanel rangePanel = new RangePanel();
final GridBagConstraints rangeConstraints = new GridBagConstraints(
0,
0,
1,
1,
0,
1,
GridBagConstraints.SOUTH,
GridBagConstraints.VERTICAL,
new Insets(5, 5, 5, 5),
0,
0);
final GridBagConstraints labelConstraints = new GridBagConstraints(
0,
1,
1,
1,
0,
0,
GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5),
0,
0);
panel.add(rangePanel, rangeConstraints);
panel.add(lblRangeAxis, labelConstraints);
return panel;
}
/**
* DOCUMENT ME!
*/
private void createComponents() {
sliderMap = new TreeMap<Integer, JSlider>(new Comparator<Integer>() {
@Override
public int compare(final Integer o1, final Integer o2) {
return o1.compareTo(o2);
}
});
pnlEqualizer.removeAll();
final GridBagConstraints baseConstraints = new GridBagConstraints(
0,
0,
1,
1,
0,
1,
GridBagConstraints.SOUTH,
GridBagConstraints.VERTICAL,
new Insets(5, 5, 5, 5),
0,
0);
if (rangeAxisPainted) {
final JPanel scaleComp = createRangeComponent();
final GridBagConstraints constraints = (GridBagConstraints)baseConstraints.clone();
pnlEqualizer.add(scaleComp, constraints);
}
for (int i = 0; i < model.getEqualizerCategoryCount(); ++i) {
final JPanel sliderComp = createSliderComponent(i);
final GridBagConstraints constraints = (GridBagConstraints)baseConstraints.clone();
// we start to add it at index one in case of scale is available
constraints.gridx = i + 1;
pnlEqualizer.add(sliderComp, constraints);
}
pnlEqualizer.repaint();
}
/**
* DOCUMENT ME!
*
* @param index category DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private JPanel createSliderComponent(final int index) {
final JPanel panel = new JPanel(new GridBagLayout());
panel.setOpaque(false);
final JLabel label = new JLabel(model.getEqualizerCategory(index));
label.setOpaque(false);
// TODO: support for rotating the label text
final JSlider slider = new JSlider(JSlider.VERTICAL);
slider.setEnabled(isEnabled());
slider.setOpaque(false);
slider.setMinimum(model.getRange().getMin());
slider.setMaximum(model.getRange().getMax());
slider.setValue(model.getValueAt(index));
slider.putClientProperty(PROP_MODEL_INDEX, index);
slider.addChangeListener(WeakListeners.change(sliderChangeL, slider));
sliderMap.put(index, slider);
final GridBagConstraints sliderConstraints = new GridBagConstraints(
0,
0,
1,
1,
0,
1,
GridBagConstraints.SOUTH,
GridBagConstraints.VERTICAL,
new Insets(5, 5, 5, 5),
0,
0);
final GridBagConstraints labelConstraints = new GridBagConstraints(
0,
1,
1,
1,
0,
0,
GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL,
new Insets(5, 5, 5, 5),
0,
0);
panel.add(slider, sliderConstraints);
panel.add(label, labelConstraints);
return panel;
}
/**
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The
* content of this method is always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
final java.awt.GridBagConstraints gridBagConstraints;
pnlEqualizer = new SplinePanel();
setLayout(new java.awt.GridBagLayout());
pnlEqualizer.setOpaque(false);
pnlEqualizer.setLayout(new java.awt.GridBagLayout());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(pnlEqualizer, gridBagConstraints);
} // </editor-fold>//GEN-END:initComponents
//~ Inner Classes ----------------------------------------------------------
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private final class SplinePanel extends JPanel {
//~ Instance fields ----------------------------------------------------
// the fallback is used if the retrieval of the slider knob position has failed only once
private boolean useSliderKnobPositionFallback;
private Double[] currentPoints;
//~ Constructors -------------------------------------------------------
/**
* Creates a new SplinePanel object.
*/
public SplinePanel() {
this.setOpaque(false);
this.useSliderKnobPositionFallback = false;
this.currentPoints = null;
}
//~ Methods ------------------------------------------------------------
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
if (!splinePainted || (model.getEqualizerCategoryCount() < 2)) {
// nothing to do, bail out
return;
}
final Collection<JSlider> sliders = sliderMap.values();
Double[] p = new Double[sliders.size()];
int i = 0;
boolean isAdjusting = false;
for (final JSlider s : sliderMap.values()) {
if (!updateSplineWhileAdjusting && s.getValueIsAdjusting()) {
isAdjusting = true;
break;
}
final Point handle = new Point();
// hack to get position of the knob
final SliderUI sui = s.getUI();
if (!useSliderKnobPositionFallback && (sui instanceof BasicSliderUI)) {
final BasicSliderUI bui = (BasicSliderUI)sui;
Field field = null;
Boolean accessible = null;
try {
field = BasicSliderUI.class.getDeclaredField("thumbRect"); // NOI18N
accessible = field.isAccessible();
field.setAccessible(true);
final Rectangle r = (Rectangle)field.get(bui);
final Point loc = r.getLocation();
handle.x = loc.x + (r.width / 2);
handle.y = loc.y + (r.height / 2);
} catch (final Exception e) {
useSliderKnobPositionFallback = true;
} finally {
if ((field != null) && (accessible != null)) {
field.setAccessible(accessible);
}
}
} else {
useSliderKnobPositionFallback = true;
}
if (useSliderKnobPositionFallback) {
// calculate position using slider value which is most likely inaccurate for y location
final int min = s.getMinimum();
final int max = s.getMaximum();
final int val = s.getValue();
final Dimension sliderSize = s.getSize();
handle.x = sliderSize.width / 2;
// slider percentage from upper bound (reversed)
final double sliderPercentage = 1 - (val / (double)(max - min));
// assume offset because of shape of knob and internal insets
final double offset = 13 * (1 - (2 * sliderPercentage));
handle.y = (int)((sliderSize.height * sliderPercentage) + offset);
}
final Point rel = SwingUtilities.convertPoint(s, handle, this);
p[i] = new Double(rel.x, rel.y);
i++;
}
if (isAdjusting) {
p = currentPoints;
}
final Double[] p1 = new Double[p.length - 1];
final Double[] p2 = new Double[p.length - 1];
calculateControlPoints(p, p1, p2);
final Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
final GeneralPath path = new GeneralPath();
path.moveTo(p[0].x, p[0].y);
for (i = 0; i < p1.length; ++i) {
path.curveTo(p1[i].x, p1[i].y, p2[i].x, p2[i].y, p[i + 1].x, p[i + 1].y);
}
g2.setPaint(splinePaint);
g2.setStroke(splineStroke);
g2.draw(path);
g2.dispose();
currentPoints = p;
}
/**
* Algorithm from
* {@link http://www.codeproject.com/Articles/31859/Draw-a-Smooth-Curve-through-a-Set-of-2D-Points-wit}.
*
* @param p DOCUMENT ME!
* @param p1 DOCUMENT ME!
* @param p2 DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
* @throws IllegalStateException DOCUMENT ME!
*/
private void calculateControlPoints(final Double[] p, final Double[] p1, final Double[] p2) {
if (p == null) {
throw new IllegalArgumentException("points must not be null"); // NOI18N
} else if (p1 == null) {
throw new IllegalArgumentException("p1 must not be null"); // NOI18N
} else if (p2 == null) {
throw new IllegalArgumentException("p2 must not be null"); // NOI18N
}
final int n = p.length - 1;
if (n == 0) {
throw new IllegalStateException(
"at least two points must be available to calculate control points for"); // NOI18N
} else if (p1.length != n) {
throw new IllegalStateException("p1 must have length of " + n + ", but is " + p1.length); // NOI18N
} else if (p2.length != n) {
throw new IllegalStateException("p2 must have length of " + n + ", but is " + p1.length); // NOI18N
}
// straight line
if (n == 1) {
p1[0].x = ((2 * p[0].x) + p[1].x) / 3;
p1[0].y = ((2 * p[0].y) + p[1].y) / 3;
p2[0].x = (2 * p1[0].x) - p[0].x;
p2[0].y = (2 * p1[0].y) - p[0].y;
} else {
final double[] rhs = new double[n];
// x vals
rhs[0] = p[0].x + (2 * p[1].x);
for (int i = 1; i < (n - 1); ++i) {
rhs[i] = (4 * p[i].x) + (2 * p[i + 1].x);
}
rhs[n - 1] = ((8 * p[n - 1].x) + p[n].x) / 2.0;
final double[] x1 = getFirstControlPoints(rhs);
// y vals
rhs[0] = p[0].y + (2 * p[1].y);
for (int i = 1; i < (n - 1); ++i) {
rhs[i] = (4 * p[i].y) + (2 * p[i + 1].y);
}
rhs[n - 1] = ((8 * p[n - 1].y) + p[n].y) / 2.0;
final double[] y1 = getFirstControlPoints(rhs);
for (int i = 0; i < n; ++i) {
p1[i] = new Double(x1[i], y1[i]);
final double x2;
final double y2;
if (i < (n - 1)) {
x2 = (2 * p[i + 1].x) - x1[i + 1];
y2 = (2 * p[i + 1].y) - y1[i + 1];
} else {
x2 = (p[n].x + x1[n - 1]) / 2.0;
y2 = (p[n].y + y1[n - 1]) / 2.0;
}
p2[i] = new Double(x2, y2);
}
}
}
/**
* DOCUMENT ME!
*
* @param rhs DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private double[] getFirstControlPoints(final double[] rhs) {
final int n = rhs.length;
final double[] x = new double[n];
final double[] tmp = new double[n];
double b = 2.0;
x[0] = rhs[0] / b;
for (int i = 1; i < n; ++i) {
tmp[i] = 1 / b;
b = ((i < (n - 1)) ? 4.0 : 3.5) - tmp[i];
x[i] = (rhs[i] - x[i - 1]) / b;
}
for (int i = 1; i < n; ++i) {
x[n - i - 1] -= tmp[n - i] * x[n - i];
}
return x;
}
}
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private final class RangePanel extends JPanel {
//~ Static fields/initializers -----------------------------------------
private static final int INSETS_WIDTH = 5;
private static final int STROKE_WIDTH = 3;
private static final int MAJOR_TICK_WIDTH = 10;
private static final int MINOR_TICK_WIDTH = 7;
private static final int TEXT_GAP_WIDTH = 8;
//~ Instance fields ----------------------------------------------------
private boolean useSliderTrackRectangleFallback;
//~ Constructors -------------------------------------------------------
/**
* Creates a new ScalePanel object.
*/
public RangePanel() {
this.setOpaque(false);
this.useSliderTrackRectangleFallback = false;
final Font font = this.getFont().deriveFont(10f);
final FontRenderContext frc = new FontRenderContext(font.getTransform(), true, true);
final Rectangle2D maxBounds = font.getStringBounds(String.valueOf(model.getRange().getMax()), frc);
final Rectangle2D minBounds = font.getStringBounds(String.valueOf(model.getRange().getMin()), frc);
final int minX1 = INSETS_WIDTH + (int)maxBounds.getWidth() + TEXT_GAP_WIDTH + MAJOR_TICK_WIDTH
+ INSETS_WIDTH;
final int minX2 = INSETS_WIDTH + (int)minBounds.getWidth() + TEXT_GAP_WIDTH + MAJOR_TICK_WIDTH
+ INSETS_WIDTH;
final int minX = Math.max(minX1, minX2);
final int minY = INSETS_WIDTH + (int)maxBounds.getHeight() + TEXT_GAP_WIDTH + (int)minBounds.getHeight()
+ INSETS_WIDTH;
this.setMinimumSize(new Dimension(minX, minY));
this.setPreferredSize(new Dimension(minX, minY));
this.setFont(font);
}
//~ Methods ------------------------------------------------------------
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
final JSlider slider = sliderMap.values().iterator().next();
final SliderUI sui = slider.getUI();
final Dimension size = getSize();
int upperY = 0;
int lowerY = 0;
if (!useSliderTrackRectangleFallback && (sui instanceof BasicSliderUI)) {
final BasicSliderUI bui = (BasicSliderUI)sui;
Field field = null;
Boolean accessible = null;
try {
field = BasicSliderUI.class.getDeclaredField("trackRect"); // NOI18N
accessible = field.isAccessible();
field.setAccessible(true);
final Rectangle r = (Rectangle)field.get(bui);
upperY = r.getLocation().y;
lowerY = upperY + (int)r.getHeight();
} catch (final Exception e) {
useSliderTrackRectangleFallback = true;
} finally {
if ((field != null) && (accessible != null)) {
field.setAccessible(accessible);
}
}
} else {
useSliderTrackRectangleFallback = true;
}
if (useSliderTrackRectangleFallback) {
// assume offset because of internal insets
upperY = 13;
lowerY = size.height - 13;
}
final int midY = ((lowerY - upperY) / 2) + upperY;
final int rangeLineX = size.width - INSETS_WIDTH - STROKE_WIDTH;
final int majorTickX = rangeLineX - MAJOR_TICK_WIDTH;
final int minorTickX = rangeLineX - MINOR_TICK_WIDTH;
// baseline of text on the lower boundary of the tick line
final int upperRangeY = upperY + STROKE_WIDTH;
final int lowerRangeY = lowerY + STROKE_WIDTH;
final int midRangeY = midY + STROKE_WIDTH;
final int rangeMin = model.getRange().getMin();
final int rangeMax = model.getRange().getMax();
final int rangeMid = (rangeMax + rangeMin) / 2;
// TODO: nicer rendering
final Graphics2D g2 = (Graphics2D)g.create();
g2.setStroke(new BasicStroke(STROKE_WIDTH, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawLine(rangeLineX, upperY, rangeLineX, lowerY);
g2.drawLine(majorTickX, upperY, rangeLineX, upperY);
g2.drawLine(majorTickX, lowerY, rangeLineX, lowerY);
g2.drawLine(minorTickX, midY, rangeLineX, midY);
g2.drawString(String.valueOf(rangeMax), INSETS_WIDTH, upperRangeY);
g2.drawString(String.valueOf(rangeMin), INSETS_WIDTH, lowerRangeY);
g2.drawString(String.valueOf(rangeMid), INSETS_WIDTH, midRangeY);
g2.dispose();
}
}
/**
* DOCUMENT ME!
*
* @version 1.0
*/
private final class SliderChangeL implements ChangeListener {
//~ Methods ------------------------------------------------------------
@Override
public void stateChanged(final ChangeEvent e) {
if (!updateInProgress) {
pnlEqualizer.repaint();
final JSlider slider = (JSlider)e.getSource();
if (updateModelWhileAdjusting || !slider.getValueIsAdjusting()) {
final int index = (Integer)slider.getClientProperty(PROP_MODEL_INDEX);
model.setValueAt(index, slider.getValue());
}
}
}
}
/**
* DOCUMENT ME!
*
* @version 1.0
*/
private final class EqualizerModelL implements EqualizerModelListener {
//~ Methods ------------------------------------------------------------
@Override
public void equalizerChanged(final EqualizerModelEvent event) {
if ((event.getSource() == model)) {
updateInProgress = true;
try {
if (event.getIndex() < 0) {
// full update
for (int i = 0; i < model.getEqualizerCategoryCount(); ++i) {
sliderMap.get(i).setValue(model.getValueAt(i));
}
} else {
final JSlider slider = sliderMap.get(event.getIndex());
final int newValue = event.getNewValue();
if (slider.getValue() != newValue) {
slider.setValue(newValue);
}
}
pnlEqualizer.repaint();
} finally {
updateInProgress = false;
}
}
}
}
}