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