1 /*
   2  * Copyright (c) 2003, 2013, 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 static final 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     protected abstract 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 paintAll paint the whole scrollbar if true, just the thumb is false
 165      */
 166     void paint(Graphics g, Color colors[], boolean paintAll) {
 167         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 168             log.finer("Painting scrollbar " + this);
 169         }
 170 
 171         boolean useBufferedImage = false;
 172         Graphics2D g2 = null;
 173         BufferedImage buffer = null;
 174         if (!(g instanceof Graphics2D)) {
 175             // Fix for 5045936, 5055171. While printing, g is an instance
 176             //   of sun.print.ProxyPrintGraphics which extends Graphics.
 177             //   So we use a separate buffered image and its graphics is
 178             //   always Graphics2D instance
 179             X11GraphicsConfig graphicsConfig = (X11GraphicsConfig)(sb.getEventSource().getGraphicsConfiguration());
 180             buffer = graphicsConfig.createCompatibleImage(width, height);
 181             g2 = buffer.createGraphics();
 182             useBufferedImage = true;
 183         } else {
 184             g2 = (Graphics2D)g;
 185         }
 186         try {
 187             Rectangle thumbRect = calculateThumbRect();
 188 
 189 //              if (prevH == thumbH && prevY == thumbPosY) {
 190 //                  return;
 191 //              }
 192 
 193             prevThumb = thumbRect;
 194 
 195             // TODO: Share Motif colors
 196             Color back = colors[XComponentPeer.BACKGROUND_COLOR];
 197             Color selectColor = new Color(MotifColorUtilities.calculateSelectFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
 198             Color darkShadow = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
 199             Color lightShadow = new Color(MotifColorUtilities.calculateTopShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue()));
 200 
 201             XToolkit.awtLock();
 202             try {
 203                 XlibWrapper.XFlush(XToolkit.getDisplay());
 204             } finally {
 205                 XToolkit.awtUnlock();
 206             }
 207             /* paint the background slightly darker */
 208             if (paintAll) {
 209                 // fill the entire background
 210                 g2.setColor(selectColor);
 211                 if (alignment == ALIGNMENT_HORIZONTAL) {
 212                     g2.fillRect(0, 0, thumbRect.x, height);
 213                     g2.fillRect(thumbRect.x + thumbRect.width , 0, width - (thumbRect.x + thumbRect.width), height);
 214                 } else {
 215                     g2.fillRect(0, 0, width, thumbRect.y);
 216                     g2.fillRect(0, thumbRect.y + thumbRect.height, width, height - (thumbRect.y + thumbRect.height));
 217                 }
 218 
 219                 // Paint edges
 220                 // TODO: Share Motif 3d rect drawing
 221 
 222                 g2.setColor(darkShadow);
 223                 g2.drawLine(0, 0, width-1, 0);           // top
 224                 g2.drawLine(0, 0, 0, height-1);          // left
 225 
 226                 g2.setColor(lightShadow);
 227                 g2.drawLine(1, height-1, width-1, height-1); // bottom
 228                 g2.drawLine(width-1, 1, width-1, height-1);  // right
 229             } else {
 230                 // Clear all thumb area
 231                 g2.setColor(selectColor);
 232                 Rectangle thumbArea = getThumbArea();
 233                 g2.fill(thumbArea);
 234             }
 235 
 236             if (paintAll) {
 237                 // ************ paint the arrows
 238                  paintArrows(g2, colors[XComponentPeer.BACKGROUND_COLOR], darkShadow, lightShadow );
 239 
 240             }
 241 
 242             // Thumb
 243             g2.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
 244             g2.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height);
 245 
 246             g2.setColor(lightShadow);
 247             g2.drawLine(thumbRect.x, thumbRect.y,
 248                        thumbRect.x + thumbRect.width, thumbRect.y); // top
 249             g2.drawLine(thumbRect.x, thumbRect.y,
 250                        thumbRect.x, thumbRect.y+thumbRect.height); // left
 251 
 252             g2.setColor(darkShadow);
 253             g2.drawLine(thumbRect.x+1,
 254                        thumbRect.y+thumbRect.height,
 255                        thumbRect.x+thumbRect.width,
 256                        thumbRect.y+thumbRect.height);  // bottom
 257             g2.drawLine(thumbRect.x+thumbRect.width,
 258                        thumbRect.y+1,
 259                        thumbRect.x+thumbRect.width,
 260                        thumbRect.y+thumbRect.height); // right
 261         } finally {
 262             if (useBufferedImage) {
 263                 g2.dispose();
 264             }
 265         }
 266         if (useBufferedImage) {
 267             g.drawImage(buffer, 0, 0, null);
 268         }
 269         XToolkit.awtLock();
 270         try {
 271             XlibWrapper.XFlush(XToolkit.getDisplay());
 272         } finally {
 273             XToolkit.awtUnlock();
 274         }
 275     }
 276 
 277       void paintArrows(Graphics2D g, Color background, Color darkShadow, Color lightShadow) {
 278 
 279           g.setColor(background);
 280 
 281         // paint firstArrow
 282         if (pressed && (mode == AdjustmentEvent.UNIT_DECREMENT)) {
 283             g.fill(firstArrow);
 284             g.setColor(lightShadow);
 285             g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0],
 286                     firstArrow.xpoints[1],firstArrow.ypoints[1]);
 287             g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1],
 288                     firstArrow.xpoints[2],firstArrow.ypoints[2]);
 289             g.setColor(darkShadow);
 290             g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2],
 291                     firstArrow.xpoints[0],firstArrow.ypoints[0]);
 292 
 293         }
 294         else {
 295             g.fill(firstArrow);
 296             g.setColor(darkShadow);
 297             g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0],
 298                     firstArrow.xpoints[1],firstArrow.ypoints[1]);
 299             g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1],
 300                     firstArrow.xpoints[2],firstArrow.ypoints[2]);
 301             g.setColor(lightShadow);
 302             g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2],
 303                     firstArrow.xpoints[0],firstArrow.ypoints[0]);
 304 
 305         }
 306 
 307         g.setColor(background);
 308         // paint second Arrow
 309         if (pressed && (mode == AdjustmentEvent.UNIT_INCREMENT)) {
 310             g.fill(secondArrow);
 311             g.setColor(lightShadow);
 312             g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0],
 313                     secondArrow.xpoints[1],secondArrow.ypoints[1]);
 314             g.setColor(darkShadow);
 315             g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1],
 316                     secondArrow.xpoints[2],secondArrow.ypoints[2]);
 317             g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2],
 318                     secondArrow.xpoints[0],secondArrow.ypoints[0]);
 319 
 320         }
 321         else {
 322             g.fill(secondArrow);
 323             g.setColor(darkShadow);
 324             g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0],
 325                     secondArrow.xpoints[1],secondArrow.ypoints[1]);
 326             g.setColor(lightShadow);
 327             g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1],
 328                     secondArrow.xpoints[2],secondArrow.ypoints[2]);
 329             g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2],
 330                     secondArrow.xpoints[0],secondArrow.ypoints[0]);
 331 
 332         }
 333 
 334     }
 335 
 336     /**
 337      * Tell the scroller to start scrolling.
 338      */
 339     void startScrolling() {
 340         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 341             log.finer("Start scrolling on " + this);
 342         }
 343         // Make sure that we scroll at least once
 344         scroll();
 345 
 346         // wake up the scroll repeater
 347         if (scroller == null) {
 348             // If there isn't a scroller, then create
 349             // one and start it.
 350             scroller = new XScrollRepeater(this);
 351         } else {
 352             scroller.setScrollbar(this);
 353         }
 354         scroller.start();
 355     }
 356 
 357     /**
 358      * Tell the instance scroller to start scrolling.
 359      * See 6243382 for more information
 360      */
 361     void startScrollingInstance() {
 362         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 363             log.finer("Start scrolling on " + this);
 364         }
 365         // Make sure that we scroll at least once
 366         scroll();
 367 
 368         i_scroller.setScrollbar(this);
 369         i_scroller.start();
 370     }
 371 
 372     /**
 373      * Tell the instance scroller to stop scrolling.
 374      * See 6243382 for more information
 375      */
 376     void stopScrollingInstance() {
 377         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 378             log.finer("Stop scrolling on " + this);
 379         }
 380 
 381         i_scroller.stop();
 382     }
 383 
 384     /**
 385      * The set method for mode property.
 386      * See 6243382 for more information
 387      */
 388     public void setMode(int mode){
 389         this.mode = mode;
 390     }
 391 
 392     /**
 393      * Scroll one unit.
 394      * @see #notifyValue
 395      */
 396     void scroll() {
 397         switch (mode) {
 398           case AdjustmentEvent.UNIT_DECREMENT:
 399               notifyValue(val - line);
 400               return;
 401 
 402           case AdjustmentEvent.UNIT_INCREMENT:
 403               notifyValue(val + line);
 404               return;
 405 
 406           case AdjustmentEvent.BLOCK_DECREMENT:
 407               notifyValue(val - page);
 408               return;
 409 
 410           case AdjustmentEvent.BLOCK_INCREMENT:
 411               notifyValue(val + page);
 412               return;
 413         }
 414         return;
 415     }
 416 
 417     boolean isInArrow(int x, int y) {
 418         // Mouse is considered to be in the arrow if it is anywhere in the
 419         // arrow area.
 420         int coord = (alignment == ALIGNMENT_HORIZONTAL ? x : y);
 421         int arrAreaH = getArrowAreaWidth();
 422 
 423         if (coord < arrAreaH || coord > barLength - arrAreaH + 1) {
 424             return true;
 425         }
 426         return false;
 427     }
 428 
 429     /**
 430      * Is x,y in the scroll thumb?
 431      *
 432      * If we ever cache the thumb rect, we may need to clone the result of
 433      * calculateThumbRect().
 434      */
 435     boolean isInThumb(int x, int y) {
 436         Rectangle thumbRect = calculateThumbRect();
 437 
 438         // If the mouse is in the shadow of the thumb or the shadow of the
 439         // scroll track, treat it as hitting the thumb.  So, slightly enlarge
 440         // our rectangle.
 441         thumbRect.x -= 1;
 442         thumbRect.width += 3;
 443         thumbRect.height += 1;
 444         return thumbRect.contains(x,y);
 445     }
 446 
 447     abstract boolean beforeThumb(int x, int y);
 448 
 449     /**
 450      *
 451      * @see java.awt.event.MouseEvent
 452      * MouseEvent.MOUSE_CLICKED
 453      * MouseEvent.MOUSE_PRESSED
 454      * MouseEvent.MOUSE_RELEASED
 455      * MouseEvent.MOUSE_MOVED
 456      * MouseEvent.MOUSE_ENTERED
 457      * MouseEvent.MOUSE_EXITED
 458      * MouseEvent.MOUSE_DRAGGED
 459      */
 460     @SuppressWarnings("deprecation")
 461     public void handleMouseEvent(int id, int modifiers, int x, int y) {
 462         if ((modifiers & InputEvent.BUTTON1_MASK) == 0) {
 463             return;
 464         }
 465 
 466         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 467              String type;
 468              switch (id) {
 469                 case MouseEvent.MOUSE_PRESSED:
 470                     type = "press";
 471                     break;
 472                 case MouseEvent.MOUSE_RELEASED:
 473                     type = "release";
 474                     break;
 475                 case MouseEvent.MOUSE_DRAGGED:
 476                     type = "drag";
 477                     break;
 478                 default:
 479                     type = "other";
 480              }
 481              log.finer("Mouse " + type + " event in scroll bar " + this +
 482                                                    "x = " + x + ", y = " + y +
 483                                                    ", on arrow: " + isInArrow(x, y) +
 484                                                    ", on thumb: " + isInThumb(x, y) + ", before thumb: " + beforeThumb(x, y)
 485                                                    + ", thumb rect" + calculateThumbRect());
 486         }
 487         switch (id) {
 488           case MouseEvent.MOUSE_PRESSED:
 489               if (isInArrow(x, y)) {
 490                   pressed = true;
 491                   if (beforeThumb(x, y)) {
 492                       mode = AdjustmentEvent.UNIT_DECREMENT;
 493                   } else {
 494                       mode = AdjustmentEvent.UNIT_INCREMENT;
 495                   }
 496                   sb.repaintScrollbarRequest(this);
 497                   startScrolling();
 498                   break;
 499               }
 500 
 501               if (isInThumb(x, y)) {
 502                   mode = AdjustmentEvent.TRACK;
 503               } else {
 504                   if (beforeThumb(x, y)) {
 505                       mode = AdjustmentEvent.BLOCK_DECREMENT;
 506                   } else {
 507                       mode = AdjustmentEvent.BLOCK_INCREMENT;
 508                   }
 509                   startScrolling();
 510               }
 511               Rectangle pos = calculateThumbRect();
 512               thumbOffset = new Point(x - pos.x, y - pos.y);
 513               break;
 514 
 515           case MouseEvent.MOUSE_RELEASED:
 516               pressed = false;
 517               sb.repaintScrollbarRequest(this);
 518               scroller.stop();
 519               if(dragging){
 520                   handleTrackEvent(x, y, false);
 521                   dragging=false;
 522               }
 523               break;
 524 
 525           case MouseEvent.MOUSE_DRAGGED:
 526               dragging = true;
 527               handleTrackEvent(x, y, true);
 528         }
 529     }
 530 
 531     private void handleTrackEvent(int x, int y, boolean isAdjusting){
 532         if (mode == AdjustmentEvent.TRACK) {
 533             notifyValue(calculateCursorOffset(x, y), isAdjusting);
 534         }
 535     }
 536 
 537     private int calculateCursorOffset(int x, int y){
 538         if (alignment == ALIGNMENT_HORIZONTAL) {
 539             if (dragging)
 540                 return Math.max(0,(int)((x - (thumbOffset.x + getArrowAreaWidth()))/getScaleFactor())) + min;
 541             else
 542                 return Math.max(0,(int)((x - (getArrowAreaWidth()))/getScaleFactor())) + min;
 543         } else {
 544             if (dragging)
 545                 return Math.max(0,(int)((y - (thumbOffset.y + getArrowAreaWidth()))/getScaleFactor())) + min;
 546             else
 547                 return Math.max(0,(int)((y - (getArrowAreaWidth()))/getScaleFactor())) + min;
 548         }
 549     }
 550 
 551 /*
 552   private void updateNeedsRepaint() {
 553         Rectangle thumbRect = calculateThumbRect();
 554         if (!prevThumb.equals(thumbRect)) {
 555             needsRepaint = true;
 556         }
 557         prevThumb = thumbRect;
 558     }
 559     */
 560 
 561     /**
 562      * Sets the values for this Scrollbar.
 563      * This method enforces the same constraints as in java.awt.Scrollbar:
 564      * <UL>
 565      * <LI> The maximum must be greater than the minimum </LI>
 566      * <LI> The value must be greater than or equal to the minimum
 567      *      and less than or equal to the maximum minus the
 568      *      visible amount </LI>
 569      * <LI> The visible amount must be greater than 1 and less than or equal
 570      *      to the difference between the maximum and minimum values. </LI>
 571      * </UL>
 572      * Values which do not meet these criteria are quietly coerced to the
 573      * appropriate boundary value.
 574      * @param value is the position in the current window.
 575      * @param visible is the amount visible per page
 576      * @param minimum is the minimum value of the scrollbar
 577      * @param maximum is the maximum value of the scrollbar
 578      */
 579     synchronized void setValues(int value, int visible, int minimum, int maximum) {
 580         if (maximum <= minimum) {
 581             maximum = minimum + 1;
 582         }
 583         if (visible > maximum - minimum) {
 584             visible = maximum - minimum;
 585         }
 586         if (visible < 1) {
 587             visible = 1;
 588         }
 589         if (value < minimum) {
 590             value = minimum;
 591         }
 592         if (value > maximum - visible) {
 593             value = maximum - visible;
 594         }
 595 
 596         this.val = value;
 597         this.vis = visible;
 598         this.min = minimum;
 599         this.max = maximum;
 600     }
 601 
 602     /**
 603      * Sets param of this Scrollbar to the specified values.
 604      * @param value is the position in the current window.
 605      * @param visible is the amount visible per page
 606      * @param minimum is the minimum value of the scrollbar
 607      * @param maximum is the maximum value of the scrollbar
 608      * @param unitSize is the unit size for increment or decrement of the value
 609      * @see #setValues
 610      */
 611     synchronized void setValues(int value, int visible, int minimum, int maximum,
 612                                 int unitSize, int blockSize) {
 613         /* Use setValues so that a consistent policy
 614          * relating minimum, maximum, and value is enforced.
 615          */
 616         setValues(value, visible, minimum, maximum);
 617         setUnitIncrement(unitSize);
 618         setBlockIncrement(blockSize);
 619     }
 620 
 621     /**
 622      * Returns the current value of this Scrollbar.
 623      * @see #getMinimum
 624      * @see #getMaximum
 625      */
 626     int getValue() {
 627         return val;
 628     }
 629 
 630     /**
 631      * Sets the value of this Scrollbar to the specified value.
 632      * @param newValue the new value of the Scrollbar. If this value is
 633      * below the current minimum or above the current maximum minus
 634      * the visible amount, it becomes the new one of those values,
 635      * respectively.
 636      * @see #getValue
 637      */
 638     synchronized void setValue(int newValue) {
 639         /* Use setValues so that a consistent policy
 640          * relating minimum, maximum, and value is enforced.
 641          */
 642         setValues(newValue, vis, min, max);
 643     }
 644 
 645     /**
 646      * Returns the minimum value of this Scrollbar.
 647      * @see #getMaximum
 648      * @see #getValue
 649      */
 650     int getMinimum() {
 651         return min;
 652     }
 653 
 654     /**
 655      * Sets the minimum value for this Scrollbar.
 656      * @param newMinimum the minimum value of the scrollbar
 657      */
 658     synchronized void setMinimum(int newMinimum) {
 659         /* Use setValues so that a consistent policy
 660          * relating minimum, maximum, and value is enforced.
 661          */
 662         setValues(val, vis, newMinimum, max);
 663     }
 664 
 665     /**
 666      * Returns the maximum value of this Scrollbar.
 667      * @see #getMinimum
 668      * @see #getValue
 669      */
 670     int getMaximum() {
 671         return max;
 672     }
 673 
 674     /**
 675      * Sets the maximum value for this Scrollbar.
 676      * @param newMaximum the maximum value of the scrollbar
 677      */
 678     synchronized void setMaximum(int newMaximum) {
 679         /* Use setValues so that a consistent policy
 680          * relating minimum, maximum, and value is enforced.
 681          */
 682         setValues(val, vis, min, newMaximum);
 683     }
 684 
 685     /**
 686      * Returns the visible amount of this Scrollbar.
 687      */
 688     int getVisibleAmount() {
 689         return vis;
 690     }
 691 
 692     /**
 693      * Sets the visible amount of this Scrollbar, which is the range
 694      * of values represented by the width of the scroll bar's bubble.
 695      * @param newAmount the amount visible per page
 696      */
 697     synchronized void setVisibleAmount(int newAmount) {
 698         setValues(val, newAmount, min, max);
 699     }
 700 
 701     /**
 702      * Sets the unit increment for this scrollbar. This is the value
 703      * that will be added (subtracted) when the user hits the unit down
 704      * (up) gadgets.
 705      * @param unitSize is the unit size for increment or decrement of the value
 706      */
 707     synchronized void setUnitIncrement(int unitSize) {
 708         line = unitSize;
 709     }
 710 
 711     /**
 712      * Gets the unit increment for this scrollbar.
 713      */
 714     int getUnitIncrement() {
 715         return line;
 716     }
 717 
 718     /**
 719      * Sets the block increment for this scrollbar. This is the value
 720      * that will be added (subtracted) when the user hits the block down
 721      * (up) gadgets.
 722      * @param blockSize is the block size for increment or decrement of the value
 723      */
 724     synchronized void setBlockIncrement(int blockSize) {
 725         page = blockSize;
 726     }
 727 
 728     /**
 729      * Gets the block increment for this scrollbar.
 730      */
 731     int getBlockIncrement() {
 732         return page;
 733     }
 734 
 735     /**
 736      * Width of the arrow image
 737      */
 738     int getArrowWidth() {
 739         return getArrowAreaWidth() - 2*ARROW_IND;
 740     }
 741 
 742     /**
 743      * Width of the area reserved for arrow
 744      */
 745     int getArrowAreaWidth() {
 746         return arrowArea;
 747     }
 748 
 749     void calculateArrowWidth() {
 750         if (barLength < 2*barWidth + MIN_THUMB_H + 2) {
 751             arrowArea = (barLength - MIN_THUMB_H + 2*ARROW_IND)/2 - 1;
 752         }
 753         else {
 754             arrowArea = barWidth - 1;
 755         }
 756     }
 757 
 758     /**
 759      * Returns the scale factor for the thumbArea ( thumbAreaH / (max - min)).
 760      * @see #getArrowAreaWidth
 761      */
 762     private double getScaleFactor(){
 763         double f = (double)(barLength - 2*getArrowAreaWidth()) / Math.max(1,(max - min));
 764         return f;
 765     }
 766 
 767     /**
 768      * Method to calculate the scroll thumb's size and position.  This is
 769      * based on CalcSliderRect in ScrollBar.c of Motif source.
 770      *
 771      * If we ever cache the thumb rect, we'll need to use a clone in
 772      * isInThumb().
 773      */
 774     protected Rectangle calculateThumbRect() {
 775         float range;
 776         float trueSize;  // Area of scroll track
 777         float factor;
 778         float slideSize;
 779         int minSliderWidth;
 780         int minSliderHeight;
 781         int hitTheWall = 0;
 782         int arrAreaH = getArrowAreaWidth();
 783         Rectangle retVal = new Rectangle(0,0,0,0);
 784 
 785         trueSize = barLength - 2*arrAreaH - 1;  // Same if vert or horiz
 786 
 787         if (alignment == ALIGNMENT_HORIZONTAL) {
 788             minSliderWidth = MIN_THUMB_H ;  // Base on user-set vis?
 789             minSliderHeight = height - 3;
 790         }
 791         else {  // Vertical
 792             minSliderWidth = width - 3;
 793             minSliderHeight = MIN_THUMB_H ;
 794 
 795         }
 796 
 797         // Total number of user units displayed
 798             range = max - min;
 799 
 800         // A naive notion of pixels per user unit
 801             factor = trueSize / range;
 802 
 803             // A naive notion of the size of the slider in pixels
 804             // in thermo, slider_size is 0 ans is ignored
 805             slideSize = vis * factor;
 806 
 807         if (alignment == ALIGNMENT_HORIZONTAL) {
 808             // Simulating MAX_SCROLLBAR_DIMENSION macro
 809             int localVal = (int) (slideSize + 0.5);
 810             int localMin = minSliderWidth;
 811             if (localVal > localMin) {
 812                 retVal.width = localVal;
 813             }
 814             else {
 815                 retVal.width = localMin;
 816                 hitTheWall = localMin;
 817             }
 818             retVal.height = minSliderHeight;
 819         }
 820         else {  // Vertical
 821             retVal.width = minSliderWidth;
 822 
 823             // Simulating MAX_SCROLLBAR_DIMENSION macro
 824             int localVal = (int) (slideSize + 0.5);
 825             int localMin = minSliderHeight;
 826             if (localVal > localMin) {
 827                 retVal.height = localVal;
 828             }
 829             else {
 830                 retVal.height = localMin;
 831                 hitTheWall = localMin;
 832             }
 833         }
 834 
 835         if (hitTheWall != 0) {
 836             trueSize -= hitTheWall;  // Actual pixels available
 837             range -= vis;            // Actual range
 838             factor = trueSize / range;
 839         }
 840 
 841         if (alignment == ALIGNMENT_HORIZONTAL) {
 842                     retVal.x = ((int) (((((float) val)
 843                         - ((float) min)) * factor) + 0.5))
 844                         + arrAreaH;
 845                     retVal.y = 1;
 846 
 847         }
 848         else {
 849             retVal.x = 1;
 850                     retVal.y = ((int) (((((float) val)
 851                         - ((float) min)) * factor) + 0.5))
 852                         + arrAreaH;
 853         }
 854 
 855         // There was one final adjustment here in the Motif function, which was
 856         // noted to be for backward-compatibility.  It has been left out for now.
 857 
 858         return retVal;
 859     }
 860 
 861     public String toString() {
 862         return getClass() + "[" + width + "x" + height + "," + barWidth + "x" + barLength + "]";
 863     }
 864 }
 865 
 866 
 867 class XScrollRepeater implements Runnable {
 868     /**
 869      * Time to pause before the first scroll repeat.
 870      */
 871     static int beginPause = 500;
 872     // Reminder - make this a user definable property
 873 
 874     /**
 875      * Time to pause between each scroll repeat.
 876      */
 877     static int repeatPause = 100;
 878     // Reminder - make this a user definable property
 879 
 880     /**
 881      * The scrollbar that we sending scrolling.
 882      */
 883     XScrollbar sb;
 884 
 885     /**
 886      * newScroll gets reset when a new scrollbar gets set.
 887      */
 888     boolean newScroll;
 889 
 890 
 891     boolean shouldSkip;
 892 
 893     /**
 894      * Creates a new scroll repeater.
 895      * @param sb the scrollbar that this thread will scroll
 896      */
 897     XScrollRepeater(XScrollbar sb) {
 898         this.setScrollbar(sb);
 899         newScroll = true;
 900     }
 901 
 902     public void start() {
 903         stop();
 904         shouldSkip = false;
 905         XToolkit.schedule(this, beginPause);
 906     }
 907 
 908     public void stop() {
 909         synchronized(this) {
 910             shouldSkip = true;
 911         }
 912         XToolkit.remove(this);
 913     }
 914 
 915     /**
 916      * Sets the scrollbar.
 917      * @param sb the scrollbar that this thread will scroll
 918      */
 919     public synchronized void setScrollbar(XScrollbar sb) {
 920         this.sb = sb;
 921         stop();
 922         newScroll = true;
 923     }
 924 
 925     public void run () {
 926         synchronized(this) {
 927             if (shouldSkip) {
 928                 return;
 929             }
 930         }
 931         sb.scroll();
 932         XToolkit.schedule(this, repeatPause);
 933     }
 934 
 935 }