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 "protected" 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