/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>
Website : http://www.gephi.org

This file is part of Gephi.

DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.

Copyright 2011 Gephi Consortium. All rights reserved.

The contents of this file are subject to the terms of either the GNU
General Public License Version 3 only ("GPL") or the Common
Development and Distribution License("CDDL") (collectively, the
"License"). You may not use this file except in compliance with the
License. You can obtain a copy of the License at
http://gephi.org/about/legal/license-notice/
or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the
specific language governing permissions and limitations under the
License.  When distributing the software, include this License Header
Notice in each file and include the License files at
/cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the
License Header, with the fields enclosed by brackets [] replaced by
your own identifying information:
"Portions Copyrighted [year] [name of copyright owner]"

If you wish your version of this file to be governed by only the CDDL
or only the GPL Version 3, indicate your decision by adding
"[Contributor] elects to include this software in this distribution
under the [CDDL or GPL Version 3] license." If you do not indicate a
single choice of license, a recipient has the option to distribute
your version of this file under either the CDDL, the GPL Version 3 or
to extend the choice of license to its licensees as provided above.
However, if you add GPL Version 3 code and therefore, elected the GPL
Version 3 license, then the option applies only if the new code is
made subject to such option by the copyright holder.

Contributor(s):

Portions Copyrighted 2011 Gephi Consortium.
 */
package org.gephi.ui.components;

import com.sun.java.swing.plaf.windows.WindowsSliderUI;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSliderUI;
import javax.swing.plaf.basic.BasicSliderUI.TrackListener;

/**
 *
 * @author Mathieu Bastian
 */
public class JRangeSlider extends JSlider {

    /**
     * Constructs a RangeSlider with default minimum and maximum values of 0
     * and 100.
     */
    public JRangeSlider() {
    }

    /**
     * Constructs a RangeSlider with the specified default minimum and maximum
     * values.
     * @param min minimum
     * @param max maximum
     */
    public JRangeSlider(int min, int max) {
        super(min, max);
    }

    /**
     * Overrides the superclass method to install the UI delegate to draw two
     * thumbs.
     */
    @Override
    public void updateUI() {
        ComponentUI uiv = UIManager.getUI(this);
        if (uiv instanceof WindowsSliderUI) {
            uiv = new JRangeSliderWindowsUI(this);
        } else {
            uiv = new JRangeSliderBasicUI(this);
        }
        setUI(uiv);
        // Update UI for slider labels.  This must be called after updating the
        // UI of the slider.  Refer to JSlider.updateUI().
        updateLabelUIs();
    }

    /**
     * Returns the lower value in the range.
     */
    @Override
    public int getValue() {
        return super.getValue();
    }

