package org.esa.snap.rcp.quicklooks; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; /** * Listener to allow for iPhone like drag scrolling of a Component within a JScrollPane. * * @author Greg Cope * <p> * <p> * <p> * This program is free software: you can redistribute it and/or modify * <p> * it under the terms of the GNU General Public License as published by * <p> * the Free Software Foundation, either version 3 of the License, or * <p> * (at your option) any later version. * <p> * <p> * <p> * This program is distributed in the hope that it will be useful, * <p> * but WITHOUT ANY WARRANTY; without even the implied warranty of * <p> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * <p> * GNU General Public License for more details. * <p> * <p> * <p> * You should have received a copy of the GNU General Public License * <p> * along with this program. If not, see //www.gnu.org/licenses/>. */ public class DragScrollListener implements MouseListener, MouseMotionListener { //flags used to turn on/off draggable scrolling directions public static final int DRAGABLE_HORIZONTAL_SCROLL_BAR = 1; public static final int DRAGABLE_VERTICAL_SCROLL_BAR = 2; //defines the intensity of automatic scrolling. private int scrollingIntensity = 10; //value used to decrease scrolling intensity during animation private double damping = 0.05; //indicates the number of milliseconds between animation updates. private int animationSpeed = 20; //Animation timer private Timer animationTimer = null; //the time of the last mouse drag event private long lastDragTime = 0; private Point lastDragPoint = null; //animation rates private double pixelsPerMSX; private double pixelsPerMSY; //flag which defines the draggable scroll directions private int scrollBarMask = DRAGABLE_HORIZONTAL_SCROLL_BAR | DRAGABLE_VERTICAL_SCROLL_BAR; //the draggable component private final Component draggableComponent; //the JScrollPane containing the component private JScrollPane scroller = null; //the default cursor private Cursor defaultCursor; //List of drag speeds used to calculate animation speed //Uses the Point2D class to represent speeds rather than locations private java.util.List<Point2D> dragSpeeds = new ArrayList<Point2D>(); public DragScrollListener(final Component c) { this.draggableComponent = c; this.defaultCursor = draggableComponent.getCursor(); this.draggableComponent.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent arg0) { setScroller(); defaultCursor = draggableComponent.getCursor(); } }); setScroller(); } private void setScroller() { Component c = getParentScroller(draggableComponent); if (c != null) { scroller = (JScrollPane) c; } else { scroller = null; } } /** * Sets the Draggable elements - the Horizontal or Vertical Direction. One * <p> * can use a bitmasked 'or' (HORIZONTAL_SCROLL_BAR | VERTICAL_SCROLL_BAR ). * * @param mask One of HORIZONTAL_SCROLL_BAR, VERTICAL_SCROLL_BAR, or HORIZONTAL_SCROLL_BAR | VERTICAL_SCROLL_BAR */ public void setDraggableElements(int mask) { scrollBarMask = mask; } /** * Sets the scrolling intensity - the default value being 5. Note, that this has an * <p> * inverse relationship to intensity (1 has the biggest difference, higher numbers having * <p> * less impact). * * @param intensity The new intensity value (Note the inverse relationship). */ public void setScrollingIntensity(int intensity) { scrollingIntensity = intensity; } /** * Sets how frequently the animation will occur in milliseconds. Default * <p> * value is 30 milliseconds. 60+ will get a bit flickery. * * @param timing The timing, in milliseconds. */ public void setAnimationTiming(int timing) { animationSpeed = timing; } /** * Sets the animation damping. * * @param damping The new value */ public void setDamping(double damping) { this.damping = damping; } /** * Empty implementation */ public void mouseEntered(MouseEvent e) { } /** * Empty implementation */ public void mouseExited(MouseEvent e) { } /** * Mouse pressed implementation */ public void mousePressed(MouseEvent e) { if (animationTimer != null && animationTimer.isRunning()) { animationTimer.stop(); } draggableComponent.setCursor(new Cursor(Cursor.MOVE_CURSOR)); dragSpeeds.clear(); lastDragPoint = e.getPoint(); } /** * Mouse released implementation. This determines if further animation * <p> * is necessary and launches the appropriate times. */ public void mouseReleased(MouseEvent e) { draggableComponent.setCursor(defaultCursor); if (scroller == null) { return; } //make sure the mouse ended in a dragging event long durationSinceLastDrag = System.currentTimeMillis() - lastDragTime; if (durationSinceLastDrag > 20) { return; } //get average speed for last few drags pixelsPerMSX = 0; pixelsPerMSY = 0; int j = 0; for (int i = dragSpeeds.size() - 1; i >= 0 && i > dragSpeeds.size() - 6; i--, j++) { pixelsPerMSX += dragSpeeds.get(i).getX(); pixelsPerMSY += dragSpeeds.get(i).getY(); } pixelsPerMSX /= -(double) j; pixelsPerMSY /= -(double) j; //start the timer if (Math.abs(pixelsPerMSX) > 0 || Math.abs(pixelsPerMSY) > 0) { animationTimer = new Timer(animationSpeed, new ScrollAnimator()); animationTimer.start(); } } /** * Empty implementation */ public void mouseClicked(MouseEvent e) { } /** * MouseDragged implementation. Sets up timing and frame animation. */ public void mouseDragged(MouseEvent e) { if (scroller == null) { return; } final Point p = e.getPoint(); final int diffx = p.x - lastDragPoint.x; final int diffy = p.y - lastDragPoint.y; lastDragPoint = e.getPoint(); //scroll the x axis if ((scrollBarMask & DRAGABLE_HORIZONTAL_SCROLL_BAR) != 0) { getHorizontalScrollBar().setValue(getHorizontalScrollBar().getValue() - diffx); } //the Scrolling affects mouse locations - offset the last drag point to compensate lastDragPoint.x = lastDragPoint.x - diffx; //scroll the y axis if ((scrollBarMask & DRAGABLE_VERTICAL_SCROLL_BAR) != 0) { getVerticalScrollBar().setValue(getVerticalScrollBar().getValue() - diffy); } //the Scrolling affects mouse locations - offset the last drag point to compensate lastDragPoint.y = lastDragPoint.y - diffy; //add a drag speed dragSpeeds.add(new Point2D.Double((e.getPoint().x - lastDragPoint.x), (e.getPoint().y - lastDragPoint.y))); lastDragTime = System.currentTimeMillis(); } /** * Empty */ public void mouseMoved(MouseEvent e) { } /** * Private inner class which accomplishes the animation. * * @author Greg Cope */ private class ScrollAnimator implements ActionListener { /** * Performs the animation through the setting of the JScrollBar values. */ public void actionPerformed(ActionEvent e) { //damp the scrolling intensity pixelsPerMSX -= pixelsPerMSX * damping; pixelsPerMSY -= pixelsPerMSY * damping; //check to see if timer should stop. if (Math.abs(pixelsPerMSX) < 0.01 && Math.abs(pixelsPerMSY) < 0.01) { animationTimer.stop(); return; } //calculate new X value int nValX = getHorizontalScrollBar().getValue() + (int) (pixelsPerMSX * scrollingIntensity); int nValY = getVerticalScrollBar().getValue() + (int) (pixelsPerMSY * scrollingIntensity); //Deal with out of scroll bounds if (nValX <= 0) { nValX = 0; } else if (nValX >= getHorizontalScrollBar().getMaximum()) { nValX = getHorizontalScrollBar().getMaximum(); } if (nValY <= 0) { nValY = 0; } else if (nValY >= getVerticalScrollBar().getMaximum()) { nValY = getVerticalScrollBar().getMaximum(); } //Check again to see if timer should stop if ((nValX == 0 || nValX == getHorizontalScrollBar().getMaximum()) && Math.abs(pixelsPerMSY) < 1) { animationTimer.stop(); return; } if ((nValY == 0 || nValY == getVerticalScrollBar().getMaximum()) && Math.abs(pixelsPerMSX) < 1) { animationTimer.stop(); return; } //Set new values if ((scrollBarMask & DRAGABLE_HORIZONTAL_SCROLL_BAR) != 0) { getHorizontalScrollBar().setValue(nValX); } if ((scrollBarMask & DRAGABLE_VERTICAL_SCROLL_BAR) != 0) { getVerticalScrollBar().setValue(nValY); } } } /** * Utility to retrieve the Horizontal Scroll Bar. * * @return */ private JScrollBar getHorizontalScrollBar() { return scroller.getHorizontalScrollBar(); } /** * Utility to retrieve the Vertical Scroll Bar * * @return */ private JScrollBar getVerticalScrollBar() { return scroller.getVerticalScrollBar(); } /** * @param c * @return */ private Component getParentScroller(Component c) { Container parent = c.getParent(); if (parent != null && parent instanceof Component) { Component parentC = (Component) parent; if (parentC instanceof JScrollPane) { return parentC; } else { return getParentScroller(parentC); } } return null; } }