jdk/src/java.desktop/unix/classes/sun/awt/X11/XScrollbar.java
author chegar
Sun, 17 Aug 2014 15:54:13 +0100
changeset 25859 3317bb8137f4
parent 23010 jdk/src/solaris/classes/sun/awt/X11/XScrollbar.java@6dadb192ad81
child 30948 0a0972d3b58d
permissions -rw-r--r--
8054834: Modular Source Code Reviewed-by: alanb, chegar, ihse, mduigou Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@oracle.com

/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.X11;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import sun.awt.SunToolkit;
import sun.awt.X11GraphicsConfig;
import sun.util.logging.PlatformLogger;

/**
* A simple vertical scroll bar.
*/
abstract class XScrollbar {

    private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XScrollbar");
    /**
     * The thread that asynchronously tells the scrollbar to scroll.
     * @see #startScrolling
     */
    private static XScrollRepeater scroller = new XScrollRepeater(null);
    /*
     * The repeater that used for concurrent scrolling of the vertical and horizontal scrollbar
     * And so there is not static keyword
     * See 6243382 for more information
     */
    private XScrollRepeater i_scroller = new XScrollRepeater(null);

    // Thumb length is always >= MIN_THUMB_H
    private final static int MIN_THUMB_H = 5;

    private static final int ARROW_IND = 1;

    XScrollbarClient sb;

    //Use set methods to set scrollbar parameters
    private int val;
    private int min;
    private int max;
    private int vis;

    private int line;
    private int page;
    private boolean needsRepaint = true;
    private boolean pressed = false;
    private boolean dragging = false;

    Polygon firstArrow, secondArrow;

    int width, height; // Dimensions of the visible part of the parent window
    int barWidth, barLength; // Rotation-independent values,
                             // equal to (width, height) for vertical,
                             // rotated by 90 for horizontal.
                             // That is, barLength is always the length between
                             // the tips of the arrows.
    int arrowArea;     // The area reserved for the scroll arrows
    int alignment;
    public static final int ALIGNMENT_VERTICAL = 1, ALIGNMENT_HORIZONTAL = 2;

    int mode;
    Point thumbOffset;
    private Rectangle prevThumb;

    public XScrollbar(int alignment, XScrollbarClient sb) {
        this.sb = sb;
        this.alignment = alignment;
    }

    public boolean needsRepaint() {
        return needsRepaint;
    }

    void notifyValue(int v) {
        notifyValue(v, false);
    }

    void notifyValue(int v, final boolean isAdjusting) {
        if (v < min) {
            v = min;
        } else if (v > max - vis) {
            v = max - vis;
        }
        final int value = v;
        final int mode = this.mode;
        if ((sb != null) && ((value != val)||(!pressed))) {
            SunToolkit.executeOnEventHandlerThread(sb.getEventSource(), new Runnable() {
                    public void run() {
                        sb.notifyValue(XScrollbar.this, mode, value, isAdjusting);
                    }
                });
        }
    }

    abstract protected void rebuildArrows();

    public void setSize(int width, int height) {
        if (log.isLoggable(PlatformLogger.Level.FINER)) {
            log.finer("Setting scroll bar " + this + " size to " + width + "x" + height);
        }
        this.width = width;
        this.height = height;
    }

    /**
     * Creates oriented directed arrow
     */
    protected Polygon createArrowShape(boolean vertical, boolean up) {
        Polygon arrow = new Polygon();
        // TODO: this should be done polymorphically in subclasses
        // FIXME: arrows overlap the thumb for very wide scrollbars
        if (vertical) {
            int x = width / 2 - getArrowWidth()/2;
            int y1 = (up ? ARROW_IND : barLength - ARROW_IND);
            int y2 = (up ? getArrowWidth() : barLength - getArrowWidth() - ARROW_IND);
            arrow.addPoint(x + getArrowWidth()/2, y1);
            arrow.addPoint(x + getArrowWidth(), y2);
            arrow.addPoint(x, y2);
            arrow.addPoint(x + getArrowWidth()/2, y1);
        } else {
            int y = height / 2 - getArrowWidth()/2;
            int x1 = (up ? ARROW_IND : barLength - ARROW_IND);
            int x2 = (up ? getArrowWidth() : barLength - getArrowWidth() - ARROW_IND);
            arrow.addPoint(x1, y + getArrowWidth()/2);
            arrow.addPoint(x2, y + getArrowWidth());
            arrow.addPoint(x2, y);
            arrow.addPoint(x1, y + getArrowWidth()/2);
        }
        return arrow;
    }

