1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.plaf.basic;
  27 
  28 import sun.swing.SwingUtilities2;
  29 import java.awt.*;
  30 import java.awt.geom.AffineTransform;
  31 import java.awt.event.*;
  32 import javax.swing.*;
  33 import javax.swing.event.*;
  34 import javax.swing.plaf.*;
  35 import java.beans.PropertyChangeListener;
  36 import java.beans.PropertyChangeEvent;
  37 import java.io.Serializable;
  38 import sun.swing.DefaultLookup;
  39 
  40 /**
  41  * A Basic L&F implementation of ProgressBarUI.
  42  *
  43  * @author Michael C. Albers
  44  * @author Kathy Walrath
  45  */
  46 public class BasicProgressBarUI extends ProgressBarUI {
  47     private int cachedPercent;
  48     private int cellLength, cellSpacing;
  49     // The "selectionForeground" is the color of the text when it is painted
  50     // over a filled area of the progress bar. The "selectionBackground"
  51     // is for the text over the unfilled progress bar area.
  52     private Color selectionForeground, selectionBackground;
  53 
  54     private Animator animator;
  55 
  56     /**
  57      * The instance of {@code JProgressBar}.
  58      */
  59     protected JProgressBar progressBar;
  60     /**
  61      * The instance of {@code ChangeListener}.
  62      */
  63     protected ChangeListener changeListener;
  64     private Handler handler;
  65 
  66     /**
  67      * The current state of the indeterminate animation's cycle.
  68      * 0, the initial value, means paint the first frame.
  69      * When the progress bar is indeterminate and showing,
  70      * the default animation thread updates this variable
  71      * by invoking incrementAnimationIndex()
  72      * every repaintInterval milliseconds.
  73      */
  74     private int animationIndex = 0;
  75 
  76     /**
  77      * The number of frames per cycle. Under the default implementation,
  78      * this depends on the cycleTime and repaintInterval.  It
  79      * must be an even number for the default painting algorithm.  This
  80      * value is set in the initIndeterminateValues method.
  81      */
  82     private int numFrames;   //0 1|numFrames-1 ... numFrames/2
  83 
  84     /**
  85      * Interval (in ms) between repaints of the indeterminate progress bar.
  86      * The value of this method is set
  87      * (every time the progress bar changes to indeterminate mode)
  88      * using the
  89      * "ProgressBar.repaintInterval" key in the defaults table.
  90      */
  91     private int repaintInterval;
  92 
  93     /**
  94      * The number of milliseconds until the animation cycle repeats.
  95      * The value of this method is set
  96      * (every time the progress bar changes to indeterminate mode)
  97      * using the
  98      * "ProgressBar.cycleTime" key in the defaults table.
  99      */
 100     private int cycleTime;  //must be repaintInterval*2*aPositiveInteger
 101 
 102     //performance stuff
 103     private static boolean ADJUSTTIMER = true; //makes a BIG difference;
 104                                                //make this false for
 105                                                //performance tests
 106 
 107     /**
 108      * Used to hold the location and size of the bouncing box (returned
 109      * by getBox) to be painted.
 110      *
 111      * @since 1.5
 112      */
 113     protected Rectangle boxRect;
 114 
 115     /**
 116      * The rectangle to be updated the next time the
 117      * animation thread calls repaint.  For bouncing-box
 118      * animation this rect should include the union of
 119      * the currently displayed box (which needs to be erased)
 120      * and the box to be displayed next.
 121      * This rectangle's values are set in
 122      * the setAnimationIndex method.
 123      */
 124     private Rectangle nextPaintRect;
 125 
 126     //cache
 127     /** The component's painting area, not including the border. */
 128     private Rectangle componentInnards;    //the current painting area
 129     private Rectangle oldComponentInnards; //used to see if the size changed
 130 
 131     /** For bouncing-box animation, the change in position per frame. */
 132     private double delta = 0.0;
 133 
 134     private int maxPosition = 0; //maximum X (horiz) or Y box location
 135 
 136     /**
 137      * Returns a new instance of {@code BasicProgressBarUI}.
 138      *
 139      * @param x a component
 140      * @return a new instance of {@code BasicProgressBarUI}
 141      */
 142     public static ComponentUI createUI(JComponent x) {
 143         return new BasicProgressBarUI();
 144     }
 145 
 146     public void installUI(JComponent c) {
 147         progressBar = (JProgressBar)c;
 148         installDefaults();
 149         installListeners();
 150         if (progressBar.isIndeterminate()) {
 151             initIndeterminateValues();
 152         }
 153     }
 154 
 155     public void uninstallUI(JComponent c) {
 156         if (progressBar.isIndeterminate()) {
 157             cleanUpIndeterminateValues();
 158         }
 159         uninstallDefaults();
 160         uninstallListeners();
 161         progressBar = null;
 162     }
 163 
 164     /**
 165      * Installs default properties.
 166      */
 167     protected void installDefaults() {
 168         LookAndFeel.installProperty(progressBar, "opaque", Boolean.TRUE);
 169         LookAndFeel.installBorder(progressBar,"ProgressBar.border");
 170         LookAndFeel.installColorsAndFont(progressBar,
 171                                          "ProgressBar.background",
 172                                          "ProgressBar.foreground",
 173                                          "ProgressBar.font");
 174         cellLength = UIManager.getInt("ProgressBar.cellLength");
 175         if (cellLength == 0) cellLength = 1;
 176         cellSpacing = UIManager.getInt("ProgressBar.cellSpacing");
 177         selectionForeground = UIManager.getColor("ProgressBar.selectionForeground");
 178         selectionBackground = UIManager.getColor("ProgressBar.selectionBackground");
 179     }
 180 
 181     /**
 182      * Unintalls default properties.
 183      */
 184     protected void uninstallDefaults() {
 185         LookAndFeel.uninstallBorder(progressBar);
 186     }
 187 
 188     /**
 189      * Registers listeners.
 190      */
 191     protected void installListeners() {
 192         //Listen for changes in the progress bar's data.
 193         changeListener = getHandler();
 194         progressBar.addChangeListener(changeListener);
 195 
 196         //Listen for changes between determinate and indeterminate state.
 197         progressBar.addPropertyChangeListener(getHandler());
 198     }
 199 
 200     private Handler getHandler() {
 201         if (handler == null) {
 202             handler = new Handler();
 203         }
 204         return handler;
 205     }
 206 
 207     /**
 208      * Starts the animation thread, creating and initializing
 209      * it if necessary. This method is invoked when an
 210      * indeterminate progress bar should start animating.
 211      * Reasons for this may include:
 212      * <ul>
 213      *    <li>The progress bar is determinate and becomes displayable
 214      *    <li>The progress bar is displayable and becomes determinate
 215      *    <li>The progress bar is displayable and determinate and this
 216      *        UI is installed
 217      * </ul>
 218      * If you implement your own animation thread,
 219      * you must override this method.
 220      *
 221      * @since 1.4
 222      * @see #stopAnimationTimer
 223      */
 224     protected void startAnimationTimer() {
 225         if (animator == null) {
 226             animator = new Animator();
 227         }
 228 
 229         animator.start(getRepaintInterval());
 230     }
 231 
 232     /**
 233      * Stops the animation thread.
 234      * This method is invoked when the indeterminate
 235      * animation should be stopped. Reasons for this may include:
 236      * <ul>
 237      *    <li>The progress bar changes to determinate
 238      *    <li>The progress bar is no longer part of a displayable hierarchy
 239      *    <li>This UI in uninstalled
 240      * </ul>
 241      * If you implement your own animation thread,
 242      * you must override this method.
 243      *
 244      * @since 1.4
 245      * @see #startAnimationTimer
 246      */
 247     protected void stopAnimationTimer() {
 248         if (animator != null) {
 249             animator.stop();
 250         }
 251     }
 252 
 253     /**
 254      * Removes all listeners installed by this object.
 255      */
 256     protected void uninstallListeners() {
 257         progressBar.removeChangeListener(changeListener);
 258         progressBar.removePropertyChangeListener(getHandler());
 259         handler = null;
 260     }
 261 
 262 
 263     /**
 264      * Returns the baseline.
 265      *
 266      * @throws NullPointerException {@inheritDoc}
 267      * @throws IllegalArgumentException {@inheritDoc}
 268      * @see javax.swing.JComponent#getBaseline(int, int)
 269      * @since 1.6
 270      */
 271     public int getBaseline(JComponent c, int width, int height) {
 272         super.getBaseline(c, width, height);
 273         if (progressBar.isStringPainted() &&
 274                 progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 275             FontMetrics metrics = progressBar.
 276                     getFontMetrics(progressBar.getFont());
 277             Insets insets = progressBar.getInsets();
 278             int y = insets.top;
 279             height = height - insets.top - insets.bottom;
 280             return y + (height + metrics.getAscent() -
 281                         metrics.getLeading() -
 282                         metrics.getDescent()) / 2;
 283         }
 284         return -1;
 285     }
 286 
 287     /**
 288      * Returns an enum indicating how the baseline of the component
 289      * changes as the size changes.
 290      *
 291      * @throws NullPointerException {@inheritDoc}
 292      * @see javax.swing.JComponent#getBaseline(int, int)
 293      * @since 1.6
 294      */
 295     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
 296             JComponent c) {
 297         super.getBaselineResizeBehavior(c);
 298         if (progressBar.isStringPainted() &&
 299                 progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 300             return Component.BaselineResizeBehavior.CENTER_OFFSET;
 301         }
 302         return Component.BaselineResizeBehavior.OTHER;
 303     }
 304 
 305     // Many of the Basic*UI components have the following methods.
 306     // This component does not have these methods because *ProgressBarUI
 307     //  is not a compound component and does not accept input.
 308     //
 309     // protected void installComponents()
 310     // protected void uninstallComponents()
 311     // protected void installKeyboardActions()
 312     // protected void uninstallKeyboardActions()
 313 
 314     /**
 315      * Returns preferred size of the horizontal {@code JProgressBar}.
 316      *
 317      * @return preferred size of the horizontal {@code JProgressBar}
 318      */
 319     protected Dimension getPreferredInnerHorizontal() {
 320         Dimension horizDim = (Dimension)DefaultLookup.get(progressBar, this,
 321             "ProgressBar.horizontalSize");
 322         if (horizDim == null) {
 323             horizDim = new Dimension(146, 12);
 324         }
 325         return horizDim;
 326     }
 327 
 328     /**
 329      * Returns preferred size of the vertical {@code JProgressBar}.
 330      *
 331      * @return preferred size of the vertical {@code JProgressBar}
 332      */
 333     protected Dimension getPreferredInnerVertical() {
 334         Dimension vertDim = (Dimension)DefaultLookup.get(progressBar, this,
 335             "ProgressBar.verticalSize");
 336         if (vertDim == null) {
 337             vertDim = new Dimension(12, 146);
 338         }
 339         return vertDim;
 340     }
 341 
 342     /**
 343      * The "selectionForeground" is the color of the text when it is painted
 344      * over a filled area of the progress bar.
 345      *
 346      * @return the color of the selected foreground
 347      */
 348     protected Color getSelectionForeground() {
 349         return selectionForeground;
 350     }
 351 
 352     /**
 353      * The "selectionBackground" is the color of the text when it is painted
 354      * over an unfilled area of the progress bar.
 355      *
 356      * @return the color of the selected background
 357      */
 358     protected Color getSelectionBackground() {
 359         return selectionBackground;
 360     }
 361 
 362     private int getCachedPercent() {
 363         return cachedPercent;
 364     }
 365 
 366     private void setCachedPercent(int cachedPercent) {
 367         this.cachedPercent = cachedPercent;
 368     }
 369 
 370     /**
 371      * Returns the width (if HORIZONTAL) or height (if VERTICAL)
 372      * of each of the individual cells/units to be rendered in the
 373      * progress bar. However, for text rendering simplification and
 374      * aesthetic considerations, this function will return 1 when
 375      * the progress string is being rendered.
 376      *
 377      * @return the value representing the spacing between cells
 378      * @see    #setCellLength
 379      * @see    JProgressBar#isStringPainted
 380      */
 381     protected int getCellLength() {
 382         if (progressBar.isStringPainted()) {
 383             return 1;
 384         } else {
 385             return cellLength;
 386         }
 387     }
 388 
 389     /**
 390      * Sets the cell length.
 391      *
 392      * @param cellLen a new cell length
 393      */
 394     protected void setCellLength(int cellLen) {
 395         this.cellLength = cellLen;
 396     }
 397 
 398     /**
 399      * Returns the spacing between each of the cells/units in the
 400      * progress bar. However, for text rendering simplification and
 401      * aesthetic considerations, this function will return 0 when
 402      * the progress string is being rendered.
 403      *
 404      * @return the value representing the spacing between cells
 405      * @see    #setCellSpacing
 406      * @see    JProgressBar#isStringPainted
 407      */
 408     protected int getCellSpacing() {
 409         if (progressBar.isStringPainted()) {
 410             return 0;
 411         } else {
 412             return cellSpacing;
 413         }
 414     }
 415 
 416     /**
 417      * Sets the cell spacing.
 418      *
 419      * @param cellSpace a new cell spacing
 420      */
 421     protected void setCellSpacing(int cellSpace) {
 422         this.cellSpacing = cellSpace;
 423     }
 424 
 425     /**
 426      * This determines the amount of the progress bar that should be filled
 427      * based on the percent done gathered from the model. This is a common
 428      * operation so it was abstracted out. It assumes that your progress bar
 429      * is linear. That is, if you are making a circular progress indicator,
 430      * you will want to override this method.
 431      *
 432      * @param b insets
 433      * @param width a width
 434      * @param height a height
 435      * @return the amount of the progress bar that should be filled
 436      */
 437     protected int getAmountFull(Insets b, int width, int height) {
 438         int amountFull = 0;
 439         BoundedRangeModel model = progressBar.getModel();
 440 
 441         if ( (model.getMaximum() - model.getMinimum()) != 0) {
 442             if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 443                 amountFull = (int)Math.round(width *
 444                                              progressBar.getPercentComplete());
 445             } else {
 446                 amountFull = (int)Math.round(height *
 447                                              progressBar.getPercentComplete());
 448             }
 449         }
 450         return amountFull;
 451     }
 452 
 453     /**
 454      * Delegates painting to one of two methods:
 455      * paintDeterminate or paintIndeterminate.
 456      */
 457     public void paint(Graphics g, JComponent c) {
 458         if (progressBar.isIndeterminate()) {
 459             paintIndeterminate(g, c);
 460         } else {
 461             paintDeterminate(g, c);
 462         }
 463     }
 464 
 465     /**
 466      * Stores the position and size of
 467      * the bouncing box that would be painted for the current animation index
 468      * in <code>r</code> and returns <code>r</code>.
 469      * Subclasses that add to the painting performed
 470      * in this class's implementation of <code>paintIndeterminate</code> --
 471      * to draw an outline around the bouncing box, for example --
 472      * can use this method to get the location of the bouncing
 473      * box that was just painted.
 474      * By overriding this method,
 475      * you have complete control over the size and position
 476      * of the bouncing box,
 477      * without having to reimplement <code>paintIndeterminate</code>.
 478      *
 479      * @param r  the Rectangle instance to be modified;
 480      *           may be <code>null</code>
 481      * @return   <code>null</code> if no box should be drawn;
 482      *           otherwise, returns the passed-in rectangle
 483      *           (if non-null)
 484      *           or a new rectangle
 485      *
 486      * @see #setAnimationIndex
 487      * @since 1.4
 488      */
 489     protected Rectangle getBox(Rectangle r) {
 490         int currentFrame = getAnimationIndex();
 491         int middleFrame = numFrames/2;
 492 
 493         if (sizeChanged() || delta == 0.0 || maxPosition == 0.0) {
 494             updateSizes();
 495         }
 496 
 497         r = getGenericBox(r);
 498 
 499         if (r == null) {
 500             return null;
 501         }
 502         if (middleFrame <= 0) {
 503             return null;
 504         }
 505 
 506         //assert currentFrame >= 0 && currentFrame < numFrames
 507         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 508             if (currentFrame < middleFrame) {
 509                 r.x = componentInnards.x
 510                       + (int)Math.round(delta * (double)currentFrame);
 511             } else {
 512                 r.x = maxPosition
 513                       - (int)Math.round(delta *
 514                                         (currentFrame - middleFrame));
 515             }
 516         } else { //VERTICAL indeterminate progress bar
 517             if (currentFrame < middleFrame) {
 518                 r.y = componentInnards.y
 519                       + (int)Math.round(delta * currentFrame);
 520             } else {
 521                 r.y = maxPosition
 522                       - (int)Math.round(delta *
 523                                         (currentFrame - middleFrame));
 524             }
 525         }
 526         return r;
 527     }
 528 
 529     /**
 530      * Updates delta, max position.
 531      * Assumes componentInnards is correct (e.g. call after sizeChanged()).
 532      */
 533     private void updateSizes() {
 534         int length = 0;
 535 
 536         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 537             length = getBoxLength(componentInnards.width,
 538                                   componentInnards.height);
 539             maxPosition = componentInnards.x + componentInnards.width
 540                           - length;
 541 
 542         } else { //VERTICAL progress bar
 543             length = getBoxLength(componentInnards.height,
 544                                   componentInnards.width);
 545             maxPosition = componentInnards.y + componentInnards.height
 546                           - length;
 547         }
 548 
 549         //If we're doing bouncing-box animation, update delta.
 550         delta = 2.0 * (double)maxPosition/(double)numFrames;
 551     }
 552 
 553     /**
 554      * Assumes that the component innards, max position, etc. are up-to-date.
 555      */
 556     private Rectangle getGenericBox(Rectangle r) {
 557         if (r == null) {
 558             r = new Rectangle();
 559         }
 560 
 561         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 562             r.width = getBoxLength(componentInnards.width,
 563                                    componentInnards.height);
 564             if (r.width < 0) {
 565                 r = null;
 566             } else {
 567                 r.height = componentInnards.height;
 568                 r.y = componentInnards.y;
 569             }
 570           // end of HORIZONTAL
 571 
 572         } else { //VERTICAL progress bar
 573             r.height = getBoxLength(componentInnards.height,
 574                                     componentInnards.width);
 575             if (r.height < 0) {
 576                 r = null;
 577             } else {
 578                 r.width = componentInnards.width;
 579                 r.x = componentInnards.x;
 580             }
 581         } // end of VERTICAL
 582 
 583         return r;
 584     }
 585 
 586     /**
 587      * Returns the length
 588      * of the "bouncing box" to be painted.
 589      * This method is invoked by the
 590      * default implementation of <code>paintIndeterminate</code>
 591      * to get the width (if the progress bar is horizontal)
 592      * or height (if vertical) of the box.
 593      * For example:
 594      * <blockquote>
 595      * <pre>
 596      *boxRect.width = getBoxLength(componentInnards.width,
 597      *                             componentInnards.height);
 598      * </pre>
 599      * </blockquote>
 600      *
 601      * @param availableLength  the amount of space available
 602      *                         for the bouncing box to move in;
 603      *                         for a horizontal progress bar,
 604      *                         for example,
 605      *                         this should be
 606      *                         the inside width of the progress bar
 607      *                         (the component width minus borders)
 608      * @param otherDimension   for a horizontal progress bar, this should be
 609      *                         the inside height of the progress bar; this
 610      *                         value might be used to constrain or determine
 611      *                         the return value
 612      *
 613      * @return the size of the box dimension being determined;
 614      *         must be no larger than <code>availableLength</code>
 615      *
 616      * @see javax.swing.SwingUtilities#calculateInnerArea
 617      * @since 1.5
 618      */
 619     protected int getBoxLength(int availableLength, int otherDimension) {
 620         return (int)Math.round(availableLength/6.0);
 621     }
 622 
 623     /**
 624      * All purpose paint method that should do the right thing for all
 625      * linear bouncing-box progress bars.
 626      * Override this if you are making another kind of
 627      * progress bar.
 628      *
 629      * @param g an instance of {@code Graphics}
 630      * @param c a component
 631      * @see #paintDeterminate
 632      *
 633      * @since 1.4
 634      */
 635     protected void paintIndeterminate(Graphics g, JComponent c) {
 636         if (!(g instanceof Graphics2D)) {
 637             return;
 638         }
 639 
 640         Insets b = progressBar.getInsets(); // area for border
 641         int barRectWidth = progressBar.getWidth() - (b.right + b.left);
 642         int barRectHeight = progressBar.getHeight() - (b.top + b.bottom);
 643 
 644         if (barRectWidth <= 0 || barRectHeight <= 0) {
 645             return;
 646         }
 647 
 648         Graphics2D g2 = (Graphics2D)g;
 649 
 650         // Paint the bouncing box.
 651         boxRect = getBox(boxRect);
 652         if (boxRect != null) {
 653             g2.setColor(progressBar.getForeground());
 654             g2.fillRect(boxRect.x, boxRect.y,
 655                        boxRect.width, boxRect.height);
 656         }
 657 
 658         // Deal with possible text painting
 659         if (progressBar.isStringPainted()) {
 660             if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 661                 paintString(g2, b.left, b.top,
 662                             barRectWidth, barRectHeight,
 663                             boxRect.x, boxRect.width, b);
 664             }
 665             else {
 666                 paintString(g2, b.left, b.top,
 667                             barRectWidth, barRectHeight,
 668                             boxRect.y, boxRect.height, b);
 669             }
 670         }
 671     }
 672 
 673 
 674     /**
 675      * All purpose paint method that should do the right thing for almost
 676      * all linear, determinate progress bars. By setting a few values in
 677      * the defaults
 678      * table, things should work just fine to paint your progress bar.
 679      * Naturally, override this if you are making a circular or
 680      * semi-circular progress bar.
 681      *
 682      * @param g an instance of {@code Graphics}
 683      * @param c a component
 684      * @see #paintIndeterminate
 685      *
 686      * @since 1.4
 687      */
 688     protected void paintDeterminate(Graphics g, JComponent c) {
 689         if (!(g instanceof Graphics2D)) {
 690             return;
 691         }
 692 
 693         Insets b = progressBar.getInsets(); // area for border
 694         int barRectWidth = progressBar.getWidth() - (b.right + b.left);
 695         int barRectHeight = progressBar.getHeight() - (b.top + b.bottom);
 696 
 697         if (barRectWidth <= 0 || barRectHeight <= 0) {
 698             return;
 699         }
 700 
 701         int cellLength = getCellLength();
 702         int cellSpacing = getCellSpacing();
 703         // amount of progress to draw
 704         int amountFull = getAmountFull(b, barRectWidth, barRectHeight);
 705 
 706         Graphics2D g2 = (Graphics2D)g;
 707         g2.setColor(progressBar.getForeground());
 708 
 709         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 710             // draw the cells
 711             if (cellSpacing == 0 && amountFull > 0) {
 712                 // draw one big Rect because there is no space between cells
 713                 g2.setStroke(new BasicStroke((float)barRectHeight,
 714                         BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
 715             } else {
 716                 // draw each individual cell
 717                 g2.setStroke(new BasicStroke((float)barRectHeight,
 718                         BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
 719                         0.f, new float[] { cellLength, cellSpacing }, 0.f));
 720             }
 721 
 722             if (BasicGraphicsUtils.isLeftToRight(c)) {
 723                 g2.drawLine(b.left, (barRectHeight/2) + b.top,
 724                         amountFull + b.left, (barRectHeight/2) + b.top);
 725             } else {
 726                 g2.drawLine((barRectWidth + b.left),
 727                         (barRectHeight/2) + b.top,
 728                         barRectWidth + b.left - amountFull,
 729                         (barRectHeight/2) + b.top);
 730             }
 731 
 732         } else { // VERTICAL
 733             // draw the cells
 734             if (cellSpacing == 0 && amountFull > 0) {
 735                 // draw one big Rect because there is no space between cells
 736                 g2.setStroke(new BasicStroke((float)barRectWidth,
 737                         BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
 738             } else {
 739                 // draw each individual cell
 740                 g2.setStroke(new BasicStroke((float)barRectWidth,
 741                         BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
 742                         0f, new float[] { cellLength, cellSpacing }, 0f));
 743             }
 744 
 745             g2.drawLine(barRectWidth/2 + b.left,
 746                     b.top + barRectHeight,
 747                     barRectWidth/2 + b.left,
 748                     b.top + barRectHeight - amountFull);
 749         }
 750 
 751         // Deal with possible text painting
 752         if (progressBar.isStringPainted()) {
 753             paintString(g, b.left, b.top,
 754                         barRectWidth, barRectHeight,
 755                         amountFull, b);
 756         }
 757     }
 758 
 759     /**
 760      * Paints the progress string.
 761      *
 762      * @param g an instance of {@code Graphics}
 763      * @param x X location of bounding box
 764      * @param y Y location of bounding box
 765      * @param width width of bounding box
 766      * @param height height of bounding box
 767      * @param amountFull size of the fill region, either width or height
 768      *        depending upon orientation.
 769      * @param b Insets of the progress bar.
 770      */
 771     protected void paintString(Graphics g, int x, int y,
 772                                int width, int height,
 773                                int amountFull, Insets b) {
 774         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 775             if (BasicGraphicsUtils.isLeftToRight(progressBar)) {
 776                 if (progressBar.isIndeterminate()) {
 777                     boxRect = getBox(boxRect);
 778                     paintString(g, x, y, width, height,
 779                             boxRect.x, boxRect.width, b);
 780                 } else {
 781                     paintString(g, x, y, width, height, x, amountFull, b);
 782                 }
 783             }
 784             else {
 785                 paintString(g, x, y, width, height, x + width - amountFull,
 786                             amountFull, b);
 787             }
 788         }
 789         else {
 790             if (progressBar.isIndeterminate()) {
 791                 boxRect = getBox(boxRect);
 792                 paintString(g, x, y, width, height,
 793                         boxRect.y, boxRect.height, b);
 794             } else {
 795                 paintString(g, x, y, width, height, y + height - amountFull,
 796                         amountFull, b);
 797             }
 798         }
 799     }
 800 
 801     /**
 802      * Paints the progress string.
 803      *
 804      * @param g Graphics used for drawing.
 805      * @param x x location of bounding box
 806      * @param y y location of bounding box
 807      * @param width width of bounding box
 808      * @param height height of bounding box
 809      * @param fillStart start location, in x or y depending on orientation,
 810      *        of the filled portion of the progress bar.
 811      * @param amountFull size of the fill region, either width or height
 812      *        depending upon orientation.
 813      * @param b Insets of the progress bar.
 814      */
 815     private void paintString(Graphics g, int x, int y, int width, int height,
 816                              int fillStart, int amountFull, Insets b) {
 817         if (!(g instanceof Graphics2D)) {
 818             return;
 819         }
 820 
 821         Graphics2D g2 = (Graphics2D)g;
 822         String progressString = progressBar.getString();
 823         g2.setFont(progressBar.getFont());
 824         Point renderLocation = getStringPlacement(g2, progressString,
 825                                                   x, y, width, height);
 826         Rectangle oldClip = g2.getClipBounds();
 827 
 828         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 829             g2.setColor(getSelectionBackground());
 830             getTextUIDrawing().drawString(progressBar, g2, progressString,
 831                                        renderLocation.x, renderLocation.y);
 832             g2.setColor(getSelectionForeground());
 833             g2.clipRect(fillStart, y, amountFull, height);
 834             getTextUIDrawing().drawString(progressBar, g2, progressString,
 835                                        renderLocation.x, renderLocation.y);
 836         } else { // VERTICAL
 837             g2.setColor(getSelectionBackground());
 838             AffineTransform rotate =
 839                     AffineTransform.getRotateInstance(Math.PI/2);
 840             g2.setFont(progressBar.getFont().deriveFont(rotate));
 841             renderLocation = getStringPlacement(g2, progressString,
 842                                                   x, y, width, height);
 843             getTextUIDrawing().drawString(progressBar, g2, progressString,
 844                                        renderLocation.x, renderLocation.y);
 845             g2.setColor(getSelectionForeground());
 846             g2.clipRect(x, fillStart, width, amountFull);
 847             getTextUIDrawing().drawString(progressBar, g2, progressString,
 848                                        renderLocation.x, renderLocation.y);
 849         }
 850         g2.setClip(oldClip);
 851     }
 852 
 853 
 854     /**
 855      * Designate the place where the progress string will be painted.
 856      * This implementation places it at the center of the progress
 857      * bar (in both x and y). Override this if you want to right,
 858      * left, top, or bottom align the progress string or if you need
 859      * to nudge it around for any reason.
 860      *
 861      * @param g an instance of {@code Graphics}
 862      * @param progressString a text
 863      * @param x an X coordinate
 864      * @param y an Y coordinate
 865      * @param width a width
 866      * @param height a height
 867      * @return the place where the progress string will be painted
 868      */
 869     protected Point getStringPlacement(Graphics g, String progressString,
 870                                        int x,int y,int width,int height) {
 871         FontMetrics fontSizer = SwingUtilities2.getFontMetrics(progressBar, g,
 872                                             progressBar.getFont());
 873         int stringWidth = getTextUIDrawing().getStringWidth(progressBar, fontSizer,
 874                                                       progressString);
 875 
 876         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 877             return new Point(x + Math.round(width/2 - stringWidth/2),
 878                              y + ((height +
 879                                  fontSizer.getAscent() -
 880                                  fontSizer.getLeading() -
 881                                  fontSizer.getDescent()) / 2));
 882         } else { // VERTICAL
 883             return new Point(x + ((width - fontSizer.getAscent() +
 884                     fontSizer.getLeading() + fontSizer.getDescent()) / 2),
 885                     y + Math.round(height/2 - stringWidth/2));
 886         }
 887     }
 888 
 889 
 890     public Dimension getPreferredSize(JComponent c) {
 891         Dimension       size;
 892         Insets          border = progressBar.getInsets();
 893         FontMetrics     fontSizer = progressBar.getFontMetrics(
 894                                                   progressBar.getFont());
 895 
 896         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 897             size = new Dimension(getPreferredInnerHorizontal());
 898             // Ensure that the progress string will fit
 899             if (progressBar.isStringPainted()) {
 900                 // I'm doing this for completeness.
 901                 String progString = progressBar.getString();
 902                 int stringWidth = getTextUIDrawing().getStringWidth(
 903                           progressBar, fontSizer, progString);
 904                 if (stringWidth > size.width) {
 905                     size.width = stringWidth;
 906                 }
 907                 // This uses both Height and Descent to be sure that
 908                 // there is more than enough room in the progress bar
 909                 // for everything.
 910                 // This does have a strange dependency on
 911                 // getStringPlacememnt() in a funny way.
 912                 int stringHeight = fontSizer.getHeight() +
 913                                    fontSizer.getDescent();
 914                 if (stringHeight > size.height) {
 915                     size.height = stringHeight;
 916                 }
 917             }
 918         } else {
 919             size = new Dimension(getPreferredInnerVertical());
 920             // Ensure that the progress string will fit.
 921             if (progressBar.isStringPainted()) {
 922                 String progString = progressBar.getString();
 923                 int stringHeight = fontSizer.getHeight() +
 924                         fontSizer.getDescent();
 925                 if (stringHeight > size.width) {
 926                     size.width = stringHeight;
 927                 }
 928                 // This is also for completeness.
 929                 int stringWidth = getTextUIDrawing().getStringWidth(
 930                                        progressBar, fontSizer, progString);
 931                 if (stringWidth > size.height) {
 932                     size.height = stringWidth;
 933                 }
 934             }
 935         }
 936 
 937         size.width += border.left + border.right;
 938         size.height += border.top + border.bottom;
 939         return size;
 940     }
 941 
 942     /**
 943      * The Minimum size for this component is 10. The rationale here
 944      * is that there should be at least one pixel per 10 percent.
 945      */
 946     public Dimension getMinimumSize(JComponent c) {
 947         Dimension pref = getPreferredSize(progressBar);
 948         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 949             pref.width = 10;
 950         } else {
 951             pref.height = 10;
 952         }
 953         return pref;
 954     }
 955 
 956     public Dimension getMaximumSize(JComponent c) {
 957         Dimension pref = getPreferredSize(progressBar);
 958         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
 959             pref.width = Short.MAX_VALUE;
 960         } else {
 961             pref.height = Short.MAX_VALUE;
 962         }
 963         return pref;
 964     }
 965 
 966     /**
 967      * Gets the index of the current animation frame.
 968      *
 969      * @return the index of the current animation frame
 970      * @since 1.4
 971      */
 972     protected int getAnimationIndex() {
 973         return animationIndex;
 974     }
 975 
 976     /**
 977      * Returns the number of frames for the complete animation loop
 978      * used by an indeterminate JProgessBar. The progress chunk will go
 979      * from one end to the other and back during the entire loop. This
 980      * visual behavior may be changed by subclasses in other Look and Feels.
 981      *
 982      * @return the number of frames
 983      * @since 1.6
 984      */
 985     protected final int getFrameCount() {
 986         return numFrames;
 987     }
 988 
 989     /**
 990      * Sets the index of the current animation frame
 991      * to the specified value and requests that the
 992      * progress bar be repainted.
 993      * Subclasses that don't use the default painting code
 994      * might need to override this method
 995      * to change the way that the <code>repaint</code> method
 996      * is invoked.
 997      *
 998      * @param newValue the new animation index; no checking
 999      *                 is performed on its value
1000      * @see #incrementAnimationIndex
1001      *
1002      * @since 1.4
1003      */
1004     protected void setAnimationIndex(int newValue) {
1005         if (animationIndex != newValue) {
1006             if (sizeChanged()) {
1007                 animationIndex = newValue;
1008                 maxPosition = 0;  //needs to be recalculated
1009                 delta = 0.0;      //needs to be recalculated
1010                 progressBar.repaint();
1011                 return;
1012             }
1013 
1014             //Get the previous box drawn.
1015             nextPaintRect = getBox(nextPaintRect);
1016 
1017             //Update the frame number.
1018             animationIndex = newValue;
1019 
1020             //Get the next box to draw.
1021             if (nextPaintRect != null) {
1022                 boxRect = getBox(boxRect);
1023                 if (boxRect != null) {
1024                     nextPaintRect.add(boxRect);
1025                 }
1026             }
1027         } else { //animationIndex == newValue
1028             return;
1029         }
1030 
1031         if (nextPaintRect != null) {
1032             progressBar.repaint(nextPaintRect);
1033         } else {
1034             progressBar.repaint();
1035         }
1036     }
1037 
1038     private boolean sizeChanged() {
1039         if ((oldComponentInnards == null) || (componentInnards == null)) {
1040             return true;
1041         }
1042 
1043         oldComponentInnards.setRect(componentInnards);
1044         componentInnards = SwingUtilities.calculateInnerArea(progressBar,
1045                                                              componentInnards);
1046         return !oldComponentInnards.equals(componentInnards);
1047     }
1048 
1049     /**
1050      * Sets the index of the current animation frame,
1051      * to the next valid value,
1052      * which results in the progress bar being repainted.
1053      * The next valid value is, by default,
1054      * the current animation index plus one.
1055      * If the new value would be too large,
1056      * this method sets the index to 0.
1057      * Subclasses might need to override this method
1058      * to ensure that the index does not go over
1059      * the number of frames needed for the particular
1060      * progress bar instance.
1061      * This method is invoked by the default animation thread
1062      * every <em>X</em> milliseconds,
1063      * where <em>X</em> is specified by the "ProgressBar.repaintInterval"
1064      * UI default.
1065      *
1066      * @see #setAnimationIndex
1067      * @since 1.4
1068      */
1069     protected void incrementAnimationIndex() {
1070         int newValue = getAnimationIndex() + 1;
1071 
1072         if (newValue < numFrames) {
1073             setAnimationIndex(newValue);
1074         } else {
1075             setAnimationIndex(0);
1076         }
1077     }
1078 
1079     /**
1080      * Returns the desired number of milliseconds between repaints.
1081      * This value is meaningful
1082      * only if the progress bar is in indeterminate mode.
1083      * The repaint interval determines how often the
1084      * default animation thread's timer is fired.
1085      * It's also used by the default indeterminate progress bar
1086      * painting code when determining
1087      * how far to move the bouncing box per frame.
1088      * The repaint interval is specified by
1089      * the "ProgressBar.repaintInterval" UI default.
1090      *
1091      * @return  the repaint interval, in milliseconds
1092      */
1093     private int getRepaintInterval() {
1094         return repaintInterval;
1095     }
1096 
1097     private int initRepaintInterval() {
1098         repaintInterval = DefaultLookup.getInt(progressBar,
1099                 this, "ProgressBar.repaintInterval", 50);
1100         return repaintInterval;
1101     }
1102 
1103     /**
1104      * Returns the number of milliseconds per animation cycle.
1105      * This value is meaningful
1106      * only if the progress bar is in indeterminate mode.
1107      * The cycle time is used by the default indeterminate progress bar
1108      * painting code when determining
1109      * how far to move the bouncing box per frame.
1110      * The cycle time is specified by
1111      * the "ProgressBar.cycleTime" UI default
1112      * and adjusted, if necessary,
1113      * by the initIndeterminateDefaults method.
1114      *
1115      * @return  the cycle time, in milliseconds
1116      */
1117     private int getCycleTime() {
1118         return cycleTime;
1119     }
1120 
1121     private int initCycleTime() {
1122         cycleTime = DefaultLookup.getInt(progressBar, this,
1123                 "ProgressBar.cycleTime", 3000);
1124         return cycleTime;
1125     }
1126 
1127 
1128     /** Initialize cycleTime, repaintInterval, numFrames, animationIndex. */
1129     private void initIndeterminateDefaults() {
1130         initRepaintInterval(); //initialize repaint interval
1131         initCycleTime();       //initialize cycle length
1132 
1133         // Make sure repaintInterval is reasonable.
1134         if (repaintInterval <= 0) {
1135             repaintInterval = 100;
1136         }
1137 
1138         // Make sure cycleTime is reasonable.
1139         if (repaintInterval > cycleTime) {
1140             cycleTime = repaintInterval * 20;
1141         } else {
1142             // Force cycleTime to be a even multiple of repaintInterval.
1143             int factor = (int)Math.ceil(
1144                                  ((double)cycleTime)
1145                                / ((double)repaintInterval*2));
1146             cycleTime = repaintInterval*factor*2;
1147         }
1148     }
1149 
1150     /**
1151      * Invoked by PropertyChangeHandler.
1152      *
1153      *  NOTE: This might not be invoked until after the first
1154      *  paintIndeterminate call.
1155      */
1156     private void initIndeterminateValues() {
1157         initIndeterminateDefaults();
1158         //assert cycleTime/repaintInterval is a whole multiple of 2.
1159         numFrames = cycleTime/repaintInterval;
1160         initAnimationIndex();
1161 
1162         boxRect = new Rectangle();
1163         nextPaintRect = new Rectangle();
1164         componentInnards = new Rectangle();
1165         oldComponentInnards = new Rectangle();
1166 
1167         // we only bother installing the HierarchyChangeListener if we
1168         // are indeterminate
1169         progressBar.addHierarchyListener(getHandler());
1170 
1171         // start the animation thread if necessary
1172         if (progressBar.isDisplayable()) {
1173             startAnimationTimer();
1174         }
1175     }
1176 
1177     /** Invoked by PropertyChangeHandler. */
1178     private void cleanUpIndeterminateValues() {
1179         // stop the animation thread if necessary
1180         if (progressBar.isDisplayable()) {
1181             stopAnimationTimer();
1182         }
1183 
1184         cycleTime = repaintInterval = 0;
1185         numFrames = animationIndex = 0;
1186         maxPosition = 0;
1187         delta = 0.0;
1188 
1189         boxRect = nextPaintRect = null;
1190         componentInnards = oldComponentInnards = null;
1191 
1192         progressBar.removeHierarchyListener(getHandler());
1193     }
1194 
1195     // Called from initIndeterminateValues to initialize the animation index.
1196     // This assumes that numFrames is set to a correct value.
1197     private void initAnimationIndex() {
1198         if ((progressBar.getOrientation() == JProgressBar.HORIZONTAL) &&
1199             (BasicGraphicsUtils.isLeftToRight(progressBar))) {
1200             // If this is a left-to-right progress bar,
1201             // start at the first frame.
1202             setAnimationIndex(0);
1203         } else {
1204             // If we go right-to-left or vertically, start at the right/bottom.
1205             setAnimationIndex(numFrames/2);
1206         }
1207     }
1208 
1209     //
1210     // Animation Thread
1211     //
1212     /**
1213      * Implements an animation thread that invokes repaint
1214      * at a fixed rate.  If ADJUSTTIMER is true, this thread
1215      * will continuously adjust the repaint interval to
1216      * try to make the actual time between repaints match
1217      * the requested rate.
1218      */
1219     private class Animator implements ActionListener {
1220         private Timer timer;
1221         private long previousDelay; //used to tune the repaint interval
1222         private int interval; //the fixed repaint interval
1223         private long lastCall; //the last time actionPerformed was called
1224         private int MINIMUM_DELAY = 5;
1225 
1226         /**
1227          * Creates a timer if one doesn't already exist,
1228          * then starts the timer thread.
1229          */
1230         private void start(int interval) {
1231             previousDelay = interval;
1232             lastCall = 0;
1233 
1234             if (timer == null) {
1235                 timer = new Timer(interval, this);
1236             } else {
1237                 timer.setDelay(interval);
1238             }
1239 
1240             if (ADJUSTTIMER) {
1241                 timer.setRepeats(false);
1242                 timer.setCoalesce(false);
1243             }
1244 
1245             timer.start();
1246         }
1247 
1248         /**
1249          * Stops the timer thread.
1250          */
1251         private void stop() {
1252             timer.stop();
1253         }
1254 
1255         /**
1256          * Reacts to the timer's action events.
1257          */
1258         public void actionPerformed(ActionEvent e) {
1259             if (ADJUSTTIMER) {
1260                 long time = System.currentTimeMillis();
1261 
1262                 if (lastCall > 0) { //adjust nextDelay
1263                 //XXX maybe should cache this after a while
1264                     //actual = time - lastCall
1265                     //difference = actual - interval
1266                     //nextDelay = previousDelay - difference
1267                     //          = previousDelay - (time - lastCall - interval)
1268                    int nextDelay = (int)(previousDelay
1269                                           - time + lastCall
1270                                           + getRepaintInterval());
1271                     if (nextDelay < MINIMUM_DELAY) {
1272                         nextDelay = MINIMUM_DELAY;
1273                     }
1274                     timer.setInitialDelay(nextDelay);
1275                     previousDelay = nextDelay;
1276                 }
1277                 timer.start();
1278                 lastCall = time;
1279             }
1280 
1281             incrementAnimationIndex(); //paint next frame
1282         }
1283     }
1284 
1285 
1286     /**
1287      * This class should be treated as a &quot;protected&quot; inner class.
1288      * Instantiate it only within subclasses of {@code BasicProgressBarUI}.
1289      */
1290     public class ChangeHandler implements ChangeListener {
1291         // NOTE: This class exists only for backward compatibility. All
1292         // its functionality has been moved into Handler. If you need to add
1293         // new functionality add it to the Handler, but make sure this
1294         // class calls into the Handler.
1295         public void stateChanged(ChangeEvent e) {
1296             getHandler().stateChanged(e);
1297         }
1298     }
1299 
1300 
1301     private class Handler implements ChangeListener, PropertyChangeListener, HierarchyListener {
1302         // ChangeListener
1303         public void stateChanged(ChangeEvent e) {
1304             BoundedRangeModel model = progressBar.getModel();
1305             int newRange = model.getMaximum() - model.getMinimum();
1306             int newPercent;
1307             int oldPercent = getCachedPercent();
1308 
1309             if (newRange > 0) {
1310                 newPercent = (int)((100 * (long)model.getValue()) / newRange);
1311             } else {
1312                 newPercent = 0;
1313             }
1314 
1315             if (newPercent != oldPercent) {
1316                 setCachedPercent(newPercent);
1317                 progressBar.repaint();
1318             }
1319         }
1320 
1321         // PropertyChangeListener
1322         public void propertyChange(PropertyChangeEvent e) {
1323             String prop = e.getPropertyName();
1324             if ("indeterminate" == prop) {
1325                 if (progressBar.isIndeterminate()) {
1326                     initIndeterminateValues();
1327                 } else {
1328                     //clean up
1329                     cleanUpIndeterminateValues();
1330                 }
1331                 progressBar.repaint();
1332             }
1333         }
1334 
1335         // we don't want the animation to keep running if we're not displayable
1336         public void hierarchyChanged(HierarchyEvent he) {
1337             if ((he.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
1338                 if (progressBar.isIndeterminate()) {
1339                     if (progressBar.isDisplayable()) {
1340                         startAnimationTimer();
1341                     } else {
1342                         stopAnimationTimer();
1343                     }
1344                 }
1345             }
1346         }
1347     }
1348 }