1 /*
   2  * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.X11;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.image.BufferedImage;
  31 import sun.awt.SunToolkit;
  32 import sun.awt.X11GraphicsConfig;
  33 import sun.util.logging.PlatformLogger;
  34 
  35 /**
  36 * A simple vertical scroll bar.
  37 */
  38 abstract class XScrollbar {
  39 
  40     private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XScrollbar");
  41     /**
  42      * The thread that asynchronously tells the scrollbar to scroll.
  43      * @see #startScrolling
  44      */
  45     private static XScrollRepeater scroller = new XScrollRepeater(null);
  46     /*
  47      * The repeater that used for concurrent scrolling of the vertical and horizontal scrollbar
  48      * And so there is not static keyword
  49      * See 6243382 for more information
  50      */
  51     private XScrollRepeater i_scroller = new XScrollRepeater(null);
  52 
  53     // Thumb length is always >= MIN_THUMB_H
  54     private final static int MIN_THUMB_H = 5;
  55 
  56     private static final int ARROW_IND = 1;
  57 
  58     XScrollbarClient sb;
  59 
  60     //Use set methods to set scrollbar parameters
  61     private int val;
  62     private int min;
  63     private int max;
  64     private int vis;
  65 
  66     private int line;
  67     private int page;
  68     private boolean needsRepaint = true;
  69     private boolean pressed = false;
  70     private boolean dragging = false;
  71 
  72     Polygon firstArrow, secondArrow;
  73 
  74     int width, height; // Dimensions of the visible part of the parent window
  75     int barWidth, barLength; // Rotation-independent values,
  76                              // equal to (width, height) for vertical,
  77                              // rotated by 90 for horizontal.
  78                              // That is, barLength is always the length between
  79                              // the tips of the arrows.
  80     int arrowArea;     // The area reserved for the scroll arrows
  81     int alignment;
  82     public static final int ALIGNMENT_VERTICAL = 1, ALIGNMENT_HORIZONTAL = 2;
  83 
  84     int mode;
  85     Point thumbOffset;
  86     private Rectangle prevThumb;
  87 
  88     public XScrollbar(int alignment, XScrollbarClient sb) {
  89         this.sb = sb;
  90         this.alignment = alignment;
  91     }
  92 
  93     public boolean needsRepaint() {
  94         return needsRepaint;
  95     }
  96 
  97     void notifyValue(int v) {
  98         notifyValue(v, false);
  99     }
 100 
 101     void notifyValue(int v, final boolean isAdjusting) {
 102         if (v < min) {
 103             v = min;
 104         } else if (v > max - vis) {
 105             v = max - vis;
 106         }
 107         final int value = v;
 108         final int mode = this.mode;
 109         if ((sb != null) && ((value != val)||(!pressed))) {
 110             SunToolkit.executeOnEventHandlerThread(sb.getEventSource(), new Runnable() {
 111                     public void run() {
 112                         sb.notifyValue(XScrollbar.this, mode, value, isAdjusting);
 113                     }
 114                 });
 115         }
 116     }
 117 
 118     abstract protected void rebuildArrows();
 119 
 120     public void setSize(int width, int height) {
 121         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 122             log.finer("Setting scroll bar " + this + " size to " + width + "x" + height);
 123         }
 124         this.width = width;
 125         this.height = height;
 126     }
 127 
 128     /**
 129      * Creates oriented directed arrow
 130      */
 131     protected Polygon createArrowShape(boolean vertical, boolean up) {
 132         Polygon arrow = new Polygon();
 133         // TODO: this should be done polymorphically in subclasses
 134         // FIXME: arrows overlap the thumb for very wide scrollbars
 135         if (vertical) {
 136             int x = width / 2 - getArrowWidth()/2;
 137             int y1 = (up ? ARROW_IND : barLength - ARROW_IND);
 138             int y2 = (up ? getArrowWidth() : barLength - getArrowWidth() - ARROW_IND);
 139             arrow.addPoint(x + getArrowWidth()/2, y1);
 140             arrow.addPoint(x + getArrowWidth(), y2);
 141             arrow.addPoint(x, y2);
 142             arrow.addPoint(x + getArrowWidth()/2, y1);
 143         } else {
 144             int y = height / 2 - getArrowWidth()/2;
 145             int x1 = (up ? ARROW_IND : barLength - ARROW_IND);
 146             int x2 = (up ? getArrowWidth() : barLength - getArrowWidth() - ARROW_IND);
 147             arrow.addPoint(x1, y + getArrowWidth()/2);
 148             arrow.addPoint(x2, y + getArrowWidth());
 149             arrow.addPoint(x2, y);
 150             arrow.addPoint(x1, y + getArrowWidth()/2);
 151         }
 152         return arrow;
 153     }
 154 
 155     /**
 156      * Gets the area of the scroll track
 157      */
 158     protected abstract Rectangle getThumbArea();
 159 
 160     /**
 161      * paint the scrollbar
 162      * @param g the graphics context to paint into
 163      * @param colors the colors to use when painting the scrollbar
 164      * @param width the width of the scrollbar
 165      * @param height the height of the scrollbar
 166      * @param paintAll paint the whole scrollbar if true, just the thumb is false
 167      */
 168     void paint(Graphics g, Color colors[], boolean paintAll) {
 169         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 170             log.finer("Painting scrollbar " + this);
 171         }
 172 
 173         boolean useBufferedImage = false;
 174         Graphics2D g2 = null;
 175         BufferedImage buffer = null;
 176         if (!(g instanceof Graphics2D)) {
 177             // Fix for 5045936, 5055171. While printing, g is an instance
 178             //   of sun.print.ProxyPrintGraphics which extends Graphics.
 179             //   So we use a separate buffered image and its graphics is
 180             //   always Graphics2D instance
 181             X11GraphicsConfig graphicsConfig = (X11GraphicsConfig)(sb.getEventSource().getGraphicsConfiguration());
 182             buffer = graphicsConfig.createCompatibleImage(width, height);
 183             g2 = buffer.createGraphics();
 184             useBufferedImage = true;
 185         } else {
 186             g2 = (Graphics2D)g;
 187         }
 188         try {
 189             Rectangle thumbRect = calculateThumbRect();
 190 
 191 //              if (prevH == thumbH && prevY == thumbPosY) {
 192 //                  return;
 193 //              }
 194 
 195             prevThumb = thumbRect;
 196 
 197             // TODO: Share Motif colors
 198             Color back = colors[XComponentPeer.BACKGROUND_COLOR];
 199             Color selectColor = new Color(MotifColorUtilities.calculateSelectFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
 200             Color darkShadow = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
 201             Color lightShadow = new Color(MotifColorUtilities.calculateTopShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
 202 
 203             XToolkit.awtLock();
 204             try {
 205                 XlibWrapper.XFlush(XToolkit.getDisplay());
 206             } finally {
 207                 XToolkit.awtUnlock();
 208             }
 209             /* paint the background slightly darker */
 210             if (paintAll) {
 211                 // fill the entire background
 212                 g2.setColor(selectColor);
 213                 if (alignment == ALIGNMENT_HORIZONTAL) {
 214                     g2.fillRect(0, 0, thumbRect.x, height);
 215                     g2.fillRect(thumbRect.x + thumbRect.width , 0, width - (thumbRect.x + thumbRect.width), height);
 216                 } else {
 217                     g2.fillRect(0, 0, width, thumbRect.y);
 218                     g2.fillRect(0, thumbRect.y + thumbRect.height, width, height - (thumbRect.y + thumbRect.height));
 219                 }
 220 
 221                 // Paint edges
 222                 // TODO: Share Motif 3d rect drawing
 223 
 224                 g2.setColor(darkShadow);
 225                 g2.drawLine(0, 0, width-1, 0);           // top
 226                 g2.drawLine(0, 0, 0, height-1);          // left
 227 
 228                 g2.setColor(lightShadow);
 229                 g2.drawLine(1, height-1, width-1, height-1); // bottom
 230                 g2.drawLine(width-1, 1, width-1, height-1);  // right
 231             } else {
 232                 // Clear all thumb area
 233                 g2.setColor(selectColor);
 234                 Rectangle thumbArea = getThumbArea();
 235                 g2.fill(thumbArea);
 236             }
 237 
 238             if (paintAll) {
 239                 // ************ paint the arrows
 240                  paintArrows(g2, colors[XComponentPeer.BACKGROUND_COLOR], darkShadow, lightShadow );
 241 
 242             }
 243 
 244             // Thumb
 245             g2.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
 246             g2.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height);
 247 
 248             g2.setColor(lightShadow);
 249             g2.drawLine(thumbRect.x, thumbRect.y,
 250                        thumbRect.x + thumbRect.width, thumbRect.y); // top
 251             g2.drawLine(thumbRect.x, thumbRect.y,
 252                        thumbRect.x, thumbRect.y+thumbRect.height); // left
 253 
 254             g2.setColor(darkShadow);
 255             g2.drawLine(thumbRect.x+1,
 256                        thumbRect.y+thumbRect.height,
 257                        thumbRect.x+thumbRect.width,
 258                        thumbRect.y+thumbRect.height);  // bottom
 259             g2.drawLine(thumbRect.x+thumbRect.width,
 260                        thumbRect.y+1,
 261                        thumbRect.x+thumbRect.width,
 262                        thumbRect.y+thumbRect.height); // right
 263         } finally {
 264             if (useBufferedImage) {
 265                 g2.dispose();
 266             }
 267         }
 268         if (useBufferedImage) {
 269             g.drawImage(buffer, 0, 0, null);
 270         }
 271         XToolkit.awtLock();
 272         try {
 273             XlibWrapper.XFlush(XToolkit.getDisplay());
 274         } finally {
 275             XToolkit.awtUnlock();
 276         }
 277     }
 278 
 279       void paintArrows(Graphics2D g, Color background, Color darkShadow, Color lightShadow) {
 280 
 281           g.setColor(background);
 282 
 283         // paint firstArrow
 284         if (pressed && (mode == AdjustmentEvent.UNIT_DECREMENT)) {
 285             g.fill(firstArrow);
 286             g.setColor(lightShadow);
 287             g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0],
 288                     firstArrow.xpoints[1],firstArrow.ypoints[1]);
 289             g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1],
 290                     firstArrow.xpoints[2],firstArrow.ypoints[2]);
 291             g.setColor(darkShadow);
 292             g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2],
 293                     firstArrow.xpoints[0],firstArrow.ypoints[0]);
 294 
 295         }
 296         else {
 297             g.fill(firstArrow);
 298             g.setColor(darkShadow);
 299             g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0],
 300                     firstArrow.xpoints[1],firstArrow.ypoints[1]);
 301             g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1],
 302                     firstArrow.xpoints[2],firstArrow.ypoints[2]);
 303             g.setColor(lightShadow);
 304             g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2],
 305                     firstArrow.xpoints[0],firstArrow.ypoints[0]);
 306 
 307         }
 308 
 309         g.setColor(background);
 310         // paint second Arrow
 311         if (pressed && (mode == AdjustmentEvent.UNIT_INCREMENT)) {
 312             g.fill(secondArrow);
 313             g.setColor(lightShadow);
 314             g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0],
 315                     secondArrow.xpoints[1],secondArrow.ypoints[1]);
 316             g.setColor(darkShadow);
 317             g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1],
 318                     secondArrow.xpoints[2],secondArrow.ypoints[2]);
 319             g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2],
 320                     secondArrow.xpoints[0],secondArrow.ypoints[0]);
 321 
 322         }
 323         else {
 324             g.fill(secondArrow);
 325             g.setColor(darkShadow);
 326             g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0],
 327                     secondArrow.xpoints[1],secondArrow.ypoints[1]);
 328             g.setColor(lightShadow);
 329             g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1],
 330                     secondArrow.xpoints[2],secondArrow.ypoints[2]);
 331             g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2],
 332                     secondArrow.xpoints[0],secondArrow.ypoints[0]);
 333 
 334         }
 335 
 336     }
 337 
 338     /**
 339      * Tell the scroller to start scrolling.
 340      */
 341     void startScrolling() {
 342         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 343             log.finer("Start scrolling on " + this);
 344         }
 345         // Make sure that we scroll at least once
 346         scroll();
 347 
 348         // wake up the scroll repeater
 349         if (scroller == null) {
 350             // If there isn't a scroller, then create
 351             // one and start it.
 352             scroller = new XScrollRepeater(this);
 353         } else {
 354             scroller.setScrollbar(this);
 355         }
 356         scroller.start();
 357     }
 358 
 359     /**
 360      * Tell the instance scroller to start scrolling.
 361      * See 6243382 for more information
 362      */
 363     void startScrollingInstance() {
 364         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 365             log.finer("Start scrolling on " + this);
 366         }
 367         // Make sure that we scroll at least once
 368         scroll();
 369 
 370         i_scroller.setScrollbar(this);
 371         i_scroller.start();
 372     }
 373 
 374     /**
 375      * Tell the instance scroller to stop scrolling.
 376      * See 6243382 for more information
 377      */
 378     void stopScrollingInstance() {
 379         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 380             log.finer("Stop scrolling on " + this);
 381         }
 382 
 383         i_scroller.stop();
 384     }
 385 
 386     /**
 387      * The set method for mode property.
 388      * See 6243382 for more information
 389      */
 390     public void setMode(int mode){
 391         this.mode = mode;
 392     }
 393 
 394     /**
 395      * Scroll one unit.
 396      * @see notifyValue
 397      */
 398     void scroll() {
 399         switch (mode) {
 400           case AdjustmentEvent.UNIT_DECREMENT:
 401               notifyValue(val - line);
 402               return;
 403 
 404           case AdjustmentEvent.UNIT_INCREMENT:
 405               notifyValue(val + line);
 406               return;
 407 
 408           case AdjustmentEvent.BLOCK_DECREMENT:
 409               notifyValue(val - page);
 410               return;
 411 
 412           case AdjustmentEvent.BLOCK_INCREMENT:
 413               notifyValue(val + page);
 414               return;
 415         }
 416         return;
 417     }
 418 
 419     boolean isInArrow(int x, int y) {
 420         // Mouse is considered to be in the arrow if it is anywhere in the
 421         // arrow area.
 422         int coord = (alignment == ALIGNMENT_HORIZONTAL ? x : y);
 423         int arrAreaH = getArrowAreaWidth();
 424 
 425         if (coord < arrAreaH || coord > barLength - arrAreaH + 1) {
 426             return true;
 427         }
 428         return false;
 429     }
 430 
 431     /**
 432      * Is x,y in the scroll thumb?
 433      *
 434      * If we ever cache the thumb rect, we may need to clone the result of
 435      * calculateThumbRect().
 436      */
 437     boolean isInThumb(int x, int y) {
 438         Rectangle thumbRect = calculateThumbRect();
 439 
 440         // If the mouse is in the shadow of the thumb or the shadow of the
 441         // scroll track, treat it as hitting the thumb.  So, slightly enlarge
 442         // our rectangle.
 443         thumbRect.x -= 1;
 444         thumbRect.width += 3;
 445         thumbRect.height += 1;
 446         return thumbRect.contains(x,y);
 447     }
 448 
 449     abstract boolean beforeThumb(int x, int y);
 450 
 451     /**
 452      *
 453      * @see java.awt.event.MouseEvent
 454      * MouseEvent.MOUSE_CLICKED
 455      * MouseEvent.MOUSE_PRESSED
 456      * MouseEvent.MOUSE_RELEASED
 457      * MouseEvent.MOUSE_MOVED
 458      * MouseEvent.MOUSE_ENTERED
 459      * MouseEvent.MOUSE_EXITED
 460      * MouseEvent.MOUSE_DRAGGED
 461      */
 462     public void handleMouseEvent(int id, int modifiers, int x, int y) {
 463         if ((modifiers & InputEvent.BUTTON1_MASK) == 0) {
 464             return;
 465         }
 466 
 467         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 468              String type;
 469              switch (id) {
 470                 case MouseEvent.MOUSE_PRESSED:
 471                     type = "press";
 472                     break;
 473                 case MouseEvent.MOUSE_RELEASED:
 474                     type = "release";
 475                     break;
 476                 case MouseEvent.MOUSE_DRAGGED:
 477                     type = "drag";
 478                     break;
 479                 default:
 480                     type = "other";
 481              }
 482              log.finer("Mouse " + type + " event in scroll bar " + this +
 483                                                    "x = " + x + ", y = " + y +
 484                                                    ", on arrow: " + isInArrow(x, y) +
 485                                                    ", on thumb: " + isInThumb(x, y) + ", before thumb: " + beforeThumb(x, y)
 486                                                    + ", thumb rect" + calculateThumbRect());
 487         }
 488         switch (id) {
 489           case MouseEvent.MOUSE_PRESSED:
 490               if (isInArrow(x, y)) {
 491                   pressed = true;
 492                   if (beforeThumb(x, y)) {
 493                       mode = AdjustmentEvent.UNIT_DECREMENT;
 494                   } else {
 495                       mode = AdjustmentEvent.UNIT_INCREMENT;
 496                   }
 497                   sb.repaintScrollbarRequest(this);
 498                   startScrolling();
 499                   break;
 500               }
 501 
 502               if (isInThumb(x, y)) {
 503                   mode = AdjustmentEvent.TRACK;
 504               } else {
 505                   if (beforeThumb(x, y)) {
 506                       mode = AdjustmentEvent.BLOCK_DECREMENT;
 507                   } else {
 508                       mode = AdjustmentEvent.BLOCK_INCREMENT;
 509                   }
 510                   startScrolling();
 511               }
 512               Rectangle pos = calculateThumbRect();
 513               thumbOffset = new Point(x - pos.x, y - pos.y);
 514               break;
 515 
 516           case MouseEvent.MOUSE_RELEASED:
 517               pressed = false;
 518               sb.repaintScrollbarRequest(this);
 519               scroller.stop();
 520               if(dragging){
 521                   handleTrackEvent(x, y, false);
 522                   dragging=false;
 523               }
 524               break;
 525 
 526           case MouseEvent.MOUSE_DRAGGED:
 527               dragging = true;
 528               handleTrackEvent(x, y, true);
 529         }
 530     }
 531 
 532     private void handleTrackEvent(int x, int y, boolean isAdjusting){
 533         if (mode == AdjustmentEvent.TRACK) {
 534             notifyValue(calculateCursorOffset(x, y), isAdjusting);
 535         }
 536     }
 537 
 538     private int calculateCursorOffset(int x, int y){
 539         if (alignment == ALIGNMENT_HORIZONTAL) {
 540             if (dragging)
 541                 return Math.max(0,(int)((x - (thumbOffset.x + getArrowAreaWidth()))/getScaleFactor())) + min;
 542             else
 543                 return Math.max(0,(int)((x - (getArrowAreaWidth()))/getScaleFactor())) + min;
 544         } else {
 545             if (dragging)
 546                 return Math.max(0,(int)((y - (thumbOffset.y + getArrowAreaWidth()))/getScaleFactor())) + min;
 547             else
 548                 return Math.max(0,(int)((y - (getArrowAreaWidth()))/getScaleFactor())) + min;
 549         }
 550     }
 551 
 552 /*
 553   private void updateNeedsRepaint() {
 554         Rectangle thumbRect = calculateThumbRect();
 555         if (!prevThumb.equals(thumbRect)) {
 556             needsRepaint = true;
 557         }
 558         prevThumb = thumbRect;
 559     }
 560     */
 561 
 562     /**
 563      * Sets the values for this Scrollbar.
 564      * This method enforces the same constraints as in java.awt.Scrollbar:
 565      * <UL>
 566      * <LI> The maximum must be greater than the minimum </LI>
 567      * <LI> The value must be greater than or equal to the minumum
 568      *      and less than or equal to the maximum minus the
 569      *      visible amount </LI>
 570      * <LI> The visible amount must be greater than 1 and less than or equal
 571      *      to the difference between the maximum and minimum values. </LI>
 572      * </UL>
 573      * Values which do not meet these criteria are quietly coerced to the
 574      * appropriate boundary value.
 575      * @param value is the position in the current window.
 576      * @param visible is the amount visible per page
 577      * @param minimum is the minimum value of the scrollbar
 578      * @param maximum is the maximum value of the scrollbar
 579      */
 580     synchronized void setValues(int value, int visible, int minimum, int maximum) {
 581         if (maximum <= minimum) {
 582             maximum = minimum + 1;
 583         }
 584         if (visible > maximum - minimum) {
 585             visible = maximum - minimum;
 586         }
 587         if (visible < 1) {
 588             visible = 1;
 589         }
 590         if (value < minimum) {
 591             value = minimum;
 592         }
 593         if (value > maximum - visible) {
 594             value = maximum - visible;
 595         }
 596 
 597         this.val = value;
 598         this.vis = visible;
 599         this.min = minimum;
 600         this.max = maximum;
 601     }
 602 
 603     /**
 604      * Sets param of this Scrollbar to the specified values.
 605      * @param value is the position in the current window.
 606      * @param visible is the amount visible per page
 607      * @param minimum is the minimum value of the scrollbar
 608      * @param maximum is the maximum value of the scrollbar
 609      * @param unitSize is the unit size for increment or decrement of the value
 610      * @param page is the block size for increment or decrement of the value
 611      * @see #setValues
 612      */
 613     synchronized void setValues(int value, int visible, int minimum, int maximum,
 614                                 int unitSize, int blockSize) {
 615         /* Use setValues so that a consistent policy
 616          * relating minimum, maximum, and value is enforced.
 617          */
 618         setValues(value, visible, minimum, maximum);
 619         setUnitIncrement(unitSize);
 620         setBlockIncrement(blockSize);
 621     }
 622 
 623     /**
 624      * Returns the current value of this Scrollbar.
 625      * @see #getMinimum
 626      * @see #getMaximum
 627      */
 628     int getValue() {
 629         return val;
 630     }
 631 
 632     /**
 633      * Sets the value of this Scrollbar to the specified value.
 634      * @param value the new value of the Scrollbar. If this value is
 635      * below the current minimum or above the current maximum minus
 636      * the visible amount, it becomes the new one of those values,
 637      * respectively.
 638      * @see #getValue
 639      */
 640     synchronized void setValue(int newValue) {
 641         /* Use setValues so that a consistent policy
 642          * relating minimum, maximum, and value is enforced.
 643          */
 644         setValues(newValue, vis, min, max);
 645     }
 646 
 647     /**
 648      * Returns the minimum value of this Scrollbar.
 649      * @see #getMaximum
 650      * @see #getValue
 651      */
 652     int getMinimum() {
 653         return min;
 654     }
 655 
 656     /**
 657      * Sets the minimum value for this Scrollbar.
 658      * @param minimum the minimum value of the scrollbar
 659      */
 660     synchronized void setMinimum(int newMinimum) {
 661         /* Use setValues so that a consistent policy
 662          * relating minimum, maximum, and value is enforced.
 663          */
 664         setValues(val, vis, newMinimum, max);
 665     }
 666 
 667     /**
 668      * Returns the maximum value of this Scrollbar.
 669      * @see #getMinimum
 670      * @see #getValue
 671      */
 672     int getMaximum() {
 673         return max;
 674     }
 675 
 676     /**
 677      * Sets the maximum value for this Scrollbar.
 678      * @param maximum the maximum value of the scrollbar
 679      */
 680     synchronized void setMaximum(int newMaximum) {
 681         /* Use setValues so that a consistent policy
 682          * relating minimum, maximum, and value is enforced.
 683          */
 684         setValues(val, vis, min, newMaximum);
 685     }
 686 
 687     /**
 688      * Returns the visible amount of this Scrollbar.
 689      */
 690     int getVisibleAmount() {
 691         return vis;
 692     }
 693 
 694     /**
 695      * Sets the visible amount of this Scrollbar, which is the range
 696      * of values represented by the width of the scroll bar's bubble.
 697      * @param visible the amount visible per page
 698      */
 699     synchronized void setVisibleAmount(int newAmount) {
 700         setValues(val, newAmount, min, max);
 701     }
 702 
 703     /**
 704      * Sets the unit increment for this scrollbar. This is the value
 705      * that will be added (subtracted) when the user hits the unit down
 706      * (up) gadgets.
 707      * @param unitSize is the unit size for increment or decrement of the value
 708      */
 709     synchronized void setUnitIncrement(int unitSize) {
 710         line = unitSize;
 711     }
 712 
 713     /**
 714      * Gets the unit increment for this scrollbar.
 715      */
 716     int getUnitIncrement() {
 717         return line;
 718     }
 719 
 720     /**
 721      * Sets the block increment for this scrollbar. This is the value
 722      * that will be added (subtracted) when the user hits the block down
 723      * (up) gadgets.
 724      * @param blockSize is the block size for increment or decrement of the value
 725      */
 726     synchronized void setBlockIncrement(int blockSize) {
 727         page = blockSize;
 728     }
 729 
 730     /**
 731      * Gets the block increment for this scrollbar.
 732      */
 733     int getBlockIncrement() {
 734         return page;
 735     }
 736 
 737     /**
 738      * Width of the arrow image
 739      */
 740     int getArrowWidth() {
 741         return getArrowAreaWidth() - 2*ARROW_IND;
 742     }
 743 
 744     /**
 745      * Width of the area reserved for arrow
 746      */
 747     int getArrowAreaWidth() {
 748         return arrowArea;
 749     }
 750 
 751     void calculateArrowWidth() {
 752         if (barLength < 2*barWidth + MIN_THUMB_H + 2) {
 753             arrowArea = (barLength - MIN_THUMB_H + 2*ARROW_IND)/2 - 1;
 754         }
 755         else {
 756             arrowArea = barWidth - 1;
 757         }
 758     }
 759 
 760     /**
 761      * Returns the scale factor for the thumbArea ( thumbAreaH / (max - min)).
 762      * @see #getArrowAreaSize
 763      */
 764     private double getScaleFactor(){
 765         double f = (double)(barLength - 2*getArrowAreaWidth()) / Math.max(1,(max - min));
 766         return f;
 767     }
 768 
 769     /**
 770      * Method to calculate the scroll thumb's size and position.  This is
 771      * based on CalcSliderRect in ScrollBar.c of Motif source.
 772      *
 773      * If we ever cache the thumb rect, we'll need to use a clone in
 774      * isInThumb().
 775      */
 776     protected Rectangle calculateThumbRect() {
 777         float range;
 778         float trueSize;  // Area of scroll track
 779         float factor;
 780         float slideSize;
 781         int minSliderWidth;
 782         int minSliderHeight;
 783         int hitTheWall = 0;
 784         int arrAreaH = getArrowAreaWidth();
 785         Rectangle retVal = new Rectangle(0,0,0,0);
 786 
 787         trueSize = barLength - 2*arrAreaH - 1;  // Same if vert or horiz
 788 
 789         if (alignment == ALIGNMENT_HORIZONTAL) {
 790             minSliderWidth = MIN_THUMB_H ;  // Base on user-set vis?
 791             minSliderHeight = height - 3;
 792         }
 793         else {  // Vertical
 794             minSliderWidth = width - 3;
 795             minSliderHeight = MIN_THUMB_H ;
 796 
 797         }
 798 
 799         // Total number of user units displayed
 800             range = max - min;
 801 
 802         // A naive notion of pixels per user unit
 803             factor = trueSize / range;
 804 
 805             // A naive notion of the size of the slider in pixels
 806             // in thermo, slider_size is 0 ans is ignored
 807             slideSize = vis * factor;
 808 
 809         if (alignment == ALIGNMENT_HORIZONTAL) {
 810             // Simulating MAX_SCROLLBAR_DIMENSION macro
 811             int localVal = (int) (slideSize + 0.5);
 812             int localMin = minSliderWidth;
 813             if (localVal > localMin) {
 814                 retVal.width = localVal;
 815             }
 816             else {
 817                 retVal.width = localMin;
 818                 hitTheWall = localMin;
 819             }
 820             retVal.height = minSliderHeight;
 821         }
 822         else {  // Vertical
 823             retVal.width = minSliderWidth;
 824 
 825             // Simulating MAX_SCROLLBAR_DIMENSION macro
 826             int localVal = (int) (slideSize + 0.5);
 827             int localMin = minSliderHeight;
 828             if (localVal > localMin) {
 829                 retVal.height = localVal;
 830             }
 831             else {
 832                 retVal.height = localMin;
 833                 hitTheWall = localMin;
 834             }
 835         }
 836 
 837         if (hitTheWall != 0) {
 838             trueSize -= hitTheWall;  // Actual pixels available
 839             range -= vis;            // Actual range
 840             factor = trueSize / range;
 841         }
 842 
 843         if (alignment == ALIGNMENT_HORIZONTAL) {
 844                     retVal.x = ((int) (((((float) val)
 845                         - ((float) min)) * factor) + 0.5))
 846                         + arrAreaH;
 847                     retVal.y = 1;
 848 
 849         }
 850         else {
 851             retVal.x = 1;
 852                     retVal.y = ((int) (((((float) val)
 853                         - ((float) min)) * factor) + 0.5))
 854                         + arrAreaH;
 855         }
 856 
 857         // There was one final adjustment here in the Motif function, which was
 858         // noted to be for backward-compatiblity.  It has been left out for now.
 859 
 860         return retVal;
 861     }
 862 
 863     public String toString() {
 864         return getClass() + "[" + width + "x" + height + "," + barWidth + "x" + barLength + "]";
 865     }
 866 }
 867 
 868 
 869 class XScrollRepeater implements Runnable {
 870     /**
 871      * Time to pause before the first scroll repeat.
 872      */
 873     static int beginPause = 500;
 874     // Reminder - make this a user definable property
 875 
 876     /**
 877      * Time to pause between each scroll repeat.
 878      */
 879     static int repeatPause = 100;
 880     // Reminder - make this a user definable property
 881 
 882     /**
 883      * The scrollbar that we sending scrolling.
 884      */
 885     XScrollbar sb;
 886 
 887     /**
 888      * newScroll gets reset when a new scrollbar gets set.
 889      */
 890     boolean newScroll;
 891 
 892 
 893     boolean shouldSkip;
 894 
 895     /**
 896      * Creates a new scroll repeater.
 897      * @param sb the scrollbar that this thread will scroll
 898      */
 899     XScrollRepeater(XScrollbar sb) {
 900         this.setScrollbar(sb);
 901         newScroll = true;
 902     }
 903 
 904     public void start() {
 905         stop();
 906         shouldSkip = false;
 907         XToolkit.schedule(this, beginPause);
 908     }
 909 
 910     public void stop() {
 911         synchronized(this) {
 912             shouldSkip = true;
 913         }
 914         XToolkit.remove(this);
 915     }
 916 
 917     /**
 918      * Sets the scrollbar.
 919      * @param sb the scrollbar that this thread will scroll
 920      */
 921     public synchronized void setScrollbar(XScrollbar sb) {
 922         this.sb = sb;
 923         stop();
 924         newScroll = true;
 925     }
 926 
 927     public void run () {
 928         synchronized(this) {
 929             if (shouldSkip) {
 930                 return;
 931             }
 932         }
 933         sb.scroll();
 934         XToolkit.schedule(this, repeatPause);
 935     }
 936 
 937 }