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 */ createUI(JComponent x)142 public static ComponentUI createUI(JComponent x) { 143 return new BasicProgressBarUI(); 144 } 145 installUI(JComponent c)146 public void installUI(JComponent c) { 147 progressBar = (JProgressBar)c; 148 installDefaults(); 149 installListeners(); 150 if (progressBar.isIndeterminate()) { 151 initIndeterminateValues(); 152 } 153 } 154 uninstallUI(JComponent c)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 */ installDefaults()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 */ uninstallDefaults()184 protected void uninstallDefaults() { 185 LookAndFeel.uninstallBorder(progressBar); 186 } 187 188 /** 189 * Registers listeners. 190 */ installListeners()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 getHandler()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 */ startAnimationTimer()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 */ stopAnimationTimer()247 protected void stopAnimationTimer() { 248 if (animator != null) { 249 animator.stop(); 250 } 251 } 252 253 /** 254 * Removes all listeners installed by this object. 255 */ uninstallListeners()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 */ getBaseline(JComponent c, int width, int height)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 */ getBaselineResizeBehavior( JComponent c)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 */ getPreferredInnerHorizontal()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 */ getPreferredInnerVertical()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 */ getSelectionForeground()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 */ getSelectionBackground()358 protected Color getSelectionBackground() { 359 return selectionBackground; 360 } 361 getCachedPercent()362 private int getCachedPercent() { 363 return cachedPercent; 364 } 365 setCachedPercent(int cachedPercent)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 */ getCellLength()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 */ setCellLength(int cellLen)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 */ getCellSpacing()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 */ setCellSpacing(int cellSpace)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 */ getAmountFull(Insets b, int width, int height)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 */ paint(Graphics g, JComponent c)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 */ getBox(Rectangle r)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 */ updateSizes()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 */ getGenericBox(Rectangle r)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 */ getBoxLength(int availableLength, int otherDimension)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 */ paintIndeterminate(Graphics g, JComponent c)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 */ paintDeterminate(Graphics g, JComponent c)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 */ paintString(Graphics g, int x, int y, int width, int height, int amountFull, Insets b)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 */ paintString(Graphics g, int x, int y, int width, int height, int fillStart, int amountFull, Insets b)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 SwingUtilities2.drawString(progressBar, g2, progressString, 831 renderLocation.x, renderLocation.y); 832 g2.setColor(getSelectionForeground()); 833 g2.clipRect(fillStart, y, amountFull, height); 834 SwingUtilities2.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 SwingUtilities2.drawString(progressBar, g2, progressString, 844 renderLocation.x, renderLocation.y); 845 g2.setColor(getSelectionForeground()); 846 g2.clipRect(x, fillStart, width, amountFull); 847 SwingUtilities2.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 */ getStringPlacement(Graphics g, String progressString, int x,int y,int width,int height)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 = SwingUtilities2.stringWidth(progressBar, fontSizer, 874 progressString); 875 876 if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) { 877 return new Point(x + (int)Math.round(width/2.0 - stringWidth/2.0), 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 + (int)Math.round(height/2.0 - stringWidth/2.0)); 886 } 887 } 888 889 getPreferredSize(JComponent c)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 = SwingUtilities2.stringWidth( 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 = SwingUtilities2.stringWidth( 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 */ getMinimumSize(JComponent c)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 getMaximumSize(JComponent c)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 */ getAnimationIndex()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 */ getFrameCount()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 */ setAnimationIndex(int newValue)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 sizeChanged()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 */ incrementAnimationIndex()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 */ getRepaintInterval()1093 private int getRepaintInterval() { 1094 return repaintInterval; 1095 } 1096 initRepaintInterval()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 */ getCycleTime()1117 private int getCycleTime() { 1118 return cycleTime; 1119 } 1120 initCycleTime()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. */ initIndeterminateDefaults()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 */ initIndeterminateValues()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. */ cleanUpIndeterminateValues()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. initAnimationIndex()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 */ start(int interval)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 */ stop()1251 private void stop() { 1252 timer.stop(); 1253 } 1254 1255 /** 1256 * Reacts to the timer's action events. 1257 */ actionPerformed(ActionEvent e)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. stateChanged(ChangeEvent e)1295 public void stateChanged(ChangeEvent e) { 1296 getHandler().stateChanged(e); 1297 } 1298 } 1299 1300 1301 private class Handler implements ChangeListener, PropertyChangeListener, HierarchyListener { 1302 // ChangeListener stateChanged(ChangeEvent e)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 propertyChange(PropertyChangeEvent e)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 hierarchyChanged(HierarchyEvent he)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 } 1349