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
26package sun.awt.X11;
27
28import java.awt.*;
29import java.awt.event.*;
30import java.awt.image.BufferedImage;
31import sun.awt.SunToolkit;
32import sun.awt.X11GraphicsConfig;
33import sun.util.logging.PlatformLogger;
34
35/**
36* A simple vertical scroll bar.
37*/
38abstract 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
867class 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}
936