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