Is it possible to to find the pixel position of the first tick mark on a jSlider scale?

StackOverflow https://stackoverflow.com/questions/15171839

  •  16-03-2022
  •  | 
  •  

Question

I am trying to align a custom drawn graph to a jSlider's scale (the slider provides the X-axis scale for the graph). I need the slider knob to set values relating to the graph, so it really needs to be a jSlider.

The trouble is, if the length of the scale labels change then the start position of the scale's first (and last) tick mark will be different, which requires me to manually change the position of the graph to match.

Is it possible to set the slider's scale and then determine the pixel position of the first tick mark so I can set the start of the graph to match?

trackRect.x of BasicSliderUI will give me the correct X point position but I am having trouble accessing it. I keep getting null pointers on trackRect with the following code; I am obviously missing something very basic here because I've seen a RangeSlider access trackRect.x by extending BasicSliderUI. I am simply not overriding anything like they are:

Class to access the trackRect.x value:

import javax.swing.JSlider;
import javax.swing.plaf.basic.BasicSliderUI;


public class ExtendedBasicSliderUI  extends BasicSliderUI
{
   public ExtendedBasicSliderUI(JSlider b)
   {
       super(b);
   }

   public int getScaleStartPosition()
   {
       return trackRect.x;
   }

}

in the constructor of the view which contains the slider:

initComponents();
extendedUI = new ExtendedBasicSliderUI(jSlider1);
jSlider1.setUI(extendedUI);

criteriaModel.setGraphAlignmentIntervalRatio(extendedUI.getScaleStartPosition());
Was it helpful?

Solution

I should have answered this ages ago as I did eventually find a work around.
I modified code for a range slider from here: http://ernienotes.wordpress.com/2010/12/27/creating-a-java-swing-range-slider/ Sorry, I had to remove the Java Doc comments to fit it in the 3000 chars limit.

The slider class code is this:

import javax.swing.JSlider;

public class RangeSlider extends JSlider
{
    RangeSliderUI rangeUI;

    public RangeSlider()
    {
        initSlider();
    }

    public RangeSlider(int min, int max)
    {
        super(min, max);
        initSlider();
    }

    private void initSlider()
    {
        setOrientation(HORIZONTAL);
    }

    @Override
    public void updateUI()
    {
        rangeUI = new RangeSliderUI(this);
        setUI(rangeUI);
        // Update UI for slider labels.  This must be called after updating the
        // UI of the slider.  Refer to JSlider.updateUI().
        updateLabelUIs();
    }

    @Override
    public int getValue()
    {
        return super.getValue();
    }

    public RangeSliderUI getRangeUI()
    {
        return rangeUI;
    }

    @Override
    public void setValue(int value)
    {
        int oldValue = getValue();
        if (oldValue == value)
        {
            return;
        }

        // Compute new value and extent to maintain upper value.
        int oldExtent = getExtent();
        int newValue = Math.min(Math.max(getMinimum(), value), oldValue + oldExtent);
        int newExtent = oldExtent + oldValue - newValue;

        // Set new value and extent, and fire a single change event.
        getModel().setRangeProperties(newValue, newExtent, getMinimum(),
            getMaximum(), getValueIsAdjusting());
    }

    public int getUpperValue()
    {
        return getValue() + getExtent();
    }

    /**
     * Sets the upper value in the range.
     */
    public void setUpperValue(int value)
    {
        // Compute new extent.
        int lowerValue = getValue();
        int newExtent = Math.min(Math.max(0, value - lowerValue), getMaximum() - lowerValue);

        // Set extent to set upper value.
        setExtent(newExtent);
    }

    public boolean isRangeSlider()
    {
        return rangeUI.isRangeSlider();
    }

    /**
     * Sets whether this is currently a range slider or not. (true is range slider, false is not range slider)
     */
    public void setRangeSlider(boolean rangeSlider)
    {
        this.rangeUI.setRangeSlider(rangeSlider);
    }
}

The UI class is this:

package YourPackage;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicSliderUI;

/**
 * UI delegate for the RangeSlider component.  RangeSliderUI paints two thumbs,
 * one for the lower value and one for the upper value.
 * 
 * Setting rangeSlider = false; causes the slider to revert to being a normal slider
 * rather than a range slider. 
 */
