1. /*
  2. * @(#)RepaintManager.java 1.36 01/11/29
  3. *
  4. * Copyright 2002 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.util.*;
  11. import java.applet.*;
  12. /**
  13. * This class manages repaint requests, allowing the number
  14. * of repaints to be minimized, for example by collapsing multiple
  15. * requests into a single repaint for members of a component tree.
  16. *
  17. * @version 1.36 11/29/01
  18. * @author Arnaud Weber
  19. */
  20. public class RepaintManager
  21. {
  22. Hashtable dirtyComponents = new Hashtable();
  23. Hashtable tmpDirtyComponents = new Hashtable();
  24. Vector invalidComponents;
  25. boolean doubleBufferingEnabled = true;
  26. Image doubleBuffer;
  27. Dimension doubleBufferSize;
  28. private Dimension doubleBufferMaxSize;
  29. private static final Object repaintManagerKey = RepaintManager.class;
  30. /**
  31. * Return the RepaintManager for the calling thread given a Component.
  32. *
  33. * @param c a Component -- unused in the default implementation, but could
  34. * be used by an overridden version to return a different RepaintManager
  35. * depending on the Component
  36. * @return the RepaintManager object
  37. */
  38. public static RepaintManager currentManager(Component c) {
  39. RepaintManager result = (RepaintManager) SwingUtilities.appContextGet(repaintManagerKey);
  40. if(result == null) {
  41. result = new RepaintManager();
  42. SwingUtilities.appContextPut(repaintManagerKey, result);
  43. }
  44. return result;
  45. }
  46. /**
  47. * Return the RepaintManager for the calling thread given a JComponent.
  48. * <p>
  49. * Note: This method exists for backward binary compatibility with earlier
  50. * versions of the Swing library. It simply returns the result returned by
  51. * {@link #currentManager(Component)}.
  52. *
  53. * @param c a JComponent -- unused
  54. * @return the RepaintManager object
  55. */
  56. public static RepaintManager currentManager(JComponent c) {
  57. return currentManager((Component)c);
  58. }
  59. /**
  60. * Set the RepaintManager that should be used for the calling
  61. * thread. <b>aRepaintManager</b> will become the current RepaintManager
  62. * for the calling thread's thread group.
  63. * @param aRepaintManager the RepaintManager object to use
  64. */
  65. public static void setCurrentManager(RepaintManager aRepaintManager) {
  66. if (aRepaintManager != null) {
  67. SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
  68. } else {
  69. SwingUtilities.appContextRemove(repaintManagerKey);
  70. }
  71. }
  72. /**
  73. * Create a new RepaintManager instance. You rarely call this constructor.
  74. * directly. To get the default RepaintManager, use
  75. * RepaintManager.currentManager(JComponent) (normally "this").
  76. */
  77. public RepaintManager() {
  78. }
  79. /**
  80. * Mark the component as in need of layout and queue a runnable
  81. * for the event dispatching thread that will validate the components
  82. * first isValidateRoot() ancestor.
  83. *
  84. * @see JComponent#isValidateRoot
  85. * @see #removeInvalidComponent
  86. */
  87. public synchronized void addInvalidComponent(JComponent invalidComponent)
  88. {
  89. Component validateRoot = null;
  90. /* Find the first JComponent ancestor of this component whose
  91. * isValidateRoot() method returns true.
  92. */
  93. for(Component c = invalidComponent; c != null; c = c.getParent()) {
  94. if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
  95. return;
  96. }
  97. if ((c instanceof JComponent) && (((JComponent)c).isValidateRoot())) {
  98. validateRoot = c;
  99. break;
  100. }
  101. }
  102. /* There's no validateRoot to apply validate to, so we're done.
  103. */
  104. if (validateRoot == null) {
  105. return;
  106. }
  107. /* If the validateRoot and all of its ancestors aren't visible
  108. * then we don't do anything. While we're walking up the tree
  109. * we find the root Window or Applet.
  110. */
  111. Component root = null;
  112. for(Component c = validateRoot; c != null; c = c.getParent()) {
  113. if (!c.isVisible() || (c.getPeer() == null)) {
  114. return;
  115. }
  116. if ((c instanceof Window) || (c instanceof Applet)) {
  117. root = c;
  118. break;
  119. }
  120. }
  121. if (root == null) {
  122. return;
  123. }
  124. /* Lazily create the invalidateComponents vector and add the
  125. * validateRoot if it's not there already. If this validateRoot
  126. * is already in the vector, we're done.
  127. */
  128. if (invalidComponents == null) {
  129. invalidComponents = new Vector();
  130. }
  131. else {
  132. int n = invalidComponents.size();
  133. for(int i = 0; i < n; i++) {
  134. if(validateRoot == (Component)(invalidComponents.elementAt(i))) {
  135. return;
  136. }
  137. }
  138. }
  139. invalidComponents.addElement(validateRoot);
  140. /* Queues a Runnable that calls RepaintManager.validateInvalidComponents()
  141. * and RepaintManager.paintDirtyRegions() with SwingUtilities.invokeLater().
  142. */
  143. SystemEventQueueUtilities.queueComponentWorkRequest(root);
  144. }
  145. /**
  146. * Remove a component from the list of invalid components.
  147. *
  148. * @see #addInvalidComponent
  149. */
  150. public synchronized void removeInvalidComponent(JComponent component) {
  151. if(invalidComponents != null) {
  152. int index = invalidComponents.indexOf(component);
  153. if(index != -1) {
  154. invalidComponents.removeElementAt(index);
  155. }
  156. }
  157. }
  158. /**
  159. * Add a component in the list of components that should be refreshed.
  160. * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
  161. * will be unioned with the region that should be redrawn.
  162. *
  163. * @see JComponent#repaint
  164. */
  165. public synchronized void addDirtyRegion(JComponent c, int x, int y, int w, int h)
  166. {
  167. /* Special cases we don't have to bother with.
  168. */
  169. if ((w <= 0) || (h <= 0) || (c == null)) {
  170. return;
  171. }
  172. if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
  173. return;
  174. }
  175. /* Make sure that c and all it ancestors (up to an Applet or
  176. * Window) are visible. This loop has the same effect as
  177. * checking c.isShowing() (and note that it's still possible
  178. * that c is completely obscured by an opaque ancestor in
  179. * the specified rectangle).
  180. */
  181. Component root = null;
  182. for (Container p = c; p != null; p = p.getParent()) {
  183. if (!p.isVisible() || (p.getPeer() == null)) {
  184. return;
  185. }
  186. if ((p instanceof Window) || (p instanceof Applet)) {
  187. root = p;
  188. break;
  189. }
  190. }
  191. Rectangle r = (Rectangle)dirtyComponents.get(c);
  192. if (r == null) {
  193. dirtyComponents.put(c, new Rectangle(x, y, w, h));
  194. }
  195. else {
  196. SwingUtilities.computeUnion(x, y, w, h, r);
  197. }
  198. /* Queues a Runnable that calls validateInvalidComponents() and
  199. * rm.paintDirtyRegions() with SwingUtilities.invokeLater().
  200. */
  201. SystemEventQueueUtilities.queueComponentWorkRequest(root);
  202. }
  203. /** Return the current dirty region for a component.
  204. * Return an empty rectangle if the component is not
  205. * dirty.
  206. */
  207. public Rectangle getDirtyRegion(JComponent aComponent) {
  208. Rectangle r = null;
  209. synchronized(this) {
  210. r = (Rectangle)dirtyComponents.get(aComponent);
  211. }
  212. if(r == null)
  213. return new Rectangle(0,0,0,0);
  214. else
  215. return new Rectangle(r);
  216. }
  217. /**
  218. * Mark a component completely dirty. <b>aComponent</b> will be
  219. * completely painted during the next paintDirtyRegions() call.
  220. */
  221. public void markCompletelyDirty(JComponent aComponent) {
  222. addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
  223. }
  224. /**
  225. * Mark a component completely clean. <b>aComponent</b> will not
  226. * get painted during the next paintDirtyRegions() call.
  227. */
  228. public void markCompletelyClean(JComponent aComponent) {
  229. synchronized(this) {
  230. dirtyComponents.remove(aComponent);
  231. }
  232. }
  233. /**
  234. * Convenience method that returns true if <b>aComponent</b> will be completely
  235. * painted during the next paintDirtyRegions(). If computing dirty regions is
  236. * expensive for your component, use this method and avoid computing dirty region
  237. * if it return true.
  238. */
  239. public boolean isCompletelyDirty(JComponent aComponent) {
  240. Rectangle r;
  241. r = getDirtyRegion(aComponent);
  242. if(r.width == Integer.MAX_VALUE &&
  243. r.height == Integer.MAX_VALUE)
  244. return true;
  245. else
  246. return false;
  247. }
  248. /**
  249. * Validate all of the components that have been marked invalid.
  250. * @see #addInvalidComponent
  251. */
  252. public void validateInvalidComponents() {
  253. Vector ic;
  254. synchronized(this) {
  255. if(invalidComponents == null) {
  256. return;
  257. }
  258. ic = invalidComponents;
  259. invalidComponents = null;
  260. }
  261. int n = ic.size();
  262. for(int i = 0; i < n; i++) {
  263. ((Component)ic.elementAt(i)).validate();
  264. }
  265. }
  266. /**
  267. * Paint all of the components that have been marked dirty.
  268. *
  269. * @see #addDirtyRegion
  270. */
  271. public void paintDirtyRegions() {
  272. int i, count;
  273. Vector roots;
  274. JComponent dirtyComponent;
  275. synchronized(this) { // swap for thread safety
  276. Hashtable tmp = tmpDirtyComponents;
  277. tmpDirtyComponents = dirtyComponents;
  278. dirtyComponents = tmp;
  279. dirtyComponents.clear();
  280. }
  281. count = tmpDirtyComponents.size();
  282. if (count == 0) {
  283. return;
  284. }
  285. Rectangle rect;
  286. int localBoundsX = 0;
  287. int localBoundsY = 0;
  288. int localBoundsH = 0;
  289. int localBoundsW = 0;
  290. Enumeration keys;
  291. roots = new Vector(count);
  292. keys = tmpDirtyComponents.keys();
  293. while(keys.hasMoreElements()) {
  294. dirtyComponent = (JComponent) keys.nextElement();
  295. collectDirtyComponents(tmpDirtyComponents, dirtyComponent, roots);
  296. }
  297. count = roots.size();
  298. // System.out.println("roots size is " + count);
  299. for(i=0 ; i < count ; i++) {
  300. dirtyComponent = (JComponent) roots.elementAt(i);
  301. rect = (Rectangle) tmpDirtyComponents.get(dirtyComponent);
  302. // System.out.println("Should refresh :" + rect);
  303. localBoundsH = dirtyComponent.getHeight();
  304. localBoundsW = dirtyComponent.getWidth();
  305. SwingUtilities.computeIntersection(localBoundsX,
  306. localBoundsY,
  307. localBoundsW,
  308. localBoundsH,
  309. rect);
  310. // System.out.println("** paint of " + dirtyComponent + rect);
  311. dirtyComponent.paintImmediately(rect.x,rect.y,rect.width,rect.height);
  312. }
  313. tmpDirtyComponents.clear();
  314. }
  315. Rectangle tmp = new Rectangle();
  316. void collectDirtyComponents(Hashtable dirtyComponents,
  317. JComponent dirtyComponent,
  318. Vector roots) {
  319. int dx, dy, rootDx, rootDy;
  320. Component component, rootDirtyComponent,parent;
  321. //Rectangle tmp;
  322. Rectangle cBounds;
  323. boolean opaqueAncestorFound = false;
  324. // Find the highest parent which is dirty. When we get out of this
  325. // rootDx and rootDy will contain the translation from the
  326. // rootDirtyComponent's coordinate system to the coordinates of the
  327. // original dirty component. The tmp Rect is also used to compute the
  328. // visible portion of the dirtyRect.
  329. component = rootDirtyComponent = dirtyComponent;
  330. cBounds = dirtyComponent._bounds;
  331. dx = rootDx = 0;
  332. dy = rootDy = 0;
  333. tmp = new Rectangle((Rectangle) dirtyComponents.get(dirtyComponent));
  334. // System.out.println("Collect dirty component for bound " + tmp +
  335. // "component bounds is " + cBounds);;
  336. SwingUtilities.computeIntersection(0,0,cBounds.width,cBounds.height,tmp);
  337. if (tmp.isEmpty()) {
  338. // System.out.println("Empty 1");
  339. return;
  340. }
  341. if(dirtyComponent.isOpaque())
  342. opaqueAncestorFound = true;
  343. for(;;) {
  344. parent = component.getParent();
  345. if(parent == null)
  346. break;
  347. if(!(parent instanceof JComponent))
  348. break;
  349. component = parent;
  350. if(((JComponent)component).isOpaque())
  351. opaqueAncestorFound = true;
  352. dx += cBounds.x;
  353. dy += cBounds.y;
  354. tmp.setLocation(tmp.x + cBounds.x,
  355. tmp.y + cBounds.y);
  356. cBounds = ((JComponent)component)._bounds;
  357. tmp = SwingUtilities.computeIntersection(0,0,cBounds.width,cBounds.height,tmp);
  358. if (tmp.isEmpty()) {
  359. // System.out.println("Empty 2");
  360. return;
  361. }
  362. if (dirtyComponents.get(component) != null) {
  363. rootDirtyComponent = component;
  364. rootDx = dx;
  365. rootDy = dy;
  366. }
  367. }
  368. if (dirtyComponent != rootDirtyComponent) {
  369. Rectangle r;
  370. tmp.setLocation(tmp.x + rootDx - dx,
  371. tmp.y + rootDy - dy);
  372. r = (Rectangle)dirtyComponents.get(rootDirtyComponent);
  373. SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
  374. }
  375. // If we haven't seen this root before, then we need to add it to the
  376. // list of root dirty Views.
  377. if (!roots.contains(rootDirtyComponent))
  378. roots.addElement(rootDirtyComponent);
  379. }
  380. /**
  381. * Returns a string that displays and identifies this
  382. * object's properties.
  383. *
  384. * @return a String representation of this object
  385. */
  386. public synchronized String toString() {
  387. StringBuffer sb = new StringBuffer();
  388. if(dirtyComponents != null)
  389. sb.append("" + dirtyComponents);
  390. return sb.toString();
  391. }
  392. /**
  393. * Return the offscreen buffer that should be used as a double buffer with the component <code>c</code>
  394. * By default there is a double buffer per RepaintManager.
  395. * The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
  396. * This happens when the maximum double buffer size as been set for the receiving
  397. * repaint manager.
  398. */
  399. public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
  400. Image result;
  401. int width,height;
  402. Dimension maxSize = getDoubleBufferMaximumSize();
  403. if (proposedWidth < 1) {
  404. width = 1;
  405. }
  406. else if(proposedWidth > maxSize.width)
  407. width = maxSize.width;
  408. else
  409. width = proposedWidth;
  410. if (proposedHeight < 1) {
  411. height = 1;
  412. }
  413. else if(proposedHeight > maxSize.height)
  414. height = maxSize.height;
  415. else
  416. height = proposedHeight;
  417. if(doubleBuffer != null) {
  418. if(doubleBuffer.getWidth(null) < width || doubleBuffer.getHeight(null) < height) {
  419. doubleBuffer = null;
  420. }
  421. }
  422. int go_width = width;
  423. int go_height = height;
  424. if(doubleBuffer != null) {
  425. go_width = doubleBufferSize.width;
  426. go_height = doubleBufferSize.height;
  427. if( doubleBufferSize.width < width ) {
  428. go_width = width;
  429. doubleBuffer = null;
  430. }
  431. if( doubleBufferSize.height < height ) {
  432. go_height = height;
  433. doubleBuffer = null;
  434. }
  435. }
  436. if(doubleBuffer == null) {
  437. doubleBuffer = c.createImage( go_width , go_height );
  438. doubleBufferSize = new Dimension( go_width , go_height );
  439. }
  440. return doubleBuffer;
  441. }
  442. /** Set the maximum double buffer size. **/
  443. public void setDoubleBufferMaximumSize(Dimension d) {
  444. doubleBufferMaxSize = d;
  445. if(doubleBuffer != null) {
  446. if(doubleBuffer.getWidth(null) > d.width || doubleBuffer.getHeight(null) > d.height) {
  447. doubleBuffer = null;
  448. }
  449. }
  450. }
  451. /**
  452. * Returns the maximum double buffer size.
  453. *
  454. * @return a Dimension object representing the maximum size
  455. */
  456. public Dimension getDoubleBufferMaximumSize() {
  457. if (doubleBufferMaxSize == null) {
  458. doubleBufferMaxSize = Toolkit.getDefaultToolkit().getScreenSize();
  459. }
  460. return doubleBufferMaxSize;
  461. }
  462. /**
  463. * Enables or disables double buffering.
  464. *
  465. * @param aFlag true to activate double buffering
  466. */
  467. public void setDoubleBufferingEnabled(boolean aFlag) {
  468. doubleBufferingEnabled = aFlag;
  469. }
  470. /**
  471. * Returns true if this object is double buffered.
  472. *
  473. * @return true if this object is double buffered
  474. */
  475. public boolean isDoubleBufferingEnabled() {
  476. return doubleBufferingEnabled;
  477. }
  478. }