| /* BasicSliderUI.java -- |
| Copyright (C) 2004, 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath 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, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing.plaf.basic; |
| |
| import java.awt.Color; |
| import java.awt.Component; |
| import java.awt.ComponentOrientation; |
| import java.awt.Dimension; |
| import java.awt.Graphics; |
| import java.awt.Insets; |
| import java.awt.Point; |
| import java.awt.Polygon; |
| import java.awt.Rectangle; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.ComponentAdapter; |
| import java.awt.event.ComponentEvent; |
| import java.awt.event.ComponentListener; |
| import java.awt.event.FocusEvent; |
| import java.awt.event.FocusListener; |
| import java.awt.event.MouseEvent; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| |
| import javax.swing.BoundedRangeModel; |
| import javax.swing.JComponent; |
| import javax.swing.JLabel; |
| import javax.swing.JSlider; |
| import javax.swing.SwingUtilities; |
| import javax.swing.Timer; |
| import javax.swing.UIDefaults; |
| import javax.swing.UIManager; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import javax.swing.event.MouseInputAdapter; |
| import javax.swing.plaf.ComponentUI; |
| import javax.swing.plaf.SliderUI; |
| |
| /** |
| * <p> |
| * BasicSliderUI.java This is the UI delegate in the Basic look and feel that |
| * paints JSliders. |
| * </p> |
| * |
| * <p> |
| * The UI delegate keeps track of 6 rectangles that place the various parts of |
| * the JSlider inside the component. |
| * </p> |
| * |
| * <p> |
| * The rectangles are organized as follows: |
| * </p> |
| * <pre> |
| * +-------------------------------------------------------+ <-- focusRect |
| * | | |
| * | +==+-------------------+==+--------------------+==+<------ contentRect |
| * | | | | |<---thumbRect | | | |
| * | | | TRACK | | |<--------- trackRect |
| * | | +-------------------+==+--------------------+ | | |
| * | | | | | | |
| * | | | TICKS GO HERE |<-------- tickRect |
| * | | | | | | |
| * | +==+-------------------------------------------+==+ | |
| * | | | | | | |
| * | | | | |<----- labelRect |
| * | | | LABELS GO HERE | | | |
| * | | | | | | |
| * | | | | | | |
| * | | | | | | |
| * | | | | | | |
| * | | | | | |
| * </pre> |
| * |
| * <p> |
| * The space between the contentRect and the focusRect are the FocusInsets. |
| * </p> |
| * |
| * <p> |
| * The space between the focusRect and the component bounds is the insetCache |
| * which are the component's insets. |
| * </p> |
| * |
| * <p> |
| * The top of the thumb is the top of the contentRect. The trackRect has to be |
| * as tall as the thumb. |
| * </p> |
| * |
| * <p> |
| * The trackRect and tickRect do not start from the left edge of the |
| * focusRect. They are trackBuffer away from each side of the focusRect. This |
| * is so that the thumb has room to move. |
| * </p> |
| * |
| * <p> |
| * The labelRect does start right against the contentRect's left and right |
| * edges and it gets all remaining space. |
| * </p> |
| */ |
| public class BasicSliderUI extends SliderUI |
| { |
| /** |
| * Helper class that listens to the {@link JSlider}'s model for changes. |
| */ |
| public class ChangeHandler implements ChangeListener |
| { |
| /** |
| * Called when the slider's model has been altered. The UI delegate should |
| * recalculate any rectangles that are dependent on the model for their |
| * positions and repaint. |
| * |
| * @param e A static {@link ChangeEvent} passed from the model. |
| */ |
| public void stateChanged(ChangeEvent e) |
| { |
| // Maximum, minimum, and extent values will be taken |
| // care of automatically when the slider is repainted. |
| // Only thing that needs recalculation is the thumb. |
| calculateThumbLocation(); |
| slider.repaint(); |
| } |
| } |
| |
| /** |
| * Helper class that listens for resize events. |
| */ |
| protected class ComponentHandler extends ComponentAdapter |
| { |
| /** |
| * Called when the size of the component changes. The UI delegate should |
| * recalculate any rectangles that are dependent on the model for their |
| * positions and repaint. |
| * |
| * @param e A {@link ComponentEvent}. |
| */ |
| public void componentResized(ComponentEvent e) |
| { |
| calculateGeometry(); |
| |
| slider.revalidate(); |
| slider.repaint(); |
| } |
| } |
| |
| /** |
| * Helper class that listens for focus events. |
| */ |
| public class FocusHandler implements FocusListener |
| { |
| /** |
| * Called when the {@link JSlider} has gained focus. It should repaint |
| * the slider with the focus drawn. |
| * |
| * @param e A {@link FocusEvent}. |
| */ |
| public void focusGained(FocusEvent e) |
| { |
| // FIXME: implement. |
| } |
| |
| /** |
| * Called when the {@link JSlider} has lost focus. It should repaint the |
| * slider without the focus drawn. |
| * |
| * @param e A {@link FocusEvent}. |
| */ |
| public void focusLost(FocusEvent e) |
| { |
| // FIXME: implement. |
| } |
| } |
| |
| /** |
| * Helper class that listens for changes to the properties of the {@link |
| * JSlider}. |
| */ |
| public class PropertyChangeHandler implements PropertyChangeListener |
| { |
| /** |
| * Called when one of the properties change. The UI should recalculate any |
| * rectangles if necessary and repaint. |
| * |
| * @param e A {@link PropertyChangeEvent}. |
| */ |
| public void propertyChange(PropertyChangeEvent e) |
| { |
| // Check for orientation changes. |
| if (e.getPropertyName().equals("orientation")) |
| recalculateIfOrientationChanged(); |
| else if (e.getPropertyName().equals("model")) |
| { |
| BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue(); |
| oldModel.removeChangeListener(changeListener); |
| slider.getModel().addChangeListener(changeListener); |
| calculateThumbLocation(); |
| } |
| |
| // elif the componentOrientation changes (this is a bound property, |
| // just undocumented) we change leftToRightCache. In Sun's |
| // implementation, the LTR cache changes on a repaint. This is strange |
| // since there is no need to do so. We could events here and |
| // update the cache. |
| // elif the border/insets change, we recalculateInsets. |
| slider.repaint(); |
| } |
| } |
| |
| /** |
| * Helper class that listens to our swing timer. This class is responsible |
| * for listening to the timer and moving the thumb in the proper direction |
| * every interval. |
| */ |
| public class ScrollListener implements ActionListener |
| { |
| /** Indicates which direction the thumb should scroll. */ |
| private transient int direction; |
| |
| /** Indicates whether we should scroll in blocks or in units. */ |
| private transient boolean block; |
| |
| /** |
| * Creates a new ScrollListener object. |
| */ |
| public ScrollListener() |
| { |
| direction = POSITIVE_SCROLL; |
| block = false; |
| } |
| |
| /** |
| * Creates a new ScrollListener object. |
| * |
| * @param dir The direction to scroll in. |
| * @param block If movement will be in blocks. |
| */ |
| public ScrollListener(int dir, boolean block) |
| { |
| direction = dir; |
| this.block = block; |
| } |
| |
| /** |
| * Called every time the swing timer reaches its interval. If the thumb |
| * needs to move, then this method will move the thumb one block or unit |
| * in the direction desired. Otherwise, the timer can be stopped. |
| * |
| * @param e An {@link ActionEvent}. |
| */ |
| public void actionPerformed(ActionEvent e) |
| { |
| if (! trackListener.shouldScroll(direction)) |
| { |
| scrollTimer.stop(); |
| return; |
| } |
| |
| if (block) |
| scrollByBlock(direction); |
| else |
| scrollByUnit(direction); |
| } |
| |
| /** |
| * Sets the direction to scroll in. |
| * |
| * @param direction The direction to scroll in. |
| */ |
| public void setDirection(int direction) |
| { |
| this.direction = direction; |
| } |
| |
| /** |
| * Sets whether movement will be in blocks. |
| * |
| * @param block If movement will be in blocks. |
| */ |
| public void setScrollByBlock(boolean block) |
| { |
| this.block = block; |
| } |
| } |
| |
| /** |
| * Helper class that listens for mouse events. |
| */ |
| public class TrackListener extends MouseInputAdapter |
| { |
| /** The current X position of the mouse. */ |
| protected int currentMouseX; |
| |
| /** The current Y position of the mouse. */ |
| protected int currentMouseY; |
| |
| /** |
| * The offset between the current slider value and the cursor's position. |
| */ |
| protected int offset; |
| |
| /** |
| * Called when the mouse has been dragged. This should find the mouse's |
| * current position and adjust the value of the {@link JSlider} |
| * accordingly. |
| * |
| * @param e A {@link MouseEvent} |
| */ |
| public void mouseDragged(MouseEvent e) |
| { |
| currentMouseX = e.getX(); |
| currentMouseY = e.getY(); |
| if (slider.getValueIsAdjusting()) |
| { |
| int value; |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| value = valueForXPosition(currentMouseX) - offset; |
| else |
| value = valueForYPosition(currentMouseY) - offset; |
| |
| slider.setValue(value); |
| } |
| } |
| |
| /** |
| * Called when the mouse has moved over a component but no buttons have |
| * been pressed yet. |
| * |
| * @param e A {@link MouseEvent} |
| */ |
| public void mouseMoved(MouseEvent e) |
| { |
| // Don't care that we're moved unless we're dragging. |
| } |
| |
| /** |
| * Called when the mouse is pressed. When the press occurs on the thumb |
| * itself, the {@link JSlider} should have its value set to where the |
| * mouse was pressed. If the press occurs on the track, then the thumb |
| * should move one block towards the direction of the mouse. |
| * |
| * @param e A {@link MouseEvent} |
| */ |
| public void mousePressed(MouseEvent e) |
| { |
| currentMouseX = e.getX(); |
| currentMouseY = e.getY(); |
| |
| int value; |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| value = valueForXPosition(currentMouseX); |
| else |
| value = valueForYPosition(currentMouseY); |
| |
| if (slider.getSnapToTicks()) |
| value = findClosestTick(value); |
| |
| // If the thumb is hit, then we don't need to set the timers to move it. |
| if (! thumbRect.contains(e.getPoint())) |
| { |
| // The mouse has hit some other part of the slider. |
| // The value moves no matter where in the slider you hit. |
| if (value > slider.getValue()) |
| scrollDueToClickInTrack(POSITIVE_SCROLL); |
| else |
| scrollDueToClickInTrack(NEGATIVE_SCROLL); |
| } |
| else |
| { |
| slider.setValueIsAdjusting(true); |
| offset = value - slider.getValue(); |
| } |
| } |
| |
| /** |
| * Called when the mouse is released. This should stop the timer that |
| * scrolls the thumb. |
| * |
| * @param e A {@link MouseEvent} |
| */ |
| public void mouseReleased(MouseEvent e) |
| { |
| currentMouseX = e.getX(); |
| currentMouseY = e.getY(); |
| |
| if (slider.getValueIsAdjusting()) |
| { |
| slider.setValueIsAdjusting(false); |
| if (slider.getSnapToTicks()) |
| slider.setValue(findClosestTick(slider.getValue())); |
| } |
| if (scrollTimer != null) |
| scrollTimer.stop(); |
| } |
| |
| /** |
| * Indicates whether the thumb should scroll in the given direction. |
| * |
| * @param direction The direction to check. |
| * |
| * @return True if the thumb should move in that direction. |
| */ |
| public boolean shouldScroll(int direction) |
| { |
| int value; |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| value = valueForXPosition(currentMouseX); |
| else |
| value = valueForYPosition(currentMouseY); |
| |
| if (direction == POSITIVE_SCROLL) |
| return (value > slider.getValue()); |
| else |
| return (value < slider.getValue()); |
| } |
| } |
| |
| /** The preferred height of the thumb. */ |
| private transient int thumbHeight; |
| |
| /** The preferred width of the thumb. */ |
| private transient int thumbWidth; |
| |
| /** The preferred height of the tick rectangle. */ |
| private transient int tickHeight; |
| |
| /** Listener for changes from the model. */ |
| protected ChangeListener changeListener; |
| |
| /** Listener for changes to the {@link JSlider}. */ |
| protected PropertyChangeListener propertyChangeListener; |
| |
| /** Listener for the scrollTimer. */ |
| protected ScrollListener scrollListener; |
| |
| /** Listener for component resizing. */ |
| protected ComponentListener componentListener; |
| |
| /** Listener for focus handling. */ |
| protected FocusListener focusListener; |
| |
| /** Listener for mouse events. */ |
| protected TrackListener trackListener; |
| |
| /** The insets between the FocusRectangle and the ContentRectangle. */ |
| protected Insets focusInsets; |
| |
| /** The {@link JSlider}'s insets. */ |
| protected Insets insetCache; |
| |
| /** Rectangle describing content bounds. See diagram above. */ |
| protected Rectangle contentRect; |
| |
| /** Rectangle describing focus bounds. See diagram above. */ |
| protected Rectangle focusRect; |
| |
| /** Rectangle describing the thumb's bounds. See diagram above. */ |
| protected Rectangle thumbRect; |
| |
| /** Rectangle describing the tick bounds. See diagram above. */ |
| protected Rectangle tickRect; |
| |
| /** Rectangle describing the label bounds. See diagram above. */ |
| protected Rectangle labelRect; |
| |
| /** Rectangle describing the track bounds. See diagram above. */ |
| protected Rectangle trackRect; |
| |
| /** FIXME: use this somewhere. */ |
| public static final int MAX_SCROLL = 2; |
| |
| /** FIXME: use this somewhere. */ |
| public static final int MIN_SCROLL = -2; |
| |
| /** A constant describing scrolling towards the minimum. */ |
| public static final int NEGATIVE_SCROLL = -1; |
| |
| /** A constant describing scrolling towards the maximum. */ |
| public static final int POSITIVE_SCROLL = 1; |
| |
| /** The gap between the edges of the contentRect and trackRect. */ |
| protected int trackBuffer; |
| |
| /** Whether this slider is actually drawn left to right. */ |
| protected boolean leftToRightCache; |
| |
| /** A timer that periodically moves the thumb. */ |
| protected Timer scrollTimer; |
| |
| /** A reference to the {@link JSlider} that this UI was created for. */ |
| protected JSlider slider; |
| |
| /** The shadow color. */ |
| private transient Color shadowColor; |
| |
| /** The highlight color. */ |
| private transient Color highlightColor; |
| |
| /** The focus color. */ |
| private transient Color focusColor; |
| |
| /** |
| * Creates a new Basic look and feel Slider UI. |
| * |
| * @param b The {@link JSlider} that this UI was created for. |
| */ |
| public BasicSliderUI(JSlider b) |
| { |
| super(); |
| } |
| |
| /** |
| * Gets the shadow color to be used for this slider. The shadow color is the |
| * color used for drawing the top and left edges of the track. |
| * |
| * @return The shadow color. |
| */ |
| protected Color getShadowColor() |
| { |
| return shadowColor; |
| } |
| |
| /** |
| * Gets the highlight color to be used for this slider. The highlight color |
| * is the color used for drawing the bottom and right edges of the track. |
| * |
| * @return The highlight color. |
| */ |
| protected Color getHighlightColor() |
| { |
| return highlightColor; |
| } |
| |
| /** |
| * Gets the focus color to be used for this slider. The focus color is the |
| * color used for drawing the focus rectangle when the component gains |
| * focus. |
| * |
| * @return The focus color. |
| */ |
| protected Color getFocusColor() |
| { |
| return focusColor; |
| } |
| |
| /** |
| * Factory method to create a BasicSliderUI for the given {@link |
| * JComponent}, which should be a {@link JSlider}. |
| * |
| * @param b The {@link JComponent} a UI is being created for. |
| * |
| * @return A BasicSliderUI for the {@link JComponent}. |
| */ |
| public static ComponentUI createUI(JComponent b) |
| { |
| return new BasicSliderUI((JSlider) b); |
| } |
| |
| /** |
| * Installs and initializes all fields for this UI delegate. Any properties |
| * of the UI that need to be initialized and/or set to defaults will be |
| * done now. It will also install any listeners necessary. |
| * |
| * @param c The {@link JComponent} that is having this UI installed. |
| */ |
| public void installUI(JComponent c) |
| { |
| super.installUI(c); |
| if (c instanceof JSlider) |
| { |
| slider = (JSlider) c; |
| |
| focusRect = new Rectangle(); |
| contentRect = new Rectangle(); |
| thumbRect = new Rectangle(); |
| trackRect = new Rectangle(); |
| tickRect = new Rectangle(); |
| labelRect = new Rectangle(); |
| |
| insetCache = slider.getInsets(); |
| leftToRightCache = ! slider.getInverted(); |
| |
| scrollTimer = new Timer(200, null); |
| scrollTimer.setRepeats(true); |
| |
| installDefaults(slider); |
| installListeners(slider); |
| installKeyboardActions(slider); |
| |
| calculateFocusRect(); |
| |
| calculateContentRect(); |
| calculateThumbSize(); |
| calculateTrackBuffer(); |
| calculateTrackRect(); |
| calculateThumbLocation(); |
| |
| calculateTickRect(); |
| calculateLabelRect(); |
| } |
| } |
| |
| /** |
| * Performs the opposite of installUI. Any properties or resources that need |
| * to be cleaned up will be done now. It will also uninstall any listeners |
| * it has. In addition, any properties of this UI will be nulled. |
| * |
| * @param c The {@link JComponent} that is having this UI uninstalled. |
| */ |
| public void uninstallUI(JComponent c) |
| { |
| super.uninstallUI(c); |
| |
| uninstallKeyboardActions(slider); |
| uninstallListeners(slider); |
| |
| scrollTimer = null; |
| |
| focusRect = null; |
| contentRect = null; |
| thumbRect = null; |
| trackRect = null; |
| tickRect = null; |
| labelRect = null; |
| |
| focusInsets = null; |
| } |
| |
| /** |
| * Initializes any default properties that this UI has from the defaults for |
| * the Basic look and feel. |
| * |
| * @param slider The {@link JSlider} that is having this UI installed. |
| */ |
| protected void installDefaults(JSlider slider) |
| { |
| UIDefaults defaults = UIManager.getLookAndFeelDefaults(); |
| |
| slider.setForeground(defaults.getColor("Slider.foreground")); |
| slider.setBackground(defaults.getColor("Slider.background")); |
| shadowColor = defaults.getColor("Slider.shadow"); |
| highlightColor = defaults.getColor("Slider.highlight"); |
| focusColor = defaults.getColor("Slider.focus"); |
| slider.setBorder(defaults.getBorder("Slider.border")); |
| slider.setOpaque(true); |
| |
| thumbHeight = defaults.getInt("Slider.thumbHeight"); |
| thumbWidth = defaults.getInt("Slider.thumbWidth"); |
| tickHeight = defaults.getInt("Slider.tickHeight"); |
| |
| focusInsets = defaults.getInsets("Slider.focusInsets"); |
| } |
| |
| /** |
| * Creates a new {@link TrackListener}. |
| * |
| * @param slider The {@link JSlider} that this {@link TrackListener} is |
| * created for. |
| * |
| * @return A new {@link TrackListener}. |
| */ |
| protected TrackListener createTrackListener(JSlider slider) |
| { |
| return new TrackListener(); |
| } |
| |
| /** |
| * Creates a new {@link ChangeListener}. |
| * |
| * @param slider The {@link JSlider} that this {@link ChangeListener} is |
| * created for. |
| * |
| * @return A new {@link ChangeListener}. |
| */ |
| protected ChangeListener createChangeListener(JSlider slider) |
| { |
| return new ChangeHandler(); |
| } |
| |
| /** |
| * Creates a new {@link ComponentListener}. |
| * |
| * @param slider The {@link JSlider} that this {@link ComponentListener} is |
| * created for. |
| * |
| * @return A new {@link ComponentListener}. |
| */ |
| protected ComponentListener createComponentListener(JSlider slider) |
| { |
| return new ComponentHandler(); |
| } |
| |
| /** |
| * Creates a new {@link FocusListener}. |
| * |
| * @param slider The {@link JSlider} that this {@link FocusListener} is |
| * created for. |
| * |
| * @return A new {@link FocusListener}. |
| */ |
| protected FocusListener createFocusListener(JSlider slider) |
| { |
| return new FocusHandler(); |
| } |
| |
| /** |
| * Creates a new {@link ScrollListener}. |
| * |
| * @param slider The {@link JSlider} that this {@link ScrollListener} is |
| * created for. |
| * |
| * @return A new {@link ScrollListener}. |
| */ |
| protected ScrollListener createScrollListener(JSlider slider) |
| { |
| return new ScrollListener(); |
| } |
| |
| /** |
| * Creates a new {@link PropertyChangeListener}. |
| * |
| * @param slider The {@link JSlider} that this {@link |
| * PropertyChangeListener} is created for. |
| * |
| * @return A new {@link PropertyChangeListener}. |
| */ |
| protected PropertyChangeListener createPropertyChangeListener(JSlider slider) |
| { |
| return new PropertyChangeHandler(); |
| } |
| |
| /** |
| * Creates and registers all the listeners for this UI delegate. This |
| * includes creating the ScrollListener and registering it to the timer. |
| * |
| * @param slider The {@link JSlider} is having listeners installed. |
| */ |
| protected void installListeners(JSlider slider) |
| { |
| propertyChangeListener = createPropertyChangeListener(slider); |
| componentListener = createComponentListener(slider); |
| trackListener = createTrackListener(slider); |
| focusListener = createFocusListener(slider); |
| changeListener = createChangeListener(slider); |
| scrollListener = createScrollListener(slider); |
| |
| slider.addPropertyChangeListener(propertyChangeListener); |
| slider.addComponentListener(componentListener); |
| slider.addMouseListener(trackListener); |
| slider.addMouseMotionListener(trackListener); |
| slider.addFocusListener(focusListener); |
| slider.getModel().addChangeListener(changeListener); |
| |
| scrollTimer.addActionListener(scrollListener); |
| } |
| |
| /** |
| * Unregisters all the listeners that this UI delegate was using. In |
| * addition, it will also null any listeners that it was using. |
| * |
| * @param slider The {@link JSlider} that is having listeners removed. |
| */ |
| protected void uninstallListeners(JSlider slider) |
| { |
| slider.removePropertyChangeListener(propertyChangeListener); |
| slider.removeComponentListener(componentListener); |
| slider.removeMouseListener(trackListener); |
| slider.removeMouseMotionListener(trackListener); |
| slider.removeFocusListener(focusListener); |
| slider.getModel().removeChangeListener(changeListener); |
| |
| scrollTimer.removeActionListener(scrollListener); |
| |
| propertyChangeListener = null; |
| componentListener = null; |
| trackListener = null; |
| focusListener = null; |
| changeListener = null; |
| scrollListener = null; |
| } |
| |
| /** |
| * Installs any keyboard actions. The list of keys that need to be bound are |
| * listed in Basic look and feel's defaults. |
| * |
| * @param slider The {@link JSlider} that is having keyboard actions |
| * installed. |
| */ |
| protected void installKeyboardActions(JSlider slider) |
| { |
| // FIXME: implement. |
| } |
| |
| /** |
| * Uninstalls any keyboard actions. The list of keys used are listed in |
| * Basic look and feel's defaults. |
| * |
| * @param slider The {@link JSlider} that is having keyboard actions |
| * uninstalled. |
| */ |
| protected void uninstallKeyboardActions(JSlider slider) |
| { |
| // FIXME: implement. |
| } |
| |
| /* XXX: This is all after experimentation with SUN's implementation. |
| |
| PreferredHorizontalSize seems to be 200x21. |
| PreferredVerticalSize seems to be 21x200. |
| |
| MinimumHorizontalSize seems to be 36x21. |
| MinimumVerticalSize seems to be 21x36. |
| |
| PreferredSize seems to be 200x63. Or Components.getBounds? |
| |
| MinimumSize seems to be 36x63. |
| |
| MaximumSize seems to be 32767x63. |
| */ |
| |
| /** |
| * This method returns the preferred size when the slider is horizontally |
| * oriented. |
| * |
| * @return The dimensions of the preferred horizontal size. |
| */ |
| public Dimension getPreferredHorizontalSize() |
| { |
| Insets insets = slider.getInsets(); |
| |
| // The width should cover all the labels (which are usually the |
| // deciding factor of the width) |
| int width = getWidthOfWidestLabel() * (slider.getLabelTable() == null ? 0 |
| : slider.getLabelTable() |
| .size()); |
| |
| // If there are not enough labels. |
| // This number is pretty much arbitrary, but it looks nice. |
| if (width < 200) |
| width = 200; |
| |
| // We can only draw inside of the focusRectangle, so we have to |
| // pad it with insets. |
| width += insets.left + insets.right + focusInsets.left + focusInsets.right; |
| |
| // Height is determined by the thumb, the ticks and the labels. |
| int height = thumbHeight; |
| |
| if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 |
| || slider.getMinorTickSpacing() > 0) |
| height += tickHeight; |
| |
| if (slider.getPaintLabels()) |
| height += getHeightOfTallestLabel(); |
| |
| height += insets.top + insets.bottom + focusInsets.top |
| + focusInsets.bottom; |
| |
| return new Dimension(width, height); |
| } |
| |
| /** |
| * This method returns the preferred size when the slider is vertically |
| * oriented. |
| * |
| * @return The dimensions of the preferred vertical size. |
| */ |
| public Dimension getPreferredVerticalSize() |
| { |
| Insets insets = slider.getInsets(); |
| |
| int height = getHeightOfTallestLabel() * (slider.getLabelTable() == null |
| ? 0 : slider.getLabelTable() |
| .size()); |
| |
| if (height < 200) |
| height = 200; |
| |
| height += insets.top + insets.bottom + focusInsets.top |
| + focusInsets.bottom; |
| |
| int width = thumbHeight; |
| |
| if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0 |
| || slider.getMinorTickSpacing() > 0) |
| width += tickHeight; |
| |
| if (slider.getPaintLabels()) |
| width += getWidthOfWidestLabel(); |
| |
| width += insets.left + insets.right + focusInsets.left + focusInsets.right; |
| |
| return new Dimension(width, height); |
| } |
| |
| /** |
| * This method returns the minimum size when the slider is horizontally |
| * oriented. |
| * |
| * @return The dimensions of the minimum horizontal size. |
| */ |
| public Dimension getMinimumHorizontalSize() |
| { |
| return getPreferredHorizontalSize(); |
| } |
| |
| /** |
| * This method returns the minimum size of the slider when it is vertically |
| * oriented. |
| * |
| * @return The dimensions of the minimum vertical size. |
| */ |
| public Dimension getMinimumVerticalSize() |
| { |
| return getPreferredVerticalSize(); |
| } |
| |
| /** |
| * This method returns the preferred size of the component. If it returns |
| * null, then it is up to the Layout Manager to give the {@link JComponent} |
| * a size. |
| * |
| * @param c The {@link JComponent} to find the preferred size for. |
| * |
| * @return The dimensions of the preferred size. |
| */ |
| public Dimension getPreferredSize(JComponent c) |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| return getPreferredHorizontalSize(); |
| else |
| return getPreferredVerticalSize(); |
| } |
| |
| /** |
| * This method returns the minimum size for this {@link JSlider} for this |
| * look and feel. If it returns null, then it is up to the Layout Manager |
| * to give the {@link JComponent} a size. |
| * |
| * @param c The {@link JComponent} to find the minimum size for. |
| * |
| * @return The dimensions of the minimum size. |
| */ |
| public Dimension getMinimumSize(JComponent c) |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| return getPreferredHorizontalSize(); |
| else |
| return getPreferredVerticalSize(); |
| } |
| |
| /** |
| * This method returns the maximum size for this {@link JSlider} for this |
| * look and feel. If it returns null, then it is up to the Layout Manager |
| * to give the {@link JComponent} a size. |
| * |
| * @param c The {@link JComponent} to find a maximum size for. |
| * |
| * @return The dimensions of the maximum size. |
| */ |
| public Dimension getMaximumSize(JComponent c) |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| return getPreferredHorizontalSize(); |
| else |
| return getPreferredVerticalSize(); |
| } |
| |
| /** |
| * This method calculates all the sizes of the rectangles by delegating to |
| * the helper methods calculateXXXRect. |
| */ |
| protected void calculateGeometry() |
| { |
| calculateFocusRect(); |
| calculateContentRect(); |
| calculateThumbSize(); |
| calculateTrackBuffer(); |
| calculateTrackRect(); |
| calculateTickRect(); |
| calculateLabelRect(); |
| calculateThumbLocation(); |
| } |
| |
| /** |
| * This method calculates the size and position of the focusRect. This |
| * method does not need to be called if the orientation changes. |
| */ |
| protected void calculateFocusRect() |
| { |
| insetCache = slider.getInsets(); |
| focusRect = SwingUtilities.calculateInnerArea(slider, focusRect); |
| |
| if (focusRect.width < 0) |
| focusRect.width = 0; |
| if (focusRect.height < 0) |
| focusRect.height = 0; |
| } |
| |
| /** |
| * This method calculates the size but not the position of the thumbRect. It |
| * must take into account the orientation of the slider. |
| */ |
| protected void calculateThumbSize() |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| if (thumbWidth > contentRect.width) |
| thumbRect.width = contentRect.width / 4; |
| else |
| thumbRect.width = thumbWidth; |
| if (thumbHeight > contentRect.height) |
| thumbRect.height = contentRect.height; |
| else |
| thumbRect.height = thumbHeight; |
| } |
| else |
| { |
| // The thumb gets flipped when inverted, so thumbWidth |
| // actually is the height and vice versa. |
| if (thumbWidth > contentRect.height) |
| thumbRect.height = contentRect.height / 4; |
| else |
| thumbRect.height = thumbWidth; |
| if (thumbHeight > contentRect.width) |
| thumbRect.width = contentRect.width; |
| else |
| thumbRect.width = thumbHeight; |
| } |
| } |
| |
| /** |
| * This method calculates the size and position of the contentRect. This |
| * method does not need to be called if the orientation changes. |
| */ |
| 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; |
| |
| if (contentRect.width < 0) |
| contentRect.width = 0; |
| if (contentRect.height < 0) |
| contentRect.height = 0; |
| } |
| |
| /** |
| * Calculates the position of the thumbRect based on the current value of |
| * the slider. It must take into account the orientation of the slider. |
| */ |
| protected void calculateThumbLocation() |
| { |
| int value = slider.getValue(); |
| |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| thumbRect.x = xPositionForValue(value) - thumbRect.width / 2; |
| thumbRect.y = contentRect.y; |
| } |
| else |
| { |
| thumbRect.x = contentRect.x; |
| thumbRect.y = yPositionForValue(value) - thumbRect.height / 2; |
| } |
| } |
| |
| /** |
| * Calculates the gap size between the left edge of the contentRect and the |
| * left edge of the trackRect. |
| */ |
| protected void calculateTrackBuffer() |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| trackBuffer = thumbRect.width; |
| else |
| trackBuffer = thumbRect.height; |
| } |
| |
| /** |
| * This method returns the size of the thumbRect. |
| * |
| * @return The dimensions of the thumb. |
| */ |
| protected Dimension getThumbSize() |
| { |
| // This is really just the bounds box for the thumb. |
| // The thumb will actually be pointed (like a rectangle + triangle at bottom) |
| return thumbRect.getSize(); |
| } |
| |
| /** |
| * Calculates the size and position of the trackRect. It must take into |
| * account the orientation of the slider. |
| */ |
| protected void calculateTrackRect() |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| trackRect.x = contentRect.x + trackBuffer; |
| trackRect.y = contentRect.y; |
| trackRect.width = contentRect.width - 2 * trackBuffer; |
| trackRect.height = thumbRect.height; |
| } |
| else |
| { |
| trackRect.x = contentRect.x; |
| trackRect.y = contentRect.y + trackBuffer; |
| trackRect.width = thumbRect.width; |
| trackRect.height = contentRect.height - 2 * trackBuffer; |
| } |
| } |
| |
| /** |
| * This method returns the height of the tick area box if the slider is |
| * horizontal and the width of the tick area box is the slider is vertical. |
| * It not necessarily how long the ticks will be. If a gap between the edge |
| * of tick box and the actual tick is desired, then that will need to be |
| * handled in the tick painting methods. |
| * |
| * @return The height (or width if the slider is vertical) of the tick |
| * rectangle. |
| */ |
| protected int getTickLength() |
| { |
| return tickHeight; |
| } |
| |
| /** |
| * This method calculates the size and position of the tickRect. It must |
| * take into account the orientation of the slider. |
| */ |
| protected void calculateTickRect() |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| tickRect.x = trackRect.x; |
| tickRect.y = trackRect.y + trackRect.height; |
| tickRect.width = trackRect.width; |
| tickRect.height = getTickLength(); |
| |
| if (tickRect.y + tickRect.height > contentRect.y + contentRect.height) |
| tickRect.height = contentRect.y + contentRect.height - tickRect.y; |
| } |
| else |
| { |
| tickRect.x = trackRect.x + trackRect.width; |
| tickRect.y = trackRect.y; |
| tickRect.width = getTickLength(); |
| tickRect.height = trackRect.height; |
| |
| if (tickRect.x + tickRect.width > contentRect.x + contentRect.width) |
| tickRect.width = contentRect.x + contentRect.width - tickRect.x; |
| } |
| } |
| |
| /** |
| * This method calculates the size and position of the labelRect. It must |
| * take into account the orientation of the slider. |
| */ |
| protected void calculateLabelRect() |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| labelRect.x = contentRect.x; |
| labelRect.y = tickRect.y + tickRect.height; |
| labelRect.width = contentRect.width; |
| labelRect.height = contentRect.height - labelRect.y; |
| } |
| else |
| { |
| labelRect.x = tickRect.x + tickRect.width; |
| labelRect.y = contentRect.y; |
| labelRect.width = contentRect.width - labelRect.x; |
| labelRect.height = contentRect.height; |
| } |
| } |
| |
| /** |
| * This method returns the width of the widest label in the slider's label |
| * table. |
| * |
| * @return The width of the widest label or 0 if no label table exists. |
| */ |
| protected int getWidthOfWidestLabel() |
| { |
| int widest = 0; |
| Component label; |
| |
| if (slider.getLabelTable() == null) |
| return 0; |
| |
| Dimension pref; |
| for (Enumeration list = slider.getLabelTable().elements(); |
| list.hasMoreElements();) |
| { |
| Object comp = list.nextElement(); |
| if (! (comp instanceof Component)) |
| continue; |
| label = (Component) comp; |
| pref = label.getPreferredSize(); |
| if (pref != null && pref.width > widest) |
| widest = pref.width; |
| } |
| return widest; |
| } |
| |
| /** |
| * This method returns the height of the tallest label in the slider's label |
| * table. |
| * |
| * @return The height of the tallest label or 0 if no label table exists. |
| */ |
| protected int getHeightOfTallestLabel() |
| { |
| int tallest = 0; |
| Component label; |
| |
| if (slider.getLabelTable() == null) |
| return 0; |
| Dimension pref; |
| for (Enumeration list = slider.getLabelTable().elements(); |
| list.hasMoreElements();) |
| { |
| Object comp = list.nextElement(); |
| if (! (comp instanceof Component)) |
| continue; |
| label = (Component) comp; |
| pref = label.getPreferredSize(); |
| if (pref != null && pref.height > tallest) |
| tallest = pref.height; |
| } |
| return tallest; |
| } |
| |
| /** |
| * This method returns the width of the label whose key has the highest |
| * value. |
| * |
| * @return The width of the high value label or 0 if no label table exists. |
| */ |
| protected int getWidthOfHighValueLabel() |
| { |
| Component highValueLabel = getHighestValueLabel(); |
| if (highValueLabel != null) |
| return highValueLabel.getWidth(); |
| else |
| return 0; |
| } |
| |
| /** |
| * This method returns the width of the label whose key has the lowest |
| * value. |
| * |
| * @return The width of the low value label or 0 if no label table exists. |
| */ |
| protected int getWidthOfLowValueLabel() |
| { |
| Component lowValueLabel = getLowestValueLabel(); |
| if (lowValueLabel != null) |
| return lowValueLabel.getWidth(); |
| else |
| return 0; |
| } |
| |
| /** |
| * This method returns the height of the label whose key has the highest |
| * value. |
| * |
| * @return The height of the high value label or 0 if no label table exists. |
| */ |
| protected int getHeightOfHighValueLabel() |
| { |
| Component highValueLabel = getHighestValueLabel(); |
| if (highValueLabel != null) |
| return highValueLabel.getHeight(); |
| else |
| return 0; |
| } |
| |
| /** |
| * This method returns the height of the label whose key has the lowest |
| * value. |
| * |
| * @return The height of the low value label or 0 if no label table exists. |
| */ |
| protected int getHeightOfLowValueLabel() |
| { |
| Component lowValueLabel = getLowestValueLabel(); |
| if (lowValueLabel != null) |
| return lowValueLabel.getHeight(); |
| else |
| return 0; |
| } |
| |
| /** |
| * This method returns whether the slider is to be drawn inverted. |
| * |
| * @return True is the slider is to be drawn inverted. |
| */ |
| protected boolean drawInverted() |
| { |
| return ! (slider.getInverted() ^ leftToRightCache); |
| } |
| |
| /** |
| * This method returns the label whose key has the lowest value. |
| * |
| * @return The low value label or null if no label table exists. |
| */ |
| protected Component getLowestValueLabel() |
| { |
| Integer key = new Integer(Integer.MAX_VALUE); |
| Integer tmpKey; |
| Dictionary labelTable = slider.getLabelTable(); |
| |
| if (labelTable == null) |
| return null; |
| |
| for (Enumeration list = labelTable.keys(); list.hasMoreElements();) |
| { |
| Object value = list.nextElement(); |
| if (! (value instanceof Integer)) |
| continue; |
| tmpKey = (Integer) value; |
| if (tmpKey.intValue() < key.intValue()) |
| key = tmpKey; |
| } |
| Object comp = labelTable.get(key); |
| if (! (comp instanceof Component)) |
| return null; |
| return (Component) comp; |
| } |
| |
| /** |
| * This method returns the label whose key has the highest value. |
| * |
| * @return The high value label or null if no label table exists. |
| */ |
| protected Component getHighestValueLabel() |
| { |
| Integer key = new Integer(Integer.MIN_VALUE); |
| Integer tmpKey; |
| Dictionary labelTable = slider.getLabelTable(); |
| |
| if (labelTable == null) |
| return null; |
| |
| for (Enumeration list = labelTable.keys(); list.hasMoreElements();) |
| { |
| Object value = list.nextElement(); |
| if (! (value instanceof Integer)) |
| continue; |
| tmpKey = (Integer) value; |
| if (tmpKey.intValue() > key.intValue()) |
| key = tmpKey; |
| } |
| Object comp = labelTable.get(key); |
| if (! (comp instanceof Component)) |
| return null; |
| return (Component) comp; |
| } |
| |
| /** |
| * This method is used to paint the {@link JSlider}. It delegates all its |
| * duties to the various paint methods like paintTicks(), paintTrack(), |
| * paintThumb(), etc. |
| * |
| * @param g The {@link Graphics} object to paint with. |
| * @param c The {@link JComponent} that is being painted. |
| */ |
| public void paint(Graphics g, JComponent c) |
| { |
| // FIXME: Move this to propertyChangeEvent handler, when we get those. |
| leftToRightCache = slider.getComponentOrientation() != ComponentOrientation.RIGHT_TO_LEFT; |
| // FIXME: This next line is only here because the above line is here. |
| calculateThumbLocation(); |
| |
| if (slider.getPaintTrack()) |
| paintTrack(g); |
| if (slider.getPaintTicks()) |
| paintTicks(g); |
| if (slider.getPaintLabels()) |
| paintLabels(g); |
| |
| //FIXME: Paint focus. |
| paintThumb(g); |
| } |
| |
| /** |
| * This method recalculates any rectangles that need to be recalculated |
| * after the insets of the component have changed. |
| */ |
| protected void recalculateIfInsetsChanged() |
| { |
| // Examining a test program shows that either Sun calls private |
| // methods that we don't know about, or these don't do anything. |
| calculateFocusRect(); |
| |
| calculateContentRect(); |
| calculateThumbSize(); |
| calculateTrackBuffer(); |
| calculateTrackRect(); |
| calculateThumbLocation(); |
| |
| calculateTickRect(); |
| calculateLabelRect(); |
| } |
| |
| /** |
| * This method recalculates any rectangles that need to be recalculated |
| * after the orientation of the slider changes. |
| */ |
| protected void recalculateIfOrientationChanged() |
| { |
| // Examining a test program shows that either Sun calls private |
| // methods that we don't know about, or these don't do anything. |
| calculateThumbSize(); |
| calculateTrackBuffer(); |
| calculateTrackRect(); |
| calculateThumbLocation(); |
| |
| calculateTickRect(); |
| calculateLabelRect(); |
| } |
| |
| /** |
| * This method is called during a repaint if the slider has focus. It draws |
| * an outline of the focusRect using the color returned by |
| * getFocusColor(). |
| * |
| * @param g The {@link Graphics} object to draw with. |
| */ |
| public void paintFocus(Graphics g) |
| { |
| Color saved_color = g.getColor(); |
| |
| g.setColor(getFocusColor()); |
| |
| g.drawRect(focusRect.x, focusRect.y, focusRect.width, focusRect.height); |
| |
| g.setColor(saved_color); |
| } |
| |
| /** |
| * <p> |
| * This method is called during a repaint if the track is to be drawn. It |
| * draws a 3D rectangle to represent the track. The track is not the size |
| * of the trackRect. The top and left edges of the track should be outlined |
| * with the shadow color. The bottom and right edges should be outlined |
| * with the highlight color. |
| * </p> |
| * <pre> |
| * a---d |
| * | | |
| * | | a------------------------d |
| * | | | | |
| * | | b------------------------c |
| * | | |
| * | | |
| * b---c |
| * </pre> |
| * |
| * <p> |
| * The b-a-d path needs to be drawn with the shadow color and the b-c-d path |
| * needs to be drawn with the highlight color. |
| * </p> |
| * |
| * @param g The {@link Graphics} object to draw with. |
| */ |
| public void paintTrack(Graphics g) |
| { |
| Color saved_color = g.getColor(); |
| int width; |
| int height; |
| |
| Point a = new Point(trackRect.x, trackRect.y); |
| Point b = new Point(a); |
| Point c = new Point(a); |
| Point d = new Point(a); |
| |
| Polygon high; |
| Polygon shadow; |
| |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| width = trackRect.width; |
| height = (thumbRect.height / 4 == 0) ? 1 : thumbRect.height / 4; |
| |
| a.translate(0, (trackRect.height / 2) - (height / 2)); |
| b.translate(0, (trackRect.height / 2) + (height / 2)); |
| c.translate(trackRect.width, (trackRect.height / 2) + (height / 2)); |
| d.translate(trackRect.width, (trackRect.height / 2) - (height / 2)); |
| } |
| else |
| { |
| width = (thumbRect.width / 4 == 0) ? 1 : thumbRect.width / 4; |
| height = trackRect.height; |
| |
| a.translate((trackRect.width / 2) - (width / 2), 0); |
| b.translate((trackRect.width / 2) - (width / 2), trackRect.height); |
| c.translate((trackRect.width / 2) + (width / 2), trackRect.height); |
| d.translate((trackRect.width / 2) + (width / 2), 0); |
| } |
| g.setColor(Color.GRAY); |
| g.fillRect(a.x, a.y, width, height); |
| |
| g.setColor(getHighlightColor()); |
| g.drawLine(b.x, b.y, c.x, c.y); |
| g.drawLine(c.x, c.y, d.x, d.y); |
| |
| g.setColor(getShadowColor()); |
| g.drawLine(b.x, b.y, a.x, a.y); |
| g.drawLine(a.x, a.y, d.x, d.y); |
| |
| g.setColor(saved_color); |
| } |
| |
| /** |
| * This method is called during a repaint if the ticks are to be drawn. This |
| * method must still verify that the majorTickSpacing and minorTickSpacing |
| * are greater than zero before drawing the ticks. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| */ |
| public void paintTicks(Graphics g) |
| { |
| int max = slider.getMaximum(); |
| int min = slider.getMinimum(); |
| int majorSpace = slider.getMajorTickSpacing(); |
| int minorSpace = slider.getMinorTickSpacing(); |
| |
| if (majorSpace > 0) |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| double loc = tickRect.x; |
| double increment = (max == min) ? 0 |
| : majorSpace * (double) tickRect.width / (max |
| - min); |
| if (drawInverted()) |
| { |
| loc += tickRect.width; |
| increment *= -1; |
| } |
| for (int i = min; i <= max; i += majorSpace) |
| { |
| paintMajorTickForHorizSlider(g, tickRect, (int) loc); |
| loc += increment; |
| } |
| } |
| else |
| { |
| double loc = tickRect.height + tickRect.y; |
| double increment = (max == min) ? 0 |
| : -majorSpace * (double) tickRect.height / (max |
| - min); |
| if (drawInverted()) |
| { |
| loc = tickRect.y; |
| increment *= -1; |
| } |
| for (int i = min; i <= max; i += majorSpace) |
| { |
| paintMajorTickForVertSlider(g, tickRect, (int) loc); |
| loc += increment; |
| } |
| } |
| } |
| if (minorSpace > 0) |
| { |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| double loc = tickRect.x; |
| double increment = (max == min) ? 0 |
| : minorSpace * (double) tickRect.width / (max |
| - min); |
| if (drawInverted()) |
| { |
| loc += tickRect.width; |
| increment *= -1; |
| } |
| for (int i = min; i <= max; i += minorSpace) |
| { |
| paintMinorTickForHorizSlider(g, tickRect, (int) loc); |
| loc += increment; |
| } |
| } |
| else |
| { |
| double loc = tickRect.height + tickRect.y; |
| double increment = (max == min) ? 0 |
| : -minorSpace * (double) tickRect.height / (max |
| - min); |
| if (drawInverted()) |
| { |
| loc = tickRect.y; |
| increment *= -1; |
| } |
| for (int i = min; i <= max; i += minorSpace) |
| { |
| paintMinorTickForVertSlider(g, tickRect, (int) loc); |
| loc += increment; |
| } |
| } |
| } |
| } |
| |
| /* Minor ticks start at 1/4 of the height (or width) of the tickRect and extend |
| to 1/2 of the tickRect. |
| |
| Major ticks start at 1/4 of the height and extend to 3/4. |
| */ |
| |
| /** |
| * This method paints a minor tick for a horizontal slider at the given x |
| * value. x represents the x coordinate to paint at. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| * @param tickBounds The tickRect rectangle. |
| * @param x The x coordinate to draw the tick at. |
| */ |
| protected void paintMinorTickForHorizSlider(Graphics g, |
| Rectangle tickBounds, int x) |
| { |
| int y = tickRect.y + tickRect.height / 4; |
| Color saved = g.getColor(); |
| g.setColor(Color.BLACK); |
| |
| g.drawLine(x, y, x, y + tickRect.height / 4); |
| g.setColor(saved); |
| } |
| |
| /** |
| * This method paints a major tick for a horizontal slider at the given x |
| * value. x represents the x coordinate to paint at. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| * @param tickBounds The tickRect rectangle. |
| * @param x The x coordinate to draw the tick at. |
| */ |
| protected void paintMajorTickForHorizSlider(Graphics g, |
| Rectangle tickBounds, int x) |
| { |
| int y = tickRect.y + tickRect.height / 4; |
| Color saved = g.getColor(); |
| g.setColor(Color.BLACK); |
| |
| g.drawLine(x, y, x, y + tickRect.height / 2); |
| g.setColor(saved); |
| } |
| |
| /** |
| * This method paints a minor tick for a vertical slider at the given y |
| * value. y represents the y coordinate to paint at. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| * @param tickBounds The tickRect rectangle. |
| * @param y The y coordinate to draw the tick at. |
| */ |
| protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds, |
| int y) |
| { |
| int x = tickRect.x + tickRect.width / 4; |
| Color saved = g.getColor(); |
| g.setColor(Color.BLACK); |
| |
| g.drawLine(x, y, x + tickRect.width / 4, y); |
| g.setColor(saved); |
| } |
| |
| /** |
| * This method paints a major tick for a vertical slider at the given y |
| * value. y represents the y coordinate to paint at. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| * @param tickBounds The tickRect rectangle. |
| * @param y The y coordinate to draw the tick at. |
| */ |
| protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds, |
| int y) |
| { |
| int x = tickRect.x + tickRect.width / 4; |
| Color saved = g.getColor(); |
| g.setColor(Color.BLACK); |
| |
| g.drawLine(x, y, x + tickRect.width / 2, y); |
| g.setColor(saved); |
| } |
| |
| /** |
| * This method paints all the labels from the slider's label table. This |
| * method must make sure that the label table is not null before painting |
| * the labels. Each entry in the label table is a (integer, component) |
| * pair. Every label is painted at the value of the integer. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| */ |
| public void paintLabels(Graphics g) |
| { |
| if (slider.getLabelTable() != null) |
| { |
| Dictionary table = slider.getLabelTable(); |
| Integer tmpKey; |
| Object key; |
| Object element; |
| Component label; |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| for (Enumeration list = table.keys(); list.hasMoreElements();) |
| { |
| key = list.nextElement(); |
| if (! (key instanceof Integer)) |
| continue; |
| tmpKey = (Integer) key; |
| element = table.get(tmpKey); |
| // We won't paint them if they're not |
| // JLabels so continue anyway |
| if (! (element instanceof JLabel)) |
| continue; |
| label = (Component) element; |
| paintHorizontalLabel(g, tmpKey.intValue(), label); |
| } |
| } |
| else |
| { |
| for (Enumeration list = table.keys(); list.hasMoreElements();) |
| { |
| key = list.nextElement(); |
| if (! (key instanceof Integer)) |
| continue; |
| tmpKey = (Integer) key; |
| element = table.get(tmpKey); |
| // We won't paint them if they're not |
| // JLabels so continue anyway |
| if (! (element instanceof JLabel)) |
| continue; |
| label = (Component) element; |
| paintVerticalLabel(g, tmpKey.intValue(), label); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method paints the label on the horizontal slider at the value |
| * specified. The value is not a coordinate. It is a value within the range |
| * of the slider. If the value is not within the range of the slider, this |
| * method will do nothing. This method should not paint outside the |
| * boundaries of the labelRect. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| * @param value The value to paint at. |
| * @param label The label to paint. |
| */ |
| protected void paintHorizontalLabel(Graphics g, int value, Component label) |
| { |
| // This relies on clipping working properly or we'll end up |
| // painting all over the place. If our preferred size is ignored, then |
| // the labels may not fit inside the slider's bounds. Rather than mucking |
| // with font sizes and possible icon sizes, we'll set the bounds for |
| // the label and let it get clipped. |
| Dimension dim = label.getPreferredSize(); |
| int w = (int) dim.getWidth(); |
| int h = (int) dim.getHeight(); |
| |
| int max = slider.getMaximum(); |
| int min = slider.getMinimum(); |
| |
| if (value > max || value < min) |
| return; |
| |
| // value |
| // | |
| // ------------ |
| // | | |
| // | | |
| // | | |
| // The label must move w/2 to the right to fit directly under the value. |
| int xpos = xPositionForValue(value) - w / 2; |
| int ypos = labelRect.y; |
| |
| // We want to center the label around the xPositionForValue |
| // So we use xpos - w / 2. However, if value is min and the label |
| // is large, we run the risk of going out of bounds. So we bring it back |
| // to 0 if it becomes negative. |
| if (xpos < 0) |
| xpos = 0; |
| |
| // If the label + starting x position is greater than |
| // the x space in the label rectangle, we reset it to the largest |
| // amount possible in the rectangle. This means ugliness. |
| if (xpos + w > labelRect.x + labelRect.width) |
| w = labelRect.x + labelRect.width - xpos; |
| |
| // If the label is too tall. We reset it to the height of the label |
| // rectangle. |
| if (h > labelRect.height) |
| h = labelRect.height; |
| |
| label.setBounds(xpos, ypos, w, h); |
| javax.swing.SwingUtilities.paintComponent(g, label, null, label.getBounds()); |
| } |
| |
| /** |
| * This method paints the label on the vertical slider at the value |
| * specified. The value is not a coordinate. It is a value within the range |
| * of the slider. If the value is not within the range of the slider, this |
| * method will do nothing. This method should not paint outside the |
| * boundaries of the labelRect. |
| * |
| * @param g The {@link Graphics} object to draw with. |
| * @param value The value to paint at. |
| * @param label The label to paint. |
| */ |
| protected void paintVerticalLabel(Graphics g, int value, Component label) |
| { |
| Dimension dim = label.getPreferredSize(); |
| int w = (int) dim.getWidth(); |
| int h = (int) dim.getHeight(); |
| |
| int max = slider.getMaximum(); |
| int min = slider.getMinimum(); |
| |
| if (value > max || value < min) |
| return; |
| |
| int xpos = labelRect.x; |
| int ypos = yPositionForValue(value) - h / 2; |
| |
| if (ypos < 0) |
| ypos = 0; |
| |
| if (ypos + h > labelRect.y + labelRect.height) |
| h = labelRect.y + labelRect.height - ypos; |
| |
| if (w > labelRect.width) |
| w = labelRect.width; |
| |
| label.setBounds(xpos, ypos, w, h); |
| javax.swing.SwingUtilities.paintComponent(g, label, null, label.getBounds()); |
| } |
| |
| /** |
| * <p> |
| * This method paints a thumb. There are two types of thumb: |
| * </p> |
| * <pre> |
| * Vertical Horizontal |
| * a---b a-----b |
| * | | | \ |
| * e c | c |
| * \ / | / |
| * d e-----d |
| * </pre> |
| * |
| * <p> |
| * In the case of vertical thumbs, we highlight the path b-a-e-d and shadow |
| * the path b-c-d. In the case of horizontal thumbs, we highlight the path |
| * c-b-a-e and shadow the path c-d-e. In both cases we fill the path |
| * a-b-c-d-e before shadows and highlights are drawn. |
| * </p> |
| * |
| * @param g The graphics object to paint with |
| */ |
| public void paintThumb(Graphics g) |
| { |
| Color saved_color = g.getColor(); |
| |
| Polygon thumb = new Polygon(); |
| |
| Point a = new Point(thumbRect.x, thumbRect.y); |
| Point b = new Point(a); |
| Point c = new Point(a); |
| Point d = new Point(a); |
| Point e = new Point(a); |
| |
| Polygon bright; |
| Polygon dark; |
| Polygon all; |
| |
| // This will be in X-dimension if the slider is inverted and y if it isn't. |
| int turnPoint; |
| |
| if (slider.getOrientation() == JSlider.HORIZONTAL) |
| { |
| turnPoint = thumbRect.height * 3 / 4; |
| |
| b.translate(thumbRect.width, 0); |
| c.translate(thumbRect.width, turnPoint); |
| d.translate(thumbRect.width / 2, thumbRect.height); |
| e.translate(0, turnPoint); |
| |
| bright = new Polygon(new int[] { b.x, a.x, e.x, d.x }, |
| new int[] { b.y, a.y, e.y, d.y }, 4); |
| |
| dark = new Polygon(new int[] { b.x, c.x, d.x }, |
| new int[] { b.y, c.y, d.y }, 3); |
| all = new Polygon(new int[] { a.x + 1, b.x, c.x, d.x, e.x + 1 }, |
| new int[] { a.y + 1, b.y + 1, c.y, d.y + 1, e.y }, 5); |
| } |
| else |
| { |
| turnPoint = thumbRect.width * 3 / 4; |
| |
| b.translate(turnPoint, 0); |
| c.translate(thumbRect.width, thumbRect.height / 2); |
| d.translate(turnPoint, thumbRect.height); |
| e.translate(0, thumbRect.height); |
| |
| bright = new Polygon(new int[] { c.x, b.x, a.x, e.x }, |
| new int[] { c.y, b.y, a.y, e.y }, 4); |
| |
| dark = new Polygon(new int[] { c.x, d.x, e.x + 1 }, |
| new int[] { c.y, d.y, e.y }, 3); |
| |
| all = new Polygon(new int[] { a.x + 1, b.x, c.x - 1, d.x, e.x + 1 }, |
| new int[] { a.y + 1, b.y + 1, c.y, d.y, e.y }, 5); |
| } |
| |
| g.setColor(Color.WHITE); |
| g.drawPolyline(bright.xpoints, bright.ypoints, bright.npoints); |
| |
| g.setColor(Color.BLACK); |
| g.drawPolyline(dark.xpoints, dark.ypoints, dark.npoints); |
| |
| g.setColor(Color.GRAY); |
| g.fillPolygon(all); |
| |
| g.setColor(saved_color); |
| } |
| |
| /** |
| * This method sets the position of the thumbRect. |
| * |
| * @param x The new x position. |
| * @param y The new y position. |
| */ |
| public void setThumbLocation(int x, int y) |
| { |
| thumbRect.x = x; |
| thumbRect.y = y; |
| } |
| |
| /** |
| * This method is used to move the thumb one block in the direction |
| * specified. If the slider snaps to ticks, this method is responsible for |
| * snapping it to a tick after the thumb has been moved. |
| * |
| * @param direction The direction to move in. |
| */ |
| public void scrollByBlock(int direction) |
| { |
| // The direction is -1 for backwards and 1 for forwards. |
| int unit = direction * (slider.getMaximum() - slider.getMinimum()) / 10; |
| |
| int moveTo = slider.getValue() + unit; |
| |
| if (slider.getSnapToTicks()) |
| moveTo = findClosestTick(moveTo); |
| |
| slider.setValue(moveTo); |
| } |
| |
| /** |
| * This method is used to move the thumb one unit in the direction |
| * specified. If the slider snaps to ticks, this method is responsible for |
| * snapping it to a tick after the thumb has been moved. |
| * |
| * @param direction The direction to move in. |
| */ |
| public void scrollByUnit(int direction) |
| { |
| // The direction is -1 for backwards and 1 for forwards. |
| int moveTo = slider.getValue() + direction; |
| |
| if (slider.getSnapToTicks()) |
| moveTo = findClosestTick(moveTo); |
| |
| slider.setValue(moveTo); |
| } |
| |
| /** |
| * This method is called when there has been a click in the track and the |
| * thumb needs to be scrolled on regular intervals. This method is only |
| * responsible for starting the timer and not for stopping it. |
| * |
| * @param dir The direction to move in. |
| */ |
| protected void scrollDueToClickInTrack(int dir) |
| { |
| scrollTimer.stop(); |
| |
| scrollListener.setDirection(dir); |
| scrollListener.setScrollByBlock(true); |
| |
| scrollTimer.start(); |
| } |
| |
| /** |
| * This method returns the X coordinate for the value passed in. |
| * |
| * @param value The value to calculate an x coordinate for. |
| * |
| * @return The x coordinate for the value. |
| */ |
| protected int xPositionForValue(int value) |
| { |
| int min = slider.getMinimum(); |
| int max = slider.getMaximum(); |
| int extent = slider.getExtent(); |
| int len = trackRect.width; |
| |
| int xPos = (max == min) ? 0 : (value - min) * len / (max - min); |
| |
| if (! drawInverted()) |
| xPos += trackRect.x; |
| else |
| { |
| xPos = trackRect.width - xPos; |
| xPos += trackRect.x; |
| } |
| return xPos; |
| } |
| |
| /** |
| * This method returns the y coordinate for the value passed in. |
| * |
| * @param value The value to calculate a y coordinate for. |
| * |
| * @return The y coordinate for the value. |
| */ |
| protected int yPositionForValue(int value) |
| { |
| int min = slider.getMinimum(); |
| int max = slider.getMaximum(); |
| int extent = slider.getExtent(); |
| int len = trackRect.height; |
| |
| int yPos = (max == min) ? 0 : (value - min) * len / (max - min); |
| |
| if (! drawInverted()) |
| { |
| yPos = trackRect.height - yPos; |
| yPos += trackRect.y; |
| } |
| else |
| yPos += trackRect.y; |
| return yPos; |
| } |
| |
| /** |
| * This method returns the value in the slider's range given the y |
| * coordinate. If the value is out of range, it will return the closest |
| * legal value. |
| * |
| * @param yPos The y coordinate to calculate a value for. |
| * |
| * @return The value for the y coordinate. |
| */ |
| public int valueForYPosition(int yPos) |
| { |
| int min = slider.getMinimum(); |
| int max = slider.getMaximum(); |
| int len = trackRect.height; |
| |
| int value; |
| |
| // If the length is 0, you shouldn't be able to even see where the slider is. |
| // This really shouldn't ever happen, but just in case, we'll return the middle. |
| if (len == 0) |
| return ((max - min) / 2); |
| |
| if (! drawInverted()) |
| value = ((len - (yPos - trackRect.y)) * (max - min) / len + min); |
| else |
| value = ((yPos - trackRect.y) * (max - min) / len + min); |
| |
| // If this isn't a legal value, then we'll have to move to one now. |
| if (value > max) |
| value = max; |
| else if (value < min) |
| value = min; |
| return value; |
| } |
| |
| /** |
| * This method returns the value in the slider's range given the x |
| * coordinate. If the value is out of range, it will return the closest |
| * legal value. |
| * |
| * @param xPos The x coordinate to calculate a value for. |
| * |
| * @return The value for the x coordinate. |
| */ |
| public int valueForXPosition(int xPos) |
| { |
| int min = slider.getMinimum(); |
| int max = slider.getMaximum(); |
| int len = trackRect.width; |
| |
| int value; |
| |
| // If the length is 0, you shouldn't be able to even see where the slider is. |
| // This really shouldn't ever happen, but just in case, we'll return the middle. |
| if (len == 0) |
| return ((max - min) / 2); |
| |
| if (! drawInverted()) |
| value = ((xPos - trackRect.x) * (max - min) / len + min); |
| else |
| value = ((len - (xPos - trackRect.x)) * (max - min) / len + min); |
| |
| // If this isn't a legal value, then we'll have to move to one now. |
| if (value > max) |
| value = max; |
| else if (value < min) |
| value = min; |
| return value; |
| } |
| |
| /** |
| * This method finds the closest value that has a tick associated with it. |
| * |
| * @param value The value to search from. |
| * |
| * @return The closest value that has a tick associated with it. |
| */ |
| private int findClosestTick(int value) |
| { |
| int min = slider.getMinimum(); |
| int max = slider.getMaximum(); |
| int majorSpace = slider.getMajorTickSpacing(); |
| int minorSpace = slider.getMinorTickSpacing(); |
| |
| // The default value to return is value + minor or |
| // value + major. |
| // Initializing at min - value leaves us with a default |
| // return value of min, which always has tick marks |
| // (if ticks are painted). |
| int minor = min - value; |
| int major = min - value; |
| |
| // If there are no major tick marks or minor tick marks |
| // e.g. snap is set to true but no ticks are set, then |
| // we can just return the value. |
| if (majorSpace <= 0 && minorSpace <= 0) |
| return value; |
| |
| // First check the major ticks. |
| if (majorSpace > 0) |
| { |
| int lowerBound = (value - min) / majorSpace; |
| int majLower = majorSpace * lowerBound + min; |
| int majHigher = majorSpace * (lowerBound + 1) + min; |
| |
| if (majHigher <= max && majHigher - value <= value - majLower) |
| major = majHigher - value; |
| else |
| major = majLower - value; |
| } |
| |
| if (minorSpace > 0) |
| { |
| int lowerBound = value / minorSpace; |
| int minLower = minorSpace * lowerBound; |
| int minHigher = minorSpace * (lowerBound + 1); |
| |
| if (minHigher <= max && minHigher - value <= value - minLower) |
| minor = minHigher - value; |
| else |
| minor = minLower - value; |
| } |
| |
| // Give preference to minor ticks |
| if (Math.abs(minor) > Math.abs(major)) |
| return value + major; |
| else |
| return value + minor; |
| } |
| } |