class RangeSliderUI extends BasicSliderUI
{

    /** Color of selected range. */
    private Color rangeColor = Color.darkGray;
    /** Location and size of thumb for upper value. */
    private Rectangle upperThumbRect;
    /** Indicator that determines whether upper thumb is selected. */
    private boolean upperThumbSelected;
    /** Indicator that determines whether lower thumb is being dragged. */
    private transient boolean lowerDragging;
    /** Indicator that determines whether upper thumb is being dragged. */
    private transient boolean upperDragging;
    // Range/single slider (true is range slider/ false is single slider)
    private boolean rangeSlider = false;

    public RangeSliderUI(RangeSlider b)
    {
        super(b);
    }

    public int getTrackRect()
    {
        return trackRect.x;
    }

    public boolean isRangeSlider()
    {
        return rangeSlider;
    }

    public void setRangeSlider(boolean rangeSlider)
    {
        this.rangeSlider = rangeSlider;
    }

    @Override
    public void installUI(JComponent c)
    {
        upperThumbRect = new Rectangle();
        super.installUI(c);
    }

    @Override
    protected TrackListener createTrackListener(JSlider slider)
    {
        return new RangeTrackListener();
    }

    @Override
    protected ChangeListener createChangeListener(JSlider slider)
    {
        return new ChangeHandler();
    }

    @Override
    protected void calculateThumbSize()
    {
        // Call superclass method for lower thumb size.
        super.calculateThumbSize();

        // Set upper thumb size.
        upperThumbRect.setSize(thumbRect.width, thumbRect.height);
    }

    @Override
    protected void calculateThumbLocation()
    {
        // Call superclass method for lower thumb location.
        super.calculateThumbLocation();

        // Adjust upper value to snap to ticks if necessary.
        if (slider.getSnapToTicks())
        {
            int upperValue = slider.getValue() + slider.getExtent();
            int snappedValue = upperValue;
            int majorTickSpacing = slider.getMajorTickSpacing();
            int minorTickSpacing = slider.getMinorTickSpacing();
            int tickSpacing = 0;

            if (minorTickSpacing > 0)
            {
                tickSpacing = minorTickSpacing;
            }
            else if (majorTickSpacing > 0)
            {
                tickSpacing = majorTickSpacing;
            }

            if (tickSpacing != 0)
            {
                // If it's not on a tick, change the value
                if ((upperValue - slider.getMinimum()) % tickSpacing != 0)
                {
                    float temp = (float) (upperValue - slider.getMinimum()) / (float) tickSpacing;
                    int whichTick = Math.round(temp);
                    snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
                }

                if (snappedValue != upperValue)
                {
                    slider.setExtent(snappedValue - slider.getValue());
                }
            }
        }

        // Calculate upper thumb location.  The thumb is centered over its value on the track.
        if (slider.getOrientation() == JSlider.HORIZONTAL)
        {
            int upperPosition = xPositionForValue(slider.getValue() + slider.getExtent());
            upperThumbRect.x = upperPosition - (upperThumbRect.width / 2);
            upperThumbRect.y = trackRect.y;
        }
        else
        {
            int upperPosition = yPositionForValue(slider.getValue() + slider.getExtent());
            upperThumbRect.x = trackRect.x;
            upperThumbRect.y = upperPosition - (upperThumbRect.height / 2);
        }
    }

    @Override
    protected Dimension getThumbSize()
    {
        return new Dimension(12, 12);
    }

    @Override
    public void paint(Graphics g, JComponent c)
    {
        super.paint(g, c);

    }

