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