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      */
createUI(JComponent x)142     public static ComponentUI createUI(JComponent x) {
143         return new BasicProgressBarUI();
144     }
145 
installUI(JComponent c)146     public void installUI(JComponent c) {
147         progressBar = (JProgressBar)c;
148         installDefaults();
149         installListeners();
150         if (progressBar.isIndeterminate()) {
151             initIndeterminateValues();
152         }
153     }
154 
uninstallUI(JComponent c)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      */
installDefaults()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      */
uninstallDefaults()184     protected void uninstallDefaults() {
185         LookAndFeel.uninstallBorder(progressBar);
186     }
187 
188     /**
189      * Registers listeners.
190      */
installListeners()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 
getHandler()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      */
startAnimationTimer()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      */
stopAnimationTimer()247     protected void stopAnimationTimer() {
248         if (animator != null) {
249             animator.stop();
250         }
251     }
252 
253     /**
254      * Removes all listeners installed by this object.
255      */
uninstallListeners()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      */
getBaseline(JComponent c, int width, int height)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      */
getBaselineResizeBehavior( JComponent c)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      */
getPreferredInnerHorizontal()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      */
getPreferredInnerVertical()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      */
getSelectionForeground()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      */
getSelectionBackground()358     protected Color getSelectionBackground() {
359         return selectionBackground;
360     }
361 
getCachedPercent()362     private int getCachedPercent() {
363         return cachedPercent;
364     }
365 
setCachedPercent(int cachedPercent)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      */
getCellLength()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      */
setCellLength(int cellLen)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      */
getCellSpacing()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      */
setCellSpacing(int cellSpace)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      */
getAmountFull(Insets b, int width, int height)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      */
paint(Graphics g, JComponent c)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      */
getBox(Rectangle r)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      */
updateSizes()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      */
getGenericBox(Rectangle r)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      */
getBoxLength(int availableLength, int otherDimension)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      */
paintIndeterminate(Graphics g, JComponent c)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      */
paintDeterminate(Graphics g, JComponent c)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      */
paintString(Graphics g, int x, int y, int width, int height, int amountFull, Insets b)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      */
paintString(Graphics g, int x, int y, int width, int height, int fillStart, int amountFull, Insets b)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             SwingUtilities2.drawString(progressBar, g2, progressString,
831                                        renderLocation.x, renderLocation.y);
832             g2.setColor(getSelectionForeground());
833             g2.clipRect(fillStart, y, amountFull, height);
834             SwingUtilities2.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             SwingUtilities2.drawString(progressBar, g2, progressString,
844                                        renderLocation.x, renderLocation.y);
845             g2.setColor(getSelectionForeground());
846             g2.clipRect(x, fillStart, width, amountFull);
847             SwingUtilities2.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      */
getStringPlacement(Graphics g, String progressString, int x,int y,int width,int height)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 = SwingUtilities2.stringWidth(progressBar, fontSizer,
874                                                       progressString);
875 
876         if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
877             return new Point(x + (int)Math.round(width/2.0 - stringWidth/2.0),
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 + (int)Math.round(height/2.0 - stringWidth/2.0));
886         }
887     }
888 
889 
getPreferredSize(JComponent c)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 = SwingUtilities2.stringWidth(
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 = SwingUtilities2.stringWidth(
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      */
getMinimumSize(JComponent c)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 
getMaximumSize(JComponent c)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      */
getAnimationIndex()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      */
getFrameCount()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      */
setAnimationIndex(int newValue)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 
sizeChanged()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      */
incrementAnimationIndex()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      */
getRepaintInterval()1093     private int getRepaintInterval() {
1094         return repaintInterval;
1095     }
1096 
initRepaintInterval()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      */
getCycleTime()1117     private int getCycleTime() {
1118         return cycleTime;
1119     }
1120 
initCycleTime()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. */
initIndeterminateDefaults()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      */
initIndeterminateValues()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. */
cleanUpIndeterminateValues()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.
initAnimationIndex()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          */
start(int interval)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          */
stop()1251         private void stop() {
1252             timer.stop();
1253         }
1254 
1255         /**
1256          * Reacts to the timer's action events.
1257          */
actionPerformed(ActionEvent e)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.
stateChanged(ChangeEvent e)1295         public void stateChanged(ChangeEvent e) {
1296             getHandler().stateChanged(e);
1297         }
1298     }
1299 
1300 
1301     private class Handler implements ChangeListener, PropertyChangeListener, HierarchyListener {
1302         // ChangeListener
stateChanged(ChangeEvent e)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
propertyChange(PropertyChangeEvent e)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
hierarchyChanged(HierarchyEvent he)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 }
1349