1. /*
  2. * @(#)JViewport.java 1.64 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import javax.swing.event.*;
  9. import javax.swing.border.*;
  10. import javax.accessibility.*;
  11. import java.applet.Applet;
  12. import java.awt.Component;
  13. import java.awt.Container;
  14. import java.awt.LayoutManager;
  15. import java.awt.Dimension;
  16. import java.awt.Rectangle;
  17. import java.awt.Point;
  18. import java.awt.Insets;
  19. import java.awt.Graphics;
  20. import java.awt.Image;
  21. import java.awt.Window;
  22. import java.awt.event.ActionEvent;
  23. import java.awt.event.ActionListener;
  24. import java.awt.event.ComponentListener;
  25. import java.awt.event.ComponentAdapter;
  26. import java.awt.event.ComponentEvent;
  27. import java.io.Serializable;
  28. /**
  29. * The "viewport" or "porthole" through which you see the underlying
  30. * information. When you scroll, what moves is the viewport. Its like
  31. * peering through a camera's viewfinder. Moving the viewfinder upwards
  32. * brings new things into view at the top of the picture and loses
  33. * things that were at the bottom.
  34. * <p><b>NOTE:</b>We have implemented a faster scrolling algorithm that
  35. * does not require a buffer to draw in. The algorithm works as follows:
  36. * <ol><li>The view and parent view and checked to see if they are JComponents,
  37. * if they aren't, stop and repaint the whole viewport.
  38. * <li>If the viewport is obscured by an ancestor, stop and repaint the whole
  39. * viewport.
  40. * <li>Compute the region that will become visible, if it is as big as
  41. * the viewport, stop and repaint the whole view region.
  42. * <li>Obtain the ancestor Windows graphics and do a copyArea on the scrolled
  43. * region.
  44. * <li>Message the view to repaint the newly visible region.
  45. * <li>The next time paint is invoked on the viewport, if the clip region
  46. * is smaller than the viewport size a timer is kicked off to repaint the
  47. * whole region.
  48. * </ol>
  49. * In general this approach is much faster. Compared to the backing store
  50. * approach this avoids the overhead of maintaining an offscreen buffer and
  51. * having to do two copyAreas. Compared to the non backing store case this
  52. * approach will greatly reduce the painted region.
  53. * <p>This approach can cause slower times than the backing store approach
  54. * when the viewport is obscured by another window, or partially offscreen.
  55. * When another window
  56. * obscures the viewport the copyArea will copy garbage and a
  57. * paint event will be generated by the system to inform us we need to
  58. * paint the newly exposed region. The only way to handle this is to
  59. * repaint the whole viewport, which can cause slower performance than the
  60. * backing store case. In most applications very rarely will the user be
  61. * scrolling while the viewport is obscured by another window or offscreen,
  62. * so this optimization is usually worth the performance hit when obscured.
  63. * <p>To turn this behavior on, put the client property
  64. * "EnableWindowBlit" (a method will added to enable/disable this in the
  65. * future).
  66. * <p>
  67. * <strong>Warning:</strong>
  68. * Serialized objects of this class will not be compatible with
  69. * future Swing releases. The current serialization support is appropriate
  70. * for short term storage or RMI between applications running the same
  71. * version of Swing. A future release of Swing will provide support for
  72. * long term persistence.
  73. *
  74. * @version 1.64 11/29/01
  75. * @author Hans Muller
  76. * @author Philip Milne
  77. * @see JScrollPane
  78. */
  79. public class JViewport extends JComponent implements Accessible
  80. {
  81. /** Property used to indicate window blitting should not be done.
  82. */
  83. static final Object EnableWindowBlit = "EnableWindowBlit";
  84. /** True when the viewport dimensions have been determined. */
  85. protected boolean isViewSizeSet = false;
  86. /**
  87. * The last viewPosition that we've painted, so we know how
  88. * much of the backing store image is valid.
  89. */
  90. protected Point lastPaintPosition = null;
  91. /**
  92. * True when this viewport is maintaining an offscreen image of its
  93. * contents, so that some scrolling can take place using fast "bit-blit"
  94. * operations instead of by accessing the view object to construct the
  95. * display.
  96. */
  97. protected boolean backingStore = false;
  98. /** The view image used for a backing store. */
  99. transient protected Image backingStoreImage = null;
  100. /**
  101. * The scrollUnderway flag is used for components like JList.
  102. * When the downarrow key is pressed on a JList and the selected
  103. * cell is the last in the list, the scrollpane autoscrolls.
  104. * Here, the old selected cell needs repainting and so we need
  105. * a flag to make the viewport do the optimised painting
  106. * only when there is an explicit call to setViewPosition(Point).
  107. * When setBounds() is called through other routes,
  108. * the flag is off and the view repaints normally.
  109. * Another approach would be to remove this from the Viewport
  110. * class and have the JList manage this case by using
  111. * setBackingStoreEnabled().
  112. */
  113. protected boolean scrollUnderway = false;
  114. /*
  115. * Listener that's notified each time the view changes size.
  116. */
  117. private ComponentListener viewListener = null;
  118. /* Only one ChangeEvent is needed per JViewport instance since the
  119. * event's only (read-only) state is the source property. The source
  120. * of events generated here is always "this".
  121. */
  122. private transient ChangeEvent changeEvent = null;
  123. /**
  124. * Whether or not to use the Window's Graphics to blit. This is only
  125. * used if the client property "EnableWindowBlit" is set
  126. * (the existance of this property indicates it should be enabled,
  127. * therefore the value does not matter).
  128. */
  129. private transient boolean windowBlit;
  130. //
  131. // Window blitting:
  132. //
  133. // As mentioned in the javadoc when using windowBlit a paint event
  134. // will be generated by the system if copyArea copies a non-visible
  135. // portion of the view (in other words, it copies garbage). We are
  136. // not guaranteed to receive the paint event before other mouse events,
  137. // so we can not be sure we haven't already copied garbage a bunch of
  138. // times to different parts of the view. For that reason when a blit
  139. // happens the ivar repaintAll is set to true. When paint is received
  140. // if repaintAll is true (we previously did a blit) it is set to
  141. // false, and if the clip region is smaller than the viewport
  142. // waitingForRepaint is set to true and a timer is started. When
  143. // the timer fires if waitingForRepaint is true, repaint is invoked.
  144. // In the mean time, if the view is asked to scroll and waitingForRepaint
  145. // is true, a blit will not happen, instead the non-backing store case
  146. // of scrolling will happen, which will reset waitingForRepaint.
  147. // waitingForRepaint is set to false in paint when the clip rect is
  148. // bigger (or equal) to the size of the viewport.
  149. // A Timer is used instead of just a repaint as it appeared to offer
  150. // better performance.
  151. /**
  152. * This is set to true in setViewPosition if doing a window blit.
  153. */
  154. private transient boolean repaintAll;
  155. /**
  156. * This is set to true in paint, if repaintAll is true and the clip
  157. * rect does not match the bounds. If true, and scrolling happens the
  158. * repaint manager is not cleared which then allows for the repaint
  159. * previously invoked to succeed.
  160. */
  161. private transient boolean waitingForRepaint;
  162. /**
  163. * Instead of directly invoking repaint, a Timer is started and when
  164. * it fires, repaint is invoked.
  165. */
  166. private transient Timer repaintTimer;
  167. /** Create a JViewport */
  168. public JViewport() {
  169. super();
  170. setLayout(createLayoutManager());
  171. }
  172. /**
  173. * Sets the Viewport's one lightweight child, which can be null.
  174. * (Since there is only one child which occupies the entire viewport,
  175. * the constraints and index arguments are ignored.)
  176. *
  177. * @param child the Component ______________
  178. * @param constraints the Object ______________
  179. * @param index the int ______________
  180. * @see #setView
  181. */
  182. protected void addImpl(Component child, Object constraints, int index) {
  183. setView(child);
  184. }
  185. /**
  186. * Removes the Viewport's one lightweight child.
  187. *
  188. * @see #setView
  189. */
  190. public void remove(Component child) {
  191. child.removeComponentListener(viewListener);
  192. super.remove(child);
  193. }
  194. /**
  195. * Overridden to scroll the view so that Rectangle within the
  196. * view becomes visible.
  197. *
  198. * @param contentRect the Rectangle to display
  199. */
  200. public void scrollRectToVisible(Rectangle contentRect) {
  201. Component view = getView();
  202. if (view == null) {
  203. return;
  204. } else {
  205. if (!view.isValid()) {
  206. // If the view is not valid, validate. scrollRectToVisible
  207. // may fail if the view is not valid first, contentRect
  208. // could be bigger than invalid size.
  209. validateView();
  210. }
  211. int dx = 0, dy = 0;
  212. Rectangle bounds = getBounds();
  213. dx = positionAdjustment(bounds.width, contentRect.width, contentRect.x);
  214. dy = positionAdjustment(bounds.height, contentRect.height, contentRect.y);
  215. if (dx != 0 || dy != 0) {
  216. Point viewPosition = getViewPosition();
  217. setViewPosition(new Point(viewPosition.x - dx, viewPosition.y - dy));
  218. // NOTE: How JViewport currently works with the backing store
  219. // is not foolproof. The sequence of events when
  220. // setViewPosition (scrollRectToVisible) is called is to reset
  221. // the views bounds, which causes a repaint on the visible
  222. // region and sets an ivar indicating scrolling
  223. // (scrollUnderway). When JViewport.paint is invoked if
  224. // scrollUnderway is true, the backing store is blitted.
  225. // This fails if between the time setViewPosition is invoked
  226. // and paint is received another repaint is queued indicating
  227. // part of the view is invalid. There is no way for JViewport
  228. // to notice another repaint has occured and it ends up
  229. // blitting what is now a dirty region and the repaint is
  230. // never delivered.
  231. // It just so happens JTable encounters this behavior by way
  232. // of scrollRectToVisible, for this reason scrollUnderway
  233. // is set to false here, which effectively disables the
  234. // backing store.
  235. scrollUnderway = false;
  236. }
  237. }
  238. }
  239. /**
  240. * This will ascend the receivers parents stopping when a component that
  241. * found that returns true to isValidateRoot. If all that Components
  242. * parents are visible, validate will then be invoked on it. The
  243. * RepaintManager is then invoked with removeInvalidComponent. This
  244. * is the synchronous version of a revalidate.
  245. */
  246. private void validateView() {
  247. Component validateRoot = null;
  248. /* Find the first JComponent ancestor of this component whose
  249. * isValidateRoot() method returns true.
  250. */
  251. for(Component c = this; c != null; c = c.getParent()) {
  252. if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
  253. return;
  254. }
  255. if ((c instanceof JComponent) &&
  256. (((JComponent)c).isValidateRoot())) {
  257. validateRoot = c;
  258. break;
  259. }
  260. }
  261. // If no validateRoot, nothing to validate from.
  262. if (validateRoot == null) {
  263. return;
  264. }
  265. // Make sure all ancestors are visible.
  266. Component root = null;
  267. for(Component c = validateRoot; c != null; c = c.getParent()) {
  268. if (!c.isVisible() || (c.getPeer() == null)) {
  269. return;
  270. }
  271. if ((c instanceof Window) || (c instanceof Applet)) {
  272. root = c;
  273. break;
  274. }
  275. }
  276. // Make sure there is a Window ancestor.
  277. if (root == null) {
  278. return;
  279. }
  280. // Validate the root.
  281. validateRoot.validate();
  282. // And let the RepaintManager it does not have to validate from
  283. // validateRoot anymore.
  284. RepaintManager rm = RepaintManager.currentManager(this);
  285. if (rm != null) {
  286. rm.removeInvalidComponent((JComponent)validateRoot);
  287. }
  288. }
  289. /* This method is used by the scrollToRect method to determine the
  290. * proper direction and amount to move by. The integer variables are named
  291. * width, but this method is applicable to height also. The code assumes that
  292. * parentWidth/childWidth are positive and childAt can be negative.
  293. */
  294. private int positionAdjustment(int parentWidth, int childWidth, int childAt) {
  295. // +-----+
  296. // | --- | No Change
  297. // +-----+
  298. if (childAt >= 0 && childWidth + childAt <= parentWidth) {
  299. return 0;
  300. }
  301. // +-----+
  302. // --------- No Change
  303. // +-----+
  304. if (childAt <= 0 && childWidth + childAt >= parentWidth) {
  305. return 0;
  306. }
  307. // +-----+ +-----+
  308. // | ---- -> | ----|
  309. // +-----+ +-----+
  310. if (childAt > 0 && childWidth <= parentWidth) {
  311. return -childAt + parentWidth - childWidth;
  312. }
  313. // +-----+ +-----+
  314. // | -------- -> |--------
  315. // +-----+ +-----+
  316. if (childAt >= 0 && childWidth >= parentWidth) {
  317. return -childAt;
  318. }
  319. // +-----+ +-----+
  320. // ---- | -> |---- |
  321. // +-----+ +-----+
  322. if (childAt <= 0 && childWidth <= parentWidth) {
  323. return -childAt;
  324. }
  325. // +-----+ +-----+
  326. //-------- | -> --------|
  327. // +-----+ +-----+
  328. if (childAt < 0 && childWidth >= parentWidth) {
  329. return -childAt + parentWidth - childWidth;
  330. }
  331. return 0;
  332. }
  333. /**
  334. * The viewport "scrolls" it's child (called the "view") by the
  335. * normal parent/child clipping (typically the view is moved in
  336. * the opposite direction of the scroll). A non-null border,
  337. * or non-zero insets, isn't supported, to prevent the geometry
  338. * of this component from becoming complex enough to inhibit
  339. * subclassing. To create a JViewport with a border, add it to a
  340. * JPanel that has a border.
  341. *
  342. * @param border the Border to set
  343. */
  344. public final void setBorder(Border border) {
  345. if (border != null) {
  346. throw new IllegalArgumentException("JViewport.setBorder() not supported");
  347. }
  348. }
  349. /**
  350. * Returns the insets (border) dimensions as (0,0,0,0), since borders
  351. * are not supported on a JViewport.
  352. *
  353. * @return new Insets(0, 0, 0, 0)
  354. * @see #setBorder
  355. */
  356. public final Insets getInsets() {
  357. return new Insets(0, 0, 0, 0);
  358. }
  359. /**
  360. * Returns an Insets object containing this JViewport's inset
  361. * values. The passed-in Insets object will be reinitialized, and
  362. * all existing values within this object are overwritten.
  363. *
  364. * @param insets the Insets object which can be reused.
  365. * @see #getInsets
  366. * @beaninfo
  367. * expert: true
  368. */
  369. public final Insets getInsets(Insets insets) {
  370. insets.left = insets.top = insets.right = insets.bottom = 0;
  371. return insets;
  372. }
  373. private Graphics getBackingStoreGraphics(Graphics g) {
  374. Graphics bsg = backingStoreImage.getGraphics();
  375. bsg.setColor(g.getColor());
  376. bsg.setFont(g.getFont());
  377. bsg.setClip(g.getClipBounds());
  378. return bsg;
  379. }
  380. private void paintViaBackingStore(Graphics g) {
  381. Graphics bsg = getBackingStoreGraphics(g);
  382. super.paint(bsg);
  383. g.drawImage(backingStoreImage, 0, 0, this);
  384. }
  385. private void paintViaBackingStore(Graphics g, Rectangle oClip) {
  386. Graphics bsg = getBackingStoreGraphics(g);
  387. super.paint(bsg);
  388. g.setClip(oClip);
  389. g.drawImage(backingStoreImage, 0, 0, this);
  390. }
  391. /**
  392. * The JViewport overrides the default implementation of
  393. * this method (in JComponent) to return false. This ensures
  394. * that the drawing machinery will call the Viewport's paint()
  395. * implementation rather than messaging the JViewport's
  396. * children directly.
  397. *
  398. * @return false
  399. */
  400. public boolean isOptimizedDrawingEnabled() {
  401. return false;
  402. }
  403. /**
  404. * Only used by the paint method below.
  405. */
  406. private Point getViewLocation() {
  407. Component view = getView();
  408. if (view != null) {
  409. return view.getLocation();
  410. }
  411. else {
  412. return new Point(0,0);
  413. }
  414. }
  415. /**
  416. * Depending on whether the backingStore is enabled,
  417. * either paint the image through the backing store or paint
  418. * just the recently exposed part, using the backing store
  419. * to "blit" the remainder.
  420. * <blockquote>
  421. * The term "blit" is the pronounced version of the PDP-10
  422. * BLT (BLock Transfer) instruction, which copied a block of
  423. * bits. (In case you were curious.)
  424. * </blockquote>
  425. *
  426. * @param g the Graphics context within which to paint
  427. */
  428. public void paint(Graphics g)
  429. {
  430. int width = getWidth();
  431. int height = getHeight();
  432. if ((width <= 0) || (height <= 0)) {
  433. return;
  434. }
  435. if (repaintAll) {
  436. repaintAll = false;
  437. Rectangle clipB = g.getClipBounds();
  438. if (clipB.width < getWidth() ||
  439. clipB.height < getHeight()) {
  440. waitingForRepaint = true;
  441. if (repaintTimer == null) {
  442. repaintTimer = createRepaintTimer();
  443. }
  444. repaintTimer.stop();
  445. repaintTimer.start();
  446. // We really don't need to paint, a future repaint will
  447. // take care of it, but if we don't we get an ugly flicker.
  448. }
  449. else {
  450. if (repaintTimer != null) {
  451. repaintTimer.stop();
  452. }
  453. waitingForRepaint = false;
  454. }
  455. }
  456. else if (waitingForRepaint) {
  457. // Need a complete repaint before resetting waitingForRepaint
  458. Rectangle clipB = g.getClipBounds();
  459. if (clipB.width >= getWidth() &&
  460. clipB.height >= getHeight()) {
  461. waitingForRepaint = false;
  462. repaintTimer.stop();
  463. }
  464. }
  465. if (!backingStore || windowBlit) {
  466. super.paint(g);
  467. lastPaintPosition = getViewLocation();
  468. return;
  469. }
  470. // If the view is smaller than the viewport, we should set the
  471. // clip. Otherwise, as the bounds of the view vary, we will
  472. // blit garbage into the exposed areas.
  473. Rectangle viewBounds = getView().getBounds();
  474. g.clipRect(0, 0, viewBounds.width, viewBounds.height);
  475. if (backingStoreImage == null) {
  476. // Backing store is enabled but this is the first call to paint.
  477. // Create the backing store, paint it and then copy to g.
  478. // The backing store image will be created with the size of
  479. // the viewport. We must make sure the clip region is the
  480. // same size, otherwise when scrolling the backing image
  481. // the region outside of the clipped region will not be painted,
  482. // and result in empty areas.
  483. backingStoreImage = createImage(width, height);
  484. Rectangle clip = g.getClipBounds();
  485. if (clip.width != width || clip.height != height) {
  486. g.setClip(0, 0, Math.min(viewBounds.width, width),
  487. Math.min(viewBounds.height, height));
  488. paintViaBackingStore(g, clip);
  489. }
  490. else {
  491. paintViaBackingStore(g);
  492. }
  493. }
  494. else {
  495. if (!scrollUnderway || lastPaintPosition.equals(getViewLocation())) {
  496. // No scrolling happened: repaint required area via backing store.
  497. paintViaBackingStore(g);
  498. } else {
  499. // The image was scrolled. Manipulate the backing store and flush it to g.
  500. Point blitFrom = new Point();
  501. Point blitTo = new Point();
  502. Dimension blitSize = new Dimension();
  503. Rectangle blitPaint = new Rectangle();
  504. Point newLocation = getViewLocation();
  505. int dx = newLocation.x - lastPaintPosition.x;
  506. int dy = newLocation.y - lastPaintPosition.y;
  507. boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint);
  508. if (!canBlit) {
  509. // The image was either moved diagonally or
  510. // moved by more than the image size: paint normally.
  511. paintViaBackingStore(g);
  512. } else {
  513. int bdx = blitTo.x - blitFrom.x;
  514. int bdy = blitTo.y - blitFrom.y;
  515. // Move the relevant part of the backing store.
  516. Rectangle clip = g.getClipBounds();
  517. // We don't want to inherit the clip region when copying
  518. // bits, if it is inherited it will result in not moving
  519. // all of the image resulting in garbage appearing on
  520. // the screen.
  521. g.setClip(0, 0, width, height);
  522. Graphics bsg = getBackingStoreGraphics(g);
  523. bsg.copyArea(blitFrom.x, blitFrom.y, blitSize.width, blitSize.height, bdx, bdy);
  524. g.setClip(clip.x, clip.y, clip.width, clip.height);
  525. // Paint the rest of the view; the part that has just been exposed.
  526. Rectangle r = viewBounds.intersection(blitPaint);
  527. bsg.setClip(r);
  528. super.paint(bsg);
  529. // Copy whole of the backing store to g.
  530. g.drawImage(backingStoreImage, 0, 0, this);
  531. }
  532. }
  533. }
  534. lastPaintPosition = getViewLocation();
  535. scrollUnderway = false;
  536. }
  537. /**
  538. * Sets the bounds of this viewport. If the viewports width
  539. * or height has changed, fire a StateChanged event.
  540. *
  541. * @param x left edge of the origin
  542. * @param y top edge of the origin
  543. * @param w width in pixels
  544. * @param h height in pixels
  545. *
  546. * @see JComponent#reshape(int, int, int, int)
  547. */
  548. public void reshape(int x, int y, int w, int h) {
  549. boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
  550. if (sizeChanged) {
  551. backingStoreImage = null;
  552. }
  553. super.reshape(x, y, w, h);
  554. if (sizeChanged) {
  555. fireStateChanged();
  556. }
  557. }
  558. /**
  559. * Returns true if this viewport is maintaining an offscreen
  560. * image of its contents.
  561. */
  562. public boolean isBackingStoreEnabled() {
  563. return backingStore;
  564. }
  565. /**
  566. * If true if this viewport will maintain an offscreen
  567. * image of its contents. The image is used to reduce the cost
  568. * of small one dimensional changes to the viewPosition.
  569. * Rather than repainting the entire viewport we use
  570. * Graphics.copyArea() to effect some of the scroll.
  571. */
  572. public void setBackingStoreEnabled(boolean x) {
  573. backingStore = x;
  574. }
  575. /**
  576. * Returns the Viewport's one child or null.
  577. *
  578. * @see #setView
  579. */
  580. public Component getView() {
  581. return (getComponentCount() > 0) ? getComponent(0) : null;
  582. }
  583. /**
  584. * Sets the Viewport's one lightweight child (<code>view</code>),
  585. * which can be null.
  586. *
  587. * @see #getView
  588. */
  589. public void setView(Component view) {
  590. /* Remove the viewport's existing children, if any.
  591. * Note that removeAll() isn't used here because it
  592. * doesn't call remove() (which JViewport overrides).
  593. */
  594. int n = getComponentCount();
  595. for(int i = n - 1; i >= 0; i--) {
  596. remove(i);
  597. }
  598. isViewSizeSet = false;
  599. if (view != null) {
  600. super.addImpl(view, null, -1);
  601. viewListener = createViewListener();
  602. view.addComponentListener(viewListener);
  603. }
  604. revalidate();
  605. repaint();
  606. }
  607. /**
  608. * If the view's size hasn't been explicitly set, return the
  609. * preferred size, otherwise return the view's current size.
  610. * If there is no view, return 0,0.
  611. *
  612. * @return a Dimension object specifying the size of the view
  613. */
  614. public Dimension getViewSize() {
  615. Component view = getView();
  616. if (view == null) {
  617. return new Dimension(0,0);
  618. }
  619. else if (isViewSizeSet) {
  620. return view.getSize();
  621. }
  622. else {
  623. return view.getPreferredSize();
  624. }
  625. }
  626. /**
  627. * Sets the view coordinates that appear in the upper left
  628. * hand corner of the viewport, and the size of the view.
  629. *
  630. * @param newSize a Dimension object specifying the size and
  631. * location of the new view coordinates, or null if there
  632. * is no view
  633. */
  634. public void setViewSize(Dimension newSize) {
  635. Component view = getView();
  636. if (view != null) {
  637. Dimension oldSize = view.getSize();
  638. if (!newSize.equals(oldSize)) {
  639. // scrollUnderway will be true if this is invoked as the
  640. // result of a validate and setViewPosition was previously
  641. // invoked.
  642. scrollUnderway = false;
  643. view.setSize(newSize);
  644. isViewSizeSet = true;
  645. fireStateChanged();
  646. }
  647. }
  648. }
  649. /**
  650. * Returns the view coordinates that appear in the upper left
  651. * hand corner of the viewport, 0,0 if there's no view.
  652. *
  653. * @return a Point object giving the upper left coordinates
  654. */
  655. public Point getViewPosition() {
  656. Component view = getView();
  657. if (view != null) {
  658. Point p = view.getLocation();
  659. p.x = -p.x;
  660. p.y = -p.y;
  661. return p;
  662. }
  663. else {
  664. return new Point(0,0);
  665. }
  666. }
  667. /**
  668. * Sets the view coordinates that appear in the upper left
  669. * hand corner of the viewport, does nothing if there's no view.
  670. *
  671. * @param p a Point object giving the upper left coordinates
  672. */
  673. public void setViewPosition(Point p)
  674. {
  675. Component view = getView();
  676. if (view == null) {
  677. return;
  678. }
  679. int oldX, oldY, x = p.x, y = p.y;
  680. /* Collect the old x,y values for the views location
  681. * and do the song and dance to avoid allocating
  682. * a Rectangle object if we don't have to.
  683. */
  684. if (view instanceof JComponent) {
  685. JComponent c = (JComponent)view;
  686. oldX = c.getX();
  687. oldY = c.getY();
  688. }
  689. else {
  690. Rectangle r = view.getBounds();
  691. oldX = r.x;
  692. oldY = r.y;
  693. }
  694. /* The view scrolls in the opposite direction to mouse
  695. * movement.
  696. */
  697. int newX = -x;
  698. int newY = -y;
  699. if ((oldX != newX) || (oldY != newY)) {
  700. if (!waitingForRepaint && windowBlit && canUseWindowBlitter()) {
  701. Graphics g = getGraphics();
  702. flushViewDirtyRegion(g);
  703. // This calls setBounds(), and then repaint().
  704. view.setLocation(newX, newY);
  705. g.clipRect(0,0,getWidth(),getHeight());
  706. // Forces a repaint of the whole component on the next
  707. // call to paint.
  708. repaintAll = windowBlitPaint(g);
  709. g.dispose();
  710. RepaintManager rm = RepaintManager.currentManager(this);
  711. rm.markCompletelyClean((JComponent)getParent());
  712. rm.markCompletelyClean(this);
  713. rm.markCompletelyClean((JComponent)view);
  714. }
  715. else {
  716. scrollUnderway = true;
  717. // This calls setBounds(), and then repaint().
  718. view.setLocation(newX, newY);
  719. }
  720. fireStateChanged();
  721. }
  722. }
  723. /**
  724. * Return a rectangle whose origin is getViewPosition and size is
  725. * getExtentSize(). This is the visible part of the view, in view
  726. * coordinates.
  727. *
  728. * @return a Rectangle giving the visible part of the view using view coordinates.
  729. */
  730. public Rectangle getViewRect() {
  731. return new Rectangle(getViewPosition(), getExtentSize());
  732. }
  733. /**
  734. * Computes the parameters for a blit where the backing store image
  735. * currently contains oldLoc in the upper left hand corner
  736. * and we're scrolling to newLoc. The parameters are modified
  737. * to return the values required for the blit.
  738. */
  739. protected boolean computeBlit(
  740. int dx,
  741. int dy,
  742. Point blitFrom,
  743. Point blitTo,
  744. Dimension blitSize,
  745. Rectangle blitPaint)
  746. {
  747. int dxAbs = Math.abs(dx);
  748. int dyAbs = Math.abs(dy);
  749. Dimension extentSize = getExtentSize();
  750. if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
  751. if (dy < 0) {
  752. blitFrom.y = -dy;
  753. blitTo.y = 0;
  754. blitPaint.y = extentSize.height + dy;
  755. }
  756. else {
  757. blitFrom.y = 0;
  758. blitTo.y = dy;
  759. blitPaint.y = 0;
  760. }
  761. blitPaint.x = blitFrom.x = blitTo.x = 0;
  762. blitSize.width = extentSize.width;
  763. blitSize.height = extentSize.height - dyAbs;
  764. blitPaint.width = extentSize.width;
  765. blitPaint.height = dyAbs;
  766. return true;
  767. }
  768. else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) {
  769. if (dx < 0) {
  770. blitFrom.x = -dx;
  771. blitTo.x = 0;
  772. blitPaint.x = extentSize.width + dx;
  773. }
  774. else {
  775. blitFrom.x = 0;
  776. blitTo.x = dx;
  777. blitPaint.x = 0;
  778. }
  779. blitPaint.y = blitFrom.y = blitTo.y = 0;
  780. blitSize.width = extentSize.width - dxAbs;
  781. blitSize.height = extentSize.height;
  782. blitPaint.y = 0;
  783. blitPaint.width = dxAbs;
  784. blitPaint.height = extentSize.height;
  785. return true;
  786. }
  787. else {
  788. return false;
  789. }
  790. }
  791. /**
  792. * Returns the size of the visible part of the view in view coordinates.
  793. *
  794. * @return a Dimension object giving the size of the view
  795. */
  796. public Dimension getExtentSize() {
  797. return getSize();
  798. }
  799. /**
  800. * Convert a size in pixel coordinates to view coordinates.
  801. * Subclasses of viewport that support "logical coordinates"
  802. * will override this method.
  803. *
  804. * @param size a Dimension object using pixel coordinates
  805. * @return a Dimension object converted to view coordinates
  806. */
  807. public Dimension toViewCoordinates(Dimension size) {
  808. return new Dimension(size);
  809. }
  810. /**
  811. * Convert a point in pixel coordinates to view coordinates.
  812. * Subclasses of viewport that support "logical coordinates"
  813. * will override this method.
  814. *
  815. * @param p a Point object using pixel coordinates
  816. * @return a Point object converted to view coordinates
  817. */
  818. public Point toViewCoordinates(Point p) {
  819. return new Point(p);
  820. }
  821. /**
  822. * Set the size of the visible part of the view using view coordinates.
  823. *
  824. * @param newExtent a Dimension object specifying the size of the view
  825. */
  826. public void setExtentSize(Dimension newExtent) {
  827. Dimension oldExtent = getExtentSize();
  828. if (!newExtent.equals(oldExtent)) {
  829. setSize(newExtent);
  830. fireStateChanged();
  831. }
  832. }
  833. /**
  834. * A listener for the view.
  835. * <p>
  836. * <strong>Warning:</strong>
  837. * Serialized objects of this class will not be compatible with
  838. * future Swing releases. The current serialization support is appropriate
  839. * for short term storage or RMI between applications running the same
  840. * version of Swing. A future release of Swing will provide support for
  841. * long term persistence.
  842. */
  843. protected class ViewListener extends ComponentAdapter implements Serializable
  844. {
  845. public void componentResized(ComponentEvent e) {
  846. fireStateChanged();
  847. }
  848. }
  849. /**
  850. * Create a listener for the view.
  851. * @return a ViewListener
  852. */
  853. protected ViewListener createViewListener() {
  854. return new ViewListener();
  855. }
  856. /**
  857. * Subclassers can override this to install a different
  858. * layout manager (or null) in the constructor. Returns
  859. * a new JViewportLayout object.
  860. *
  861. * @return a LayoutManager
  862. */
  863. protected LayoutManager createLayoutManager() {
  864. return new ViewportLayout();
  865. }
  866. /**
  867. * Add a ChangeListener to the list that's notified each time the view's
  868. * size, position, or the viewport's extent size has changed.
  869. *
  870. * @param l the ChangeListener to add
  871. * @see #removeChangeListener
  872. * @see #setViewPosition
  873. * @see #setViewSize
  874. * @see #setExtentSize
  875. */
  876. public void addChangeListener(ChangeListener l) {
  877. listenerList.add(ChangeListener.class, l);
  878. }
  879. /**
  880. * Remove a ChangeListener from the list that's notified each
  881. * time the views size, position, or the viewports extent size
  882. * has changed.
  883. *
  884. * @param l the ChangeListener to remove
  885. * @see #addChangeListener
  886. */
  887. public void removeChangeListener(ChangeListener l) {
  888. listenerList.remove(ChangeListener.class, l);
  889. }
  890. /*
  891. * Notify all ChangeListeners when the views
  892. * size, position, or the viewports extent size has changed.
  893. *
  894. * @see #addChangeListener
  895. * @see #removeChangeListener
  896. * @see EventListenerList
  897. */
  898. protected void fireStateChanged()
  899. {
  900. Object[] listeners = listenerList.getListenerList();
  901. for (int i = listeners.length - 2; i >= 0; i -= 2) {
  902. if (listeners[i] == ChangeListener.class) {
  903. if (changeEvent == null) {
  904. changeEvent = new ChangeEvent(this);
  905. }
  906. ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
  907. }
  908. }
  909. }
  910. /**
  911. * We always repaint in our parent coordinate system to make sure
  912. * only one paint is performed by the RepaintManager.
  913. *
  914. * @param tm maximum time in milliseconds before update
  915. * @param x the <i>x</i> coordinate (pixels over from left)
  916. * @param y the <i>y</i> coordinate (pixels down from top)
  917. * @param width the width
  918. * @param height the height
  919. * @see java.awt.Component#update(java.awt.Graphics)
  920. */
  921. public void repaint(long tm, int x, int y, int w, int h) {
  922. Container parent = getParent();
  923. if(parent != null)
  924. parent.repaint(tm,x+getX(),y+getY(),w,h);
  925. else
  926. super.repaint(tm,x,y,w,h);
  927. }
  928. /**
  929. * Returns a string representation of this JViewport. This method
  930. * is intended to be used only for debugging purposes, and the
  931. * content and format of the returned string may vary between
  932. * implementations. The returned string may be empty but may not
  933. * be <code>null</code>.
  934. *
  935. * @return a string representation of this JViewport.
  936. */
  937. protected String paramString() {
  938. String isViewSizeSetString = (isViewSizeSet ?
  939. "true" : "false");
  940. String lastPaintPositionString = (lastPaintPosition != null ?
  941. lastPaintPosition.toString() : "");
  942. String backingStoreString = (backingStore ?
  943. "true" : "false");
  944. String backingStoreImageString = (backingStoreImage != null ?
  945. backingStoreImage.toString() : "");
  946. String scrollUnderwayString = (scrollUnderway ?
  947. "true" : "false");
  948. return super.paramString() +
  949. ",backingStore=" + backingStoreString +
  950. ",backingStoreImage=" + backingStoreImageString +
  951. ",isViewSizeSet=" + isViewSizeSetString +
  952. ",lastPaintPosition=" + lastPaintPositionString +
  953. ",scrollUnderway=" + scrollUnderwayString;
  954. }
  955. //
  956. // Following is used when doBlit is true.
  957. //
  958. /**
  959. * Notifies listeners of a property change. This is subclassed to update
  960. * the <code>windowBlit</code> ivar (putClientProperty is final).
  961. */
  962. protected void firePropertyChange(String propertyName, Object oldValue,
  963. Object newValue) {
  964. super.firePropertyChange(propertyName, oldValue, newValue);
  965. if (propertyName.equals(EnableWindowBlit)) {
  966. windowBlit = (newValue != null);
  967. }
  968. }
  969. private Timer createRepaintTimer() {
  970. Timer timer = new Timer(300, new ActionListener() {
  971. public void actionPerformed(ActionEvent ae) {
  972. // waitingForRepaint will be false if a paint came down
  973. // with the complete clip rect, in which case we don't
  974. // have to cause a repaint.
  975. if (waitingForRepaint) {
  976. repaint();
  977. }
  978. }
  979. });
  980. timer.setRepeats(false);
  981. return timer;
  982. }
  983. /**
  984. * If the repaint manager has a dirty region for the view, the view is
  985. * asked to paint.
  986. */
  987. private void flushViewDirtyRegion(Graphics g) {
  988. RepaintManager rm = RepaintManager.currentManager(this);
  989. JComponent view = (JComponent) getView();
  990. Rectangle dirty;
  991. dirty = rm.getDirtyRegion((JComponent) getView());
  992. if(dirty != null && dirty.width > 0 && dirty.height > 0) {
  993. dirty.x += view.getX();
  994. dirty.y += view.getY();
  995. Rectangle clip = g.getClipBounds();
  996. if (clip == null) {
  997. // Only happens in 1.2
  998. g.setClip(0, 0, getWidth(), getHeight());
  999. }
  1000. g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
  1001. paintView(g);
  1002. }
  1003. }
  1004. /**
  1005. * Used when blitting.
  1006. * @return true if blitting succeeded.
  1007. */
  1008. private boolean windowBlitPaint(Graphics g) {
  1009. int width = getWidth();
  1010. int height = getHeight();
  1011. if ((width == 0) || (height == 0)) {
  1012. return false;
  1013. }
  1014. boolean retValue;
  1015. RepaintManager rm = RepaintManager.currentManager(this);
  1016. JComponent view = (JComponent) getView();
  1017. if (lastPaintPosition == null ||
  1018. lastPaintPosition.equals(getViewLocation())) {
  1019. paintView(g);
  1020. retValue = false;
  1021. } else {
  1022. // The image was scrolled. Manipulate the backing store and flush
  1023. // it to g.
  1024. Point blitFrom = new Point();
  1025. Point blitTo = new Point();
  1026. Dimension blitSize = new Dimension();
  1027. Rectangle blitPaint = new Rectangle();
  1028. Point newLocation = getViewLocation();
  1029. int dx = newLocation.x - lastPaintPosition.x;
  1030. int dy = newLocation.y - lastPaintPosition.y;
  1031. boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize,
  1032. blitPaint);
  1033. if (!canBlit) {
  1034. paintView(g);
  1035. retValue = false;
  1036. } else {
  1037. boolean isDBE = rm.isDoubleBufferingEnabled();
  1038. int bdx = blitTo.x - blitFrom.x;
  1039. int bdy = blitTo.y - blitFrom.y;
  1040. // Prepare the rest of the view; the part that has just been
  1041. // exposed.
  1042. Rectangle r = view.getBounds().intersection(blitPaint);
  1043. r.x -= view.getX();
  1044. r.y -= view.getY();
  1045. Image off = rm.getOffscreenBuffer(this,getWidth(),getHeight());
  1046. Graphics og = off.getGraphics();
  1047. og.translate(-r.x,-r.y);
  1048. og.setClip(r.x,r.y,r.width,r.height);
  1049. rm.setDoubleBufferingEnabled(false);
  1050. view.paint(og);
  1051. rm.setDoubleBufferingEnabled(isDBE);
  1052. // Move the relevant part of the backing store.
  1053. blitWindowGraphics(blitFrom.x, blitFrom.y, blitSize.width,
  1054. blitSize.height, bdx, bdy);
  1055. r.x += view.getX();
  1056. r.y += view.getY();
  1057. g.setClip(r.x,r.y,r.width,r.height);
  1058. g.drawImage(off,r.x,r.y,null);
  1059. og.dispose();
  1060. retValue = true;
  1061. }
  1062. }
  1063. lastPaintPosition = getViewLocation();
  1064. return retValue;
  1065. }
  1066. /**
  1067. * Called to paint the view, usually when blitPaint can not blit.
  1068. */
  1069. private void paintView(Graphics g) {
  1070. Rectangle r = g.getClipBounds();
  1071. RepaintManager rm = RepaintManager.currentManager(this);
  1072. boolean dblbEnable = rm.isDoubleBufferingEnabled();
  1073. JComponent view = (JComponent) getView();
  1074. r.x -= view.getX();
  1075. r.y -= view.getY();
  1076. Image off = rm.getOffscreenBuffer(this,r.width,r.height);
  1077. Graphics og = off.getGraphics();
  1078. og.translate(-r.x,-r.y);
  1079. og.setClip(r.x,r.y,r.width,r.height);
  1080. rm.setDoubleBufferingEnabled(false);
  1081. view.paint(og);
  1082. if(dblbEnable)
  1083. rm.setDoubleBufferingEnabled(true);
  1084. g.drawImage(off,r.x + view.getX(),r.y + view.getY(),null);
  1085. og.dispose();
  1086. }
  1087. /**
  1088. * Blits the parent windows graphics from the given region offset
  1089. * to <code>ox</code>, <code>oy</code>.
  1090. */
  1091. private void blitWindowGraphics(int x, int y, int w, int h, int ox,
  1092. int oy) {
  1093. Container parent;
  1094. for(parent = getParent() ; !(parent instanceof Window) ;
  1095. parent = parent.getParent());
  1096. Graphics wg = parent.getGraphics();
  1097. Rectangle r = new Rectangle(x,y,w,h);
  1098. r = SwingUtilities.convertRectangle(this, r, parent);
  1099. wg.copyArea(r.x,r.y,r.width,r.height, ox, oy);
  1100. }
  1101. /**
  1102. * Returns true if the receiver is not obscured by one of its ancestors,
  1103. * or its ancestors children.
  1104. */
  1105. private boolean canUseWindowBlitter() {
  1106. if (!(getParent() instanceof JComponent) &&
  1107. !(getView() instanceof JComponent)) {
  1108. return false;
  1109. }
  1110. Rectangle clip = new Rectangle(0,0,getWidth(),getHeight());
  1111. Rectangle oldClip = new Rectangle();
  1112. Rectangle tmp2;
  1113. Rectangle b;
  1114. Container parent;
  1115. Component lastParent = null;
  1116. for(parent = this ; !(parent instanceof java.awt.Window) &&
  1117. parent != null; parent = parent.getParent()) {
  1118. if(parent instanceof JComponent) {
  1119. b = ((JComponent)parent)._bounds;
  1120. } else {
  1121. b = parent.getBounds();
  1122. }
  1123. oldClip.setBounds(clip);
  1124. SwingUtilities.computeIntersection(0, 0, b.width, b.height, clip);
  1125. if(!clip.equals(oldClip))
  1126. return false;
  1127. if(lastParent != null && parent instanceof JComponent &&
  1128. !((JComponent)parent).isOptimizedDrawingEnabled()) {
  1129. Component comps[] = parent.getComponents();
  1130. int index = 0;
  1131. for(int i = comps.length - 1 ;i >= 0; i--) {
  1132. if(comps[i] == lastParent) {
  1133. index = i - 1;
  1134. break;
  1135. }
  1136. }
  1137. while(index >= 0) {
  1138. if(comps[index] instanceof JComponent) {
  1139. tmp2 = ((JComponent)comps[index])._bounds;
  1140. } else {
  1141. tmp2 = comps[index].getBounds();
  1142. }
  1143. if(tmp2.intersects(clip))
  1144. return false;
  1145. index--;
  1146. }
  1147. }
  1148. clip.x += b.x;
  1149. clip.y += b.y;
  1150. lastParent = parent;
  1151. }
  1152. if (parent == null) {
  1153. // No Window parent.
  1154. return false;
  1155. }
  1156. return true;
  1157. }
  1158. /////////////////
  1159. // Accessibility support
  1160. ////////////////
  1161. /**
  1162. * Get the AccessibleContext associated with this JComponent
  1163. *
  1164. * @return the AccessibleContext of this JComponent
  1165. */
  1166. public AccessibleContext getAccessibleContext() {
  1167. if (accessibleContext == null) {
  1168. accessibleContext = new AccessibleJViewport();
  1169. }
  1170. return accessibleContext;
  1171. }
  1172. /**
  1173. * The class used to obtain the accessible role for this object.
  1174. * <p>
  1175. * <strong>Warning:</strong>
  1176. * Serialized objects of this class will not be compatible with
  1177. * future Swing releases. The current serialization support is appropriate
  1178. * for short term storage or RMI between applications running the same
  1179. * version of Swing. A future release of Swing will provide support for
  1180. * long term persistence.
  1181. */
  1182. protected class AccessibleJViewport extends AccessibleJComponent {
  1183. /**
  1184. * Get the role of this object.
  1185. *
  1186. * @return an instance of AccessibleRole describing the role of
  1187. * the object
  1188. */
  1189. public AccessibleRole getAccessibleRole() {
  1190. return AccessibleRole.VIEWPORT;
  1191. }
  1192. } // inner class AccessibleJViewport
  1193. }