0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package javax.swing.plaf.basic;
0027
0028 import sun.swing.SwingUtilities2;
0029 import java.awt.*;
0030 import java.awt.geom.AffineTransform;
0031 import java.awt.event.*;
0032 import javax.swing.*;
0033 import javax.swing.event.*;
0034 import javax.swing.plaf.*;
0035 import java.beans.PropertyChangeListener;
0036 import java.beans.PropertyChangeEvent;
0037 import java.io.Serializable;
0038 import sun.swing.DefaultLookup;
0039
0040 /**
0041 * A Basic L&F implementation of ProgressBarUI.
0042 *
0043 * @version 1.80 05/05/07
0044 * @author Michael C. Albers
0045 * @author Kathy Walrath
0046 */
0047 public class BasicProgressBarUI extends ProgressBarUI {
0048 private int cachedPercent;
0049 private int cellLength, cellSpacing;
0050 // The "selectionForeground" is the color of the text when it is painted
0051 // over a filled area of the progress bar. The "selectionBackground"
0052 // is for the text over the unfilled progress bar area.
0053 private Color selectionForeground, selectionBackground;
0054
0055 private Animator animator;
0056
0057 protected JProgressBar progressBar;
0058 protected ChangeListener changeListener;
0059 private Handler handler;
0060
0061 /**
0062 * The current state of the indeterminate animation's cycle.
0063 * 0, the initial value, means paint the first frame.
0064 * When the progress bar is indeterminate and showing,
0065 * the default animation thread updates this variable
0066 * by invoking incrementAnimationIndex()
0067 * every repaintInterval milliseconds.
0068 */
0069 private int animationIndex = 0;
0070
0071 /**
0072 * The number of frames per cycle. Under the default implementation,
0073 * this depends on the cycleTime and repaintInterval. It
0074 * must be an even number for the default painting algorithm. This
0075 * value is set in the initIndeterminateValues method.
0076 */
0077 private int numFrames; //0 1|numFrames-1 ... numFrames/2
0078
0079 /**
0080 * Interval (in ms) between repaints of the indeterminate progress bar.
0081 * The value of this method is set
0082 * (every time the progress bar changes to indeterminate mode)
0083 * using the
0084 * "ProgressBar.repaintInterval" key in the defaults table.
0085 */
0086 private int repaintInterval;
0087
0088 /**
0089 * The number of milliseconds until the animation cycle repeats.
0090 * The value of this method is set
0091 * (every time the progress bar changes to indeterminate mode)
0092 * using the
0093 * "ProgressBar.cycleTime" key in the defaults table.
0094 */
0095 private int cycleTime; //must be repaintInterval*2*aPositiveInteger
0096
0097 //performance stuff
0098 private static boolean ADJUSTTIMER = true; //makes a BIG difference;
0099 //make this false for
0100 //performance tests
0101
0102 /**
0103 * Used to hold the location and size of the bouncing box (returned
0104 * by getBox) to be painted.
0105 *
0106 * @since 1.5
0107 */
0108 protected Rectangle boxRect;
0109
0110 /**
0111 * The rectangle to be updated the next time the
0112 * animation thread calls repaint. For bouncing-box
0113 * animation this rect should include the union of
0114 * the currently displayed box (which needs to be erased)
0115 * and the box to be displayed next.
0116 * This rectangle's values are set in
0117 * the setAnimationIndex method.
0118 */
0119 private Rectangle nextPaintRect;
0120
0121 //cache
0122 /** The component's painting area, not including the border. */
0123 private Rectangle componentInnards; //the current painting area
0124 private Rectangle oldComponentInnards; //used to see if the size changed
0125
0126 /** For bouncing-box animation, the change in position per frame. */
0127 private double delta = 0.0;
0128
0129 private int maxPosition = 0; //maximum X (horiz) or Y box location
0130
0131 public static ComponentUI createUI(JComponent x) {
0132 return new BasicProgressBarUI();
0133 }
0134
0135 public void installUI(JComponent c) {
0136 progressBar = (JProgressBar) c;
0137 installDefaults();
0138 installListeners();
0139 if (progressBar.isIndeterminate()) {
0140 initIndeterminateValues();
0141 }
0142 }
0143
0144 public void uninstallUI(JComponent c) {
0145 if (progressBar.isIndeterminate()) {
0146 cleanUpIndeterminateValues();
0147 }
0148 uninstallDefaults();
0149 uninstallListeners();
0150 progressBar = null;
0151 }
0152
0153 protected void installDefaults() {
0154 LookAndFeel
0155 .installProperty(progressBar, "opaque", Boolean.TRUE);
0156 LookAndFeel.installBorder(progressBar, "ProgressBar.border");
0157 LookAndFeel.installColorsAndFont(progressBar,
0158 "ProgressBar.background", "ProgressBar.foreground",
0159 "ProgressBar.font");
0160 cellLength = UIManager.getInt("ProgressBar.cellLength");
0161 cellSpacing = UIManager.getInt("ProgressBar.cellSpacing");
0162 selectionForeground = UIManager
0163 .getColor("ProgressBar.selectionForeground");
0164 selectionBackground = UIManager
0165 .getColor("ProgressBar.selectionBackground");
0166 }
0167
0168 protected void uninstallDefaults() {
0169 LookAndFeel.uninstallBorder(progressBar);
0170 }
0171
0172 protected void installListeners() {
0173 //Listen for changes in the progress bar's data.
0174 changeListener = getHandler();
0175 progressBar.addChangeListener(changeListener);
0176
0177 //Listen for changes between determinate and indeterminate state.
0178 progressBar.addPropertyChangeListener(getHandler());
0179 }
0180
0181 private Handler getHandler() {
0182 if (handler == null) {
0183 handler = new Handler();
0184 }
0185 return handler;
0186 }
0187
0188 /**
0189 * Starts the animation thread, creating and initializing
0190 * it if necessary. This method is invoked when an
0191 * indeterminate progress bar should start animating.
0192 * Reasons for this may include:
0193 * <ul>
0194 * <li>The progress bar is determinate and becomes displayable
0195 * <li>The progress bar is displayable and becomes determinate
0196 * <li>The progress bar is displayable and determinate and this
0197 * UI is installed
0198 * </ul>
0199 * If you implement your own animation thread,
0200 * you must override this method.
0201 *
0202 * @since 1.4
0203 * @see #stopAnimationTimer
0204 */
0205 protected void startAnimationTimer() {
0206 if (animator == null) {
0207 animator = new Animator();
0208 }
0209
0210 animator.start(getRepaintInterval());
0211 }
0212
0213 /**
0214 * Stops the animation thread.
0215 * This method is invoked when the indeterminate
0216 * animation should be stopped. Reasons for this may include:
0217 * <ul>
0218 * <li>The progress bar changes to determinate
0219 * <li>The progress bar is no longer part of a displayable hierarchy
0220 * <li>This UI in uninstalled
0221 * </ul>
0222 * If you implement your own animation thread,
0223 * you must override this method.
0224 *
0225 * @since 1.4
0226 * @see #startAnimationTimer
0227 */
0228 protected void stopAnimationTimer() {
0229 if (animator != null) {
0230 animator.stop();
0231 }
0232 }
0233
0234 /**
0235 * Removes all listeners installed by this object.
0236 */
0237 protected void uninstallListeners() {
0238 progressBar.removeChangeListener(changeListener);
0239 progressBar.removePropertyChangeListener(getHandler());
0240 handler = null;
0241 }
0242
0243 /**
0244 * Returns the baseline.
0245 *
0246 * @throws NullPointerException {@inheritDoc}
0247 * @throws IllegalArgumentException {@inheritDoc}
0248 * @see javax.swing.JComponent#getBaseline(int, int)
0249 * @since 1.6
0250 */
0251 public int getBaseline(JComponent c, int width, int height) {
0252 super .getBaseline(c, width, height);
0253 if (progressBar.isStringPainted()
0254 && progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0255 FontMetrics metrics = progressBar
0256 .getFontMetrics(progressBar.getFont());
0257 Insets insets = progressBar.getInsets();
0258 int y = insets.top;
0259 height = height - insets.top - insets.bottom;
0260 return y
0261 + (height + metrics.getAscent()
0262 - metrics.getLeading() - metrics
0263 .getDescent()) / 2;
0264 }
0265 return -1;
0266 }
0267
0268 /**
0269 * Returns an enum indicating how the baseline of the component
0270 * changes as the size changes.
0271 *
0272 * @throws NullPointerException {@inheritDoc}
0273 * @see javax.swing.JComponent#getBaseline(int, int)
0274 * @since 1.6
0275 */
0276 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
0277 JComponent c) {
0278 super .getBaselineResizeBehavior(c);
0279 if (progressBar.isStringPainted()
0280 && progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0281 return Component.BaselineResizeBehavior.CENTER_OFFSET;
0282 }
0283 return Component.BaselineResizeBehavior.OTHER;
0284 }
0285
0286 // Many of the Basic*UI components have the following methods.
0287 // This component does not have these methods because *ProgressBarUI
0288 // is not a compound component and does not accept input.
0289 //
0290 // protected void installComponents()
0291 // protected void uninstallComponents()
0292 // protected void installKeyboardActions()
0293 // protected void uninstallKeyboardActions()
0294
0295 protected Dimension getPreferredInnerHorizontal() {
0296 Dimension horizDim = (Dimension) DefaultLookup.get(progressBar,
0297 this , "ProgressBar.horizontalSize");
0298 if (horizDim == null) {
0299 horizDim = new Dimension(146, 12);
0300 }
0301 return horizDim;
0302 }
0303
0304 protected Dimension getPreferredInnerVertical() {
0305 Dimension vertDim = (Dimension) DefaultLookup.get(progressBar,
0306 this , "ProgressBar.verticalSize");
0307 if (vertDim == null) {
0308 vertDim = new Dimension(12, 146);
0309 }
0310 return vertDim;
0311 }
0312
0313 /**
0314 * The "selectionForeground" is the color of the text when it is painted
0315 * over a filled area of the progress bar.
0316 */
0317 protected Color getSelectionForeground() {
0318 return selectionForeground;
0319 }
0320
0321 /**
0322 * The "selectionBackground" is the color of the text when it is painted
0323 * over an unfilled area of the progress bar.
0324 */
0325 protected Color getSelectionBackground() {
0326 return selectionBackground;
0327 }
0328
0329 private int getCachedPercent() {
0330 return cachedPercent;
0331 }
0332
0333 private void setCachedPercent(int cachedPercent) {
0334 this .cachedPercent = cachedPercent;
0335 }
0336
0337 /**
0338 * Returns the width (if HORIZONTAL) or height (if VERTICAL)
0339 * of each of the indivdual cells/units to be rendered in the
0340 * progress bar. However, for text rendering simplification and
0341 * aesthetic considerations, this function will return 1 when
0342 * the progress string is being rendered.
0343 *
0344 * @return the value representing the spacing between cells
0345 * @see #setCellLength
0346 * @see JProgressBar#isStringPainted
0347 */
0348 protected int getCellLength() {
0349 if (progressBar.isStringPainted()) {
0350 return 1;
0351 } else {
0352 return cellLength;
0353 }
0354 }
0355
0356 protected void setCellLength(int cellLen) {
0357 this .cellLength = cellLen;
0358 }
0359
0360 /**
0361 * Returns the spacing between each of the cells/units in the
0362 * progress bar. However, for text rendering simplification and
0363 * aesthetic considerations, this function will return 0 when
0364 * the progress string is being rendered.
0365 *
0366 * @return the value representing the spacing between cells
0367 * @see #setCellSpacing
0368 * @see JProgressBar#isStringPainted
0369 */
0370 protected int getCellSpacing() {
0371 if (progressBar.isStringPainted()) {
0372 return 0;
0373 } else {
0374 return cellSpacing;
0375 }
0376 }
0377
0378 protected void setCellSpacing(int cellSpace) {
0379 this .cellSpacing = cellSpace;
0380 }
0381
0382 /**
0383 * This determines the amount of the progress bar that should be filled
0384 * based on the percent done gathered from the model. This is a common
0385 * operation so it was abstracted out. It assumes that your progress bar
0386 * is linear. That is, if you are making a circular progress indicator,
0387 * you will want to override this method.
0388 */
0389 protected int getAmountFull(Insets b, int width, int height) {
0390 int amountFull = 0;
0391 BoundedRangeModel model = progressBar.getModel();
0392
0393 if ((model.getMaximum() - model.getMinimum()) != 0) {
0394 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0395 amountFull = (int) Math.round(width
0396 * progressBar.getPercentComplete());
0397 } else {
0398 amountFull = (int) Math.round(height
0399 * progressBar.getPercentComplete());
0400 }
0401 }
0402 return amountFull;
0403 }
0404
0405 /**
0406 * Delegates painting to one of two methods:
0407 * paintDeterminate or paintIndeterminate.
0408 */
0409 public void paint(Graphics g, JComponent c) {
0410 if (progressBar.isIndeterminate()) {
0411 paintIndeterminate(g, c);
0412 } else {
0413 paintDeterminate(g, c);
0414 }
0415 }
0416
0417 /**
0418 * Stores the position and size of
0419 * the bouncing box that would be painted for the current animation index
0420 * in <code>r</code> and returns <code>r</code>.
0421 * Subclasses that add to the painting performed
0422 * in this class's implementation of <code>paintIndeterminate</code> --
0423 * to draw an outline around the bouncing box, for example --
0424 * can use this method to get the location of the bouncing
0425 * box that was just painted.
0426 * By overriding this method,
0427 * you have complete control over the size and position
0428 * of the bouncing box,
0429 * without having to reimplement <code>paintIndeterminate</code>.
0430 *
0431 * @param r the Rectangle instance to be modified;
0432 * may be <code>null</code>
0433 * @return <code>null</code> if no box should be drawn;
0434 * otherwise, returns the passed-in rectangle
0435 * (if non-null)
0436 * or a new rectangle
0437 *
0438 * @see #setAnimationIndex
0439 * @since 1.4
0440 */
0441 protected Rectangle getBox(Rectangle r) {
0442 int currentFrame = getAnimationIndex();
0443 int middleFrame = numFrames / 2;
0444
0445 if (sizeChanged() || delta == 0.0 || maxPosition == 0.0) {
0446 updateSizes();
0447 }
0448
0449 r = getGenericBox(r);
0450
0451 if (r == null) {
0452 return null;
0453 }
0454 if (middleFrame <= 0) {
0455 return null;
0456 }
0457
0458 //assert currentFrame >= 0 && currentFrame < numFrames
0459 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0460 if (currentFrame < middleFrame) {
0461 r.x = componentInnards.x
0462 + (int) Math.round(delta
0463 * (double) currentFrame);
0464 } else {
0465 r.x = maxPosition
0466 - (int) Math.round(delta
0467 * (currentFrame - middleFrame));
0468 }
0469 } else { //VERTICAL indeterminate progress bar
0470 if (currentFrame < middleFrame) {
0471 r.y = componentInnards.y
0472 + (int) Math.round(delta * currentFrame);
0473 } else {
0474 r.y = maxPosition
0475 - (int) Math.round(delta
0476 * (currentFrame - middleFrame));
0477 }
0478 }
0479 return r;
0480 }
0481
0482 /**
0483 * Updates delta, max position.
0484 * Assumes componentInnards is correct (e.g. call after sizeChanged()).
0485 */
0486 private void updateSizes() {
0487 int length = 0;
0488
0489 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0490 length = getBoxLength(componentInnards.width,
0491 componentInnards.height);
0492 maxPosition = componentInnards.x + componentInnards.width
0493 - length;
0494
0495 } else { //VERTICAL progress bar
0496 length = getBoxLength(componentInnards.height,
0497 componentInnards.width);
0498 maxPosition = componentInnards.y + componentInnards.height
0499 - length;
0500 }
0501
0502 //If we're doing bouncing-box animation, update delta.
0503 delta = 2.0 * (double) maxPosition / (double) numFrames;
0504 }
0505
0506 /**
0507 * Assumes that the component innards, max position, etc. are up-to-date.
0508 */
0509 private Rectangle getGenericBox(Rectangle r) {
0510 if (r == null) {
0511 r = new Rectangle();
0512 }
0513
0514 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0515 r.width = getBoxLength(componentInnards.width,
0516 componentInnards.height);
0517 if (r.width < 0) {
0518 r = null;
0519 } else {
0520 r.height = componentInnards.height;
0521 r.y = componentInnards.y;
0522 }
0523 // end of HORIZONTAL
0524
0525 } else { //VERTICAL progress bar
0526 r.height = getBoxLength(componentInnards.height,
0527 componentInnards.width);
0528 if (r.height < 0) {
0529 r = null;
0530 } else {
0531 r.width = componentInnards.width;
0532 r.x = componentInnards.x;
0533 }
0534 } // end of VERTICAL
0535
0536 return r;
0537 }
0538
0539 /**
0540 * Returns the length
0541 * of the "bouncing box" to be painted.
0542 * This method is invoked by the
0543 * default implementation of <code>paintIndeterminate</code>
0544 * to get the width (if the progress bar is horizontal)
0545 * or height (if vertical) of the box.
0546 * For example:
0547 * <blockquote>
0548 * <pre>
0549 *boxRect.width = getBoxLength(componentInnards.width,
0550 * componentInnards.height);
0551 * </pre>
0552 * </blockquote>
0553 *
0554 * @param availableLength the amount of space available
0555 * for the bouncing box to move in;
0556 * for a horizontal progress bar,
0557 * for example,
0558 * this should be
0559 * the inside width of the progress bar
0560 * (the component width minus borders)
0561 * @param otherDimension for a horizontal progress bar, this should be
0562 * the inside height of the progress bar; this
0563 * value might be used to constrain or determine
0564 * the return value
0565 *
0566 * @return the size of the box dimension being determined;
0567 * must be no larger than <code>availableLength</code>
0568 *
0569 * @see javax.swing.SwingUtilities#calculateInnerArea
0570 * @since 1.5
0571 */
0572 protected int getBoxLength(int availableLength, int otherDimension) {
0573 return (int) Math.round(availableLength / 6.0);
0574 }
0575
0576 /**
0577 * All purpose paint method that should do the right thing for all
0578 * linear bouncing-box progress bars.
0579 * Override this if you are making another kind of
0580 * progress bar.
0581 *
0582 * @see #paintDeterminate
0583 *
0584 * @since 1.4
0585 */
0586 protected void paintIndeterminate(Graphics g, JComponent c) {
0587 if (!(g instanceof Graphics2D)) {
0588 return;
0589 }
0590
0591 Insets b = progressBar.getInsets(); // area for border
0592 int barRectWidth = progressBar.getWidth() - (b.right + b.left);
0593 int barRectHeight = progressBar.getHeight()
0594 - (b.top + b.bottom);
0595
0596 if (barRectWidth <= 0 || barRectHeight <= 0) {
0597 return;
0598 }
0599
0600 Graphics2D g2 = (Graphics2D) g;
0601
0602 // Paint the bouncing box.
0603 boxRect = getBox(boxRect);
0604 if (boxRect != null) {
0605 g2.setColor(progressBar.getForeground());
0606 g2.fillRect(boxRect.x, boxRect.y, boxRect.width,
0607 boxRect.height);
0608 }
0609
0610 // Deal with possible text painting
0611 if (progressBar.isStringPainted()) {
0612 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0613 paintString(g2, b.left, b.top, barRectWidth,
0614 barRectHeight, boxRect.x, boxRect.width, b);
0615 } else {
0616 paintString(g2, b.left, b.top, barRectWidth,
0617 barRectHeight, boxRect.y, boxRect.height, b);
0618 }
0619 }
0620 }
0621
0622 /**
0623 * All purpose paint method that should do the right thing for almost
0624 * all linear, determinate progress bars. By setting a few values in
0625 * the defaults
0626 * table, things should work just fine to paint your progress bar.
0627 * Naturally, override this if you are making a circular or
0628 * semi-circular progress bar.
0629 *
0630 * @see #paintIndeterminate
0631 *
0632 * @since 1.4
0633 */
0634 protected void paintDeterminate(Graphics g, JComponent c) {
0635 if (!(g instanceof Graphics2D)) {
0636 return;
0637 }
0638
0639 Insets b = progressBar.getInsets(); // area for border
0640 int barRectWidth = progressBar.getWidth() - (b.right + b.left);
0641 int barRectHeight = progressBar.getHeight()
0642 - (b.top + b.bottom);
0643
0644 if (barRectWidth <= 0 || barRectHeight <= 0) {
0645 return;
0646 }
0647
0648 int cellLength = getCellLength();
0649 int cellSpacing = getCellSpacing();
0650 // amount of progress to draw
0651 int amountFull = getAmountFull(b, barRectWidth, barRectHeight);
0652
0653 Graphics2D g2 = (Graphics2D) g;
0654 g2.setColor(progressBar.getForeground());
0655
0656 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0657 // draw the cells
0658 if (cellSpacing == 0 && amountFull > 0) {
0659 // draw one big Rect because there is no space between cells
0660 g2.setStroke(new BasicStroke((float) barRectHeight,
0661 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
0662 } else {
0663 // draw each individual cell
0664 g2.setStroke(new BasicStroke((float) barRectHeight,
0665 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
0666 0.f, new float[] { cellLength, cellSpacing },
0667 0.f));
0668 }
0669
0670 if (BasicGraphicsUtils.isLeftToRight(c)) {
0671 g2.drawLine(b.left, (barRectHeight / 2) + b.top,
0672 amountFull + b.left, (barRectHeight / 2)
0673 + b.top);
0674 } else {
0675 g2.drawLine((barRectWidth + b.left),
0676 (barRectHeight / 2) + b.top, barRectWidth
0677 + b.left - amountFull,
0678 (barRectHeight / 2) + b.top);
0679 }
0680
0681 } else { // VERTICAL
0682 // draw the cells
0683 if (cellSpacing == 0 && amountFull > 0) {
0684 // draw one big Rect because there is no space between cells
0685 g2.setStroke(new BasicStroke((float) barRectWidth,
0686 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
0687 } else {
0688 // draw each individual cell
0689 g2
0690 .setStroke(new BasicStroke(
0691 (float) barRectWidth,
0692 BasicStroke.CAP_BUTT,
0693 BasicStroke.JOIN_BEVEL,
0694 0f,
0695 new float[] { cellLength, cellSpacing },
0696 0f));
0697 }
0698
0699 g2.drawLine(barRectWidth / 2 + b.left, b.top
0700 + barRectHeight, barRectWidth / 2 + b.left, b.top
0701 + barRectHeight - amountFull);
0702 }
0703
0704 // Deal with possible text painting
0705 if (progressBar.isStringPainted()) {
0706 paintString(g, b.left, b.top, barRectWidth, barRectHeight,
0707 amountFull, b);
0708 }
0709 }
0710
0711 protected void paintString(Graphics g, int x, int y, int width,
0712 int height, int amountFull, Insets b) {
0713 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0714 if (BasicGraphicsUtils.isLeftToRight(progressBar)) {
0715 if (progressBar.isIndeterminate()) {
0716 boxRect = getBox(boxRect);
0717 paintString(g, x, y, width, height, boxRect.x,
0718 boxRect.width, b);
0719 } else {
0720 paintString(g, x, y, width, height, x, amountFull,
0721 b);
0722 }
0723 } else {
0724 paintString(g, x, y, width, height, x + width
0725 - amountFull, amountFull, b);
0726 }
0727 } else {
0728 if (progressBar.isIndeterminate()) {
0729 boxRect = getBox(boxRect);
0730 paintString(g, x, y, width, height, boxRect.y,
0731 boxRect.height, b);
0732 } else {
0733 paintString(g, x, y, width, height, y + height
0734 - amountFull, amountFull, b);
0735 }
0736 }
0737 }
0738
0739 /**
0740 * Paints the progress string.
0741 *
0742 * @param g Graphics used for drawing.
0743 * @param x x location of bounding box
0744 * @param y y location of bounding box
0745 * @param width width of bounding box
0746 * @param height height of bounding box
0747 * @param fillStart start location, in x or y depending on orientation,
0748 * of the filled portion of the progress bar.
0749 * @param amountFull size of the fill region, either width or height
0750 * depending upon orientation.
0751 * @param b Insets of the progress bar.
0752 */
0753 private void paintString(Graphics g, int x, int y, int width,
0754 int height, int fillStart, int amountFull, Insets b) {
0755 if (!(g instanceof Graphics2D)) {
0756 return;
0757 }
0758
0759 Graphics2D g2 = (Graphics2D) g;
0760 String progressString = progressBar.getString();
0761 g2.setFont(progressBar.getFont());
0762 Point renderLocation = getStringPlacement(g2, progressString,
0763 x, y, width, height);
0764 Rectangle oldClip = g2.getClipBounds();
0765
0766 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0767 g2.setColor(getSelectionBackground());
0768 SwingUtilities2.drawString(progressBar, g2, progressString,
0769 renderLocation.x, renderLocation.y);
0770 g2.setColor(getSelectionForeground());
0771 g2.clipRect(fillStart, y, amountFull, height);
0772 SwingUtilities2.drawString(progressBar, g2, progressString,
0773 renderLocation.x, renderLocation.y);
0774 } else { // VERTICAL
0775 g2.setColor(getSelectionBackground());
0776 AffineTransform rotate = AffineTransform
0777 .getRotateInstance(Math.PI / 2);
0778 g2.setFont(progressBar.getFont().deriveFont(rotate));
0779 renderLocation = getStringPlacement(g2, progressString, x,
0780 y, width, height);
0781 SwingUtilities2.drawString(progressBar, g2, progressString,
0782 renderLocation.x, renderLocation.y);
0783 g2.setColor(getSelectionForeground());
0784 g2.clipRect(x, fillStart, width, amountFull);
0785 SwingUtilities2.drawString(progressBar, g2, progressString,
0786 renderLocation.x, renderLocation.y);
0787 }
0788 g2.setClip(oldClip);
0789 }
0790
0791 /**
0792 * Designate the place where the progress string will be painted.
0793 * This implementation places it at the center of the progress
0794 * bar (in both x and y). Override this if you want to right,
0795 * left, top, or bottom align the progress string or if you need
0796 * to nudge it around for any reason.
0797 */
0798 protected Point getStringPlacement(Graphics g,
0799 String progressString, int x, int y, int width, int height) {
0800 FontMetrics fontSizer = SwingUtilities2.getFontMetrics(
0801 progressBar, g, progressBar.getFont());
0802 int stringWidth = SwingUtilities2.stringWidth(progressBar,
0803 fontSizer, progressString);
0804
0805 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0806 return new Point(x
0807 + Math.round(width / 2 - stringWidth / 2), y
0808 + ((height + fontSizer.getAscent()
0809 - fontSizer.getLeading() - fontSizer
0810 .getDescent()) / 2));
0811 } else { // VERTICAL
0812 return new Point(x
0813 + ((width - fontSizer.getAscent()
0814 + fontSizer.getLeading() + fontSizer
0815 .getDescent()) / 2), y
0816 + Math.round(height / 2 - stringWidth / 2));
0817 }
0818 }
0819
0820 public Dimension getPreferredSize(JComponent c) {
0821 Dimension size;
0822 Insets border = progressBar.getInsets();
0823 FontMetrics fontSizer = progressBar.getFontMetrics(progressBar
0824 .getFont());
0825
0826 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0827 size = new Dimension(getPreferredInnerHorizontal());
0828 // Ensure that the progress string will fit
0829 if (progressBar.isStringPainted()) {
0830 // I'm doing this for completeness.
0831 String progString = progressBar.getString();
0832 int stringWidth = SwingUtilities2.stringWidth(
0833 progressBar, fontSizer, progString);
0834 if (stringWidth > size.width) {
0835 size.width = stringWidth;
0836 }
0837 // This uses both Height and Descent to be sure that
0838 // there is more than enough room in the progress bar
0839 // for everything.
0840 // This does have a strange dependency on
0841 // getStringPlacememnt() in a funny way.
0842 int stringHeight = fontSizer.getHeight()
0843 + fontSizer.getDescent();
0844 if (stringHeight > size.height) {
0845 size.height = stringHeight;
0846 }
0847 }
0848 } else {
0849 size = new Dimension(getPreferredInnerVertical());
0850 // Ensure that the progress string will fit.
0851 if (progressBar.isStringPainted()) {
0852 String progString = progressBar.getString();
0853 int stringHeight = fontSizer.getHeight()
0854 + fontSizer.getDescent();
0855 if (stringHeight > size.width) {
0856 size.width = stringHeight;
0857 }
0858 // This is also for completeness.
0859 int stringWidth = SwingUtilities2.stringWidth(
0860 progressBar, fontSizer, progString);
0861 if (stringWidth > size.height) {
0862 size.height = stringWidth;
0863 }
0864 }
0865 }
0866
0867 size.width += border.left + border.right;
0868 size.height += border.top + border.bottom;
0869 return size;
0870 }
0871
0872 /**
0873 * The Minimum size for this component is 10. The rationale here
0874 * is that there should be at least one pixel per 10 percent.
0875 */
0876 public Dimension getMinimumSize(JComponent c) {
0877 Dimension pref = getPreferredSize(progressBar);
0878 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0879 pref.width = 10;
0880 } else {
0881 pref.height = 10;
0882 }
0883 return pref;
0884 }
0885
0886 public Dimension getMaximumSize(JComponent c) {
0887 Dimension pref = getPreferredSize(progressBar);
0888 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
0889 pref.width = Short.MAX_VALUE;
0890 } else {
0891 pref.height = Short.MAX_VALUE;
0892 }
0893 return pref;
0894 }
0895
0896 /**
0897 * Gets the index of the current animation frame.
0898 *
0899 * @since 1.4
0900 */
0901 protected int getAnimationIndex() {
0902 return animationIndex;
0903 }
0904
0905 /**
0906 * Returns the number of frames for the complete animation loop
0907 * used by an indeterminate JProgessBar. The progress chunk will go
0908 * from one end to the other and back during the entire loop. This
0909 * visual behavior may be changed by subclasses in other Look and Feels.
0910 *
0911 * @return the number of frames
0912 * @since 1.6
0913 */
0914 protected final int getFrameCount() {
0915 return numFrames;
0916 }
0917
0918 /**
0919 * Sets the index of the current animation frame
0920 * to the specified value and requests that the
0921 * progress bar be repainted.
0922 * Subclasses that don't use the default painting code
0923 * might need to override this method
0924 * to change the way that the <code>repaint</code> method
0925 * is invoked.
0926 *
0927 * @param newValue the new animation index; no checking
0928 * is performed on its value
0929 * @see #incrementAnimationIndex
0930 *
0931 * @since 1.4
0932 */
0933 protected void setAnimationIndex(int newValue) {
0934 if (animationIndex != newValue) {
0935 if (sizeChanged()) {
0936 animationIndex = newValue;
0937 maxPosition = 0; //needs to be recalculated
0938 delta = 0.0; //needs to be recalculated
0939 progressBar.repaint();
0940 return;
0941 }
0942
0943 //Get the previous box drawn.
0944 nextPaintRect = getBox(nextPaintRect);
0945
0946 //Update the frame number.
0947 animationIndex = newValue;
0948
0949 //Get the next box to draw.
0950 if (nextPaintRect != null) {
0951 boxRect = getBox(boxRect);
0952 if (boxRect != null) {
0953 nextPaintRect.add(boxRect);
0954 }
0955 }
0956 } else { //animationIndex == newValue
0957 return;
0958 }
0959
0960 if (nextPaintRect != null) {
0961 progressBar.repaint(nextPaintRect);
0962 } else {
0963 progressBar.repaint();
0964 }
0965 }
0966
0967 private boolean sizeChanged() {
0968 if ((oldComponentInnards == null) || (componentInnards == null)) {
0969 return true;
0970 }
0971
0972 oldComponentInnards.setRect(componentInnards);
0973 componentInnards = SwingUtilities.calculateInnerArea(
0974 progressBar, componentInnards);
0975 return !oldComponentInnards.equals(componentInnards);
0976 }
0977
0978 /**
0979 * Sets the index of the current animation frame,
0980 * to the next valid value,
0981 * which results in the progress bar being repainted.
0982 * The next valid value is, by default,
0983 * the current animation index plus one.
0984 * If the new value would be too large,
0985 * this method sets the index to 0.
0986 * Subclasses might need to override this method
0987 * to ensure that the index does not go over
0988 * the number of frames needed for the particular
0989 * progress bar instance.
0990 * This method is invoked by the default animation thread
0991 * every <em>X</em> milliseconds,
0992 * where <em>X</em> is specified by the "ProgressBar.repaintInterval"
0993 * UI default.
0994 *
0995 * @see #setAnimationIndex
0996 * @since 1.4
0997 */
0998 protected void incrementAnimationIndex() {
0999 int newValue = getAnimationIndex() + 1;
1000
1001 if (newValue < numFrames) {
1002 setAnimationIndex(newValue);
1003 } else {
1004 setAnimationIndex(0);
1005 }
1006 }
1007
1008 /**
1009 * Returns the desired number of milliseconds between repaints.
1010 * This value is meaningful
1011 * only if the progress bar is in indeterminate mode.
1012 * The repaint interval determines how often the
1013 * default animation thread's timer is fired.
1014 * It's also used by the default indeterminate progress bar
1015 * painting code when determining
1016 * how far to move the bouncing box per frame.
1017 * The repaint interval is specified by
1018 * the "ProgressBar.repaintInterval" UI default.
1019 *
1020 * @return the repaint interval, in milliseconds
1021 */
1022 private int getRepaintInterval() {
1023 return repaintInterval;
1024 }
1025
1026 private int initRepaintInterval() {
1027 repaintInterval = DefaultLookup.getInt(progressBar, this ,
1028 "ProgressBar.repaintInterval", 50);
1029 return repaintInterval;
1030 }
1031
1032 /**
1033 * Returns the number of milliseconds per animation cycle.
1034 * This value is meaningful
1035 * only if the progress bar is in indeterminate mode.
1036 * The cycle time is used by the default indeterminate progress bar
1037 * painting code when determining
1038 * how far to move the bouncing box per frame.
1039 * The cycle time is specified by
1040 * the "ProgressBar.cycleTime" UI default
1041 * and adjusted, if necessary,
1042 * by the initIndeterminateDefaults method.
1043 *
1044 * @return the cycle time, in milliseconds
1045 */
1046 private int getCycleTime() {
1047 return cycleTime;
1048 }
1049
1050 private int initCycleTime() {
1051 cycleTime = DefaultLookup.getInt(progressBar, this ,
1052 "ProgressBar.cycleTime", 3000);
1053 return cycleTime;
1054 }
1055
1056 /** Initialize cycleTime, repaintInterval, numFrames, animationIndex. */
1057 private void initIndeterminateDefaults() {
1058 initRepaintInterval(); //initialize repaint interval
1059 initCycleTime(); //initialize cycle length
1060
1061 // Make sure repaintInterval is reasonable.
1062 if (repaintInterval <= 0) {
1063 repaintInterval = 100;
1064 }
1065
1066 // Make sure cycleTime is reasonable.
1067 if (repaintInterval > cycleTime) {
1068 cycleTime = repaintInterval * 20;
1069 } else {
1070 // Force cycleTime to be a even multiple of repaintInterval.
1071 int factor = (int) Math.ceil(((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 */
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. */
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.
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 */
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 */
1178 private void stop() {
1179 timer.stop();
1180 }
1181
1182 /**
1183 * Reacts to the timer's action events.
1184 */
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 - time
1196 + lastCall + 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 * This inner class is marked "public" due to a compiler bug.
1213 * This class should be treated as a "protected" inner class.
1214 * Instantiate it only within subclasses of BasicProgressBarUI.
1215 */
1216 public class ChangeHandler implements ChangeListener {
1217 // NOTE: This class exists only for backward compatability. All
1218 // its functionality has been moved into Handler. If you need to add
1219 // new functionality add it to the Handler, but make sure this
1220 // class calls into the Handler.
1221 public void stateChanged(ChangeEvent e) {
1222 getHandler().stateChanged(e);
1223 }
1224 }
1225
1226 private class Handler implements ChangeListener,
1227 PropertyChangeListener, HierarchyListener {
1228 // ChangeListener
1229 public void stateChanged(ChangeEvent e) {
1230 BoundedRangeModel model = progressBar.getModel();
1231 int newRange = model.getMaximum() - model.getMinimum();
1232 int newPercent;
1233 int oldPercent = getCachedPercent();
1234
1235 if (newRange > 0) {
1236 newPercent = (int) ((100 * (long) model.getValue()) / newRange);
1237 } else {
1238 newPercent = 0;
1239 }
1240
1241 if (newPercent != oldPercent) {
1242 setCachedPercent(newPercent);
1243 progressBar.repaint();
1244 }
1245 }
1246
1247 // PropertyChangeListener
1248 public void propertyChange(PropertyChangeEvent e) {
1249 String prop = e.getPropertyName();
1250 if ("indeterminate" == prop) {
1251 if (progressBar.isIndeterminate()) {
1252 initIndeterminateValues();
1253 } else {
1254 //clean up
1255 cleanUpIndeterminateValues();
1256 }
1257 progressBar.repaint();
1258 }
1259 }
1260
1261 // we don't want the animation to keep running if we're not displayable
1262 public void hierarchyChanged(HierarchyEvent he) {
1263 if ((he.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
1264 if (progressBar.isIndeterminate()) {
1265 if (progressBar.isDisplayable()) {
1266 startAnimationTimer();
1267 } else {
1268 stopAnimationTimer();
1269 }
1270 }
1271 }
1272 }
1273 }
1274 }
|