/*
 * @(#)PDFScrollBarUI.java  1.0.2  2006-04-23
 *
 * Copyright (c) 2005 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */

package ch.randelshofer.pdf.plaf;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
/**
 * PDFScrollBarUI.
 *
 * @author  Werner Randelshofer
 * @version 1.0.1 2005-11-29 Tweaked layoutVScrollBar.
 * <br>1.0 September 11, 2005 Created.
 */
public class PDFScrollBarUI extends BasicScrollBarUI {
    
    protected Dimension smallMinimumThumbSize;
    protected boolean isPlaceButtonsTogether;
    /**
     * Creates a new instance.
     */
    public PDFScrollBarUI() {
    }
    
    public static ComponentUI createUI(JComponent c)    {
        return new PDFScrollBarUI();
    }
    
    @Override
    protected void installDefaults() {
        super.installDefaults();
        PDFLookAndFeel plaf = PDFLookAndFeel.getInstance();
        smallMinimumThumbSize = (Dimension)plaf.get("ScrollBar.smallMinimumThumbSize");
        updatePlaceButtonsTogether();
        LookAndFeel.installColorsAndFont(scrollbar, "ScrollBar.background", "ScrollBar.foreground", "ScrollBar.font");
        scrollbar.setFocusable(false);
    }
    private void updatePlaceButtonsTogether() {
        PDFLookAndFeel plaf = PDFLookAndFeel.getInstance();
        Object value = scrollbar.getClientProperty("Quaqua.ScrollBar.placeButtonsTogether");
        if (value == null) {
            isPlaceButtonsTogether = plaf.getBoolean("ScrollBar.placeButtonsTogether");
        } else {
            isPlaceButtonsTogether = value.equals(Boolean.TRUE);
        }
    }
    private boolean isPlaceButtonsTogether() {
        return isPlaceButtonsTogether;
    }
    
    protected TrackListener createTrackListener(){
        return new PDFTrackListener();
    }
    protected ArrowButtonListener createArrowButtonListener(){
        return new PDFArrowButtonListener();
    }
    protected PropertyChangeListener createPropertyChangeListener() {
        return new PDFPropertyChangeHandler();
    }
    
    protected JButton createDecreaseButton(int orientation)  {
        return new PDFArrowButton(scrollbar);
    }
    
    protected JButton createIncreaseButton(int orientation)  {
        return new PDFArrowButton(scrollbar);
    }
    
    
    protected ScrollListener createScrollListener(){
        return new PDFScrollListener();
    }
    /**
     * Returns true, if the scrollbar is using the small size style.
     */
    private boolean isSmall() {
        return false;
    }
    
    /**
     * Return the smallest acceptable size for the thumb.  If the scrollbar
     * becomes so small that this size isn't available, the thumb will be
     * hidden.
     * <p>
     * <b>Warning </b>: the value returned by this method should not be
     * be modified, it's a shared static constant.
     *
     * @return The smallest acceptable size for the thumb.
     * @see #getMaximumThumbSize
     */
    protected Dimension getMinimumThumbSize() {
        return isSmall()
        ? smallMinimumThumbSize
        : minimumThumbSize;
    }
    @Override
    public Dimension getMaximumSize(JComponent c) {
        if (isSmall()) {
            return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
            ? new Dimension(11, Integer.MAX_VALUE)
            : new Dimension(Integer.MAX_VALUE, 11);
        } else {
            return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
            ? new Dimension(14, Integer.MAX_VALUE)
            : new Dimension(Integer.MAX_VALUE, 14);
        }
    }
    @Override
    public Dimension getPreferredSize(JComponent c) {
        if (isSmall()) {
            return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
            ? new Dimension(11, 24)
            : new Dimension(24, 11);
        } else {
            return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
            ? new Dimension(14, 32)
            : new Dimension(32, 14);
        }
    }
    
