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