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