JViewport.java revision 11926:39bd7fa12bc3
11553Srgrimes/* 21553Srgrimes * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 31553Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 41553Srgrimes * 51553Srgrimes * This code is free software; you can redistribute it and/or modify it 61553Srgrimes * under the terms of the GNU General Public License version 2 only, as 71553Srgrimes * published by the Free Software Foundation. Oracle designates this 81553Srgrimes * particular file as subject to the "Classpath" exception as provided 91553Srgrimes * by Oracle in the LICENSE file that accompanied this code. 101553Srgrimes * 111553Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT 121553Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 131553Srgrimes * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 141553Srgrimes * version 2 for more details (a copy is included in the LICENSE file that 151553Srgrimes * accompanied this code). 161553Srgrimes * 171553Srgrimes * You should have received a copy of the GNU General Public License version 181553Srgrimes * 2 along with this work; if not, write to the Free Software Foundation, 191553Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 201553Srgrimes * 211553Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 221553Srgrimes * or visit www.oracle.com if you need additional information or have any 231553Srgrimes * questions. 241553Srgrimes */ 251553Srgrimes 261553Srgrimespackage javax.swing; 271553Srgrimes 281553Srgrimesimport java.awt.*; 291553Srgrimesimport java.awt.event.*; 301553Srgrimesimport java.awt.peer.ComponentPeer; 311553Srgrimesimport java.beans.Transient; 321553Srgrimesimport javax.swing.plaf.ViewportUI; 331553Srgrimes 341553Srgrimesimport javax.swing.event.*; 351553Srgrimesimport javax.swing.border.*; 361553Srgrimesimport javax.accessibility.*; 371553Srgrimes 381553Srgrimesimport java.io.Serializable; 391553Srgrimes 401553Srgrimesimport sun.awt.AWTAccessor; 4129780Scharnier 421553Srgrimes/** 431553Srgrimes * The "viewport" or "porthole" through which you see the underlying 441553Srgrimes * information. When you scroll, what moves is the viewport. It is like 451553Srgrimes * peering through a camera's viewfinder. Moving the viewfinder upwards 46117621Sgad * brings new things into view at the top of the picture and loses 471553Srgrimes * things that were at the bottom. 48117621Sgad * <p> 49117621Sgad * By default, <code>JViewport</code> is opaque. To change this, use the 5029780Scharnier * <code>setOpaque</code> method. 511553Srgrimes * <p> 52117621Sgad * <b>NOTE:</b>We have implemented a faster scrolling algorithm that 53117621Sgad * does not require a buffer to draw in. The algorithm works as follows: 54117621Sgad * <ol><li>The view and parent view and checked to see if they are 551553Srgrimes * <code>JComponents</code>, 561553Srgrimes * if they aren't, stop and repaint the whole viewport. 571553Srgrimes * <li>If the viewport is obscured by an ancestor, stop and repaint the whole 581553Srgrimes * viewport. 591553Srgrimes * <li>Compute the region that will become visible, if it is as big as 601553Srgrimes * the viewport, stop and repaint the whole view region. 611553Srgrimes * <li>Obtain the ancestor <code>Window</code>'s graphics and 621553Srgrimes * do a <code>copyArea</code> on the scrolled region. 631553Srgrimes * <li>Message the view to repaint the newly visible region. 641553Srgrimes * <li>The next time paint is invoked on the viewport, if the clip region 6595434Sgad * is smaller than the viewport size a timer is kicked off to repaint the 6695434Sgad * whole region. 671553Srgrimes * </ol> 681553Srgrimes * In general this approach is much faster. Compared to the backing store 691553Srgrimes * approach this avoids the overhead of maintaining an offscreen buffer and 7031492Swollman * having to do two <code>copyArea</code>s. 7153956Sache * Compared to the non backing store case this 721553Srgrimes * approach will greatly reduce the painted region. 731553Srgrimes * <p> 741553Srgrimes * This approach can cause slower times than the backing store approach 751553Srgrimes * when the viewport is obscured by another window, or partially offscreen. 761553Srgrimes * When another window 771553Srgrimes * obscures the viewport the copyArea will copy garbage and a 78241015Smdf * paint event will be generated by the system to inform us we need to 791553Srgrimes * paint the newly exposed region. The only way to handle this is to 801553Srgrimes * repaint the whole viewport, which can cause slower performance than the 811553Srgrimes * backing store case. In most applications very rarely will the user be 821553Srgrimes * scrolling while the viewport is obscured by another window or offscreen, 831553Srgrimes * so this optimization is usually worth the performance hit when obscured. 841553Srgrimes * <p> 851553Srgrimes * <strong>Warning:</strong> Swing is not thread safe. For more 861553Srgrimes * information see <a 8778300Sgad * href="package-summary.html#threading">Swing's Threading 8823122Smpp * Policy</a>. 891553Srgrimes * <p> 901553Srgrimes * <strong>Warning:</strong> 911553Srgrimes * Serialized objects of this class will not be compatible with 921553Srgrimes * future Swing releases. The current serialization support is 931553Srgrimes * appropriate for short term storage or RMI between applications running 941553Srgrimes * the same version of Swing. As of 1.4, support for long term storage 9578146Sgad * of all JavaBeans™ 961553Srgrimes * has been added to the <code>java.beans</code> package. 971553Srgrimes * Please see {@link java.beans.XMLEncoder}. 981553Srgrimes * 9984695Sgad * @author Hans Muller 1001553Srgrimes * @author Philip Milne 1018857Srgrimes * @see JScrollPane 1021553Srgrimes * @since 1.2 1031553Srgrimes */ 1041553Srgrimes@SuppressWarnings("serial") // Same-version serialization only 1051553Srgrimespublic class JViewport extends JComponent implements Accessible 10653956Sache{ 1071553Srgrimes /** 10843507Swollman * @see #getUIClassID 1091553Srgrimes * @see #readObject 11061913Swollman */ 1111553Srgrimes private static final String uiClassID = "ViewportUI"; 1121553Srgrimes 1131553Srgrimes /** Property used to indicate window blitting should not be done. 11478146Sgad */ 11578146Sgad static final Object EnableWindowBlit = "EnableWindowBlit"; 11678146Sgad 11778146Sgad /** 11878146Sgad * True when the viewport dimensions have been determined. 11978146Sgad * The default is false. 12078146Sgad */ 12178146Sgad protected boolean isViewSizeSet = false; 12278146Sgad 12378146Sgad /** 12478146Sgad * The last <code>viewPosition</code> that we've painted, so we know how 12578146Sgad * much of the backing store image is valid. 12678146Sgad */ 12778146Sgad protected Point lastPaintPosition = null; 1281553Srgrimes 12927618Simp /** 13027618Simp * True when this viewport is maintaining an offscreen image of its 13119202Simp * contents, so that some scrolling can take place using fast "bit-blit" 13278146Sgad * operations instead of by accessing the view object to construct the 1331553Srgrimes * display. The default is <code>false</code>. 1341553Srgrimes * 1351553Srgrimes * @deprecated As of Java 2 platform v1.3 13678146Sgad * @see #setScrollMode 13778146Sgad */ 1381553Srgrimes @Deprecated 13915733Sjoerg protected boolean backingStore = false; 14068275Sgad 1411553Srgrimes /** The view image used for a backing store. */ 14268275Sgad transient protected Image backingStoreImage = null; 14331492Swollman 1441553Srgrimes /** 14531492Swollman * The <code>scrollUnderway</code> flag is used for components like 14627618Simp * <code>JList</code>. When the downarrow key is pressed on a 14727618Simp * <code>JList</code> and the selected 148241852Seadler * cell is the last in the list, the <code>scrollpane</code> autoscrolls. 1491553Srgrimes * Here, the old selected cell needs repainting and so we need 1501553Srgrimes * a flag to make the viewport do the optimized painting 1511553Srgrimes * only when there is an explicit call to 1521553Srgrimes * <code>setViewPosition(Point)</code>. 1531553Srgrimes * When <code>setBounds</code> is called through other routes, 1541553Srgrimes * the flag is off and the view repaints normally. Another approach 1551553Srgrimes * would be to remove this from the <code>JViewport</code> 1561553Srgrimes * class and have the <code>JList</code> manage this case by using 1571553Srgrimes * <code>setBackingStoreEnabled</code>. The default is 15878280Sgad * <code>false</code>. 15978300Sgad */ 1601553Srgrimes protected boolean scrollUnderway = false; 1611553Srgrimes 16215733Sjoerg /* 16315733Sjoerg * Listener that is notified each time the view changes size. 16461913Swollman */ 16561913Swollman private ComponentListener viewListener = null; 16615733Sjoerg 16715733Sjoerg /* Only one <code>ChangeEvent</code> is needed per 16842340Simp * <code>JViewport</code> instance since the 16942339Simp * event's only (read-only) state is the source property. The source 17042339Simp * of events generated here is always "this". 17115733Sjoerg */ 17215733Sjoerg private transient ChangeEvent changeEvent = null; 17325787Sbrian 1741553Srgrimes /** 17515733Sjoerg * Use <code>graphics.copyArea</code> to implement scrolling. 17615733Sjoerg * This is the fastest for most applications. 17715733Sjoerg * 17815733Sjoerg * @see #setScrollMode 17915733Sjoerg * @since 1.3 1801553Srgrimes */ 1811553Srgrimes public static final int BLIT_SCROLL_MODE = 1; 1821553Srgrimes 1831553Srgrimes /** 18415733Sjoerg * Draws viewport contents into an offscreen image. 1851553Srgrimes * This was previously the default mode for <code>JTable</code>. 1861553Srgrimes * This mode may offer advantages over "blit mode" 18715733Sjoerg * in some cases, but it requires a large chunk of extra RAM. 1881553Srgrimes * 18915733Sjoerg * @see #setScrollMode 1901553Srgrimes * @since 1.3 1911553Srgrimes */ 19215733Sjoerg public static final int BACKINGSTORE_SCROLL_MODE = 2; 19315733Sjoerg 1941553Srgrimes /** 1951553Srgrimes * This mode uses the very simple method of redrawing the entire 19653956Sache * contents of the scrollpane each time it is scrolled. 19753956Sache * This was the default behavior in Swing 1.0 and Swing 1.1. 19853956Sache * Either of the other two options will provide better performance 19953956Sache * in most cases. 2001553Srgrimes * 20115733Sjoerg * @see #setScrollMode 2021553Srgrimes * @since 1.3 2031553Srgrimes */ 20415733Sjoerg public static final int SIMPLE_SCROLL_MODE = 0; 20515733Sjoerg 20643519Swollman /** 20715733Sjoerg * @see #setScrollMode 20815733Sjoerg * @since 1.3 20961913Swollman */ 21061913Swollman private int scrollMode = BLIT_SCROLL_MODE; 21161913Swollman 21261913Swollman // 21315733Sjoerg // Window blitting: 21415733Sjoerg // 21515733Sjoerg // As mentioned in the javadoc when using windowBlit a paint event 2161553Srgrimes // will be generated by the system if copyArea copies a non-visible 21715733Sjoerg // portion of the view (in other words, it copies garbage). We are 21815733Sjoerg // not guaranteed to receive the paint event before other mouse events, 2191553Srgrimes // so we can not be sure we haven't already copied garbage a bunch of 2201553Srgrimes // times to different parts of the view. For that reason when a blit 22115733Sjoerg // happens and the Component is obscured (the check for obscurity 2221553Srgrimes // is not supported on all platforms and is checked via ComponentPeer 2231553Srgrimes // methods) the ivar repaintAll is set to true. When paint is received 2241553Srgrimes // if repaintAll is true (we previously did a blit) it is set to 2251553Srgrimes // false, and if the clip region is smaller than the viewport 2261553Srgrimes // waitingForRepaint is set to true and a timer is started. When 2271553Srgrimes // the timer fires if waitingForRepaint is true, repaint is invoked. 22835251Sobrien // In the mean time, if the view is asked to scroll and waitingForRepaint 22935251Sobrien // is true, a blit will not happen, instead the non-backing store case 2301553Srgrimes // of scrolling will happen, which will reset waitingForRepaint. 2311553Srgrimes // waitingForRepaint is set to false in paint when the clip rect is 23215733Sjoerg // bigger (or equal) to the size of the viewport. 23315733Sjoerg // A Timer is used instead of just a repaint as it appeared to offer 23442340Simp // better performance. 23542339Simp 23642339Simp 2371553Srgrimes /** 2381553Srgrimes * This is set to true in <code>setViewPosition</code> 2391553Srgrimes * if doing a window blit and the viewport is obscured. 2401553Srgrimes */ 2411553Srgrimes private transient boolean repaintAll; 2421553Srgrimes 24315733Sjoerg /** 24415733Sjoerg * This is set to true in paint, if <code>repaintAll</code> 2451553Srgrimes * is true and the clip rectangle does not match the bounds. 2461553Srgrimes * If true, and scrolling happens the 24715733Sjoerg * repaint manager is not cleared which then allows for the repaint 24815733Sjoerg * previously invoked to succeed. 24915733Sjoerg */ 25015733Sjoerg private transient boolean waitingForRepaint; 2511553Srgrimes 2521553Srgrimes /** 2531553Srgrimes * Instead of directly invoking repaint, a <code>Timer</code> 2541553Srgrimes * is started and when it fires, repaint is invoked. 25515733Sjoerg */ 25615733Sjoerg private transient Timer repaintTimer; 2571553Srgrimes 2581553Srgrimes /** 25915733Sjoerg * Set to true in paintView when paint is invoked. 26015733Sjoerg */ 26115733Sjoerg private transient boolean inBlitPaint; 26215733Sjoerg 26315733Sjoerg /** 26415733Sjoerg * Whether or not a valid view has been installed. 2651553Srgrimes */ 2661553Srgrimes private boolean hasHadValidView; 26715733Sjoerg 26815733Sjoerg /** 2691553Srgrimes * When view is changed we have to synchronize scrollbar values 27015733Sjoerg * with viewport (see the BasicScrollPaneUI#syncScrollPaneWithViewport method). 27115733Sjoerg * This flag allows to invoke that method while ScrollPaneLayout#layoutContainer 27215733Sjoerg * is running. 27315733Sjoerg */ 2741553Srgrimes private boolean viewChanged; 2751553Srgrimes 27631492Swollman /** Creates a <code>JViewport</code>. */ 27731492Swollman public JViewport() { 27831492Swollman super(); 27931492Swollman setLayout(createLayoutManager()); 28031583Sjdp setOpaque(true); 2811553Srgrimes updateUI(); 2821553Srgrimes setInheritsPopupMenu(true); 28331492Swollman } 28431492Swollman 28531492Swollman 28631492Swollman 2871553Srgrimes /** 28843507Swollman * Returns the L&F object that renders this component. 28943507Swollman * 29043507Swollman * @return a <code>ViewportUI</code> object 29143507Swollman * @since 1.3 29284695Sgad */ 29343507Swollman public ViewportUI getUI() { 29484695Sgad return (ViewportUI)ui; 29584695Sgad } 29631492Swollman 29731492Swollman 29884695Sgad /** 29931492Swollman * Sets the L&F object that renders this component. 3001553Srgrimes * 30143507Swollman * @param ui the <code>ViewportUI</code> L&F object 3021553Srgrimes * @see UIDefaults#getUI 3031553Srgrimes * @beaninfo 3041553Srgrimes * bound: true 30531492Swollman * hidden: true 30631492Swollman * attribute: visualUpdate true 30731492Swollman * description: The UI object that implements the Component's LookAndFeel. 3081553Srgrimes * @since 1.3 3091553Srgrimes */ 31084695Sgad public void setUI(ViewportUI ui) { 3111553Srgrimes super.setUI(ui); 3121553Srgrimes } 3131553Srgrimes 3141553Srgrimes 31531492Swollman /** 3161553Srgrimes * Resets the UI property to a value from the current look and feel. 3171553Srgrimes * 3181553Srgrimes * @see JComponent#updateUI 3191553Srgrimes */ 3201553Srgrimes public void updateUI() { 32131492Swollman setUI((ViewportUI)UIManager.getUI(this)); 32231492Swollman } 32331492Swollman 3241553Srgrimes 3251553Srgrimes /** 3261553Srgrimes * Returns a string that specifies the name of the L&F class 32731492Swollman * that renders this component. 3281553Srgrimes * 329241852Seadler * @return the string "ViewportUI" 33031492Swollman * 33131492Swollman * @see JComponent#getUIClassID 332241852Seadler * @see UIDefaults#getUI 33378300Sgad */ 33484695Sgad public String getUIClassID() { 33568149Sgad return uiClassID; 33656287Sjoe } 3371553Srgrimes 33823122Smpp 3391553Srgrimes /** 3401553Srgrimes * Sets the <code>JViewport</code>'s one lightweight child, 34131492Swollman * which can be <code>null</code>. 34231492Swollman * (Since there is only one child which occupies the entire viewport, 3431553Srgrimes * the <code>constraints</code> and <code>index</code> 3441553Srgrimes * arguments are ignored.) 34584695Sgad * 3461553Srgrimes * @param child the lightweight <code>child</code> of the viewport 34761913Swollman * @param constraints the <code>constraints</code> to be respected 34861913Swollman * @param index the index 3491553Srgrimes * @see #setView 3501553Srgrimes */ 3511553Srgrimes protected void addImpl(Component child, Object constraints, int index) { 35284695Sgad setView(child); 3531553Srgrimes } 3541553Srgrimes 3551553Srgrimes 3561553Srgrimes /** 3571553Srgrimes * Removes the <code>Viewport</code>s one lightweight child. 3581553Srgrimes * 35961913Swollman * @see #setView 36061913Swollman */ 36161913Swollman public void remove(Component child) { 36261913Swollman child.removeComponentListener(viewListener); 36361913Swollman super.remove(child); 36461913Swollman } 36553956Sache 36653956Sache /** 3671553Srgrimes * Scrolls the view so that <code>Rectangle</code> 36853956Sache * within the view becomes visible. 36953956Sache * <p> 37053956Sache * This attempts to validate the view before scrolling if the 37153956Sache * view is currently not valid - <code>isValid</code> returns false. 37253956Sache * To avoid excessive validation when the containment hierarchy is 37353956Sache * being created this will not validate if one of the ancestors does not 3741553Srgrimes * have a peer, or there is no validate root ancestor, or one of the 3751553Srgrimes * ancestors is not a <code>Window</code> or <code>Applet</code>. 3761553Srgrimes * <p> 37715733Sjoerg * Note that this method will not scroll outside of the 37831492Swollman * valid viewport; for example, if <code>contentRect</code> is larger 37915733Sjoerg * than the viewport, scrolling will be confined to the viewport's 38015733Sjoerg * bounds. 38115733Sjoerg * 38231492Swollman * @param contentRect the <code>Rectangle</code> to display 38315733Sjoerg * @see JComponent#isValidateRoot 38415733Sjoerg * @see java.awt.Component#isValid 38515733Sjoerg */ 38615733Sjoerg public void scrollRectToVisible(Rectangle contentRect) { 3871553Srgrimes Component view = getView(); 3881553Srgrimes 3891553Srgrimes if (view == null) { 390241015Smdf return; 391241015Smdf } else { 3921553Srgrimes if (!view.isValid()) { 3931553Srgrimes // If the view is not valid, validate. scrollRectToVisible 3941553Srgrimes // may fail if the view is not valid first, contentRect 3951553Srgrimes // could be bigger than invalid size. 3961553Srgrimes validateView(); 3971553Srgrimes } 3981553Srgrimes int dx, dy; 3991553Srgrimes 4001553Srgrimes dx = positionAdjustment(getWidth(), contentRect.width, contentRect.x); 4011553Srgrimes dy = positionAdjustment(getHeight(), contentRect.height, contentRect.y); 4021553Srgrimes 4031553Srgrimes if (dx != 0 || dy != 0) { 4041553Srgrimes Point viewPosition = getViewPosition(); 4051553Srgrimes Dimension viewSize = view.getSize(); 40678280Sgad int startX = viewPosition.x; 40778280Sgad int startY = viewPosition.y; 40868275Sgad Dimension extent = getExtentSize(); 40968275Sgad 41068275Sgad viewPosition.x -= dx; 41168275Sgad viewPosition.y -= dy; 41268275Sgad // Only constrain the location if the view is valid. If the 41368275Sgad // the view isn't valid, it typically indicates the view 41468275Sgad // isn't visible yet and most likely has a bogus size as will 41568275Sgad // we, and therefore we shouldn't constrain the scrolling 41668275Sgad if (view.isValid()) { 41768275Sgad if (getParent().getComponentOrientation().isLeftToRight()) { 418241852Seadler if (viewPosition.x + extent.width > viewSize.width) { 41968275Sgad viewPosition.x = Math.max(0, viewSize.width - extent.width); 42068275Sgad } else if (viewPosition.x < 0) { 42168275Sgad viewPosition.x = 0; 42268275Sgad } 42368275Sgad } else { 42468275Sgad if (extent.width > viewSize.width) { 42568275Sgad viewPosition.x = viewSize.width - extent.width; 42668275Sgad } else { 42768275Sgad viewPosition.x = Math.max(0, Math.min(viewSize.width - extent.width, viewPosition.x)); 42868275Sgad } 42968275Sgad } 43068275Sgad if (viewPosition.y + extent.height > viewSize.height) { 43168275Sgad viewPosition.y = Math.max(0, viewSize.height - 43268275Sgad extent.height); 43368275Sgad } 43468275Sgad else if (viewPosition.y < 0) { 43568275Sgad viewPosition.y = 0; 43668275Sgad } 43768275Sgad } 43868275Sgad if (viewPosition.x != startX || viewPosition.y != startY) { 43968275Sgad setViewPosition(viewPosition); 44068275Sgad // NOTE: How JViewport currently works with the 44168275Sgad // backing store is not foolproof. The sequence of 44268275Sgad // events when setViewPosition 44368275Sgad // (scrollRectToVisible) is called is to reset the 44468275Sgad // views bounds, which causes a repaint on the 44568275Sgad // visible region and sets an ivar indicating 44668275Sgad // scrolling (scrollUnderway). When 44768275Sgad // JViewport.paint is invoked if scrollUnderway is 44868275Sgad // true, the backing store is blitted. This fails 44968275Sgad // if between the time setViewPosition is invoked 45068275Sgad // and paint is received another repaint is queued 45168275Sgad // indicating part of the view is invalid. There 45268275Sgad // is no way for JViewport to notice another 45368275Sgad // repaint has occurred and it ends up blitting 45468275Sgad // what is now a dirty region and the repaint is 45568275Sgad // never delivered. 456241852Seadler // It just so happens JTable encounters this 45768275Sgad // behavior by way of scrollRectToVisible, for 45868275Sgad // this reason scrollUnderway is set to false 45968275Sgad // here, which effectively disables the backing 460241852Seadler // store. 46168275Sgad scrollUnderway = false; 46268275Sgad } 46368275Sgad } 46468275Sgad } 46568275Sgad } 46668275Sgad 46768275Sgad /** 46868275Sgad * Ascends the <code>Viewport</code>'s parents stopping when 46968275Sgad * a component is found that returns 470241852Seadler * <code>true</code> to <code>isValidateRoot</code>. 47168275Sgad * If all the <code>Component</code>'s parents are visible, 47268275Sgad * <code>validate</code> will then be invoked on it. The 47368275Sgad * <code>RepaintManager</code> is then invoked with 47468275Sgad * <code>removeInvalidComponent</code>. This 47568275Sgad * is the synchronous version of a <code>revalidate</code>. 47668275Sgad */ 47768275Sgad private void validateView() { 47868275Sgad Component validateRoot = SwingUtilities.getValidateRoot(this, false); 47968275Sgad 48068275Sgad if (validateRoot == null) { 48168275Sgad return; 482241852Seadler } 48368275Sgad 48468275Sgad // Validate the root. 4851553Srgrimes validateRoot.validate(); 48678280Sgad 48719202Simp // And let the RepaintManager it does not have to validate from 48831492Swollman // validateRoot anymore. 48919202Simp RepaintManager rm = RepaintManager.currentManager(this); 49019202Simp 49178280Sgad if (rm != null) { 4921553Srgrimes rm.removeInvalidComponent((JComponent)validateRoot); 4931553Srgrimes } 4941553Srgrimes } 4951553Srgrimes 4961553Srgrimes /* Used by the scrollRectToVisible method to determine the 4971553Srgrimes * proper direction and amount to move by. The integer variables are named 4981553Srgrimes * width, but this method is applicable to height also. The code assumes that 4991553Srgrimes * parentWidth/childWidth are positive and childAt can be negative. 5001553Srgrimes */ 501241852Seadler private int positionAdjustment(int parentWidth, int childWidth, int childAt) { 5021553Srgrimes 50384697Sgad // +-----+ 5041553Srgrimes // | --- | No Change 50584697Sgad // +-----+ 5061553Srgrimes if (childAt >= 0 && childWidth + childAt <= parentWidth) { 50784697Sgad return 0; 50878280Sgad } 50978280Sgad 5101553Srgrimes // +-----+ 5111553Srgrimes // --------- No Change 5121553Srgrimes // +-----+ 5131553Srgrimes if (childAt <= 0 && childWidth + childAt >= parentWidth) { 5141553Srgrimes return 0; 5151553Srgrimes } 51678280Sgad 5171553Srgrimes // +-----+ +-----+ 5181553Srgrimes // | ---- -> | ----| 5191553Srgrimes // +-----+ +-----+ 5201553Srgrimes if (childAt > 0 && childWidth <= parentWidth) { 521241852Seadler return -childAt + parentWidth - childWidth; 5221553Srgrimes } 5231553Srgrimes 52431492Swollman // +-----+ +-----+ 5251553Srgrimes // | -------- -> |-------- 5261553Srgrimes // +-----+ +-----+ 5271553Srgrimes if (childAt >= 0 && childWidth >= parentWidth) { 5281553Srgrimes return -childAt; 52927618Simp } 5301553Srgrimes 5311553Srgrimes // +-----+ +-----+ 5321553Srgrimes // ---- | -> |---- | 5331553Srgrimes // +-----+ +-----+ 5341553Srgrimes if (childAt <= 0 && childWidth <= parentWidth) { 5351553Srgrimes return -childAt; 5361553Srgrimes } 53778146Sgad 5381553Srgrimes // +-----+ +-----+ 5391553Srgrimes //-------- | -> --------| 5401553Srgrimes // +-----+ +-----+ 5411553Srgrimes if (childAt < 0 && childWidth >= parentWidth) { 5421553Srgrimes return -childAt + parentWidth - childWidth; 5431553Srgrimes } 5441553Srgrimes 5451553Srgrimes return 0; 5461553Srgrimes } 5471553Srgrimes 5481553Srgrimes 5491553Srgrimes /** 5501553Srgrimes * The viewport "scrolls" its child (called the "view") by the 5511553Srgrimes * normal parent/child clipping (typically the view is moved in 55278280Sgad * the opposite direction of the scroll). A non-<code>null</code> border, 5531553Srgrimes * or non-zero insets, isn't supported, to prevent the geometry 5541553Srgrimes * of this component from becoming complex enough to inhibit 5551553Srgrimes * subclassing. To create a <code>JViewport</code> with a border, 5561553Srgrimes * add it to a <code>JPanel</code> that has a border. 5571553Srgrimes * <p>Note: If <code>border</code> is non-<code>null</code>, this 5581553Srgrimes * method will throw an exception as borders are not supported on 55931492Swollman * a <code>JViewPort</code>. 56031492Swollman * 56178280Sgad * @param border the <code>Border</code> to set 5621553Srgrimes * @exception IllegalArgumentException this method is not implemented 5631553Srgrimes */ 5641553Srgrimes public final void setBorder(Border border) { 5651553Srgrimes if (border != null) { 5661553Srgrimes throw new IllegalArgumentException("JViewport.setBorder() not supported"); 5678857Srgrimes } 56878280Sgad } 56978280Sgad 5701553Srgrimes 5711553Srgrimes /** 5721553Srgrimes * Returns the insets (border) dimensions as (0,0,0,0), since borders 5731553Srgrimes * are not supported on a <code>JViewport</code>. 5741553Srgrimes * 5751553Srgrimes * @return a <code>Rectangle</code> of zero dimension and zero origin 5761553Srgrimes * @see #setBorder 5771553Srgrimes */ 57878146Sgad public final Insets getInsets() { 57978146Sgad return new Insets(0, 0, 0, 0); 5801553Srgrimes } 5811553Srgrimes 58227282Sdima /** 58327618Simp * Returns an <code>Insets</code> object containing this 5841553Srgrimes * <code>JViewport</code>s inset values. The passed-in 5851553Srgrimes * <code>Insets</code> object will be reinitialized, and 58627282Sdima * all existing values within this object are overwritten. 5871553Srgrimes * 5881553Srgrimes * @param insets the <code>Insets</code> object which can be reused 5891553Srgrimes * @return this viewports inset values 5901553Srgrimes * @see #getInsets 5911553Srgrimes * @beaninfo 5921553Srgrimes * expert: true 5931553Srgrimes */ 5941553Srgrimes public final Insets getInsets(Insets insets) { 59527635Simp insets.left = insets.top = insets.right = insets.bottom = 0; 5961553Srgrimes return insets; 5971553Srgrimes } 5981553Srgrimes 5991553Srgrimes 6001553Srgrimes private Graphics getBackingStoreGraphics(Graphics g) { 6011553Srgrimes Graphics bsg = backingStoreImage.getGraphics(); 6021553Srgrimes bsg.setColor(g.getColor()); 60327282Sdima bsg.setFont(g.getFont()); 60427282Sdima bsg.setClip(g.getClipBounds()); 6051553Srgrimes return bsg; 6061553Srgrimes } 607241852Seadler 60827618Simp 609241852Seadler private void paintViaBackingStore(Graphics g) { 61027618Simp Graphics bsg = getBackingStoreGraphics(g); 6111553Srgrimes try { 6121553Srgrimes super.paint(bsg); 6131553Srgrimes g.drawImage(backingStoreImage, 0, 0, this); 6141553Srgrimes } finally { 6151553Srgrimes bsg.dispose(); 6161553Srgrimes } 61778146Sgad } 6181553Srgrimes 6191553Srgrimes private void paintViaBackingStore(Graphics g, Rectangle oClip) { 6201553Srgrimes Graphics bsg = getBackingStoreGraphics(g); 62184696Sgad try { 6221553Srgrimes super.paint(bsg); 6231553Srgrimes g.setClip(oClip); 62419187Simp g.drawImage(backingStoreImage, 0, 0, this); 6251553Srgrimes } finally { 6261553Srgrimes bsg.dispose(); 6271553Srgrimes } 6281553Srgrimes } 6291553Srgrimes 6301553Srgrimes /** 6311553Srgrimes * The <code>JViewport</code> overrides the default implementation of 6321553Srgrimes * this method (in <code>JComponent</code>) to return false. 6331553Srgrimes * This ensures 6341553Srgrimes * that the drawing machinery will call the <code>Viewport</code>'s 6351553Srgrimes * <code>paint</code> 63678146Sgad * implementation rather than messaging the <code>JViewport</code>'s 6371553Srgrimes * children directly. 6381553Srgrimes * 6391553Srgrimes * @return false 6401553Srgrimes */ 641241852Seadler public boolean isOptimizedDrawingEnabled() { 6421553Srgrimes return false; 6431553Srgrimes } 6441553Srgrimes 64578280Sgad /** 6461553Srgrimes * Returns true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE} to cause 6471553Srgrimes * painting to originate from {@code JViewport}, or one of its 6481553Srgrimes * ancestors. Otherwise returns {@code false}. 64978280Sgad * 65027618Simp * @return true if scroll mode is a {@code BACKINGSTORE_SCROLL_MODE}. 6511553Srgrimes * @see JComponent#isPaintingOrigin() 652241852Seadler */ 6531553Srgrimes protected boolean isPaintingOrigin() { 6541553Srgrimes return scrollMode == BACKINGSTORE_SCROLL_MODE; 6551553Srgrimes } 6561553Srgrimes 6571553Srgrimes 6581553Srgrimes /** 6591553Srgrimes * Only used by the paint method below. 6601553Srgrimes */ 6611553Srgrimes private Point getViewLocation() { 6621553Srgrimes Component view = getView(); 6631553Srgrimes if (view != null) { 6641553Srgrimes return view.getLocation(); 6651553Srgrimes } 6661553Srgrimes else { 6671553Srgrimes return new Point(0,0); 66878146Sgad } 6691553Srgrimes } 67039084Swollman 6711553Srgrimes /** 6721553Srgrimes * Depending on whether the <code>backingStore</code> is enabled, 6731553Srgrimes * either paint the image through the backing store or paint 6741553Srgrimes * just the recently exposed part, using the backing store 6751553Srgrimes * to "blit" the remainder. 6761553Srgrimes * <blockquote> 677241852Seadler * The term "blit" is the pronounced version of the PDP-10 6781553Srgrimes * BLT (BLock Transfer) instruction, which copied a block of 6791553Srgrimes * bits. (In case you were curious.) 6801553Srgrimes * </blockquote> 6811553Srgrimes * 6821553Srgrimes * @param g the <code>Graphics</code> context within which to paint 6831553Srgrimes */ 6841553Srgrimes public void paint(Graphics g) 6851553Srgrimes { 6861553Srgrimes int width = getWidth(); 6871553Srgrimes int height = getHeight(); 6881553Srgrimes 6891553Srgrimes if ((width <= 0) || (height <= 0)) { 6901553Srgrimes return; 6911553Srgrimes } 6921553Srgrimes 6931553Srgrimes if (inBlitPaint) { 6941553Srgrimes // We invoked paint as part of copyArea cleanup, let it through. 6951553Srgrimes super.paint(g); 6961553Srgrimes return; 6971553Srgrimes } 6981553Srgrimes 6991553Srgrimes if (repaintAll) { 7001553Srgrimes repaintAll = false; 7011553Srgrimes Rectangle clipB = g.getClipBounds(); 70278146Sgad if (clipB.width < getWidth() || 7031553Srgrimes clipB.height < getHeight()) { 7041553Srgrimes waitingForRepaint = true; 70579743Sgad if (repaintTimer == null) { 70679743Sgad repaintTimer = createRepaintTimer(); 70779743Sgad } 7081553Srgrimes repaintTimer.stop(); 7091553Srgrimes repaintTimer.start(); 71078280Sgad // We really don't need to paint, a future repaint will 7111553Srgrimes // take care of it, but if we don't we get an ugly flicker. 7121553Srgrimes } 7131553Srgrimes else { 71478280Sgad if (repaintTimer != null) { 7151553Srgrimes repaintTimer.stop(); 7161553Srgrimes } 7171553Srgrimes waitingForRepaint = false; 71878280Sgad } 7191553Srgrimes } 7201553Srgrimes else if (waitingForRepaint) { 7211553Srgrimes // Need a complete repaint before resetting waitingForRepaint 72278280Sgad Rectangle clipB = g.getClipBounds(); 7231553Srgrimes if (clipB.width >= getWidth() && 7241553Srgrimes clipB.height >= getHeight()) { 7251553Srgrimes waitingForRepaint = false; 72678280Sgad repaintTimer.stop(); 7271553Srgrimes } 7281553Srgrimes } 72939084Swollman 73039084Swollman if (!backingStore || isBlitting() || getView() == null) { 73139084Swollman super.paint(g); 7321553Srgrimes lastPaintPosition = getViewLocation(); 7331553Srgrimes return; 73478280Sgad } 73527618Simp 73627618Simp // If the view is smaller than the viewport and we are not opaque 7371553Srgrimes // (that is, we won't paint our background), we should set the 7381553Srgrimes // clip. Otherwise, as the bounds of the view vary, we will 73979743Sgad // blit garbage into the exposed areas. 74079743Sgad Rectangle viewBounds = getView().getBounds(); 74179743Sgad if (!isOpaque()) { 74279743Sgad g.clipRect(0, 0, viewBounds.width, viewBounds.height); 74379743Sgad } 74427635Simp 7459568Storstenb if (backingStoreImage == null) { 7461553Srgrimes // Backing store is enabled but this is the first call to paint. 7471553Srgrimes // Create the backing store, paint it and then copy to g. 7481553Srgrimes // The backing store image will be created with the size of 7499568Storstenb // the viewport. We must make sure the clip region is the 7501553Srgrimes // same size, otherwise when scrolling the backing image 75179743Sgad // the region outside of the clipped region will not be painted, 75279743Sgad // and result in empty areas. 75379743Sgad backingStoreImage = createImage(width, height); 75479743Sgad Rectangle clip = g.getClipBounds(); 75579743Sgad if (clip.width != width || clip.height != height) { 75679743Sgad if (!isOpaque()) { 7571553Srgrimes g.setClip(0, 0, Math.min(viewBounds.width, width), 7581553Srgrimes Math.min(viewBounds.height, height)); 7591553Srgrimes } 7601553Srgrimes else { 76178280Sgad g.setClip(0, 0, width, height); 7621553Srgrimes } 7631553Srgrimes paintViaBackingStore(g, clip); 7641553Srgrimes } 7651553Srgrimes else { 7661553Srgrimes paintViaBackingStore(g); 7671553Srgrimes } 7681553Srgrimes } 7691553Srgrimes else { 7701553Srgrimes if (!scrollUnderway || lastPaintPosition.equals(getViewLocation())) { 7719568Storstenb // No scrolling happened: repaint required area via backing store. 77278146Sgad paintViaBackingStore(g); 7739568Storstenb } else { 7749568Storstenb // The image was scrolled. Manipulate the backing store and flush it to g. 7759568Storstenb Point blitFrom = new Point(); 7769568Storstenb Point blitTo = new Point(); 7779568Storstenb Dimension blitSize = new Dimension(); 7789568Storstenb Rectangle blitPaint = new Rectangle(); 7799568Storstenb 7809568Storstenb Point newLocation = getViewLocation(); 7819568Storstenb int dx = newLocation.x - lastPaintPosition.x; 7829568Storstenb int dy = newLocation.y - lastPaintPosition.y; 7839568Storstenb boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint); 7849568Storstenb if (!canBlit) { 7859568Storstenb // The image was either moved diagonally or 7869568Storstenb // moved by more than the image size: paint normally. 7871553Srgrimes paintViaBackingStore(g); 7881553Srgrimes } else { 7891553Srgrimes int bdx = blitTo.x - blitFrom.x; 7901553Srgrimes int bdy = blitTo.y - blitFrom.y; 79178146Sgad 7921553Srgrimes // Move the relevant part of the backing store. 7931553Srgrimes Rectangle clip = g.getClipBounds(); 7941553Srgrimes // We don't want to inherit the clip region when copying 7951553Srgrimes // bits, if it is inherited it will result in not moving 7961553Srgrimes // all of the image resulting in garbage appearing on 7971553Srgrimes // the screen. 7981553Srgrimes g.setClip(0, 0, width, height); 7991553Srgrimes Graphics bsg = getBackingStoreGraphics(g); 8001553Srgrimes try { 8011553Srgrimes bsg.copyArea(blitFrom.x, blitFrom.y, blitSize.width, blitSize.height, bdx, bdy); 8021553Srgrimes 8031553Srgrimes g.setClip(clip.x, clip.y, clip.width, clip.height); 8041553Srgrimes // Paint the rest of the view; the part that has just been exposed. 8051553Srgrimes Rectangle r = viewBounds.intersection(blitPaint); 8061553Srgrimes bsg.setClip(r); 80778146Sgad super.paint(bsg); 8081553Srgrimes 8091553Srgrimes // Copy whole of the backing store to g. 8101553Srgrimes g.drawImage(backingStoreImage, 0, 0, this); 81131492Swollman } finally { 81278146Sgad bsg.dispose(); 81331492Swollman } 81431492Swollman } 81531492Swollman } 81678146Sgad } 81731492Swollman lastPaintPosition = getViewLocation(); 81878146Sgad scrollUnderway = false; 81931492Swollman } 82078146Sgad 82131492Swollman 8221553Srgrimes /** 8231553Srgrimes * Sets the bounds of this viewport. If the viewport's width 8241553Srgrimes * or height has changed, fire a <code>StateChanged</code> event. 82515733Sjoerg * 82615733Sjoerg * @param x left edge of the origin 82715733Sjoerg * @param y top edge of the origin 82878146Sgad * @param w width in pixels 82915733Sjoerg * @param h height in pixels 83061913Swollman * 83161913Swollman * @see JComponent#reshape(int, int, int, int) 83261913Swollman */ 83361913Swollman @SuppressWarnings("deprecation") 83415733Sjoerg public void reshape(int x, int y, int w, int h) { 83515733Sjoerg boolean sizeChanged = (getWidth() != w) || (getHeight() != h); 83615733Sjoerg if (sizeChanged) { 83715733Sjoerg backingStoreImage = null; 83815733Sjoerg } 8391553Srgrimes super.reshape(x, y, w, h); 8401553Srgrimes if (sizeChanged || viewChanged) { 8411553Srgrimes viewChanged = false; 84278146Sgad 8431553Srgrimes fireStateChanged(); 8441553Srgrimes } 8451553Srgrimes } 8461553Srgrimes 8471553Srgrimes 84831492Swollman /** 849241852Seadler * Used to control the method of scrolling the viewport contents. 850236289Seadler * You may want to change this mode to get maximum performance for your 85178280Sgad * use case. 8521553Srgrimes * 8531553Srgrimes * @param mode one of the following values: 8541553Srgrimes * <ul> 85578280Sgad * <li> JViewport.BLIT_SCROLL_MODE 8561553Srgrimes * <li> JViewport.BACKINGSTORE_SCROLL_MODE 8571553Srgrimes * <li> JViewport.SIMPLE_SCROLL_MODE 858241852Seadler * </ul> 8591553Srgrimes * 8601553Srgrimes * @see #BLIT_SCROLL_MODE 8611553Srgrimes * @see #BACKINGSTORE_SCROLL_MODE 8621553Srgrimes * @see #SIMPLE_SCROLL_MODE 8631553Srgrimes * 8641553Srgrimes * @beaninfo 8651553Srgrimes * bound: false 8661553Srgrimes * description: Method of moving contents for incremental scrolls. 86778300Sgad * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE 86831492Swollman * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE 86931492Swollman * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE 87031492Swollman * 87131492Swollman * @since 1.3 8721553Srgrimes */ 8731553Srgrimes public void setScrollMode(int mode) { 87419202Simp scrollMode = mode; 8751553Srgrimes backingStore = mode == BACKINGSTORE_SCROLL_MODE; 8761553Srgrimes } 8771553Srgrimes 8781553Srgrimes /** 8791553Srgrimes * Returns the current scrolling mode. 8801553Srgrimes * 8811553Srgrimes * @return the <code>scrollMode</code> property 8821553Srgrimes * @see #setScrollMode 88378146Sgad * @since 1.3 8841553Srgrimes */ 8851553Srgrimes public int getScrollMode() { 8861553Srgrimes return scrollMode; 8871553Srgrimes } 88831492Swollman 88978300Sgad /** 89078300Sgad * Returns <code>true</code> if this viewport is maintaining 8911553Srgrimes * an offscreen image of its contents. 8921553Srgrimes * 893 * @return <code>true</code> if <code>scrollMode</code> is 894 * <code>BACKINGSTORE_SCROLL_MODE</code> 895 * 896 * @deprecated As of Java 2 platform v1.3, replaced by 897 * <code>getScrollMode()</code>. 898 */ 899 @Deprecated 900 public boolean isBackingStoreEnabled() { 901 return scrollMode == BACKINGSTORE_SCROLL_MODE; 902 } 903 904 905 /** 906 * If true if this viewport will maintain an offscreen 907 * image of its contents. The image is used to reduce the cost 908 * of small one dimensional changes to the <code>viewPosition</code>. 909 * Rather than repainting the entire viewport we use 910 * <code>Graphics.copyArea</code> to effect some of the scroll. 911 * 912 * @param enabled if true, maintain an offscreen backing store 913 * 914 * @deprecated As of Java 2 platform v1.3, replaced by 915 * <code>setScrollMode()</code>. 916 */ 917 @Deprecated 918 public void setBackingStoreEnabled(boolean enabled) { 919 if (enabled) { 920 setScrollMode(BACKINGSTORE_SCROLL_MODE); 921 } else { 922 setScrollMode(BLIT_SCROLL_MODE); 923 } 924 } 925 926 private boolean isBlitting() { 927 Component view = getView(); 928 return (scrollMode == BLIT_SCROLL_MODE) && 929 (view instanceof JComponent) && view.isOpaque(); 930 } 931 932 933 /** 934 * Returns the <code>JViewport</code>'s one child or <code>null</code>. 935 * 936 * @return the viewports child, or <code>null</code> if none exists 937 * 938 * @see #setView 939 */ 940 public Component getView() { 941 return (getComponentCount() > 0) ? getComponent(0) : null; 942 } 943 944 /** 945 * Sets the <code>JViewport</code>'s one lightweight child 946 * (<code>view</code>), which can be <code>null</code>. 947 * 948 * @param view the viewport's new lightweight child 949 * 950 * @see #getView 951 */ 952 public void setView(Component view) { 953 954 /* Remove the viewport's existing children, if any. 955 * Note that removeAll() isn't used here because it 956 * doesn't call remove() (which JViewport overrides). 957 */ 958 int n = getComponentCount(); 959 for(int i = n - 1; i >= 0; i--) { 960 remove(getComponent(i)); 961 } 962 963 isViewSizeSet = false; 964 965 if (view != null) { 966 super.addImpl(view, null, -1); 967 viewListener = createViewListener(); 968 view.addComponentListener(viewListener); 969 } 970 971 if (hasHadValidView) { 972 // Only fire a change if a view has been installed. 973 fireStateChanged(); 974 } 975 else if (view != null) { 976 hasHadValidView = true; 977 } 978 979 viewChanged = true; 980 981 revalidate(); 982 repaint(); 983 } 984 985 986 /** 987 * If the view's size hasn't been explicitly set, return the 988 * preferred size, otherwise return the view's current size. 989 * If there is no view, return 0,0. 990 * 991 * @return a <code>Dimension</code> object specifying the size of the view 992 */ 993 public Dimension getViewSize() { 994 Component view = getView(); 995 996 if (view == null) { 997 return new Dimension(0,0); 998 } 999 else if (isViewSizeSet) { 1000 return view.getSize(); 1001 } 1002 else { 1003 return view.getPreferredSize(); 1004 } 1005 } 1006 1007 1008 /** 1009 * Sets the size of the view. A state changed event will be fired. 1010 * 1011 * @param newSize a <code>Dimension</code> object specifying the new 1012 * size of the view 1013 */ 1014 public void setViewSize(Dimension newSize) { 1015 Component view = getView(); 1016 if (view != null) { 1017 Dimension oldSize = view.getSize(); 1018 if (!newSize.equals(oldSize)) { 1019 // scrollUnderway will be true if this is invoked as the 1020 // result of a validate and setViewPosition was previously 1021 // invoked. 1022 scrollUnderway = false; 1023 view.setSize(newSize); 1024 isViewSizeSet = true; 1025 fireStateChanged(); 1026 } 1027 } 1028 } 1029 1030 /** 1031 * Returns the view coordinates that appear in the upper left 1032 * hand corner of the viewport, or 0,0 if there's no view. 1033 * 1034 * @return a <code>Point</code> object giving the upper left coordinates 1035 */ 1036 public Point getViewPosition() { 1037 Component view = getView(); 1038 if (view != null) { 1039 Point p = view.getLocation(); 1040 p.x = -p.x; 1041 p.y = -p.y; 1042 return p; 1043 } 1044 else { 1045 return new Point(0,0); 1046 } 1047 } 1048 1049 1050 /** 1051 * Sets the view coordinates that appear in the upper left 1052 * hand corner of the viewport, does nothing if there's no view. 1053 * 1054 * @param p a <code>Point</code> object giving the upper left coordinates 1055 */ 1056 public void setViewPosition(Point p) 1057 { 1058 Component view = getView(); 1059 if (view == null) { 1060 return; 1061 } 1062 1063 int oldX, oldY, x = p.x, y = p.y; 1064 1065 /* Collect the old x,y values for the views location 1066 * and do the song and dance to avoid allocating 1067 * a Rectangle object if we don't have to. 1068 */ 1069 if (view instanceof JComponent) { 1070 JComponent c = (JComponent)view; 1071 oldX = c.getX(); 1072 oldY = c.getY(); 1073 } 1074 else { 1075 Rectangle r = view.getBounds(); 1076 oldX = r.x; 1077 oldY = r.y; 1078 } 1079 1080 /* The view scrolls in the opposite direction to mouse 1081 * movement. 1082 */ 1083 int newX = -x; 1084 int newY = -y; 1085 1086 if ((oldX != newX) || (oldY != newY)) { 1087 if (!waitingForRepaint && isBlitting() && canUseWindowBlitter()) { 1088 RepaintManager rm = RepaintManager.currentManager(this); 1089 // The cast to JComponent will work, if view is not 1090 // a JComponent, isBlitting will return false. 1091 JComponent jview = (JComponent)view; 1092 Rectangle dirty = rm.getDirtyRegion(jview); 1093 if (dirty == null || !dirty.contains(jview.getVisibleRect())) { 1094 rm.beginPaint(); 1095 try { 1096 Graphics g = JComponent.safelyGetGraphics(this); 1097 flushViewDirtyRegion(g, dirty); 1098 view.setLocation(newX, newY); 1099 Rectangle r = new Rectangle( 1100 0, 0, getWidth(), Math.min(getHeight(), jview.getHeight())); 1101 g.setClip(r); 1102 // Repaint the complete component if the blit succeeded 1103 // and needsRepaintAfterBlit returns true. 1104 repaintAll = (windowBlitPaint(g) && 1105 needsRepaintAfterBlit()); 1106 g.dispose(); 1107 rm.notifyRepaintPerformed(this, r.x, r.y, r.width, r.height); 1108 rm.markCompletelyClean((JComponent)getParent()); 1109 rm.markCompletelyClean(this); 1110 rm.markCompletelyClean(jview); 1111 } finally { 1112 rm.endPaint(); 1113 } 1114 } 1115 else { 1116 // The visible region is dirty, no point in doing copyArea 1117 view.setLocation(newX, newY); 1118 repaintAll = false; 1119 } 1120 } 1121 else { 1122 scrollUnderway = true; 1123 // This calls setBounds(), and then repaint(). 1124 view.setLocation(newX, newY); 1125 repaintAll = false; 1126 } 1127 // we must validate the hierarchy to not break the hw/lw mixing 1128 revalidate(); 1129 fireStateChanged(); 1130 } 1131 } 1132 1133 1134 /** 1135 * Returns a rectangle whose origin is <code>getViewPosition</code> 1136 * and size is <code>getExtentSize</code>. 1137 * This is the visible part of the view, in view coordinates. 1138 * 1139 * @return a <code>Rectangle</code> giving the visible part of 1140 * the view using view coordinates. 1141 */ 1142 public Rectangle getViewRect() { 1143 return new Rectangle(getViewPosition(), getExtentSize()); 1144 } 1145 1146 1147 /** 1148 * Computes the parameters for a blit where the backing store image 1149 * currently contains <code>oldLoc</code> in the upper left hand corner 1150 * and we're scrolling to <code>newLoc</code>. 1151 * The parameters are modified 1152 * to return the values required for the blit. 1153 * 1154 * @param dx the horizontal delta 1155 * @param dy the vertical delta 1156 * @param blitFrom the <code>Point</code> we're blitting from 1157 * @param blitTo the <code>Point</code> we're blitting to 1158 * @param blitSize the <code>Dimension</code> of the area to blit 1159 * @param blitPaint the area to blit 1160 * @return true if the parameters are modified and we're ready to blit; 1161 * false otherwise 1162 */ 1163 protected boolean computeBlit( 1164 int dx, 1165 int dy, 1166 Point blitFrom, 1167 Point blitTo, 1168 Dimension blitSize, 1169 Rectangle blitPaint) 1170 { 1171 int dxAbs = Math.abs(dx); 1172 int dyAbs = Math.abs(dy); 1173 Dimension extentSize = getExtentSize(); 1174 1175 if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) { 1176 if (dy < 0) { 1177 blitFrom.y = -dy; 1178 blitTo.y = 0; 1179 blitPaint.y = extentSize.height + dy; 1180 } 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 } 1204 else { 1205 blitFrom.x = 0; 1206 blitTo.x = dx; 1207 blitPaint.x = 0; 1208 } 1209 1210 blitPaint.y = blitFrom.y = blitTo.y = 0; 1211 1212 blitSize.width = extentSize.width - dxAbs; 1213 blitSize.height = extentSize.height; 1214 1215 blitPaint.width = dxAbs; 1216 blitPaint.height = extentSize.height; 1217 1218 return true; 1219 } 1220 1221 else { 1222 return false; 1223 } 1224 } 1225 1226 1227 /** 1228 * Returns the size of the visible part of the view in view coordinates. 1229 * 1230 * @return a <code>Dimension</code> object giving the size of the view 1231 */ 1232 @Transient 1233 public Dimension getExtentSize() { 1234 return getSize(); 1235 } 1236 1237 1238 /** 1239 * Converts a size in pixel coordinates to view coordinates. 1240 * Subclasses of viewport that support "logical coordinates" 1241 * will override this method. 1242 * 1243 * @param size a <code>Dimension</code> object using pixel coordinates 1244 * @return a <code>Dimension</code> object converted to view coordinates 1245 */ 1246 public Dimension toViewCoordinates(Dimension size) { 1247 return new Dimension(size); 1248 } 1249 1250 /** 1251 * Converts a point in pixel coordinates to view coordinates. 1252 * Subclasses of viewport that support "logical coordinates" 1253 * will override this method. 1254 * 1255 * @param p a <code>Point</code> object using pixel coordinates 1256 * @return a <code>Point</code> object converted to view coordinates 1257 */ 1258 public Point toViewCoordinates(Point p) { 1259 return new Point(p); 1260 } 1261 1262 1263 /** 1264 * Sets the size of the visible part of the view using view coordinates. 1265 * 1266 * @param newExtent a <code>Dimension</code> object specifying 1267 * the size of the view 1268 */ 1269 public void setExtentSize(Dimension newExtent) { 1270 Dimension oldExtent = getExtentSize(); 1271 if (!newExtent.equals(oldExtent)) { 1272 setSize(newExtent); 1273 fireStateChanged(); 1274 } 1275 } 1276 1277 /** 1278 * A listener for the view. 1279 * <p> 1280 * <strong>Warning:</strong> 1281 * Serialized objects of this class will not be compatible with 1282 * future Swing releases. The current serialization support is 1283 * appropriate for short term storage or RMI between applications running 1284 * the same version of Swing. As of 1.4, support for long term storage 1285 * of all JavaBeans™ 1286 * has been added to the <code>java.beans</code> package. 1287 * Please see {@link java.beans.XMLEncoder}. 1288 */ 1289 @SuppressWarnings("serial") // Same-version serialization only 1290 protected class ViewListener extends ComponentAdapter implements Serializable 1291 { 1292 public void componentResized(ComponentEvent e) { 1293 fireStateChanged(); 1294 revalidate(); 1295 } 1296 } 1297 1298 /** 1299 * Creates a listener for the view. 1300 * @return a <code>ViewListener</code> 1301 */ 1302 protected ViewListener createViewListener() { 1303 return new ViewListener(); 1304 } 1305 1306 1307 /** 1308 * Subclassers can override this to install a different 1309 * layout manager (or <code>null</code>) in the constructor. Returns 1310 * the <code>LayoutManager</code> to install on the <code>JViewport</code>. 1311 * 1312 * @return a <code>LayoutManager</code> 1313 */ 1314 protected LayoutManager createLayoutManager() { 1315 return ViewportLayout.SHARED_INSTANCE; 1316 } 1317 1318 1319 /** 1320 * Adds a <code>ChangeListener</code> to the list that is 1321 * notified each time the view's 1322 * size, position, or the viewport's extent size has changed. 1323 * 1324 * @param l the <code>ChangeListener</code> to add 1325 * @see #removeChangeListener 1326 * @see #setViewPosition 1327 * @see #setViewSize 1328 * @see #setExtentSize 1329 */ 1330 public void addChangeListener(ChangeListener l) { 1331 listenerList.add(ChangeListener.class, l); 1332 } 1333 1334 /** 1335 * Removes a <code>ChangeListener</code> from the list that's notified each 1336 * time the views size, position, or the viewports extent size 1337 * has changed. 1338 * 1339 * @param l the <code>ChangeListener</code> to remove 1340 * @see #addChangeListener 1341 */ 1342 public void removeChangeListener(ChangeListener l) { 1343 listenerList.remove(ChangeListener.class, l); 1344 } 1345 1346 /** 1347 * Returns an array of all the <code>ChangeListener</code>s added 1348 * to this JViewport with addChangeListener(). 1349 * 1350 * @return all of the <code>ChangeListener</code>s added or an empty 1351 * array if no listeners have been added 1352 * @since 1.4 1353 */ 1354 public ChangeListener[] getChangeListeners() { 1355 return listenerList.getListeners(ChangeListener.class); 1356 } 1357 1358 /** 1359 * Notifies all <code>ChangeListeners</code> when the views 1360 * size, position, or the viewports extent size has changed. 1361 * 1362 * @see #addChangeListener 1363 * @see #removeChangeListener 1364 * @see EventListenerList 1365 */ 1366 protected void fireStateChanged() 1367 { 1368 Object[] listeners = listenerList.getListenerList(); 1369 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1370 if (listeners[i] == ChangeListener.class) { 1371 if (changeEvent == null) { 1372 changeEvent = new ChangeEvent(this); 1373 } 1374 ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent); 1375 } 1376 } 1377 } 1378 1379 /** 1380 * Always repaint in the parents coordinate system to make sure 1381 * only one paint is performed by the <code>RepaintManager</code>. 1382 * 1383 * @param tm maximum time in milliseconds before update 1384 * @param x the <code>x</code> coordinate (pixels over from left) 1385 * @param y the <code>y</code> coordinate (pixels down from top) 1386 * @param w the width 1387 * @param h the height 1388 * @see java.awt.Component#update(java.awt.Graphics) 1389 */ 1390 public void repaint(long tm, int x, int y, int w, int h) { 1391 Container parent = getParent(); 1392 if(parent != null) 1393 parent.repaint(tm,x+getX(),y+getY(),w,h); 1394 else 1395 super.repaint(tm,x,y,w,h); 1396 } 1397 1398 1399 /** 1400 * Returns a string representation of this <code>JViewport</code>. 1401 * This method 1402 * is intended to be used only for debugging purposes, and the 1403 * content and format of the returned string may vary between 1404 * implementations. The returned string may be empty but may not 1405 * be <code>null</code>. 1406 * 1407 * @return a string representation of this <code>JViewport</code> 1408 */ 1409 protected String paramString() { 1410 String isViewSizeSetString = (isViewSizeSet ? 1411 "true" : "false"); 1412 String lastPaintPositionString = (lastPaintPosition != null ? 1413 lastPaintPosition.toString() : ""); 1414 String scrollUnderwayString = (scrollUnderway ? 1415 "true" : "false"); 1416 1417 return super.paramString() + 1418 ",isViewSizeSet=" + isViewSizeSetString + 1419 ",lastPaintPosition=" + lastPaintPositionString + 1420 ",scrollUnderway=" + scrollUnderwayString; 1421 } 1422 1423 // 1424 // Following is used when doBlit is true. 1425 // 1426 1427 /** 1428 * Notifies listeners of a property change. This is subclassed to update 1429 * the <code>windowBlit</code> property. 1430 * (The <code>putClientProperty</code> property is final). 1431 * 1432 * @param propertyName a string containing the property name 1433 * @param oldValue the old value of the property 1434 * @param newValue the new value of the property 1435 */ 1436 protected void firePropertyChange(String propertyName, Object oldValue, 1437 Object newValue) { 1438 super.firePropertyChange(propertyName, oldValue, newValue); 1439 if (propertyName.equals(EnableWindowBlit)) { 1440 if (newValue != null) { 1441 setScrollMode(BLIT_SCROLL_MODE); 1442 } else { 1443 setScrollMode(SIMPLE_SCROLL_MODE); 1444 } 1445 } 1446 } 1447 1448 /** 1449 * Returns true if the component needs to be completely repainted after 1450 * a blit and a paint is received. 1451 */ 1452 private boolean needsRepaintAfterBlit() { 1453 // Find the first heavy weight ancestor. isObscured and 1454 // canDetermineObscurity are only appropriate for heavy weights. 1455 Component heavyParent = getParent(); 1456 1457 while (heavyParent != null && heavyParent.isLightweight()) { 1458 heavyParent = heavyParent.getParent(); 1459 } 1460 1461 if (heavyParent != null) { 1462 ComponentPeer peer = AWTAccessor.getComponentAccessor() 1463 .getPeer(heavyParent); 1464 1465 if (peer != null && peer.canDetermineObscurity() && 1466 !peer.isObscured()) { 1467 // The peer says we aren't obscured, therefore we can assume 1468 // that we won't later be messaged to paint a portion that 1469 // we tried to blit that wasn't valid. 1470 // It is certainly possible that when we blited we were 1471 // obscured, and by the time this is invoked we aren't, but the 1472 // chances of that happening are pretty slim. 1473 return false; 1474 } 1475 } 1476 return true; 1477 } 1478 1479 private Timer createRepaintTimer() { 1480 Timer timer = new Timer(300, new ActionListener() { 1481 public void actionPerformed(ActionEvent ae) { 1482 // waitingForRepaint will be false if a paint came down 1483 // with the complete clip rect, in which case we don't 1484 // have to cause a repaint. 1485 if (waitingForRepaint) { 1486 repaint(); 1487 } 1488 } 1489 }); 1490 timer.setRepeats(false); 1491 return timer; 1492 } 1493 1494 /** 1495 * If the repaint manager has a dirty region for the view, the view is 1496 * asked to paint. 1497 * 1498 * @param g the <code>Graphics</code> context within which to paint 1499 */ 1500 private void flushViewDirtyRegion(Graphics g, Rectangle dirty) { 1501 JComponent view = (JComponent) getView(); 1502 if(dirty != null && dirty.width > 0 && dirty.height > 0) { 1503 dirty.x += view.getX(); 1504 dirty.y += view.getY(); 1505 Rectangle clip = g.getClipBounds(); 1506 if (clip == null) { 1507 // Only happens in 1.2 1508 g.setClip(0, 0, getWidth(), getHeight()); 1509 } 1510 g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height); 1511 clip = g.getClipBounds(); 1512 // Only paint the dirty region if it is visible. 1513 if (clip.width > 0 && clip.height > 0) { 1514 paintView(g); 1515 } 1516 } 1517 } 1518 1519 /** 1520 * Used when blitting. 1521 * 1522 * @param g the <code>Graphics</code> context within which to paint 1523 * @return true if blitting succeeded; otherwise false 1524 */ 1525 private boolean windowBlitPaint(Graphics g) { 1526 int width = getWidth(); 1527 int height = getHeight(); 1528 1529 if ((width == 0) || (height == 0)) { 1530 return false; 1531 } 1532 1533 boolean retValue; 1534 RepaintManager rm = RepaintManager.currentManager(this); 1535 JComponent view = (JComponent) getView(); 1536 1537 if (lastPaintPosition == null || 1538 lastPaintPosition.equals(getViewLocation())) { 1539 paintView(g); 1540 retValue = false; 1541 } else { 1542 // The image was scrolled. Manipulate the backing store and flush 1543 // it to g. 1544 Point blitFrom = new Point(); 1545 Point blitTo = new Point(); 1546 Dimension blitSize = new Dimension(); 1547 Rectangle blitPaint = new Rectangle(); 1548 1549 Point newLocation = getViewLocation(); 1550 int dx = newLocation.x - lastPaintPosition.x; 1551 int dy = newLocation.y - lastPaintPosition.y; 1552 boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, 1553 blitPaint); 1554 if (!canBlit) { 1555 paintView(g); 1556 retValue = false; 1557 } else { 1558 // Prepare the rest of the view; the part that has just been 1559 // exposed. 1560 Rectangle r = view.getBounds().intersection(blitPaint); 1561 r.x -= view.getX(); 1562 r.y -= view.getY(); 1563 1564 blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height, 1565 blitFrom.x, blitFrom.y, blitTo.x, blitTo.y, 1566 blitSize.width, blitSize.height); 1567 retValue = true; 1568 } 1569 } 1570 lastPaintPosition = getViewLocation(); 1571 return retValue; 1572 } 1573 1574 // 1575 // NOTE: the code below uses paintForceDoubleBuffered for historical 1576 // reasons. If we're going to allow a blit we've already accounted for 1577 // everything that paintImmediately and _paintImmediately does, for that 1578 // reason we call into paintForceDoubleBuffered to diregard whether or 1579 // not setDoubleBuffered(true) was invoked on the view. 1580 // 1581 1582 private void blitDoubleBuffered(JComponent view, Graphics g, 1583 int clipX, int clipY, int clipW, int clipH, 1584 int blitFromX, int blitFromY, int blitToX, int blitToY, 1585 int blitW, int blitH) { 1586 // NOTE: 1587 // blitFrom/blitTo are in JViewport coordinates system 1588 // not the views coordinate space. 1589 // clip* are in the views coordinate space. 1590 RepaintManager rm = RepaintManager.currentManager(this); 1591 int bdx = blitToX - blitFromX; 1592 int bdy = blitToY - blitFromY; 1593 1594 Composite oldComposite = null; 1595 // Shift the scrolled region 1596 if (g instanceof Graphics2D) { 1597 Graphics2D g2d = (Graphics2D) g; 1598 oldComposite = g2d.getComposite(); 1599 g2d.setComposite(AlphaComposite.Src); 1600 } 1601 rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy, 1602 false); 1603 if (oldComposite != null) { 1604 ((Graphics2D) g).setComposite(oldComposite); 1605 } 1606 // Paint the newly exposed region. 1607 int x = view.getX(); 1608 int y = view.getY(); 1609 g.translate(x, y); 1610 g.setClip(clipX, clipY, clipW, clipH); 1611 view.paintForceDoubleBuffered(g); 1612 g.translate(-x, -y); 1613 } 1614 1615 /** 1616 * Called to paint the view, usually when <code>blitPaint</code> 1617 * can not blit. 1618 * 1619 * @param g the <code>Graphics</code> context within which to paint 1620 */ 1621 private void paintView(Graphics g) { 1622 Rectangle clip = g.getClipBounds(); 1623 JComponent view = (JComponent)getView(); 1624 1625 if (view.getWidth() >= getWidth()) { 1626 // Graphics is relative to JViewport, need to map to view's 1627 // coordinates space. 1628 int x = view.getX(); 1629 int y = view.getY(); 1630 g.translate(x, y); 1631 g.setClip(clip.x - x, clip.y - y, clip.width, clip.height); 1632 view.paintForceDoubleBuffered(g); 1633 g.translate(-x, -y); 1634 g.setClip(clip.x, clip.y, clip.width, clip.height); 1635 } 1636 else { 1637 // To avoid any problems that may result from the viewport being 1638 // bigger than the view we start painting from the viewport. 1639 try { 1640 inBlitPaint = true; 1641 paintForceDoubleBuffered(g); 1642 } finally { 1643 inBlitPaint = false; 1644 } 1645 } 1646 } 1647 1648 /** 1649 * Returns true if the viewport is not obscured by one of its ancestors, 1650 * or its ancestors children and if the viewport is showing. Blitting 1651 * when the view isn't showing will work, 1652 * or rather <code>copyArea</code> will work, 1653 * but will not produce the expected behavior. 1654 */ 1655 private boolean canUseWindowBlitter() { 1656 if (!isShowing() || (!(getParent() instanceof JComponent) && 1657 !(getView() instanceof JComponent))) { 1658 return false; 1659 } 1660 if (isPainting()) { 1661 // We're in the process of painting, don't blit. If we were 1662 // to blit we would draw on top of what we're already drawing, 1663 // so bail. 1664 return false; 1665 } 1666 1667 Rectangle dirtyRegion = RepaintManager.currentManager(this). 1668 getDirtyRegion((JComponent)getParent()); 1669 1670 if (dirtyRegion != null && dirtyRegion.width > 0 && 1671 dirtyRegion.height > 0) { 1672 // Part of the scrollpane needs to be repainted too, don't blit. 1673 return false; 1674 } 1675 1676 Rectangle clip = new Rectangle(0,0,getWidth(),getHeight()); 1677 Rectangle oldClip = new Rectangle(); 1678 Rectangle tmp2 = null; 1679 Container parent; 1680 Component lastParent = null; 1681 int x, y, w, h; 1682 1683 for(parent = this; parent != null && isLightweightComponent(parent); parent = parent.getParent()) { 1684 x = parent.getX(); 1685 y = parent.getY(); 1686 w = parent.getWidth(); 1687 h = parent.getHeight(); 1688 1689 oldClip.setBounds(clip); 1690 SwingUtilities.computeIntersection(0, 0, w, h, clip); 1691 if(!clip.equals(oldClip)) 1692 return false; 1693 1694 if(lastParent != null && parent instanceof JComponent && 1695 !((JComponent)parent).isOptimizedDrawingEnabled()) { 1696 Component comps[] = parent.getComponents(); 1697 int index = 0; 1698 1699 for(int i = comps.length - 1 ;i >= 0; i--) { 1700 if(comps[i] == lastParent) { 1701 index = i - 1; 1702 break; 1703 } 1704 } 1705 1706 while(index >= 0) { 1707 tmp2 = comps[index].getBounds(tmp2); 1708 1709 if(tmp2.intersects(clip)) 1710 return false; 1711 index--; 1712 } 1713 } 1714 clip.x += x; 1715 clip.y += y; 1716 lastParent = parent; 1717 } 1718 if (parent == null) { 1719 // No Window parent. 1720 return false; 1721 } 1722 return true; 1723 } 1724 1725 1726///////////////// 1727// Accessibility support 1728//////////////// 1729 1730 /** 1731 * Gets the AccessibleContext associated with this JViewport. 1732 * For viewports, the AccessibleContext takes the form of an 1733 * AccessibleJViewport. 1734 * A new AccessibleJViewport instance is created if necessary. 1735 * 1736 * @return an AccessibleJViewport that serves as the 1737 * AccessibleContext of this JViewport 1738 */ 1739 public AccessibleContext getAccessibleContext() { 1740 if (accessibleContext == null) { 1741 accessibleContext = new AccessibleJViewport(); 1742 } 1743 return accessibleContext; 1744 } 1745 1746 /** 1747 * This class implements accessibility support for the 1748 * <code>JViewport</code> class. It provides an implementation of the 1749 * Java Accessibility API appropriate to viewport user-interface elements. 1750 * <p> 1751 * <strong>Warning:</strong> 1752 * Serialized objects of this class will not be compatible with 1753 * future Swing releases. The current serialization support is 1754 * appropriate for short term storage or RMI between applications running 1755 * the same version of Swing. As of 1.4, support for long term storage 1756 * of all JavaBeans™ 1757 * has been added to the <code>java.beans</code> package. 1758 * Please see {@link java.beans.XMLEncoder}. 1759 */ 1760 @SuppressWarnings("serial") // Same-version serialization only 1761 protected class AccessibleJViewport extends AccessibleJComponent { 1762 /** 1763 * Get the role of this object. 1764 * 1765 * @return an instance of AccessibleRole describing the role of 1766 * the object 1767 */ 1768 public AccessibleRole getAccessibleRole() { 1769 return AccessibleRole.VIEWPORT; 1770 } 1771 } // inner class AccessibleJViewport 1772} 1773