/* * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org * Copyright (c) 1998-2008, University of Maryland * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions * and the following disclaimer in the documentation and/or other materials provided with the * distribution. * * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its * contributors may be used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package edu.umd.cs.piccolox.swing; import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.plaf.ScrollPaneUI; import edu.umd.cs.piccolo.PCanvas; /** * A simple extension to a standard scroll pane that uses the jazz version of * the viewport by default. Also uses the jazz version of ScrollPaneLayout * * @author Lance Good */ public class PScrollPane extends JScrollPane { private static final long serialVersionUID = 1L; /** A reusable null action. */ protected PNullAction nullAction = null; /** Controls whether key actions are disabled on this component. */ protected boolean disableKeyActions = false; private final AdjustmentListener scrollAdjustmentListener = new AdjustmentListener() { private boolean lastAdjustingState = false; public void adjustmentValueChanged(final AdjustmentEvent event) { if (event.getSource() instanceof JScrollBar) { JScrollBar scrollBar = (JScrollBar) event.getSource(); setAdjusting(scrollBar.getValueIsAdjusting()); } } /** * Updates the underlying PCanvas' interacting flag depending on whether * scroll bar adjustments are still taking place. * * @param isAdjusting true if the scroll bar is still being interacted * with */ private void setAdjusting(final boolean isAdjusting) { if (isAdjusting != lastAdjustingState) { Component c = getViewport().getView(); if (c instanceof PCanvas) { ((PCanvas) c).setInteracting(isAdjusting); } lastAdjustingState = isAdjusting; } } }; /** * Constructs a scollpane for the provided component with the specified * scrollbar policies. * * @param view component being viewed through the scrollpane * @param vsbPolicy vertical scroll bar policy * @param hsbPolicy horizontal scroll bar policy */ public PScrollPane(final Component view, final int vsbPolicy, final int hsbPolicy) { super(view, vsbPolicy, hsbPolicy); // Set the layout and sync it with the scroll pane final PScrollPaneLayout layout = new PScrollPaneLayout.UIResource(); setLayout(layout); layout.syncWithScrollPane(this); horizontalScrollBar.addAdjustmentListener(scrollAdjustmentListener); verticalScrollBar.addAdjustmentListener(scrollAdjustmentListener); } /** * Intercepts the vertical scroll bar setter to ensure that the adjustment * listener is installed appropriately. * * @param newVerticalScrollBar the new vertical scroll bar to use with this PScrollPane */ public void setVerticalScrollBar(final JScrollBar newVerticalScrollBar) { if (verticalScrollBar != null) { verticalScrollBar.removeAdjustmentListener(scrollAdjustmentListener); } super.setVerticalScrollBar(newVerticalScrollBar); newVerticalScrollBar.addAdjustmentListener(scrollAdjustmentListener); } /** * Intercepts the horizontal scroll bar setter to ensure that the adjustment * listener is installed appropriately. * * @param newHorizontalScrollBar the new horizontal scroll bar to use with this PScrollPane */ public void setHorizontalScrollBar(final JScrollBar newHorizontalScrollBar) { if (horizontalScrollBar != null) { horizontalScrollBar.removeAdjustmentListener(scrollAdjustmentListener); } super.setHorizontalScrollBar(newHorizontalScrollBar); newHorizontalScrollBar.addAdjustmentListener(scrollAdjustmentListener); } /** * Constructs a scroll pane for the provided component. * * @param view component being viewed through the scroll pane */ public PScrollPane(final Component view) { this(view, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED); } /** * Constructs a scroll pane not attached to any component with the specified * scroll bar policies. * * @param vsbPolicy vertical scroll bar policy * @param hsbPolicy horizontal scroll bar policy */ public PScrollPane(final int vsbPolicy, final int hsbPolicy) { this(null, vsbPolicy, hsbPolicy); } /** * Constructs a scroll pane not attached to any component. */ public PScrollPane() { this(null, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED); } /** * Disable or enable key actions on this PScrollPane. * * @param disable true disables key actions, false enables key actions */ public void setKeyActionsDisabled(final boolean disable) { if (disable && disableKeyActions != disable) { disableKeyActions = disable; disableKeyActions(); } else if (!disable && disableKeyActions != disable) { disableKeyActions = disable; installCustomKeyActions(); } } /** * Sets the UI. * * @param ui the scroll pane UI to associate with this PScollPane */ public void setUI(final ScrollPaneUI ui) { super.setUI(ui); if (!disableKeyActions) { installCustomKeyActions(); } else { disableKeyActions(); } } /** * Install custom key actions (in place of the Swing defaults) to correctly * scroll the view. */ protected void installCustomKeyActions() { final ActionMap map = getActionMap(); map.put("scrollUp", new PScrollAction("scrollUp", SwingConstants.VERTICAL, -1, true)); map.put("scrollDown", new PScrollAction("scrollDown", SwingConstants.VERTICAL, 1, true)); map.put("scrollLeft", new PScrollAction("scrollLeft", SwingConstants.HORIZONTAL, -1, true)); map.put("scrollRight", new PScrollAction("ScrollRight", SwingConstants.HORIZONTAL, 1, true)); map.put("unitScrollRight", new PScrollAction("UnitScrollRight", SwingConstants.HORIZONTAL, 1, false)); map.put("unitScrollLeft", new PScrollAction("UnitScrollLeft", SwingConstants.HORIZONTAL, -1, false)); map.put("unitScrollUp", new PScrollAction("UnitScrollUp", SwingConstants.VERTICAL, -1, false)); map.put("unitScrollDown", new PScrollAction("UnitScrollDown", SwingConstants.VERTICAL, 1, false)); map.put("scrollEnd", new PScrollEndAction("ScrollEnd")); map.put("scrollHome", new PScrollHomeAction("ScrollHome")); } /** * Disables key actions on this PScrollPane. */ protected void disableKeyActions() { final ActionMap map = getActionMap(); if (nullAction == null) { nullAction = new PNullAction(); } map.put("scrollUp", nullAction); map.put("scrollDown", nullAction); map.put("scrollLeft", nullAction); map.put("scrollRight", nullAction); map.put("unitScrollRight", nullAction); map.put("unitScrollLeft", nullAction); map.put("unitScrollUp", nullAction); map.put("unitScrollDown", nullAction); map.put("scrollEnd", nullAction); map.put("scrollHome", nullAction); } /** * Overridden to create the Piccolo2D viewport. * * @return the Piccolo2D version of the viewport */ protected JViewport createViewport() { return new PViewport(); } /** * Action to scroll left/right/up/down. Modified from * javax.swing.plaf.basic.BasicScrollPaneUI.ScrollAction. * * Gets the view parameters (position and size) from the Viewport rather * than directly from the view - also only performs its actions when the * relevant scrollbar is visible. */ protected static class PScrollAction extends AbstractAction { private static final int MINIMUM_SCROLL_SIZE = 10; private static final long serialVersionUID = 1L; /** Direction to scroll. */ protected int orientation; /** 1 indicates scroll down, -1 up. */ protected int direction; /** True indicates a block scroll, otherwise a unit scroll. */ private final boolean block; /** * Constructs a scroll action with the given name in the given * orientiation stated and in the direction provided. * * @param name arbitrary name of action * @param orientation horizontal or vertical * @param direction 1 indicates scroll down, -1 up * @param block true if block scroll as opposed to unit */ protected PScrollAction(final String name, final int orientation, final int direction, final boolean block) { super(name); this.orientation = orientation; this.direction = direction; this.block = block; } /** * Performs the scroll action if the action was performed on visible * scrollbars and if the viewport is valid. * * @param event the event responsible for this action being performed */ public void actionPerformed(final ActionEvent event) { final JScrollPane scrollpane = (JScrollPane) event.getSource(); if (!isScrollEventOnVisibleScrollbars(scrollpane)) { return; } final JViewport vp = scrollpane.getViewport(); if (vp == null) { return; } Component view = vp.getView(); if (view == null) { return; } final Rectangle visRect = vp.getViewRect(); // LEG: Modification to query the viewport for the // view size rather than going directly to the view final Dimension vSize = vp.getViewSize(); final 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 = MINIMUM_SCROLL_SIZE; } } 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 { 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; } } vp.setViewPosition(visRect.getLocation()); } private boolean isScrollEventOnVisibleScrollbars(final JScrollPane scrollpane) { return orientation == SwingConstants.VERTICAL && scrollpane.getVerticalScrollBar().isShowing() || orientation == SwingConstants.HORIZONTAL && scrollpane.getHorizontalScrollBar().isShowing(); } } /** * Action to scroll to x,y location of 0,0. Modified from * javax.swing.plaf.basic.BasicScrollPaneUI.ScrollEndAction. * * Only performs the event if a scrollbar is visible. */ private static class PScrollHomeAction extends AbstractAction { private static final long serialVersionUID = 1L; protected PScrollHomeAction(final String name) { super(name); } public void actionPerformed(final ActionEvent e) { final JScrollPane scrollpane = (JScrollPane) e.getSource(); // LEG: Modification to only perform these actions if one of the // scrollbars is actually showing if (scrollpane.getVerticalScrollBar().isShowing() || scrollpane.getHorizontalScrollBar().isShowing()) { final JViewport vp = scrollpane.getViewport(); if (vp != null && vp.getView() != null) { vp.setViewPosition(new Point(0, 0)); } } } } /** * Action to scroll to last visible location. Modified from * javax.swing.plaf.basic.BasicScrollPaneUI.ScrollEndAction. * * Gets the view size from the viewport rather than directly from the view - * also only performs the event if a scrollbar is visible. */ protected static class PScrollEndAction extends AbstractAction { private static final long serialVersionUID = 1L; /** * Constructs a scroll to end action with the given name. * * @param name name to assign to this action */ protected PScrollEndAction(final String name) { super(name); } /** * Scrolls to the end of the viewport if there are visible scrollbars. * * @param event event responsible for the scroll event */ public void actionPerformed(final ActionEvent event) { final JScrollPane scrollpane = (JScrollPane) event.getSource(); // LEG: Modification to only perform these actions if one of the // scrollbars is actually showing if (scrollpane.getVerticalScrollBar().isShowing() || scrollpane.getHorizontalScrollBar().isShowing()) { final JViewport vp = scrollpane.getViewport(); if (vp != null && vp.getView() != null) { final Rectangle visRect = vp.getViewRect(); // LEG: Modification to query the viewport for the // view size rather than going directly to the view final Dimension size = vp.getViewSize(); vp.setViewPosition(new Point(size.width - visRect.width, size.height - visRect.height)); } } } } /** * An action to do nothing - put into an action map to keep it from looking * to its parent. */ protected static class PNullAction extends AbstractAction { private static final long serialVersionUID = 1L; /** * Does nothing. * * @param e Event responsible for this action */ public void actionPerformed(final ActionEvent e) { } } }