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