0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package javax.swing;
0027
0028 import java.awt.*;
0029 import java.awt.event.*;
0030 import java.awt.image.VolatileImage;
0031 import java.awt.peer.ComponentPeer;
0032 import java.applet.Applet;
0033 import javax.swing.plaf.ViewportUI;
0034
0035 import javax.swing.event.*;
0036 import javax.swing.border.*;
0037 import javax.accessibility.*;
0038
0039 import java.io.Serializable;
0040
0041 /**
0042 * The "viewport" or "porthole" through which you see the underlying
0043 * information. When you scroll, what moves is the viewport. It is like
0044 * peering through a camera's viewfinder. Moving the viewfinder upwards
0045 * brings new things into view at the top of the picture and loses
0046 * things that were at the bottom.
0047 * <p>
0048 * By default, <code>JViewport</code> is opaque. To change this, use the
0049 * <code>setOpaque</code> method.
0050 * <p>
0051 * <b>NOTE:</b>We have implemented a faster scrolling algorithm that
0052 * does not require a buffer to draw in. The algorithm works as follows:
0053 * <ol><li>The view and parent view and checked to see if they are
0054 * <code>JComponents</code>,
0055 * if they aren't, stop and repaint the whole viewport.
0056 * <li>If the viewport is obscured by an ancestor, stop and repaint the whole
0057 * viewport.
0058 * <li>Compute the region that will become visible, if it is as big as
0059 * the viewport, stop and repaint the whole view region.
0060 * <li>Obtain the ancestor <code>Window</code>'s graphics and
0061 * do a <code>copyArea</code> on the scrolled region.
0062 * <li>Message the view to repaint the newly visible region.
0063 * <li>The next time paint is invoked on the viewport, if the clip region
0064 * is smaller than the viewport size a timer is kicked off to repaint the
0065 * whole region.
0066 * </ol>
0067 * In general this approach is much faster. Compared to the backing store
0068 * approach this avoids the overhead of maintaining an offscreen buffer and
0069 * having to do two <code>copyArea</code>s.
0070 * Compared to the non backing store case this
0071 * approach will greatly reduce the painted region.
0072 * <p>
0073 * This approach can cause slower times than the backing store approach
0074 * when the viewport is obscured by another window, or partially offscreen.
0075 * When another window
0076 * obscures the viewport the copyArea will copy garbage and a
0077 * paint event will be generated by the system to inform us we need to
0078 * paint the newly exposed region. The only way to handle this is to
0079 * repaint the whole viewport, which can cause slower performance than the
0080 * backing store case. In most applications very rarely will the user be
0081 * scrolling while the viewport is obscured by another window or offscreen,
0082 * so this optimization is usually worth the performance hit when obscured.
0083 * <p>
0084 * <strong>Warning:</strong> Swing is not thread safe. For more
0085 * information see <a
0086 * href="package-summary.html#threading">Swing's Threading
0087 * Policy</a>.
0088 * <p>
0089 * <strong>Warning:</strong>
0090 * Serialized objects of this class will not be compatible with
0091 * future Swing releases. The current serialization support is
0092 * appropriate for short term storage or RMI between applications running
0093 * the same version of Swing. As of 1.4, support for long term storage
0094 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0095 * has been added to the <code>java.beans</code> package.
0096 * Please see {@link java.beans.XMLEncoder}.
0097 *
0098 * @version 1.128 05/05/07
0099 * @author Hans Muller
0100 * @author Philip Milne
0101 * @see JScrollPane
0102 */
0103 public class JViewport extends JComponent implements Accessible {
0104 /**
0105 * @see #getUIClassID
0106 * @see #readObject
0107 */
0108 private static final String uiClassID = "ViewportUI";
0109
0110 /** Property used to indicate window blitting should not be done.
0111 */
0112 static final Object EnableWindowBlit = "EnableWindowBlit";
0113
0114 /**
0115 * True when the viewport dimensions have been determined.
0116 * The default is false.
0117 */
0118 protected boolean isViewSizeSet = false;
0119
0120 /**
0121 * The last <code>viewPosition</code> that we've painted, so we know how
0122 * much of the backing store image is valid.
0123 */
0124 protected Point lastPaintPosition = null;
0125
0126 /**
0127 * True when this viewport is maintaining an offscreen image of its
0128 * contents, so that some scrolling can take place using fast "bit-blit"
0129 * operations instead of by accessing the view object to construct the
0130 * display. The default is <code>false</code>.
0131 *
0132 * @deprecated As of Java 2 platform v1.3
0133 * @see #setScrollMode
0134 */
0135 @Deprecated
0136 protected boolean backingStore = false;
0137
0138 /** The view image used for a backing store. */
0139 transient protected Image backingStoreImage = null;
0140
0141 /**
0142 * The <code>scrollUnderway</code> flag is used for components like
0143 * <code>JList</code>. When the downarrow key is pressed on a
0144 * <code>JList</code> and the selected
0145 * cell is the last in the list, the <code>scrollpane</code> autoscrolls.
0146 * Here, the old selected cell needs repainting and so we need
0147 * a flag to make the viewport do the optimized painting
0148 * only when there is an explicit call to
0149 * <code>setViewPosition(Point)</code>.
0150 * When <code>setBounds</code> is called through other routes,
0151 * the flag is off and the view repaints normally. Another approach
0152 * would be to remove this from the <code>JViewport</code>
0153 * class and have the <code>JList</code> manage this case by using
0154 * <code>setBackingStoreEnabled</code>. The default is
0155 * <code>false</code>.
0156 */
0157 protected boolean scrollUnderway = false;
0158
0159 /*
0160 * Listener that is notified each time the view changes size.
0161 */
0162 private ComponentListener viewListener = null;
0163
0164 /* Only one <code>ChangeEvent</code> is needed per
0165 * <code>JViewport</code> instance since the
0166 * event's only (read-only) state is the source property. The source
0167 * of events generated here is always "this".
0168 */
0169 private transient ChangeEvent changeEvent = null;
0170
0171 /**
0172 * Use <code>graphics.copyArea</code> to implement scrolling.
0173 * This is the fastest for most applications.
0174 *
0175 * @see #setScrollMode
0176 * @since 1.3
0177 */
0178 public static final int BLIT_SCROLL_MODE = 1;
0179
0180 /**
0181 * Draws viewport contents into an offscreen image.
0182 * This was previously the default mode for <code>JTable</code>.
0183 * This mode may offer advantages over "blit mode"
0184 * in some cases, but it requires a large chunk of extra RAM.
0185 *
0186 * @see #setScrollMode
0187 * @since 1.3
0188 */
0189 public static final int BACKINGSTORE_SCROLL_MODE = 2;
0190
0191 /**
0192 * This mode uses the very simple method of redrawing the entire
0193 * contents of the scrollpane each time it is scrolled.
0194 * This was the default behavior in Swing 1.0 and Swing 1.1.
0195 * Either of the other two options will provide better performance
0196 * in most cases.
0197 *
0198 * @see #setScrollMode
0199 * @since 1.3
0200 */
0201 public static final int SIMPLE_SCROLL_MODE = 0;
0202
0203 /**
0204 * @see #setScrollMode
0205 * @since 1.3
0206 */
0207 private int scrollMode = BLIT_SCROLL_MODE;
0208
0209 //
0210 // Window blitting:
0211 //
0212 // As mentioned in the javadoc when using windowBlit a paint event
0213 // will be generated by the system if copyArea copies a non-visible
0214 // portion of the view (in other words, it copies garbage). We are
0215 // not guaranteed to receive the paint event before other mouse events,
0216 // so we can not be sure we haven't already copied garbage a bunch of
0217 // times to different parts of the view. For that reason when a blit
0218 // happens and the Component is obscured (the check for obscurity
0219 // is not supported on all platforms and is checked via ComponentPeer
0220 // methods) the ivar repaintAll is set to true. When paint is received
0221 // if repaintAll is true (we previously did a blit) it is set to
0222 // false, and if the clip region is smaller than the viewport
0223 // waitingForRepaint is set to true and a timer is started. When
0224 // the timer fires if waitingForRepaint is true, repaint is invoked.
0225 // In the mean time, if the view is asked to scroll and waitingForRepaint
0226 // is true, a blit will not happen, instead the non-backing store case
0227 // of scrolling will happen, which will reset waitingForRepaint.
0228 // waitingForRepaint is set to false in paint when the clip rect is
0229 // bigger (or equal) to the size of the viewport.
0230 // A Timer is used instead of just a repaint as it appeared to offer
0231 // better performance.
0232
0233 /**
0234 * This is set to true in <code>setViewPosition</code>
0235 * if doing a window blit and the viewport is obscured.
0236 */
0237 private transient boolean repaintAll;
0238
0239 /**
0240 * This is set to true in paint, if <code>repaintAll</code>
0241 * is true and the clip rectangle does not match the bounds.
0242 * If true, and scrolling happens the
0243 * repaint manager is not cleared which then allows for the repaint
0244 * previously invoked to succeed.
0245 */
0246 private transient boolean waitingForRepaint;
0247
0248 /**
0249 * Instead of directly invoking repaint, a <code>Timer</code>
0250 * is started and when it fires, repaint is invoked.
0251 */
0252 private transient Timer repaintTimer;
0253
0254 /**
0255 * Set to true in paintView when paint is invoked.
0256 */
0257 private transient boolean inBlitPaint;
0258
0259 /**
0260 * Whether or not a valid view has been installed.
0261 */
0262 private boolean hasHadValidView;
0263
0264 /** Creates a <code>JViewport</code>. */
0265 public JViewport() {
0266 super ();
0267 setLayout(createLayoutManager());
0268 setOpaque(true);
0269 updateUI();
0270 setInheritsPopupMenu(true);
0271 }
0272
0273 /**
0274 * Returns the L&F object that renders this component.
0275 *
0276 * @return a <code>ViewportUI</code> object
0277 * @since 1.3
0278 */
0279 public ViewportUI getUI() {
0280 return (ViewportUI) ui;
0281 }
0282
0283 /**
0284 * Sets the L&F object that renders this component.
0285 *
0286 * @param ui the <code>ViewportUI</code> L&F object
0287 * @see UIDefaults#getUI
0288 * @beaninfo
0289 * bound: true
0290 * hidden: true
0291 * attribute: visualUpdate true
0292 * description: The UI object that implements the Component's LookAndFeel.
0293 * @since 1.3
0294 */
0295 public void setUI(ViewportUI ui) {
0296 super .setUI(ui);
0297 }
0298
0299 /**
0300 * Resets the UI property to a value from the current look and feel.
0301 *
0302 * @see JComponent#updateUI
0303 */
0304 public void updateUI() {
0305 setUI((ViewportUI) UIManager.getUI(this ));
0306 }
0307
0308 /**
0309 * Returns a string that specifies the name of the L&F class
0310 * that renders this component.
0311 *
0312 * @return the string "ViewportUI"
0313 *
0314 * @see JComponent#getUIClassID
0315 * @see UIDefaults#getUI
0316 */
0317 public String getUIClassID() {
0318 return uiClassID;
0319 }
0320
0321 /**
0322 * Sets the <code>JViewport</code>'s one lightweight child,
0323 * which can be <code>null</code>.
0324 * (Since there is only one child which occupies the entire viewport,
0325 * the <code>constraints</code> and <code>index</code>
0326 * arguments are ignored.)
0327 *
0328 * @param child the lightweight <code>child</code> of the viewport
0329 * @param constraints the <code>constraints</code> to be respected
0330 * @param index the index
0331 * @see #setView
0332 */
0333 protected void addImpl(Component child, Object constraints,
0334 int index) {
0335 setView(child);
0336 }
0337
0338 /**
0339 * Removes the <code>Viewport</code>s one lightweight child.
0340 *
0341 * @see #setView
0342 */
0343 public void remove(Component child) {
0344 child.removeComponentListener(viewListener);
0345 super .remove(child);
0346 }
0347
0348 /**
0349 * Scrolls the view so that <code>Rectangle</code>
0350 * within the view becomes visible.
0351 * <p>
0352 * This attempts to validate the view before scrolling if the
0353 * view is currently not valid - <code>isValid</code> returns false.
0354 * To avoid excessive validation when the containment hierarchy is
0355 * being created this will not validate if one of the ancestors does not
0356 * have a peer, or there is no validate root ancestor, or one of the
0357 * ancestors is not a <code>Window</code> or <code>Applet</code>.
0358 * <p>
0359 * Note that this method will not scroll outside of the
0360 * valid viewport; for example, if <code>contentRect</code> is larger
0361 * than the viewport, scrolling will be confined to the viewport's
0362 * bounds.
0363 *
0364 * @param contentRect the <code>Rectangle</code> to display
0365 * @see JComponent#isValidateRoot
0366 * @see java.awt.Component#isValid
0367 * @see java.awt.Component#getPeer
0368 */
0369 public void scrollRectToVisible(Rectangle contentRect) {
0370 Component view = getView();
0371
0372 if (view == null) {
0373 return;
0374 } else {
0375 if (!view.isValid()) {
0376 // If the view is not valid, validate. scrollRectToVisible
0377 // may fail if the view is not valid first, contentRect
0378 // could be bigger than invalid size.
0379 validateView();
0380 }
0381 int dx = 0, dy = 0;
0382
0383 dx = positionAdjustment(getWidth(), contentRect.width,
0384 contentRect.x);
0385 dy = positionAdjustment(getHeight(), contentRect.height,
0386 contentRect.y);
0387
0388 if (dx != 0 || dy != 0) {
0389 Point viewPosition = getViewPosition();
0390 Dimension viewSize = view.getSize();
0391 int startX = viewPosition.x;
0392 int startY = viewPosition.y;
0393 Dimension extent = getExtentSize();
0394
0395 viewPosition.x -= dx;
0396 viewPosition.y -= dy;
0397 // Only constrain the location if the view is valid. If the
0398 // the view isn't valid, it typically indicates the view
0399 // isn't visible yet and most likely has a bogus size as will
0400 // we, and therefore we shouldn't constrain the scrolling
0401 if (view.isValid()) {
0402 if (getParent().getComponentOrientation()
0403 .isLeftToRight()) {
0404 if (viewPosition.x + extent.width > viewSize.width) {
0405 viewPosition.x = Math.max(0, viewSize.width
0406 - extent.width);
0407 } else if (viewPosition.x < 0) {
0408 viewPosition.x = 0;
0409 }
0410 } else {
0411 if (extent.width > viewSize.width) {
0412 viewPosition.x = viewSize.width
0413 - extent.width;
0414 } else {
0415 viewPosition.x = Math.max(0, Math.min(
0416 viewSize.width - extent.width,
0417 viewPosition.x));
0418 }
0419 }
0420 if (viewPosition.y + extent.height > viewSize.height) {
0421 viewPosition.y = Math.max(0, viewSize.height
0422 - extent.height);
0423 } else if (viewPosition.y < 0) {
0424 viewPosition.y = 0;
0425 }
0426 }
0427 if (viewPosition.x != startX
0428 || viewPosition.y != startY) {
0429 setViewPosition(viewPosition);
0430 // NOTE: How JViewport currently works with the
0431 // backing store is not foolproof. The sequence of
0432 // events when setViewPosition
0433 // (scrollRectToVisible) is called is to reset the
0434 // views bounds, which causes a repaint on the
0435 // visible region and sets an ivar indicating
0436 // scrolling (scrollUnderway). When
0437 // JViewport.paint is invoked if scrollUnderway is
0438 // true, the backing store is blitted. This fails
0439 // if between the time setViewPosition is invoked
0440 // and paint is received another repaint is queued
0441 // indicating part of the view is invalid. There
0442 // is no way for JViewport to notice another
0443 // repaint has occured and it ends up blitting
0444 // what is now a dirty region and the repaint is
0445 // never delivered.
0446 // It just so happens JTable encounters this
0447 // behavior by way of scrollRectToVisible, for
0448 // this reason scrollUnderway is set to false
0449 // here, which effectively disables the backing
0450 // store.
0451 scrollUnderway = false;
0452 }
0453 }
0454 }
0455 }
0456
0457 /**
0458 * Ascends the <code>Viewport</code>'s parents stopping when
0459 * a component is found that returns
0460 * <code>true</code> to <code>isValidateRoot</code>.
0461 * If all the <code>Component</code>'s parents are visible,
0462 * <code>validate</code> will then be invoked on it. The
0463 * <code>RepaintManager</code> is then invoked with
0464 * <code>removeInvalidComponent</code>. This
0465 * is the synchronous version of a <code>revalidate</code>.
0466 */
0467 private void validateView() {
0468 Component validateRoot = null;
0469
0470 /* Find the first JComponent ancestor of this component whose
0471 * isValidateRoot() method returns true.
0472 */
0473 for (Component c = this ; c != null; c = c.getParent()) {
0474 if ((c instanceof CellRendererPane)
0475 || (c.getPeer() == null)) {
0476 return;
0477 }
0478 if ((c instanceof JComponent)
0479 && (((JComponent) c).isValidateRoot())) {
0480 validateRoot = c;
0481 break;
0482 }
0483 }
0484
0485 // If no validateRoot, nothing to validate from.
0486 if (validateRoot == null) {
0487 return;
0488 }
0489
0490 // Make sure all ancestors are visible.
0491 Component root = null;
0492
0493 for (Component c = validateRoot; c != null; c = c.getParent()) {
0494 // We don't check isVisible here, otherwise if the component
0495 // is contained in something like a JTabbedPane when the
0496 // component is made visible again it won't have scrolled
0497 // to the correct location.
0498 if (c.getPeer() == null) {
0499 return;
0500 }
0501 if ((c instanceof Window) || (c instanceof Applet)) {
0502 root = c;
0503 break;
0504 }
0505 }
0506
0507 // Make sure there is a Window ancestor.
0508 if (root == null) {
0509 return;
0510 }
0511
0512 // Validate the root.
0513 validateRoot.validate();
0514
0515 // And let the RepaintManager it does not have to validate from
0516 // validateRoot anymore.
0517 RepaintManager rm = RepaintManager.currentManager(this );
0518
0519 if (rm != null) {
0520 rm.removeInvalidComponent((JComponent) validateRoot);
0521 }
0522 }
0523
0524 /* Used by the scrollRectToVisible method to determine the
0525 * proper direction and amount to move by. The integer variables are named
0526 * width, but this method is applicable to height also. The code assumes that
0527 * parentWidth/childWidth are positive and childAt can be negative.
0528 */
0529 private int positionAdjustment(int parentWidth, int childWidth,
0530 int childAt) {
0531
0532 // +-----+
0533 // | --- | No Change
0534 // +-----+
0535 if (childAt >= 0 && childWidth + childAt <= parentWidth) {
0536 return 0;
0537 }
0538
0539 // +-----+
0540 // --------- No Change
0541 // +-----+
0542 if (childAt <= 0 && childWidth + childAt >= parentWidth) {
0543 return 0;
0544 }
0545
0546 // +-----+ +-----+
0547 // | ---- -> | ----|
0548 // +-----+ +-----+
0549 if (childAt > 0 && childWidth <= parentWidth) {
0550 return -childAt + parentWidth - childWidth;
0551 }
0552
0553 // +-----+ +-----+
0554 // | -------- -> |--------
0555 // +-----+ +-----+
0556 if (childAt >= 0 && childWidth >= parentWidth) {
0557 return -childAt;
0558 }
0559
0560 // +-----+ +-----+
0561 // ---- | -> |---- |
0562 // +-----+ +-----+
0563 if (childAt <= 0 && childWidth <= parentWidth) {
0564 return -childAt;
0565 }
0566
0567 // +-----+ +-----+
0568 //-------- | -> --------|
0569 // +-----+ +-----+
0570 if (childAt < 0 && childWidth >= parentWidth) {
0571 return -childAt + parentWidth - childWidth;
0572 }
0573
0574 return 0;
0575 }
0576
0577 /**
0578 * The viewport "scrolls" its child (called the "view") by the
0579 * normal parent/child clipping (typically the view is moved in
0580 * the opposite direction of the scroll). A non-<code>null</code> border,
0581 * or non-zero insets, isn't supported, to prevent the geometry
0582 * of this component from becoming complex enough to inhibit
0583 * subclassing. To create a <code>JViewport</code> with a border,
0584 * add it to a <code>JPanel</code> that has a border.
0585 * <p>Note: If <code>border</code> is non-<code>null</code>, this
0586 * method will throw an exception as borders are not supported on
0587 * a <code>JViewPort</code>.
0588 *
0589 * @param border the <code>Border</code> to set
0590 * @exception IllegalArgumentException this method is not implemented
0591 */
0592 public final void setBorder(Border border) {
0593 if (border != null) {
0594 throw new IllegalArgumentException(
0595 "JViewport.setBorder() not supported");
0596 }
0597 }
0598
0599 /**
0600 * Returns the insets (border) dimensions as (0,0,0,0), since borders
0601 * are not supported on a <code>JViewport</code>.
0602 *
0603 * @return a <code>Rectange</code> of zero dimension and zero origin
0604 * @see #setBorder
0605 */
0606 public final Insets getInsets() {
0607 return new Insets(0, 0, 0, 0);
0608 }
0609
0610 /**
0611 * Returns an <code>Insets</code> object containing this
0612 * <code>JViewport</code>s inset values. The passed-in
0613 * <code>Insets</code> object will be reinitialized, and
0614 * all existing values within this object are overwritten.
0615 *
0616 * @param insets the <code>Insets</code> object which can be reused
0617 * @return this viewports inset values
0618 * @see #getInsets
0619 * @beaninfo
0620 * expert: true
0621 */
0622 public final Insets getInsets(Insets insets) {
0623 insets.left = insets.top = insets.right = insets.bottom = 0;
0624 return insets;
0625 }
0626
0627 private Graphics getBackingStoreGraphics(Graphics g) {
0628 Graphics bsg = backingStoreImage.getGraphics();
0629 bsg.setColor(g.getColor());
0630 bsg.setFont(g.getFont());
0631 bsg.setClip(g.getClipBounds());
0632 return bsg;
0633 }
0634
0635 private void paintViaBackingStore(Graphics g) {
0636 Graphics bsg = getBackingStoreGraphics(g);
0637 try {
0638 super .paint(bsg);
0639 g.drawImage(backingStoreImage, 0, 0, this );
0640 } finally {
0641 bsg.dispose();
0642 }
0643 }
0644
0645 private void paintViaBackingStore(Graphics g, Rectangle oClip) {
0646 Graphics bsg = getBackingStoreGraphics(g);
0647 try {
0648 super .paint(bsg);
0649 g.setClip(oClip);
0650 g.drawImage(backingStoreImage, 0, 0, this );
0651 } finally {
0652 bsg.dispose();
0653 }
0654 }
0655
0656 /**
0657 * The <code>JViewport</code> overrides the default implementation of
0658 * this method (in <code>JComponent</code>) to return false.
0659 * This ensures
0660 * that the drawing machinery will call the <code>Viewport</code>'s
0661 * <code>paint</code>
0662 * implementation rather than messaging the <code>JViewport</code>'s
0663 * children directly.
0664 *
0665 * @return false
0666 */
0667 public boolean isOptimizedDrawingEnabled() {
0668 return false;
0669 }
0670
0671 /**
0672 * Returns true if scroll mode is a BACKINGSTORE_SCROLL_MODE to cause
0673 * painting to originate from <code>JViewport</code>, or one of its
0674 * ancestors. Otherwise returns false.
0675 *
0676 * @return true if if scroll mode is a BACKINGSTORE_SCROLL_MODE.
0677 * @see JComponent#isPaintingOrigin()
0678 */
0679 boolean isPaintingOrigin() {
0680 if (scrollMode == BACKINGSTORE_SCROLL_MODE) {
0681 return true;
0682 }
0683 return false;
0684 }
0685
0686 /**
0687 * Only used by the paint method below.
0688 */
0689 private Point getViewLocation() {
0690 Component view = getView();
0691 if (view != null) {
0692 return view.getLocation();
0693 } else {
0694 return new Point(0, 0);
0695 }
0696 }
0697
0698 /**
0699 * Depending on whether the <code>backingStore</code> is enabled,
0700 * either paint the image through the backing store or paint
0701 * just the recently exposed part, using the backing store
0702 * to "blit" the remainder.
0703 * <blockquote>
0704 * The term "blit" is the pronounced version of the PDP-10
0705 * BLT (BLock Transfer) instruction, which copied a block of
0706 * bits. (In case you were curious.)
0707 * </blockquote>
0708 *
0709 * @param g the <code>Graphics</code> context within which to paint
0710 */
0711 public void paint(Graphics g) {
0712 int width = getWidth();
0713 int height = getHeight();
0714
0715 if ((width <= 0) || (height <= 0)) {
0716 return;
0717 }
0718
0719 if (inBlitPaint) {
0720 // We invoked paint as part of copyArea cleanup, let it through.
0721 super .paint(g);
0722 return;
0723 }
0724
0725 if (repaintAll) {
0726 repaintAll = false;
0727 Rectangle clipB = g.getClipBounds();
0728 if (clipB.width < getWidth() || clipB.height < getHeight()) {
0729 waitingForRepaint = true;
0730 if (repaintTimer == null) {
0731 repaintTimer = createRepaintTimer();
0732 }
0733 repaintTimer.stop();
0734 repaintTimer.start();
0735 // We really don't need to paint, a future repaint will
0736 // take care of it, but if we don't we get an ugly flicker.
0737 } else {
0738 if (repaintTimer != null) {
0739 repaintTimer.stop();
0740 }
0741 waitingForRepaint = false;
0742 }
0743 } else if (waitingForRepaint) {
0744 // Need a complete repaint before resetting waitingForRepaint
0745 Rectangle clipB = g.getClipBounds();
0746 if (clipB.width >= getWidth()
0747 && clipB.height >= getHeight()) {
0748 waitingForRepaint = false;
0749 repaintTimer.stop();
0750 }
0751 }
0752
0753 if (!backingStore || isBlitting() || getView() == null) {
0754 super .paint(g);
0755 lastPaintPosition = getViewLocation();
0756 return;
0757 }
0758
0759 // If the view is smaller than the viewport and we are not opaque
0760 // (that is, we won't paint our background), we should set the
0761 // clip. Otherwise, as the bounds of the view vary, we will
0762 // blit garbage into the exposed areas.
0763 Rectangle viewBounds = getView().getBounds();
0764 if (!isOpaque()) {
0765 g.clipRect(0, 0, viewBounds.width, viewBounds.height);
0766 }
0767
0768 if (backingStoreImage == null) {
0769 // Backing store is enabled but this is the first call to paint.
0770 // Create the backing store, paint it and then copy to g.
0771 // The backing store image will be created with the size of
0772 // the viewport. We must make sure the clip region is the
0773 // same size, otherwise when scrolling the backing image
0774 // the region outside of the clipped region will not be painted,
0775 // and result in empty areas.
0776 backingStoreImage = createImage(width, height);
0777 Rectangle clip = g.getClipBounds();
0778 if (clip.width != width || clip.height != height) {
0779 if (!isOpaque()) {
0780 g.setClip(0, 0, Math.min(viewBounds.width, width),
0781 Math.min(viewBounds.height, height));
0782 } else {
0783 g.setClip(0, 0, width, height);
0784 }
0785 paintViaBackingStore(g, clip);
0786 } else {
0787 paintViaBackingStore(g);
0788 }
0789 } else {
0790 if (!scrollUnderway
0791 || lastPaintPosition.equals(getViewLocation())) {
0792 // No scrolling happened: repaint required area via backing store.
0793 paintViaBackingStore(g);
0794 } else {
0795 // The image was scrolled. Manipulate the backing store and flush it to g.
0796 Point blitFrom = new Point();
0797 Point blitTo = new Point();
0798 Dimension blitSize = new Dimension();
0799 Rectangle blitPaint = new Rectangle();
0800
0801 Point newLocation = getViewLocation();
0802 int dx = newLocation.x - lastPaintPosition.x;
0803 int dy = newLocation.y - lastPaintPosition.y;
0804 boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo,
0805 blitSize, blitPaint);
0806 if (!canBlit) {
0807 // The image was either moved diagonally or
0808 // moved by more than the image size: paint normally.
0809 paintViaBackingStore(g);
0810 } else {
0811 int bdx = blitTo.x - blitFrom.x;
0812 int bdy = blitTo.y - blitFrom.y;
0813
0814 // Move the relevant part of the backing store.
0815 Rectangle clip = g.getClipBounds();
0816 // We don't want to inherit the clip region when copying
0817 // bits, if it is inherited it will result in not moving
0818 // all of the image resulting in garbage appearing on
0819 // the screen.
0820 g.setClip(0, 0, width, height);
0821 Graphics bsg = getBackingStoreGraphics(g);
0822 try {
0823 bsg.copyArea(blitFrom.x, blitFrom.y,
0824 blitSize.width, blitSize.height, bdx,
0825 bdy);
0826
0827 g.setClip(clip.x, clip.y, clip.width,
0828 clip.height);
0829 // Paint the rest of the view; the part that has just been exposed.
0830 Rectangle r = viewBounds
0831 .intersection(blitPaint);
0832 bsg.setClip(r);
0833 super .paint(bsg);
0834
0835 // Copy whole of the backing store to g.
0836 g.drawImage(backingStoreImage, 0, 0, this );
0837 } finally {
0838 bsg.dispose();
0839 }
0840 }
0841 }
0842 }
0843 lastPaintPosition = getViewLocation();
0844 scrollUnderway = false;
0845 }
0846
0847 /**
0848 * Sets the bounds of this viewport. If the viewport's width
0849 * or height has changed, fire a <code>StateChanged</code> event.
0850 *
0851 * @param x left edge of the origin
0852 * @param y top edge of the origin
0853 * @param w width in pixels
0854 * @param h height in pixels
0855 *
0856 * @see JComponent#reshape(int, int, int, int)
0857 */
0858 public void reshape(int x, int y, int w, int h) {
0859 boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
0860 if (sizeChanged) {
0861 backingStoreImage = null;
0862 }
0863 super .reshape(x, y, w, h);
0864 if (sizeChanged) {
0865 fireStateChanged();
0866 }
0867 }
0868
0869 /**
0870 * Used to control the method of scrolling the viewport contents.
0871 * You may want to change this mode to get maximum performance for your
0872 * use case.
0873 *
0874 * @param mode one of the following values:
0875 * <ul>
0876 * <li> JViewport.BLIT_SCROLL_MODE
0877 * <li> JViewport.BACKINGSTORE_SCROLL_MODE
0878 * <li> JViewport.SIMPLE_SCROLL_MODE
0879 * </ul>
0880 *
0881 * @see #BLIT_SCROLL_MODE
0882 * @see #BACKINGSTORE_SCROLL_MODE
0883 * @see #SIMPLE_SCROLL_MODE
0884 *
0885 * @beaninfo
0886 * bound: false
0887 * description: Method of moving contents for incremental scrolls.
0888 * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE
0889 * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE
0890 * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE
0891 *
0892 * @since 1.3
0893 */
0894 public void setScrollMode(int mode) {
0895 scrollMode = mode;
0896 if (mode == BACKINGSTORE_SCROLL_MODE) {
0897 backingStore = true;
0898 } else {
0899 backingStore = false;
0900 }
0901 }
0902
0903 /**
0904 * Returns the current scrolling mode.
0905 *
0906 * @return the <code>scrollMode</code> property
0907 * @see #setScrollMode
0908 * @since 1.3
0909 */
0910 public int getScrollMode() {
0911 return scrollMode;
0912 }
0913
0914 /**
0915 * Returns <code>true</code> if this viewport is maintaining
0916 * an offscreen image of its contents.
0917 *
0918 * @return <code>true</code> if <code>scrollMode</code> is
0919 * <code>BACKINGSTORE_SCROLL_MODE</code>
0920 *
0921 * @deprecated As of Java 2 platform v1.3, replaced by
0922 * <code>getScrollMode()</code>.
0923 */
0924 @Deprecated
0925 public boolean isBackingStoreEnabled() {
0926 return scrollMode == BACKINGSTORE_SCROLL_MODE;
0927 }
0928
0929 /**
0930 * If true if this viewport will maintain an offscreen
0931 * image of its contents. The image is used to reduce the cost
0932 * of small one dimensional changes to the <code>viewPosition</code>.
0933 * Rather than repainting the entire viewport we use
0934 * <code>Graphics.copyArea</code> to effect some of the scroll.
0935 *
0936 * @param enabled if true, maintain an offscreen backing store
0937 *
0938 * @deprecated As of Java 2 platform v1.3, replaced by
0939 * <code>setScrollMode()</code>.
0940 */
0941 @Deprecated
0942 public void setBackingStoreEnabled(boolean enabled) {
0943 if (enabled) {
0944 setScrollMode(BACKINGSTORE_SCROLL_MODE);
0945 } else {
0946 setScrollMode(BLIT_SCROLL_MODE);
0947 }
0948 }
0949
0950 private final boolean isBlitting() {
0951 Component view = getView();
0952 return (scrollMode == BLIT_SCROLL_MODE)
0953 && (view instanceof JComponent)
0954 && ((JComponent) view).isOpaque();
0955 }
0956
0957 /**
0958 * Returns the <code>JViewport</code>'s one child or <code>null</code>.
0959 *
0960 * @return the viewports child, or <code>null</code> if none exists
0961 *
0962 * @see #setView
0963 */
0964 public Component getView() {
0965 return (getComponentCount() > 0) ? getComponent(0) : null;
0966 }
0967
0968 /**
0969 * Sets the <code>JViewport</code>'s one lightweight child
0970 * (<code>view</code>), which can be <code>null</code>.
0971 *
0972 * @param view the viewport's new lightweight child
0973 *
0974 * @see #getView
0975 */
0976 public void setView(Component view) {
0977
0978 /* Remove the viewport's existing children, if any.
0979 * Note that removeAll() isn't used here because it
0980 * doesn't call remove() (which JViewport overrides).
0981 */
0982 int n = getComponentCount();
0983 for (int i = n - 1; i >= 0; i--) {
0984 remove(getComponent(i));
0985 }
0986
0987 isViewSizeSet = false;
0988
0989 if (view != null) {
0990 super .addImpl(view, null, -1);
0991 viewListener = createViewListener();
0992 view.addComponentListener(viewListener);
0993 }
0994
0995 if (hasHadValidView) {
0996 // Only fire a change if a view has been installed.
0997 fireStateChanged();
0998 } else if (view != null) {
0999 hasHadValidView = true;
1000 }
1001
1002 revalidate();
1003 repaint();
1004 }
1005
1006 /**
1007 * If the view's size hasn't been explicitly set, return the
1008 * preferred size, otherwise return the view's current size.
1009 * If there is no view, return 0,0.
1010 *
1011 * @return a <code>Dimension</code> object specifying the size of the view
1012 */
1013 public Dimension getViewSize() {
1014 Component view = getView();
1015
1016 if (view == null) {
1017 return new Dimension(0, 0);
1018 } else if (isViewSizeSet) {
1019 return view.getSize();
1020 } else {
1021 return view.getPreferredSize();
1022 }
1023 }
1024
1025 /**
1026 * Sets the size of the view. A state changed event will be fired.
1027 *
1028 * @param newSize a <code>Dimension</code> object specifying the new
1029 * size of the view
1030 */
1031 public void setViewSize(Dimension newSize) {
1032 Component view = getView();
1033 if (view != null) {
1034 Dimension oldSize = view.getSize();
1035 if (!newSize.equals(oldSize)) {
1036 // scrollUnderway will be true if this is invoked as the
1037 // result of a validate and setViewPosition was previously
1038 // invoked.
1039 scrollUnderway = false;
1040 view.setSize(newSize);
1041 isViewSizeSet = true;
1042 fireStateChanged();
1043 }
1044 }
1045 }
1046
1047 /**
1048 * Returns the view coordinates that appear in the upper left
1049 * hand corner of the viewport, or 0,0 if there's no view.
1050 *
1051 * @return a <code>Point</code> object giving the upper left coordinates
1052 */
1053 public Point getViewPosition() {
1054 Component view = getView();
1055 if (view != null) {
1056 Point p = view.getLocation();
1057 p.x = -p.x;
1058 p.y = -p.y;
1059 return p;
1060 } else {
1061 return new Point(0, 0);
1062 }
1063 }
1064
1065 /**
1066 * Sets the view coordinates that appear in the upper left
1067 * hand corner of the viewport, does nothing if there's no view.
1068 *
1069 * @param p a <code>Point</code> object giving the upper left coordinates
1070 */
1071 public void setViewPosition(Point p) {
1072 Component view = getView();
1073 if (view == null) {
1074 return;
1075 }
1076
1077 int oldX, oldY, x = p.x, y = p.y;
1078
1079 /* Collect the old x,y values for the views location
1080 * and do the song and dance to avoid allocating
1081 * a Rectangle object if we don't have to.
1082 */
1083 if (view instanceof JComponent) {
1084 JComponent c = (JComponent) view;
1085 oldX = c.getX();
1086 oldY = c.getY();
1087 } else {
1088 Rectangle r = view.getBounds();
1089 oldX = r.x;
1090 oldY = r.y;
1091 }
1092
1093 /* The view scrolls in the opposite direction to mouse
1094 * movement.
1095 */
1096 int newX = -x;
1097 int newY = -y;
1098
1099 if ((oldX != newX) || (oldY != newY)) {
1100 if (!waitingForRepaint && isBlitting()
1101 && canUseWindowBlitter()) {
1102 RepaintManager rm = RepaintManager.currentManager(this );
1103 // The cast to JComponent will work, if view is not
1104 // a JComponent, isBlitting will return false.
1105 JComponent jview = (JComponent) view;
1106 Rectangle dirty = rm.getDirtyRegion(jview);
1107 if (dirty == null
1108 || !dirty.contains(jview.getVisibleRect())) {
1109 rm.beginPaint();
1110 try {
1111 Graphics g = JComponent.safelyGetGraphics(this );
1112 flushViewDirtyRegion(g, dirty);
1113 view.setLocation(newX, newY);
1114 g.setClip(0, 0, getWidth(), Math.min(
1115 getHeight(), jview.getHeight()));
1116 // Repaint the complete component if the blit succeeded
1117 // and needsRepaintAfterBlit returns true.
1118 repaintAll = (windowBlitPaint(g) && needsRepaintAfterBlit());
1119 g.dispose();
1120 rm
1121 .markCompletelyClean((JComponent) getParent());
1122 rm.markCompletelyClean(this );
1123 rm.markCompletelyClean(jview);
1124 } finally {
1125 rm.endPaint();
1126 }
1127 } else {
1128 // The visible region is dirty, no point in doing copyArea
1129 view.setLocation(newX, newY);
1130 repaintAll = false;
1131 }
1132 } else {
1133 scrollUnderway = true;
1134 // This calls setBounds(), and then repaint().
1135 view.setLocation(newX, newY);
1136 repaintAll = false;
1137 }
1138 fireStateChanged();
1139 }
1140 }
1141
1142 /**
1143 * Returns a rectangle whose origin is <code>getViewPosition</code>
1144 * and size is <code>getExtentSize</code>.
1145 * This is the visible part of the view, in view coordinates.
1146 *
1147 * @return a <code>Rectangle</code> giving the visible part of
1148 * the view using view coordinates.
1149 */
1150 public Rectangle getViewRect() {
1151 return new Rectangle(getViewPosition(), getExtentSize());
1152 }
1153
1154 /**
1155 * Computes the parameters for a blit where the backing store image
1156 * currently contains <code>oldLoc</code> in the upper left hand corner
1157 * and we're scrolling to <code>newLoc</code>.
1158 * The parameters are modified
1159 * to return the values required for the blit.
1160 *
1161 * @param dx the horizontal delta
1162 * @param dy the vertical delta
1163 * @param blitFrom the <code>Point</code> we're blitting from
1164 * @param blitTo the <code>Point</code> we're blitting to
1165 * @param blitSize the <code>Dimension</code> of the area to blit
1166 * @param blitPaint the area to blit
1167 * @return true if the parameters are modified and we're ready to blit;
1168 * false otherwise
1169 */
1170 protected boolean computeBlit(int dx, int dy, Point blitFrom,
1171 Point blitTo, Dimension blitSize, Rectangle blitPaint) {
1172 int dxAbs = Math.abs(dx);
1173 int dyAbs = Math.abs(dy);
1174 Dimension extentSize = getExtentSize();
1175
1176 if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
1177 if (dy < 0) {
1178 blitFrom.y = -dy;
1179 blitTo.y = 0;
1180 blitPaint.y = extentSize.height + dy;
1181 } else {
1182 blitFrom.y = 0;
1183 blitTo.y = dy;
1184 blitPaint.y = 0;
1185 }
1186
1187 blitPaint.x = blitFrom.x = blitTo.x = 0;
1188
1189 blitSize.width = extentSize.width;
1190 blitSize.height = extentSize.height - dyAbs;
1191
1192 blitPaint.width = extentSize.width;
1193 blitPaint.height = dyAbs;
1194
1195 return true;
1196 }
1197
1198 else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) {
1199 if (dx < 0) {
1200 blitFrom.x = -dx;
1201 blitTo.x = 0;
1202 blitPaint.x = extentSize.width + dx;
1203 } else {
1204 blitFrom.x = 0;
1205 blitTo.x = dx;
1206 blitPaint.x = 0;
1207 }
1208
1209 blitPaint.y = blitFrom.y = blitTo.y = 0;
1210
1211 blitSize.width = extentSize.width - dxAbs;
1212 blitSize.height = extentSize.height;
1213
1214 blitPaint.width = dxAbs;
1215 blitPaint.height = extentSize.height;
1216
1217 return true;
1218 }
1219
1220 else {
1221 return false;
1222 }
1223 }
1224
1225 /**
1226 * Returns the size of the visible part of the view in view coordinates.
1227 *
1228 * @return a <code>Dimension</code> object giving the size of the view
1229 */
1230 public Dimension getExtentSize() {
1231 return getSize();
1232 }
1233
1234 /**
1235 * Converts a size in pixel coordinates to view coordinates.
1236 * Subclasses of viewport that support "logical coordinates"
1237 * will override this method.
1238 *
1239 * @param size a <code>Dimension</code> object using pixel coordinates
1240 * @return a <code>Dimension</code> object converted to view coordinates
1241 */
1242 public Dimension toViewCoordinates(Dimension size) {
1243 return new Dimension(size);
1244 }
1245
1246 /**
1247 * Converts a point in pixel coordinates to view coordinates.
1248 * Subclasses of viewport that support "logical coordinates"
1249 * will override this method.
1250 *
1251 * @param p a <code>Point</code> object using pixel coordinates
1252 * @return a <code>Point</code> object converted to view coordinates
1253 */
1254 public Point toViewCoordinates(Point p) {
1255 return new Point(p);
1256 }
1257
1258 /**
1259 * Sets the size of the visible part of the view using view coordinates.
1260 *
1261 * @param newExtent a <code>Dimension</code> object specifying
1262 * the size of the view
1263 */
1264 public void setExtentSize(Dimension newExtent) {
1265 Dimension oldExtent = getExtentSize();
1266 if (!newExtent.equals(oldExtent)) {
1267 setSize(newExtent);
1268 fireStateChanged();
1269 }
1270 }
1271
1272 /**
1273 * A listener for the view.
1274 * <p>
1275 * <strong>Warning:</strong>
1276 * Serialized objects of this class will not be compatible with
1277 * future Swing releases. The current serialization support is
1278 * appropriate for short term storage or RMI between applications running
1279 * the same version of Swing. As of 1.4, support for long term storage
1280 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1281 * has been added to the <code>java.beans</code> package.
1282 * Please see {@link java.beans.XMLEncoder}.
1283 */
1284 protected class ViewListener extends ComponentAdapter implements
1285 Serializable {
1286 public void componentResized(ComponentEvent e) {
1287 fireStateChanged();
1288 revalidate();
1289 }
1290 }
1291
1292 /**
1293 * Creates a listener for the view.
1294 * @return a <code>ViewListener</code>
1295 */
1296 protected ViewListener createViewListener() {
1297 return new ViewListener();
1298 }
1299
1300 /**
1301 * Subclassers can override this to install a different
1302 * layout manager (or <code>null</code>) in the constructor. Returns
1303 * the <code>LayoutManager</code> to install on the <code>JViewport</code>.
1304 *
1305 * @return a <code>LayoutManager</code>
1306 */
1307 protected LayoutManager createLayoutManager() {
1308 return ViewportLayout.SHARED_INSTANCE;
1309 }
1310
1311 /**
1312 * Adds a <code>ChangeListener</code> to the list that is
1313 * notified each time the view's
1314 * size, position, or the viewport's extent size has changed.
1315 *
1316 * @param l the <code>ChangeListener</code> to add
1317 * @see #removeChangeListener
1318 * @see #setViewPosition
1319 * @see #setViewSize
1320 * @see #setExtentSize
1321 */
1322 public void addChangeListener(ChangeListener l) {
1323 listenerList.add(ChangeListener.class, l);
1324 }
1325
1326 /**
1327 * Removes a <code>ChangeListener</code> from the list that's notified each
1328 * time the views size, position, or the viewports extent size
1329 * has changed.
1330 *
1331 * @param l the <code>ChangeListener</code> to remove
1332 * @see #addChangeListener
1333 */
1334 public void removeChangeListener(ChangeListener l) {
1335 listenerList.remove(ChangeListener.class, l);
1336 }
1337
1338 /**
1339 * Returns an array of all the <code>ChangeListener</code>s added
1340 * to this JViewport with addChangeListener().
1341 *
1342 * @return all of the <code>ChangeListener</code>s added or an empty
1343 * array if no listeners have been added
1344 * @since 1.4
1345 */
1346 public ChangeListener[] getChangeListeners() {
1347 return (ChangeListener[]) listenerList
1348 .getListeners(ChangeListener.class);
1349 }
1350
1351 /**
1352 * Notifies all <code>ChangeListeners</code> when the views
1353 * size, position, or the viewports extent size has changed.
1354 *
1355 * @see #addChangeListener
1356 * @see #removeChangeListener
1357 * @see EventListenerList
1358 */
1359 protected void fireStateChanged() {
1360 Object[] listeners = listenerList.getListenerList();
1361 for (int i = listeners.length - 2; i >= 0; i -= 2) {
1362 if (listeners[i] == ChangeListener.class) {
1363 if (changeEvent == null) {
1364 changeEvent = new ChangeEvent(this );
1365 }
1366 ((ChangeListener) listeners[i + 1])
1367 .stateChanged(changeEvent);
1368 }
1369 }
1370 }
1371
1372 /**
1373 * Always repaint in the parents coordinate system to make sure
1374 * only one paint is performed by the <code>RepaintManager</code>.
1375 *
1376 * @param tm maximum time in milliseconds before update
1377 * @param x the <code>x</code> coordinate (pixels over from left)
1378 * @param y the <code>y</code> coordinate (pixels down from top)
1379 * @param w the width
1380 * @param h the height
1381 * @see java.awt.Component#update(java.awt.Graphics)
1382 */
1383 public void repaint(long tm, int x, int y, int w, int h) {
1384 Container parent = getParent();
1385 if (parent != null)
1386 parent.repaint(tm, x + getX(), y + getY(), w, h);
1387 else
1388 super .repaint(tm, x, y, w, h);
1389 }
1390
1391 /**
1392 * Returns a string representation of this <code>JViewport</code>.
1393 * This method
1394 * is intended to be used only for debugging purposes, and the
1395 * content and format of the returned string may vary between
1396 * implementations. The returned string may be empty but may not
1397 * be <code>null</code>.
1398 *
1399 * @return a string representation of this <code>JViewport</code>
1400 */
1401 protected String paramString() {
1402 String isViewSizeSetString = (isViewSizeSet ? "true" : "false");
1403 String lastPaintPositionString = (lastPaintPosition != null ? lastPaintPosition
1404 .toString()
1405 : "");
1406 String scrollUnderwayString = (scrollUnderway ? "true"
1407 : "false");
1408
1409 return super .paramString() + ",isViewSizeSet="
1410 + isViewSizeSetString + ",lastPaintPosition="
1411 + lastPaintPositionString + ",scrollUnderway="
1412 + scrollUnderwayString;
1413 }
1414
1415 //
1416 // Following is used when doBlit is true.
1417 //
1418
1419 /**
1420 * Notifies listeners of a property change. This is subclassed to update
1421 * the <code>windowBlit</code> property.
1422 * (The <code>putClientProperty</code> property is final).
1423 *
1424 * @param propertyName a string containing the property name
1425 * @param oldValue the old value of the property
1426 * @param newValue the new value of the property
1427 */
1428 protected void firePropertyChange(String propertyName,
1429 Object oldValue, Object newValue) {
1430 super .firePropertyChange(propertyName, oldValue, newValue);
1431 if (propertyName.equals(EnableWindowBlit)) {
1432 if (newValue != null) {
1433 setScrollMode(BLIT_SCROLL_MODE);
1434 } else {
1435 setScrollMode(SIMPLE_SCROLL_MODE);
1436 }
1437 }
1438 }
1439
1440 /**
1441 * Returns true if the component needs to be completely repainted after
1442 * a blit and a paint is received.
1443 */
1444 private boolean needsRepaintAfterBlit() {
1445 // Find the first heavy weight ancestor. isObscured and
1446 // canDetermineObscurity are only appropriate for heavy weights.
1447 Component heavyParent = getParent();
1448
1449 while (heavyParent != null && heavyParent.isLightweight()) {
1450 heavyParent = heavyParent.getParent();
1451 }
1452
1453 if (heavyParent != null) {
1454 ComponentPeer peer = heavyParent.getPeer();
1455
1456 if (peer != null && peer.canDetermineObscurity()
1457 && !peer.isObscured()) {
1458 // The peer says we aren't obscured, therefore we can assume
1459 // that we won't later be messaged to paint a portion that
1460 // we tried to blit that wasn't valid.
1461 // It is certainly possible that when we blited we were
1462 // obscured, and by the time this is invoked we aren't, but the
1463 // chances of that happening are pretty slim.
1464 return false;
1465 }
1466 }
1467 return true;
1468 }
1469
1470 private Timer createRepaintTimer() {
1471 Timer timer = new Timer(300, new ActionListener() {
1472 public void actionPerformed(ActionEvent ae) {
1473 // waitingForRepaint will be false if a paint came down
1474 // with the complete clip rect, in which case we don't
1475 // have to cause a repaint.
1476 if (waitingForRepaint) {
1477 repaint();
1478 }
1479 }
1480 });
1481 timer.setRepeats(false);
1482 return timer;
1483 }
1484
1485 /**
1486 * If the repaint manager has a dirty region for the view, the view is
1487 * asked to paint.
1488 *
1489 * @param g the <code>Graphics</code> context within which to paint
1490 */
1491 private void flushViewDirtyRegion(Graphics g, Rectangle dirty) {
1492 JComponent view = (JComponent) getView();
1493 if (dirty != null && dirty.width > 0 && dirty.height > 0) {
1494 dirty.x += view.getX();
1495 dirty.y += view.getY();
1496 Rectangle clip = g.getClipBounds();
1497 if (clip == null) {
1498 // Only happens in 1.2
1499 g.setClip(0, 0, getWidth(), getHeight());
1500 }
1501 g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
1502 clip = g.getClipBounds();
1503 // Only paint the dirty region if it is visible.
1504 if (clip.width > 0 && clip.height > 0) {
1505 paintView(g);
1506 }
1507 }
1508 }
1509
1510 /**
1511 * Used when blitting.
1512 *
1513 * @param g the <code>Graphics</code> context within which to paint
1514 * @return true if blitting succeeded; otherwise false
1515 */
1516 private boolean windowBlitPaint(Graphics g) {
1517 int width = getWidth();
1518 int height = getHeight();
1519
1520 if ((width == 0) || (height == 0)) {
1521 return false;
1522 }
1523
1524 boolean retValue;
1525 RepaintManager rm = RepaintManager.currentManager(this );
1526 JComponent view = (JComponent) getView();
1527
1528 if (lastPaintPosition == null
1529 || lastPaintPosition.equals(getViewLocation())) {
1530 paintView(g);
1531 retValue = false;
1532 } else {
1533 // The image was scrolled. Manipulate the backing store and flush
1534 // it to g.
1535 Point blitFrom = new Point();
1536 Point blitTo = new Point();
1537 Dimension blitSize = new Dimension();
1538 Rectangle blitPaint = new Rectangle();
1539
1540 Point newLocation = getViewLocation();
1541 int dx = newLocation.x - lastPaintPosition.x;
1542 int dy = newLocation.y - lastPaintPosition.y;
1543 boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo,
1544 blitSize, blitPaint);
1545 if (!canBlit) {
1546 paintView(g);
1547 retValue = false;
1548 } else {
1549 // Prepare the rest of the view; the part that has just been
1550 // exposed.
1551 Rectangle r = view.getBounds().intersection(blitPaint);
1552 r.x -= view.getX();
1553 r.y -= view.getY();
1554
1555 blitDoubleBuffered(view, g, r.x, r.y, r.width,
1556 r.height, blitFrom.x, blitFrom.y, blitTo.x,
1557 blitTo.y, blitSize.width, blitSize.height);
1558 retValue = true;
1559 }
1560 }
1561 lastPaintPosition = getViewLocation();
1562 return retValue;
1563 }
1564
1565 //
1566 // NOTE: the code below uses paintForceDoubleBuffered for historical
1567 // reasons. If we're going to allow a blit we've already accounted for
1568 // everything that paintImmediately and _paintImmediately does, for that
1569 // reason we call into paintForceDoubleBuffered to diregard whether or
1570 // not setDoubleBuffered(true) was invoked on the view.
1571 //
1572
1573 private void blitDoubleBuffered(JComponent view, Graphics g,
1574 int clipX, int clipY, int clipW, int clipH, int blitFromX,
1575 int blitFromY, int blitToX, int blitToY, int blitW,
1576 int blitH) {
1577 // NOTE:
1578 // blitFrom/blitTo are in JViewport coordinates system
1579 // not the views coordinate space.
1580 // clip* are in the views coordinate space.
1581 RepaintManager rm = RepaintManager.currentManager(this );
1582 int bdx = blitToX - blitFromX;
1583 int bdy = blitToY - blitFromY;
1584
1585 // Shift the scrolled region
1586 rm.copyArea(this , g, blitFromX, blitFromY, blitW, blitH, bdx,
1587 bdy, false);
1588
1589 // Paint the newly exposed region.
1590 int x = view.getX();
1591 int y = view.getY();
1592 g.translate(x, y);
1593 g.setClip(clipX, clipY, clipW, clipH);
1594 view.paintForceDoubleBuffered(g);
1595 g.translate(-x, -y);
1596 }
1597
1598 /**
1599 * Called to paint the view, usually when <code>blitPaint</code>
1600 * can not blit.
1601 *
1602 * @param g the <code>Graphics</code> context within which to paint
1603 */
1604 private void paintView(Graphics g) {
1605 Rectangle clip = g.getClipBounds();
1606 JComponent view = (JComponent) getView();
1607
1608 if (view.getWidth() >= getWidth()) {
1609 // Graphics is relative to JViewport, need to map to view's
1610 // coordinates space.
1611 int x = view.getX();
1612 int y = view.getY();
1613 g.translate(x, y);
1614 g.setClip(clip.x - x, clip.y - y, clip.width, clip.height);
1615 view.paintForceDoubleBuffered(g);
1616 g.translate(-x, -y);
1617 g.setClip(clip.x, clip.y, clip.width, clip.height);
1618 } else {
1619 // To avoid any problems that may result from the viewport being
1620 // bigger than the view we start painting from the viewport.
1621 try {
1622 inBlitPaint = true;
1623 paintForceDoubleBuffered(g);
1624 } finally {
1625 inBlitPaint = false;
1626 }
1627 }
1628 }
1629
1630 /**
1631 * Returns true if the viewport is not obscured by one of its ancestors,
1632 * or its ancestors children and if the viewport is showing. Blitting
1633 * when the view isn't showing will work,
1634 * or rather <code>copyArea</code> will work,
1635 * but will not produce the expected behavior.
1636 */
1637 private boolean canUseWindowBlitter() {
1638 if (!isShowing()
1639 || (!(getParent() instanceof JComponent) && !(getView() instanceof JComponent))) {
1640 return false;
1641 }
1642 if (isPainting()) {
1643 // We're in the process of painting, don't blit. If we were
1644 // to blit we would draw on top of what we're already drawing,
1645 // so bail.
1646 return false;
1647 }
1648
1649 Rectangle dirtyRegion = RepaintManager.currentManager(this )
1650 .getDirtyRegion((JComponent) getParent());
1651
1652 if (dirtyRegion != null && dirtyRegion.width > 0
1653 && dirtyRegion.height > 0) {
1654 // Part of the scrollpane needs to be repainted too, don't blit.
1655 return false;
1656 }
1657
1658 Rectangle clip = new Rectangle(0, 0, getWidth(), getHeight());
1659 Rectangle oldClip = new Rectangle();
1660 Rectangle tmp2 = null;
1661 Container parent;
1662 Component lastParent = null;
1663 int x, y, w, h;
1664
1665 for (parent = this ; parent != null
1666 && isLightweightComponent(parent); parent = parent
1667 .getParent()) {
1668 x = parent.getX();
1669 y = parent.getY();
1670 w = parent.getWidth();
1671 h = parent.getHeight();
1672
1673 oldClip.setBounds(clip);
1674 SwingUtilities.computeIntersection(0, 0, w, h, clip);
1675 if (!clip.equals(oldClip))
1676 return false;
1677
1678 if (lastParent != null
1679 && parent instanceof JComponent
1680 && !((JComponent) parent)
1681 .isOptimizedDrawingEnabled()) {
1682 Component comps[] = parent.getComponents();
1683 int index = 0;
1684
1685 for (int i = comps.length - 1; i >= 0; i--) {
1686 if (comps[i] == lastParent) {
1687 index = i - 1;
1688 break;
1689 }
1690 }
1691
1692 while (index >= 0) {
1693 tmp2 = comps[index].getBounds(tmp2);
1694
1695 if (tmp2.intersects(clip))
1696 return false;
1697 index--;
1698 }
1699 }
1700 clip.x += x;
1701 clip.y += y;
1702 lastParent = parent;
1703 }
1704 if (parent == null) {
1705 // No Window parent.
1706 return false;
1707 }
1708 return true;
1709 }
1710
1711 /////////////////
1712 // Accessibility support
1713 ////////////////
1714
1715 /**
1716 * Gets the AccessibleContext associated with this JViewport.
1717 * For viewports, the AccessibleContext takes the form of an
1718 * AccessibleJViewport.
1719 * A new AccessibleJViewport instance is created if necessary.
1720 *
1721 * @return an AccessibleJViewport that serves as the
1722 * AccessibleContext of this JViewport
1723 */
1724 public AccessibleContext getAccessibleContext() {
1725 if (accessibleContext == null) {
1726 accessibleContext = new AccessibleJViewport();
1727 }
1728 return accessibleContext;
1729 }
1730
1731 /**
1732 * This class implements accessibility support for the
1733 * <code>JViewport</code> class. It provides an implementation of the
1734 * Java Accessibility API appropriate to viewport user-interface elements.
1735 * <p>
1736 * <strong>Warning:</strong>
1737 * Serialized objects of this class will not be compatible with
1738 * future Swing releases. The current serialization support is
1739 * appropriate for short term storage or RMI between applications running
1740 * the same version of Swing. As of 1.4, support for long term storage
1741 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1742 * has been added to the <code>java.beans</code> package.
1743 * Please see {@link java.beans.XMLEncoder}.
1744 */
1745 protected class AccessibleJViewport extends AccessibleJComponent {
1746 /**
1747 * Get the role of this object.
1748 *
1749 * @return an instance of AccessibleRole describing the role of
1750 * the object
1751 */
1752 public AccessibleRole getAccessibleRole() {
1753 return AccessibleRole.VIEWPORT;
1754 }
1755 } // inner class AccessibleJViewport
1756 }
|