    /**
     * Sets the lower value in the range.
     * @param value lower value
     */
    @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());
    }

    /**
     * Returns the upper value in the range.
     * @return upper value
     */
    public int getUpperValue() {
        return getValue() + getExtent();
    }

    /**
     * Sets the upper value in the range.
     * @param value upper value
     */
    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 void setValues(int low, int high) {
        boolean changeLow = low != getValue();
        boolean changeHigh = high != getUpperValue();
        if (changeHigh || changeLow) {
            int extent = getExtent();

            extent = Math.min(Math.max(0, high - low), getMaximum() - low);

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

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

    private static class JRangeSliderWindowsUI extends WindowsSliderUI {

        /** Color of selected range. */
        //private Color rangeColor = new Color(168,223,85,178);//nice green
        private final Color rangeColor = new Color(49, 220, 251, 178);//nice vista blue
        /** Location and size of thumb for upper value. */
        private Rectangle upperThumbRect;
        /** Indicator that determines whether upper thumb is selected. */
        private boolean upperThumbSelected;
        /** Indicator set when lower thumb is being dragged. */
        private transient boolean lowerDragging;
        /** Indicator set when upper thumb is being dragged. */
        private transient boolean upperDragging;

        /**
         * Constructs a RangeSliderUI for the specified slider component.
         * @param b RangeSlider
         */
        public JRangeSliderWindowsUI(JSlider slider) {
            super(slider);

            /*Color rColor = UIManager.getColor("Slider.focus");
            if(rColor!=null) {
            rangeColor = rColor;
            }*/
        }

        /**
         * Installs this UI delegate on the specified component.
         */
        @Override
        public void installUI(JComponent c) {
            upperThumbRect = new Rectangle();
            super.installUI(c);
        }

        /**
         * Creates a listener to handle track events in the specified slider.
         */
        @Override
        protected TrackListener createTrackListener(JSlider slider) {
            return new RangeTrackListener();
        }

        /**
         * Creates a listener to handle change events in the specified slider.
         */
        @Override
        protected ChangeListener createChangeListener(JSlider slider) {
            return new ChangeHandler();
        }

        /**
         * Updates the dimensions for both thumbs.
         */
        @Override
        protected void calculateThumbSize() {
            // Call superclass method for lower thumb size.
            super.calculateThumbSize();

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

        /**
         * Updates the locations for both thumbs.
         */
        @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);
            }
        }

        /**
         * Paints the slider.  The selected thumb is always painted on top of the
         * other thumb.
         */
        @Override
        public void paint(Graphics g, JComponent c) {
            super.paint(g, c);
            Rectangle r = thumbRect;
            thumbRect = upperThumbRect;
            paintThumb(g);
            thumbRect = r;
            //paintThumb(g);
        /*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);
            }
            }*/
        }

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

            Rectangle trackBounds = trackRect;

            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.y + (thumbRect.height / 2);
                int upperY = upperThumbRect.y + (upperThumbRect.height / 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);
            }
        }

        /**
         * Paints the thumb for the lower value using the specified graphics object.
         */
        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();
        }

        /**
         * Paints the thumb for the upper value using the specified graphics object.
         */
        private void paintUpperThumb(Graphics g) {
            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.PINK);
            g2d.fill(thumbShape);

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

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

        /**
         * Returns a Shape representing a thumb.
         */
        private Shape createThumbShape(int width, int height) {
            // Use circular shape.
            Ellipse2D shape = new Ellipse2D.Double(0, 0, width, height);
            return shape;
        }

        /**
         * Sets the location of the upper thumb, and repaints the slider.  This is
         * called when the upper thumb is dragged to repaint the slider.  The
         * <code>setThumbLocation()</code> method performs the same task for the
         * lower 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);
        }

        /**
         * Moves the selected thumb in the specified direction by a block increment.
         * This method is called when the user presses the Page Up or Down keys.
         */
        @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 = ((JRangeSlider) slider).getUpperValue();
                    ((JRangeSlider) slider).setUpperValue(oldValue + delta);
                } else {
                    int oldValue = slider.getValue();
                    slider.setValue(oldValue + delta);
                }
            }
        }

        /**
         * Moves the selected thumb in the specified direction by a unit increment.
         * This method is called when the user presses one of the arrow keys.
         */
        @Override
        public void scrollByUnit(int direction) {
            synchronized (slider) {
                int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);

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

        /**
         * Listener to handle model change events.  This calculates the thumb
         * locations and repaints the slider if the value change is not caused by
         * dragging a thumb.
         */
        public class ChangeHandler implements ChangeListener {

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

        /**
         * Listener to handle mouse movements in the slider track.
         */
        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) {
                    if (upperThumbRect.contains(currentMouseX, currentMouseY)) {
                        upperPressed = true;
                    } else if (thumbRect.contains(currentMouseX, currentMouseY)) {
                        lowerPressed = true;
                    }
                } else {
                    if (thumbRect.contains(currentMouseX, currentMouseY)) {
                        lowerPressed = true;
                    } else if (upperThumbRect.contains(currentMouseX, currentMouseY)) {
                        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;
            }

            /**
             * Moves the location of the lower thumb, and sets its corresponding
             * value in the slider.
             */
            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;
                }
            }

            /**
             * Moves the location of the upper thumb, and sets its corresponding
             * value in the slider.
             */
            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;
                }
            }
        }
    }

    private static class JRangeSliderBasicUI extends BasicSliderUI {

        /** Color of selected range. */
        //private Color rangeColor = new Color(168,223,85,178);//nice green
        private Color rangeColor = new Color(49, 220, 251, 178);//nice vista blue
        /** Location and size of thumb for upper value. */
        private Rectangle upperThumbRect;
        /** Indicator that determines whether upper thumb is selected. */
        private boolean upperThumbSelected;
        /** Indicator set when lower thumb is being dragged. */
        private transient boolean lowerDragging;
        /** Indicator set when upper thumb is being dragged. */
        private transient boolean upperDragging;

        /**
         * Constructs a RangeSliderUI for the specified slider component.
         * @param b RangeSlider
         */
        public JRangeSliderBasicUI(JSlider slider) {
            super(slider);

            /*Color rColor = UIManager.getColor("Slider.focus");
            if(rColor!=null) {
            rangeColor = rColor;
            }*/
        }

        /**
         * Installs this UI delegate on the specified component.
         */
        @Override
        public void installUI(JComponent c) {
            upperThumbRect = new Rectangle();
            super.installUI(c);
        }

        /**
         * Creates a listener to handle track events in the specified slider.
         */
        @Override
        protected TrackListener createTrackListener(JSlider slider) {
            return new RangeTrackListener();
        }

        /**
         * Creates a listener to handle change events in the specified slider.
         */
        @Override
        protected ChangeListener createChangeListener(JSlider slider) {
            return new ChangeHandler();
        }

        /**
         * Updates the dimensions for both thumbs.
         */
        @Override
        protected void calculateThumbSize() {
            // Call superclass method for lower thumb size.
            super.calculateThumbSize();

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

        /**
         * Updates the locations for both thumbs.
         */
        @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);
            }
        }

        /**
         * Paints the slider.  The selected thumb is always painted on top of the
         * other thumb.
         */
        @Override
        public void paint(Graphics g, JComponent c) {
            super.paint(g, c);
            Rectangle r = thumbRect;
            thumbRect = upperThumbRect;
            paintThumb(g);
            thumbRect = r;
            //paintThumb(g);
        /*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);
            }
            }*/
        }

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

            Rectangle trackBounds = trackRect;

            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.y + (thumbRect.height / 2);
                int upperY = upperThumbRect.y + (upperThumbRect.height / 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);
            }
        }

        /**
         * Paints the thumb for the lower value using the specified graphics object.
         */
        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();
        }

        /**
         * Paints the thumb for the upper value using the specified graphics object.
         */
        private void paintUpperThumb(Graphics g) {
            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.PINK);
            g2d.fill(thumbShape);

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

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

        /**
         * Returns a Shape representing a thumb.
         */
        private Shape createThumbShape(int width, int height) {
            // Use circular shape.
            Ellipse2D shape = new Ellipse2D.Double(0, 0, width, height);
            return shape;
        }

        /**
         * Sets the location of the upper thumb, and repaints the slider.  This is
         * called when the upper thumb is dragged to repaint the slider.  The
         * <code>setThumbLocation()</code> method performs the same task for the
         * lower 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);
        }

        /**
         * Moves the selected thumb in the specified direction by a block increment.
         * This method is called when the user presses the Page Up or Down keys.
         */
        @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 = ((JRangeSlider) slider).getUpperValue();
                    ((JRangeSlider) slider).setUpperValue(oldValue + delta);
                } else {
                    int oldValue = slider.getValue();
                    slider.setValue(oldValue + delta);
                }
            }
        }

        /**
         * Moves the selected thumb in the specified direction by a unit increment.
         * This method is called when the user presses one of the arrow keys.
         */
        @Override
        public void scrollByUnit(int direction) {
            synchronized (slider) {
                int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);

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

        /**
         * Listener to handle model change events.  This calculates the thumb
         * locations and repaints the slider if the value change is not caused by
         * dragging a thumb.
         */
        public class ChangeHandler implements ChangeListener {

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

        /**
         * Listener to handle mouse movements in the slider track.
         */
        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) {
                    if (upperThumbRect.contains(currentMouseX, currentMouseY)) {
                        upperPressed = true;
                    } else if (thumbRect.contains(currentMouseX, currentMouseY)) {
                        lowerPressed = true;
                    }
                } else {
                    if (thumbRect.contains(currentMouseX, currentMouseY)) {
                        lowerPressed = true;
                    } else if (upperThumbRect.contains(currentMouseX, currentMouseY)) {
                        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;
            }

            /**
             * Moves the location of the lower thumb, and sets its corresponding
             * value in the slider.
             */
            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;
                }
            }

            /**
             * Moves the location of the upper thumb, and sets its corresponding
             * value in the slider.
             */
            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;
                }
            }
        }
    }
}
