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