/* * org.openmicroscopy.shoola.util.ui.slider.TwoKnobsSliderUI * *------------------------------------------------------------------------------ * Copyright (C) 2006-2014 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; //Java imports import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Paint; import java.awt.Rectangle; import java.util.Iterator; import java.util.Map; import javax.swing.ImageIcon; import javax.swing.UIManager; //Third-party libraries //Application-internal dependencies import org.openmicroscopy.shoola.util.image.geom.Factory; import org.openmicroscopy.shoola.util.ui.IconManager; import org.openmicroscopy.shoola.util.ui.UIUtilities; /** * The UI delegate for the {@link TwoKnobsSlider}. * A delegate can't be shared among different instances of * {@link TwoKnobsSlider} and has a life-time dependency with its owning slider. * * @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 */ class TwoKnobsSliderUI { /** Extra space added to the track. */ static final int EXTRA = 3; /** Space added to paint the label. */ static final int BUFFER = 1; /** The default color of the track. */ private static final Color TRACK_COLOR = Color.LIGHT_GRAY; /** Default size of a thumbnail i.e. 16x16. */ private static final Dimension DEFAULT_THUMB_SIZE = new Dimension(16, 16); /** The component that owns this delegate. */ private TwoKnobsSlider component; /** Reference to the model. */ private TwoKnobsSliderModel model; /** The rectangle hosting the track and the two knobs. */ private Rectangle trackRect; /** The rectangle hosting the ticks. */ private Rectangle tickRect; /** The rectangle hosting the label. */ private Rectangle labelRect; /** The rectangle hosting the start value. */ private Rectangle startLabelRect; /** The rectangle hosting the start value. */ private Rectangle endLabelRect; /** The color the track. */ private Color shadowColor; /** The color of the font. */ private Color fontColor; /** The image used to draw the thumb. */ private Image thumbImage; /** The image used to draw the arrow thumb. */ private Image upArrowImage; /** The image used to draw the disabled arrow thumb. */ private Image disabledUpArrowImage; /** The image used to draw the thumb when the slider is disabled. */ private Image disabledThumbImage; /** The image used to draw the thumb. */ private Image thumbImageDarker; /** The image used to draw the thumb when the slider is disabled. */ private Image disabledThumbImageDarker; /** Initializes the components. */ private void initialize() { trackRect = new Rectangle(); tickRect = new Rectangle(); labelRect = new Rectangle(); startLabelRect = new Rectangle(); endLabelRect = new Rectangle(); shadowColor = UIManager.getColor("Slider.shadow"); fontColor = UIUtilities.LINE_COLOR; } /** Loads the thumb for the two knob slider.*/ private void createThumbImage() { // Create the thumb image IconManager icons = IconManager.getInstance(); ImageIcon icon = icons.getImageIcon(IconManager.THUMB); thumbImage = icon.getImage(); icon = icons.getImageIcon(IconManager.THUMB_DISABLED); disabledThumbImage = icon.getImage(); icon = icons.getImageIcon(IconManager.UP_ARROW_10); upArrowImage = icon.getImage(); icon = icons.getImageIcon(IconManager.UP_ARROW_DISABLED_10); disabledUpArrowImage = icon.getImage(); } /** * Paints the ticks. * * @param g The graphics context. */ private void paintTicks(Graphics2D g) { g.setColor(shadowColor); g.translate(0, tickRect.y); double value = model.getMinimum(); int xPos = 0; double minor = model.getMinorTickSpacing(); double major = model.getMajorTickSpacing(); double max = model.getMaximum(); double min = model.getMinimum(); if (model.getOrientation() == TwoKnobsSlider.HORIZONTAL) { if (minor > 0) { while (value <= max) { xPos = xPositionForValue(value); paintMinorTickForHorizSlider(g, tickRect, xPos); value += minor; } } if (major > 0) { value = min; while (value <= max) { xPos = xPositionForValue(value ); paintMajorTickForHorizSlider(g, tickRect, xPos); value += major; } } g.translate(0, -tickRect.y); } else { g.translate(tickRect.x, 0); value = min; int yPos = 0; if (minor > 0) { while (value <= max) { yPos = yPositionForValue(value); paintMinorTickForVertSlider(g, tickRect, yPos); value += minor; } } if (major > 0) { value = min; while (value <= max) { yPos = yPositionForValue(value); paintMajorTickForVertSlider( g, tickRect, yPos); value += major; } } g.translate(-tickRect.x, 0); } } /** * Paints the labels. * * @param g The graphics context. * @param fontMetrics Information on how to render the font. */ private void paintLabels(Graphics2D g, FontMetrics fontMetrics) { g.setColor(fontColor); Map labels = model.getLabels(); Iterator i = labels.keySet().iterator(); Double key; int value; while (i.hasNext()) { key = (Double) i.next(); value = key.intValue(); if (model.getOrientation() == TwoKnobsSlider.HORIZONTAL) { g.translate(0, labelRect.y); paintHorizontalLabel(g, fontMetrics, value); g.translate(0, -labelRect.y); } else { g.translate(labelRect.x, 0); paintVerticalLabel(g, fontMetrics, value); g.translate(-labelRect.x, 0); } } } /** * Paints the current values. * * @param g The graphics context. * @param fontMetrics Information on how to render the font. */ private void paintCurrentValues(Graphics2D g, FontMetrics fontMetrics) { g.setColor(fontColor); String s = ""+model.getStartValue(); g.drawString(s, startLabelRect.x, fontMetrics.getHeight()); //g.translate(startLabelRect.x, 0); //paintHorizontalLabel(g, fontMetrics, model.getStartValue()); //g.translate(-startLabelRect.x, 0); s = ""+model.getEndValue(); g.drawString(s, startLabelRect.y, fontMetrics.getHeight()); //g.translate(endLabelRect.x, 0); //paintHorizontalLabel(g, fontMetrics, model.getEndValue()); //g.translate(-endLabelRect.x, 0); } /** * Paints the label for an horizontal slider. * * @param g The graphics context. * @param fontMetrics Information on how to render the font. * @param value The value to paint. */ private void paintHorizontalLabel(Graphics2D g, FontMetrics fontMetrics, int value) { String s = ""+value; int labelLeft = xPositionForValue(value)-fontMetrics.stringWidth(s)/2; g.translate(labelLeft, 0); g.drawString(s, 0, fontMetrics.getHeight()); g.translate(-labelLeft, 0); } /** * Paints the label for a vertical slider. * * @param g The graphics context. * @param fontMetrics Information on how to render the font. * @param value The value to paint. */ private void paintVerticalLabel(Graphics2D g, FontMetrics fontMetrics, int value) { int v = fontMetrics.getHeight()/2+(fontMetrics.getHeight()/2)%2; int labelTop = yPositionForValue(value)-v; g.translate(0, labelTop); g.drawString(""+value, 0, fontMetrics.getHeight()); g.translate(0, -labelTop); } /** * Paints the minor tick for an horizontal slider. * * @param g The graphics context. * @param bounds The bounds of the tick box. * @param x The x-position. */ private void paintMinorTickForHorizSlider(Graphics2D g, Rectangle bounds, int x) { g.drawLine(x, 0, x, bounds.height/2-1); } /** * Paints the major tick for an horizontal slider. * * @param g The graphics context. * @param bounds The bounds of the tick box. * @param x The x-position. */ private void paintMajorTickForHorizSlider(Graphics2D g, Rectangle bounds, int x) { g.drawLine(x, 0, x, bounds.height-2); } /** * Paints the minor tick for an horizontal slider. * * @param g The graphics context. * @param bounds The bounds of the tick box. * @param y The y-position. */ private void paintMinorTickForVertSlider(Graphics2D g, Rectangle bounds, int y) { g.drawLine(0, y, bounds.width/2-1, y); } /** * Paints the major tick for an vertical slider. * * @param g The graphics context. * @param bounds The bounds of the tick box. * @param y The y-position. */ private void paintMajorTickForVertSlider(Graphics2D g, Rectangle bounds, int y) { g.drawLine(0, y, bounds.width-2, y); } /** * Paints the track and the knobs for an horizontal slider. * * @param g2D The graphic context. */ private void paintTrackAndKnobsForHorizSlider(Graphics2D g2D) { int l = xPositionForValue(model.getStartValue()); int r = xPositionForValue(model.getEndValue()); if (!component.getColourGradient()) { Paint paint = new GradientPaint(0, trackRect.y, UIUtilities.TRACK_GRADIENT_START, 0, trackRect.y+trackRect.height-10, UIUtilities.TRACK_GRADIENT_END, false); g2D.setPaint(paint); g2D.fillRoundRect(trackRect.x, trackRect.y+3, trackRect.width, trackRect.height-12, trackRect.height/3, trackRect.height/3); g2D.setColor(TRACK_COLOR); g2D.drawRoundRect(trackRect.x, trackRect.y+2, trackRect.width, trackRect.height-11, trackRect.height/3, trackRect.height/3); } else { Color[] colors = component.getGradientColors(); Paint paint = new GradientPaint(trackRect.x, trackRect.y-2, colors[0], trackRect.width, trackRect.height+2, colors[1], false); g2D.setPaint(paint); g2D.fillRoundRect(trackRect.x, trackRect.y+2, trackRect.width, trackRect.height-10, trackRect.height/3, trackRect.height/3); g2D.setColor(Color.black); g2D.drawRoundRect(trackRect.x, trackRect.y+2, trackRect.width, trackRect.height-9, trackRect.height/3, trackRect.height/3); } //Draw the knobs int w = component.getKnobWidth(); int h = component.getKnobHeight(); Image img; int offset = 0; if (!component.getColourGradient()) { if (model.isEnabled()) img = thumbImage; else img = disabledThumbImage; } else { w = 12; h = 12; if (model.isEnabled()) img = upArrowImage; else img = disabledUpArrowImage; offset = 5; } if (component.getKnobControl() == TwoKnobsSlider.LEFT) { if (model.allowOverlap() && !component.getColourGradient()) { img = thumbImageDarker; if (!model.isEnabled()) img = disabledThumbImageDarker; } g2D.drawImage(img, r-w/2, 1+offset, w, h, null); if (!component.getColourGradient()) { if (model.isEnabled()) img = thumbImage; else img = disabledThumbImage; } g2D.drawImage(img, l-w/2, 1+offset, w, h, null); } else { g2D.drawImage(img, l-w/2, 1+offset, w, h, null); if (model.allowOverlap() && !component.getColourGradient()) { img = thumbImageDarker; if (!model.isEnabled()) img = disabledThumbImageDarker; } g2D.drawImage(img, r-w/2, 1+offset, w, h, null); } } /** * Paints the track and the knobs for a vertical slider. * * @param g2D The graphic context. */ private void paintTrackAndKnobsForVertSlider(Graphics2D g2D) { int down = yPositionForValue(model.getStartValue()); int up = yPositionForValue(model.getEndValue()); int w = component.getKnobWidth(); int h = component.getKnobHeight(); int x = trackRect.x-w/2+(trackRect.width-w)/2; Paint paint; if (!component.getColourGradient()) { paint = new GradientPaint(trackRect.x+1, trackRect.y+h/2, UIUtilities.TRACK_GRADIENT_START, trackRect.x+1+trackRect.width-w-2, trackRect.y+h/2, UIUtilities.TRACK_GRADIENT_END, false); g2D.setPaint(paint); g2D.fillRoundRect(trackRect.x+1, trackRect.y+h/2, trackRect.width-w-2, trackRect.height, trackRect.width/3, trackRect.width/3); } else { Color[] colors = component.getGradientColors(); paint = new GradientPaint(trackRect.x+1, trackRect.y+h/2, colors[0], trackRect.x+1+trackRect.width-w-2, trackRect.y+h/2, colors[1], false); g2D.setPaint(paint); g2D.fillRoundRect(trackRect.x, trackRect.y+3, trackRect.width, trackRect.height-12, trackRect.height/3, trackRect.height/3); } //Draw the knobs Image img; if (!component.getColourGradient()) { if (model.isEnabled()) img = thumbImage; else img = disabledThumbImage; } else { w = 10; h = 10; if (model.isEnabled()) img = upArrowImage; else img = disabledUpArrowImage; } if (component.getKnobControl() == TwoKnobsSlider.LEFT) { if (model.allowOverlap() && !component.getColourGradient()) { img = thumbImageDarker; if (!model.isEnabled()) img = disabledThumbImageDarker; } g2D.drawImage(img, x, down, w, h, null); if (!component.getColourGradient()) { if (model.isEnabled()) img = thumbImage; else img = disabledThumbImage; } g2D.drawImage(img, x, up, w, h, null); } else { g2D.drawImage(img, x, up, w, h, null); if (model.allowOverlap() && !component.getColourGradient()) { img = thumbImageDarker; if (!model.isEnabled()) img = disabledThumbImageDarker; } g2D.drawImage(img, x, down, w, h, null); } } /** * Determines the boundary of each rectangle composing the slider * according to the font metrics and the dimension of the component. * * @param fontMetrics The font metrics. * @param size The dimension of the component. */ private void computeRectangles(FontMetrics fontMetrics, Dimension size) { int w = component.getKnobWidth(); int h = component.getKnobHeight(); int fontWidth = fontMetrics.stringWidth(model.render(model.getAbsoluteMaximum())); int x = 0; if (model.getOrientation() == TwoKnobsSlider.HORIZONTAL) { x = fontWidth/2; //x += w; //06-03 if (model.isPaintCurrentValues()) { int v = fontMetrics.stringWidth(model.render( model.getAbsoluteMinimum())); v = v/2; x += v; } if (model.isPaintEndLabels()) trackRect.setBounds(x, EXTRA, size.width-2*x, h); else //trackRect.setBounds(w/2, EXTRA, size.width-2*w, h); trackRect.setBounds(w/2, EXTRA, size.width-w, h); if (model.isPaintCurrentValues()) { int v = fontMetrics.stringWidth(model.render( model.getAbsoluteMinimum())); v = v/2; x += v; startLabelRect.setBounds(0, trackRect.y, v, trackRect.y); v = fontWidth/2; x += v; endLabelRect.setBounds(trackRect.x+trackRect.width, trackRect.y, v, trackRect.y); } if (model.isPaintTicks()) tickRect = new Rectangle(trackRect.x, trackRect.y+trackRect.height, trackRect.width, trackRect.height); labelRect = new Rectangle(tickRect.x, trackRect.y+trackRect.height+tickRect.height, trackRect.width, fontMetrics.getHeight()+2*BUFFER); } else { int y = fontMetrics.getHeight()/2+h; if (model.isPaintEndLabels()) trackRect.setBounds(w/2, y, w+2*EXTRA, size.height-2*y); else //trackRect.setBounds(x+w-EXTRA, h/2, w+2*EXTRA, // size.height-h-h/2); trackRect.setBounds(w/2, 0, w+2*EXTRA, size.height-h); if (model.isPaintTicks()) tickRect = new Rectangle(trackRect.x+trackRect.width, trackRect.y-h, trackRect.width, trackRect.height); labelRect = new Rectangle(trackRect.x+trackRect.width+ tickRect.width, trackRect.y, fontWidth+2*BUFFER, trackRect.height); } } /** * Creates a new instance. * * @param component The component that owns this uiDelegate. * Mustn't be <code>null</code>. * @param model Reference to the model. Mustn't be <code>null</code>. */ TwoKnobsSliderUI(TwoKnobsSlider component, TwoKnobsSliderModel model) { if (component == null) throw new NullPointerException("No component"); if (model == null) throw new NullPointerException("No model"); this.component = component; this.model = model; initialize(); createThumbImage(); } /** Creates darker thumbnails.*/ void createDarkerImage() { if (!model.allowOverlap()) return; thumbImageDarker = Factory.makeConstrastDecImage(thumbImage); disabledThumbImageDarker = Factory.makeConstrastDecImage(disabledThumbImage); } /** * Returns the width of the image knob. * * @return See above. */ int getKnobWidth() { if (thumbImage == null) return DEFAULT_THUMB_SIZE.width; return thumbImage.getWidth(null); } /** * Returns the height of the image knob. * * @return See above. */ int getKnobHeight() { if (thumbImage == null) return DEFAULT_THUMB_SIZE.height; return thumbImage.getHeight(null); } /** * Sets the color of the font. * * @param c The color to set. */ void setFontColor(Color c) { if (c == null) return; fontColor = c; } /** * Determines the x-coordinate of the knob corresponding to the passed * value. * * @param value The value to map. * @return See above. */ int xPositionForValue(double value) { double min = model.getPartialMinimum(); double max = model.getPartialMaximum(); int trackLength = trackRect.width; double valueRange = (double) max-(double) min; double pixelsPerValue = (double)trackLength/valueRange; int trackLeft = trackRect.x; int trackRight = trackRect.x+trackRect.width-1; int xPosition = trackLeft; xPosition += Math.round(pixelsPerValue*(value-min)); xPosition = Math.max(trackLeft, xPosition); xPosition = Math.min(trackRight, xPosition); return xPosition; } /** * Determines the y-coordinate of the knob corresponding to the passed * value. * * @param value The value to map. * @return See above. */ int yPositionForValue(double value) { double min = model.getPartialMinimum(); double max = model.getPartialMaximum(); int trackLength = trackRect.height; double valueRange = (double) max-(double) min; double pixelsPerValue = (double)trackLength/valueRange; int trackTop = trackRect.y; int trackBottom = trackRect.y+trackRect.height-1; int yPosition= trackTop; yPosition += Math.round(pixelsPerValue*((double) max-value)); yPosition = Math.max(trackTop, yPosition); yPosition = Math.min(trackBottom, yPosition); return yPosition; } /** * Determines the value corresponding to the passed x-coordinate. * * @param xPosition The x-coordinate to map. * @param start Pass <code>true</code> to start calculating from the start, * <code>false</code> to start from the end. * @return See above. */ double xValueForPosition(int xPosition, boolean start) { double value; double minValue = model.getPartialMinimum(); double maxValue = model.getPartialMaximum(); int trackLength = trackRect.width; int trackLeft = trackRect.x; int trackRight = trackRect.x+trackRect.width-1; if (xPosition <= trackLeft) value = minValue; else if (xPosition >= trackRight) value = maxValue; else { int distanceFromTrackLeft = trackRight-xPosition; if (start) distanceFromTrackLeft = xPosition-trackLeft; double valueRange = (double) maxValue-(double) minValue; double valuePerPixel = Math.ceil(valueRange/trackLength*1000)/1000; double valueFromTrackLeft = Math.ceil(distanceFromTrackLeft*valuePerPixel*1000)/1000; if (start) value = minValue+valueFromTrackLeft; else value = maxValue-valueFromTrackLeft; } return value; } /** * Determines the value corresponding to the passed y-coordinate. * * @param yPosition The y-coordinate to map. * @param start Pass <code>true</code> to start calculating from the start, * <code>false</code> to start from the end. * @return See above. */ double yValueForPosition(int yPosition, boolean start) { double value; double minValue = model.getPartialMinimum(); double maxValue = model.getPartialMaximum(); int trackLength = trackRect.height; int trackTop = trackRect.y; int trackBottom = trackRect.y+trackRect.height-1; if (yPosition <= trackTop) value = maxValue; else if (yPosition >= trackBottom) value = minValue; else { int distanceFromTrackTop = trackBottom-yPosition; if (start) distanceFromTrackTop = yPosition-trackTop; double valueRange = (double) maxValue-(double) minValue; double valuePerPixel = Math.ceil(valueRange/trackLength*1000)/1000; double valueFromTrackTop = Math.ceil(distanceFromTrackTop*valuePerPixel*1000)/1000; //value = maxValue-valueFromTrackTop; if (!start) value = minValue+valueFromTrackTop; else value = maxValue-valueFromTrackTop; } return value; } /** * Paints the slider. * * @param g2D The graphics context. * @param size The dimension of the component. */ void paintComponent(Graphics2D g2D, Dimension size) { FontMetrics fontMetrics = g2D.getFontMetrics(); computeRectangles(fontMetrics, size); //Draw the track g2D.setColor(TRACK_COLOR); if (model.getOrientation() == TwoKnobsSlider.HORIZONTAL) paintTrackAndKnobsForHorizSlider(g2D); else paintTrackAndKnobsForVertSlider(g2D); if (model.isPaintTicks()) paintTicks(g2D); if (model.isPaintLabels() || model.isPaintEndLabels()) paintLabels(g2D, fontMetrics); } }