/* * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.plaf.basic; import java.awt.event.*; import java.awt.*; import java.beans.*; import java.util.Dictionary; import java.util.Enumeration; import javax.swing.*; import javax.swing.event.*; import javax.swing.plaf.*; import sun.swing.DefaultLookup; import sun.swing.UIAction; /** * A Basic L&F implementation of SliderUI. * * @author Tom Santos */ public class BasicSliderUI extends SliderUI{ // Old actions forward to an instance of this. private static final Actions SHARED_ACTION = new Actions(); /** Positive scroll */ public static final int POSITIVE_SCROLL = +1; /** Negative scroll */ public static final int NEGATIVE_SCROLL = -1; /** Minimum scroll */ public static final int MIN_SCROLL = -2; /** Maximum scroll */ public static final int MAX_SCROLL = +2; /** Scroll timer */ protected Timer scrollTimer; /** Slider */ protected JSlider slider; /** Focus insets */ protected Insets focusInsets = null; /** Inset cache */ protected Insets insetCache = null; /** Left-to-right cache */ protected boolean leftToRightCache = true; /** Focus rectangle */ protected Rectangle focusRect = null; /** Content rectangle */ protected Rectangle contentRect = null; /** Label rectangle */ protected Rectangle labelRect = null; /** Tick rectangle */ protected Rectangle tickRect = null; /** Track rectangle */ protected Rectangle trackRect = null; /** Thumb rectangle */ protected Rectangle thumbRect = null; /** The distance that the track is from the side of the control */ protected int trackBuffer = 0; private transient boolean isDragging; /** Track listener */ protected TrackListener trackListener; /** Change listener */ protected ChangeListener changeListener; /** Component listener */ protected ComponentListener componentListener; /** Focus listener */ protected FocusListener focusListener; /** Scroll listener */ protected ScrollListener scrollListener; /** Property chane listener */ protected PropertyChangeListener propertyChangeListener; private Handler handler; private int lastValue; // Colors private Color shadowColor; private Color highlightColor; private Color focusColor; /** * Whther or not sameLabelBaselines is up to date. */ private boolean checkedLabelBaselines; /** * Whether or not all the entries in the labeltable have the same * baseline. */ private boolean sameLabelBaselines; /** * Returns the shadow color. * @return the shadow color */ protected Color getShadowColor() { return shadowColor; } /** * Returns the highlight color. * @return the highlight color */ protected Color getHighlightColor() { return highlightColor; } /** * Returns the focus color. * @return the focus color */ protected Color getFocusColor() { return focusColor; } /** * Returns true if the user is dragging the slider. * * @return true if the user is dragging the slider * @since 1.5 */ protected boolean isDragging() { return isDragging; } ///////////////////////////////////////////////////////////////////////////// // ComponentUI Interface Implementation methods ///////////////////////////////////////////////////////////////////////////// /** * Creates a UI. * @param b a component * @return a UI */ public static ComponentUI createUI(JComponent b) { return new BasicSliderUI((JSlider)b); } /** * Constructs a {@code BasicSliderUI}. * @param b a slider */ public BasicSliderUI(JSlider b) { } /** * Installs a UI. * @param c a component */ public void installUI(JComponent c) { slider = (JSlider) c; checkedLabelBaselines = false; slider.setEnabled(slider.isEnabled()); LookAndFeel.installProperty(slider, "opaque", Boolean.TRUE); isDragging = false; trackListener = createTrackListener( slider ); changeListener = createChangeListener( slider ); componentListener = createComponentListener( slider ); focusListener = createFocusListener( slider ); scrollListener = createScrollListener( slider ); propertyChangeListener = createPropertyChangeListener( slider ); installDefaults( slider ); installListeners( slider ); installKeyboardActions( slider ); scrollTimer = new Timer( 100, scrollListener ); scrollTimer.setInitialDelay( 300 ); insetCache = slider.getInsets(); leftToRightCache = BasicGraphicsUtils.isLeftToRight(slider); focusRect = new Rectangle(); contentRect = new Rectangle(); labelRect = new Rectangle(); tickRect = new Rectangle(); trackRect = new Rectangle(); thumbRect = new Rectangle(); lastValue = slider.getValue(); calculateGeometry(); // This figures out where the labels, ticks, track, and thumb are. } /** * Uninstalls a UI. * @param c a component */ public void uninstallUI(JComponent c) { if ( c != slider ) throw new IllegalComponentStateException( this + " was asked to deinstall() " + c + " when it only knows about " + slider + "."); scrollTimer.stop(); scrollTimer = null; uninstallDefaults(slider); uninstallListeners( slider ); uninstallKeyboardActions(slider); insetCache = null; leftToRightCache = true; focusRect = null; contentRect = null; labelRect = null; tickRect = null; trackRect = null; thumbRect = null; trackListener = null; changeListener = null; componentListener = null; focusListener = null; scrollListener = null; propertyChangeListener = null; slider = null; } /** * Installs the defaults. * @param slider a slider */ protected void installDefaults( JSlider slider ) { LookAndFeel.installBorder(slider, "Slider.border"); LookAndFeel.installColorsAndFont(slider, "Slider.background", "Slider.foreground", "Slider.font"); highlightColor = UIManager.getColor("Slider.highlight"); shadowColor = UIManager.getColor("Slider.shadow"); focusColor = UIManager.getColor("Slider.focus"); focusInsets = (Insets)UIManager.get( "Slider.focusInsets" ); // use default if missing so that BasicSliderUI can be used in other // LAFs like Nimbus if (focusInsets == null) focusInsets = new InsetsUIResource(2,2,2,2); } /** * Uninstalls the defaults. * @param slider a slider */ protected void uninstallDefaults(JSlider slider) { LookAndFeel.uninstallBorder(slider); focusInsets = null; } /** * Creates a track listener. * @return a track listener * @param slider a slider */ protected TrackListener createTrackListener(JSlider slider) { return new TrackListener(); } /** * Creates a change listener. * @return a change listener * @param slider a slider */ protected ChangeListener createChangeListener(JSlider slider) { return getHandler(); } /** * Creates a composite listener. * @return a composite listener * @param slider a slider */ protected ComponentListener createComponentListener(JSlider slider) { return getHandler(); } /** * Creates a focus listener. * @return a focus listener * @param slider a slider */ protected FocusListener createFocusListener(JSlider slider) { return getHandler(); } /** * Creates a scroll listener. * @return a scroll listener * @param slider a slider */ protected ScrollListener createScrollListener( JSlider slider ) { return new ScrollListener(); } /** * Creates a property change listener. * @return a property change listener * @param slider a slider */ protected PropertyChangeListener createPropertyChangeListener( JSlider slider) { return getHandler(); } private Handler getHandler() { if (handler == null) { handler = new Handler(); } return handler; } /** * Installs listeners. * @param slider a slider */ protected void installListeners( JSlider slider ) { slider.addMouseListener(trackListener); slider.addMouseMotionListener(trackListener); slider.addFocusListener(focusListener); slider.addComponentListener(componentListener); slider.addPropertyChangeListener( propertyChangeListener ); slider.getModel().addChangeListener(changeListener); } /** * Uninstalls listeners. * @param slider a slider */ protected void uninstallListeners( JSlider slider ) { slider.removeMouseListener(trackListener); slider.removeMouseMotionListener(trackListener); slider.removeFocusListener(focusListener); slider.removeComponentListener(componentListener); slider.removePropertyChangeListener( propertyChangeListener ); slider.getModel().removeChangeListener(changeListener); handler = null; } /** * Installs keyboard actions. * @param slider a slider */ protected void installKeyboardActions( JSlider slider ) { InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider); SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, km); LazyActionMap.installLazyActionMap(slider, BasicSliderUI.class, "Slider.actionMap"); } InputMap getInputMap(int condition, JSlider slider) { if (condition == JComponent.WHEN_FOCUSED) { InputMap keyMap = (InputMap)DefaultLookup.get(slider, this, "Slider.focusInputMap"); InputMap rtlKeyMap; if (slider.getComponentOrientation().isLeftToRight() || ((rtlKeyMap = (InputMap)DefaultLookup.get(slider, this, "Slider.focusInputMap.RightToLeft")) == null)) { return keyMap; } else { rtlKeyMap.setParent(keyMap); return rtlKeyMap; } } return null; } /** * Populates ComboBox's actions. */ static void loadActionMap(LazyActionMap map) { map.put(new Actions(Actions.POSITIVE_UNIT_INCREMENT)); map.put(new Actions(Actions.POSITIVE_BLOCK_INCREMENT)); map.put(new Actions(Actions.NEGATIVE_UNIT_INCREMENT)); map.put(new Actions(Actions.NEGATIVE_BLOCK_INCREMENT)); map.put(new Actions(Actions.MIN_SCROLL_INCREMENT)); map.put(new Actions(Actions.MAX_SCROLL_INCREMENT)); } /** * Uninstalls keyboard actions. * @param slider a slider */ protected void uninstallKeyboardActions( JSlider slider ) { SwingUtilities.replaceUIActionMap(slider, null); SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, null); } /** * Returns the baseline. * * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} * @see javax.swing.JComponent#getBaseline(int, int) * @since 1.6 */ public int getBaseline(JComponent c, int width, int height) { super.getBaseline(c, width, height); if (slider.getPaintLabels() && labelsHaveSameBaselines()) { FontMetrics metrics = slider.getFontMetrics(slider.getFont()); Insets insets = slider.getInsets(); Dimension thumbSize = getThumbSize(); if (slider.getOrientation() == JSlider.HORIZONTAL) { int tickLength = getTickLength(); int contentHeight = height - insets.top - insets.bottom - focusInsets.top - focusInsets.bottom; int thumbHeight = thumbSize.height; int centerSpacing = thumbHeight; if (slider.getPaintTicks()) { centerSpacing += tickLength; } // Assume uniform labels. centerSpacing += getHeightOfTallestLabel(); int trackY = insets.top + focusInsets.top + (contentHeight - centerSpacing - 1) / 2; int trackHeight = thumbHeight; int tickY = trackY + trackHeight; int tickHeight = tickLength; if (!slider.getPaintTicks()) { tickHeight = 0; } int labelY = tickY + tickHeight; return labelY + metrics.getAscent(); } else { // vertical boolean inverted = slider.getInverted(); Integer value = inverted ? getLowestValue() : getHighestValue(); if (value != null) { int thumbHeight = thumbSize.height; int trackBuffer = Math.max(metrics.getHeight() / 2, thumbHeight / 2); int contentY = focusInsets.top + insets.top; int trackY = contentY + trackBuffer; int trackHeight = height - focusInsets.top - focusInsets.bottom - insets.top - insets.bottom - trackBuffer - trackBuffer; int yPosition = yPositionForValue(value, trackY, trackHeight); return yPosition - metrics.getHeight() / 2 + metrics.getAscent(); } } } return 0; } /** * Returns an enum indicating how the baseline of the component * changes as the size changes. * * @throws NullPointerException {@inheritDoc} * @see javax.swing.JComponent#getBaseline(int, int) * @since 1.6 */ public Component.BaselineResizeBehavior getBaselineResizeBehavior( JComponent c) { super.getBaselineResizeBehavior(c); // NOTE: BasicSpinner really provides for CENTER_OFFSET, but // the default min/pref size is smaller than it should be // so that getBaseline() doesn't implement the contract // for CENTER_OFFSET as defined in Component. return Component.BaselineResizeBehavior.OTHER; } /** * Returns true if all the labels from the label table have the same * baseline. * * @return true if all the labels from the label table have the * same baseline * @since 1.6 */ protected boolean labelsHaveSameBaselines() { if (!checkedLabelBaselines) { checkedLabelBaselines = true; @SuppressWarnings("rawtypes") Dictionary dictionary = slider.getLabelTable(); if (dictionary != null) { sameLabelBaselines = true; Enumeration elements = dictionary.elements(); int baseline = -1; while (elements.hasMoreElements()) { JComponent label = (JComponent) elements.nextElement(); Dimension pref = label.getPreferredSize(); int labelBaseline = label.getBaseline(pref.width, pref.height); if (labelBaseline >= 0) { if (baseline == -1) { baseline = labelBaseline; } else if (baseline != labelBaseline) { sameLabelBaselines = false; break; } } else { sameLabelBaselines = false; break; } } } else { sameLabelBaselines = false; } } return sameLabelBaselines; } /** * Returns the preferred horizontal size. * @return the preferred horizontal size */ public Dimension getPreferredHorizontalSize() { Dimension horizDim = (Dimension)DefaultLookup.get(slider, this, "Slider.horizontalSize"); if (horizDim == null) { horizDim = new Dimension(200, 21); } return horizDim; } /** * Returns the preferred vertical size. * @return the preferred vertical size */ public Dimension getPreferredVerticalSize() { Dimension vertDim = (Dimension)DefaultLookup.get(slider, this, "Slider.verticalSize"); if (vertDim == null) { vertDim = new Dimension(21, 200); } return vertDim; } /** * Returns the minimum horizontal size. * @return the minimum horizontal size */ public Dimension getMinimumHorizontalSize() { Dimension minHorizDim = (Dimension)DefaultLookup.get(slider, this, "Slider.minimumHorizontalSize"); if (minHorizDim == null) { minHorizDim = new Dimension(36, 21); } return minHorizDim; } /** * Returns the minimum vertical size. * @return the minimum vertical size */ public Dimension getMinimumVerticalSize() { Dimension minVertDim = (Dimension)DefaultLookup.get(slider, this, "Slider.minimumVerticalSize"); if (minVertDim == null) { minVertDim = new Dimension(21, 36); } return minVertDim; } /** * Returns the preferred size. * @param c a component * @return the preferred size */ public Dimension getPreferredSize(JComponent c) { recalculateIfInsetsChanged(); Dimension d; if ( slider.getOrientation() == JSlider.VERTICAL ) { d = new Dimension(getPreferredVerticalSize()); d.width = insetCache.left + insetCache.right; d.width += focusInsets.left + focusInsets.right; d.width += trackRect.width + tickRect.width + labelRect.width; } else { d = new Dimension(getPreferredHorizontalSize()); d.height = insetCache.top + insetCache.bottom; d.height += focusInsets.top + focusInsets.bottom; d.height += trackRect.height + tickRect.height + labelRect.height; } return d; } /** * Returns the minimum size. * @param c a component * @return the minimum size */ public Dimension getMinimumSize(JComponent c) { recalculateIfInsetsChanged(); Dimension d; if ( slider.getOrientation() == JSlider.VERTICAL ) { d = new Dimension(getMinimumVerticalSize()); d.width = insetCache.left + insetCache.right; d.width += focusInsets.left + focusInsets.right; d.width += trackRect.width + tickRect.width + labelRect.width; } else { d = new Dimension(getMinimumHorizontalSize()); d.height = insetCache.top + insetCache.bottom; d.height += focusInsets.top + focusInsets.bottom; d.height += trackRect.height + tickRect.height + labelRect.height; } return d; } /** * Returns the maximum size. * @param c a component * @return the maximum size */ public Dimension getMaximumSize(JComponent c) { Dimension d = getPreferredSize(c); if ( slider.getOrientation() == JSlider.VERTICAL ) { d.height = Short.MAX_VALUE; } else { d.width = Short.MAX_VALUE; } return d; } /** * Calculates the geometry. */ protected void calculateGeometry() { calculateFocusRect(); calculateContentRect(); calculateThumbSize(); calculateTrackBuffer(); calculateTrackRect(); calculateTickRect(); calculateLabelRect(); calculateThumbLocation(); } /** * Calculates the focus rectangle. */ protected void calculateFocusRect() { focusRect.x = insetCache.left; focusRect.y = insetCache.top; focusRect.width = slider.getWidth() - (insetCache.left + insetCache.right); focusRect.height = slider.getHeight() - (insetCache.top + insetCache.bottom); } /** * Calculates the thumb size rectangle. */ protected void calculateThumbSize() { Dimension size = getThumbSize(); thumbRect.setSize( size.width, size.height ); } /** * Calculates the content rectangle. */ protected void calculateContentRect() { contentRect.x = focusRect.x + focusInsets.left; contentRect.y = focusRect.y + focusInsets.top; contentRect.width = focusRect.width - (focusInsets.left + focusInsets.right); contentRect.height = focusRect.height - (focusInsets.top + focusInsets.bottom); } private int getTickSpacing() { int majorTickSpacing = slider.getMajorTickSpacing(); int minorTickSpacing = slider.getMinorTickSpacing(); int result; if (minorTickSpacing > 0) { result = minorTickSpacing; } else if (majorTickSpacing > 0) { result = majorTickSpacing; } else { result = 0; } return result; } /** * Calculates the thumb location. */ protected void calculateThumbLocation() { if ( slider.getSnapToTicks() ) { int sliderValue = slider.getValue(); int snappedValue = sliderValue; int tickSpacing = getTickSpacing(); if ( tickSpacing != 0 ) { // If it's not on a tick, change the value if ( (sliderValue - slider.getMinimum()) % tickSpacing != 0 ) { float temp = (float)(sliderValue - slider.getMinimum()) / (float)tickSpacing; int whichTick = Math.round( temp ); // This is the fix for the bug #6401380 if (temp - (int)temp == .5 && sliderValue < lastValue) { whichTick --; } snappedValue = slider.getMinimum() + (whichTick * tickSpacing); } if( snappedValue != sliderValue ) { slider.setValue( snappedValue ); } } } if ( slider.getOrientation() == JSlider.HORIZONTAL ) { int valuePosition = xPositionForValue(slider.getValue()); thumbRect.x = valuePosition - (thumbRect.width / 2); thumbRect.y = trackRect.y; } else { int valuePosition = yPositionForValue(slider.getValue()); thumbRect.x = trackRect.x; thumbRect.y = valuePosition - (thumbRect.height / 2); } } /** * Calculates the track buffer. */ protected void calculateTrackBuffer() { if ( slider.getPaintLabels() && slider.getLabelTable() != null ) { Component highLabel = getHighestValueLabel(); Component lowLabel = getLowestValueLabel(); if ( slider.getOrientation() == JSlider.HORIZONTAL ) { trackBuffer = Math.max( highLabel.getBounds().width, lowLabel.getBounds().width ) / 2; trackBuffer = Math.max( trackBuffer, thumbRect.width / 2 ); } else { trackBuffer = Math.max( highLabel.getBounds().height, lowLabel.getBounds().height ) / 2; trackBuffer = Math.max( trackBuffer, thumbRect.height / 2 ); } } else { if ( slider.getOrientation() == JSlider.HORIZONTAL ) { trackBuffer = thumbRect.width / 2; } else { trackBuffer = thumbRect.height / 2; } } } /** * Calculates the track rectangle. */ protected void calculateTrackRect() { int centerSpacing; // used to center sliders added using BorderLayout.CENTER (bug 4275631) if ( slider.getOrientation() == JSlider.HORIZONTAL ) { centerSpacing = thumbRect.height; if ( slider.getPaintTicks() ) centerSpacing += getTickLength(); if ( slider.getPaintLabels() ) centerSpacing += getHeightOfTallestLabel(); trackRect.x = contentRect.x + trackBuffer; trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1)/2; trackRect.width = contentRect.width - (trackBuffer * 2); trackRect.height = thumbRect.height; } else { centerSpacing = thumbRect.width; if (BasicGraphicsUtils.isLeftToRight(slider)) { if ( slider.getPaintTicks() ) centerSpacing += getTickLength(); if ( slider.getPaintLabels() ) centerSpacing += getWidthOfWidestLabel(); } else { if ( slider.getPaintTicks() ) centerSpacing -= getTickLength(); if ( slider.getPaintLabels() ) centerSpacing -= getWidthOfWidestLabel(); } trackRect.x = contentRect.x + (contentRect.width - centerSpacing - 1)/2; trackRect.y = contentRect.y + trackBuffer; trackRect.width = thumbRect.width; trackRect.height = contentRect.height - (trackBuffer * 2); } } /** * Gets the height of the tick area for horizontal sliders and the width of * the tick area for vertical sliders. BasicSliderUI uses the returned value * to determine the tick area rectangle. If you want to give your ticks some * room, make this larger than you need and paint your ticks away from the * sides in paintTicks(). * * @return an integer representing the height of the tick area for * horizontal sliders, and the width of the tick area for the vertical * sliders */ protected int getTickLength() { return 8; } /** * Calculates the tick rectangle. */ protected void calculateTickRect() { if ( slider.getOrientation() == JSlider.HORIZONTAL ) { tickRect.x = trackRect.x; tickRect.y = trackRect.y + trackRect.height; tickRect.width = trackRect.width; tickRect.height = (slider.getPaintTicks()) ? getTickLength() : 0; } else { tickRect.width = (slider.getPaintTicks()) ? getTickLength() : 0; if(BasicGraphicsUtils.isLeftToRight(slider)) { tickRect.x = trackRect.x + trackRect.width; } else { tickRect.x = trackRect.x - tickRect.width; } tickRect.y = trackRect.y; tickRect.height = trackRect.height; } } /** * Calculates the label rectangle. */ protected void calculateLabelRect() { if ( slider.getPaintLabels() ) { if ( slider.getOrientation() == JSlider.HORIZONTAL ) { labelRect.x = tickRect.x - trackBuffer; labelRect.y = tickRect.y + tickRect.height; labelRect.width = tickRect.width + (trackBuffer * 2); labelRect.height = getHeightOfTallestLabel(); } else { if(BasicGraphicsUtils.isLeftToRight(slider)) { labelRect.x = tickRect.x + tickRect.width; labelRect.width = getWidthOfWidestLabel(); } else { labelRect.width = getWidthOfWidestLabel(); labelRect.x = tickRect.x - labelRect.width; } labelRect.y = tickRect.y - trackBuffer; labelRect.height = tickRect.height + (trackBuffer * 2); } } else { if ( slider.getOrientation() == JSlider.HORIZONTAL ) { labelRect.x = tickRect.x; labelRect.y = tickRect.y + tickRect.height; labelRect.width = tickRect.width; labelRect.height = 0; } else { if(BasicGraphicsUtils.isLeftToRight(slider)) { labelRect.x = tickRect.x + tickRect.width; } else { labelRect.x = tickRect.x; } labelRect.y = tickRect.y; labelRect.width = 0; labelRect.height = tickRect.height; } } } /** * Returns the thumb size. * @return the thumb size */ protected Dimension getThumbSize() { Dimension size = new Dimension(); if ( slider.getOrientation() == JSlider.VERTICAL ) { size.width = 20; size.height = 11; } else { size.width = 11; size.height = 20; } return size; } /** * A property change handler. */ public class PropertyChangeHandler implements PropertyChangeListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. /** {@inheritDoc} */ public void propertyChange( PropertyChangeEvent e ) { getHandler().propertyChange(e); } } /** * Returns the width of the widest label. * @return the width of the widest label */ protected int getWidthOfWidestLabel() { @SuppressWarnings("rawtypes") Dictionary dictionary = slider.getLabelTable(); int widest = 0; if ( dictionary != null ) { Enumeration keys = dictionary.keys(); while ( keys.hasMoreElements() ) { JComponent label = (JComponent) dictionary.get(keys.nextElement()); widest = Math.max( label.getPreferredSize().width, widest ); } } return widest; } /** * Returns the height of the tallest label. * @return the height of the tallest label */ protected int getHeightOfTallestLabel() { @SuppressWarnings("rawtypes") Dictionary dictionary = slider.getLabelTable(); int tallest = 0; if ( dictionary != null ) { Enumeration keys = dictionary.keys(); while ( keys.hasMoreElements() ) { JComponent label = (JComponent) dictionary.get(keys.nextElement()); tallest = Math.max( label.getPreferredSize().height, tallest ); } } return tallest; } /** * Returns the width of the highest value label. * @return the width of the highest value label */ protected int getWidthOfHighValueLabel() { Component label = getHighestValueLabel(); int width = 0; if ( label != null ) { width = label.getPreferredSize().width; } return width; } /** * Returns the width of the lowest value label. * @return the width of the lowest value label */ protected int getWidthOfLowValueLabel() { Component label = getLowestValueLabel(); int width = 0; if ( label != null ) { width = label.getPreferredSize().width; } return width; } /** * Returns the height of the highest value label. * @return the height of the highest value label */ protected int getHeightOfHighValueLabel() { Component label = getHighestValueLabel(); int height = 0; if ( label != null ) { height = label.getPreferredSize().height; } return height; } /** * Returns the height of the lowest value label. * @return the height of the lowest value label */ protected int getHeightOfLowValueLabel() { Component label = getLowestValueLabel(); int height = 0; if ( label != null ) { height = label.getPreferredSize().height; } return height; } /** * Draws inverted. * @return the inverted-ness */ protected boolean drawInverted() { if (slider.getOrientation()==JSlider.HORIZONTAL) { if(BasicGraphicsUtils.isLeftToRight(slider)) { return slider.getInverted(); } else { return !slider.getInverted(); } } else { return slider.getInverted(); } } /** * Returns the biggest value that has an entry in the label table. * * @return biggest value that has an entry in the label table, or * null. * @since 1.6 */ protected Integer getHighestValue() { @SuppressWarnings("rawtypes") Dictionary dictionary = slider.getLabelTable(); if (dictionary == null) { return null; } Enumeration keys = dictionary.keys(); Integer max = null; while (keys.hasMoreElements()) { Integer i = (Integer) keys.nextElement(); if (max == null || i > max) { max = i; } } return max; } /** * Returns the smallest value that has an entry in the label table. * * @return smallest value that has an entry in the label table, or * null. * @since 1.6 */ protected Integer getLowestValue() { @SuppressWarnings("rawtypes") Dictionary dictionary = slider.getLabelTable(); if (dictionary == null) { return null; } Enumeration keys = dictionary.keys(); Integer min = null; while (keys.hasMoreElements()) { Integer i = (Integer) keys.nextElement(); if (min == null || i < min) { min = i; } } return min; } /** * Returns the label that corresponds to the highest slider value in the * label table. * * @return the label that corresponds to the highest slider value in the * label table * @see JSlider#setLabelTable */ protected Component getLowestValueLabel() { Integer min = getLowestValue(); if (min != null) { return (Component)slider.getLabelTable().get(min); } return null; } /** * Returns the label that corresponds to the lowest slider value in the * label table. * * @return the label that corresponds to the lowest slider value in the * label table * @see JSlider#setLabelTable */ protected Component getHighestValueLabel() { Integer max = getHighestValue(); if (max != null) { return (Component)slider.getLabelTable().get(max); } return null; } public void paint( Graphics g, JComponent c ) { recalculateIfInsetsChanged(); recalculateIfOrientationChanged(); Rectangle clip = g.getClipBounds(); if ( !clip.intersects(trackRect) && slider.getPaintTrack()) calculateGeometry(); if ( slider.getPaintTrack() && clip.intersects( trackRect ) ) { paintTrack( g ); } if ( slider.getPaintTicks() && clip.intersects( tickRect ) ) { paintTicks( g ); } if ( slider.getPaintLabels() && clip.intersects( labelRect ) ) { paintLabels( g ); } if ( slider.hasFocus() && clip.intersects( focusRect ) ) { paintFocus( g ); } if ( clip.intersects( thumbRect ) ) { paintThumb( g ); } } /** * Recalculates if the insets have changed. */ protected void recalculateIfInsetsChanged() { Insets newInsets = slider.getInsets(); if ( !newInsets.equals( insetCache ) ) { insetCache = newInsets; calculateGeometry(); } } /** * Recalculates if the orientation has changed. */ protected void recalculateIfOrientationChanged() { boolean ltr = BasicGraphicsUtils.isLeftToRight(slider); if ( ltr!=leftToRightCache ) { leftToRightCache = ltr; calculateGeometry(); } } /** * Paints focus. * @param g the graphics */ public void paintFocus(Graphics g) { g.setColor( getFocusColor() ); BasicGraphicsUtils.drawDashedRect( g, focusRect.x, focusRect.y, focusRect.width, focusRect.height ); } /** * Paints track. * @param g the graphics */ public void paintTrack(Graphics g) { Rectangle trackBounds = trackRect; if ( slider.getOrientation() == JSlider.HORIZONTAL ) { int cy = (trackBounds.height / 2) - 2; int cw = trackBounds.width; g.translate(trackBounds.x, trackBounds.y + cy); g.setColor(getShadowColor()); g.drawLine(0, 0, cw - 1, 0); g.drawLine(0, 1, 0, 2); g.setColor(getHighlightColor()); g.drawLine(0, 3, cw, 3); g.drawLine(cw, 0, cw, 3); g.setColor(Color.black); g.drawLine(1, 1, cw-2, 1); g.translate(-trackBounds.x, -(trackBounds.y + cy)); } else { int cx = (trackBounds.width / 2) - 2; int ch = trackBounds.height; g.translate(trackBounds.x + cx, trackBounds.y); g.setColor(getShadowColor()); g.drawLine(0, 0, 0, ch - 1); g.drawLine(1, 0, 2, 0); g.setColor(getHighlightColor()); g.drawLine(3, 0, 3, ch); g.drawLine(0, ch, 3, ch); g.setColor(Color.black); g.drawLine(1, 1, 1, ch-2); g.translate(-(trackBounds.x + cx), -trackBounds.y); } } /** * Paints ticks. * @param g the graphics */ public void paintTicks(Graphics g) { Rectangle tickBounds = tickRect; g.setColor(DefaultLookup.getColor(slider, this, "Slider.tickColor", Color.black)); if ( slider.getOrientation() == JSlider.HORIZONTAL ) { g.translate(0, tickBounds.y); if (slider.getMinorTickSpacing() > 0) { int value = slider.getMinimum(); while ( value <= slider.getMaximum() ) { int xPos = xPositionForValue(value); paintMinorTickForHorizSlider( g, tickBounds, xPos ); // Overflow checking if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) { break; } value += slider.getMinorTickSpacing(); } } if (slider.getMajorTickSpacing() > 0) { int value = slider.getMinimum(); while ( value <= slider.getMaximum() ) { int xPos = xPositionForValue(value); paintMajorTickForHorizSlider( g, tickBounds, xPos ); // Overflow checking if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) { break; } value += slider.getMajorTickSpacing(); } } g.translate( 0, -tickBounds.y); } else { g.translate(tickBounds.x, 0); if (slider.getMinorTickSpacing() > 0) { int offset = 0; if(!BasicGraphicsUtils.isLeftToRight(slider)) { offset = tickBounds.width - tickBounds.width / 2; g.translate(offset, 0); } int value = slider.getMinimum(); while (value <= slider.getMaximum()) { int yPos = yPositionForValue(value); paintMinorTickForVertSlider( g, tickBounds, yPos ); // Overflow checking if (Integer.MAX_VALUE - slider.getMinorTickSpacing() < value) { break; } value += slider.getMinorTickSpacing(); } if(!BasicGraphicsUtils.isLeftToRight(slider)) { g.translate(-offset, 0); } } if (slider.getMajorTickSpacing() > 0) { if(!BasicGraphicsUtils.isLeftToRight(slider)) { g.translate(2, 0); } int value = slider.getMinimum(); while (value <= slider.getMaximum()) { int yPos = yPositionForValue(value); paintMajorTickForVertSlider( g, tickBounds, yPos ); // Overflow checking if (Integer.MAX_VALUE - slider.getMajorTickSpacing() < value) { break; } value += slider.getMajorTickSpacing(); } if(!BasicGraphicsUtils.isLeftToRight(slider)) { g.translate(-2, 0); } } g.translate(-tickBounds.x, 0); } } /** * Paints minor tick for horizontal slider. * @param g the graphics * @param tickBounds the tick bounds * @param x the x coordinate */ protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) { g.drawLine( x, 0, x, tickBounds.height / 2 - 1 ); } /** * Paints major tick for horizontal slider. * @param g the graphics * @param tickBounds the tick bounds * @param x the x coordinate */ protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) { g.drawLine( x, 0, x, tickBounds.height - 2 ); } /** * Paints minor tick for vertical slider. * @param g the graphics * @param tickBounds the tick bounds * @param y the y coordinate */ protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) { g.drawLine( 0, y, tickBounds.width / 2 - 1, y ); } /** * Paints major tick for vertical slider. * @param g the graphics * @param tickBounds the tick bounds * @param y the y coordinate */ protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) { g.drawLine( 0, y, tickBounds.width - 2, y ); } /** * Paints the labels. * @param g the graphics */ public void paintLabels( Graphics g ) { Rectangle labelBounds = labelRect; @SuppressWarnings("rawtypes") Dictionary dictionary = slider.getLabelTable(); if ( dictionary != null ) { Enumeration keys = dictionary.keys(); int minValue = slider.getMinimum(); int maxValue = slider.getMaximum(); boolean enabled = slider.isEnabled(); while ( keys.hasMoreElements() ) { Integer key = (Integer)keys.nextElement(); int value = key.intValue(); if (value >= minValue && value <= maxValue) { JComponent label = (JComponent) dictionary.get(key); label.setEnabled(enabled); if (label instanceof JLabel) { Icon icon = label.isEnabled() ? ((JLabel) label).getIcon() : ((JLabel) label).getDisabledIcon(); if (icon instanceof ImageIcon) { // Register Slider as an image observer. It allows to catch notifications about // image changes (e.g. gif animation) Toolkit.getDefaultToolkit().checkImage(((ImageIcon) icon).getImage(), -1, -1, slider); } } if ( slider.getOrientation() == JSlider.HORIZONTAL ) { g.translate( 0, labelBounds.y ); paintHorizontalLabel( g, value, label ); g.translate( 0, -labelBounds.y ); } else { int offset = 0; if (!BasicGraphicsUtils.isLeftToRight(slider)) { offset = labelBounds.width - label.getPreferredSize().width; } g.translate( labelBounds.x + offset, 0 ); paintVerticalLabel( g, value, label ); g.translate( -labelBounds.x - offset, 0 ); } } } } } /** * Called for every label in the label table. Used to draw the labels for * horizontal sliders. The graphics have been translated to labelRect.y * already. * * @param g the graphics context in which to paint * @param value the value of the slider * @param label the component label in the label table that needs to be * painted * @see JSlider#setLabelTable */ protected void paintHorizontalLabel( Graphics g, int value, Component label ) { int labelCenter = xPositionForValue( value ); int labelLeft = labelCenter - (label.getPreferredSize().width / 2); g.translate( labelLeft, 0 ); label.paint( g ); g.translate( -labelLeft, 0 ); } /** * Called for every label in the label table. Used to draw the labels for * vertical sliders. The graphics have been translated to labelRect.x * already. * * @param g the graphics context in which to paint * @param value the value of the slider * @param label the component label in the label table that needs to be * painted * @see JSlider#setLabelTable */ protected void paintVerticalLabel( Graphics g, int value, Component label ) { int labelCenter = yPositionForValue( value ); int labelTop = labelCenter - (label.getPreferredSize().height / 2); g.translate( 0, labelTop ); label.paint( g ); g.translate( 0, -labelTop ); } /** * Paints the thumb. * @param g the graphics */ public void paintThumb(Graphics g) { Rectangle knobBounds = thumbRect; int w = knobBounds.width; int h = knobBounds.height; g.translate(knobBounds.x, knobBounds.y); if ( slider.isEnabled() ) { g.setColor(slider.getBackground()); } else { g.setColor(slider.getBackground().darker()); } Boolean paintThumbArrowShape = (Boolean)slider.getClientProperty("Slider.paintThumbArrowShape"); if ((!slider.getPaintTicks() && paintThumbArrowShape == null) || paintThumbArrowShape == Boolean.FALSE) { // "plain" version g.fillRect(0, 0, w, h); g.setColor(Color.black); g.drawLine(0, h-1, w-1, h-1); g.drawLine(w-1, 0, w-1, h-1); g.setColor(highlightColor); g.drawLine(0, 0, 0, h-2); g.drawLine(1, 0, w-2, 0); g.setColor(shadowColor); g.drawLine(1, h-2, w-2, h-2); g.drawLine(w-2, 1, w-2, h-3); } else if ( slider.getOrientation() == JSlider.HORIZONTAL ) { int cw = w / 2; g.fillRect(1, 1, w-3, h-1-cw); Polygon p = new Polygon(); p.addPoint(1, h-cw); p.addPoint(cw-1, h-1); p.addPoint(w-2, h-1-cw); g.fillPolygon(p); g.setColor(highlightColor); g.drawLine(0, 0, w-2, 0); g.drawLine(0, 1, 0, h-1-cw); g.drawLine(0, h-cw, cw-1, h-1); g.setColor(Color.black); g.drawLine(w-1, 0, w-1, h-2-cw); g.drawLine(w-1, h-1-cw, w-1-cw, h-1); g.setColor(shadowColor); g.drawLine(w-2, 1, w-2, h-2-cw); g.drawLine(w-2, h-1-cw, w-1-cw, h-2); } else { // vertical int cw = h / 2; if(BasicGraphicsUtils.isLeftToRight(slider)) { g.fillRect(1, 1, w-1-cw, h-3); Polygon p = new Polygon(); p.addPoint(w-cw-1, 0); p.addPoint(w-1, cw); p.addPoint(w-1-cw, h-2); g.fillPolygon(p); g.setColor(highlightColor); g.drawLine(0, 0, 0, h - 2); // left g.drawLine(1, 0, w-1-cw, 0); // top g.drawLine(w-cw-1, 0, w-1, cw); // top slant g.setColor(Color.black); g.drawLine(0, h-1, w-2-cw, h-1); // bottom g.drawLine(w-1-cw, h-1, w-1, h-1-cw); // bottom slant g.setColor(shadowColor); g.drawLine(1, h-2, w-2-cw, h-2 ); // bottom g.drawLine(w-1-cw, h-2, w-2, h-cw-1 ); // bottom slant } else { g.fillRect(5, 1, w-1-cw, h-3); Polygon p = new Polygon(); p.addPoint(cw, 0); p.addPoint(0, cw); p.addPoint(cw, h-2); g.fillPolygon(p); g.setColor(highlightColor); g.drawLine(cw-1, 0, w-2, 0); // top g.drawLine(0, cw, cw, 0); // top slant g.setColor(Color.black); g.drawLine(0, h-1-cw, cw, h-1 ); // bottom slant g.drawLine(cw, h-1, w-1, h-1); // bottom g.setColor(shadowColor); g.drawLine(cw, h-2, w-2, h-2 ); // bottom g.drawLine(w-1, 1, w-1, h-2 ); // right } } g.translate(-knobBounds.x, -knobBounds.y); } // Used exclusively by setThumbLocation() private static Rectangle unionRect = new Rectangle(); /** * Sets the thumb location. * @param x the x coordinate * @param y the y coordinate */ public void setThumbLocation(int x, int y) { unionRect.setBounds( thumbRect ); thumbRect.setLocation( x, y ); SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, unionRect ); slider.repaint( unionRect.x, unionRect.y, unionRect.width, unionRect.height ); } /** * Scrolls by block. * @param direction the direction */ public void scrollByBlock(int direction) { synchronized(slider) { int blockIncrement = (slider.getMaximum() - slider.getMinimum()) / 10; if (blockIncrement == 0) { blockIncrement = 1; } int tickSpacing = getTickSpacing(); if (slider.getSnapToTicks()) { if (blockIncrement < tickSpacing) { blockIncrement = tickSpacing; } } else { if (tickSpacing > 0) { blockIncrement = tickSpacing; } } int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); slider.setValue(slider.getValue() + delta); } } /** * Scrolls by unit. * @param direction the direction */ public void scrollByUnit(int direction) { synchronized(slider) { int delta = ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); if (slider.getSnapToTicks()) { delta *= getTickSpacing(); } slider.setValue(slider.getValue() + delta); } } /** * This function is called when a mousePressed was detected in the track, * not in the thumb. The default behavior is to scroll by block. You can * override this method to stop it from scrolling or to add additional * behavior. * * @param dir the direction and number of blocks to scroll */ protected void scrollDueToClickInTrack( int dir ) { scrollByBlock( dir ); } /** * Returns the x position for a value. * @param value the value * @return the x position for a value */ protected int xPositionForValue( int value ) { int min = slider.getMinimum(); int max = slider.getMaximum(); 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; if ( !drawInverted() ) { xPosition = trackLeft; xPosition += Math.round( pixelsPerValue * ((double)value - min) ); } else { xPosition = trackRight; xPosition -= Math.round( pixelsPerValue * ((double)value - min) ); } xPosition = Math.max( trackLeft, xPosition ); xPosition = Math.min( trackRight, xPosition ); return xPosition; } /** * Returns the y position for a value. * @param value the value * @return the y position for a value */ protected int yPositionForValue( int value ) { return yPositionForValue(value, trackRect.y, trackRect.height); } /** * Returns the y location for the specified value. No checking is * done on the arguments. In particular if trackHeight is * negative undefined results may occur. * * @param value the slider value to get the location for * @param trackY y-origin of the track * @param trackHeight the height of the track * @return the y location for the specified value of the slider * @since 1.6 */ protected int yPositionForValue(int value, int trackY, int trackHeight) { int min = slider.getMinimum(); int max = slider.getMaximum(); double valueRange = (double)max - (double)min; double pixelsPerValue = (double)trackHeight / valueRange; int trackBottom = trackY + (trackHeight - 1); int yPosition; if ( !drawInverted() ) { yPosition = trackY; yPosition += Math.round( pixelsPerValue * ((double)max - value ) ); } else { yPosition = trackY; yPosition += Math.round( pixelsPerValue * ((double)value - min) ); } yPosition = Math.max( trackY, yPosition ); yPosition = Math.min( trackBottom, yPosition ); return yPosition; } /** * Returns the value at the y position. If {@code yPos} is beyond the * track at the bottom or the top, this method sets the value to either * the minimum or maximum value of the slider, depending on if the slider * is inverted or not. * * @param yPos the location of the slider along the y axis * @return the value at the y position */ public int valueForYPosition( int yPos ) { int value; final int minValue = slider.getMinimum(); final int maxValue = slider.getMaximum(); final int trackLength = trackRect.height; final int trackTop = trackRect.y; final int trackBottom = trackRect.y + (trackRect.height - 1); if ( yPos <= trackTop ) { value = drawInverted() ? minValue : maxValue; } else if ( yPos >= trackBottom ) { value = drawInverted() ? maxValue : minValue; } else { int distanceFromTrackTop = yPos - trackTop; double valueRange = (double)maxValue - (double)minValue; double valuePerPixel = valueRange / (double)trackLength; int valueFromTrackTop = (int)Math.round( distanceFromTrackTop * valuePerPixel ); value = drawInverted() ? minValue + valueFromTrackTop : maxValue - valueFromTrackTop; } return value; } /** * Returns the value at the x position. If {@code xPos} is beyond the * track at the left or the right, this method sets the value to either the * minimum or maximum value of the slider, depending on if the slider is * inverted or not. * * @param xPos the location of the slider along the x axis * @return the value of the x position */ public int valueForXPosition( int xPos ) { int value; final int minValue = slider.getMinimum(); final int maxValue = slider.getMaximum(); final int trackLength = trackRect.width; final int trackLeft = trackRect.x; final int trackRight = trackRect.x + (trackRect.width - 1); if ( xPos <= trackLeft ) { value = drawInverted() ? maxValue : minValue; } else if ( xPos >= trackRight ) { value = drawInverted() ? minValue : maxValue; } else { int distanceFromTrackLeft = xPos - trackLeft; double valueRange = (double)maxValue - (double)minValue; double valuePerPixel = valueRange / (double)trackLength; int valueFromTrackLeft = (int)Math.round( distanceFromTrackLeft * valuePerPixel ); value = drawInverted() ? maxValue - valueFromTrackLeft : minValue + valueFromTrackLeft; } return value; } private class Handler implements ChangeListener, ComponentListener, FocusListener, PropertyChangeListener { // Change Handler public void stateChanged(ChangeEvent e) { if (!isDragging) { calculateThumbLocation(); slider.repaint(); } lastValue = slider.getValue(); } // Component Handler public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { calculateGeometry(); slider.repaint(); } public void componentShown(ComponentEvent e) { } // Focus Handler public void focusGained(FocusEvent e) { slider.repaint(); } public void focusLost(FocusEvent e) { slider.repaint(); } // Property Change Handler public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if (propertyName == "orientation" || propertyName == "inverted" || propertyName == "labelTable" || propertyName == "majorTickSpacing" || propertyName == "minorTickSpacing" || propertyName == "paintTicks" || propertyName == "paintTrack" || propertyName == "font" || propertyName == "graphicsConfiguration" || propertyName == "paintLabels" || propertyName == "Slider.paintThumbArrowShape") { checkedLabelBaselines = false; calculateGeometry(); slider.repaint(); } else if (propertyName == "componentOrientation") { calculateGeometry(); slider.repaint(); InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider); SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, km); } else if (propertyName == "model") { ((BoundedRangeModel)e.getOldValue()).removeChangeListener( changeListener); ((BoundedRangeModel)e.getNewValue()).addChangeListener( changeListener); calculateThumbLocation(); slider.repaint(); } } } ///////////////////////////////////////////////////////////////////////// /// Model Listener Class ///////////////////////////////////////////////////////////////////////// /** * Data model listener. * * This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of Foo. */ public class ChangeHandler implements ChangeListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void stateChanged(ChangeEvent e) { getHandler().stateChanged(e); } } ///////////////////////////////////////////////////////////////////////// /// Track Listener Class ///////////////////////////////////////////////////////////////////////// /** * Track mouse movements. * * This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of Foo. */ public class TrackListener extends MouseInputAdapter { /** The offset */ protected transient int offset; /** Current mouse x. */ protected transient int currentMouseX; /** Current mouse y. */ protected transient int currentMouseY; /** * {@inheritDoc} */ public void mouseReleased(MouseEvent e) { if (!slider.isEnabled()) { return; } offset = 0; scrollTimer.stop(); isDragging = false; slider.setValueIsAdjusting(false); slider.repaint(); } /** * If the mouse is pressed above the "thumb" component * then reduce the scrollbars value by one page ("page up"), * otherwise increase it by one page. If there is no * thumb then page up if the mouse is in the upper half * of the track. */ public void mousePressed(MouseEvent e) { if (!slider.isEnabled()) { return; } // We should recalculate geometry just before // calculation of the thumb movement direction. // It is important for the case, when JSlider // is a cell editor in JTable. See 6348946. calculateGeometry(); currentMouseX = e.getX(); currentMouseY = e.getY(); if (slider.isRequestFocusEnabled()) { slider.requestFocus(); } // Clicked in the Thumb area? if (thumbRect.contains(currentMouseX, currentMouseY)) { if (UIManager.getBoolean("Slider.onlyLeftMouseButtonDrag") && !SwingUtilities.isLeftMouseButton(e)) { return; } switch (slider.getOrientation()) { case JSlider.VERTICAL: offset = currentMouseY - thumbRect.y; break; case JSlider.HORIZONTAL: offset = currentMouseX - thumbRect.x; break; } isDragging = true; return; } if (!SwingUtilities.isLeftMouseButton(e)) { return; } isDragging = false; slider.setValueIsAdjusting(true); Dimension sbSize = slider.getSize(); int direction = POSITIVE_SCROLL; switch (slider.getOrientation()) { case JSlider.VERTICAL: if ( thumbRect.isEmpty() ) { int scrollbarCenter = sbSize.height / 2; if ( !drawInverted() ) { direction = (currentMouseY < scrollbarCenter) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; } else { direction = (currentMouseY < scrollbarCenter) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; } } else { int thumbY = thumbRect.y; if ( !drawInverted() ) { direction = (currentMouseY < thumbY) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; } else { direction = (currentMouseY < thumbY) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; } } break; case JSlider.HORIZONTAL: if ( thumbRect.isEmpty() ) { int scrollbarCenter = sbSize.width / 2; if ( !drawInverted() ) { direction = (currentMouseX < scrollbarCenter) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; } else { direction = (currentMouseX < scrollbarCenter) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; } } else { int thumbX = thumbRect.x; if ( !drawInverted() ) { direction = (currentMouseX < thumbX) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; } else { direction = (currentMouseX < thumbX) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; } } break; } if (shouldScroll(direction)) { scrollDueToClickInTrack(direction); } if (shouldScroll(direction)) { scrollTimer.stop(); scrollListener.setDirection(direction); scrollTimer.start(); } } /** * Returns if scrolling should occur * @param direction the direction. * @return if scrolling should occur */ public boolean shouldScroll(int direction) { Rectangle r = thumbRect; if (slider.getOrientation() == JSlider.VERTICAL) { if (drawInverted() ? direction < 0 : direction > 0) { if (r.y <= currentMouseY) { return false; } } else if (r.y + r.height >= currentMouseY) { return false; } } else { if (drawInverted() ? direction < 0 : direction > 0) { if (r.x + r.width >= currentMouseX) { return false; } } else if (r.x <= currentMouseX) { return false; } } if (direction > 0 && slider.getValue() + slider.getExtent() >= slider.getMaximum()) { return false; } else if (direction < 0 && slider.getValue() <= slider.getMinimum()) { return false; } return true; } /** * Set the models value to the position of the top/left * of the thumb relative to the origin of the track. */ public void mouseDragged(MouseEvent e) { int thumbMiddle; if (!slider.isEnabled()) { return; } currentMouseX = e.getX(); currentMouseY = e.getY(); if (!isDragging) { return; } slider.setValueIsAdjusting(true); switch (slider.getOrientation()) { case JSlider.VERTICAL: int halfThumbHeight = thumbRect.height / 2; int thumbTop = e.getY() - offset; int trackTop = trackRect.y; int trackBottom = trackRect.y + (trackRect.height - 1); int vMax = yPositionForValue(slider.getMaximum() - slider.getExtent()); if (drawInverted()) { trackBottom = vMax; } else { trackTop = vMax; } thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight); thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight); setThumbLocation(thumbRect.x, thumbTop); thumbMiddle = thumbTop + halfThumbHeight; slider.setValue( valueForYPosition( thumbMiddle ) ); break; case JSlider.HORIZONTAL: int halfThumbWidth = thumbRect.width / 2; int thumbLeft = e.getX() - offset; int trackLeft = trackRect.x; int trackRight = trackRect.x + (trackRect.width - 1); int hMax = xPositionForValue(slider.getMaximum() - slider.getExtent()); if (drawInverted()) { trackLeft = hMax; } else { trackRight = hMax; } thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth); thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth); setThumbLocation(thumbLeft, thumbRect.y); thumbMiddle = thumbLeft + halfThumbWidth; slider.setValue(valueForXPosition(thumbMiddle)); break; } } /** {@inheritDoc} */ public void mouseMoved(MouseEvent e) { } } /** * Scroll-event listener. * * This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of Foo. */ public class ScrollListener implements ActionListener { // changed this class to public to avoid bogus IllegalAccessException // bug in InternetExplorer browser. It was protected. Work around // for 4109432 int direction = POSITIVE_SCROLL; boolean useBlockIncrement; /** * Constructs a {@code ScrollListener} */ public ScrollListener() { direction = POSITIVE_SCROLL; useBlockIncrement = true; } /** * Constructs a {@code ScrollListener} * @param dir the direction * @param block whether or not to scroll by block */ public ScrollListener(int dir, boolean block) { direction = dir; useBlockIncrement = block; } /** * Sets the direction. * @param direction the new direction */ public void setDirection(int direction) { this.direction = direction; } /** * Sets scrolling by block * @param block the new scroll by block value */ public void setScrollByBlock(boolean block) { this.useBlockIncrement = block; } /** {@inheritDoc} */ public void actionPerformed(ActionEvent e) { if (useBlockIncrement) { scrollByBlock(direction); } else { scrollByUnit(direction); } if (!trackListener.shouldScroll(direction)) { ((Timer)e.getSource()).stop(); } } } /** * Listener for resizing events. *

* This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of Foo. */ public class ComponentHandler extends ComponentAdapter { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void componentResized(ComponentEvent e) { getHandler().componentResized(e); } } /** * Focus-change listener. *

* This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of Foo. */ public class FocusHandler implements FocusListener { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void focusGained(FocusEvent e) { getHandler().focusGained(e); } public void focusLost(FocusEvent e) { getHandler().focusLost(e); } } /** * As of Java 2 platform v1.3 this undocumented class is no longer used. * The recommended approach to creating bindings is to use a * combination of an ActionMap, to contain the action, * and an InputMap to contain the mapping from KeyStroke * to action description. The InputMap is usually described in the * LookAndFeel tables. *

* Please refer to the key bindings specification for further details. *

* This class should be treated as a "protected" inner class. * Instantiate it only within subclasses of Foo. */ @SuppressWarnings("serial") // Superclass is not serializable across versions public class ActionScroller extends AbstractAction { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Actions. If you need to add // new functionality add it to the Actions, but make sure this // class calls into the Actions. int dir; boolean block; JSlider slider; /** * Constructs an {@code ActionScroller}. * @param slider a slider * @param dir the direction * @param block block scrolling or not */ public ActionScroller( JSlider slider, int dir, boolean block) { this.dir = dir; this.block = block; this.slider = slider; } /** {@inheritDoc} */ public void actionPerformed(ActionEvent e) { SHARED_ACTION.scroll(slider, BasicSliderUI.this, dir, block); } /** {@inheritDoc} */ public boolean isEnabled() { boolean b = true; if (slider != null) { b = slider.isEnabled(); } return b; } } /** * A static version of the above. */ @SuppressWarnings("serial") // Superclass is not serializable across versions static class SharedActionScroller extends AbstractAction { // NOTE: This class exists only for backward compatibility. All // its functionality has been moved into Actions. If you need to add // new functionality add it to the Actions, but make sure this // class calls into the Actions. int dir; boolean block; public SharedActionScroller(int dir, boolean block) { this.dir = dir; this.block = block; } public void actionPerformed(ActionEvent evt) { JSlider slider = (JSlider)evt.getSource(); BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType( slider.getUI(), BasicSliderUI.class); if (ui == null) { return; } SHARED_ACTION.scroll(slider, ui, dir, block); } } private static class Actions extends UIAction { public static final String POSITIVE_UNIT_INCREMENT = "positiveUnitIncrement"; public static final String POSITIVE_BLOCK_INCREMENT = "positiveBlockIncrement"; public static final String NEGATIVE_UNIT_INCREMENT = "negativeUnitIncrement"; public static final String NEGATIVE_BLOCK_INCREMENT = "negativeBlockIncrement"; public static final String MIN_SCROLL_INCREMENT = "minScroll"; public static final String MAX_SCROLL_INCREMENT = "maxScroll"; Actions() { super(null); } public Actions(String name) { super(name); } public void actionPerformed(ActionEvent evt) { JSlider slider = (JSlider)evt.getSource(); BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType( slider.getUI(), BasicSliderUI.class); String name = getName(); if (ui == null) { return; } if (POSITIVE_UNIT_INCREMENT == name) { scroll(slider, ui, POSITIVE_SCROLL, false); } else if (NEGATIVE_UNIT_INCREMENT == name) { scroll(slider, ui, NEGATIVE_SCROLL, false); } else if (POSITIVE_BLOCK_INCREMENT == name) { scroll(slider, ui, POSITIVE_SCROLL, true); } else if (NEGATIVE_BLOCK_INCREMENT == name) { scroll(slider, ui, NEGATIVE_SCROLL, true); } else if (MIN_SCROLL_INCREMENT == name) { scroll(slider, ui, MIN_SCROLL, false); } else if (MAX_SCROLL_INCREMENT == name) { scroll(slider, ui, MAX_SCROLL, false); } } private void scroll(JSlider slider, BasicSliderUI ui, int direction, boolean isBlock) { boolean invert = slider.getInverted(); if (direction == NEGATIVE_SCROLL || direction == POSITIVE_SCROLL) { if (invert) { direction = (direction == POSITIVE_SCROLL) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; } if (isBlock) { ui.scrollByBlock(direction); } else { ui.scrollByUnit(direction); } } else { // MIN or MAX if (invert) { direction = (direction == MIN_SCROLL) ? MAX_SCROLL : MIN_SCROLL; } slider.setValue((direction == MIN_SCROLL) ? slider.getMinimum() : slider.getMaximum()); } } } }