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