/* * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.awt.X11; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import sun.awt.SunToolkit; import sun.awt.X11GraphicsConfig; import sun.util.logging.PlatformLogger; /** * A simple vertical scroll bar. */ abstract class XScrollbar { private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XScrollbar"); /** * The thread that asynchronously tells the scrollbar to scroll. * @see #startScrolling */ private static XScrollRepeater scroller = new XScrollRepeater(null); /* * The repeater that used for concurrent scrolling of the vertical and horizontal scrollbar * And so there is not static keyword * See 6243382 for more information */ private XScrollRepeater i_scroller = new XScrollRepeater(null); // Thumb length is always >= MIN_THUMB_H private final static int MIN_THUMB_H = 5; private static final int ARROW_IND = 1; XScrollbarClient sb; //Use set methods to set scrollbar parameters private int val; private int min; private int max; private int vis; private int line; private int page; private boolean needsRepaint = true; private boolean pressed = false; private boolean dragging = false; Polygon firstArrow, secondArrow; int width, height; // Dimensions of the visible part of the parent window int barWidth, barLength; // Rotation-independent values, // equal to (width, height) for vertical, // rotated by 90 for horizontal. // That is, barLength is always the length between // the tips of the arrows. int arrowArea; // The area reserved for the scroll arrows int alignment; public static final int ALIGNMENT_VERTICAL = 1, ALIGNMENT_HORIZONTAL = 2; int mode; Point thumbOffset; private Rectangle prevThumb; public XScrollbar(int alignment, XScrollbarClient sb) { this.sb = sb; this.alignment = alignment; } public boolean needsRepaint() { return needsRepaint; } void notifyValue(int v) { notifyValue(v, false); } void notifyValue(int v, final boolean isAdjusting) { if (v < min) { v = min; } else if (v > max - vis) { v = max - vis; } final int value = v; final int mode = this.mode; if ((sb != null) && ((value != val)||(!pressed))) { SunToolkit.executeOnEventHandlerThread(sb.getEventSource(), new Runnable() { public void run() { sb.notifyValue(XScrollbar.this, mode, value, isAdjusting); } }); } } abstract protected void rebuildArrows(); public void setSize(int width, int height) { if (log.isLoggable(PlatformLogger.Level.FINER)) { log.finer("Setting scroll bar " + this + " size to " + width + "x" + height); } this.width = width; this.height = height; } /** * Creates oriented directed arrow */ protected Polygon createArrowShape(boolean vertical, boolean up) { Polygon arrow = new Polygon(); // TODO: this should be done polymorphically in subclasses // FIXME: arrows overlap the thumb for very wide scrollbars if (vertical) { int x = width / 2 - getArrowWidth()/2; int y1 = (up ? ARROW_IND : barLength - ARROW_IND); int y2 = (up ? getArrowWidth() : barLength - getArrowWidth() - ARROW_IND); arrow.addPoint(x + getArrowWidth()/2, y1); arrow.addPoint(x + getArrowWidth(), y2); arrow.addPoint(x, y2); arrow.addPoint(x + getArrowWidth()/2, y1); } else { int y = height / 2 - getArrowWidth()/2; int x1 = (up ? ARROW_IND : barLength - ARROW_IND); int x2 = (up ? getArrowWidth() : barLength - getArrowWidth() - ARROW_IND); arrow.addPoint(x1, y + getArrowWidth()/2); arrow.addPoint(x2, y + getArrowWidth()); arrow.addPoint(x2, y); arrow.addPoint(x1, y + getArrowWidth()/2); } return arrow; } /** * Gets the area of the scroll track */ protected abstract Rectangle getThumbArea(); /** * paint the scrollbar * @param g the graphics context to paint into * @param colors the colors to use when painting the scrollbar * @param width the width of the scrollbar * @param height the height of the scrollbar * @param paintAll paint the whole scrollbar if true, just the thumb is false */ void paint(Graphics g, Color colors[], boolean paintAll) { if (log.isLoggable(PlatformLogger.Level.FINER)) { log.finer("Painting scrollbar " + this); } boolean useBufferedImage = false; Graphics2D g2 = null; BufferedImage buffer = null; if (!(g instanceof Graphics2D)) { // Fix for 5045936, 5055171. While printing, g is an instance // of sun.print.ProxyPrintGraphics which extends Graphics. // So we use a separate buffered image and its graphics is // always Graphics2D instance X11GraphicsConfig graphicsConfig = (X11GraphicsConfig)(sb.getEventSource().getGraphicsConfiguration()); buffer = graphicsConfig.createCompatibleImage(width, height); g2 = buffer.createGraphics(); useBufferedImage = true; } else { g2 = (Graphics2D)g; } try { Rectangle thumbRect = calculateThumbRect(); // if (prevH == thumbH && prevY == thumbPosY) { // return; // } prevThumb = thumbRect; // TODO: Share Motif colors Color back = colors[XComponentPeer.BACKGROUND_COLOR]; Color selectColor = new Color(MotifColorUtilities.calculateSelectFromBackground(back.getRed(),back.getGreen(),back.getBlue())); Color darkShadow = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue())); Color lightShadow = new Color(MotifColorUtilities.calculateTopShadowFromBackground(back.getRed(),back.getGreen(),back.getBlue())); XToolkit.awtLock(); try { XlibWrapper.XFlush(XToolkit.getDisplay()); } finally { XToolkit.awtUnlock(); } /* paint the background slightly darker */ if (paintAll) { // fill the entire background g2.setColor(selectColor); if (alignment == ALIGNMENT_HORIZONTAL) { g2.fillRect(0, 0, thumbRect.x, height); g2.fillRect(thumbRect.x + thumbRect.width , 0, width - (thumbRect.x + thumbRect.width), height); } else { g2.fillRect(0, 0, width, thumbRect.y); g2.fillRect(0, thumbRect.y + thumbRect.height, width, height - (thumbRect.y + thumbRect.height)); } // Paint edges // TODO: Share Motif 3d rect drawing g2.setColor(darkShadow); g2.drawLine(0, 0, width-1, 0); // top g2.drawLine(0, 0, 0, height-1); // left g2.setColor(lightShadow); g2.drawLine(1, height-1, width-1, height-1); // bottom g2.drawLine(width-1, 1, width-1, height-1); // right } else { // Clear all thumb area g2.setColor(selectColor); Rectangle thumbArea = getThumbArea(); g2.fill(thumbArea); } if (paintAll) { // ************ paint the arrows paintArrows(g2, colors[XComponentPeer.BACKGROUND_COLOR], darkShadow, lightShadow ); } // Thumb g2.setColor(colors[XComponentPeer.BACKGROUND_COLOR]); g2.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height); g2.setColor(lightShadow); g2.drawLine(thumbRect.x, thumbRect.y, thumbRect.x + thumbRect.width, thumbRect.y); // top g2.drawLine(thumbRect.x, thumbRect.y, thumbRect.x, thumbRect.y+thumbRect.height); // left g2.setColor(darkShadow); g2.drawLine(thumbRect.x+1, thumbRect.y+thumbRect.height, thumbRect.x+thumbRect.width, thumbRect.y+thumbRect.height); // bottom g2.drawLine(thumbRect.x+thumbRect.width, thumbRect.y+1, thumbRect.x+thumbRect.width, thumbRect.y+thumbRect.height); // right } finally { if (useBufferedImage) { g2.dispose(); } } if (useBufferedImage) { g.drawImage(buffer, 0, 0, null); } XToolkit.awtLock(); try { XlibWrapper.XFlush(XToolkit.getDisplay()); } finally { XToolkit.awtUnlock(); } } void paintArrows(Graphics2D g, Color background, Color darkShadow, Color lightShadow) { g.setColor(background); // paint firstArrow if (pressed && (mode == AdjustmentEvent.UNIT_DECREMENT)) { g.fill(firstArrow); g.setColor(lightShadow); g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0], firstArrow.xpoints[1],firstArrow.ypoints[1]); g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1], firstArrow.xpoints[2],firstArrow.ypoints[2]); g.setColor(darkShadow); g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2], firstArrow.xpoints[0],firstArrow.ypoints[0]); } else { g.fill(firstArrow); g.setColor(darkShadow); g.drawLine(firstArrow.xpoints[0],firstArrow.ypoints[0], firstArrow.xpoints[1],firstArrow.ypoints[1]); g.drawLine(firstArrow.xpoints[1],firstArrow.ypoints[1], firstArrow.xpoints[2],firstArrow.ypoints[2]); g.setColor(lightShadow); g.drawLine(firstArrow.xpoints[2],firstArrow.ypoints[2], firstArrow.xpoints[0],firstArrow.ypoints[0]); } g.setColor(background); // paint second Arrow if (pressed && (mode == AdjustmentEvent.UNIT_INCREMENT)) { g.fill(secondArrow); g.setColor(lightShadow); g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0], secondArrow.xpoints[1],secondArrow.ypoints[1]); g.setColor(darkShadow); g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1], secondArrow.xpoints[2],secondArrow.ypoints[2]); g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2], secondArrow.xpoints[0],secondArrow.ypoints[0]); } else { g.fill(secondArrow); g.setColor(darkShadow); g.drawLine(secondArrow.xpoints[0],secondArrow.ypoints[0], secondArrow.xpoints[1],secondArrow.ypoints[1]); g.setColor(lightShadow); g.drawLine(secondArrow.xpoints[1],secondArrow.ypoints[1], secondArrow.xpoints[2],secondArrow.ypoints[2]); g.drawLine(secondArrow.xpoints[2],secondArrow.ypoints[2], secondArrow.xpoints[0],secondArrow.ypoints[0]); } } /** * Tell the scroller to start scrolling. */ void startScrolling() { if (log.isLoggable(PlatformLogger.Level.FINER)) { log.finer("Start scrolling on " + this); } // Make sure that we scroll at least once scroll(); // wake up the scroll repeater if (scroller == null) { // If there isn't a scroller, then create // one and start it. scroller = new XScrollRepeater(this); } else { scroller.setScrollbar(this); } scroller.start(); } /** * Tell the instance scroller to start scrolling. * See 6243382 for more information */ void startScrollingInstance() { if (log.isLoggable(PlatformLogger.Level.FINER)) { log.finer("Start scrolling on " + this); } // Make sure that we scroll at least once scroll(); i_scroller.setScrollbar(this); i_scroller.start(); } /** * Tell the instance scroller to stop scrolling. * See 6243382 for more information */ void stopScrollingInstance() { if (log.isLoggable(PlatformLogger.Level.FINER)) { log.finer("Stop scrolling on " + this); } i_scroller.stop(); } /** * The set method for mode property. * See 6243382 for more information */ public void setMode(int mode){ this.mode = mode; } /** * Scroll one unit. * @see notifyValue */ void scroll() { switch (mode) { case AdjustmentEvent.UNIT_DECREMENT: notifyValue(val - line); return; case AdjustmentEvent.UNIT_INCREMENT: notifyValue(val + line); return; case AdjustmentEvent.BLOCK_DECREMENT: notifyValue(val - page); return; case AdjustmentEvent.BLOCK_INCREMENT: notifyValue(val + page); return; } return; } boolean isInArrow(int x, int y) { // Mouse is considered to be in the arrow if it is anywhere in the // arrow area. int coord = (alignment == ALIGNMENT_HORIZONTAL ? x : y); int arrAreaH = getArrowAreaWidth(); if (coord < arrAreaH || coord > barLength - arrAreaH + 1) { return true; } return false; } /** * Is x,y in the scroll thumb? * * If we ever cache the thumb rect, we may need to clone the result of * calculateThumbRect(). */ boolean isInThumb(int x, int y) { Rectangle thumbRect = calculateThumbRect(); // If the mouse is in the shadow of the thumb or the shadow of the // scroll track, treat it as hitting the thumb. So, slightly enlarge // our rectangle. thumbRect.x -= 1; thumbRect.width += 3; thumbRect.height += 1; return thumbRect.contains(x,y); } abstract boolean beforeThumb(int x, int y); /** * * @see java.awt.event.MouseEvent * MouseEvent.MOUSE_CLICKED * MouseEvent.MOUSE_PRESSED * MouseEvent.MOUSE_RELEASED * MouseEvent.MOUSE_MOVED * MouseEvent.MOUSE_ENTERED * MouseEvent.MOUSE_EXITED * MouseEvent.MOUSE_DRAGGED */ public void handleMouseEvent(int id, int modifiers, int x, int y) { if ((modifiers & InputEvent.BUTTON1_MASK) == 0) { return; } if (log.isLoggable(PlatformLogger.Level.FINER)) { String type; switch (id) { case MouseEvent.MOUSE_PRESSED: type = "press"; break; case MouseEvent.MOUSE_RELEASED: type = "release"; break; case MouseEvent.MOUSE_DRAGGED: type = "drag"; break; default: type = "other"; } log.finer("Mouse " + type + " event in scroll bar " + this + "x = " + x + ", y = " + y + ", on arrow: " + isInArrow(x, y) + ", on thumb: " + isInThumb(x, y) + ", before thumb: " + beforeThumb(x, y) + ", thumb rect" + calculateThumbRect()); } switch (id) { case MouseEvent.MOUSE_PRESSED: if (isInArrow(x, y)) { pressed = true; if (beforeThumb(x, y)) { mode = AdjustmentEvent.UNIT_DECREMENT; } else { mode = AdjustmentEvent.UNIT_INCREMENT; } sb.repaintScrollbarRequest(this); startScrolling(); break; } if (isInThumb(x, y)) { mode = AdjustmentEvent.TRACK; } else { if (beforeThumb(x, y)) { mode = AdjustmentEvent.BLOCK_DECREMENT; } else { mode = AdjustmentEvent.BLOCK_INCREMENT; } startScrolling(); } Rectangle pos = calculateThumbRect(); thumbOffset = new Point(x - pos.x, y - pos.y); break; case MouseEvent.MOUSE_RELEASED: pressed = false; sb.repaintScrollbarRequest(this); scroller.stop(); if(dragging){ handleTrackEvent(x, y, false); dragging=false; } break; case MouseEvent.MOUSE_DRAGGED: dragging = true; handleTrackEvent(x, y, true); } } private void handleTrackEvent(int x, int y, boolean isAdjusting){ if (mode == AdjustmentEvent.TRACK) { notifyValue(calculateCursorOffset(x, y), isAdjusting); } } private int calculateCursorOffset(int x, int y){ if (alignment == ALIGNMENT_HORIZONTAL) { if (dragging) return Math.max(0,(int)((x - (thumbOffset.x + getArrowAreaWidth()))/getScaleFactor())) + min; else return Math.max(0,(int)((x - (getArrowAreaWidth()))/getScaleFactor())) + min; } else { if (dragging) return Math.max(0,(int)((y - (thumbOffset.y + getArrowAreaWidth()))/getScaleFactor())) + min; else return Math.max(0,(int)((y - (getArrowAreaWidth()))/getScaleFactor())) + min; } } /* private void updateNeedsRepaint() { Rectangle thumbRect = calculateThumbRect(); if (!prevThumb.equals(thumbRect)) { needsRepaint = true; } prevThumb = thumbRect; } */ /** * Sets the values for this Scrollbar. * This method enforces the same constraints as in java.awt.Scrollbar: * <UL> * <LI> The maximum must be greater than the minimum </LI> * <LI> The value must be greater than or equal to the minumum * and less than or equal to the maximum minus the * visible amount </LI> * <LI> The visible amount must be greater than 1 and less than or equal * to the difference between the maximum and minimum values. </LI> * </UL> * Values which do not meet these criteria are quietly coerced to the * appropriate boundary value. * @param value is the position in the current window. * @param visible is the amount visible per page * @param minimum is the minimum value of the scrollbar * @param maximum is the maximum value of the scrollbar */ synchronized void setValues(int value, int visible, int minimum, int maximum) { if (maximum <= minimum) { maximum = minimum + 1; } if (visible > maximum - minimum) { visible = maximum - minimum; } if (visible < 1) { visible = 1; } if (value < minimum) { value = minimum; } if (value > maximum - visible) { value = maximum - visible; } this.val = value; this.vis = visible; this.min = minimum; this.max = maximum; } /** * Sets param of this Scrollbar to the specified values. * @param value is the position in the current window. * @param visible is the amount visible per page * @param minimum is the minimum value of the scrollbar * @param maximum is the maximum value of the scrollbar * @param unitSize is the unit size for increment or decrement of the value * @param page is the block size for increment or decrement of the value * @see #setValues */ synchronized void setValues(int value, int visible, int minimum, int maximum, int unitSize, int blockSize) { /* Use setValues so that a consistent policy * relating minimum, maximum, and value is enforced. */ setValues(value, visible, minimum, maximum); setUnitIncrement(unitSize); setBlockIncrement(blockSize); } /** * Returns the current value of this Scrollbar. * @see #getMinimum * @see #getMaximum */ int getValue() { return val; } /** * Sets the value of this Scrollbar to the specified value. * @param value the new value of the Scrollbar. If this value is * below the current minimum or above the current maximum minus * the visible amount, it becomes the new one of those values, * respectively. * @see #getValue */ synchronized void setValue(int newValue) { /* Use setValues so that a consistent policy * relating minimum, maximum, and value is enforced. */ setValues(newValue, vis, min, max); } /** * Returns the minimum value of this Scrollbar. * @see #getMaximum * @see #getValue */ int getMinimum() { return min; } /** * Sets the minimum value for this Scrollbar. * @param minimum the minimum value of the scrollbar */ synchronized void setMinimum(int newMinimum) { /* Use setValues so that a consistent policy * relating minimum, maximum, and value is enforced. */ setValues(val, vis, newMinimum, max); } /** * Returns the maximum value of this Scrollbar. * @see #getMinimum * @see #getValue */ int getMaximum() { return max; } /** * Sets the maximum value for this Scrollbar. * @param maximum the maximum value of the scrollbar */ synchronized void setMaximum(int newMaximum) { /* Use setValues so that a consistent policy * relating minimum, maximum, and value is enforced. */ setValues(val, vis, min, newMaximum); } /** * Returns the visible amount of this Scrollbar. */ int getVisibleAmount() { return vis; } /** * Sets the visible amount of this Scrollbar, which is the range * of values represented by the width of the scroll bar's bubble. * @param visible the amount visible per page */ synchronized void setVisibleAmount(int newAmount) { setValues(val, newAmount, min, max); } /** * Sets the unit increment for this scrollbar. This is the value * that will be added (subtracted) when the user hits the unit down * (up) gadgets. * @param unitSize is the unit size for increment or decrement of the value */ synchronized void setUnitIncrement(int unitSize) { line = unitSize; } /** * Gets the unit increment for this scrollbar. */ int getUnitIncrement() { return line; } /** * Sets the block increment for this scrollbar. This is the value * that will be added (subtracted) when the user hits the block down * (up) gadgets. * @param blockSize is the block size for increment or decrement of the value */ synchronized void setBlockIncrement(int blockSize) { page = blockSize; } /** * Gets the block increment for this scrollbar. */ int getBlockIncrement() { return page; } /** * Width of the arrow image */ int getArrowWidth() { return getArrowAreaWidth() - 2*ARROW_IND; } /** * Width of the area reserved for arrow */ int getArrowAreaWidth() { return arrowArea; } void calculateArrowWidth() { if (barLength < 2*barWidth + MIN_THUMB_H + 2) { arrowArea = (barLength - MIN_THUMB_H + 2*ARROW_IND)/2 - 1; } else { arrowArea = barWidth - 1; } } /** * Returns the scale factor for the thumbArea ( thumbAreaH / (max - min)). * @see #getArrowAreaSize */ private double getScaleFactor(){ double f = (double)(barLength - 2*getArrowAreaWidth()) / Math.max(1,(max - min)); return f; } /** * Method to calculate the scroll thumb's size and position. This is * based on CalcSliderRect in ScrollBar.c of Motif source. * * If we ever cache the thumb rect, we'll need to use a clone in * isInThumb(). */ protected Rectangle calculateThumbRect() { float range; float trueSize; // Area of scroll track float factor; float slideSize; int minSliderWidth; int minSliderHeight; int hitTheWall = 0; int arrAreaH = getArrowAreaWidth(); Rectangle retVal = new Rectangle(0,0,0,0); trueSize = barLength - 2*arrAreaH - 1; // Same if vert or horiz if (alignment == ALIGNMENT_HORIZONTAL) { minSliderWidth = MIN_THUMB_H ; // Base on user-set vis? minSliderHeight = height - 3; } else { // Vertical minSliderWidth = width - 3; minSliderHeight = MIN_THUMB_H ; } // Total number of user units displayed range = max - min; // A naive notion of pixels per user unit factor = trueSize / range; // A naive notion of the size of the slider in pixels // in thermo, slider_size is 0 ans is ignored slideSize = vis * factor; if (alignment == ALIGNMENT_HORIZONTAL) { // Simulating MAX_SCROLLBAR_DIMENSION macro int localVal = (int) (slideSize + 0.5); int localMin = minSliderWidth; if (localVal > localMin) { retVal.width = localVal; } else { retVal.width = localMin; hitTheWall = localMin; } retVal.height = minSliderHeight; } else { // Vertical retVal.width = minSliderWidth; // Simulating MAX_SCROLLBAR_DIMENSION macro int localVal = (int) (slideSize + 0.5); int localMin = minSliderHeight; if (localVal > localMin) { retVal.height = localVal; } else { retVal.height = localMin; hitTheWall = localMin; } } if (hitTheWall != 0) { trueSize -= hitTheWall; // Actual pixels available range -= vis; // Actual range factor = trueSize / range; } if (alignment == ALIGNMENT_HORIZONTAL) { retVal.x = ((int) (((((float) val) - ((float) min)) * factor) + 0.5)) + arrAreaH; retVal.y = 1; } else { retVal.x = 1; retVal.y = ((int) (((((float) val) - ((float) min)) * factor) + 0.5)) + arrAreaH; } // There was one final adjustment here in the Motif function, which was // noted to be for backward-compatiblity. It has been left out for now. return retVal; } public String toString() { return getClass() + "[" + width + "x" + height + "," + barWidth + "x" + barLength + "]"; } } class XScrollRepeater implements Runnable { /** * Time to pause before the first scroll repeat. */ static int beginPause = 500; // Reminder - make this a user definable property /** * Time to pause between each scroll repeat. */ static int repeatPause = 100; // Reminder - make this a user definable property /** * The scrollbar that we sending scrolling. */ XScrollbar sb; /** * newScroll gets reset when a new scrollbar gets set. */ boolean newScroll; boolean shouldSkip; /** * Creates a new scroll repeater. * @param sb the scrollbar that this thread will scroll */ XScrollRepeater(XScrollbar sb) { this.setScrollbar(sb); newScroll = true; } public void start() { stop(); shouldSkip = false; XToolkit.schedule(this, beginPause); } public void stop() { synchronized(this) { shouldSkip = true; } XToolkit.remove(this); } /** * Sets the scrollbar. * @param sb the scrollbar that this thread will scroll */ public synchronized void setScrollbar(XScrollbar sb) { this.sb = sb; stop(); newScroll = true; } public void run () { synchronized(this) { if (shouldSkip) { return; } } sb.scroll(); XToolkit.schedule(this, repeatPause); } }