/* * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.plaf.basic; import sun.swing.DefaultLookup; import sun.swing.UIAction; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import javax.swing.plaf.*; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.awt.Component; import java.awt.Container; import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.Dimension; import java.awt.Point; import java.awt.Insets; import java.awt.Graphics; import java.awt.event.*; import java.io.Serializable; import java.awt.Toolkit; import java.awt.ComponentOrientation; /** {@collect.stats} * A default L&F implementation of ScrollPaneUI. * * @author Hans Muller */ public class BasicScrollPaneUI extends ScrollPaneUI implements ScrollPaneConstants { protected JScrollPane scrollpane; protected ChangeListener vsbChangeListener; protected ChangeListener hsbChangeListener; protected ChangeListener viewportChangeListener; protected PropertyChangeListener spPropertyChangeListener; private MouseWheelListener mouseScrollListener; /** {@collect.stats} * PropertyChangeListener installed on the vertical scrollbar. */ private PropertyChangeListener vsbPropertyChangeListener; /** {@collect.stats} * PropertyChangeListener installed on the horizontal scrollbar. */ private PropertyChangeListener hsbPropertyChangeListener; private Handler handler; /** {@collect.stats} * State flag that shows whether setValue() was called from a user program * before the value of "extent" was set in right-to-left component * orientation. */ private boolean setValueCalled = false; public static ComponentUI createUI(JComponent x) { return new BasicScrollPaneUI(); } static void loadActionMap(LazyActionMap map) { map.put(new Actions(Actions.SCROLL_UP)); map.put(new Actions(Actions.SCROLL_DOWN)); map.put(new Actions(Actions.SCROLL_HOME)); map.put(new Actions(Actions.SCROLL_END)); map.put(new Actions(Actions.UNIT_SCROLL_UP)); map.put(new Actions(Actions.UNIT_SCROLL_DOWN)); map.put(new Actions(Actions.SCROLL_LEFT)); map.put(new Actions(Actions.SCROLL_RIGHT)); map.put(new Actions(Actions.UNIT_SCROLL_RIGHT)); map.put(new Actions(Actions.UNIT_SCROLL_LEFT)); } public void paint(Graphics g, JComponent c) { Border vpBorder = scrollpane.getViewportBorder(); if (vpBorder != null) { Rectangle r = scrollpane.getViewportBorderBounds(); vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height); } } /** {@collect.stats} * @return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE) */ public Dimension getMaximumSize(JComponent c) { return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); } protected void installDefaults(JScrollPane scrollpane) { LookAndFeel.installBorder(scrollpane, "ScrollPane.border"); LookAndFeel.installColorsAndFont(scrollpane, "ScrollPane.background", "ScrollPane.foreground", "ScrollPane.font"); Border vpBorder = scrollpane.getViewportBorder(); if ((vpBorder == null) ||( vpBorder instanceof UIResource)) { vpBorder = UIManager.getBorder("ScrollPane.viewportBorder"); scrollpane.setViewportBorder(vpBorder); } LookAndFeel.installProperty(scrollpane, "opaque", Boolean.TRUE); } protected void installListeners(JScrollPane c) { vsbChangeListener = createVSBChangeListener(); vsbPropertyChangeListener = createVSBPropertyChangeListener(); hsbChangeListener = createHSBChangeListener(); hsbPropertyChangeListener = createHSBPropertyChangeListener(); viewportChangeListener = createViewportChangeListener(); spPropertyChangeListener = createPropertyChangeListener(); JViewport viewport = scrollpane.getViewport(); JScrollBar vsb = scrollpane.getVerticalScrollBar(); JScrollBar hsb = scrollpane.getHorizontalScrollBar(); if (viewport != null) { viewport.addChangeListener(viewportChangeListener); } if (vsb != null) { vsb.getModel().addChangeListener(vsbChangeListener); vsb.addPropertyChangeListener(vsbPropertyChangeListener); } if (hsb != null) { hsb.getModel().addChangeListener(hsbChangeListener); hsb.addPropertyChangeListener(hsbPropertyChangeListener); } scrollpane.addPropertyChangeListener(spPropertyChangeListener); mouseScrollListener = createMouseWheelListener(); scrollpane.addMouseWheelListener(mouseScrollListener); } protected void installKeyboardActions(JScrollPane c) { InputMap inputMap = getInputMap(JComponent. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); SwingUtilities.replaceUIInputMap(c, JComponent. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap); LazyActionMap.installLazyActionMap(c, BasicScrollPaneUI.class, "ScrollPane.actionMap"); } InputMap getInputMap(int condition) { if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { InputMap keyMap = (InputMap)DefaultLookup.get(scrollpane, this, "ScrollPane.ancestorInputMap"); InputMap rtlKeyMap; if (scrollpane.getComponentOrientation().isLeftToRight() || ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollpane, this, "ScrollPane.ancestorInputMap.RightToLeft")) == null)) { return keyMap; } else { rtlKeyMap.setParent(keyMap); return rtlKeyMap; } } return null; } public void installUI(JComponent x) { scrollpane = (JScrollPane)x; installDefaults(scrollpane); installListeners(scrollpane); installKeyboardActions(scrollpane); } protected void uninstallDefaults(JScrollPane c) { LookAndFeel.uninstallBorder(scrollpane); if (scrollpane.getViewportBorder() instanceof UIResource) { scrollpane.setViewportBorder(null); } } protected void uninstallListeners(JComponent c) { JViewport viewport = scrollpane.getViewport(); JScrollBar vsb = scrollpane.getVerticalScrollBar(); JScrollBar hsb = scrollpane.getHorizontalScrollBar(); if (viewport != null) { viewport.removeChangeListener(viewportChangeListener); } if (vsb != null) { vsb.getModel().removeChangeListener(vsbChangeListener); vsb.removePropertyChangeListener(vsbPropertyChangeListener); } if (hsb != null) { hsb.getModel().removeChangeListener(hsbChangeListener); hsb.removePropertyChangeListener(hsbPropertyChangeListener); } scrollpane.removePropertyChangeListener(spPropertyChangeListener); if (mouseScrollListener != null) { scrollpane.removeMouseWheelListener(mouseScrollListener); } vsbChangeListener = null; hsbChangeListener = null; viewportChangeListener = null; spPropertyChangeListener = null; mouseScrollListener = null; handler = null; } protected void uninstallKeyboardActions(JScrollPane c) { SwingUtilities.replaceUIActionMap(c, null); SwingUtilities.replaceUIInputMap(c, JComponent. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); } public void uninstallUI(JComponent c) { uninstallDefaults(scrollpane); uninstallListeners(scrollpane); uninstallKeyboardActions(scrollpane); scrollpane = null; } private Handler getHandler() { if (handler == null) { handler = new Handler(); } return handler; } protected void syncScrollPaneWithViewport() { JViewport viewport = scrollpane.getViewport(); JScrollBar vsb = scrollpane.getVerticalScrollBar(); JScrollBar hsb = scrollpane.getHorizontalScrollBar(); JViewport rowHead = scrollpane.getRowHeader(); JViewport colHead = scrollpane.getColumnHeader(); boolean ltr = scrollpane.getComponentOrientation().isLeftToRight(); if (viewport != null) { Dimension extentSize = viewport.getExtentSize(); Dimension viewSize = viewport.getViewSize(); Point viewPosition = viewport.getViewPosition(); if (vsb != null) { int extent = extentSize.height; int max = viewSize.height; int value = Math.max(0, Math.min(viewPosition.y, max - extent)); vsb.setValues(value, extent, 0, max); } if (hsb != null) { int extent = extentSize.width; int max = viewSize.width; int value; if (ltr) { value = Math.max(0, Math.min(viewPosition.x, max - extent)); } else { int currentValue = hsb.getValue(); /* Use a particular formula to calculate "value" * until effective x coordinate is calculated. */ if (setValueCalled && ((max - currentValue) == viewPosition.x)) { value = Math.max(0, Math.min(max - extent, currentValue)); /* After "extent" is set, turn setValueCalled flag off. */ if (extent != 0) { setValueCalled = false; } } else { if (extent > max) { viewPosition.x = max - extent; viewport.setViewPosition(viewPosition); value = 0; } else { /* The following line can't handle a small value of * viewPosition.x like Integer.MIN_VALUE correctly * because (max - extent - viewPositoiin.x) causes * an overflow. As a result, value becomes zero. * (e.g. setViewPosition(Integer.MAX_VALUE, ...) * in a user program causes a overflow. * Its expected value is (max - extent).) * However, this seems a trivial bug and adding a * fix makes this often-called method slow, so I'll * leave it until someone claims. */ value = Math.max(0, Math.min(max - extent, max - extent - viewPosition.x)); } } } hsb.setValues(value, extent, 0, max); } if (rowHead != null) { Point p = rowHead.getViewPosition(); p.y = viewport.getViewPosition().y; p.x = 0; rowHead.setViewPosition(p); } if (colHead != null) { Point p = colHead.getViewPosition(); if (ltr) { p.x = viewport.getViewPosition().x; } else { p.x = Math.max(0, viewport.getViewPosition().x); } p.y = 0; colHead.setViewPosition(p); } } } /** {@collect.stats} * Returns the baseline. * * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} * @see javax.swing.JComponent#getBaseline(int, int) * @since 1.6 */ public int getBaseline(JComponent c, int width, int height) { JViewport viewport = scrollpane.getViewport(); Insets spInsets = scrollpane.getInsets(); int y = spInsets.top; height = height - spInsets.top - spInsets.bottom; width = width - spInsets.left - spInsets.right; JViewport columnHeader = scrollpane.getColumnHeader(); if (columnHeader != null && columnHeader.isVisible()) { Component header = columnHeader.getView(); if (header != null && header.isVisible()) { // Header is always given it's preferred size. Dimension headerPref = header.getPreferredSize(); int baseline = header.getBaseline(headerPref.width, headerPref.height); if (baseline >= 0) { return y + baseline; } } Dimension columnPref = columnHeader.getPreferredSize(); height -= columnPref.height; y += columnPref.height; } Component view = (viewport == null) ? null : viewport.getView(); if (view != null && view.isVisible() && view.getBaselineResizeBehavior() == Component.BaselineResizeBehavior.CONSTANT_ASCENT) { Border viewportBorder = scrollpane.getViewportBorder(); if (viewportBorder != null) { Insets vpbInsets = viewportBorder.getBorderInsets(scrollpane); y += vpbInsets.top; height = height - vpbInsets.top - vpbInsets.bottom; width = width - vpbInsets.left - vpbInsets.right; } if (view.getWidth() > 0 && view.getHeight() > 0) { Dimension min = view.getMinimumSize(); width = Math.max(min.width, view.getWidth()); height = Math.max(min.height, view.getHeight()); } if (width > 0 && height > 0) { int baseline = view.getBaseline(width, height); if (baseline > 0) { return y + baseline; } } } return -1; } /** {@collect.stats} * Returns an enum indicating how the baseline of the component * changes as the size changes. * * @throws NullPointerException {@inheritDoc} * @see javax.swing.JComponent#getBaseline(int, int) * @since 1.6 */ public Component.BaselineResizeBehavior getBaselineResizeBehavior( JComponent c) { super.getBaselineResizeBehavior(c); // Baseline is either from the header, in which case it's always // the same size and therefor can be created as CONSTANT_ASCENT. // If the header doesn't have a baseline than the baseline will only // be valid if it's BaselineResizeBehavior is // CONSTANT_ASCENT, so, return CONSTANT_ASCENT. return Component.BaselineResizeBehavior.CONSTANT_ASCENT; } /** {@collect.stats} * Listener for viewport events. */ public class ViewportChangeHandler implements ChangeListener { // NOTE: This class exists only for backward compatability. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void stateChanged(ChangeEvent e) { getHandler().stateChanged(e); } } protected ChangeListener createViewportChangeListener() { return getHandler(); } /** {@collect.stats} * Horizontal scrollbar listener. */ public class HSBChangeListener implements ChangeListener { // NOTE: This class exists only for backward compatability. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void stateChanged(ChangeEvent e) { getHandler().stateChanged(e); } } /** {@collect.stats} * Returns a <code>PropertyChangeListener</code> that will be installed * on the horizontal <code>JScrollBar</code>. */ private PropertyChangeListener createHSBPropertyChangeListener() { return getHandler(); } protected ChangeListener createHSBChangeListener() { return getHandler(); } /** {@collect.stats} * Vertical scrollbar listener. */ public class VSBChangeListener implements ChangeListener { // NOTE: This class exists only for backward compatability. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void stateChanged(ChangeEvent e) { getHandler().stateChanged(e); } } /** {@collect.stats} * Returns a <code>PropertyChangeListener</code> that will be installed * on the vertical <code>JScrollBar</code>. */ private PropertyChangeListener createVSBPropertyChangeListener() { return getHandler(); } protected ChangeListener createVSBChangeListener() { return getHandler(); } /** {@collect.stats} * MouseWheelHandler is an inner class which implements the * MouseWheelListener interface. MouseWheelHandler responds to * MouseWheelEvents by scrolling the JScrollPane appropriately. * If the scroll pane's * <code>isWheelScrollingEnabled</code> * method returns false, no scrolling occurs. * * @see javax.swing.JScrollPane#isWheelScrollingEnabled * @see #createMouseWheelListener * @see java.awt.event.MouseWheelListener * @see java.awt.event.MouseWheelEvent * @since 1.4 */ protected class MouseWheelHandler implements MouseWheelListener { // NOTE: This class exists only for backward compatability. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. /** {@collect.stats} * Called when the mouse wheel is rotated while over a * JScrollPane. * * @param e MouseWheelEvent to be handled * @since 1.4 */ public void mouseWheelMoved(MouseWheelEvent e) { getHandler().mouseWheelMoved(e); } } /** {@collect.stats} * Creates an instance of MouseWheelListener, which is added to the * JScrollPane by installUI(). The returned MouseWheelListener is used * to handle mouse wheel-driven scrolling. * * @return MouseWheelListener which implements wheel-driven scrolling * @see #installUI * @see MouseWheelHandler * @since 1.4 */ protected MouseWheelListener createMouseWheelListener() { return getHandler(); } protected void updateScrollBarDisplayPolicy(PropertyChangeEvent e) { scrollpane.revalidate(); scrollpane.repaint(); } protected void updateViewport(PropertyChangeEvent e) { JViewport oldViewport = (JViewport)(e.getOldValue()); JViewport newViewport = (JViewport)(e.getNewValue()); if (oldViewport != null) { oldViewport.removeChangeListener(viewportChangeListener); } if (newViewport != null) { Point p = newViewport.getViewPosition(); if (scrollpane.getComponentOrientation().isLeftToRight()) { p.x = Math.max(p.x, 0); } else { int max = newViewport.getViewSize().width; int extent = newViewport.getExtentSize().width; if (extent > max) { p.x = max - extent; } else { p.x = Math.max(0, Math.min(max - extent, p.x)); } } p.y = Math.max(p.y, 0); newViewport.setViewPosition(p); newViewport.addChangeListener(viewportChangeListener); } } protected void updateRowHeader(PropertyChangeEvent e) { JViewport newRowHead = (JViewport)(e.getNewValue()); if (newRowHead != null) { JViewport viewport = scrollpane.getViewport(); Point p = newRowHead.getViewPosition(); p.y = (viewport != null) ? viewport.getViewPosition().y : 0; newRowHead.setViewPosition(p); } } protected void updateColumnHeader(PropertyChangeEvent e) { JViewport newColHead = (JViewport)(e.getNewValue()); if (newColHead != null) { JViewport viewport = scrollpane.getViewport(); Point p = newColHead.getViewPosition(); if (viewport == null) { p.x = 0; } else { if (scrollpane.getComponentOrientation().isLeftToRight()) { p.x = viewport.getViewPosition().x; } else { p.x = Math.max(0, viewport.getViewPosition().x); } } newColHead.setViewPosition(p); scrollpane.add(newColHead, COLUMN_HEADER); } } private void updateHorizontalScrollBar(PropertyChangeEvent pce) { updateScrollBar(pce, hsbChangeListener, hsbPropertyChangeListener); } private void updateVerticalScrollBar(PropertyChangeEvent pce) { updateScrollBar(pce, vsbChangeListener, vsbPropertyChangeListener); } private void updateScrollBar(PropertyChangeEvent pce, ChangeListener cl, PropertyChangeListener pcl) { JScrollBar sb = (JScrollBar)pce.getOldValue(); if (sb != null) { if (cl != null) { sb.getModel().removeChangeListener(cl); } if (pcl != null) { sb.removePropertyChangeListener(pcl); } } sb = (JScrollBar)pce.getNewValue(); if (sb != null) { if (cl != null) { sb.getModel().addChangeListener(cl); } if (pcl != null) { sb.addPropertyChangeListener(pcl); } } } public class PropertyChangeHandler implements PropertyChangeListener { // NOTE: This class exists only for backward compatability. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void propertyChange(PropertyChangeEvent e) { getHandler().propertyChange(e); } } /** {@collect.stats} * Creates an instance of PropertyChangeListener that's added to * the JScrollPane by installUI(). Subclasses can override this method * to return a custom PropertyChangeListener, e.g. * <pre> * class MyScrollPaneUI extends BasicScrollPaneUI { * protected PropertyChangeListener <b>createPropertyChangeListener</b>() { * return new MyPropertyChangeListener(); * } * public class MyPropertyChangeListener extends PropertyChangeListener { * public void propertyChange(PropertyChangeEvent e) { * if (e.getPropertyName().equals("viewport")) { * // do some extra work when the viewport changes * } * super.propertyChange(e); * } * } * } * </pre> * * @see java.beans.PropertyChangeListener * @see #installUI */ protected PropertyChangeListener createPropertyChangeListener() { return getHandler(); } private static class Actions extends UIAction { private static final String SCROLL_UP = "scrollUp"; private static final String SCROLL_DOWN = "scrollDown"; private static final String SCROLL_HOME = "scrollHome"; private static final String SCROLL_END = "scrollEnd"; private static final String UNIT_SCROLL_UP = "unitScrollUp"; private static final String UNIT_SCROLL_DOWN = "unitScrollDown"; private static final String SCROLL_LEFT = "scrollLeft"; private static final String SCROLL_RIGHT = "scrollRight"; private static final String UNIT_SCROLL_LEFT = "unitScrollLeft"; private static final String UNIT_SCROLL_RIGHT = "unitScrollRight"; Actions(String key) { super(key); } public void actionPerformed(ActionEvent e) { JScrollPane scrollPane = (JScrollPane)e.getSource(); boolean ltr = scrollPane.getComponentOrientation().isLeftToRight(); String key = getName(); if (key == SCROLL_UP) { scroll(scrollPane, SwingConstants.VERTICAL, -1, true); } else if (key == SCROLL_DOWN) { scroll(scrollPane, SwingConstants.VERTICAL, 1, true); } else if (key == SCROLL_HOME) { scrollHome(scrollPane); } else if (key == SCROLL_END) { scrollEnd(scrollPane); } else if (key == UNIT_SCROLL_UP) { scroll(scrollPane, SwingConstants.VERTICAL, -1, false); } else if (key == UNIT_SCROLL_DOWN) { scroll(scrollPane, SwingConstants.VERTICAL, 1, false); } else if (key == SCROLL_LEFT) { scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1, true); } else if (key == SCROLL_RIGHT) { scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1, true); } else if (key == UNIT_SCROLL_LEFT) { scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1, false); } else if (key == UNIT_SCROLL_RIGHT) { scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1, false); } } private void scrollEnd(JScrollPane scrollpane) { JViewport vp = scrollpane.getViewport(); Component view; if (vp != null && (view = vp.getView()) != null) { Rectangle visRect = vp.getViewRect(); Rectangle bounds = view.getBounds(); if (scrollpane.getComponentOrientation().isLeftToRight()) { vp.setViewPosition(new Point(bounds.width - visRect.width, bounds.height - visRect.height)); } else { vp.setViewPosition(new Point(0, bounds.height - visRect.height)); } } } private void scrollHome(JScrollPane scrollpane) { JViewport vp = scrollpane.getViewport(); Component view; if (vp != null && (view = vp.getView()) != null) { if (scrollpane.getComponentOrientation().isLeftToRight()) { vp.setViewPosition(new Point(0, 0)); } else { Rectangle visRect = vp.getViewRect(); Rectangle bounds = view.getBounds(); vp.setViewPosition(new Point(bounds.width - visRect.width, 0)); } } } private void scroll(JScrollPane scrollpane, int orientation, int direction, boolean block) { JViewport vp = scrollpane.getViewport(); Component view; if (vp != null && (view = vp.getView()) != null) { Rectangle visRect = vp.getViewRect(); Dimension vSize = view.getSize(); int amount; if (view instanceof Scrollable) { if (block) { amount = ((Scrollable)view).getScrollableBlockIncrement (visRect, orientation, direction); } else { amount = ((Scrollable)view).getScrollableUnitIncrement (visRect, orientation, direction); } } else { if (block) { if (orientation == SwingConstants.VERTICAL) { amount = visRect.height; } else { amount = visRect.width; } } else { amount = 10; } } if (orientation == SwingConstants.VERTICAL) { visRect.y += (amount * direction); if ((visRect.y + visRect.height) > vSize.height) { visRect.y = Math.max(0, vSize.height - visRect.height); } else if (visRect.y < 0) { visRect.y = 0; } } else { if (scrollpane.getComponentOrientation().isLeftToRight()) { visRect.x += (amount * direction); if ((visRect.x + visRect.width) > vSize.width) { visRect.x = Math.max(0, vSize.width - visRect.width); } else if (visRect.x < 0) { visRect.x = 0; } } else { visRect.x -= (amount * direction); if (visRect.width > vSize.width) { visRect.x = vSize.width - visRect.width; } else { visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x)); } } } vp.setViewPosition(visRect.getLocation()); } } } class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener { // // MouseWheelListener // public void mouseWheelMoved(MouseWheelEvent e) { if (scrollpane.isWheelScrollingEnabled() && e.getWheelRotation() != 0) { JScrollBar toScroll = scrollpane.getVerticalScrollBar(); int direction = e.getWheelRotation() < 0 ? -1 : 1; int orientation = SwingConstants.VERTICAL; // find which scrollbar to scroll, or return if none if (toScroll == null || !toScroll.isVisible()) { toScroll = scrollpane.getHorizontalScrollBar(); if (toScroll == null || !toScroll.isVisible()) { return; } orientation = SwingConstants.HORIZONTAL; } if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { JViewport vp = scrollpane.getViewport(); if (vp == null) { return; } Component comp = vp.getView(); int units = Math.abs(e.getUnitsToScroll()); // When the scrolling speed is set to maximum, it's possible // for a single wheel click to scroll by more units than // will fit in the visible area. This makes it // hard/impossible to get to certain parts of the scrolling // Component with the wheel. To make for more accurate // low-speed scrolling, we limit scrolling to the block // increment if the wheel was only rotated one click. boolean limitScroll = Math.abs(e.getWheelRotation()) == 1; // Check if we should use the visibleRect trick Object fastWheelScroll = toScroll.getClientProperty( "JScrollBar.fastWheelScrolling"); if (Boolean.TRUE == fastWheelScroll && comp instanceof Scrollable) { // 5078454: Under maximum acceleration, we may scroll // by many 100s of units in ~1 second. // // BasicScrollBarUI.scrollByUnits() can bog down the EDT // with repaints in this situation. However, the // Scrollable interface allows us to pass in an // arbitrary visibleRect. This allows us to accurately // calculate the total scroll amount, and then update // the GUI once. This technique provides much faster // accelerated wheel scrolling. Scrollable scrollComp = (Scrollable) comp; Rectangle viewRect = vp.getViewRect(); int startingX = viewRect.x; boolean leftToRight = comp.getComponentOrientation().isLeftToRight(); int scrollMin = toScroll.getMinimum(); int scrollMax = toScroll.getMaximum() - toScroll.getModel().getExtent(); if (limitScroll) { int blockIncr = scrollComp.getScrollableBlockIncrement(viewRect, orientation, direction); if (direction < 0) { scrollMin = Math.max(scrollMin, toScroll.getValue() - blockIncr); } else { scrollMax = Math.min(scrollMax, toScroll.getValue() + blockIncr); } } for (int i = 0; i < units; i++) { int unitIncr = scrollComp.getScrollableUnitIncrement(viewRect, orientation, direction); // Modify the visible rect for the next unit, and // check to see if we're at the end already. if (orientation == SwingConstants.VERTICAL) { if (direction < 0) { viewRect.y -= unitIncr; if (viewRect.y <= scrollMin) { viewRect.y = scrollMin; break; } } else { // (direction > 0 viewRect.y += unitIncr; if (viewRect.y >= scrollMax) { viewRect.y = scrollMax; break; } } } else { // Scroll left if ((leftToRight && direction < 0) || (!leftToRight && direction > 0)) { viewRect.x -= unitIncr; if (leftToRight) { if (viewRect.x < scrollMin) { viewRect.x = scrollMin; break; } } } // Scroll right else if ((leftToRight && direction > 0) || (!leftToRight && direction < 0)) { viewRect.x += unitIncr; if (leftToRight) { if (viewRect.x > scrollMax) { viewRect.x = scrollMax; break; } } } else { assert false : "Non-sensical ComponentOrientation / scroll direction"; } } } // Set the final view position on the ScrollBar if (orientation == SwingConstants.VERTICAL) { toScroll.setValue(viewRect.y); } else { if (leftToRight) { toScroll.setValue(viewRect.x); } else { // rightToLeft scrollbars are oriented with // minValue on the right and maxValue on the // left. int newPos = toScroll.getValue() - (viewRect.x - startingX); if (newPos < scrollMin) { newPos = scrollMin; } else if (newPos > scrollMax) { newPos = scrollMax; } toScroll.setValue(newPos); } } } else { // Viewport's view is not a Scrollable, or fast wheel // scrolling is not enabled. BasicScrollBarUI.scrollByUnits(toScroll, direction, units, limitScroll); } } else if (e.getScrollType() == MouseWheelEvent.WHEEL_BLOCK_SCROLL) { BasicScrollBarUI.scrollByBlock(toScroll, direction); } } } // // ChangeListener: This is added to the vieport, and hsb/vsb models. // public void stateChanged(ChangeEvent e) { JViewport viewport = scrollpane.getViewport(); if (viewport != null) { if (e.getSource() == viewport) { viewportStateChanged(e); } else { JScrollBar hsb = scrollpane.getHorizontalScrollBar(); if (hsb != null && e.getSource() == hsb.getModel()) { hsbStateChanged(viewport, e); } else { JScrollBar vsb = scrollpane.getVerticalScrollBar(); if (vsb != null && e.getSource() == vsb.getModel()) { vsbStateChanged(viewport, e); } } } } } private void vsbStateChanged(JViewport viewport, ChangeEvent e) { BoundedRangeModel model = (BoundedRangeModel)(e.getSource()); Point p = viewport.getViewPosition(); p.y = model.getValue(); viewport.setViewPosition(p); } private void hsbStateChanged(JViewport viewport, ChangeEvent e) { BoundedRangeModel model = (BoundedRangeModel)(e.getSource()); Point p = viewport.getViewPosition(); int value = model.getValue(); if (scrollpane.getComponentOrientation().isLeftToRight()) { p.x = value; } else { int max = viewport.getViewSize().width; int extent = viewport.getExtentSize().width; int oldX = p.x; /* Set new X coordinate based on "value". */ p.x = max - extent - value; /* If setValue() was called before "extent" was fixed, * turn setValueCalled flag on. */ if ((extent == 0) && (value != 0) && (oldX == max)) { setValueCalled = true; } else { /* When a pane without a horizontal scroll bar was * reduced and the bar appeared, the viewport should * show the right side of the view. */ if ((extent != 0) && (oldX < 0) && (p.x == 0)) { p.x += value; } } } viewport.setViewPosition(p); } private void viewportStateChanged(ChangeEvent e) { syncScrollPaneWithViewport(); } // // PropertyChangeListener: This is installed on both the JScrollPane // and the horizontal/vertical scrollbars. // // Listens for changes in the model property and reinstalls the // horizontal/vertical PropertyChangeListeners. public void propertyChange(PropertyChangeEvent e) { if (e.getSource() == scrollpane) { scrollPanePropertyChange(e); } else { sbPropertyChange(e); } } private void scrollPanePropertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if (propertyName == "verticalScrollBarDisplayPolicy") { updateScrollBarDisplayPolicy(e); } else if (propertyName == "horizontalScrollBarDisplayPolicy") { updateScrollBarDisplayPolicy(e); } else if (propertyName == "viewport") { updateViewport(e); } else if (propertyName == "rowHeader") { updateRowHeader(e); } else if (propertyName == "columnHeader") { updateColumnHeader(e); } else if (propertyName == "verticalScrollBar") { updateVerticalScrollBar(e); } else if (propertyName == "horizontalScrollBar") { updateHorizontalScrollBar(e); } else if (propertyName == "componentOrientation") { scrollpane.revalidate(); scrollpane.repaint(); } } // PropertyChangeListener for the horizontal and vertical scrollbars. private void sbPropertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); Object source = e.getSource(); if ("model" == propertyName) { JScrollBar sb = scrollpane.getVerticalScrollBar(); BoundedRangeModel oldModel = (BoundedRangeModel)e. getOldValue(); ChangeListener cl = null; if (source == sb) { cl = vsbChangeListener; } else if (source == scrollpane.getHorizontalScrollBar()) { sb = scrollpane.getHorizontalScrollBar(); cl = hsbChangeListener; } if (cl != null) { if (oldModel != null) { oldModel.removeChangeListener(cl); } if (sb.getModel() != null) { sb.getModel().addChangeListener(cl); } } } else if ("componentOrientation" == propertyName) { if (source == scrollpane.getHorizontalScrollBar()) { JScrollBar hsb = scrollpane.getHorizontalScrollBar(); JViewport viewport = scrollpane.getViewport(); Point p = viewport.getViewPosition(); if (scrollpane.getComponentOrientation().isLeftToRight()) { p.x = hsb.getValue(); } else { p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue(); } viewport.setViewPosition(p); } } } } }