/* * Copyright (c) 1997, 2006, 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 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"); if (cellLength == 0) cellLength = 1; 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 class should be treated as a "protected" inner class. * Instantiate it only within subclasses of {@code 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(); } } } } } }