    /**
     * Gets the area of the scroll track
     */
    protected abstract Rectangle getThumbArea();

    /**
     * paint the scrollbar
     * @param g the graphics context to paint into
     * @param colors the colors to use when painting the scrollbar
     * @param width the width of the scrollbar
     * @param height the height of the scrollbar
     * @param paintAll paint the whole scrollbar if true, just the thumb is false
     */
    void paint(Graphics g, Color colors[], boolean paintAll) {
        if (log.isLoggable(PlatformLogger.Level.FINER)) {
            log.finer("Painting scrollbar " + this);
        }

        boolean useBufferedImage = false;
        Graphics2D g2 = null;
        BufferedImage buffer = null;
        if (!(g instanceof Graphics2D)) {
            // Fix for 5045936, 5055171. While printing, g is an instance
            //   of sun.print.ProxyPrintGraphics which extends Graphics.
            //   So we use a separate buffered image and its graphics is
            //   always Graphics2D instance
            X11GraphicsConfig graphicsConfig = (X11GraphicsConfig)(sb.getEventSource().getGraphicsConfiguration());
            buffer = graphicsConfig.createCompatibleImage(width, height);
            g2 = buffer.createGraphics();
            useBufferedImage = true;
        } else {
            g2 = (Graphics2D)g;
        }
        try {
            Rectangle thumbRect = calculateThumbRect();

//              if (prevH == thumbH && prevY == thumbPosY) {
//                  return;
//              }

            prevThumb = thumbRect;

            // TODO: Share Motif colors
            Color back = colors[XComponentPeer.BACKGROUND_COLOR];
            Color selectColor = new Color(MotifColorUtilities.calculateSelectFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
            Color darkShadow = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
            Color lightShadow = new Color(MotifColorUtilities.calculateTopShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue()));

            XToolkit.awtLock();
            try {
                XlibWrapper.XFlush(XToolkit.getDisplay());
            } finally {
                XToolkit.awtUnlock();
            }
            /* paint the background slightly darker */
            if (paintAll) {
                // fill the entire background
                g2.setColor(selectColor);
                if (alignment == ALIGNMENT_HORIZONTAL) {
                    g2.fillRect(0, 0, thumbRect.x, height);
                    g2.fillRect(thumbRect.x + thumbRect.width , 0, width - (thumbRect.x + thumbRect.width), height);
                } else {
                    g2.fillRect(0, 0, width, thumbRect.y);
                    g2.fillRect(0, thumbRect.y + thumbRect.height, width, height - (thumbRect.y + thumbRect.height));
                }

                // Paint edges
                // TODO: Share Motif 3d rect drawing

                g2.setColor(darkShadow);
                g2.drawLine(0, 0, width-1, 0);           // top
                g2.drawLine(0, 0, 0, height-1);          // left

                g2.setColor(lightShadow);
                g2.drawLine(1, height-1, width-1, height-1); // bottom
                g2.drawLine(width-1, 1, width-1, height-1);  // right
            } else {
                // Clear all thumb area
                g2.setColor(selectColor);
                Rectangle thumbArea = getThumbArea();
                g2.fill(thumbArea);
            }

            if (paintAll) {
                // ************ paint the arrows
                 paintArrows(g2, colors[XComponentPeer.BACKGROUND_COLOR], darkShadow, lightShadow );

            }

            // Thumb
            g2.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
            g2.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height);

            g2.setColor(lightShadow);
            g2.drawLine(thumbRect.x, thumbRect.y,
                       thumbRect.x + thumbRect.width, thumbRect.y); // top
            g2.drawLine(thumbRect.x, thumbRect.y,
                       thumbRect.x, thumbRect.y+thumbRect.height); // left

            g2.setColor(darkShadow);
            g2.drawLine(thumbRect.x+1,
                       thumbRect.y+thumbRect.height,
                       thumbRect.x+thumbRect.width,
                       thumbRect.y+thumbRect.height);  // bottom
            g2.drawLine(thumbRect.x+thumbRect.width,
                       thumbRect.y+1,
                       thumbRect.x+thumbRect.width,
                       thumbRect.y+thumbRect.height); // right
        } finally {
            if (useBufferedImage) {
                g2.dispose();
            }
        }
        if (useBufferedImage) {
            g.drawImage(buffer, 0, 0, null);
        }
        XToolkit.awtLock();
        try {
            XlibWrapper.XFlush(XToolkit.getDisplay());
        } finally {
            XToolkit.awtUnlock();
        }
    }

      void paintArrows(Graphics2D g, Color background, Color darkShadow, Color lightShadow) {

          g.setColor(background);

        // paint firstArrow
        if (pressed && (mode == AdjustmentEvent.UNIT_DECREMENT)) {
            g.fill(firstArrow);
            g.setColor(lightShadow);
            g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0],
                    firstArrow.xpoints[1],firstArrow.ypoints[1]);
            g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1],
                    firstArrow.xpoints[2],firstArrow.ypoints[2]);
            g.setColor(darkShadow);
            g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2],
                    firstArrow.xpoints[0],firstArrow.ypoints[0]);

        }
        else {
            g.fill(firstArrow);
            g.setColor(darkShadow);
            g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0],
                    firstArrow.xpoints[1],firstArrow.ypoints[1]);
            g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1],
                    firstArrow.xpoints[2],firstArrow.ypoints[2]);
            g.setColor(lightShadow);
            g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2],
                    firstArrow.xpoints[0],firstArrow.ypoints[0]);

        }

        g.setColor(background);
        // paint second Arrow
        if (pressed && (mode == AdjustmentEvent.UNIT_INCREMENT)) {
            g.fill(secondArrow);
            g.setColor(lightShadow);
            g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0],
                    secondArrow.xpoints[1],secondArrow.ypoints[1]);
            g.setColor(darkShadow);
            g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1],
                    secondArrow.xpoints[2],secondArrow.ypoints[2]);
            g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2],
                    secondArrow.xpoints[0],secondArrow.ypoints[0]);

        }
        else {
            g.fill(secondArrow);
            g.setColor(darkShadow);
            g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0],
                    secondArrow.xpoints[1],secondArrow.ypoints[1]);
            g.setColor(lightShadow);
            g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1],
                    secondArrow.xpoints[2],secondArrow.ypoints[2]);
            g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2],
                    secondArrow.xpoints[0],secondArrow.ypoints[0]);

        }

    }

    /**
     * Tell the scroller to start scrolling.
     */
    void startScrolling() {
        if (log.isLoggable(PlatformLogger.Level.FINER)) {
            log.finer("Start scrolling on " + this);
        }
        // Make sure that we scroll at least once
        scroll();

        // wake up the scroll repeater
        if (scroller == null) {
            // If there isn't a scroller, then create
            // one and start it.
            scroller = new XScrollRepeater(this);
        } else {
            scroller.setScrollbar(this);
        }
        scroller.start();
    }

    /**
     * Tell the instance scroller to start scrolling.
     * See 6243382 for more information
     */
    void startScrollingInstance() {
        if (log.isLoggable(PlatformLogger.Level.FINER)) {
            log.finer("Start scrolling on " + this);
        }
        // Make sure that we scroll at least once
        scroll();

        i_scroller.setScrollbar(this);
        i_scroller.start();
    }

    /**
     * Tell the instance scroller to stop scrolling.
     * See 6243382 for more information
     */
    void stopScrollingInstance() {
        if (log.isLoggable(PlatformLogger.Level.FINER)) {
            log.finer("Stop scrolling on " + this);
        }

        i_scroller.stop();
    }

    /**
     * The set method for mode property.
     * See 6243382 for more information
     */
    public void setMode(int mode){
        this.mode = mode;
    }

    /**
     * Scroll one unit.
     * @see notifyValue
     */
    void scroll() {
        switch (mode) {
          case AdjustmentEvent.UNIT_DECREMENT:
              notifyValue(val - line);
              return;

          case AdjustmentEvent.UNIT_INCREMENT:
              notifyValue(val + line);
              return;

          case AdjustmentEvent.BLOCK_DECREMENT:
              notifyValue(val - page);
              return;

          case AdjustmentEvent.BLOCK_INCREMENT:
              notifyValue(val + page);
              return;
        }
        return;
    }

    boolean isInArrow(int x, int y) {
        // Mouse is considered to be in the arrow if it is anywhere in the
        // arrow area.
        int coord = (alignment == ALIGNMENT_HORIZONTAL ? x : y);
        int arrAreaH = getArrowAreaWidth();

        if (coord < arrAreaH || coord > barLength - arrAreaH + 1) {
            return true;
        }
        return false;
    }

    /**
     * Is x,y in the scroll thumb?
     *
     * If we ever cache the thumb rect, we may need to clone the result of
     * calculateThumbRect().
     */
    boolean isInThumb(int x, int y) {
        Rectangle thumbRect = calculateThumbRect();

        // If the mouse is in the shadow of the thumb or the shadow of the
        // scroll track, treat it as hitting the thumb.  So, slightly enlarge
        // our rectangle.
        thumbRect.x -= 1;
        thumbRect.width += 3;
        thumbRect.height += 1;
        return thumbRect.contains(x,y);
    }

    abstract boolean beforeThumb(int x, int y);

    /**
     *
     * @see java.awt.event.MouseEvent
     * MouseEvent.MOUSE_CLICKED
     * MouseEvent.MOUSE_PRESSED
     * MouseEvent.MOUSE_RELEASED
     * MouseEvent.MOUSE_MOVED
     * MouseEvent.MOUSE_ENTERED
     * MouseEvent.MOUSE_EXITED
     * MouseEvent.MOUSE_DRAGGED
     */
    public void handleMouseEvent(int id, int modifiers, int x, int y) {
        if ((modifiers & InputEvent.BUTTON1_MASK) == 0) {
            return;
        }

        if (log.isLoggable(PlatformLogger.Level.FINER)) {
             String type;
             switch (id) {
                case MouseEvent.MOUSE_PRESSED:
                    type = "press";
                    break;
                case MouseEvent.MOUSE_RELEASED:
                    type = "release";
                    break;
                case MouseEvent.MOUSE_DRAGGED:
                    type = "drag";
                    break;
                default:
                    type = "other";
             }
             log.finer("Mouse " + type + " event in scroll bar " + this +
                                                   "x = " + x + ", y = " + y +
                                                   ", on arrow: " + isInArrow(x, y) +
                                                   ", on thumb: " + isInThumb(x, y) + ", before thumb: " + beforeThumb(x, y)
                                                   + ", thumb rect" + calculateThumbRect());
        }
        switch (id) {
          case MouseEvent.MOUSE_PRESSED:
              if (isInArrow(x, y)) {
                  pressed = true;
                  if (beforeThumb(x, y)) {
                      mode = AdjustmentEvent.UNIT_DECREMENT;
                  } else {
                      mode = AdjustmentEvent.UNIT_INCREMENT;
                  }
                  sb.repaintScrollbarRequest(this);
                  startScrolling();
                  break;
              }

              if (isInThumb(x, y)) {
                  mode = AdjustmentEvent.TRACK;
              } else {
                  if (beforeThumb(x, y)) {
                      mode = AdjustmentEvent.BLOCK_DECREMENT;
                  } else {
                      mode = AdjustmentEvent.BLOCK_INCREMENT;
                  }
                  startScrolling();
              }
              Rectangle pos = calculateThumbRect();
              thumbOffset = new Point(x - pos.x, y - pos.y);
              break;

          case MouseEvent.MOUSE_RELEASED:
              pressed = false;
              sb.repaintScrollbarRequest(this);
              scroller.stop();
              if(dragging){
                  handleTrackEvent(x, y, false);
                  dragging=false;
              }
              break;

          case MouseEvent.MOUSE_DRAGGED:
              dragging = true;
              handleTrackEvent(x, y, true);
        }
    }

    private void handleTrackEvent(int x, int y, boolean isAdjusting){
        if (mode == AdjustmentEvent.TRACK) {
            notifyValue(calculateCursorOffset(x, y), isAdjusting);
        }
    }

    private int calculateCursorOffset(int x, int y){
        if (alignment == ALIGNMENT_HORIZONTAL) {
            if (dragging)
                return Math.max(0,(int)((x - (thumbOffset.x + getArrowAreaWidth()))/getScaleFactor())) + min;
            else
                return Math.max(0,(int)((x - (getArrowAreaWidth()))/getScaleFactor())) + min;
        } else {
            if (dragging)
                return Math.max(0,(int)((y - (thumbOffset.y + getArrowAreaWidth()))/getScaleFactor())) + min;
            else
                return Math.max(0,(int)((y - (getArrowAreaWidth()))/getScaleFactor())) + min;
        }
    }