    @Override
    public void paintTrack(Graphics g)
    {
        // Draw track.
        super.paintTrack(g);

        Rectangle trackBounds = trackRect;

        if (rangeSlider)
        {
            if (slider.getOrientation() == JSlider.HORIZONTAL)
            {
                // Determine position of selected range by moving from the middle
                // of one thumb to the other.
                int lowerX = thumbRect.x + (thumbRect.width / 2);
                int upperX = upperThumbRect.x + (upperThumbRect.width / 2);

                // Determine track position.
                int cy = (trackBounds.height / 2) - 2;

                // Save color and shift position.
                Color oldColor = g.getColor();
                g.translate(trackBounds.x, trackBounds.y + cy);

                // Draw selected range.
                g.setColor(rangeColor);
                for (int y = 0; y <= 3; y++)
                {
                    g.drawLine(lowerX - trackBounds.x, y, upperX - trackBounds.x, y);
                }

                // Restore position and color.
                g.translate(-trackBounds.x, -(trackBounds.y + cy));
                g.setColor(oldColor);

            }
            else
            {
                // Determine position of selected range by moving from the middle
                // of one thumb to the other.
                int lowerY = thumbRect.x + (thumbRect.width / 2);
                int upperY = upperThumbRect.x + (upperThumbRect.width / 2);

                // Determine track position.
                int cx = (trackBounds.width / 2) - 2;

                // Save color and shift position.
                Color oldColor = g.getColor();
                g.translate(trackBounds.x + cx, trackBounds.y);

                // Draw selected range.
                g.setColor(rangeColor);
                for (int x = 0; x <= 3; x++)
                {
                    g.drawLine(x, lowerY - trackBounds.y, x, upperY - trackBounds.y);
                }

                // Restore position and color.
                g.translate(-(trackBounds.x + cx), -trackBounds.y);
                g.setColor(oldColor);
            }
        }

        // Shifted the knob painting to here so it is painted with the track or not.
        Rectangle clipRect = g.getClipBounds();
        if (upperThumbSelected)
        {
            // Paint lower thumb first, then upper thumb.
            if (clipRect.intersects(thumbRect))
            {
                paintLowerThumb(g);
            }
            if (clipRect.intersects(upperThumbRect))
            {
                paintUpperThumb(g);
            }

        }
        else
        {
            // Paint upper thumb first, then lower thumb.
            if (clipRect.intersects(upperThumbRect))
            {
                paintUpperThumb(g);
            }
            if (clipRect.intersects(thumbRect))
            {
                paintLowerThumb(g);
            }
        }
    }

    @Override
    public void paintThumb(Graphics g)
    {
    }

    private void paintLowerThumb(Graphics g)
    {
        Rectangle knobBounds = thumbRect;
        int w = knobBounds.width;
        int h = knobBounds.height;

        // Create graphics copy.
        Graphics2D g2d = (Graphics2D) g.create();

        // Create default thumb shape.
        Shape thumbShape = createThumbShape(w - 1, h - 1);

        // Draw thumb.
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.translate(knobBounds.x, knobBounds.y);

        g2d.setColor(Color.CYAN);
        g2d.fill(thumbShape);

        g2d.setColor(Color.BLUE);
        g2d.draw(thumbShape);

        // Dispose graphics.
        g2d.dispose();
    }

    private void paintUpperThumb(Graphics g)
    {
        if (rangeSlider)
        {
            Rectangle knobBounds = upperThumbRect;
            int w = knobBounds.width;
            int h = knobBounds.height;

            // Create graphics copy.
            Graphics2D g2d = (Graphics2D) g.create();

            // Create default thumb shape.
            Shape thumbShape = createThumbShape(w - 1, h - 1);

            // Draw thumb.
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.translate(knobBounds.x, knobBounds.y);

            g2d.setColor(Color.orange);
            g2d.fill(thumbShape);

            g2d.setColor(Color.orange.darker().darker());
            g2d.draw(thumbShape);

            // Dispose graphics.
            g2d.dispose();
        }
    }

    private Shape createThumbShape(int width, int height)
    {
        Point p1 = new Point(0, 0);
        Point p2 = new Point(width, 0);
        Point p3 = new Point(width, (height / 2) + 1);
        Point p4 = new Point((width / 2) + 1, height);
        Point p5 = new Point(0, (height / 2) + 1);


        int[] xs =
        {
            p1.x, p2.x, p3.x, p4.x, p5.x
        };
        int[] ys =
        {
            p1.y, p2.y, p3.y, p4.y, p5.y
        };

        Polygon thumb = new Polygon(xs, ys, xs.length);

        return thumb;
    }

