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 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 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     public void handleMouseEvent(int id, int modifiers, int x, int y) {
 461         if ((modifiers & InputEvent.BUTTON1_MASK) == 0) {
 462             return;
 463         }
 464 
 465         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 466              String type;
 467              switch (id) {
 468                 case MouseEvent.MOUSE_PRESSED:
 469                     type = "press";
 470                     break;
 471                 case MouseEvent.MOUSE_RELEASED:
 472                     type = "release";
 473                     break;
 474                 case MouseEvent.MOUSE_DRAGGED:
 475                     type = "drag";
 476                     break;
 477                 default:
 478                     type = "other";
 479              }
 480              log.finer("Mouse " + type + " event in scroll bar " + this +
 481                                                    "x = " + x + ", y = " + y +
 482                                                    ", on arrow: " + isInArrow(x, y) +
 483                                                    ", on thumb: " + isInThumb(x, y) + ", before thumb: " + beforeThumb(x, y)
 484                                                    + ", thumb rect" + calculateThumbRect());
 485         }
 486         switch (id) {
 487           case MouseEvent.MOUSE_PRESSED:
 488               if (isInArrow(x, y)) {
 489                   pressed = true;
 490                   if (beforeThumb(x, y)) {
 491                       mode = AdjustmentEvent.UNIT_DECREMENT;
 492                   } else {
 493                       mode = AdjustmentEvent.UNIT_INCREMENT;
 494                   }
 495                   sb.repaintScrollbarRequest(this);
 496                   startScrolling();
 497                   break;
 498               }
 499 
 500               if (isInThumb(x, y)) {
 501                   mode = AdjustmentEvent.TRACK;
 502               } else {
 503                   if (beforeThumb(x, y)) {
 504                       mode = AdjustmentEvent.BLOCK_DECREMENT;
 505                   } else {
 506                       mode = AdjustmentEvent.BLOCK_INCREMENT;
 507                   }
 508                   startScrolling();
 509               }
 510               Rectangle pos = calculateThumbRect();
 511               thumbOffset = new Point(x - pos.x, y - pos.y);
 512               break;
 513 
 514           case MouseEvent.MOUSE_RELEASED:
 515               pressed = false;
 516               sb.repaintScrollbarRequest(this);
 517               scroller.stop();
 518               if(dragging){
 519                   handleTrackEvent(x, y, false);
 520                   dragging=false;
 521               }
 522               break;
 523 
 524           case MouseEvent.MOUSE_DRAGGED:
 525               dragging = true;
 526               handleTrackEvent(x, y, true);
 527         }
 528     }
 529 
 530     private void handleTrackEvent(int x, int y, boolean isAdjusting){
 531         if (mode == AdjustmentEvent.TRACK) {
 532             notifyValue(calculateCursorOffset(x, y), isAdjusting);
 533         }
 534     }
 535 
 536     private int calculateCursorOffset(int x, int y){
 537         if (alignment == ALIGNMENT_HORIZONTAL) {
 538             if (dragging)
 539                 return Math.max(0,(int)((x - (thumbOffset.x + getArrowAreaWidth()))/getScaleFactor())) + min;
 540             else
 541                 return Math.max(0,(int)((x - (getArrowAreaWidth()))/getScaleFactor())) + min;
 542         } else {
 543             if (dragging)
 544                 return Math.max(0,(int)((y - (thumbOffset.y + getArrowAreaWidth()))/getScaleFactor())) + min;
 545             else
 546                 return Math.max(0,(int)((y - (getArrowAreaWidth()))/getScaleFactor())) + min;
 547         }
 548     }
 549 
 550 /*
 551   private void updateNeedsRepaint() {
 552         Rectangle thumbRect = calculateThumbRect();
 553         if (!prevThumb.equals(thumbRect)) {
 554             needsRepaint = true;
 555         }
 556         prevThumb = thumbRect;
 557     }
 558     */
 559 
 560     /**
 561      * Sets the values for this Scrollbar.
 562      * This method enforces the same constraints as in java.awt.Scrollbar:
 563      * <UL>
 564      * <LI> The maximum must be greater than the minimum </LI>
 565      * <LI> The value must be greater than or equal to the minimum
 566      *      and less than or equal to the maximum minus the
 567      *      visible amount </LI>
 568      * <LI> The visible amount must be greater than 1 and less than or equal
 569      *      to the difference between the maximum and minimum values. </LI>
 570      * </UL>
 571      * Values which do not meet these criteria are quietly coerced to the
 572      * appropriate boundary value.
 573      * @param value is the position in the current window.
 574      * @param visible is the amount visible per page
 575      * @param minimum is the minimum value of the scrollbar
 576      * @param maximum is the maximum value of the scrollbar
 577      */
 578     synchronized void setValues(int value, int visible, int minimum, int maximum) {
 579         if (maximum <= minimum) {
 580             maximum = minimum + 1;
 581         }
 582         if (visible > maximum - minimum) {
 583             visible = maximum - minimum;
 584         }
 585         if (visible < 1) {
 586             visible = 1;
 587         }
 588         if (value < minimum) {
 589             value = minimum;
 590         }
 591         if (value > maximum - visible) {
 592             value = maximum - visible;
 593         }
 594 
 595         this.val = value;
 596         this.vis = visible;
 597         this.min = minimum;
 598         this.max = maximum;
 599     }
 600 
 601     /**
 602      * Sets param of this Scrollbar to the specified values.
 603      * @param value is the position in the current window.
 604      * @param visible is the amount visible per page
 605      * @param minimum is the minimum value of the scrollbar
 606      * @param maximum is the maximum value of the scrollbar
 607      * @param unitSize is the unit size for increment or decrement of the value
 608      * @see #setValues
 609      */
 610     synchronized void setValues(int value, int visible, int minimum, int maximum,
 611                                 int unitSize, int blockSize) {
 612         /* Use setValues so that a consistent policy
 613          * relating minimum, maximum, and value is enforced.
 614          */
 615         setValues(value, visible, minimum, maximum);
 616         setUnitIncrement(unitSize);
 617         setBlockIncrement(blockSize);
 618     }
 619 
 620     /**
 621      * Returns the current value of this Scrollbar.
 622      * @see #getMinimum
 623      * @see #getMaximum
 624      */
 625     int getValue() {
 626         return val;
 627     }
 628 
 629     /**
 630      * Sets the value of this Scrollbar to the specified value.
 631      * @param newValue the new value of the Scrollbar. If this value is
 632      * below the current minimum or above the current maximum minus
 633      * the visible amount, it becomes the new one of those values,
 634      * respectively.
 635      * @see #getValue
 636      */
 637     synchronized void setValue(int newValue) {
 638         /* Use setValues so that a consistent policy
 639          * relating minimum, maximum, and value is enforced.
 640          */
 641         setValues(newValue, vis, min, max);
 642     }
 643 
 644     /**
 645      * Returns the minimum value of this Scrollbar.
 646      * @see #getMaximum
 647      * @see #getValue
 648      */
 649     int getMinimum() {
 650         return min;
 651     }
 652 
 653     /**
 654      * Sets the minimum value for this Scrollbar.
 655      * @param newMinimum the minimum value of the scrollbar
 656      */
 657     synchronized void setMinimum(int newMinimum) {
 658         /* Use setValues so that a consistent policy
 659          * relating minimum, maximum, and value is enforced.
 660          */
 661         setValues(val, vis, newMinimum, max);
 662     }
 663 
 664     /**
 665      * Returns the maximum value of this Scrollbar.
 666      * @see #getMinimum
 667      * @see #getValue
 668      */
 669     int getMaximum() {
 670         return max;
 671     }
 672 
 673     /**
 674      * Sets the maximum value for this Scrollbar.
 675      * @param newMaximum the maximum value of the scrollbar
 676      */
 677     synchronized void setMaximum(int newMaximum) {
 678         /* Use setValues so that a consistent policy
 679          * relating minimum, maximum, and value is enforced.
 680          */
 681         setValues(val, vis, min, newMaximum);
 682     }
 683 
 684     /**
 685      * Returns the visible amount of this Scrollbar.
 686      */
 687     int getVisibleAmount() {
 688         return vis;
 689     }
 690 
 691     /**
 692      * Sets the visible amount of this Scrollbar, which is the range
 693      * of values represented by the width of the scroll bar's bubble.
 694      * @param newAmount the amount visible per page
 695      */
 696     synchronized void setVisibleAmount(int newAmount) {
 697         setValues(val, newAmount, min, max);
 698     }
 699 
 700     /**
 701      * Sets the unit increment for this scrollbar. This is the value
 702      * that will be added (subtracted) when the user hits the unit down
 703      * (up) gadgets.
 704      * @param unitSize is the unit size for increment or decrement of the value
 705      */
 706     synchronized void setUnitIncrement(int unitSize) {
 707         line = unitSize;
 708     }
 709 
 710     /**
 711      * Gets the unit increment for this scrollbar.
 712      */
 713     int getUnitIncrement() {
 714         return line;
 715     }
 716 
 717     /**
 718      * Sets the block increment for this scrollbar. This is the value
 719      * that will be added (subtracted) when the user hits the block down
 720      * (up) gadgets.
 721      * @param blockSize is the block size for increment or decrement of the value
 722      */
 723     synchronized void setBlockIncrement(int blockSize) {
 724         page = blockSize;
 725     }
 726 
 727     /**
 728      * Gets the block increment for this scrollbar.
 729      */
 730     int getBlockIncrement() {
 731         return page;
 732     }
 733 
 734     /**
 735      * Width of the arrow image
 736      */
 737     int getArrowWidth() {
 738         return getArrowAreaWidth() - 2*ARROW_IND;
 739     }
 740 
 741     /**
 742      * Width of the area reserved for arrow
 743      */
 744     int getArrowAreaWidth() {
 745         return arrowArea;
 746     }
 747 
 748     void calculateArrowWidth() {
 749         if (barLength < 2*barWidth + MIN_THUMB_H + 2) {
 750             arrowArea = (barLength - MIN_THUMB_H + 2*ARROW_IND)/2 - 1;
 751         }
 752         else {
 753             arrowArea = barWidth - 1;
 754         }
 755     }
 756 
 757     /**
 758      * Returns the scale factor for the thumbArea ( thumbAreaH / (max - min)).
 759      * @see #getArrowAreaWidth
 760      */
 761     private double getScaleFactor(){
 762         double f = (double)(barLength - 2*getArrowAreaWidth()) / Math.max(1,(max - min));
 763         return f;
 764     }
 765 
 766     /**
 767      * Method to calculate the scroll thumb's size and position.  This is
 768      * based on CalcSliderRect in ScrollBar.c of Motif source.
 769      *
 770      * If we ever cache the thumb rect, we'll need to use a clone in
 771      * isInThumb().
 772      */
 773     protected Rectangle calculateThumbRect() {
 774         float range;
 775         float trueSize;  // Area of scroll track
 776         float factor;
 777         float slideSize;
 778         int minSliderWidth;
 779         int minSliderHeight;
 780         int hitTheWall = 0;
 781         int arrAreaH = getArrowAreaWidth();
 782         Rectangle retVal = new Rectangle(0,0,0,0);
 783 
 784         trueSize = barLength - 2*arrAreaH - 1;  // Same if vert or horiz
 785 
 786         if (alignment == ALIGNMENT_HORIZONTAL) {
 787             minSliderWidth = MIN_THUMB_H ;  // Base on user-set vis?
 788             minSliderHeight = height - 3;
 789         }
 790         else {  // Vertical
 791             minSliderWidth = width - 3;
 792             minSliderHeight = MIN_THUMB_H ;
 793 
 794         }
 795 
 796         // Total number of user units displayed
 797             range = max - min;
 798 
 799         // A naive notion of pixels per user unit
 800             factor = trueSize / range;
 801 
 802             // A naive notion of the size of the slider in pixels
 803             // in thermo, slider_size is 0 ans is ignored
 804             slideSize = vis * factor;
 805 
 806         if (alignment == ALIGNMENT_HORIZONTAL) {
 807             // Simulating MAX_SCROLLBAR_DIMENSION macro
 808             int localVal = (int) (slideSize + 0.5);
 809             int localMin = minSliderWidth;
 810             if (localVal > localMin) {
 811                 retVal.width = localVal;
 812             }
 813             else {
 814                 retVal.width = localMin;
 815                 hitTheWall = localMin;
 816             }
 817             retVal.height = minSliderHeight;
 818         }
 819         else {  // Vertical
 820             retVal.width = minSliderWidth;
 821 
 822             // Simulating MAX_SCROLLBAR_DIMENSION macro
 823             int localVal = (int) (slideSize + 0.5);
 824             int localMin = minSliderHeight;
 825             if (localVal > localMin) {
 826                 retVal.height = localVal;
 827             }
 828             else {
 829                 retVal.height = localMin;
 830                 hitTheWall = localMin;
 831             }
 832         }
 833 
 834         if (hitTheWall != 0) {
 835             trueSize -= hitTheWall;  // Actual pixels available
 836             range -= vis;            // Actual range
 837             factor = trueSize / range;
 838         }
 839 
 840         if (alignment == ALIGNMENT_HORIZONTAL) {
 841                     retVal.x = ((int) (((((float) val)
 842                         - ((float) min)) * factor) + 0.5))
 843                         + arrAreaH;
 844                     retVal.y = 1;
 845 
 846         }
 847         else {
 848             retVal.x = 1;
 849                     retVal.y = ((int) (((((float) val)
 850                         - ((float) min)) * factor) + 0.5))
 851                         + arrAreaH;
 852         }
 853 
 854         // There was one final adjustment here in the Motif function, which was
 855         // noted to be for backward-compatibility.  It has been left out for now.
 856 
 857         return retVal;
 858     }
 859 
 860     public String toString() {
 861         return getClass() + "[" + width + "x" + height + "," + barWidth + "x" + barLength + "]";
 862     }
 863 }
 864 
 865 
 866 class XScrollRepeater implements Runnable {
 867     /**
 868      * Time to pause before the first scroll repeat.
 869      */
 870     static int beginPause = 500;
 871     // Reminder - make this a user definable property
 872 
 873     /**
 874      * Time to pause between each scroll repeat.
 875      */
 876     static int repeatPause = 100;
 877     // Reminder - make this a user definable property
 878 
 879     /**
 880      * The scrollbar that we sending scrolling.
 881      */
 882     XScrollbar sb;
 883 
 884     /**
 885      * newScroll gets reset when a new scrollbar gets set.
 886      */
 887     boolean newScroll;
 888 
 889 
 890     boolean shouldSkip;
 891 
 892     /**
 893      * Creates a new scroll repeater.
 894      * @param sb the scrollbar that this thread will scroll
 895      */
 896     XScrollRepeater(XScrollbar sb) {
 897         this.setScrollbar(sb);
 898         newScroll = true;
 899     }
 900 
 901     public void start() {
 902         stop();
 903         shouldSkip = false;
 904         XToolkit.schedule(this, beginPause);
 905     }
 906 
 907     public void stop() {
 908         synchronized(this) {
 909             shouldSkip = true;
 910         }
 911         XToolkit.remove(this);
 912     }
 913 
 914     /**
 915      * Sets the scrollbar.
 916      * @param sb the scrollbar that this thread will scroll
 917      */
 918     public synchronized void setScrollbar(XScrollbar sb) {
 919         this.sb = sb;
 920         stop();
 921         newScroll = true;
 922     }
 923 
 924     public void run () {
 925         synchronized(this) {
 926             if (shouldSkip) {
 927                 return;
 928             }
 929         }
 930         sb.scroll();
 931         XToolkit.schedule(this, repeatPause);
 932     }
 933 
 934 }