You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1180 lines
44 KiB
1180 lines
44 KiB
/*
|
|
* Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
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.Rectangle;
|
|
import java.awt.Dimension;
|
|
import java.awt.Point;
|
|
import java.awt.Insets;
|
|
import java.awt.Graphics;
|
|
import java.awt.event.*;
|
|
|
|
/**
|
|
* 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;
|
|
private int oldExtent = Integer.MIN_VALUE;
|
|
|
|
/**
|
|
* PropertyChangeListener installed on the vertical scrollbar.
|
|
*/
|
|
private PropertyChangeListener vsbPropertyChangeListener;
|
|
|
|
/**
|
|
* PropertyChangeListener installed on the horizontal scrollbar.
|
|
*/
|
|
private PropertyChangeListener hsbPropertyChangeListener;
|
|
|
|
private Handler handler;
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @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));
|
|
if (oldExtent > extent) {
|
|
value -= oldExtent - extent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
oldExtent = extent;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (c == null) {
|
|
throw new NullPointerException("Component must be non-null");
|
|
}
|
|
|
|
if (width < 0 || height < 0) {
|
|
throw new IllegalArgumentException("Width and height must be >= 0");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
|
|
/**
|
|
* Listener for viewport events.
|
|
*/
|
|
public class ViewportChangeHandler implements ChangeListener
|
|
{
|
|
|
|
// NOTE: This class exists only for backward compatibility. 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();
|
|
}
|
|
|
|
|
|
/**
|
|
* Horizontal scrollbar listener.
|
|
*/
|
|
public class HSBChangeListener implements ChangeListener
|
|
{
|
|
|
|
// NOTE: This class exists only for backward compatibility. 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
|
|
/**
|
|
* Vertical scrollbar listener.
|
|
*/
|
|
public class VSBChangeListener implements ChangeListener
|
|
{
|
|
|
|
// NOTE: This class exists only for backward compatibility. 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);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
/**
|
|
* 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 compatibility. 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.
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 compatibility. 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);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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()
|
|
|| e.isShiftDown()) {
|
|
toScroll = scrollpane.getHorizontalScrollBar();
|
|
if (toScroll == null || !toScroll.isVisible()) {
|
|
return;
|
|
}
|
|
orientation = SwingConstants.HORIZONTAL;
|
|
}
|
|
|
|
e.consume();
|
|
|
|
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) {
|
|
syncScrollPaneWithViewport();
|
|
}
|
|
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);
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|