    /**
     * Return the largest acceptable size for the thumb.  To create a fixed
     * size thumb one make this method and <code>getMinimumThumbSize</code>
     * return the same value.
     * <p>
     * <b>Warning </b>: the value returned by this method should not be
     * be modified, it's a shared static constant.
     *
     * @return The largest acceptable size for the thumb.
     * @see #getMinimumThumbSize
     */
    protected Dimension getMaximumThumbSize()	{
        return maximumThumbSize;
    }
    
    public void paint(Graphics g, JComponent c) {
        super.paint(g, c);
    }
    
    @Override
    protected void paintThumb(Graphics gr, JComponent c, Rectangle thumbBounds) {
        if(thumbBounds.isEmpty())	{
            return;
        }
        Graphics2D g = (Graphics2D) gr;
        
        boolean isSmall = isSmall();
        PDFLookAndFeel plaf = PDFLookAndFeel.getInstance();
        
            if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
                Border b = plaf.getBorder("ScrollBar.vThumbBorder");
                b.paintBorder(scrollbar, g, thumbBounds.x, thumbBounds.y, thumbBounds.width, thumbBounds.height);
            } else {
                Border b = plaf.getBorder("ScrollBar.hThumbBorder");
                b.paintBorder(scrollbar, g, thumbBounds.x, thumbBounds.y, thumbBounds.width, thumbBounds.height);
            }
    }
    
    /**
     * This method actually paints the track plus the button artwork.
     */
    @Override
    protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) {
        Dimension sbSize = scrollbar.getSize();
        Insets sbInsets = scrollbar.getInsets();
        Rectangle contentBounds = new Rectangle(
        sbInsets.left, sbInsets.top,
        sbSize.width - sbInsets.left - sbInsets.right,
        sbSize.height - sbInsets.top - sbInsets.bottom
        );
        
        Border trackAndButtons = getTrackAndButtonsBorder();
        Insets tbInsets = trackAndButtons.getBorderInsets(scrollbar);
        
        if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
            if (isPlaceButtonsTogether()
            && contentBounds.height < tbInsets.top + tbInsets.bottom) {
                // Special treatment, if track and buttons do
                // not fit into available space
                Graphics clipped = g.create(contentBounds.x, contentBounds.y, contentBounds.width, contentBounds.height);
                int buttonsH = incrButton.getPreferredSize().height * 2;
                trackAndButtons.paintBorder(c, g,
                contentBounds.x,
                contentBounds.y + contentBounds.height - tbInsets.top - tbInsets.bottom + Math.max(0, (buttonsH - contentBounds.height) / 2),
                contentBounds.width,
                tbInsets.top + tbInsets.bottom
                );
                clipped.dispose();
            } else {
                trackAndButtons.paintBorder(c, g, contentBounds.x, contentBounds.y, contentBounds.width, contentBounds.height);
            }
        } else {
            if (isPlaceButtonsTogether()
            && contentBounds.width < tbInsets.left + tbInsets.right) {
                // Special treatment, if track and buttons do
                // not fit into available space
                //trackAndButtons.paintBorder(c, g, contentBounds.x, contentBounds.y, contentBounds.width, contentBounds.height);
                Graphics clipped = g.create(contentBounds.x, contentBounds.y, contentBounds.width, contentBounds.height);
                int buttonsW = incrButton.getPreferredSize().width * 2;
                trackAndButtons.paintBorder(c, g,
                contentBounds.x + contentBounds.width - tbInsets.left - tbInsets.right + Math.max(0, (buttonsW - contentBounds.width) / 2),
                contentBounds.y,
                tbInsets.left + tbInsets.right,
                contentBounds.height
                );
                clipped.dispose();
            } else {
                trackAndButtons.paintBorder(c, g, contentBounds.x, contentBounds.y, contentBounds.width, contentBounds.height);
            }
        }
    }
    
    /**
     * We use a border to fill the background of the scroll bar.
     * The border contains the track and the buttons in their current visual
     * representation depending on the various states of the scroll bar
     * ('enabled','pressed','inactive').
     */
    protected Border getTrackAndButtonsBorder() {
        char vh = (scrollbar.getOrientation() == JScrollBar.VERTICAL) ? 'v' : 'h';
        boolean isSmall = isSmall();
        PDFLookAndFeel plaf = PDFLookAndFeel.getInstance();
        
        // Return empty track if extent of scroll bar fully visible
        if (scrollbar.getMinimum() + scrollbar.getVisibleAmount() == scrollbar.getMaximum()) {
            return plaf.getBorder("ScrollBar."+vh+"Track");
        }
        
        // compute button index
        int buttonsIndex;
        
        // Scroll Bar arrows are always shown in active state by some Mac OS X
        // Applications and by some they are grayed on inactive windows.
        //if (scrollbar.isEnabled() && QuaquaUtilities.isOnActiveWindow(scrollbar)) {
        if (scrollbar.isEnabled()) {
            if (incrButton.getModel().isArmed() && incrButton.getModel().isPressed()) {
                buttonsIndex = 1;
            } else if (decrButton.getModel().isArmed() && decrButton.getModel().isPressed()) {
                buttonsIndex = 3;
            } else {
                buttonsIndex = 0;
            }
        } else {
            buttonsIndex = 2;
        }
        
        Border[] borders;
        if (isPlaceButtonsTogether()) {
            borders = (Border[]) plaf.get("ScrollBar.tog."+vh+"Buttons");
        } else {
            borders = (Border[]) plaf.get("ScrollBar.sep."+vh+"Buttons");
        }
        return (borders == null) ? null : borders[buttonsIndex];
    }
    /**
     * Indicates whether the user can absolutely position the offset with
     * a mouse click (depending on the settings in "Appearance" panel of
     * " the "System Preferences" application).
     * <p>The return value is determined from the plaf property
     * ScrollBar.allowsAbsolutePositioning.
     */
    public boolean getSupportsAbsolutePositioning() {
        PDFLookAndFeel plaf = PDFLookAndFeel.getInstance();
        return plaf.getBoolean("ScrollBar.supportsAbsolutePositioning");
    }
    
    protected void layoutVScrollbar(JScrollBar sb)  {
        Dimension sbSize = sb.getSize();
        Insets sbInsets = sb.getInsets();
        
        // Width and left edge of the buttons and thumb.
        int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
        int itemX = sbInsets.left;
        
        /**
         * Hide thumb and buttons if the complete extent is shown.
         * Note that setting the
         * thumbs bounds will cause a repaint.
         */
        if (sb.getMinimum() + sb.getVisibleAmount() == sb.getMaximum()) {
            trackRect.setBounds(sbInsets.left, sbInsets.top, itemW, sbSize.height - (sbInsets.top + sbInsets.bottom));
            decrButton.setBounds(0, 0, 0, 0);
            incrButton.setBounds(0, 0, 0, 0);
            setThumbBounds(0, 0, 0, 0);
            return;
        }
        
        boolean isSmall = isSmall();
        
        int incrButtonH, decrButtonH;
        incrButtonH = decrButtonH = (isSmall) ? 12 : 16;
        
        // The thumb must fit within the height left over after we
        // subtract the preferredSize of the buttons and the insets.
        int sbInsetsH = sbInsets.top + sbInsets.bottom;
        int sbButtonsH = decrButtonH + incrButtonH;
        
        int trackY;
        int trackH;
        
        int thumbH;
        int thumbY;
        
        int incrButtonY;
        int decrButtonY;
        
        int sbAvailButtonH = (sbSize.height - sbInsetsH);
        
        // Compute y-locations and heights
        if (isPlaceButtonsTogether()) {
            incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
            decrButtonY = incrButtonY - decrButtonH;
            
            // If the buttons don't fit, allocate half of the available
            // space to each.
            if (sbAvailButtonH < sbButtonsH) {
                incrButtonH = sbAvailButtonH / 2;
                decrButtonH = sbAvailButtonH - incrButtonH;
                incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
                decrButtonY = sbInsets.top;
            }
            
            trackY = sbInsets.top + ((isSmall) ? 4 : 5); // depends on size style
            trackH = decrButtonY - trackY + ((isSmall) ? 0 : 3); // depends on size style
        } else {
            decrButtonY = sbInsets.top;
            incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
            
            // If the buttons don't fit, allocate half of the available
            // space to each.
            if (sbAvailButtonH < sbButtonsH) {
                incrButtonH = decrButtonH = sbAvailButtonH / 2;
                incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
            }
            
            trackY = decrButtonY + decrButtonH + ((isSmall) ? 0 : -1);
            trackH = incrButtonY - trackY + ((isSmall) ? 2 : 3);
        }
        decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
        incrButton.setBounds(itemX, incrButtonY, itemW, incrButtonH);
        trackRect.setBounds(itemX, trackY, itemW, trackH);
        
        
        /* Compute the height and origin of the thumb.   The case
         * where the thumb is at the bottom edge is handled specially
         * to avoid numerical problems in computing thumbY.  Enforce
         * the thumbs min/max dimensions.  If the thumb doesn't
         * fit in the track (trackH) we'll hide it later.
         */
        float min = sb.getMinimum();
        float extent = sb.getVisibleAmount();
        float range = sb.getMaximum() - min;
        float value = sb.getValue();
        float ftrackH = (float) trackH;
        
        thumbH = (range <= 0)
        ? getMaximumThumbSize().height : (int)(ftrackH * (extent / range));
        thumbH = Math.max(thumbH, getMinimumThumbSize().height);
        thumbH = Math.min(thumbH, getMaximumThumbSize().height);
        
        thumbY = trackY + trackH - thumbH;
        if (sb.getValue() < (sb.getMaximum() - sb.getVisibleAmount())) {
            float thumbRange = ftrackH - thumbH;
            thumbY = (int)(0.5f + (thumbRange * ((value - min) / (range - extent))));
            thumbY += trackY;
        }
        
        /* If the thumb isn't going to fit, zero it's bounds.  Otherwise
         * make sure it fits into the track.  Note that setting the
         * thumbs bounds will cause a repaint.
         */
        if(thumbH >= trackH)	{
            setThumbBounds(0, 0, 0, 0);
        } else {
            setThumbBounds(itemX, thumbY, itemW, thumbH);
        }
    }
    
    
    protected void layoutHScrollbar(JScrollBar sb) {
        Dimension sbSize = sb.getSize();
        Insets sbInsets = sb.getInsets();
        
        /* Height and top edge of the buttons and thumb.
         */
        int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
        int itemY = sbInsets.top;
        
        /**
         * Hide thumb and buttons if the complete extent is shown.
         * Note that setting the
         * thumbs bounds will cause a repaint.
         */
        if (sb.getMinimum() + sb.getVisibleAmount() == sb.getMaximum()) {
            trackRect.setBounds(sbInsets.left, sbInsets.top, sbSize.width - (sbInsets.left + sbInsets.right), itemH);
            decrButton.setBounds(0, 0, 0, 0);
            incrButton.setBounds(0, 0, 0, 0);
            setThumbBounds(0, 0, 0, 0);
            return;
        }
        
        boolean isSmall = isSmall();
        
        int leftButtonW, rightButtonW;
        leftButtonW = rightButtonW = (isSmall) ? 12 : 16;
        
        // The thumb must fit within the width left over after we
        // subtract the preferredSize of the buttons and the insets.
        int sbInsetsW = sbInsets.left + sbInsets.right;
        int sbButtonsW = leftButtonW + rightButtonW;
        
        int trackX;
        int trackW;
        
        int thumbW;
        int thumbX;
        
        int rightButtonX;
        int leftButtonX;
        
        int sbAvailButtonW = (sbSize.width - sbInsetsW);
        boolean ltr;
        
        if (isPlaceButtonsTogether()) {
            ltr = true;
            
            // Nominal locations of the buttons, assuming their preferred
            // size will fit.
            rightButtonX = sbSize.width - (sbInsets.right + rightButtonW);
            leftButtonX = rightButtonX - leftButtonW;
            
            // If the buttons don't fit, allocate half of the available
            // space to each and move the right one over.
            if (sbAvailButtonW < sbButtonsW) {
                leftButtonW = sbAvailButtonW / 2;
                rightButtonW = sbAvailButtonW - leftButtonW;
                leftButtonX = sbInsets.left;
                rightButtonX = leftButtonX + leftButtonW;
            }
            
            trackX = sbInsets.left + 5;
            trackW = leftButtonX - trackX;
            if (! isSmall) trackW += 1;
        } else {
            ltr = sb.getComponentOrientation().isLeftToRight();
            if (! ltr) {
                int helper = leftButtonW;
                leftButtonW = rightButtonW;
                rightButtonW = helper;
            }
            
            // Nominal locations of the buttons, assuming their preferred
            // size will fit.
            leftButtonX = sbInsets.left;
            rightButtonX = sbSize.width - (sbInsets.right + rightButtonW);
            
            // If the buttons don't fit, allocate half of the available
            // space to each and move the right one over.
            if (sbAvailButtonW < sbButtonsW) {
                rightButtonW = leftButtonW = sbAvailButtonW / 2;
                rightButtonX = sbSize.width - (sbInsets.right + rightButtonW);
            }
            
            trackX = leftButtonX + leftButtonW - 1;
            trackW = rightButtonX - trackX + 1;
        }
        (ltr ? decrButton : incrButton).setBounds(leftButtonX, itemY, leftButtonW, itemH);
        (ltr ? incrButton : decrButton).setBounds(rightButtonX, itemY, rightButtonW, itemH);
        trackRect.setBounds(trackX, itemY, trackW, itemH);
        
        /* Compute the width and origin of the thumb.  Enforce
         * the thumbs min/max dimensions.  The case where the thumb
         * is at the right edge is handled specially to avoid numerical
         * problems in computing thumbX.  If the thumb doesn't
         * fit in the track (trackH) we'll hide it later.
         */
        float min = sb.getMinimum();
        float max = sb.getMaximum();
        float extent = sb.getVisibleAmount();
        float range = max - min;
        float value = sb.getValue();
        float ftrackW = (float) trackW;
        
        thumbW = (range <= 0)
        ? getMaximumThumbSize().width : (int)(ftrackW * (extent / range));
        thumbW = Math.max(thumbW, getMinimumThumbSize().width);
        thumbW = Math.min(thumbW, getMaximumThumbSize().width);
        
        thumbX = trackX + trackW - thumbW;
        if (sb.getValue() < (max - sb.getVisibleAmount())) {
            float thumbRange = ftrackW - thumbW;
            thumbX = (int)(0.5f + (thumbRange * ((value - min) / (range - extent))));
            thumbX += trackX;
        }
        
        /* Make sure the thumb fits between the buttons.  Note
         * that setting the thumbs bounds causes a repaint.
         */
        if (thumbW >= (int)trackW) {
            setThumbBounds(0, 0, 0, 0);
        } else {
            setThumbBounds(thumbX, itemY, thumbW, itemH);
        }
    }
    
    protected class PDFTrackListener extends TrackListener {
        protected transient int offset;
        protected transient int currentMouseX, currentMouseY;
        protected transient int direction = +1;
        
        public void mouseReleased(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e) ||
            (!getSupportsAbsolutePositioning() &&
            SwingUtilities.isMiddleMouseButton(e)))
                return;
            if(!scrollbar.isEnabled())
                return;
            
            Rectangle r = getTrackBounds();
            scrollbar.repaint(r.x, r.y, r.width, r.height);
            
            trackHighlight = NO_HIGHLIGHT;
            isDragging = false;
            offset = 0;
            scrollTimer.stop();
            scrollbar.setValueIsAdjusting(false);
        }
        
        
        /**
         * If the mouse is pressed above the "thumb" component
         * then reduce the scrollbars value by one page ("page up"),
         * otherwise increase it by one page.  If there is no
         * thumb then page up if the mouse is in the upper half
         * of the track.
         */
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e) ||
            (!getSupportsAbsolutePositioning() &&
            SwingUtilities.isMiddleMouseButton(e)))
                return;
            if(!scrollbar.isEnabled())
                return;
            
            if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
                scrollbar.requestFocus();
            }
            
            scrollbar.setValueIsAdjusting(true);
            
            currentMouseX = e.getX();
            currentMouseY = e.getY();
            
            // Clicked in the Thumb area?
            if(getThumbBounds().contains(currentMouseX, currentMouseY))	{
                switch (scrollbar.getOrientation()) {
                    case JScrollBar.VERTICAL:
                        offset = currentMouseY - getThumbBounds().y;
                        break;
                    case JScrollBar.HORIZONTAL:
                        offset = currentMouseX - getThumbBounds().x;
                        break;
                }
                isDragging = true;
                return;
            } else if (getSupportsAbsolutePositioning()
            || SwingUtilities.isMiddleMouseButton(e)) {
                switch (scrollbar.getOrientation()) {
                    case JScrollBar.VERTICAL:
                        offset = getThumbBounds().height / 2;
                        break;
                    case JScrollBar.HORIZONTAL:
                        offset = getThumbBounds().width / 2;
                        break;
                }
                isDragging = true;
                setValueFrom(e);
                return;
            }
            isDragging = false;
            
            Dimension sbSize = scrollbar.getSize();
            direction = +1;
            
            switch (scrollbar.getOrientation()) {
                case JScrollBar.VERTICAL:
                    if (getThumbBounds().isEmpty()) {
                        int scrollbarCenter = sbSize.height / 2;
                        direction = (currentMouseY < scrollbarCenter) ? -1 : +1;
                    } else {
                        int thumbY = getThumbBounds().y;
                        direction = (currentMouseY < thumbY) ? -1 : +1;
                    }
                    break;
                case JScrollBar.HORIZONTAL:
                    if (getThumbBounds().isEmpty()) {
                        int scrollbarCenter = sbSize.width / 2;
                        direction = (currentMouseX < scrollbarCenter) ? -1 : +1;
                    } else {
                        int thumbX = getThumbBounds().x;
                        direction = (currentMouseX < thumbX) ? -1 : +1;
                    }
                    if (!scrollbar.getComponentOrientation().isLeftToRight()) {
                        direction = -direction;
                    }
                    break;
            }
            scrollByBlock(direction);
            
            scrollTimer.stop();
            scrollListener.setDirection(direction);
            scrollListener.setScrollByBlock(true);
            startScrollTimerIfNecessary();
        }
        
        
        /**
         * Set the models value to the position of the thumb's top of Vertical
         * scrollbar, or the left/right of Horizontal scrollbar in
         * left-to-right/right-to-left scrollbar relative to the origin of the
         * track.
         */
        public void mouseDragged(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e) ||
            (!getSupportsAbsolutePositioning() &&
            SwingUtilities.isMiddleMouseButton(e)))
                return;
            if(!scrollbar.isEnabled() || getThumbBounds().isEmpty()) {
                return;
            }
            if (isDragging) {
                setValueFrom(e);
            } else {
                currentMouseX = e.getX();
                currentMouseY = e.getY();
                startScrollTimerIfNecessary();
            }
        }
        
        private void setValueFrom(MouseEvent e) {
            BoundedRangeModel model = scrollbar.getModel();
            Rectangle thumbR = getThumbBounds();
            Rectangle trackR = getTrackBounds();
            float trackLength;
            int thumbMin, thumbMax, thumbPos;
            
            if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
                thumbMin = trackR.y;
                thumbMax = trackR.y + trackR.height - thumbR.height;
                
                thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getY() - offset)));
                setThumbBounds(thumbR.x, thumbPos, thumbR.width, thumbR.height);
                trackLength = trackR.height;
            } else {
                /*
                if (scrollbar.getComponentOrientation().isLeftToRight()) {
                    thumbMin = decrButton.getX() + decrButton.getWidth();
                    thumbMax = incrButton.getX() - thumbR.width;
                } else {
                    thumbMin = incrButton.getX() + incrButton.getWidth();
                    thumbMax = decrButton.getX() - thumbR.width;
                }*/
                thumbMin = trackR.x;
                thumbMax = trackR.x + trackR.width - thumbR.width;
                
                thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getX() - offset)));
                setThumbBounds(thumbPos, thumbR.y, thumbR.width, thumbR.height);
                trackLength = getTrackBounds().width;
            }
            
            /* Set the scrollbars value.  If the thumb has reached the end of
             * the scrollbar, then just set the value to its maximum.  Otherwise
             * compute the value as accurately as possible.
             */
            if (thumbPos == thumbMax) {
                if (scrollbar.getOrientation() == JScrollBar.VERTICAL ||
                scrollbar.getComponentOrientation().isLeftToRight()) {
                    scrollbar.setValue(model.getMaximum() - model.getExtent());
                } else {
                    scrollbar.setValue(model.getMinimum());
                }
            } else {
                float valueMax = model.getMaximum() - model.getExtent();
                float valueRange = valueMax - model.getMinimum();
                float thumbValue = thumbPos - thumbMin;
                float thumbRange = thumbMax - thumbMin;
                int value;
                if (scrollbar.getOrientation() == JScrollBar.VERTICAL ||
                scrollbar.getComponentOrientation().isLeftToRight()) {
                    value = (int)(0.5 + ((thumbValue / thumbRange) * valueRange));
                } else {
                    value = (int)(0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange));
                }
                scrollbar.setValue(value + model.getMinimum());
            }
        }
        
        private void startScrollTimerIfNecessary() {
            if (scrollTimer.isRunning()) {
                return;
            }
            switch (scrollbar.getOrientation()) {
                case JScrollBar.VERTICAL:
                    if (direction >0) {
                        if (getThumbBounds().y + getThumbBounds().height <
                        ((PDFTrackListener) trackListener).currentMouseY) {
                            scrollTimer.start();
                        }
                    } else if (getThumbBounds().y >
                    ((PDFTrackListener) trackListener).currentMouseY) {
                        scrollTimer.start();
                    }
                    break;
                case JScrollBar.HORIZONTAL:
                    if (direction >0) {
                        if (getThumbBounds().x + getThumbBounds().width <
                        ((PDFTrackListener) trackListener).currentMouseX) {
                            scrollTimer.start();
                        }
                    } else if (getThumbBounds().x >
                    ((PDFTrackListener) trackListener).currentMouseX) {
                        scrollTimer.start();
                    }
                    break;
            }
        }
        
        public void mouseMoved(MouseEvent e) {
        }
    }
    /**
     * Listener for cursor keys.
     */
    protected class PDFArrowButtonListener extends ArrowButtonListener {
        public void mousePressed(MouseEvent evt) {
            super.mousePressed(evt);
            repaintButtons();
        }
        
        public void mouseReleased(MouseEvent evt) {
            super.mouseReleased(evt);
            repaintButtons();
        }
        public void mouseEntered(MouseEvent evt) {
            super.mouseEntered(evt);
            if ((evt.getModifiers() & (InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) != 0) {
                //if (evt.getButton() != MouseEvent.NOBUTTON) {
                repaintButtons();
            }
        }
        
        public void mouseExited(MouseEvent evt) {
            super.mouseExited(evt);
            if ((evt.getModifiers() & (InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) != 0) {
                //if (evt.getButton() != MouseEvent.NOBUTTON) {
                repaintButtons();
            }
        }
        
        protected void repaintButtons() {
            Dimension sbSize = scrollbar.getSize();
            Insets sbInsets = scrollbar.getInsets();
            Rectangle contentBounds = new Rectangle(
            sbInsets.left, sbInsets.top,
            sbSize.width - sbInsets.left - sbInsets.right,
            sbSize.height - sbInsets.top - sbInsets.bottom
            );
            
            if (isPlaceButtonsTogether()) {
                Border trackAndButtonsBorder = getTrackAndButtonsBorder();
                Insets tbInsets = trackAndButtonsBorder.getBorderInsets(scrollbar);
                
                if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
                    scrollbar.repaint(
                    contentBounds.x,
                    contentBounds.y + contentBounds.height - tbInsets.bottom,
                    contentBounds.width,
                    tbInsets.bottom
                    );
                } else {
                    scrollbar.repaint(
                    contentBounds.x + contentBounds.width - tbInsets.right,
                    contentBounds.y,
                    tbInsets.right,
                    contentBounds.height
                    );
                }
            } else {
                scrollbar.repaint();
            }
        }
    }
    /**
     * Listener for scrolling events initiated in the
     * <code>ScrollPane</code>.
     */
    protected class PDFScrollListener extends ScrollListener {
        int direction = +1;
        boolean useBlockIncrement;
        
        public PDFScrollListener()	{
            direction = +1;
            useBlockIncrement = false;
        }
        
        public PDFScrollListener(int dir, boolean block)	{
            direction = dir;
            useBlockIncrement = block;
        }
        
        public void setDirection(int direction) { this.direction = direction; }
        public void setScrollByBlock(boolean block) { this.useBlockIncrement = block; }
        
        public void actionPerformed(ActionEvent e) {
            PDFTrackListener trackListener = (PDFTrackListener) PDFScrollBarUI.this.trackListener;
            if(useBlockIncrement)	{
                scrollByBlock(direction);
                // Stop scrolling if the thumb catches up with the mouse
                if(scrollbar.getOrientation() == JScrollBar.VERTICAL)	{
                    if(direction > 0)	{
                        if(getThumbBounds().y + getThumbBounds().height
                        >= trackListener.currentMouseY)
                            ((Timer)e.getSource()).stop();
                    } else if(getThumbBounds().y <= trackListener.currentMouseY)	{
                        ((Timer)e.getSource()).stop();
                    }
                } else {
                    if(direction > 0)	{
                        if(getThumbBounds().x + getThumbBounds().width
                        >= trackListener.currentMouseX)
                            ((Timer)e.getSource()).stop();
                    } else if(getThumbBounds().x <= trackListener.currentMouseX)	{
                        ((Timer)e.getSource()).stop();
                    }
                }
            } else {
                scrollByUnit(direction);
            }
            
            if(direction > 0
            && scrollbar.getValue()+scrollbar.getVisibleAmount()
            >= scrollbar.getMaximum())
                ((Timer)e.getSource()).stop();
            else if(direction < 0
            && scrollbar.getValue() <= scrollbar.getMinimum())
                ((Timer)e.getSource()).stop();
        }
    }
    
    public class PDFPropertyChangeHandler extends BasicScrollBarUI.PropertyChangeHandler {
        public void propertyChange(PropertyChangeEvent e) {
            String name = e.getPropertyName();
            if ("Frame.active".equals(name)) {
                scrollbar.repaint();
            } else if ("Quaqua.ScrollBar.placeButtonsTogether".equals(name)) {
                updatePlaceButtonsTogether();
            }
            super.propertyChange(e);
        }
    }
}