/*
  private void updateNeedsRepaint() {
        Rectangle thumbRect = calculateThumbRect();
        if (!prevThumb.equals(thumbRect)) {
            needsRepaint = true;
        }
        prevThumb = thumbRect;
    }
    */

    /**
     * Sets the values for this Scrollbar.
     * This method enforces the same constraints as in java.awt.Scrollbar:
     * <UL>
     * <LI> The maximum must be greater than the minimum </LI>
     * <LI> The value must be greater than or equal to the minimum
     *      and less than or equal to the maximum minus the
     *      visible amount </LI>
     * <LI> The visible amount must be greater than 1 and less than or equal
     *      to the difference between the maximum and minimum values. </LI>
     * </UL>
     * Values which do not meet these criteria are quietly coerced to the
     * appropriate boundary value.
     * @param value is the position in the current window.
     * @param visible is the amount visible per page
     * @param minimum is the minimum value of the scrollbar
     * @param maximum is the maximum value of the scrollbar
     */
    synchronized void setValues(int value, int visible, int minimum, int maximum) {
        if (maximum <= minimum) {
            maximum = minimum + 1;
        }
        if (visible > maximum - minimum) {
            visible = maximum - minimum;
        }
        if (visible < 1) {
            visible = 1;
        }
        if (value < minimum) {
            value = minimum;
        }
        if (value > maximum - visible) {
            value = maximum - visible;
        }

        this.val = value;
        this.vis = visible;
        this.min = minimum;
        this.max = maximum;
    }

    /**
     * Sets param of this Scrollbar to the specified values.
     * @param value is the position in the current window.
     * @param visible is the amount visible per page
     * @param minimum is the minimum value of the scrollbar
     * @param maximum is the maximum value of the scrollbar
     * @param unitSize is the unit size for increment or decrement of the value
     * @param page is the block size for increment or decrement of the value
     * @see #setValues
     */
    synchronized void setValues(int value, int visible, int minimum, int maximum,
                                int unitSize, int blockSize) {
        /* Use setValues so that a consistent policy
         * relating minimum, maximum, and value is enforced.
         */
        setValues(value, visible, minimum, maximum);
        setUnitIncrement(unitSize);
        setBlockIncrement(blockSize);
    }

    /**
     * Returns the current value of this Scrollbar.
     * @see #getMinimum
     * @see #getMaximum
     */
    int getValue() {
        return val;
    }

    /**
     * Sets the value of this Scrollbar to the specified value.
     * @param value the new value of the Scrollbar. If this value is
     * below the current minimum or above the current maximum minus
     * the visible amount, it becomes the new one of those values,
     * respectively.
     * @see #getValue
     */
    synchronized void setValue(int newValue) {
        /* Use setValues so that a consistent policy
         * relating minimum, maximum, and value is enforced.
         */
        setValues(newValue, vis, min, max);
    }

    /**
     * Returns the minimum value of this Scrollbar.
     * @see #getMaximum
     * @see #getValue
     */
    int getMinimum() {
        return min;
    }

    /**
     * Sets the minimum value for this Scrollbar.
     * @param minimum the minimum value of the scrollbar
     */
    synchronized void setMinimum(int newMinimum) {
        /* Use setValues so that a consistent policy
         * relating minimum, maximum, and value is enforced.
         */
        setValues(val, vis, newMinimum, max);
    }

    /**
     * Returns the maximum value of this Scrollbar.
     * @see #getMinimum
     * @see #getValue
     */
    int getMaximum() {
        return max;
    }

    /**
     * Sets the maximum value for this Scrollbar.
     * @param maximum the maximum value of the scrollbar
     */
    synchronized void setMaximum(int newMaximum) {
        /* Use setValues so that a consistent policy
         * relating minimum, maximum, and value is enforced.
         */
        setValues(val, vis, min, newMaximum);
    }

    /**
     * Returns the visible amount of this Scrollbar.
     */
    int getVisibleAmount() {
        return vis;
    }

    /**
     * Sets the visible amount of this Scrollbar, which is the range
     * of values represented by the width of the scroll bar's bubble.
     * @param visible the amount visible per page
     */
    synchronized void setVisibleAmount(int newAmount) {
        setValues(val, newAmount, min, max);
    }

    /**
     * Sets the unit increment for this scrollbar. This is the value
     * that will be added (subtracted) when the user hits the unit down
     * (up) gadgets.
     * @param unitSize is the unit size for increment or decrement of the value
     */
    synchronized void setUnitIncrement(int unitSize) {
        line = unitSize;
    }

    /**
     * Gets the unit increment for this scrollbar.
     */
    int getUnitIncrement() {
        return line;
    }

    /**
     * Sets the block increment for this scrollbar. This is the value
     * that will be added (subtracted) when the user hits the block down
     * (up) gadgets.
     * @param blockSize is the block size for increment or decrement of the value
     */
    synchronized void setBlockIncrement(int blockSize) {
        page = blockSize;
    }

    /**
     * Gets the block increment for this scrollbar.
     */
    int getBlockIncrement() {
        return page;
    }

    /**
     * Width of the arrow image
     */
    int getArrowWidth() {
        return getArrowAreaWidth() - 2*ARROW_IND;
    }

    /**
     * Width of the area reserved for arrow
     */
    int getArrowAreaWidth() {
        return arrowArea;
    }

    void calculateArrowWidth() {
        if (barLength < 2*barWidth + MIN_THUMB_H + 2) {
            arrowArea = (barLength - MIN_THUMB_H + 2*ARROW_IND)/2 - 1;
        }
        else {
            arrowArea = barWidth - 1;
        }
    }

    /**
     * Returns the scale factor for the thumbArea ( thumbAreaH / (max - min)).
     * @see #getArrowAreaSize
     */
    private double getScaleFactor(){
        double f = (double)(barLength - 2*getArrowAreaWidth()) / Math.max(1,(max - min));
        return f;
    }

    /**
     * Method to calculate the scroll thumb's size and position.  This is
     * based on CalcSliderRect in ScrollBar.c of Motif source.
     *
     * If we ever cache the thumb rect, we'll need to use a clone in
     * isInThumb().
     */
    protected Rectangle calculateThumbRect() {
        float range;
        float trueSize;  // Area of scroll track
        float factor;
        float slideSize;
        int minSliderWidth;
        int minSliderHeight;
        int hitTheWall = 0;
        int arrAreaH = getArrowAreaWidth();
        Rectangle retVal = new Rectangle(0,0,0,0);

        trueSize = barLength - 2*arrAreaH - 1;  // Same if vert or horiz

        if (alignment == ALIGNMENT_HORIZONTAL) {
            minSliderWidth = MIN_THUMB_H ;  // Base on user-set vis?
            minSliderHeight = height - 3;
        }
        else {  // Vertical
            minSliderWidth = width - 3;
            minSliderHeight = MIN_THUMB_H ;

        }

        // Total number of user units displayed
            range = max - min;

        // A naive notion of pixels per user unit
            factor = trueSize / range;

            // A naive notion of the size of the slider in pixels
            // in thermo, slider_size is 0 ans is ignored
            slideSize = vis * factor;

        if (alignment == ALIGNMENT_HORIZONTAL) {
            // Simulating MAX_SCROLLBAR_DIMENSION macro
            int localVal = (int) (slideSize + 0.5);
            int localMin = minSliderWidth;
            if (localVal > localMin) {
                retVal.width = localVal;
            }
            else {
                retVal.width = localMin;
                hitTheWall = localMin;
            }
            retVal.height = minSliderHeight;
        }
        else {  // Vertical
            retVal.width = minSliderWidth;

            // Simulating MAX_SCROLLBAR_DIMENSION macro
            int localVal = (int) (slideSize + 0.5);
            int localMin = minSliderHeight;
            if (localVal > localMin) {
                retVal.height = localVal;
            }
            else {
                retVal.height = localMin;
                hitTheWall = localMin;
            }
        }

        if (hitTheWall != 0) {
            trueSize -= hitTheWall;  // Actual pixels available
            range -= vis;            // Actual range
            factor = trueSize / range;
        }

        if (alignment == ALIGNMENT_HORIZONTAL) {
                    retVal.x = ((int) (((((float) val)
                        - ((float) min)) * factor) + 0.5))
                        + arrAreaH;
                    retVal.y = 1;

        }
        else {
            retVal.x = 1;
                    retVal.y = ((int) (((((float) val)
                        - ((float) min)) * factor) + 0.5))
                        + arrAreaH;
        }

        // There was one final adjustment here in the Motif function, which was
        // noted to be for backward-compatibility.  It has been left out for now.

        return retVal;
    }

    public String toString() {
        return getClass() + "[" + width + "x" + height + "," + barWidth + "x" + barLength + "]";
    }
}


