jdk/src/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java
changeset 2 90ce3da70b43
child 2658 43e06bc950ec
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1275 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.plaf.basic;
+
+import sun.swing.SwingUtilities2;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeEvent;
+import java.io.Serializable;
+import sun.swing.DefaultLookup;
+
+/**
+ * A Basic L&F implementation of ProgressBarUI.
+ *
+ * @author Michael C. Albers
+ * @author Kathy Walrath
+ */
+public class BasicProgressBarUI extends ProgressBarUI {
+    private int cachedPercent;
+    private int cellLength, cellSpacing;
+    // The "selectionForeground" is the color of the text when it is painted
+    // over a filled area of the progress bar. The "selectionBackground"
+    // is for the text over the unfilled progress bar area.
+    private Color selectionForeground, selectionBackground;
+
+    private Animator animator;
+
+    protected JProgressBar progressBar;
+    protected ChangeListener changeListener;
+    private Handler handler;
+
+    /**
+     * The current state of the indeterminate animation's cycle.
+     * 0, the initial value, means paint the first frame.
+     * When the progress bar is indeterminate and showing,
+     * the default animation thread updates this variable
+     * by invoking incrementAnimationIndex()
+     * every repaintInterval milliseconds.
+     */
+    private int animationIndex = 0;
+
+    /**
+     * The number of frames per cycle. Under the default implementation,
+     * this depends on the cycleTime and repaintInterval.  It
+     * must be an even number for the default painting algorithm.  This
+     * value is set in the initIndeterminateValues method.
+     */
+    private int numFrames;   //0 1|numFrames-1 ... numFrames/2
+
+    /**
+     * Interval (in ms) between repaints of the indeterminate progress bar.
+     * The value of this method is set
+     * (every time the progress bar changes to indeterminate mode)
+     * using the
+     * "ProgressBar.repaintInterval" key in the defaults table.
+     */
+    private int repaintInterval;
+
+    /**
+     * The number of milliseconds until the animation cycle repeats.
+     * The value of this method is set
+     * (every time the progress bar changes to indeterminate mode)
+     * using the
+     * "ProgressBar.cycleTime" key in the defaults table.
+     */
+    private int cycleTime;  //must be repaintInterval*2*aPositiveInteger
+
+    //performance stuff
+    private static boolean ADJUSTTIMER = true; //makes a BIG difference;
+                                               //make this false for
+                                               //performance tests
+
+    /**
+     * Used to hold the location and size of the bouncing box (returned
+     * by getBox) to be painted.
+     *
+     * @since 1.5
+     */
+    protected Rectangle boxRect;
+
+    /**
+     * The rectangle to be updated the next time the
+     * animation thread calls repaint.  For bouncing-box
+     * animation this rect should include the union of
+     * the currently displayed box (which needs to be erased)
+     * and the box to be displayed next.
+     * This rectangle's values are set in
+     * the setAnimationIndex method.
+     */
+    private Rectangle nextPaintRect;
+
+    //cache
+    /** The component's painting area, not including the border. */
+    private Rectangle componentInnards;    //the current painting area
+    private Rectangle oldComponentInnards; //used to see if the size changed
+
+    /** For bouncing-box animation, the change in position per frame. */
+    private double delta = 0.0;
+
+    private int maxPosition = 0; //maximum X (horiz) or Y box location
+
+
+    public static ComponentUI createUI(JComponent x) {
+        return new BasicProgressBarUI();
+    }
+
+    public void installUI(JComponent c) {
+        progressBar = (JProgressBar)c;
+        installDefaults();
+        installListeners();
+        if (progressBar.isIndeterminate()) {
+            initIndeterminateValues();
+        }
+    }
+
+    public void uninstallUI(JComponent c) {
+        if (progressBar.isIndeterminate()) {
+            cleanUpIndeterminateValues();
+        }
+        uninstallDefaults();
+        uninstallListeners();
+        progressBar = null;
+    }
+
+    protected void installDefaults() {
+        LookAndFeel.installProperty(progressBar, "opaque", Boolean.TRUE);
+        LookAndFeel.installBorder(progressBar,"ProgressBar.border");
+        LookAndFeel.installColorsAndFont(progressBar,
+                                         "ProgressBar.background",
+                                         "ProgressBar.foreground",
+                                         "ProgressBar.font");
+        cellLength = UIManager.getInt("ProgressBar.cellLength");
+        cellSpacing = UIManager.getInt("ProgressBar.cellSpacing");
+        selectionForeground = UIManager.getColor("ProgressBar.selectionForeground");
+        selectionBackground = UIManager.getColor("ProgressBar.selectionBackground");
+    }
+
+    protected void uninstallDefaults() {
+        LookAndFeel.uninstallBorder(progressBar);
+    }
+
+    protected void installListeners() {
+        //Listen for changes in the progress bar's data.
+        changeListener = getHandler();
+        progressBar.addChangeListener(changeListener);
+
+        //Listen for changes between determinate and indeterminate state.
+        progressBar.addPropertyChangeListener(getHandler());
+    }
+
+    private Handler getHandler() {
+        if (handler == null) {
+            handler = new Handler();
+        }
+        return handler;
+    }
+
+    /**
+     * Starts the animation thread, creating and initializing
+     * it if necessary. This method is invoked when an
+     * indeterminate progress bar should start animating.
+     * Reasons for this may include:
+     * <ul>
+     *    <li>The progress bar is determinate and becomes displayable
+     *    <li>The progress bar is displayable and becomes determinate
+     *    <li>The progress bar is displayable and determinate and this
+     *        UI is installed
+     * </ul>
+     * If you implement your own animation thread,
+     * you must override this method.
+     *
+     * @since 1.4
+     * @see #stopAnimationTimer
+     */
+    protected void startAnimationTimer() {
+        if (animator == null) {
+            animator = new Animator();
+        }
+
+        animator.start(getRepaintInterval());
+    }
+
+    /**
+     * Stops the animation thread.
+     * This method is invoked when the indeterminate
+     * animation should be stopped. Reasons for this may include:
+     * <ul>
+     *    <li>The progress bar changes to determinate
+     *    <li>The progress bar is no longer part of a displayable hierarchy
+     *    <li>This UI in uninstalled
+     * </ul>
+     * If you implement your own animation thread,
+     * you must override this method.
+     *
+     * @since 1.4
+     * @see #startAnimationTimer
+     */
+    protected void stopAnimationTimer() {
+        if (animator != null) {
+            animator.stop();
+        }
+    }
+
+    /**
+     * Removes all listeners installed by this object.
+     */
+    protected void uninstallListeners() {
+        progressBar.removeChangeListener(changeListener);
+        progressBar.removePropertyChangeListener(getHandler());
+        handler = null;
+    }
+
+
+    /**
+     * Returns the baseline.
+     *
+     * @throws NullPointerException {@inheritDoc}
+     * @throws IllegalArgumentException {@inheritDoc}
+     * @see javax.swing.JComponent#getBaseline(int, int)
+     * @since 1.6
+     */
+    public int getBaseline(JComponent c, int width, int height) {
+        super.getBaseline(c, width, height);
+        if (progressBar.isStringPainted() &&
+                progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            FontMetrics metrics = progressBar.
+                    getFontMetrics(progressBar.getFont());
+            Insets insets = progressBar.getInsets();
+            int y = insets.top;
+            height = height - insets.top - insets.bottom;
+            return y + (height + metrics.getAscent() -
+                        metrics.getLeading() -
+                        metrics.getDescent()) / 2;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns an enum indicating how the baseline of the component
+     * changes as the size changes.
+     *
+     * @throws NullPointerException {@inheritDoc}
+     * @see javax.swing.JComponent#getBaseline(int, int)
+     * @since 1.6
+     */
+    public Component.BaselineResizeBehavior getBaselineResizeBehavior(
+            JComponent c) {
+        super.getBaselineResizeBehavior(c);
+        if (progressBar.isStringPainted() &&
+                progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            return Component.BaselineResizeBehavior.CENTER_OFFSET;
+        }
+        return Component.BaselineResizeBehavior.OTHER;
+    }
+
+    // Many of the Basic*UI components have the following methods.
+    // This component does not have these methods because *ProgressBarUI
+    //  is not a compound component and does not accept input.
+    //
+    // protected void installComponents()
+    // protected void uninstallComponents()
+    // protected void installKeyboardActions()
+    // protected void uninstallKeyboardActions()
+
+    protected Dimension getPreferredInnerHorizontal() {
+        Dimension horizDim = (Dimension)DefaultLookup.get(progressBar, this,
+            "ProgressBar.horizontalSize");
+        if (horizDim == null) {
+            horizDim = new Dimension(146, 12);
+        }
+        return horizDim;
+    }
+
+    protected Dimension getPreferredInnerVertical() {
+        Dimension vertDim = (Dimension)DefaultLookup.get(progressBar, this,
+            "ProgressBar.verticalSize");
+        if (vertDim == null) {
+            vertDim = new Dimension(12, 146);
+        }
+        return vertDim;
+    }
+
+    /**
+     * The "selectionForeground" is the color of the text when it is painted
+     * over a filled area of the progress bar.
+     */
+    protected Color getSelectionForeground() {
+        return selectionForeground;
+    }
+
+    /**
+     * The "selectionBackground" is the color of the text when it is painted
+     * over an unfilled area of the progress bar.
+     */
+    protected Color getSelectionBackground() {
+        return selectionBackground;
+    }
+
+    private int getCachedPercent() {
+        return cachedPercent;
+    }
+
+    private void setCachedPercent(int cachedPercent) {
+        this.cachedPercent = cachedPercent;
+    }
+
+    /**
+     * Returns the width (if HORIZONTAL) or height (if VERTICAL)
+     * of each of the indivdual cells/units to be rendered in the
+     * progress bar. However, for text rendering simplification and
+     * aesthetic considerations, this function will return 1 when
+     * the progress string is being rendered.
+     *
+     * @return the value representing the spacing between cells
+     * @see    #setCellLength
+     * @see    JProgressBar#isStringPainted
+     */
+    protected int getCellLength() {
+        if (progressBar.isStringPainted()) {
+            return 1;
+        } else {
+            return cellLength;
+        }
+    }
+
+    protected void setCellLength(int cellLen) {
+        this.cellLength = cellLen;
+    }
+
+    /**
+     * Returns the spacing between each of the cells/units in the
+     * progress bar. However, for text rendering simplification and
+     * aesthetic considerations, this function will return 0 when
+     * the progress string is being rendered.
+     *
+     * @return the value representing the spacing between cells
+     * @see    #setCellSpacing
+     * @see    JProgressBar#isStringPainted
+     */
+    protected int getCellSpacing() {
+        if (progressBar.isStringPainted()) {
+            return 0;
+        } else {
+            return cellSpacing;
+        }
+    }
+
+    protected void setCellSpacing(int cellSpace) {
+        this.cellSpacing = cellSpace;
+    }
+
+    /**
+     * This determines the amount of the progress bar that should be filled
+     * based on the percent done gathered from the model. This is a common
+     * operation so it was abstracted out. It assumes that your progress bar
+     * is linear. That is, if you are making a circular progress indicator,
+     * you will want to override this method.
+     */
+    protected int getAmountFull(Insets b, int width, int height) {
+        int amountFull = 0;
+        BoundedRangeModel model = progressBar.getModel();
+
+        if ( (model.getMaximum() - model.getMinimum()) != 0) {
+            if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+                amountFull = (int)Math.round(width *
+                                             progressBar.getPercentComplete());
+            } else {
+                amountFull = (int)Math.round(height *
+                                             progressBar.getPercentComplete());
+            }
+        }
+        return amountFull;
+    }
+
+    /**
+     * Delegates painting to one of two methods:
+     * paintDeterminate or paintIndeterminate.
+     */
+    public void paint(Graphics g, JComponent c) {
+        if (progressBar.isIndeterminate()) {
+            paintIndeterminate(g, c);
+        } else {
+            paintDeterminate(g, c);
+        }
+    }
+
+    /**
+     * Stores the position and size of
+     * the bouncing box that would be painted for the current animation index
+     * in <code>r</code> and returns <code>r</code>.
+     * Subclasses that add to the painting performed
+     * in this class's implementation of <code>paintIndeterminate</code> --
+     * to draw an outline around the bouncing box, for example --
+     * can use this method to get the location of the bouncing
+     * box that was just painted.
+     * By overriding this method,
+     * you have complete control over the size and position
+     * of the bouncing box,
+     * without having to reimplement <code>paintIndeterminate</code>.
+     *
+     * @param r  the Rectangle instance to be modified;
+     *           may be <code>null</code>
+     * @return   <code>null</code> if no box should be drawn;
+     *           otherwise, returns the passed-in rectangle
+     *           (if non-null)
+     *           or a new rectangle
+     *
+     * @see #setAnimationIndex
+     * @since 1.4
+     */
+    protected Rectangle getBox(Rectangle r) {
+        int currentFrame = getAnimationIndex();
+        int middleFrame = numFrames/2;
+
+        if (sizeChanged() || delta == 0.0 || maxPosition == 0.0) {
+            updateSizes();
+        }
+
+        r = getGenericBox(r);
+
+        if (r == null) {
+            return null;
+        }
+        if (middleFrame <= 0) {
+            return null;
+        }
+
+        //assert currentFrame >= 0 && currentFrame < numFrames
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            if (currentFrame < middleFrame) {
+                r.x = componentInnards.x
+                      + (int)Math.round(delta * (double)currentFrame);
+            } else {
+                r.x = maxPosition
+                      - (int)Math.round(delta *
+                                        (currentFrame - middleFrame));
+            }
+        } else { //VERTICAL indeterminate progress bar
+            if (currentFrame < middleFrame) {
+                r.y = componentInnards.y
+                      + (int)Math.round(delta * currentFrame);
+            } else {
+                r.y = maxPosition
+                      - (int)Math.round(delta *
+                                        (currentFrame - middleFrame));
+            }
+        }
+        return r;
+    }
+
+    /**
+     * Updates delta, max position.
+     * Assumes componentInnards is correct (e.g. call after sizeChanged()).
+     */
+    private void updateSizes() {
+        int length = 0;
+
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            length = getBoxLength(componentInnards.width,
+                                  componentInnards.height);
+            maxPosition = componentInnards.x + componentInnards.width
+                          - length;
+
+        } else { //VERTICAL progress bar
+            length = getBoxLength(componentInnards.height,
+                                  componentInnards.width);
+            maxPosition = componentInnards.y + componentInnards.height
+                          - length;
+        }
+
+        //If we're doing bouncing-box animation, update delta.
+        delta = 2.0 * (double)maxPosition/(double)numFrames;
+    }
+
+    /**
+     * Assumes that the component innards, max position, etc. are up-to-date.
+     */
+    private Rectangle getGenericBox(Rectangle r) {
+        if (r == null) {
+            r = new Rectangle();
+        }
+
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            r.width = getBoxLength(componentInnards.width,
+                                   componentInnards.height);
+            if (r.width < 0) {
+                r = null;
+            } else {
+                r.height = componentInnards.height;
+                r.y = componentInnards.y;
+            }
+          // end of HORIZONTAL
+
+        } else { //VERTICAL progress bar
+            r.height = getBoxLength(componentInnards.height,
+                                    componentInnards.width);
+            if (r.height < 0) {
+                r = null;
+            } else {
+                r.width = componentInnards.width;
+                r.x = componentInnards.x;
+            }
+        } // end of VERTICAL
+
+        return r;
+    }
+
+    /**
+     * Returns the length
+     * of the "bouncing box" to be painted.
+     * This method is invoked by the
+     * default implementation of <code>paintIndeterminate</code>
+     * to get the width (if the progress bar is horizontal)
+     * or height (if vertical) of the box.
+     * For example:
+     * <blockquote>
+     * <pre>
+     *boxRect.width = getBoxLength(componentInnards.width,
+     *                             componentInnards.height);
+     * </pre>
+     * </blockquote>
+     *
+     * @param availableLength  the amount of space available
+     *                         for the bouncing box to move in;
+     *                         for a horizontal progress bar,
+     *                         for example,
+     *                         this should be
+     *                         the inside width of the progress bar
+     *                         (the component width minus borders)
+     * @param otherDimension   for a horizontal progress bar, this should be
+     *                         the inside height of the progress bar; this
+     *                         value might be used to constrain or determine
+     *                         the return value
+     *
+     * @return the size of the box dimension being determined;
+     *         must be no larger than <code>availableLength</code>
+     *
+     * @see javax.swing.SwingUtilities#calculateInnerArea
+     * @since 1.5
+     */
+    protected int getBoxLength(int availableLength, int otherDimension) {
+        return (int)Math.round(availableLength/6.0);
+    }
+
+    /**
+     * All purpose paint method that should do the right thing for all
+     * linear bouncing-box progress bars.
+     * Override this if you are making another kind of
+     * progress bar.
+     *
+     * @see #paintDeterminate
+     *
+     * @since 1.4
+     */
+    protected void paintIndeterminate(Graphics g, JComponent c) {
+        if (!(g instanceof Graphics2D)) {
+            return;
+        }
+
+        Insets b = progressBar.getInsets(); // area for border
+        int barRectWidth = progressBar.getWidth() - (b.right + b.left);
+        int barRectHeight = progressBar.getHeight() - (b.top + b.bottom);
+
+        if (barRectWidth <= 0 || barRectHeight <= 0) {
+            return;
+        }
+
+        Graphics2D g2 = (Graphics2D)g;
+
+        // Paint the bouncing box.
+        boxRect = getBox(boxRect);
+        if (boxRect != null) {
+            g2.setColor(progressBar.getForeground());
+            g2.fillRect(boxRect.x, boxRect.y,
+                       boxRect.width, boxRect.height);
+        }
+
+        // Deal with possible text painting
+        if (progressBar.isStringPainted()) {
+            if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+                paintString(g2, b.left, b.top,
+                            barRectWidth, barRectHeight,
+                            boxRect.x, boxRect.width, b);
+            }
+            else {
+                paintString(g2, b.left, b.top,
+                            barRectWidth, barRectHeight,
+                            boxRect.y, boxRect.height, b);
+            }
+        }
+    }
+
+
+    /**
+     * All purpose paint method that should do the right thing for almost
+     * all linear, determinate progress bars. By setting a few values in
+     * the defaults
+     * table, things should work just fine to paint your progress bar.
+     * Naturally, override this if you are making a circular or
+     * semi-circular progress bar.
+     *
+     * @see #paintIndeterminate
+     *
+     * @since 1.4
+     */
+    protected void paintDeterminate(Graphics g, JComponent c) {
+        if (!(g instanceof Graphics2D)) {
+            return;
+        }
+
+        Insets b = progressBar.getInsets(); // area for border
+        int barRectWidth = progressBar.getWidth() - (b.right + b.left);
+        int barRectHeight = progressBar.getHeight() - (b.top + b.bottom);
+
+        if (barRectWidth <= 0 || barRectHeight <= 0) {
+            return;
+        }
+
+        int cellLength = getCellLength();
+        int cellSpacing = getCellSpacing();
+        // amount of progress to draw
+        int amountFull = getAmountFull(b, barRectWidth, barRectHeight);
+
+        Graphics2D g2 = (Graphics2D)g;
+        g2.setColor(progressBar.getForeground());
+
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            // draw the cells
+            if (cellSpacing == 0 && amountFull > 0) {
+                // draw one big Rect because there is no space between cells
+                g2.setStroke(new BasicStroke((float)barRectHeight,
+                        BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+            } else {
+                // draw each individual cell
+                g2.setStroke(new BasicStroke((float)barRectHeight,
+                        BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
+                        0.f, new float[] { cellLength, cellSpacing }, 0.f));
+            }
+
+            if (BasicGraphicsUtils.isLeftToRight(c)) {
+                g2.drawLine(b.left, (barRectHeight/2) + b.top,
+                        amountFull + b.left, (barRectHeight/2) + b.top);
+            } else {
+                g2.drawLine((barRectWidth + b.left),
+                        (barRectHeight/2) + b.top,
+                        barRectWidth + b.left - amountFull,
+                        (barRectHeight/2) + b.top);
+            }
+
+        } else { // VERTICAL
+            // draw the cells
+            if (cellSpacing == 0 && amountFull > 0) {
+                // draw one big Rect because there is no space between cells
+                g2.setStroke(new BasicStroke((float)barRectWidth,
+                        BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+            } else {
+                // draw each individual cell
+                g2.setStroke(new BasicStroke((float)barRectWidth,
+                        BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
+                        0f, new float[] { cellLength, cellSpacing }, 0f));
+            }
+
+            g2.drawLine(barRectWidth/2 + b.left,
+                    b.top + barRectHeight,
+                    barRectWidth/2 + b.left,
+                    b.top + barRectHeight - amountFull);
+        }
+
+        // Deal with possible text painting
+        if (progressBar.isStringPainted()) {
+            paintString(g, b.left, b.top,
+                        barRectWidth, barRectHeight,
+                        amountFull, b);
+        }
+    }
+
+
+    protected void paintString(Graphics g, int x, int y,
+                               int width, int height,
+                               int amountFull, Insets b) {
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            if (BasicGraphicsUtils.isLeftToRight(progressBar)) {
+                if (progressBar.isIndeterminate()) {
+                    boxRect = getBox(boxRect);
+                    paintString(g, x, y, width, height,
+                            boxRect.x, boxRect.width, b);
+                } else {
+                    paintString(g, x, y, width, height, x, amountFull, b);
+                }
+            }
+            else {
+                paintString(g, x, y, width, height, x + width - amountFull,
+                            amountFull, b);
+            }
+        }
+        else {
+            if (progressBar.isIndeterminate()) {
+                boxRect = getBox(boxRect);
+                paintString(g, x, y, width, height,
+                        boxRect.y, boxRect.height, b);
+            } else {
+                paintString(g, x, y, width, height, y + height - amountFull,
+                        amountFull, b);
+            }
+        }
+    }
+
+    /**
+     * Paints the progress string.
+     *
+     * @param g Graphics used for drawing.
+     * @param x x location of bounding box
+     * @param y y location of bounding box
+     * @param width width of bounding box
+     * @param height height of bounding box
+     * @param fillStart start location, in x or y depending on orientation,
+     *        of the filled portion of the progress bar.
+     * @param amountFull size of the fill region, either width or height
+     *        depending upon orientation.
+     * @param b Insets of the progress bar.
+     */
+    private void paintString(Graphics g, int x, int y, int width, int height,
+                             int fillStart, int amountFull, Insets b) {
+        if (!(g instanceof Graphics2D)) {
+            return;
+        }
+
+        Graphics2D g2 = (Graphics2D)g;
+        String progressString = progressBar.getString();
+        g2.setFont(progressBar.getFont());
+        Point renderLocation = getStringPlacement(g2, progressString,
+                                                  x, y, width, height);
+        Rectangle oldClip = g2.getClipBounds();
+
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            g2.setColor(getSelectionBackground());
+            SwingUtilities2.drawString(progressBar, g2, progressString,
+                                       renderLocation.x, renderLocation.y);
+            g2.setColor(getSelectionForeground());
+            g2.clipRect(fillStart, y, amountFull, height);
+            SwingUtilities2.drawString(progressBar, g2, progressString,
+                                       renderLocation.x, renderLocation.y);
+        } else { // VERTICAL
+            g2.setColor(getSelectionBackground());
+            AffineTransform rotate =
+                    AffineTransform.getRotateInstance(Math.PI/2);
+            g2.setFont(progressBar.getFont().deriveFont(rotate));
+            renderLocation = getStringPlacement(g2, progressString,
+                                                  x, y, width, height);
+            SwingUtilities2.drawString(progressBar, g2, progressString,
+                                       renderLocation.x, renderLocation.y);
+            g2.setColor(getSelectionForeground());
+            g2.clipRect(x, fillStart, width, amountFull);
+            SwingUtilities2.drawString(progressBar, g2, progressString,
+                                       renderLocation.x, renderLocation.y);
+        }
+        g2.setClip(oldClip);
+    }
+
+
+    /**
+     * Designate the place where the progress string will be painted.
+     * This implementation places it at the center of the progress
+     * bar (in both x and y). Override this if you want to right,
+     * left, top, or bottom align the progress string or if you need
+     * to nudge it around for any reason.
+     */
+    protected Point getStringPlacement(Graphics g, String progressString,
+                                       int x,int y,int width,int height) {
+        FontMetrics fontSizer = SwingUtilities2.getFontMetrics(progressBar, g,
+                                            progressBar.getFont());
+        int stringWidth = SwingUtilities2.stringWidth(progressBar, fontSizer,
+                                                      progressString);
+
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            return new Point(x + Math.round(width/2 - stringWidth/2),
+                             y + ((height +
+                                 fontSizer.getAscent() -
+                                 fontSizer.getLeading() -
+                                 fontSizer.getDescent()) / 2));
+        } else { // VERTICAL
+            return new Point(x + ((width - fontSizer.getAscent() +
+                    fontSizer.getLeading() + fontSizer.getDescent()) / 2),
+                    y + Math.round(height/2 - stringWidth/2));
+        }
+    }
+
+
+    public Dimension getPreferredSize(JComponent c) {
+        Dimension       size;
+        Insets          border = progressBar.getInsets();
+        FontMetrics     fontSizer = progressBar.getFontMetrics(
+                                                  progressBar.getFont());
+
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            size = new Dimension(getPreferredInnerHorizontal());
+            // Ensure that the progress string will fit
+            if (progressBar.isStringPainted()) {
+                // I'm doing this for completeness.
+                String progString = progressBar.getString();
+                int stringWidth = SwingUtilities2.stringWidth(
+                          progressBar, fontSizer, progString);
+                if (stringWidth > size.width) {
+                    size.width = stringWidth;
+                }
+                // This uses both Height and Descent to be sure that
+                // there is more than enough room in the progress bar
+                // for everything.
+                // This does have a strange dependency on
+                // getStringPlacememnt() in a funny way.
+                int stringHeight = fontSizer.getHeight() +
+                                   fontSizer.getDescent();
+                if (stringHeight > size.height) {
+                    size.height = stringHeight;
+                }
+            }
+        } else {
+            size = new Dimension(getPreferredInnerVertical());
+            // Ensure that the progress string will fit.
+            if (progressBar.isStringPainted()) {
+                String progString = progressBar.getString();
+                int stringHeight = fontSizer.getHeight() +
+                        fontSizer.getDescent();
+                if (stringHeight > size.width) {
+                    size.width = stringHeight;
+                }
+                // This is also for completeness.
+                int stringWidth = SwingUtilities2.stringWidth(
+                                       progressBar, fontSizer, progString);
+                if (stringWidth > size.height) {
+                    size.height = stringWidth;
+                }
+            }
+        }
+
+        size.width += border.left + border.right;
+        size.height += border.top + border.bottom;
+        return size;
+    }
+
+    /**
+     * The Minimum size for this component is 10. The rationale here
+     * is that there should be at least one pixel per 10 percent.
+     */
+    public Dimension getMinimumSize(JComponent c) {
+        Dimension pref = getPreferredSize(progressBar);
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            pref.width = 10;
+        } else {
+            pref.height = 10;
+        }
+        return pref;
+    }
+
+    public Dimension getMaximumSize(JComponent c) {
+        Dimension pref = getPreferredSize(progressBar);
+        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+            pref.width = Short.MAX_VALUE;
+        } else {
+            pref.height = Short.MAX_VALUE;
+        }
+        return pref;
+    }
+
+    /**
+     * Gets the index of the current animation frame.
+     *
+     * @since 1.4
+     */
+    protected int getAnimationIndex() {
+        return animationIndex;
+    }
+
+    /**
+     * Returns the number of frames for the complete animation loop
+     * used by an indeterminate JProgessBar. The progress chunk will go
+     * from one end to the other and back during the entire loop. This
+     * visual behavior may be changed by subclasses in other Look and Feels.
+     *
+     * @return the number of frames
+     * @since 1.6
+     */
+    protected final int getFrameCount() {
+        return numFrames;
+    }
+
+    /**
+     * Sets the index of the current animation frame
+     * to the specified value and requests that the
+     * progress bar be repainted.
+     * Subclasses that don't use the default painting code
+     * might need to override this method
+     * to change the way that the <code>repaint</code> method
+     * is invoked.
+     *
+     * @param newValue the new animation index; no checking
+     *                 is performed on its value
+     * @see #incrementAnimationIndex
+     *
+     * @since 1.4
+     */
+    protected void setAnimationIndex(int newValue) {
+        if (animationIndex != newValue) {
+            if (sizeChanged()) {
+                animationIndex = newValue;
+                maxPosition = 0;  //needs to be recalculated
+                delta = 0.0;      //needs to be recalculated
+                progressBar.repaint();
+                return;
+            }
+
+            //Get the previous box drawn.
+            nextPaintRect = getBox(nextPaintRect);
+
+            //Update the frame number.
+            animationIndex = newValue;
+
+            //Get the next box to draw.
+            if (nextPaintRect != null) {
+                boxRect = getBox(boxRect);
+                if (boxRect != null) {
+                    nextPaintRect.add(boxRect);
+                }
+            }
+        } else { //animationIndex == newValue
+            return;
+        }
+
+        if (nextPaintRect != null) {
+            progressBar.repaint(nextPaintRect);
+        } else {
+            progressBar.repaint();
+        }
+    }
+
+    private boolean sizeChanged() {
+        if ((oldComponentInnards == null) || (componentInnards == null)) {
+            return true;
+        }
+
+        oldComponentInnards.setRect(componentInnards);
+        componentInnards = SwingUtilities.calculateInnerArea(progressBar,
+                                                             componentInnards);
+        return !oldComponentInnards.equals(componentInnards);
+    }
+
+    /**
+     * Sets the index of the current animation frame,
+     * to the next valid value,
+     * which results in the progress bar being repainted.
+     * The next valid value is, by default,
+     * the current animation index plus one.
+     * If the new value would be too large,
+     * this method sets the index to 0.
+     * Subclasses might need to override this method
+     * to ensure that the index does not go over
+     * the number of frames needed for the particular
+     * progress bar instance.
+     * This method is invoked by the default animation thread
+     * every <em>X</em> milliseconds,
+     * where <em>X</em> is specified by the "ProgressBar.repaintInterval"
+     * UI default.
+     *
+     * @see #setAnimationIndex
+     * @since 1.4
+     */
+    protected void incrementAnimationIndex() {
+        int newValue = getAnimationIndex() + 1;
+
+        if (newValue < numFrames) {
+            setAnimationIndex(newValue);
+        } else {
+            setAnimationIndex(0);
+        }
+    }
+
+    /**
+     * Returns the desired number of milliseconds between repaints.
+     * This value is meaningful
+     * only if the progress bar is in indeterminate mode.
+     * The repaint interval determines how often the
+     * default animation thread's timer is fired.
+     * It's also used by the default indeterminate progress bar
+     * painting code when determining
+     * how far to move the bouncing box per frame.
+     * The repaint interval is specified by
+     * the "ProgressBar.repaintInterval" UI default.
+     *
+     * @return  the repaint interval, in milliseconds
+     */
+    private int getRepaintInterval() {
+        return repaintInterval;
+    }
+
+    private int initRepaintInterval() {
+        repaintInterval = DefaultLookup.getInt(progressBar,
+                this, "ProgressBar.repaintInterval", 50);
+        return repaintInterval;
+    }
+
+    /**
+     * Returns the number of milliseconds per animation cycle.
+     * This value is meaningful
+     * only if the progress bar is in indeterminate mode.
+     * The cycle time is used by the default indeterminate progress bar
+     * painting code when determining
+     * how far to move the bouncing box per frame.
+     * The cycle time is specified by
+     * the "ProgressBar.cycleTime" UI default
+     * and adjusted, if necessary,
+     * by the initIndeterminateDefaults method.
+     *
+     * @return  the cycle time, in milliseconds
+     */
+    private int getCycleTime() {
+        return cycleTime;
+    }
+
+    private int initCycleTime() {
+        cycleTime = DefaultLookup.getInt(progressBar, this,
+                "ProgressBar.cycleTime", 3000);
+        return cycleTime;
+    }
+
+
+    /** Initialize cycleTime, repaintInterval, numFrames, animationIndex. */
+    private void initIndeterminateDefaults() {
+        initRepaintInterval(); //initialize repaint interval
+        initCycleTime();       //initialize cycle length
+
+        // Make sure repaintInterval is reasonable.
+        if (repaintInterval <= 0) {
+            repaintInterval = 100;
+        }
+
+        // Make sure cycleTime is reasonable.
+        if (repaintInterval > cycleTime) {
+            cycleTime = repaintInterval * 20;
+        } else {
+            // Force cycleTime to be a even multiple of repaintInterval.
+            int factor = (int)Math.ceil(
+                                 ((double)cycleTime)
+                               / ((double)repaintInterval*2));
+            cycleTime = repaintInterval*factor*2;
+        }
+    }
+
+    /**
+     * Invoked by PropertyChangeHandler.
+     *
+     *  NOTE: This might not be invoked until after the first
+     *  paintIndeterminate call.
+     */
+    private void initIndeterminateValues() {
+        initIndeterminateDefaults();
+        //assert cycleTime/repaintInterval is a whole multiple of 2.
+        numFrames = cycleTime/repaintInterval;
+        initAnimationIndex();
+
+        boxRect = new Rectangle();
+        nextPaintRect = new Rectangle();
+        componentInnards = new Rectangle();
+        oldComponentInnards = new Rectangle();
+
+        // we only bother installing the HierarchyChangeListener if we
+        // are indeterminate
+        progressBar.addHierarchyListener(getHandler());
+
+        // start the animation thread if necessary
+        if (progressBar.isDisplayable()) {
+            startAnimationTimer();
+        }
+    }
+
+    /** Invoked by PropertyChangeHandler. */
+    private void cleanUpIndeterminateValues() {
+        // stop the animation thread if necessary
+        if (progressBar.isDisplayable()) {
+            stopAnimationTimer();
+        }
+
+        cycleTime = repaintInterval = 0;
+        numFrames = animationIndex = 0;
+        maxPosition = 0;
+        delta = 0.0;
+
+        boxRect = nextPaintRect = null;
+        componentInnards = oldComponentInnards = null;
+
+        progressBar.removeHierarchyListener(getHandler());
+    }
+
+    // Called from initIndeterminateValues to initialize the animation index.
+    // This assumes that numFrames is set to a correct value.
+    private void initAnimationIndex() {
+        if ((progressBar.getOrientation() == JProgressBar.HORIZONTAL) &&
+            (BasicGraphicsUtils.isLeftToRight(progressBar))) {
+            // If this is a left-to-right progress bar,
+            // start at the first frame.
+            setAnimationIndex(0);
+        } else {
+            // If we go right-to-left or vertically, start at the right/bottom.
+            setAnimationIndex(numFrames/2);
+        }
+    }
+
+    //
+    // Animation Thread
+    //
+    /**
+     * Implements an animation thread that invokes repaint
+     * at a fixed rate.  If ADJUSTTIMER is true, this thread
+     * will continuously adjust the repaint interval to
+     * try to make the actual time between repaints match
+     * the requested rate.
+     */
+    private class Animator implements ActionListener {
+        private Timer timer;
+        private long previousDelay; //used to tune the repaint interval
+        private int interval; //the fixed repaint interval
+        private long lastCall; //the last time actionPerformed was called
+        private int MINIMUM_DELAY = 5;
+
+        /**
+         * Creates a timer if one doesn't already exist,
+         * then starts the timer thread.
+         */
+        private void start(int interval) {
+            previousDelay = interval;
+            lastCall = 0;
+
+            if (timer == null) {
+                timer = new Timer(interval, this);
+            } else {
+                timer.setDelay(interval);
+            }
+
+            if (ADJUSTTIMER) {
+                timer.setRepeats(false);
+                timer.setCoalesce(false);
+            }
+
+            timer.start();
+        }
+
+        /**
+         * Stops the timer thread.
+         */
+        private void stop() {
+            timer.stop();
+        }
+
+        /**
+         * Reacts to the timer's action events.
+         */
+        public void actionPerformed(ActionEvent e) {
+            if (ADJUSTTIMER) {
+                long time = System.currentTimeMillis();
+
+                if (lastCall > 0) { //adjust nextDelay
+                //XXX maybe should cache this after a while
+                    //actual = time - lastCall
+                    //difference = actual - interval
+                    //nextDelay = previousDelay - difference
+                    //          = previousDelay - (time - lastCall - interval)
+                   int nextDelay = (int)(previousDelay
+                                          - time + lastCall
+                                          + getRepaintInterval());
+                    if (nextDelay < MINIMUM_DELAY) {
+                        nextDelay = MINIMUM_DELAY;
+                    }
+                    timer.setInitialDelay(nextDelay);
+                    previousDelay = nextDelay;
+                }
+                timer.start();
+                lastCall = time;
+            }
+
+            incrementAnimationIndex(); //paint next frame
+        }
+    }
+
+
+    /**
+     * This inner class is marked &quot;public&quot; due to a compiler bug.
+     * This class should be treated as a &quot;protected&quot; inner class.
+     * Instantiate it only within subclasses of BasicProgressBarUI.
+     */
+    public class ChangeHandler implements ChangeListener {
+        // NOTE: This class exists only for backward compatability. All
+        // its functionality has been moved into Handler. If you need to add
+        // new functionality add it to the Handler, but make sure this
+        // class calls into the Handler.
+        public void stateChanged(ChangeEvent e) {
+            getHandler().stateChanged(e);
+        }
+    }
+
+
+    private class Handler implements ChangeListener, PropertyChangeListener, HierarchyListener {
+        // ChangeListener
+        public void stateChanged(ChangeEvent e) {
+            BoundedRangeModel model = progressBar.getModel();
+            int newRange = model.getMaximum() - model.getMinimum();
+            int newPercent;
+            int oldPercent = getCachedPercent();
+
+            if (newRange > 0) {
+                newPercent = (int)((100 * (long)model.getValue()) / newRange);
+            } else {
+                newPercent = 0;
+            }
+
+            if (newPercent != oldPercent) {
+                setCachedPercent(newPercent);
+                progressBar.repaint();
+            }
+        }
+
+        // PropertyChangeListener
+        public void propertyChange(PropertyChangeEvent e) {
+            String prop = e.getPropertyName();
+            if ("indeterminate" == prop) {
+                if (progressBar.isIndeterminate()) {
+                    initIndeterminateValues();
+                } else {
+                    //clean up
+                    cleanUpIndeterminateValues();
+                }
+                progressBar.repaint();
+            }
+        }
+
+        // we don't want the animation to keep running if we're not displayable
+        public void hierarchyChanged(HierarchyEvent he) {
+            if ((he.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
+                if (progressBar.isIndeterminate()) {
+                    if (progressBar.isDisplayable()) {
+                        startAnimationTimer();
+                    } else {
+                        stopAnimationTimer();
+                    }
+                }
+            }
+        }
+    }
+}