1. /*
  2. * @(#)RepaintManager.java 1.53 03/08/26
  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;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.awt.image.VolatileImage;
  11. import java.util.*;
  12. import java.applet.*;
  13. import sun.security.action.GetPropertyAction;
  14. /**
  15. * This class manages repaint requests, allowing the number
  16. * of repaints to be minimized, for example by collapsing multiple
  17. * requests into a single repaint for members of a component tree.
  18. *
  19. * @version 1.53 08/26/03
  20. * @author Arnaud Weber
  21. */
  22. public class RepaintManager
  23. {
  24. /**
  25. * Maps from GraphicsConfiguration to VolatileImage.
  26. */
  27. private Map volatileMap = new HashMap(1);
  28. Hashtable dirtyComponents = new Hashtable();
  29. Hashtable tmpDirtyComponents = new Hashtable();
  30. Vector invalidComponents;
  31. boolean doubleBufferingEnabled = true;
  32. private Dimension doubleBufferMaxSize;
  33. // Support for both the standard and volatile offscreen buffers exists to
  34. // provide backwards compatibility for the [rare] programs which may be
  35. // calling getOffScreenBuffer() and not expecting to get a VolatileImage.
  36. // Swing internally is migrating to use *only* the volatile image buffer.
  37. // Support for standard offscreen buffer
  38. //
  39. DoubleBufferInfo standardDoubleBuffer;
  40. private static final Object repaintManagerKey = RepaintManager.class;
  41. // Whether or not a VolatileImage should be used for double-buffered painting
  42. static boolean volatileImageBufferEnabled = true;
  43. // The maximum number of times Swing will attempt to use the VolatileImage
  44. // buffer during a paint operation.
  45. static final int VOLATILE_LOOP_MAX = 2;
  46. static {
  47. String vib = (String) java.security.AccessController.doPrivileged(
  48. new GetPropertyAction("swing.volatileImageBufferEnabled"));
  49. volatileImageBufferEnabled = (vib == null || vib.equals("true"));
  50. }
  51. /**
  52. * Return the RepaintManager for the calling thread given a Component.
  53. *
  54. * @param c a Component -- unused in the default implementation, but could
  55. * be used by an overridden version to return a different RepaintManager
  56. * depending on the Component
  57. * @return the RepaintManager object
  58. */
  59. public static RepaintManager currentManager(Component c) {
  60. // Note: SystemEventQueueUtilities.ComponentWorkRequest passes
  61. // in null as the component, so if component is ever used to
  62. // determine the current RepaintManager, SystemEventQueueUtilities
  63. // will need to be modified accordingly.
  64. RepaintManager result = (RepaintManager) SwingUtilities.appContextGet(repaintManagerKey);
  65. if(result == null) {
  66. result = new RepaintManager();
  67. SwingUtilities.appContextPut(repaintManagerKey, result);
  68. }
  69. return result;
  70. }
  71. /**
  72. * Return the RepaintManager for the calling thread given a JComponent.
  73. * <p>
  74. * Note: This method exists for backward binary compatibility with earlier
  75. * versions of the Swing library. It simply returns the result returned by
  76. * {@link #currentManager(Component)}.
  77. *
  78. * @param c a JComponent -- unused
  79. * @return the RepaintManager object
  80. */
  81. public static RepaintManager currentManager(JComponent c) {
  82. return currentManager((Component)c);
  83. }
  84. /**
  85. * Set the RepaintManager that should be used for the calling
  86. * thread. <b>aRepaintManager</b> will become the current RepaintManager
  87. * for the calling thread's thread group.
  88. * @param aRepaintManager the RepaintManager object to use
  89. */
  90. public static void setCurrentManager(RepaintManager aRepaintManager) {
  91. if (aRepaintManager != null) {
  92. SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
  93. } else {
  94. SwingUtilities.appContextRemove(repaintManagerKey);
  95. }
  96. }
  97. /**
  98. * Create a new RepaintManager instance. You rarely call this constructor.
  99. * directly. To get the default RepaintManager, use
  100. * RepaintManager.currentManager(JComponent) (normally "this").
  101. */
  102. public RepaintManager() {
  103. SwingUtilities.doPrivileged(new Runnable() {
  104. public void run() {
  105. boolean nativeDoubleBuffering = Boolean.getBoolean("awt.nativeDoubleBuffering");
  106. // If native doublebuffering is being used, do NOT use
  107. // Swing doublebuffering.
  108. doubleBufferingEnabled = !nativeDoubleBuffering;
  109. }
  110. });
  111. }
  112. /**
  113. * Mark the component as in need of layout and queue a runnable
  114. * for the event dispatching thread that will validate the components
  115. * first isValidateRoot() ancestor.
  116. *
  117. * @see JComponent#isValidateRoot
  118. * @see #removeInvalidComponent
  119. */
  120. public synchronized void addInvalidComponent(JComponent invalidComponent)
  121. {
  122. Component validateRoot = null;
  123. /* Find the first JComponent ancestor of this component whose
  124. * isValidateRoot() method returns true.
  125. */
  126. for(Component c = invalidComponent; c != null; c = c.getParent()) {
  127. if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
  128. return;
  129. }
  130. if ((c instanceof JComponent) && (((JComponent)c).isValidateRoot())) {
  131. validateRoot = c;
  132. break;
  133. }
  134. }
  135. /* There's no validateRoot to apply validate to, so we're done.
  136. */
  137. if (validateRoot == null) {
  138. return;
  139. }
  140. /* If the validateRoot and all of its ancestors aren't visible
  141. * then we don't do anything. While we're walking up the tree
  142. * we find the root Window or Applet.
  143. */
  144. Component root = null;
  145. for(Component c = validateRoot; c != null; c = c.getParent()) {
  146. if (!c.isVisible() || (c.getPeer() == null)) {
  147. return;
  148. }
  149. if ((c instanceof Window) || (c instanceof Applet)) {
  150. root = c;
  151. break;
  152. }
  153. }
  154. if (root == null) {
  155. return;
  156. }
  157. /* Lazily create the invalidateComponents vector and add the
  158. * validateRoot if it's not there already. If this validateRoot
  159. * is already in the vector, we're done.
  160. */
  161. if (invalidComponents == null) {
  162. invalidComponents = new Vector();
  163. }
  164. else {
  165. int n = invalidComponents.size();
  166. for(int i = 0; i < n; i++) {
  167. if(validateRoot == (Component)(invalidComponents.elementAt(i))) {
  168. return;
  169. }
  170. }
  171. }
  172. invalidComponents.addElement(validateRoot);
  173. /* Queues a Runnable that calls RepaintManager.validateInvalidComponents()
  174. * and RepaintManager.paintDirtyRegions() with SwingUtilities.invokeLater().
  175. */
  176. SystemEventQueueUtilities.queueComponentWorkRequest(root);
  177. }
  178. /**
  179. * Remove a component from the list of invalid components.
  180. *
  181. * @see #addInvalidComponent
  182. */
  183. public synchronized void removeInvalidComponent(JComponent component) {
  184. if(invalidComponents != null) {
  185. int index = invalidComponents.indexOf(component);
  186. if(index != -1) {
  187. invalidComponents.removeElementAt(index);
  188. }
  189. }
  190. }
  191. /**
  192. * Add a component in the list of components that should be refreshed.
  193. * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
  194. * will be unioned with the region that should be redrawn.
  195. *
  196. * @see JComponent#repaint
  197. */
  198. public synchronized void addDirtyRegion(JComponent c, int x, int y, int w, int h)
  199. {
  200. /* Special cases we don't have to bother with.
  201. */
  202. if ((w <= 0) || (h <= 0) || (c == null)) {
  203. return;
  204. }
  205. if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
  206. return;
  207. }
  208. Rectangle r = (Rectangle)dirtyComponents.get(c);
  209. if (r != null) {
  210. // A non-null r implies c is already marked as dirty,
  211. // and that the parent is valid. Therefore we can
  212. // just union the rect and bail.
  213. SwingUtilities.computeUnion(x, y, w, h, r);
  214. return;
  215. }
  216. /* Make sure that c and all it ancestors (up to an Applet or
  217. * Window) are visible. This loop has the same effect as
  218. * checking c.isShowing() (and note that it's still possible
  219. * that c is completely obscured by an opaque ancestor in
  220. * the specified rectangle).
  221. */
  222. Component root = null;
  223. for (Container p = c; p != null; p = p.getParent()) {
  224. if (!p.isVisible() || (p.getPeer() == null)) {
  225. return;
  226. }
  227. if ((p instanceof Window) || (p instanceof Applet)) {
  228. // Iconified frames are still visible!
  229. if (p instanceof Frame &&
  230. (((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
  231. Frame.ICONIFIED) {
  232. return;
  233. }
  234. root = p;
  235. break;
  236. }
  237. }
  238. if (root == null) return;
  239. dirtyComponents.put(c, new Rectangle(x, y, w, h));
  240. /* Queues a Runnable that calls validateInvalidComponents() and
  241. * rm.paintDirtyRegions() with SwingUtilities.invokeLater().
  242. */
  243. SystemEventQueueUtilities.queueComponentWorkRequest(root);
  244. }
  245. /** Return the current dirty region for a component.
  246. * Return an empty rectangle if the component is not
  247. * dirty.
  248. */
  249. public Rectangle getDirtyRegion(JComponent aComponent) {
  250. Rectangle r = null;
  251. synchronized(this) {
  252. r = (Rectangle)dirtyComponents.get(aComponent);
  253. }
  254. if(r == null)
  255. return new Rectangle(0,0,0,0);
  256. else
  257. return new Rectangle(r);
  258. }
  259. /**
  260. * Mark a component completely dirty. <b>aComponent</b> will be
  261. * completely painted during the next paintDirtyRegions() call.
  262. */
  263. public void markCompletelyDirty(JComponent aComponent) {
  264. addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
  265. }
  266. /**
  267. * Mark a component completely clean. <b>aComponent</b> will not
  268. * get painted during the next paintDirtyRegions() call.
  269. */
  270. public void markCompletelyClean(JComponent aComponent) {
  271. synchronized(this) {
  272. dirtyComponents.remove(aComponent);
  273. }
  274. }
  275. /**
  276. * Convenience method that returns true if <b>aComponent</b> will be completely
  277. * painted during the next paintDirtyRegions(). If computing dirty regions is
  278. * expensive for your component, use this method and avoid computing dirty region
  279. * if it return true.
  280. */
  281. public boolean isCompletelyDirty(JComponent aComponent) {
  282. Rectangle r;
  283. r = getDirtyRegion(aComponent);
  284. if(r.width == Integer.MAX_VALUE &&
  285. r.height == Integer.MAX_VALUE)
  286. return true;
  287. else
  288. return false;
  289. }
  290. /**
  291. * Validate all of the components that have been marked invalid.
  292. * @see #addInvalidComponent
  293. */
  294. public void validateInvalidComponents() {
  295. Vector ic;
  296. synchronized(this) {
  297. if(invalidComponents == null) {
  298. return;
  299. }
  300. ic = invalidComponents;
  301. invalidComponents = null;
  302. }
  303. int n = ic.size();
  304. for(int i = 0; i < n; i++) {
  305. ((Component)ic.elementAt(i)).validate();
  306. }
  307. }
  308. /**
  309. * Paint all of the components that have been marked dirty.
  310. *
  311. * @see #addDirtyRegion
  312. */
  313. public void paintDirtyRegions() {
  314. int i, count;
  315. Vector roots;
  316. JComponent dirtyComponent;
  317. synchronized(this) { // swap for thread safety
  318. Hashtable tmp = tmpDirtyComponents;
  319. tmpDirtyComponents = dirtyComponents;
  320. dirtyComponents = tmp;
  321. dirtyComponents.clear();
  322. }
  323. count = tmpDirtyComponents.size();
  324. if (count == 0) {
  325. return;
  326. }
  327. Rectangle rect;
  328. int localBoundsX = 0;
  329. int localBoundsY = 0;
  330. int localBoundsH = 0;
  331. int localBoundsW = 0;
  332. Enumeration keys;
  333. roots = new Vector(count);
  334. keys = tmpDirtyComponents.keys();
  335. while(keys.hasMoreElements()) {
  336. dirtyComponent = (JComponent) keys.nextElement();
  337. collectDirtyComponents(tmpDirtyComponents, dirtyComponent, roots);
  338. }
  339. count = roots.size();
  340. // System.out.println("roots size is " + count);
  341. for(i=0 ; i < count ; i++) {
  342. dirtyComponent = (JComponent) roots.elementAt(i);
  343. rect = (Rectangle) tmpDirtyComponents.get(dirtyComponent);
  344. // System.out.println("Should refresh :" + rect);
  345. localBoundsH = dirtyComponent.getHeight();
  346. localBoundsW = dirtyComponent.getWidth();
  347. SwingUtilities.computeIntersection(localBoundsX,
  348. localBoundsY,
  349. localBoundsW,
  350. localBoundsH,
  351. rect);
  352. // System.out.println("** paint of " + dirtyComponent + rect);
  353. dirtyComponent.paintImmediately(rect.x,rect.y,rect.width,rect.height);
  354. }
  355. tmpDirtyComponents.clear();
  356. }
  357. Rectangle tmp = new Rectangle();
  358. void collectDirtyComponents(Hashtable dirtyComponents,
  359. JComponent dirtyComponent,
  360. Vector roots) {
  361. int dx, dy, rootDx, rootDy;
  362. Component component, rootDirtyComponent,parent;
  363. //Rectangle tmp;
  364. Rectangle cBounds;
  365. // Find the highest parent which is dirty. When we get out of this
  366. // rootDx and rootDy will contain the translation from the
  367. // rootDirtyComponent's coordinate system to the coordinates of the
  368. // original dirty component. The tmp Rect is also used to compute the
  369. // visible portion of the dirtyRect.
  370. component = rootDirtyComponent = dirtyComponent;
  371. cBounds = dirtyComponent._bounds;
  372. dx = rootDx = 0;
  373. dy = rootDy = 0;
  374. tmp.setBounds((Rectangle) dirtyComponents.get(dirtyComponent));
  375. // System.out.println("Collect dirty component for bound " + tmp +
  376. // "component bounds is " + cBounds);;
  377. SwingUtilities.computeIntersection(0,0,cBounds.width,cBounds.height,tmp);
  378. if (tmp.isEmpty()) {
  379. // System.out.println("Empty 1");
  380. return;
  381. }
  382. for(;;) {
  383. parent = component.getParent();
  384. if(parent == null)
  385. break;
  386. if(!(parent instanceof JComponent))
  387. break;
  388. component = parent;
  389. dx += cBounds.x;
  390. dy += cBounds.y;
  391. tmp.setLocation(tmp.x + cBounds.x,
  392. tmp.y + cBounds.y);
  393. cBounds = ((JComponent)component)._bounds;
  394. tmp = SwingUtilities.computeIntersection(0,0,cBounds.width,cBounds.height,tmp);
  395. if (tmp.isEmpty()) {
  396. // System.out.println("Empty 2");
  397. return;
  398. }
  399. if (dirtyComponents.get(component) != null) {
  400. rootDirtyComponent = component;
  401. rootDx = dx;
  402. rootDy = dy;
  403. }
  404. }
  405. if (dirtyComponent != rootDirtyComponent) {
  406. Rectangle r;
  407. tmp.setLocation(tmp.x + rootDx - dx,
  408. tmp.y + rootDy - dy);
  409. r = (Rectangle)dirtyComponents.get(rootDirtyComponent);
  410. SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
  411. }
  412. // If we haven't seen this root before, then we need to add it to the
  413. // list of root dirty Views.
  414. if (!roots.contains(rootDirtyComponent))
  415. roots.addElement(rootDirtyComponent);
  416. }
  417. /**
  418. * Returns a string that displays and identifies this
  419. * object's properties.
  420. *
  421. * @return a String representation of this object
  422. */
  423. public synchronized String toString() {
  424. StringBuffer sb = new StringBuffer();
  425. if(dirtyComponents != null)
  426. sb.append("" + dirtyComponents);
  427. return sb.toString();
  428. }
  429. /**
  430. * Return the offscreen buffer that should be used as a double buffer with
  431. * the component <code>c</code>.
  432. * By default there is a double buffer per RepaintManager.
  433. * The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
  434. * This happens when the maximum double buffer size as been set for the receiving
  435. * repaint manager.
  436. */
  437. public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
  438. return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
  439. }
  440. /**
  441. * Return a volatile offscreen buffer that should be used as a
  442. * double buffer with the specified component <code>c</code>.
  443. * The image returned will be an instance of VolatileImage, or null
  444. * if a VolatileImage object could not be instantiated.
  445. * This buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>.
  446. * This happens when the maximum double buffer size has been set for this
  447. * repaint manager.
  448. *
  449. * @see java.awt.image.VolatileImage
  450. * @since 1.4
  451. */
  452. public Image getVolatileOffscreenBuffer(Component c,
  453. int proposedWidth,int proposedHeight) {
  454. GraphicsConfiguration config = c.getGraphicsConfiguration();
  455. if (config == null) {
  456. config = GraphicsEnvironment.getLocalGraphicsEnvironment().
  457. getDefaultScreenDevice().getDefaultConfiguration();
  458. }
  459. Dimension maxSize = getDoubleBufferMaximumSize();
  460. int width = proposedWidth < 1 ? 1 :
  461. (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
  462. int height = proposedHeight < 1 ? 1 :
  463. (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
  464. VolatileImage image = (VolatileImage)volatileMap.get(config);
  465. if (image == null || image.getWidth() < width ||
  466. image.getHeight() < height) {
  467. if (image != null) {
  468. image.flush();
  469. }
  470. image = config.createCompatibleVolatileImage(width, height);
  471. volatileMap.put(config, image);
  472. }
  473. return image;
  474. }
  475. private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
  476. Dimension maxSize = getDoubleBufferMaximumSize();
  477. DoubleBufferInfo doubleBuffer = null;
  478. int width, height;
  479. if (standardDoubleBuffer == null) {
  480. standardDoubleBuffer = new DoubleBufferInfo();
  481. }
  482. doubleBuffer = standardDoubleBuffer;
  483. width = proposedWidth < 1? 1 :
  484. (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
  485. height = proposedHeight < 1? 1 :
  486. (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
  487. if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
  488. (doubleBuffer.size.width < width ||
  489. doubleBuffer.size.height < height))) {
  490. doubleBuffer.needsReset = false;
  491. if (doubleBuffer.image != null) {
  492. doubleBuffer.image.flush();
  493. doubleBuffer.image = null;
  494. }
  495. width = Math.max(doubleBuffer.size.width, width);
  496. height = Math.max(doubleBuffer.size.height, height);
  497. }
  498. Image result = doubleBuffer.image;
  499. if (doubleBuffer.image == null) {
  500. result = c.createImage(width , height);
  501. doubleBuffer.size = new Dimension(width, height);
  502. if (c instanceof JComponent) {
  503. ((JComponent)c).setCreatedDoubleBuffer(true);
  504. doubleBuffer.image = result;
  505. }
  506. // JComponent will inform us when it is no longer valid
  507. // (via removeNotify) we have no such hook to other components,
  508. // therefore we don't keep a ref to the Component
  509. // (indirectly through the Image) by stashing the image.
  510. }
  511. return result;
  512. }
  513. /** Set the maximum double buffer size. **/
  514. public void setDoubleBufferMaximumSize(Dimension d) {
  515. doubleBufferMaxSize = d;
  516. if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
  517. if (standardDoubleBuffer.image.getWidth(null) > d.width ||
  518. standardDoubleBuffer.image.getHeight(null) > d.height) {
  519. standardDoubleBuffer.image = null;
  520. }
  521. }
  522. // Clear out the VolatileImages
  523. Iterator gcs = volatileMap.keySet().iterator();
  524. while (gcs.hasNext()) {
  525. GraphicsConfiguration gc = (GraphicsConfiguration)gcs.next();
  526. VolatileImage image = (VolatileImage)volatileMap.get(gc);
  527. if (image.getWidth() > d.width || image.getHeight() > d.height) {
  528. image.flush();
  529. gcs.remove();
  530. }
  531. }
  532. }
  533. /**
  534. * Returns the maximum double buffer size.
  535. *
  536. * @return a Dimension object representing the maximum size
  537. */
  538. public Dimension getDoubleBufferMaximumSize() {
  539. if (doubleBufferMaxSize == null) {
  540. try {
  541. doubleBufferMaxSize = Toolkit.getDefaultToolkit().getScreenSize();
  542. } catch (HeadlessException e) {
  543. doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  544. }
  545. }
  546. return doubleBufferMaxSize;
  547. }
  548. /**
  549. * Enables or disables double buffering in this RepaintManager.
  550. * CAUTION: The default value for this property is set for optimal
  551. * paint performance on the given platform and it is not recommended
  552. * that programs modify this property directly.
  553. *
  554. * @param aFlag true to activate double buffering
  555. * @see #isDoubleBufferingEnabled
  556. */
  557. public void setDoubleBufferingEnabled(boolean aFlag) {
  558. doubleBufferingEnabled = aFlag;
  559. }
  560. /**
  561. * Returns true if this RepaintManager is double buffered.
  562. * The default value for this property may vary from platform
  563. * to platform. On platforms where native double buffering
  564. * is supported in the AWT, the default value will be <code>false</code>
  565. * to avoid unnecessary buffering in Swing.
  566. * On platforms where native double buffering is not supported,
  567. * the default value will be <code>true</code>.
  568. *
  569. * @return true if this object is double buffered
  570. */
  571. public boolean isDoubleBufferingEnabled() {
  572. return doubleBufferingEnabled;
  573. }
  574. /**
  575. * This resets the double buffer. Actually, it marks the double buffer
  576. * as invalid, the double buffer will then be recreated on the next
  577. * invocation of getOffscreenBuffer.
  578. */
  579. void resetDoubleBuffer() {
  580. if (standardDoubleBuffer != null) {
  581. standardDoubleBuffer.needsReset = true;
  582. }
  583. }
  584. /**
  585. * This resets the volatile double buffer.
  586. */
  587. void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
  588. Image image = (Image)volatileMap.remove(gc);
  589. if (image != null) {
  590. image.flush();
  591. }
  592. }
  593. /**
  594. * Returns true if we should use the <code>Image</code> returned
  595. * from <code>getVolatileOffscreenBuffer</code> to do double buffering.
  596. */
  597. boolean useVolatileDoubleBuffer() {
  598. return volatileImageBufferEnabled;
  599. }
  600. private class DoubleBufferInfo {
  601. public Image image;
  602. public Dimension size;
  603. public boolean needsReset = false;
  604. }
  605. }