class XScrollRepeater implements Runnable {
    /**
     * Time to pause before the first scroll repeat.
     */
    static int beginPause = 500;
    // Reminder - make this a user definable property

    /**
     * Time to pause between each scroll repeat.
     */
    static int repeatPause = 100;
    // Reminder - make this a user definable property

    /**
     * The scrollbar that we sending scrolling.
     */
    XScrollbar sb;

    /**
     * newScroll gets reset when a new scrollbar gets set.
     */
    boolean newScroll;


    boolean shouldSkip;

    /**
     * Creates a new scroll repeater.
     * @param sb the scrollbar that this thread will scroll
     */
    XScrollRepeater(XScrollbar sb) {
        this.setScrollbar(sb);
        newScroll = true;
    }

    public void start() {
        stop();
        shouldSkip = false;
        XToolkit.schedule(this, beginPause);
    }

    public void stop() {
        synchronized(this) {
            shouldSkip = true;
        }
        XToolkit.remove(this);
    }

    /**
     * Sets the scrollbar.
     * @param sb the scrollbar that this thread will scroll
     */
    public synchronized void setScrollbar(XScrollbar sb) {
        this.sb = sb;
        stop();
        newScroll = true;
    }

    public void run () {
        synchronized(this) {
            if (shouldSkip) {
                return;
            }
        }
        sb.scroll();
        XToolkit.schedule(this, repeatPause);
    }

}