    private void setUpperThumbLocation(int x, int y)
    {
        Rectangle upperUnionRect = new Rectangle();
        upperUnionRect.setBounds(upperThumbRect);

        upperThumbRect.setLocation(x, y);

        SwingUtilities.computeUnion(upperThumbRect.x, upperThumbRect.y, upperThumbRect.width, upperThumbRect.height, upperUnionRect);
        slider.repaint(upperUnionRect.x, upperUnionRect.y, upperUnionRect.width, upperUnionRect.height);
    }

    @Override
    public void scrollByBlock(int direction)
    {
        synchronized (slider)
        {
            int blockIncrement = (slider.getMaximum() - slider.getMinimum()) / 10;
            if (blockIncrement <= 0 && slider.getMaximum() > slider.getMinimum())
            {
                blockIncrement = 1;
            }
            int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);

            if (upperThumbSelected)
            {
                int oldValue = ((RangeSlider) slider).getUpperValue();
                ((RangeSlider) slider).setUpperValue(oldValue + delta);
            }
            else
            {
                int oldValue = slider.getValue();
                slider.setValue(oldValue + delta);
            }
        }
    }

    @Override
    public void scrollByUnit(int direction)
    {
        synchronized (slider)
        {
            int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);

            if (upperThumbSelected)
            {
                int oldValue = ((RangeSlider) slider).getUpperValue();
                ((RangeSlider) slider).setUpperValue(oldValue + delta);
            }
            else
            {
                int oldValue = slider.getValue();
                slider.setValue(oldValue + delta);
            }
        }
    }

    public class ChangeHandler implements ChangeListener
    {

        public void stateChanged(ChangeEvent arg0)
        {
            if (!lowerDragging && !upperDragging)
            {
                calculateThumbLocation();
                slider.repaint();
            }
        }
    }

    public class RangeTrackListener extends TrackListener
    {

        @Override
        public void mousePressed(MouseEvent e)
        {
            if (!slider.isEnabled())
            {
                return;
            }

            currentMouseX = e.getX();
            currentMouseY = e.getY();

            if (slider.isRequestFocusEnabled())
            {
                slider.requestFocus();
            }

            // Determine which thumb is pressed.  If the upper thumb is selected (last one dragged), then check its position first;
            // otherwise check the position of the lower thumb first.
            boolean lowerPressed = false;
            boolean upperPressed = false;

            if (upperThumbSelected)
            {
                // Only enable upper slider movement if in range mode.
                if (upperThumbRect.contains(currentMouseX, currentMouseY) && rangeSlider)
                {
                    upperPressed = true;
                }
                else
                {
                    if (thumbRect.contains(currentMouseX, currentMouseY))
                    {
                        lowerPressed = true;
                    }
                }
            }
            else
            {
                if (thumbRect.contains(currentMouseX, currentMouseY))
                {
                    lowerPressed = true;
                }
                else
                {
                    // Only enable if in range mode.
                    if (upperThumbRect.contains(currentMouseX, currentMouseY) && rangeSlider)
                    {
                        upperPressed = true;
                    }
                }
            }

            // Handle lower thumb pressed.
            if (lowerPressed)
            {
                switch (slider.getOrientation())
                {
                    case JSlider.VERTICAL:
                        offset = currentMouseY - thumbRect.y;
                        break;
                    case JSlider.HORIZONTAL:
                        offset = currentMouseX - thumbRect.x;
                        break;
                }
                upperThumbSelected = false;
                lowerDragging = true;
                return;
            }
            lowerDragging = false;

            // Handle upper thumb pressed.
            if (upperPressed)
            {
                switch (slider.getOrientation())
                {
                    case JSlider.VERTICAL:
                        offset = currentMouseY - upperThumbRect.y;
                        break;
                    case JSlider.HORIZONTAL:
                        offset = currentMouseX - upperThumbRect.x;
                        break;
                }
                upperThumbSelected = true;
                upperDragging = true;
                return;
            }
            upperDragging = false;
        }

        @Override
        public void mouseReleased(MouseEvent e)
        {
            lowerDragging = false;
            upperDragging = false;
            slider.setValueIsAdjusting(false);
            super.mouseReleased(e);
        }

        @Override
        public void mouseDragged(MouseEvent e)
        {
            if (!slider.isEnabled())
            {
                return;
            }

            currentMouseX = e.getX();
            currentMouseY = e.getY();

            if (lowerDragging)
            {
                slider.setValueIsAdjusting(true);
                moveLowerThumb();

            }
            else if (upperDragging)
            {
                slider.setValueIsAdjusting(true);
                moveUpperThumb();
            }
        }

        @Override
        public boolean shouldScroll(int direction)
        {
            return false;
        }

        private void moveLowerThumb()
        {
            int thumbMiddle = 0;

            switch (slider.getOrientation())
            {
                case JSlider.VERTICAL:
                    int halfThumbHeight = thumbRect.height / 2;
                    int thumbTop = currentMouseY - offset;
                    int trackTop = trackRect.y;
                    int trackBottom = trackRect.y + (trackRect.height - 1);
                    int vMax = yPositionForValue(slider.getValue() + slider.getExtent());

                    // Apply bounds to thumb position.
                    if (drawInverted())
                    {
                        trackBottom = vMax;
                    }
                    else
                    {
                        trackTop = vMax;
                    }
                    thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
                    thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);

                    setThumbLocation(thumbRect.x, thumbTop);

                    // Update slider value.
                    thumbMiddle = thumbTop + halfThumbHeight;
                    slider.setValue(valueForYPosition(thumbMiddle));
                    break;

                case JSlider.HORIZONTAL:
                    int halfThumbWidth = thumbRect.width / 2;
                    int thumbLeft = currentMouseX - offset;
                    int trackLeft = trackRect.x;
                    int trackRight = trackRect.x + (trackRect.width - 1);
                    int hMax = xPositionForValue(slider.getValue() + slider.getExtent());

                    // Apply bounds to thumb position.
                    if (drawInverted())
                    {
                        trackLeft = hMax;
                    }
                    else
                    {
                        trackRight = hMax;
                    }
                    thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
                    thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);

                    setThumbLocation(thumbLeft, thumbRect.y);

                    // Update slider value.
                    thumbMiddle = thumbLeft + halfThumbWidth;
                    slider.setValue(valueForXPosition(thumbMiddle));
                    break;

                default:
                    return;
            }
        }

        private void moveUpperThumb()
        {
            int thumbMiddle = 0;

            switch (slider.getOrientation())
            {
                case JSlider.VERTICAL:
                    int halfThumbHeight = thumbRect.height / 2;
                    int thumbTop = currentMouseY - offset;
                    int trackTop = trackRect.y;
                    int trackBottom = trackRect.y + (trackRect.height - 1);
                    int vMin = yPositionForValue(slider.getValue());

                    // Apply bounds to thumb position.
                    if (drawInverted())
                    {
                        trackTop = vMin;
                    }
                    else
                    {
                        trackBottom = vMin;
                    }
                    thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
                    thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);

                    setUpperThumbLocation(thumbRect.x, thumbTop);

                    // Update slider extent.
                    thumbMiddle = thumbTop + halfThumbHeight;
                    slider.setExtent(valueForYPosition(thumbMiddle) - slider.getValue());
                    break;

                case JSlider.HORIZONTAL:
                    int halfThumbWidth = thumbRect.width / 2;
                    int thumbLeft = currentMouseX - offset;
                    int trackLeft = trackRect.x;
                    int trackRight = trackRect.x + (trackRect.width - 1);
                    int hMin = xPositionForValue(slider.getValue());

                    // Apply bounds to thumb position.
                    if (drawInverted())
                    {
                        trackRight = hMin;
                    }
                    else
                    {
                        trackLeft = hMin;
                    }
                    thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
                    thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);

                    setUpperThumbLocation(thumbLeft, thumbRect.y);

                    // Update slider extent.
                    thumbMiddle = thumbLeft + halfThumbWidth;
                    slider.setExtent(valueForXPosition(thumbMiddle) - slider.getValue());
                    break;

                default:
                    return;
            }
        }
    }
}

The x position for the track rectangle (which is also the position of the first tick mark) , to align your graph to the slider, is gotten by calling:

rangeSlider1.getRangeUI().getTrackRect()

I also didn't like their round knobs and changed them to the more traditional shape.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top