ch.randelshofer.quaqua.QuaquaScrollPaneUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Quaqua Show documentation
Show all versions of Quaqua Show documentation
A Mavenisation of the Quaqua Mac OSX Swing Look and Feel (Java library)
Quaqua Look and Feel (C) 2003-2010, Werner Randelshofer.
Mavenisation by Matt Gumbley, DevZendo.org - for problems with
Mavenisation, see Matt; for issues with Quaqua, see the Quaqua home page.
For full license details, see http://randelshofer.ch/quaqua/license.html
The newest version!
/*
* @(#)QuaquaScrollPaneUI.java
*
* Copyright (c) 2004-2010 Werner Randelshofer, Immensee, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package ch.randelshofer.quaqua;
import ch.randelshofer.quaqua.util.*;
import ch.randelshofer.quaqua.util.Debug;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* QuaquaScrollPaneUI.
*
* @author Werner Randelshofer
* @version $Id: QuaquaScrollPaneUI.java 361 2010-11-21 11:19:20Z wrandelshofer $
*/
public class QuaquaScrollPaneUI extends BasicScrollPaneUI implements VisuallyLayoutable {
//private HierarchyListener hierarchyListener;
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;
/**
* PropertyChangeListener installed on the vertical scrollbar.
*/
private PropertyChangeListener vsbPropertyChangeListener;
/**
* PropertyChangeListener installed on the horizontal scrollbar.
*/
private PropertyChangeListener hsbPropertyChangeListener;
private MouseWheelListener mouseScrollListener;
/** Creates a new instance. */
public QuaquaScrollPaneUI() {
}
public static ComponentUI createUI(JComponent c) {
return new QuaquaScrollPaneUI();
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
QuaquaUtilities.installProperty(c, "opaque", UIManager.get("ScrollPane.opaque"));
c.setFocusable(UIManager.getBoolean("ScrollPane.focusable"));
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return getHandler();
}
@Override
protected void installDefaults(JScrollPane scrollpane) {
super.installDefaults(scrollpane);
if (scrollpane.getLayout() instanceof UIResource) {
ScrollPaneLayout layout = new QuaquaScrollPaneLayout.UIResource();
scrollpane.setLayout(layout);
layout.syncWithScrollPane(scrollpane);
}
}
@Override
protected void uninstallDefaults(JScrollPane scrollpane) {
super.uninstallDefaults(scrollpane);
if (scrollpane.getLayout() instanceof UIResource) {
ScrollPaneLayout layout = new ScrollPaneLayout.UIResource();
scrollpane.setLayout(layout);
layout.syncWithScrollPane(scrollpane);
}
}
@Override
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);
}
@Override
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;
}
/**
* 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
*/
@Override
protected MouseWheelListener createMouseWheelListener() {
return getHandler();
}
/**
* Returns a PropertyChangeListener
that will be installed
* on the vertical JScrollBar
.
*/
private PropertyChangeListener createVSBPropertyChangeListener() {
return getHandler();
}
/**
* Returns a PropertyChangeListener
that will be installed
* on the horizontal JScrollBar
.
*/
private PropertyChangeListener createHSBPropertyChangeListener() {
return getHandler();
}
@Override
protected ChangeListener createVSBChangeListener() {
return getHandler();
}
@Override
protected ChangeListener createHSBChangeListener() {
return getHandler();
}
protected ChangeListener createViewportChangeListener() {
return getHandler();
}
private Handler getHandler() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
public Insets getVisualMargin(Component c) {
Insets margin = (Insets) ((JComponent) c).getClientProperty("Quaqua.Component.visualMargin");
if (margin == null) {
margin = UIManager.getInsets("Component.visualMargin");
}
return (margin == null) ? new Insets(0, 0, 0, 0) : margin;
}
public void update(Graphics g, JComponent c) {
if (c.isOpaque()) {
g.setColor(c.getBackground());
Insets margin = getVisualMargin(c);
g.fillRect(margin.left, margin.top, c.getWidth() - margin.left - margin.right, c.getHeight() - margin.top - margin.bottom);
paint(g, c);
Debug.paint(g, c, this);
}
}
public int getBaseline(JComponent c, int width, int height) {
return -1;
}
public Rectangle getVisualBounds(JComponent c, int type, int width, int height) {
Rectangle bounds = new Rectangle(0, 0, width, height);
if (type == VisuallyLayoutable.CLIP_BOUNDS) {
return bounds;
}
JScrollPane b = (JScrollPane) c;
if (type == VisuallyLayoutable.COMPONENT_BOUNDS
&& b.getBorder() != null) {
Border border = b.getBorder();
if (border instanceof UIResource) {
InsetsUtil.subtractInto(getVisualMargin(b), bounds);
}
return bounds;
}
return bounds;
}
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);
}
}
}
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);
}
}
}
class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener {
//
// MouseWheelListener
//
public void mouseWheelMoved(MouseWheelEvent e) {
if (scrollpane.isWheelScrollingEnabled()
&& e.getWheelRotation() != 0) {
int direction = e.getWheelRotation() < 0 ? -1 : 1;
int orientation =
(e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == 0
? SwingConstants.VERTICAL //
: SwingConstants.HORIZONTAL;
JScrollBar toScroll = orientation==SwingConstants.VERTICAL
? scrollpane.getVerticalScrollBar()
: scrollpane.getHorizontalScrollBar();
// If the scrollpane can not be scrolled in this direction,
// scroll the parent scrollpane.
if (orientation == SwingConstants.VERTICAL
&& scrollpane.getVerticalScrollBarPolicy() == JScrollPane.VERTICAL_SCROLLBAR_NEVER
|| orientation == SwingConstants.HORIZONTAL
&& scrollpane.getHorizontalScrollBarPolicy() == JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
JScrollPane parentPane=(JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, scrollpane);
if (parentPane!=null) {
parentPane.dispatchEvent(e);
}
return;
}
// find which scrollbar to scroll, or return if none
if (toScroll == null || !toScroll.isVisible()) {
toScroll = scrollpane.getHorizontalScrollBar();
if (toScroll == null || !toScroll.isVisible()) {
return;
}
}
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.
QuaquaScrollBarUI.scrollByUnits(toScroll, direction,
units, limitScroll);
}
} else if (e.getScrollType()
== MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
QuaquaScrollBarUI.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) {
String name = e.getPropertyName();
if ("Frame.active".equals(name)) {
QuaquaUtilities.repaintBorder((JComponent) e.getSource());
} else if (name.equals("JComponent.sizeVariant")) {
QuaquaUtilities.applySizeVariant(scrollpane);
}
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);
}
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy