/* * org.openmicroscopy.shoola.util.ui.slider.TwoKnobsSlider * *------------------------------------------------------------------------------ * Copyright (C) 2006-2016 University of Dundee. All rights reserved. * * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.util.ui.slider; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import javax.swing.JPanel; /** * A two knobs slider. * This component extends {@link JPanel} and is composed of * two knobs to select a sub-interval of an interval defined * by a minimum and maximum value. * The slider behaves mostly like a {@link javax.swing.JSlider}. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * <small> * (<b>Internal version:</b> $Revision: $ $Date: $) * </small> * @since OME2.2 */ public class TwoKnobsSlider extends JPanel { /** The default minimum value. */ public static final int DEFAULT_MIN = 0; /** The default maximum value. */ public static final int DEFAULT_MAX = 100; /** Bound property name indicating if the dragged knob is released. */ public final static String KNOB_RELEASED_PROPERTY = "knobReleased"; /** Bound property name indicating if the left knob is moved. */ public final static String LEFT_MOVED_PROPERTY = "leftMoved"; /** Bound property name indicating if the left knob is moved. */ public final static String RIGHT_MOVED_PROPERTY = "rightMoved"; /** Bound property name indicating if the new start value is set. */ public final static String START_VALUE_PROPERTY = "startValue"; /** Bound property name indicating if the new end value is set. */ public final static String END_VALUE_PROPERTY = "endValue"; /** Bound property name indicating if the new max value is set. */ public final static String MAX_VALUE_PROPERTY = "maxValue"; /** Bound property name indicating if the new min value is set. */ public final static String MIN_VALUE_PROPERTY = "minValue"; /** Bound property name indicating if values of the slider are reset. */ public final static String SET_VALUES_PROPERTY = "setValues"; /** Identifies an horizontal slider. */ public static final int HORIZONTAL = 100; /** Identifies a vertical slider. */ public static final int VERTICAL = 101; /** Initial value of the knob control. */ public static final int INITIAL = 0; /** Indicates that the left knob is moved. */ public static final int LEFT = 1; /** Indicates that the right know is moved. */ public static final int RIGHT = 2; /** The default dimension of an horizontal slider. */ protected static final Dimension MIN_HORIZONTAL = new Dimension(36, 21); /** The default dimension of a vertical slider. */ protected static final Dimension MIN_VERTICAL = new Dimension(21, 36); /** The preferred dimension of a vertical slider. */ protected static final Dimension PREFERRED_VERTICAL = new Dimension(21, 80); /** The preferred dimension of a horizontal slider. */ protected static final Dimension PREFERRED_HORIZONTAL = new Dimension(80, 21); /** The insets. */ protected Insets insetCache = null; /** The width of a knob. */ private int knobWidth; /** The height of the knob. */ private int knobHeight; /** The component's model. */ private TwoKnobsSliderModel model; /** The View component that renders this slider. */ private TwoKnobsSliderUI uiDelegate; /** The colors for the gradient. */ private Color[] gradients; /** * Indicates which knob is moved. * One of the following constants: {@link #INITIAL}, {@link #LEFT} or * {@link #RIGHT}. */ private int knobControl; /** The preferred size of this component. */ private Dimension preferredSize_; /** The height of the font. */ private int fontHeight; /** Flag indicating that the colors have been set. */ private boolean colourGradient; /** Computes the preferred size of this component. */ private void calculatePreferredSize() { int h = knobHeight; int w = 0; if (model.isPaintTicks()) h += knobHeight; if (model.isPaintLabels() || model.isPaintEndLabels()) h += TwoKnobsSliderUI.EXTRA+fontHeight+2*TwoKnobsSliderUI.BUFFER; if (model.isPaintCurrentValues()) { FontMetrics fm = getFontMetrics(getFont()); w += fm.stringWidth(model.render(model.getAbsoluteMinimum())); w += fm.stringWidth(model.render(model.getAbsoluteMaximum())); } if (model.getOrientation() == VERTICAL) preferredSize_ = PREFERRED_VERTICAL; else preferredSize_ = new Dimension(PREFERRED_HORIZONTAL.width+w, h); } /** Sets the default values. */ private void setDefault() { insetCache = getInsets(); fontHeight = getFontMetrics(getFont()).getHeight(); knobControl = INITIAL; knobWidth = uiDelegate.getKnobWidth(); knobHeight = uiDelegate.getKnobHeight(); colourGradient = false; calculatePreferredSize(); } /** * Adds a <code>MouseListener</code> and a <code>MouseMotionListener</code>. */ private void attachListeners() { addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent evt) { handleMouseEvent(evt); } public void mouseReleased(MouseEvent evt) { release(); } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent evt) { handleMouseEvent(evt); } }); } /** Fires a property indicating that the dragged knob is released. */ private void release() { if (!model.isEnabled()) return; firePropertyChange(KNOB_RELEASED_PROPERTY, Integer.valueOf(INITIAL), Integer.valueOf(knobControl)); knobControl = INITIAL; } /** * Handles the events according to the orientation selected. * * @param me The event to handle. */ private void handleMouseEvent(MouseEvent me) { if (!model.isEnabled()) return; double oldStart = getStartValue(); double oldEnd = getEndValue(); if (model.getOrientation() == TwoKnobsSlider.HORIZONTAL) { handleMouseEventForHorizSlider((int) me.getPoint().getX()); switch (knobControl) { case LEFT: if (oldStart != getStartValue()) firePropertyChange(LEFT_MOVED_PROPERTY, oldStart, getStartValue()); break; case RIGHT: if (oldEnd != getEndValue()) firePropertyChange(RIGHT_MOVED_PROPERTY, oldEnd, getEndValue()); } } else { handleMouseEventForVertSlider((int) me.getPoint().getY()); switch (knobControl) { case LEFT: if (oldEnd != getEndValue()) firePropertyChange(LEFT_MOVED_PROPERTY, oldEnd, getEndValue()); break; case RIGHT: if (oldStart != getStartValue()) firePropertyChange(RIGHT_MOVED_PROPERTY, oldStart, getStartValue()); } } } /** * Handles the event for an horizontal slider. * * @param x The x-coordinate of the mouse. */ private void handleMouseEventForHorizSlider(int x) { int leftKnob = uiDelegate.xPositionForValue(model.getStartValue()); int rightKnob = uiDelegate.xPositionForValue(model.getEndValue()); int left = leftKnob, right = rightKnob; int xmin = uiDelegate.xPositionForValue(model.getPartialMinimum()); int xmax = uiDelegate.xPositionForValue(model.getPartialMaximum()); //Identifies the closest knob int limit = leftKnob+(rightKnob-leftKnob)/2; if (x < limit && knobControl != RIGHT) { knobControl = LEFT; left = x; } else if (x > limit && knobControl != LEFT) { knobControl = RIGHT; right = x; } if (knobControl == LEFT) { //left knob moved. model.setStartValue(uiDelegate.xValueForPosition(left, true)); } else if (knobControl == RIGHT) { //right knob moved. model.setEndValue(uiDelegate.xValueForPosition(x, false)); } repaint(); } /** * Handles the event for a vertical slider. * * @param y The y-coordinate of the mouse. */ private void handleMouseEventForVertSlider(int y) { int upKnob = uiDelegate.yPositionForValue(model.getEndValue()); int downKnob = uiDelegate.yPositionForValue(model.getStartValue()); int up = upKnob, down = downKnob; int ymin = uiDelegate.yPositionForValue(model.getPartialMaximum()); int ymax = uiDelegate.yPositionForValue(model.getPartialMinimum()); //Identifies the closest knob int limit = upKnob+(downKnob-upKnob)/2; if (y < limit && knobControl != RIGHT) { knobControl = LEFT; //corresponds to the up knob up = y; } else if (y > limit && knobControl != LEFT) { knobControl = RIGHT; down = y; } if (knobControl == LEFT) { //left knob moved. if (up < ymin) up = ymin; else if (up > (ymax-knobHeight)) up = ymax-knobHeight; else { if (up > down && down < ymax) up = down-1; } model.setEndValue(uiDelegate.yValueForPosition(up, true)); } else if (knobControl == RIGHT) { //right knob moved. if (down > ymax) down = ymax; else if (down < (ymin+knobHeight)) down = ymin+knobHeight; else { if (down < up && up > ymin) down = up+1; } model.setStartValue(uiDelegate.yValueForPosition(down, false)); } repaint(); } /** * Returns the preferred size of an horizontal slider. * * @return See above. */ protected Dimension getPreferredHorizontalSize() { return PREFERRED_HORIZONTAL; } /** * Returns the preferred size of a vertical slider. * * @return See above. */ protected Dimension getPreferredVerticalSize() { return PREFERRED_VERTICAL; } /** * Returns the minimum size of an horizontal slider. * * @return See above. */ protected Dimension getMinimumHorizontalSize() { return MIN_HORIZONTAL; } /** * Returns the minimum size of an horizontal slider. * * @return See above. */ protected Dimension getMinimumVerticalSize() { return MIN_VERTICAL; } /** * Returns the {@link #knobControl} i.e. either {@link #LEFT}, * {@link #RIGHT} of <code>-1</code> if not assigned. * * @return See above. */ int getKnobControl() { return knobControl; } /** * Creates a default slider with two knobs. * The minimum and start values are {@link #DEFAULT_MIN}. * The maximum and end values are {@link #DEFAULT_MAX}. */ public TwoKnobsSlider() { this(DEFAULT_MIN, DEFAULT_MAX, DEFAULT_MIN, DEFAULT_MAX); } /** * Creates a slider with two knobs of passed minimum, maximum, start and * end value. * * @param min The minimum value of the slider. * @param max The maximum value of the slider. * @param start The start value. * @param end The end value. */ public TwoKnobsSlider(double min, double max, double start, double end) { this(min, max, min, max, start, end); } /** * Creates a slider with two knobs of passed minimum, maximum, start and * end value. * * @param absoluteMin The absolute minimum value of the slider. * @param absoluteMax The absolute minimum value of the slider. * @param min The minimum value of the slider. * @param max The maximum value of the slider. * @param start The start value. * @param end The end value. */ public TwoKnobsSlider(double absoluteMin, double absoluteMax, double min, double max, double start, double end) { model = new TwoKnobsSliderModel(absoluteMax, absoluteMin, max, min, start, end); uiDelegate = new TwoKnobsSliderUI(this, model); attachListeners(); setDefault(); } /** * Returns the height of the knob. * * @return See above. */ public int getKnobHeight() { return knobHeight; } /** * Returns the width of the knob. * * @return See above. */ public int getKnobWidth() { return knobWidth; } /** * Sets the color of the font. * * @param c The font color. */ public void setFontColor(Color c) { if (c == null) return; uiDelegate.setFontColor(c); } /** * Returns the value of the start knob i.e. value between * {@link TwoKnobsSliderModel#getMinimum()} and {@link #getEndValue()}. * * @return See above. */ public double getStartValue() { return model.getStartValue(); } /** * Rounds and returns the start value as int * (e. g. 2.49 will be rounded to 2, whereas 2.51 will * be rounded to 3) (one based) * @return See above. */ public int getStartValueAsInt() { return (int)(model.getStartValue()+0.5); } /** * Returns the value of the end knob i.e. value between * {@link #getStartValue()} and {@link TwoKnobsSliderModel#getMaximum()}. * * @return See above. */ public double getEndValue() { return model.getEndValue(); } /** * Rounds and returns the end value as int * (e. g. 2.49 will be rounded to 2, whereas 2.51 will * be rounded to 3) (one based) * @return See above. */ public int getEndValueAsInt() { return (int)(model.getEndValue()+0.5); } /** * Sets the start value. The value must be greater than the minimum and * lower than the end value. * * @param v The value to set. */ public void setStartValue(double v) { double old = model.getStartValue(); model.setStartValue(v); firePropertyChange(START_VALUE_PROPERTY, Double.valueOf(old), Double.valueOf(v)); repaint(); } /** * Sets the end value. The value must be greater than the start value and * lower than the maximum. * * @param v The value to set. */ public void setEndValue(double v) { double old = model.getStartValue(); model.setEndValue(v); firePropertyChange(END_VALUE_PROPERTY, Double.valueOf(old), Double.valueOf(v)); repaint(); /* int max = model.getAbsoluteMaximum(); if (v > max) return; if (v <= getStartValue()) return; int old = model.getEndValue(); model.setEndValue(v); firePropertyChange(END_VALUE_PROPERTY, Integer.valueOf(old), Integer.valueOf(v)); repaint();*/ } /** * Resets the default value of the slider. * * @param absoluteMax The absolute maximum value of the slider. * @param absoluteMin The absolute minimum value of the slider. * @param max The maximum value. * @param min The minimum value. * @param start The value of the start knob. (one based) * @param end The value of the end knob. (one based) */ public void setValues(double absoluteMax, double absoluteMin, double max, double min, double start, double end) { model.checkValues(absoluteMax, absoluteMin, max, min, start, end); firePropertyChange(SET_VALUES_PROPERTY, Boolean.valueOf(false), Boolean.valueOf(true)); repaint(); } /** * Sets the input interval. * * @param start The value of the start knob. (one based) * @param end The value of the end knob. (one based) */ public void setInterval(double start, double end) { if (start > end) return; double max = model.getAbsoluteMaximum(); if (end > max) return; double oldEnd = model.getEndValue(); double oldStart = model.getStartValue(); model.setInterval(start, end); firePropertyChange(START_VALUE_PROPERTY, Double.valueOf(oldStart), Double.valueOf(start)); firePropertyChange(END_VALUE_PROPERTY, Double.valueOf(oldEnd), Double.valueOf(end)); repaint(); } /** * Paints the labels if the passed flag is <code>true</code>. * * @param paintLabel Passed <code>true</code> to paint the labels. */ public void setPaintLabels(boolean paintLabel) { if (model.isPaintLabels() == paintLabel) return; model.setPaintLabels(paintLabel); calculatePreferredSize(); repaint(); } /** * Paints the current values if the passed flag is <code>true</code>. * * @param paintCurrentValues Passed <code>true</code> to paint the values. */ public void setPaintCurrentValues(boolean paintCurrentValues) { if (model.isPaintCurrentValues() == paintCurrentValues) return; model.setPaintCurrentValues(paintCurrentValues); calculatePreferredSize(); repaint(); } /** * Paints the minimum and maximum labels if the passed flag * is <code>true</code>. * * @param paintLabel Passed <code>true</code> to paint the labels. */ public void setPaintEndLabels(boolean paintLabel) { if (model.isPaintEndLabels() == paintLabel) return; model.setPaintEndLabels(paintLabel); calculatePreferredSize(); repaint(); } /** * Paints the ticks if the passed value is <code>true</code>. * * @param paintTicks Passed <code>true</code> to paint the ticks. */ public void setPaintTicks(boolean paintTicks) { if (model.isPaintTicks() == paintTicks) return; model.setPaintTicks(paintTicks); calculatePreferredSize(); repaint(); } /** * Passes <code>true</code> to allow knobs motions, <code>false</code> * otherwise. * * @param b The value to set. */ public void setEnabled(boolean b) { super.setEnabled(b); model.setEnabled(b); } /** * Returns the orientation of the slider either * {@link #HORIZONTAL} or {@link #VERTICAL}. * * @return See above. */ public int getOrientation() { return model.getOrientation(); } /** * Sets the orientation of the slider either * {@link #HORIZONTAL} or {@link #VERTICAL}. * * @param v The value to set. */ public void setOrientation(int v) { if (v == HORIZONTAL || v == VERTICAL) model.setOrientation(v); else throw new IllegalArgumentException("Orientation not supported."); } /** * Sets the space between the minor ticks. Passes <code>true</code> * to set the value to <code>0</code>, <code>false</code> otherwise * * @param b */ public void setPaintMinorTicks(boolean b) { if (b) { double m = model.getMinorTickSpacing(); if (m == 0) m = 1; model.setMinorTickSpacing(m); } model.setMinorTickSpacing(0); repaint(); } /** * Sets the space between the minor ticks. * * @param s The space between minor */ public void setMinorTickSpacing(int s) { model.setMinorTickSpacing(s); } /** * Returns the minimum value if the absolute min equals the minimum * otherwise returns the minimum value. * * @return See above. */ public double getPartialMinimum() { return model.getPartialMinimum(); } /** * Returns the maximum value if the absolute max equals the maximum * otherwise returns the maximum value. * * @return See above. */ public double getPartialMaximum() { return model.getPartialMaximum(); } /** * Sets the color gradient of the slider. This will replace the track with * a gradient. * * @param rgbStart Start color of the gradient. * @param rgbEnd End color of the gradient. */ public void setColourGradients(Color rgbStart, Color rgbEnd) { if (rgbStart == null || rgbEnd == null) return; colourGradient = true; gradients = new Color[2]; gradients[0] = rgbStart; gradients[1] = rgbEnd; } /** * Sets the colors. * * @param colors The colors to set. */ public void setColourGradients(Color[] colors) { if (colors == null || colors.length < 2) return; colourGradient = true; gradients = colors; } /** * Returns the colors of the gradient. * * @return See above. */ Color[] getGradientColors() { return gradients; } /** * Returns <code>True</code> if the color gradient is set, * <code>false</code> otherwise. * * @return See above. */ public boolean getColourGradient() { return colourGradient; } /** * Sets the flag indicating that we allow to have <code>start == end</code>. * * @param overlap Pass <code>true</code> to allow <code>start == end</code>, * <code>false</code> otherwise. */ public void setOverlap(boolean overlap) { model.setOverlap(overlap); uiDelegate.createDarkerImage(); } /** * Overrides method to return the <code>Preferred Size</code>. * @see JPanel#getPreferredSize() */ public Dimension getPreferredSize() { if (getOrientation() == VERTICAL) return getPreferredVerticalSize(); int width = getPreferredHorizontalSize().width; int height = insetCache.top + insetCache.bottom; height += preferredSize_.height; return new Dimension(width, height); } /** * Overrides method to return the <code>Minimum Size</code>. * @see JPanel#getMinimumSize() */ public Dimension getMinimumSize() { return preferredSize_; } /** * Overrides the {@link #update(Graphics)} method to avoid * flicking event. * @see JPanel#update(Graphics) */ public void update(Graphics g) { paintComponent(g); } /** * Method invoked at runtime when the slider is resized. * @see JPanel#setBounds(int, int, int, int) */ public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); calculatePreferredSize(); repaint(); } /** * Overrides the {@link #paintComponent(Graphics)} to paint the slider, * label, ticks if required. * @see JPanel#paintComponent(Graphics) */ public void paintComponent(Graphics g) { super.paintComponent(g); uiDelegate.paintComponent((Graphics2D) g, getSize()); } }