1. /*
  2. * @(#)JViewport.java 1.114 04/05/18
  3. *
  4. * Copyright 2004 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.awt.peer.ComponentPeer;
  12. import java.applet.Applet;
  13. import javax.swing.plaf.ViewportUI;
  14. import javax.swing.event.*;
  15. import javax.swing.border.*;
  16. import javax.accessibility.*;
  17. import java.io.Serializable;
  18. /**
  19. * The "viewport" or "porthole" through which you see the underlying
  20. * information. When you scroll, what moves is the viewport. It is like
  21. * peering through a camera's viewfinder. Moving the viewfinder upwards
  22. * brings new things into view at the top of the picture and loses
  23. * things that were at the bottom.
  24. * <p>
  25. * By default, <code>JViewport</code> is opaque. To change this, use the
  26. * <code>setOpaque</code> method.
  27. * <p>
  28. * <b>NOTE:</b>We have implemented a faster scrolling algorithm that
  29. * does not require a buffer to draw in. The algorithm works as follows:
  30. * <ol><li>The view and parent view and checked to see if they are
  31. * <code>JComponents</code>,
  32. * if they aren't, stop and repaint the whole viewport.
  33. * <li>If the viewport is obscured by an ancestor, stop and repaint the whole
  34. * viewport.
  35. * <li>Compute the region that will become visible, if it is as big as
  36. * the viewport, stop and repaint the whole view region.
  37. * <li>Obtain the ancestor <code>Window</code>'s graphics and
  38. * do a <code>copyArea</code> on the scrolled region.
  39. * <li>Message the view to repaint the newly visible region.
  40. * <li>The next time paint is invoked on the viewport, if the clip region
  41. * is smaller than the viewport size a timer is kicked off to repaint the
  42. * whole region.
  43. * </ol>
  44. * In general this approach is much faster. Compared to the backing store
  45. * approach this avoids the overhead of maintaining an offscreen buffer and
  46. * having to do two <code>copyArea</code>s.
  47. * Compared to the non backing store case this
  48. * approach will greatly reduce the painted region.
  49. * <p>
  50. * This approach can cause slower times than the backing store approach
  51. * when the viewport is obscured by another window, or partially offscreen.
  52. * When another window
  53. * obscures the viewport the copyArea will copy garbage and a
  54. * paint event will be generated by the system to inform us we need to
  55. * paint the newly exposed region. The only way to handle this is to
  56. * repaint the whole viewport, which can cause slower performance than the
  57. * backing store case. In most applications very rarely will the user be
  58. * scrolling while the viewport is obscured by another window or offscreen,
  59. * so this optimization is usually worth the performance hit when obscured.
  60. * <p>
  61. * <strong>Warning:</strong>
  62. * Serialized objects of this class will not be compatible with
  63. * future Swing releases. The current serialization support is
  64. * appropriate for short term storage or RMI between applications running
  65. * the same version of Swing. As of 1.4, support for long term storage
  66. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  67. * has been added to the <code>java.beans</code> package.
  68. * Please see {@link java.beans.XMLEncoder}.
  69. *
  70. * @version 1.114 05/18/04
  71. * @author Hans Muller
  72. * @author Philip Milne
  73. * @see JScrollPane
  74. */
  75. public class JViewport extends JComponent implements Accessible
  76. {
  77. /**
  78. * @see #getUIClassID
  79. * @see #readObject
  80. */
  81. private static final String uiClassID = "ViewportUI";
  82. /** Property used to indicate window blitting should not be done.
  83. */
  84. static final Object EnableWindowBlit = "EnableWindowBlit";
  85. /**
  86. * True when the viewport dimensions have been determined.
  87. * The default is false.
  88. */
  89. protected boolean isViewSizeSet = false;
  90. /**
  91. * The last <code>viewPosition</code> that we've painted, so we know how
  92. * much of the backing store image is valid.
  93. */
  94. protected Point lastPaintPosition = null;
  95. /**
  96. * True when this viewport is maintaining an offscreen image of its
  97. * contents, so that some scrolling can take place using fast "bit-blit"
  98. * operations instead of by accessing the view object to construct the
  99. * display. The default is <code>false</code>.
  100. *
  101. * @deprecated As of Java 2 platform v1.3
  102. * @see #setScrollMode
  103. */
  104. @Deprecated
  105. protected boolean backingStore = false;
  106. /** The view image used for a backing store. */
  107. transient protected Image backingStoreImage = null;
  108. /**
  109. * The <code>scrollUnderway</code> flag is used for components like
  110. * <code>JList</code>. When the downarrow key is pressed on a
  111. * <code>JList</code> and the selected
  112. * cell is the last in the list, the <code>scrollpane</code> autoscrolls.
  113. * Here, the old selected cell needs repainting and so we need
  114. * a flag to make the viewport do the optimized painting
  115. * only when there is an explicit call to
  116. * <code>setViewPosition(Point)</code>.
  117. * When <code>setBounds</code> is called through other routes,
  118. * the flag is off and the view repaints normally. Another approach
  119. * would be to remove this from the <code>JViewport</code>
  120. * class and have the <code>JList</code> manage this case by using
  121. * <code>setBackingStoreEnabled</code>. The default is
  122. * <code>false</code>.
  123. */
  124. protected boolean scrollUnderway = false;
  125. /*
  126. * Listener that is notified each time the view changes size.
  127. */
  128. private ComponentListener viewListener = null;
  129. /* Only one <code>ChangeEvent</code> is needed per
  130. * <code>JViewport</code> instance since the
  131. * event's only (read-only) state is the source property. The source
  132. * of events generated here is always "this".
  133. */
  134. private transient ChangeEvent changeEvent = null;
  135. /**
  136. * Use <code>graphics.copyArea</code> to implement scrolling.
  137. * This is the fastest for most applications.
  138. *
  139. * @see #setScrollMode
  140. * @since 1.3
  141. */
  142. public static final int BLIT_SCROLL_MODE = 1;
  143. /**
  144. * Draws viewport contents into an offscreen image.
  145. * This was previously the default mode for <code>JTable</code>.
  146. * This mode may offer advantages over "blit mode"
  147. * in some cases, but it requires a large chunk of extra RAM.
  148. *
  149. * @see #setScrollMode
  150. * @since 1.3
  151. */
  152. public static final int BACKINGSTORE_SCROLL_MODE = 2;
  153. /**
  154. * This mode uses the very simple method of redrawing the entire
  155. * contents of the scrollpane each time it is scrolled.
  156. * This was the default behavior in Swing 1.0 and Swing 1.1.
  157. * Either of the other two options will provide better performance
  158. * in most cases.
  159. *
  160. * @see #setScrollMode
  161. * @since 1.3
  162. */
  163. public static final int SIMPLE_SCROLL_MODE = 0;
  164. /**
  165. * @see #setScrollMode
  166. * @since 1.3
  167. */
  168. private int scrollMode = BLIT_SCROLL_MODE;
  169. //
  170. // Window blitting:
  171. //
  172. // As mentioned in the javadoc when using windowBlit a paint event
  173. // will be generated by the system if copyArea copies a non-visible
  174. // portion of the view (in other words, it copies garbage). We are
  175. // not guaranteed to receive the paint event before other mouse events,
  176. // so we can not be sure we haven't already copied garbage a bunch of
  177. // times to different parts of the view. For that reason when a blit
  178. // happens and the Component is obscured (the check for obscurity
  179. // is not supported on all platforms and is checked via ComponentPeer
  180. // methods) the ivar repaintAll is set to true. When paint is received
  181. // if repaintAll is true (we previously did a blit) it is set to
  182. // false, and if the clip region is smaller than the viewport
  183. // waitingForRepaint is set to true and a timer is started. When
  184. // the timer fires if waitingForRepaint is true, repaint is invoked.
  185. // In the mean time, if the view is asked to scroll and waitingForRepaint
  186. // is true, a blit will not happen, instead the non-backing store case
  187. // of scrolling will happen, which will reset waitingForRepaint.
  188. // waitingForRepaint is set to false in paint when the clip rect is
  189. // bigger (or equal) to the size of the viewport.
  190. // A Timer is used instead of just a repaint as it appeared to offer
  191. // better performance.
  192. /**
  193. * This is set to true in <code>setViewPosition</code>
  194. * if doing a window blit and the viewport is obscured.
  195. */
  196. private transient boolean repaintAll;
  197. /**
  198. * This is set to true in paint, if <code>repaintAll</code>
  199. * is true and the clip rectangle does not match the bounds.
  200. * If true, and scrolling happens the
  201. * repaint manager is not cleared which then allows for the repaint
  202. * previously invoked to succeed.
  203. */
  204. private transient boolean waitingForRepaint;
  205. /**
  206. * Instead of directly invoking repaint, a <code>Timer</code>
  207. * is started and when it fires, repaint is invoked.
  208. */
  209. private transient Timer repaintTimer;
  210. /**
  211. * Whether or not a valid view has been installed.
  212. */
  213. private boolean hasHadValidView;
  214. /** Creates a <code>JViewport</code>. */
  215. public JViewport() {
  216. super();
  217. setLayout(createLayoutManager());
  218. setOpaque(true);
  219. updateUI();
  220. }
  221. /**
  222. * Returns the L&F object that renders this component.
  223. *
  224. * @return a <code>ViewportUI</code> object
  225. */
  226. public ViewportUI getUI() {
  227. return (ViewportUI)ui;
  228. }
  229. /**
  230. * Sets the L&F object that renders this component.
  231. *
  232. * @param ui the <code>ViewportUI</code> L&F object
  233. * @see UIDefaults#getUI
  234. * @beaninfo
  235. * bound: true
  236. * hidden: true
  237. * attribute: visualUpdate true
  238. * description: The UI object that implements the Component's LookAndFeel.
  239. */
  240. public void setUI(ViewportUI ui) {
  241. super.setUI(ui);
  242. }
  243. /**
  244. * Resets the UI property to a value from the current look and feel.
  245. *
  246. * @see JComponent#updateUI
  247. */
  248. public void updateUI() {
  249. setUI((ViewportUI)UIManager.getUI(this));
  250. }
  251. /**
  252. * Returns a string that specifies the name of the L&F class
  253. * that renders this component.
  254. *
  255. * @return the string "ViewportUI"
  256. *
  257. * @see JComponent#getUIClassID
  258. * @see UIDefaults#getUI
  259. */
  260. public String getUIClassID() {
  261. return uiClassID;
  262. }
  263. /**
  264. * Sets the <code>JViewport</code>'s one lightweight child,
  265. * which can be <code>null</code>.
  266. * (Since there is only one child which occupies the entire viewport,
  267. * the <code>constraints</code> and <code>index</code>
  268. * arguments are ignored.)
  269. *
  270. * @param child the lightweight <code>child</code> of the viewport
  271. * @param constraints the <code>constraints</code> to be respected
  272. * @param index the index
  273. * @see #setView
  274. */
  275. protected void addImpl(Component child, Object constraints, int index) {
  276. setView(child);
  277. }
  278. /**
  279. * Removes the <code>Viewport</code>s one lightweight child.
  280. *
  281. * @see #setView
  282. */
  283. public void remove(Component child) {
  284. child.removeComponentListener(viewListener);
  285. super.remove(child);
  286. }
  287. /**
  288. * Scrolls the view so that <code>Rectangle</code>
  289. * within the view becomes visible.
  290. * <p>
  291. * This attempts to validate the view before scrolling if the
  292. * view is currently not valid - <code>isValid</code> returns false.
  293. * To avoid excessive validation when the containment hierarchy is
  294. * being created this will not validate if one of the ancestors does not
  295. * have a peer, or there is no validate root ancestor, or one of the
  296. * ancestors is not a <code>Window</code> or <code>Applet</code>.
  297. * <p>
  298. * Note that this method will not scroll outside of the
  299. * valid viewport; for example, if <code>contentRect</code> is larger
  300. * than the viewport, scrolling will be confined to the viewport's
  301. * bounds.
  302. *
  303. * @param contentRect the <code>Rectangle</code> to display
  304. * @see JComponent#isValidateRoot
  305. * @see java.awt.Component#isValid
  306. * @see java.awt.Component#getPeer
  307. */
  308. public void scrollRectToVisible(Rectangle contentRect) {
  309. Component view = getView();
  310. if (view == null) {
  311. return;
  312. } else {
  313. if (!view.isValid()) {
  314. // If the view is not valid, validate. scrollRectToVisible
  315. // may fail if the view is not valid first, contentRect
  316. // could be bigger than invalid size.
  317. validateView();
  318. }
  319. int dx = 0, dy = 0;
  320. dx = positionAdjustment(getWidth(), contentRect.width, contentRect.x);
  321. dy = positionAdjustment(getHeight(), contentRect.height, contentRect.y);
  322. if (dx != 0 || dy != 0) {
  323. Point viewPosition = getViewPosition();
  324. Dimension viewSize = view.getSize();
  325. int startX = viewPosition.x;
  326. int startY = viewPosition.y;
  327. Dimension extent = getExtentSize();
  328. viewPosition.x -= dx;
  329. viewPosition.y -= dy;
  330. // Only constrain the location if the view is valid. If the
  331. // the view isn't valid, it typically indicates the view
  332. // isn't visible yet and most likely has a bogus size as will
  333. // we, and therefore we shouldn't constrain the scrolling
  334. if (view.isValid()) {
  335. if (getParent().getComponentOrientation().isLeftToRight()) {
  336. if (viewPosition.x + extent.width > viewSize.width) {
  337. viewPosition.x = Math.max(0, viewSize.width - extent.width);
  338. } else if (viewPosition.x < 0) {
  339. viewPosition.x = 0;
  340. }
  341. } else {
  342. if (extent.width > viewSize.width) {
  343. viewPosition.x = viewSize.width - extent.width;
  344. } else {
  345. viewPosition.x = Math.max(0, Math.min(viewSize.width - extent.width, viewPosition.x));
  346. }
  347. }
  348. if (viewPosition.y + extent.height > viewSize.height) {
  349. viewPosition.y = Math.max(0, viewSize.height -
  350. extent.height);
  351. }
  352. else if (viewPosition.y < 0) {
  353. viewPosition.y = 0;
  354. }
  355. }
  356. if (viewPosition.x != startX || viewPosition.y != startY) {
  357. setViewPosition(viewPosition);
  358. // NOTE: How JViewport currently works with the
  359. // backing store is not foolproof. The sequence of
  360. // events when setViewPosition
  361. // (scrollRectToVisible) is called is to reset the
  362. // views bounds, which causes a repaint on the
  363. // visible region and sets an ivar indicating
  364. // scrolling (scrollUnderway). When
  365. // JViewport.paint is invoked if scrollUnderway is
  366. // true, the backing store is blitted. This fails
  367. // if between the time setViewPosition is invoked
  368. // and paint is received another repaint is queued
  369. // indicating part of the view is invalid. There
  370. // is no way for JViewport to notice another
  371. // repaint has occured and it ends up blitting
  372. // what is now a dirty region and the repaint is
  373. // never delivered.
  374. // It just so happens JTable encounters this
  375. // behavior by way of scrollRectToVisible, for
  376. // this reason scrollUnderway is set to false
  377. // here, which effectively disables the backing
  378. // store.
  379. scrollUnderway = false;
  380. }
  381. }
  382. }
  383. }
  384. /**
  385. * Ascends the <code>Viewport</code>'s parents stopping when
  386. * a component is found that returns
  387. * <code>true</code> to <code>isValidateRoot</code>.
  388. * If all the <code>Component</code>'s parents are visible,
  389. * <code>validate</code> will then be invoked on it. The
  390. * <code>RepaintManager</code> is then invoked with
  391. * <code>removeInvalidComponent</code>. This
  392. * is the synchronous version of a <code>revalidate</code>.
  393. */
  394. private void validateView() {
  395. Component validateRoot = null;
  396. /* Find the first JComponent ancestor of this component whose
  397. * isValidateRoot() method returns true.
  398. */
  399. for(Component c = this; c != null; c = c.getParent()) {
  400. if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
  401. return;
  402. }
  403. if ((c instanceof JComponent) &&
  404. (((JComponent)c).isValidateRoot())) {
  405. validateRoot = c;
  406. break;
  407. }
  408. }
  409. // If no validateRoot, nothing to validate from.
  410. if (validateRoot == null) {
  411. return;
  412. }
  413. // Make sure all ancestors are visible.
  414. Component root = null;
  415. for(Component c = validateRoot; c != null; c = c.getParent()) {
  416. // We don't check isVisible here, otherwise if the component
  417. // is contained in something like a JTabbedPane when the
  418. // component is made visible again it won't have scrolled
  419. // to the correct location.
  420. if (c.getPeer() == null) {
  421. return;
  422. }
  423. if ((c instanceof Window) || (c instanceof Applet)) {
  424. root = c;
  425. break;
  426. }
  427. }
  428. // Make sure there is a Window ancestor.
  429. if (root == null) {
  430. return;
  431. }
  432. // Validate the root.
  433. validateRoot.validate();
  434. // And let the RepaintManager it does not have to validate from
  435. // validateRoot anymore.
  436. RepaintManager rm = RepaintManager.currentManager(this);
  437. if (rm != null) {
  438. rm.removeInvalidComponent((JComponent)validateRoot);
  439. }
  440. }
  441. /* Used by the scrollRectToVisible method to determine the
  442. * proper direction and amount to move by. The integer variables are named
  443. * width, but this method is applicable to height also. The code assumes that
  444. * parentWidth/childWidth are positive and childAt can be negative.
  445. */
  446. private int positionAdjustment(int parentWidth, int childWidth, int childAt) {
  447. // +-----+
  448. // | --- | No Change
  449. // +-----+
  450. if (childAt >= 0 && childWidth + childAt <= parentWidth) {
  451. return 0;
  452. }
  453. // +-----+
  454. // --------- No Change
  455. // +-----+
  456. if (childAt <= 0 && childWidth + childAt >= parentWidth) {
  457. return 0;
  458. }
  459. // +-----+ +-----+
  460. // | ---- -> | ----|
  461. // +-----+ +-----+
  462. if (childAt > 0 && childWidth <= parentWidth) {
  463. return -childAt + parentWidth - childWidth;
  464. }
  465. // +-----+ +-----+
  466. // | -------- -> |--------
  467. // +-----+ +-----+
  468. if (childAt >= 0 && childWidth >= parentWidth) {
  469. return -childAt;
  470. }
  471. // +-----+ +-----+
  472. // ---- | -> |---- |
  473. // +-----+ +-----+
  474. if (childAt <= 0 && childWidth <= parentWidth) {
  475. return -childAt;
  476. }
  477. // +-----+ +-----+
  478. //-------- | -> --------|
  479. // +-----+ +-----+
  480. if (childAt < 0 && childWidth >= parentWidth) {
  481. return -childAt + parentWidth - childWidth;
  482. }
  483. return 0;
  484. }
  485. /**
  486. * The viewport "scrolls" its child (called the "view") by the
  487. * normal parent/child clipping (typically the view is moved in
  488. * the opposite direction of the scroll). A non-<code>null</code> border,
  489. * or non-zero insets, isn't supported, to prevent the geometry
  490. * of this component from becoming complex enough to inhibit
  491. * subclassing. To create a <code>JViewport</code> with a border,
  492. * add it to a <code>JPanel</code> that has a border.
  493. * <p>Note: If <code>border</code> is non-<code>null</code>, this
  494. * method will throw an exception as borders are not supported on
  495. * a <code>JViewPort</code>.
  496. *
  497. * @param border the <code>Border</code> to set
  498. * @exception IllegalArgumentException this method is not implemented
  499. */
  500. public final void setBorder(Border border) {
  501. if (border != null) {
  502. throw new IllegalArgumentException("JViewport.setBorder() not supported");
  503. }
  504. }
  505. /**
  506. * Returns the insets (border) dimensions as (0,0,0,0), since borders
  507. * are not supported on a <code>JViewport</code>.
  508. *
  509. * @return a <code>Rectange</code> of zero dimension and zero origin
  510. * @see #setBorder
  511. */
  512. public final Insets getInsets() {
  513. return new Insets(0, 0, 0, 0);
  514. }
  515. /**
  516. * Returns an <code>Insets</code> object containing this
  517. * <code>JViewport</code>s inset values. The passed-in
  518. * <code>Insets</code> object will be reinitialized, and
  519. * all existing values within this object are overwritten.
  520. *
  521. * @param insets the <code>Insets</code> object which can be reused
  522. * @return this viewports inset values
  523. * @see #getInsets
  524. * @beaninfo
  525. * expert: true
  526. */
  527. public final Insets getInsets(Insets insets) {
  528. insets.left = insets.top = insets.right = insets.bottom = 0;
  529. return insets;
  530. }
  531. private Graphics getBackingStoreGraphics(Graphics g) {
  532. Graphics bsg = backingStoreImage.getGraphics();
  533. bsg.setColor(g.getColor());
  534. bsg.setFont(g.getFont());
  535. bsg.setClip(g.getClipBounds());
  536. return bsg;
  537. }
  538. private void paintViaBackingStore(Graphics g) {
  539. Graphics bsg = getBackingStoreGraphics(g);
  540. try {
  541. super.paint(bsg);
  542. g.drawImage(backingStoreImage, 0, 0, this);
  543. } finally {
  544. bsg.dispose();
  545. }
  546. }
  547. private void paintViaBackingStore(Graphics g, Rectangle oClip) {
  548. Graphics bsg = getBackingStoreGraphics(g);
  549. try {
  550. super.paint(bsg);
  551. g.setClip(oClip);
  552. g.drawImage(backingStoreImage, 0, 0, this);
  553. } finally {
  554. bsg.dispose();
  555. }
  556. }
  557. /**
  558. * The <code>JViewport</code> overrides the default implementation of
  559. * this method (in <code>JComponent</code>) to return false.
  560. * This ensures
  561. * that the drawing machinery will call the <code>Viewport</code>'s
  562. * <code>paint</code>
  563. * implementation rather than messaging the <code>JViewport</code>'s
  564. * children directly.
  565. *
  566. * @return false
  567. */
  568. public boolean isOptimizedDrawingEnabled() {
  569. return false;
  570. }
  571. /**
  572. * Returns true if scroll mode is a BACKINGSTORE_SCROLL_MODE to cause
  573. * painting to originate from <code>JViewport</code>, or one of its
  574. * ancestors. Otherwise returns false.
  575. *
  576. * @return true if if scroll mode is a BACKINGSTORE_SCROLL_MODE.
  577. * @see JComponent#isPaintingOrigin()
  578. */
  579. boolean isPaintingOrigin() {
  580. if (scrollMode == BACKINGSTORE_SCROLL_MODE) {
  581. return true;
  582. }
  583. return false;
  584. }
  585. /**
  586. * Only used by the paint method below.
  587. */
  588. private Point getViewLocation() {
  589. Component view = getView();
  590. if (view != null) {
  591. return view.getLocation();
  592. }
  593. else {
  594. return new Point(0,0);
  595. }
  596. }
  597. /**
  598. * Depending on whether the <code>backingStore</code> is enabled,
  599. * either paint the image through the backing store or paint
  600. * just the recently exposed part, using the backing store
  601. * to "blit" the remainder.
  602. * <blockquote>
  603. * The term "blit" is the pronounced version of the PDP-10
  604. * BLT (BLock Transfer) instruction, which copied a block of
  605. * bits. (In case you were curious.)
  606. * </blockquote>
  607. *
  608. * @param g the <code>Graphics</code> context within which to paint
  609. */
  610. public void paint(Graphics g)
  611. {
  612. int width = getWidth();
  613. int height = getHeight();
  614. if ((width <= 0) || (height <= 0)) {
  615. return;
  616. }
  617. if (repaintAll) {
  618. repaintAll = false;
  619. Rectangle clipB = g.getClipBounds();
  620. if (clipB.width < getWidth() ||
  621. clipB.height < getHeight()) {
  622. waitingForRepaint = true;
  623. if (repaintTimer == null) {
  624. repaintTimer = createRepaintTimer();
  625. }
  626. repaintTimer.stop();
  627. repaintTimer.start();
  628. // We really don't need to paint, a future repaint will
  629. // take care of it, but if we don't we get an ugly flicker.
  630. }
  631. else {
  632. if (repaintTimer != null) {
  633. repaintTimer.stop();
  634. }
  635. waitingForRepaint = false;
  636. }
  637. }
  638. else if (waitingForRepaint) {
  639. // Need a complete repaint before resetting waitingForRepaint
  640. Rectangle clipB = g.getClipBounds();
  641. if (clipB.width >= getWidth() &&
  642. clipB.height >= getHeight()) {
  643. waitingForRepaint = false;
  644. repaintTimer.stop();
  645. }
  646. }
  647. if (!backingStore || isBlitting() || getView() == null) {
  648. super.paint(g);
  649. lastPaintPosition = getViewLocation();
  650. return;
  651. }
  652. // If the view is smaller than the viewport and we are not opaque
  653. // (that is, we won't paint our background), we should set the
  654. // clip. Otherwise, as the bounds of the view vary, we will
  655. // blit garbage into the exposed areas.
  656. Rectangle viewBounds = getView().getBounds();
  657. if (!isOpaque()) {
  658. g.clipRect(0, 0, viewBounds.width, viewBounds.height);
  659. }
  660. if (backingStoreImage == null) {
  661. // Backing store is enabled but this is the first call to paint.
  662. // Create the backing store, paint it and then copy to g.
  663. // The backing store image will be created with the size of
  664. // the viewport. We must make sure the clip region is the
  665. // same size, otherwise when scrolling the backing image
  666. // the region outside of the clipped region will not be painted,
  667. // and result in empty areas.
  668. backingStoreImage = createImage(width, height);
  669. Rectangle clip = g.getClipBounds();
  670. if (clip.width != width || clip.height != height) {
  671. if (!isOpaque()) {
  672. g.setClip(0, 0, Math.min(viewBounds.width, width),
  673. Math.min(viewBounds.height, height));
  674. }
  675. else {
  676. g.setClip(0, 0, width, height);
  677. }
  678. paintViaBackingStore(g, clip);
  679. }
  680. else {
  681. paintViaBackingStore(g);
  682. }
  683. }
  684. else {
  685. if (!scrollUnderway || lastPaintPosition.equals(getViewLocation())) {
  686. // No scrolling happened: repaint required area via backing store.
  687. paintViaBackingStore(g);
  688. } else {
  689. // The image was scrolled. Manipulate the backing store and flush it to g.
  690. Point blitFrom = new Point();
  691. Point blitTo = new Point();
  692. Dimension blitSize = new Dimension();
  693. Rectangle blitPaint = new Rectangle();
  694. Point newLocation = getViewLocation();
  695. int dx = newLocation.x - lastPaintPosition.x;
  696. int dy = newLocation.y - lastPaintPosition.y;
  697. boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint);
  698. if (!canBlit) {
  699. // The image was either moved diagonally or
  700. // moved by more than the image size: paint normally.
  701. paintViaBackingStore(g);
  702. } else {
  703. int bdx = blitTo.x - blitFrom.x;
  704. int bdy = blitTo.y - blitFrom.y;
  705. // Move the relevant part of the backing store.
  706. Rectangle clip = g.getClipBounds();
  707. // We don't want to inherit the clip region when copying
  708. // bits, if it is inherited it will result in not moving
  709. // all of the image resulting in garbage appearing on
  710. // the screen.
  711. g.setClip(0, 0, width, height);
  712. Graphics bsg = getBackingStoreGraphics(g);
  713. try {
  714. bsg.copyArea(blitFrom.x, blitFrom.y, blitSize.width, blitSize.height, bdx, bdy);
  715. g.setClip(clip.x, clip.y, clip.width, clip.height);
  716. // Paint the rest of the view; the part that has just been exposed.
  717. Rectangle r = viewBounds.intersection(blitPaint);
  718. bsg.setClip(r);
  719. super.paint(bsg);
  720. // Copy whole of the backing store to g.
  721. g.drawImage(backingStoreImage, 0, 0, this);
  722. } finally {
  723. bsg.dispose();
  724. }
  725. }
  726. }
  727. }
  728. lastPaintPosition = getViewLocation();
  729. scrollUnderway = false;
  730. }
  731. /**
  732. * Sets the bounds of this viewport. If the viewport's width
  733. * or height has changed, fire a <code>StateChanged</code> event.
  734. *
  735. * @param x left edge of the origin
  736. * @param y top edge of the origin
  737. * @param w width in pixels
  738. * @param h height in pixels
  739. *
  740. * @see JComponent#reshape(int, int, int, int)
  741. */
  742. public void reshape(int x, int y, int w, int h) {
  743. boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
  744. if (sizeChanged) {
  745. backingStoreImage = null;
  746. }
  747. super.reshape(x, y, w, h);
  748. if (sizeChanged) {
  749. fireStateChanged();
  750. }
  751. }
  752. /**
  753. * Used to control the method of scrolling the viewport contents.
  754. * You may want to change this mode to get maximum performance for your
  755. * use case.
  756. *
  757. * @param mode one of the following values:
  758. * <ul>
  759. * <li> JViewport.BLIT_SCROLL_MODE
  760. * <li> JViewport.BACKINGSTORE_SCROLL_MODE
  761. * <li> JViewport.SIMPLE_SCROLL_MODE
  762. * </ul>
  763. *
  764. * @see #BLIT_SCROLL_MODE
  765. * @see #BACKINGSTORE_SCROLL_MODE
  766. * @see #SIMPLE_SCROLL_MODE
  767. *
  768. * @beaninfo
  769. * bound: false
  770. * description: Method of moving contents for incremental scrolls.
  771. * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE
  772. * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE
  773. * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE
  774. *
  775. * @since 1.3
  776. */
  777. public void setScrollMode(int mode) {
  778. scrollMode = mode;
  779. if (mode == BACKINGSTORE_SCROLL_MODE) {
  780. backingStore = true;
  781. } else {
  782. backingStore = false;
  783. }
  784. }
  785. /**
  786. * Returns the current scrolling mode.
  787. *
  788. * @return the <code>scrollMode</code> property
  789. * @see #setScrollMode
  790. * @since 1.3
  791. */
  792. public int getScrollMode() {
  793. return scrollMode;
  794. }
  795. /**
  796. * Returns <code>true</code> if this viewport is maintaining
  797. * an offscreen image of its contents.
  798. *
  799. * @return <code>true</code> if <code>scrollMode</code> is
  800. * <code>BACKINGSTORE_SCROLL_MODE</code>
  801. *
  802. * @deprecated As of Java 2 platform v1.3, replaced by
  803. * <code>getScrollMode()</code>.
  804. */
  805. @Deprecated
  806. public boolean isBackingStoreEnabled() {
  807. return scrollMode == BACKINGSTORE_SCROLL_MODE;
  808. }
  809. /**
  810. * If true if this viewport will maintain an offscreen
  811. * image of its contents. The image is used to reduce the cost
  812. * of small one dimensional changes to the <code>viewPosition</code>.
  813. * Rather than repainting the entire viewport we use
  814. * <code>Graphics.copyArea</code> to effect some of the scroll.
  815. *
  816. * @param enabled if true, maintain an offscreen backing store
  817. *
  818. * @deprecated As of Java 2 platform v1.3, replaced by
  819. * <code>setScrollMode()</code>.
  820. */
  821. @Deprecated
  822. public void setBackingStoreEnabled(boolean enabled) {
  823. if (enabled) {
  824. setScrollMode(BACKINGSTORE_SCROLL_MODE);
  825. } else {
  826. setScrollMode(BLIT_SCROLL_MODE);
  827. }
  828. }
  829. private final boolean isBlitting() {
  830. Component view = getView();
  831. return (scrollMode == BLIT_SCROLL_MODE) &&
  832. (view instanceof JComponent) && ((JComponent)view).isOpaque();
  833. }
  834. /**
  835. * Returns the <code>JViewport</code>'s one child or <code>null</code>.
  836. *
  837. * @return the viewports child, or <code>null</code> if none exists
  838. *
  839. * @see #setView
  840. */
  841. public Component getView() {
  842. try {
  843. return getComponent(0);
  844. } catch (ArrayIndexOutOfBoundsException e) {
  845. return null;
  846. }
  847. }
  848. /**
  849. * Sets the <code>JViewport</code>'s one lightweight child
  850. * (<code>view</code>), which can be <code>null</code>.
  851. *
  852. * @param view the viewport's new lightweight child
  853. *
  854. * @see #getView
  855. */
  856. public void setView(Component view) {
  857. /* Remove the viewport's existing children, if any.
  858. * Note that removeAll() isn't used here because it
  859. * doesn't call remove() (which JViewport overrides).
  860. */
  861. int n = getComponentCount();
  862. for(int i = n - 1; i >= 0; i--) {
  863. remove(getComponent(i));
  864. }
  865. isViewSizeSet = false;
  866. if (view != null) {
  867. super.addImpl(view, null, -1);
  868. viewListener = createViewListener();
  869. view.addComponentListener(viewListener);
  870. }
  871. if (hasHadValidView) {
  872. // Only fire a change if a view has been installed.
  873. fireStateChanged();
  874. }
  875. else if (view != null) {
  876. hasHadValidView = true;
  877. }
  878. revalidate();
  879. repaint();
  880. }
  881. /**
  882. * If the view's size hasn't been explicitly set, return the
  883. * preferred size, otherwise return the view's current size.
  884. * If there is no view, return 0,0.
  885. *
  886. * @return a <code>Dimension</code> object specifying the size of the view
  887. */
  888. public Dimension getViewSize() {
  889. Component view = getView();
  890. if (view == null) {
  891. return new Dimension(0,0);
  892. }
  893. else if (isViewSizeSet) {
  894. return view.getSize();
  895. }
  896. else {
  897. return view.getPreferredSize();
  898. }
  899. }
  900. /**
  901. * Sets the size of the view. A state changed event will be fired.
  902. *
  903. * @param newSize a <code>Dimension</code> object specifying the new
  904. * size of the view
  905. */
  906. public void setViewSize(Dimension newSize) {
  907. Component view = getView();
  908. if (view != null) {
  909. Dimension oldSize = view.getSize();
  910. if (!newSize.equals(oldSize)) {
  911. // scrollUnderway will be true if this is invoked as the
  912. // result of a validate and setViewPosition was previously
  913. // invoked.
  914. scrollUnderway = false;
  915. view.setSize(newSize);
  916. isViewSizeSet = true;
  917. fireStateChanged();
  918. }
  919. }
  920. }
  921. /**
  922. * Returns the view coordinates that appear in the upper left
  923. * hand corner of the viewport, or 0,0 if there's no view.
  924. *
  925. * @return a <code>Point</code> object giving the upper left coordinates
  926. */
  927. public Point getViewPosition() {
  928. Component view = getView();
  929. if (view != null) {
  930. Point p = view.getLocation();
  931. p.x = -p.x;
  932. p.y = -p.y;
  933. return p;
  934. }
  935. else {
  936. return new Point(0,0);
  937. }
  938. }
  939. /**
  940. * Sets the view coordinates that appear in the upper left
  941. * hand corner of the viewport, does nothing if there's no view.
  942. *
  943. * @param p a <code>Point</code> object giving the upper left coordinates
  944. */
  945. public void setViewPosition(Point p)
  946. {
  947. Component view = getView();
  948. if (view == null) {
  949. return;
  950. }
  951. int oldX, oldY, x = p.x, y = p.y;
  952. /* Collect the old x,y values for the views location
  953. * and do the song and dance to avoid allocating
  954. * a Rectangle object if we don't have to.
  955. */
  956. if (view instanceof JComponent) {
  957. JComponent c = (JComponent)view;
  958. oldX = c.getX();
  959. oldY = c.getY();
  960. }
  961. else {
  962. Rectangle r = view.getBounds();
  963. oldX = r.x;
  964. oldY = r.y;
  965. }
  966. /* The view scrolls in the opposite direction to mouse
  967. * movement.
  968. */
  969. int newX = -x;
  970. int newY = -y;
  971. if ((oldX != newX) || (oldY != newY)) {
  972. if (!waitingForRepaint && isBlitting() && canUseWindowBlitter()) {
  973. Graphics g = getGraphics();
  974. flushViewDirtyRegion(g);
  975. // This calls setBounds(), and then repaint().
  976. view.setLocation(newX, newY);
  977. // The cast to JComponent here is valid, if view is not
  978. // a JComponent, isBlitting will return false.
  979. g.setClip(0,0,getWidth(), Math.min(getHeight(),
  980. ((JComponent)view).getHeight()));
  981. // Repaint the complete component if the blit succeeded
  982. // and needsRepaintAfterBlit returns true.
  983. repaintAll = (windowBlitPaint(g) &&
  984. needsRepaintAfterBlit());
  985. g.dispose();
  986. RepaintManager rm = RepaintManager.currentManager(this);
  987. rm.markCompletelyClean((JComponent)getParent());
  988. rm.markCompletelyClean(this);
  989. rm.markCompletelyClean((JComponent)view);
  990. }
  991. else {
  992. scrollUnderway = true;
  993. // This calls setBounds(), and then repaint().
  994. view.setLocation(newX, newY);
  995. repaintAll = false;
  996. }
  997. fireStateChanged();
  998. }
  999. }
  1000. /**
  1001. * Returns a rectangle whose origin is <code>getViewPosition</code>
  1002. * and size is <code>getExtentSize</code>.
  1003. * This is the visible part of the view, in view coordinates.
  1004. *
  1005. * @return a <code>Rectangle</code> giving the visible part of
  1006. * the view using view coordinates.
  1007. */
  1008. public Rectangle getViewRect() {
  1009. return new Rectangle(getViewPosition(), getExtentSize());
  1010. }
  1011. /**
  1012. * Computes the parameters for a blit where the backing store image
  1013. * currently contains <code>oldLoc</code> in the upper left hand corner
  1014. * and we're scrolling to <code>newLoc</code>.
  1015. * The parameters are modified
  1016. * to return the values required for the blit.
  1017. *
  1018. * @param dx the horizontal delta
  1019. * @param dy the vertical delta
  1020. * @param blitFrom the <code>Point</code> we're blitting from
  1021. * @param blitTo the <code>Point</code> we're blitting to
  1022. * @param blitSize the <code>Dimension</code> of the area to blit
  1023. * @param blitPaint the area to blit
  1024. * @return true if the parameters are modified and we're ready to blit;
  1025. * false otherwise
  1026. */
  1027. protected boolean computeBlit(
  1028. int dx,
  1029. int dy,
  1030. Point blitFrom,
  1031. Point blitTo,
  1032. Dimension blitSize,
  1033. Rectangle blitPaint)
  1034. {
  1035. int dxAbs = Math.abs(dx);
  1036. int dyAbs = Math.abs(dy);
  1037. Dimension extentSize = getExtentSize();
  1038. if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
  1039. if (dy < 0) {
  1040. blitFrom.y = -dy;
  1041. blitTo.y = 0;
  1042. blitPaint.y = extentSize.height + dy;
  1043. }
  1044. else {
  1045. blitFrom.y = 0;
  1046. blitTo.y = dy;
  1047. blitPaint.y = 0;
  1048. }
  1049. blitPaint.x = blitFrom.x = blitTo.x = 0;
  1050. blitSize.width = extentSize.width;
  1051. blitSize.height = extentSize.height - dyAbs;
  1052. blitPaint.width = extentSize.width;
  1053. blitPaint.height = dyAbs;
  1054. return true;
  1055. }
  1056. else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) {
  1057. if (dx < 0) {
  1058. blitFrom.x = -dx;
  1059. blitTo.x = 0;
  1060. blitPaint.x = extentSize.width + dx;
  1061. }
  1062. else {
  1063. blitFrom.x = 0;
  1064. blitTo.x = dx;
  1065. blitPaint.x = 0;
  1066. }
  1067. blitPaint.y = blitFrom.y = blitTo.y = 0;
  1068. blitSize.width = extentSize.width - dxAbs;
  1069. blitSize.height = extentSize.height;
  1070. blitPaint.y = 0;
  1071. blitPaint.width = dxAbs;
  1072. blitPaint.height = extentSize.height;
  1073. return true;
  1074. }
  1075. else {
  1076. return false;
  1077. }
  1078. }
  1079. /**
  1080. * Returns the size of the visible part of the view in view coordinates.
  1081. *
  1082. * @return a <code>Dimension</code> object giving the size of the view
  1083. */
  1084. public Dimension getExtentSize() {
  1085. return getSize();
  1086. }
  1087. /**
  1088. * Converts a size in pixel coordinates to view coordinates.
  1089. * Subclasses of viewport that support "logical coordinates"
  1090. * will override this method.
  1091. *
  1092. * @param size a <code>Dimension</code> object using pixel coordinates
  1093. * @return a <code>Dimension</code> object converted to view coordinates
  1094. */
  1095. public Dimension toViewCoordinates(Dimension size) {
  1096. return new Dimension(size);
  1097. }
  1098. /**
  1099. * Converts a point in pixel coordinates to view coordinates.
  1100. * Subclasses of viewport that support "logical coordinates"
  1101. * will override this method.
  1102. *
  1103. * @param p a <code>Point</code> object using pixel coordinates
  1104. * @return a <code>Point</code> object converted to view coordinates
  1105. */
  1106. public Point toViewCoordinates(Point p) {
  1107. return new Point(p);
  1108. }
  1109. /**
  1110. * Sets the size of the visible part of the view using view coordinates.
  1111. *
  1112. * @param newExtent a <code>Dimension</code> object specifying
  1113. * the size of the view
  1114. */
  1115. public void setExtentSize(Dimension newExtent) {
  1116. Dimension oldExtent = getExtentSize();
  1117. if (!newExtent.equals(oldExtent)) {
  1118. setSize(newExtent);
  1119. fireStateChanged();
  1120. }
  1121. }
  1122. /**
  1123. * A listener for the view.
  1124. * <p>
  1125. * <strong>Warning:</strong>
  1126. * Serialized objects of this class will not be compatible with
  1127. * future Swing releases. The current serialization support is
  1128. * appropriate for short term storage or RMI between applications running
  1129. * the same version of Swing. As of 1.4, support for long term storage
  1130. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1131. * has been added to the <code>java.beans</code> package.
  1132. * Please see {@link java.beans.XMLEncoder}.
  1133. */
  1134. protected class ViewListener extends ComponentAdapter implements Serializable
  1135. {
  1136. public void componentResized(ComponentEvent e) {
  1137. fireStateChanged();
  1138. revalidate();
  1139. }
  1140. }
  1141. /**
  1142. * Creates a listener for the view.
  1143. * @return a <code>ViewListener</code>
  1144. */
  1145. protected ViewListener createViewListener() {
  1146. return new ViewListener();
  1147. }
  1148. /**
  1149. * Subclassers can override this to install a different
  1150. * layout manager (or <code>null</code>) in the constructor. Returns
  1151. * the <code>LayoutManager</code> to install on the <code>JViewport</code>.
  1152. *
  1153. * @return a <code>LayoutManager</code>
  1154. */
  1155. protected LayoutManager createLayoutManager() {
  1156. return ViewportLayout.SHARED_INSTANCE;
  1157. }
  1158. /**
  1159. * Adds a <code>ChangeListener</code> to the list that is
  1160. * notified each time the view's
  1161. * size, position, or the viewport's extent size has changed.
  1162. *
  1163. * @param l the <code>ChangeListener</code> to add
  1164. * @see #removeChangeListener
  1165. * @see #setViewPosition
  1166. * @see #setViewSize
  1167. * @see #setExtentSize
  1168. */
  1169. public void addChangeListener(ChangeListener l) {
  1170. listenerList.add(ChangeListener.class, l);
  1171. }
  1172. /**
  1173. * Removes a <code>ChangeListener</code> from the list that's notified each
  1174. * time the views size, position, or the viewports extent size
  1175. * has changed.
  1176. *
  1177. * @param l the <code>ChangeListener</code> to remove
  1178. * @see #addChangeListener
  1179. */
  1180. public void removeChangeListener(ChangeListener l) {
  1181. listenerList.remove(ChangeListener.class, l);
  1182. }
  1183. /**
  1184. * Returns an array of all the <code>ChangeListener</code>s added
  1185. * to this JViewport with addChangeListener().
  1186. *
  1187. * @return all of the <code>ChangeListener</code>s added or an empty
  1188. * array if no listeners have been added
  1189. * @since 1.4
  1190. */
  1191. public ChangeListener[] getChangeListeners() {
  1192. return (ChangeListener[])listenerList.getListeners(
  1193. ChangeListener.class);
  1194. }
  1195. /**
  1196. * Notifies all <code>ChangeListeners</code> when the views
  1197. * size, position, or the viewports extent size has changed.
  1198. *
  1199. * @see #addChangeListener
  1200. * @see #removeChangeListener
  1201. * @see EventListenerList
  1202. */
  1203. protected void fireStateChanged()
  1204. {
  1205. Object[] listeners = listenerList.getListenerList();
  1206. for (int i = listeners.length - 2; i >= 0; i -= 2) {
  1207. if (listeners[i] == ChangeListener.class) {
  1208. if (changeEvent == null) {
  1209. changeEvent = new ChangeEvent(this);
  1210. }
  1211. ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
  1212. }
  1213. }
  1214. }
  1215. /**
  1216. * Always repaint in the parents coordinate system to make sure
  1217. * only one paint is performed by the <code>RepaintManager</code>.
  1218. *
  1219. * @param tm maximum time in milliseconds before update
  1220. * @param x the <code>x</code> coordinate (pixels over from left)
  1221. * @param y the <code>y</code> coordinate (pixels down from top)
  1222. * @param w the width
  1223. * @param h the height
  1224. * @see java.awt.Component#update(java.awt.Graphics)
  1225. */
  1226. public void repaint(long tm, int x, int y, int w, int h) {
  1227. Container parent = getParent();
  1228. if(parent != null)
  1229. parent.repaint(tm,x+getX(),y+getY(),w,h);
  1230. else
  1231. super.repaint(tm,x,y,w,h);
  1232. }
  1233. /**
  1234. * Returns a string representation of this <code>JViewport</code>.
  1235. * This method
  1236. * is intended to be used only for debugging purposes, and the
  1237. * content and format of the returned string may vary between
  1238. * implementations. The returned string may be empty but may not
  1239. * be <code>null</code>.
  1240. *
  1241. * @return a string representation of this <code>JViewport</code>
  1242. */
  1243. protected String paramString() {
  1244. String isViewSizeSetString = (isViewSizeSet ?
  1245. "true" : "false");
  1246. String lastPaintPositionString = (lastPaintPosition != null ?
  1247. lastPaintPosition.toString() : "");
  1248. String scrollUnderwayString = (scrollUnderway ?
  1249. "true" : "false");
  1250. return super.paramString() +
  1251. ",isViewSizeSet=" + isViewSizeSetString +
  1252. ",lastPaintPosition=" + lastPaintPositionString +
  1253. ",scrollUnderway=" + scrollUnderwayString;
  1254. }
  1255. //
  1256. // Following is used when doBlit is true.
  1257. //
  1258. /**
  1259. * Notifies listeners of a property change. This is subclassed to update
  1260. * the <code>windowBlit</code> property.
  1261. * (The <code>putClientProperty</code> property is final).
  1262. *
  1263. * @param propertyName a string containing the property name
  1264. * @param oldValue the old value of the property
  1265. * @param newValue the new value of the property
  1266. */
  1267. protected void firePropertyChange(String propertyName, Object oldValue,
  1268. Object newValue) {
  1269. super.firePropertyChange(propertyName, oldValue, newValue);
  1270. if (propertyName.equals(EnableWindowBlit)) {
  1271. if (newValue != null) {
  1272. setScrollMode(BLIT_SCROLL_MODE);
  1273. } else {
  1274. setScrollMode(SIMPLE_SCROLL_MODE);
  1275. }
  1276. }
  1277. }
  1278. /**
  1279. * Returns true if the component needs to be completely repainted after
  1280. * a blit and a paint is received.
  1281. */
  1282. private boolean needsRepaintAfterBlit() {
  1283. // Find the first heavy weight ancestor. isObscured and
  1284. // canDetermineObscurity are only appropriate for heavy weights.
  1285. Component heavyParent = getParent();
  1286. while (heavyParent != null && heavyParent.isLightweight()) {
  1287. heavyParent = heavyParent.getParent();
  1288. }
  1289. if (heavyParent != null) {
  1290. ComponentPeer peer = heavyParent.getPeer();
  1291. if (peer != null && peer.canDetermineObscurity() &&
  1292. !peer.isObscured()) {
  1293. // The peer says we aren't obscured, therefore we can assume
  1294. // that we won't later be messaged to paint a portion that
  1295. // we tried to blit that wasn't valid.
  1296. // It is certainly possible that when we blited we were
  1297. // obscured, and by the time this is invoked we aren't, but the
  1298. // chances of that happening are pretty slim.
  1299. return false;
  1300. }
  1301. }
  1302. return true;
  1303. }
  1304. private Timer createRepaintTimer() {
  1305. Timer timer = new Timer(300, new ActionListener() {
  1306. public void actionPerformed(ActionEvent ae) {
  1307. // waitingForRepaint will be false if a paint came down
  1308. // with the complete clip rect, in which case we don't
  1309. // have to cause a repaint.
  1310. if (waitingForRepaint) {
  1311. repaint();
  1312. }
  1313. }
  1314. });
  1315. timer.setRepeats(false);
  1316. return timer;
  1317. }
  1318. /**
  1319. * If the repaint manager has a dirty region for the view, the view is
  1320. * asked to paint.
  1321. *
  1322. * @param g the <code>Graphics</code> context within which to paint
  1323. */
  1324. private void flushViewDirtyRegion(Graphics g) {
  1325. RepaintManager rm = RepaintManager.currentManager(this);
  1326. JComponent view = (JComponent) getView();
  1327. Rectangle dirty;
  1328. dirty = rm.getDirtyRegion(view);
  1329. if(dirty != null && dirty.width > 0 && dirty.height > 0) {
  1330. dirty.x += view.getX();
  1331. dirty.y += view.getY();
  1332. Rectangle clip = g.getClipBounds();
  1333. if (clip == null) {
  1334. // Only happens in 1.2
  1335. g.setClip(0, 0, getWidth(), getHeight());
  1336. }
  1337. g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
  1338. clip = g.getClipBounds();
  1339. // Only paint the dirty region if it is visible.
  1340. if (clip.width > 0 && clip.height > 0) {
  1341. paintView(g);
  1342. }
  1343. }
  1344. }
  1345. /**
  1346. * Used when blitting.
  1347. *
  1348. * @param g the <code>Graphics</code> context within which to paint
  1349. * @return true if blitting succeeded; otherwise false
  1350. */
  1351. private boolean windowBlitPaint(Graphics g) {
  1352. int width = getWidth();
  1353. int height = getHeight();
  1354. if ((width == 0) || (height == 0)) {
  1355. return false;
  1356. }
  1357. boolean retValue;
  1358. RepaintManager rm = RepaintManager.currentManager(this);
  1359. JComponent view = (JComponent) getView();
  1360. if (lastPaintPosition == null ||
  1361. lastPaintPosition.equals(getViewLocation())) {
  1362. paintView(g);
  1363. retValue = false;
  1364. } else {
  1365. // The image was scrolled. Manipulate the backing store and flush
  1366. // it to g.
  1367. Point blitFrom = new Point();
  1368. Point blitTo = new Point();
  1369. Dimension blitSize = new Dimension();
  1370. Rectangle blitPaint = new Rectangle();
  1371. Point newLocation = getViewLocation();
  1372. int dx = newLocation.x - lastPaintPosition.x;
  1373. int dy = newLocation.y - lastPaintPosition.y;
  1374. boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize,
  1375. blitPaint);
  1376. if (!canBlit) {
  1377. paintView(g);
  1378. retValue = false;
  1379. } else {
  1380. // Prepare the rest of the view; the part that has just been
  1381. // exposed.
  1382. Rectangle r = view.getBounds().intersection(blitPaint);
  1383. r.x -= view.getX();
  1384. r.y -= view.getY();
  1385. // Attempt to use VolatileImage buffer for maximum performance.
  1386. // If for any reason this fails (which should be rare), fallback to
  1387. // plain old Image buffer.
  1388. //
  1389. boolean paintCompleted = false;
  1390. Image off = null;
  1391. if (rm.useVolatileDoubleBuffer() &&
  1392. (off = rm.getVolatileOffscreenBuffer(this,getWidth(),getHeight())) != null) {
  1393. VolatileImage vImage = (java.awt.image.VolatileImage)off;
  1394. GraphicsConfiguration gc = view.getGraphicsConfiguration();
  1395. for(int i = 0; !paintCompleted && i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
  1396. if (vImage.validate(gc) ==
  1397. VolatileImage.IMAGE_INCOMPATIBLE)
  1398. {
  1399. rm.resetVolatileDoubleBuffer(gc);
  1400. off = rm.getVolatileOffscreenBuffer(this,getWidth(),getHeight());
  1401. vImage = (java.awt.image.VolatileImage)off;
  1402. }
  1403. blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height,
  1404. blitFrom.x, blitFrom.y, blitTo.x, blitTo.y,
  1405. blitSize.width, blitSize.height, off);
  1406. paintCompleted = !(vImage.contentsLost());
  1407. }
  1408. }
  1409. if (!paintCompleted) {
  1410. off = rm.getOffscreenBuffer(this, getWidth(), getHeight());
  1411. blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height,
  1412. blitFrom.x, blitFrom.y, blitTo.x, blitTo.y,
  1413. blitSize.width, blitSize.height, off);
  1414. paintCompleted = true;
  1415. }
  1416. retValue = true;
  1417. }
  1418. }
  1419. lastPaintPosition = getViewLocation();
  1420. return retValue;
  1421. }
  1422. private void blitDoubleBuffered(JComponent view, Graphics g,
  1423. int clipX, int clipY, int clipW, int clipH,
  1424. int blitFromX, int blitFromY, int blitToX, int blitToY,
  1425. int blitW, int blitH, Image off) {
  1426. RepaintManager rm = RepaintManager.currentManager(this);
  1427. boolean isDBE = rm.isDoubleBufferingEnabled();
  1428. int bdx = blitToX - blitFromX;
  1429. int bdy = blitToY - blitFromY;
  1430. Graphics og = off.getGraphics();
  1431. og.translate(-clipX,-clipY);
  1432. og.setClip(clipX,clipY,clipW,clipH);
  1433. rm.setDoubleBufferingEnabled(false);
  1434. view.paint(og);
  1435. rm.setDoubleBufferingEnabled(isDBE);
  1436. // Move the relevant part of the backing store.
  1437. blitWindowGraphics(blitFromX, blitFromY, blitW, blitH, bdx, bdy);
  1438. clipX += view.getX();
  1439. clipY += view.getY();
  1440. g.setClip(clipX,clipY,clipW,clipH);
  1441. g.drawImage(off,clipX,clipY,null);
  1442. og.dispose();
  1443. }
  1444. /**
  1445. * Called to paint the view, usually when <code>blitPaint</code>
  1446. * can not blit.
  1447. *
  1448. * @param g the <code>Graphics</code> context within which to paint
  1449. */
  1450. private void paintView(Graphics g) {
  1451. Rectangle r = g.getClipBounds();
  1452. RepaintManager rm = RepaintManager.currentManager(this);
  1453. JComponent view = (JComponent) getView();
  1454. r.x -= view.getX();
  1455. r.y -= view.getY();
  1456. // Attempt to use VolatileImage buffer for maximum performance.
  1457. // If for any reason this fails (which should be rare), fallback to
  1458. // plain old Image buffer.
  1459. //
  1460. boolean paintCompleted = false;
  1461. Image off = null;
  1462. if (rm.useVolatileDoubleBuffer() &&
  1463. (off = rm.getVolatileOffscreenBuffer(this,r.width,r.height)) != null) {
  1464. VolatileImage vImage = (java.awt.image.VolatileImage)off;
  1465. GraphicsConfiguration gc = view.getGraphicsConfiguration();
  1466. for(int i=0; !paintCompleted && i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
  1467. if (vImage.validate(gc) ==
  1468. VolatileImage.IMAGE_INCOMPATIBLE)
  1469. {
  1470. rm.resetVolatileDoubleBuffer(gc);
  1471. off = rm.getVolatileOffscreenBuffer(this,getWidth(),getHeight());
  1472. vImage = (java.awt.image.VolatileImage)off;
  1473. }
  1474. paintViewDoubleBuffered(view, g, r.x, r.y, r.width, r.height, off);
  1475. paintCompleted = !(vImage.contentsLost());
  1476. }
  1477. }
  1478. if (!paintCompleted) {
  1479. off = rm.getOffscreenBuffer(this,r.width,r.height);
  1480. paintViewDoubleBuffered(view, g, r.x, r.y, r.width, r.height, off);
  1481. paintCompleted = true;
  1482. }
  1483. }
  1484. private void paintViewDoubleBuffered(JComponent view, Graphics g,
  1485. int clipX, int clipY, int clipW, int clipH, Image off) {
  1486. RepaintManager rm = RepaintManager.currentManager(this);
  1487. boolean isDBE = rm.isDoubleBufferingEnabled();
  1488. Graphics og = off.getGraphics();
  1489. if (view.getWidth() < clipW) {
  1490. og.setColor(getBackground());
  1491. og.fillRect(0,0,clipW,clipH);
  1492. }
  1493. og.translate(-clipX, -clipY);
  1494. og.setClip(clipX, clipY, clipW, clipH);
  1495. rm.setDoubleBufferingEnabled(false);
  1496. view.paint(og);
  1497. rm.setDoubleBufferingEnabled(isDBE);
  1498. g.drawImage(off, clipX + view.getX(), clipY + view.getY(), null);
  1499. og.dispose();
  1500. }
  1501. /**
  1502. * Blits the parent windows graphics from the given region offset
  1503. * to <code>ox</code>, <code>oy</code>.
  1504. */
  1505. private void blitWindowGraphics(int x, int y, int w, int h, int ox,
  1506. int oy) {
  1507. Container parent;
  1508. for(parent = getParent() ; isLightweightComponent(parent) ;
  1509. parent = parent.getParent());
  1510. Graphics wg = parent.getGraphics();
  1511. Rectangle r = new Rectangle(x,y,w,h);
  1512. r = SwingUtilities.convertRectangle(this, r, parent);
  1513. wg.copyArea(r.x,r.y,r.width,r.height, ox, oy);
  1514. wg.dispose();
  1515. }
  1516. /**
  1517. * Returns true if the viewport is not obscured by one of its ancestors,
  1518. * or its ancestors children and if the viewport is showing. Blitting
  1519. * when the view isn't showing will work,
  1520. * or rather <code>copyArea</code> will work,
  1521. * but will not produce the expected behavior.
  1522. */
  1523. private boolean canUseWindowBlitter() {
  1524. if (!isShowing() || (!(getParent() instanceof JComponent) &&
  1525. !(getView() instanceof JComponent))) {
  1526. return false;
  1527. }
  1528. if (isPainting()) {
  1529. // We're in the process of painting, don't blit. If we were
  1530. // to blit we would draw on top of what we're already drawing,
  1531. // so bail.
  1532. return false;
  1533. }
  1534. Rectangle dirtyRegion = RepaintManager.currentManager(this).
  1535. getDirtyRegion((JComponent)getParent());
  1536. if (dirtyRegion != null && dirtyRegion.width > 0 &&
  1537. dirtyRegion.height > 0) {
  1538. // Part of the scrollpane needs to be repainted too, don't blit.
  1539. return false;
  1540. }
  1541. Rectangle clip = new Rectangle(0,0,getWidth(),getHeight());
  1542. Rectangle oldClip = new Rectangle();
  1543. Rectangle tmp2 = null;
  1544. Container parent;
  1545. Component lastParent = null;
  1546. int x, y, w, h;
  1547. for(parent = this; parent != null && isLightweightComponent(parent); parent = parent.getParent()) {
  1548. x = parent.getX();
  1549. y = parent.getY();
  1550. w = parent.getWidth();
  1551. h = parent.getHeight();
  1552. oldClip.setBounds(clip);
  1553. SwingUtilities.computeIntersection(0, 0, w, h, clip);
  1554. if(!clip.equals(oldClip))
  1555. return false;
  1556. if(lastParent != null && parent instanceof JComponent &&
  1557. !((JComponent)parent).isOptimizedDrawingEnabled()) {
  1558. Component comps[] = parent.getComponents();
  1559. int index = 0;
  1560. for(int i = comps.length - 1 ;i >= 0; i--) {
  1561. if(comps[i] == lastParent) {
  1562. index = i - 1;
  1563. break;
  1564. }
  1565. }
  1566. while(index >= 0) {
  1567. tmp2 = comps[index].getBounds(tmp2);
  1568. if(tmp2.intersects(clip))
  1569. return false;
  1570. index--;
  1571. }
  1572. }
  1573. clip.x += x;
  1574. clip.y += y;
  1575. lastParent = parent;
  1576. }
  1577. if (parent == null) {
  1578. // No Window parent.
  1579. return false;
  1580. }
  1581. return true;
  1582. }
  1583. /////////////////
  1584. // Accessibility support
  1585. ////////////////
  1586. /**
  1587. * Gets the AccessibleContext associated with this JViewport.
  1588. * For viewports, the AccessibleContext takes the form of an
  1589. * AccessibleJViewport.
  1590. * A new AccessibleJViewport instance is created if necessary.
  1591. *
  1592. * @return an AccessibleJViewport that serves as the
  1593. * AccessibleContext of this JViewport
  1594. */
  1595. public AccessibleContext getAccessibleContext() {
  1596. if (accessibleContext == null) {
  1597. accessibleContext = new AccessibleJViewport();
  1598. }
  1599. return accessibleContext;
  1600. }
  1601. /**
  1602. * This class implements accessibility support for the
  1603. * <code>JViewport</code> class. It provides an implementation of the
  1604. * Java Accessibility API appropriate to viewport user-interface elements.
  1605. * <p>
  1606. * <strong>Warning:</strong>
  1607. * Serialized objects of this class will not be compatible with
  1608. * future Swing releases. The current serialization support is
  1609. * appropriate for short term storage or RMI between applications running
  1610. * the same version of Swing. As of 1.4, support for long term storage
  1611. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1612. * has been added to the <code>java.beans</code> package.
  1613. * Please see {@link java.beans.XMLEncoder}.
  1614. */
  1615. protected class AccessibleJViewport extends AccessibleJComponent {
  1616. /**
  1617. * Get the role of this object.
  1618. *
  1619. * @return an instance of AccessibleRole describing the role of
  1620. * the object
  1621. */
  1622. public AccessibleRole getAccessibleRole() {
  1623. return AccessibleRole.VIEWPORT;
  1624. }
  1625. } // inner class AccessibleJViewport
  1626. }