matthewh86
matthewh86

Reputation: 314

Java/Swing JViewport jumps around when mouse dragging

I've attempted to integrate http://java-swing-tips.blogspot.co.uk/2008/06/mouse-drag-auto-scrolling.html into my program, but it seems to misbehave and I'm not sure what's happening.

The gameGrid label is actually a separate class with click events, which is why I have the mouse listeners attempting to call the parent mouseListeners (previously the grid mouseListeners were overriding the scrollable viewport mouseListeners and so it wouldn't drag), but the JPanel with some shapes shows the same behaviour.

package uk.co.mhayward.games.factions;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;

public class WhyYouJump extends JFrame {

    public static void main(String[] args) {
        new WhyYouJump();
    }

    private static final long serialVersionUID = 3697837493691218641L;

    private final JPanel gameGridPanel;

    private final JScrollPane gridScrollPane;

    private final JViewport gridScrollPaneViewport;

    private final JTextArea jTextArea = new JTextArea();

    public WhyYouJump() throws HeadlessException {
        super();

        gameGridPanel = new JPanel() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Graphics2D g2d = (Graphics2D) g;

                Polygon shape1 = new Polygon();
                Polygon shape2 = new Polygon();
                Polygon shape3 = new Polygon();

                for (int i = 0; i < 6; i++) {
                    shape1.addPoint((int) (200 + 50 * Math.cos(i * 2 * Math.PI / 6)),
                            (int) (200 + 50 * Math.sin(i * 2 * Math.PI / 6)));
                    shape2.addPoint((int) (400 + 50 * Math.cos(i * 2 * Math.PI / 6)),
                            (int) (200 + 50 * Math.sin(i * 2 * Math.PI / 6)));
                    shape3.addPoint((int) (100 + 50 * Math.cos(i * 2 * Math.PI / 6)),
                            (int) (100 + 50 * Math.sin(i * 2 * Math.PI / 6)));
                }

                g2d.setStroke(new BasicStroke(3));
                g2d.setPaint(Color.WHITE);
                g2d.fill(shape1);
                g2d.fill(shape2);
                g2d.fill(shape3);
                g2d.setPaint(Color.BLACK);
                g2d.draw(shape1);
                g2d.draw(shape2);
                g2d.draw(shape3);
            }
        };

        gameGridPanel.setPreferredSize(new Dimension(1440, 900));
        gameGridPanel.setSize(new Dimension(1440, 900));

        gridScrollPane = new JScrollPane(gameGridPanel);

        gameGridPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                for (MouseListener l : gameGridPanel.getParent().getMouseListeners()) {
                    e.setSource(gridScrollPaneViewport);
                    l.mousePressed(e);
                }
                jTextArea.append(e.getPoint().toString() + "\n");
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                for (MouseListener l : gameGridPanel.getParent().getMouseListeners()) {
                    e.setSource(gridScrollPaneViewport);
                    l.mouseReleased(e);
                }
            }

            @Override
            public void mouseExited(MouseEvent e) {
                for (MouseListener l : gameGridPanel.getParent().getMouseListeners()) {
                    e.setSource(gridScrollPaneViewport);
                    l.mouseExited(e);
                }
            }
        });

        gameGridPanel.addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                for (MouseMotionListener l : gameGridPanel.getParent().getMouseMotionListeners()) {
                    e.setSource(gridScrollPaneViewport);
                    l.mouseDragged(e);
                }
            }
        });

        gameGridPanel.addHierarchyListener(new HierarchyListener() {

            public void hierarchyChanged(HierarchyEvent e) {
                for (HierarchyListener l : gameGridPanel.getParent().getHierarchyListeners()) {
                    e.setSource(gridScrollPaneViewport);
                    l.hierarchyChanged(e);
                }
            }
        });

        this.setLayout(new BorderLayout());
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        gridScrollPane.setPreferredSize(new Dimension(500, 300));
        gridScrollPane.getVerticalScrollBar().setUnitIncrement(16);

        ViewportDragScrollListener l = new ViewportDragScrollListener(gameGridPanel, false);
        gridScrollPaneViewport = gridScrollPane.getViewport();
        gridScrollPaneViewport.addMouseMotionListener(l);
        gridScrollPaneViewport.addMouseListener(l);
        gridScrollPaneViewport.addHierarchyListener(l);

        this.add("Center", gridScrollPane);

        JScrollPane textScrollPane = new JScrollPane(jTextArea);
        textScrollPane.setPreferredSize(new Dimension(140, 180));
        this.add("South", textScrollPane);

        this.pack();
        this.setLocation(100, 100);
        this.setVisible(true);
    }

    class ViewportDragScrollListener extends MouseAdapter implements HierarchyListener {
        private static final int SPEED = 4;
        private static final int DELAY = 10;
        private final Cursor dc;
        private final Cursor hc = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
        private final javax.swing.Timer scroller;
        private final JComponent label;
        private final Point startPt = new Point();
        private final Point move = new Point();
        private boolean autoScroll = false;

        public ViewportDragScrollListener(JComponent comp, boolean autoScroll) {
            this.label = comp;
            this.autoScroll = autoScroll;
            this.dc = comp.getCursor();
            this.scroller = new javax.swing.Timer(DELAY, new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    JViewport vport = (JViewport) label.getParent();
                    Point vp = vport.getViewPosition();
                    vp.translate(move.x, move.y);
                    label.scrollRectToVisible(new Rectangle(vp, vport.getSize()));
                }
            });
        }

        public void hierarchyChanged(HierarchyEvent e) {
            JComponent c = (JComponent) e.getSource();
            if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0 && !c.isDisplayable() && autoScroll) {
                scroller.stop();
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            JViewport vport = (JViewport) e.getSource();
            Point pt = e.getPoint();
            int dx = startPt.x - pt.x;
            int dy = startPt.y - pt.y;
            Point vp = vport.getViewPosition();
            vp.translate(dx, dy);
            label.scrollRectToVisible(new Rectangle(vp, vport.getSize()));
            move.setLocation(SPEED * dx, SPEED * dy);
            startPt.setLocation(pt);
        }

        @Override
        public void mousePressed(MouseEvent e) {
            ((JComponent) e.getSource()).setCursor(hc); //label.setCursor(hc);
            startPt.setLocation(e.getPoint());
            move.setLocation(0, 0);
            if (autoScroll) {
                scroller.stop();
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            ((JComponent) e.getSource()).setCursor(dc); //label.setCursor(dc);
            if (autoScroll) {
                scroller.start();
            }
        }

        @Override
        public void mouseExited(MouseEvent e) {
            ((JComponent) e.getSource()).setCursor(dc); //label.setCursor(dc);
            move.setLocation(0, 0);
            if (autoScroll) {
                scroller.stop();
            }
        }
    }
}

Upvotes: 0

Views: 1229

Answers (2)

aterai
aterai

Reputation: 9808

One way, using SwingUtilities.convertMouseEvent(gamePanel,mouseEvent,viewport): (Edit: As Guillaume Polet already says)

MouseAdapter convertMouseEventListener = new MouseAdapter() {
  private void dispatchEvent(MouseEvent e) {
    JComponent c = (JComponent)e.getComponent();
    JComponent p = (JComponent)e.getComponent().getParent();
    p.dispatchEvent(SwingUtilities.convertMouseEvent(c,e,p));
  }
  @Override public void mouseDragged(MouseEvent e)  { dispatchEvent(e); }
  @Override public void mouseClicked(MouseEvent e)  { dispatchEvent(e); }
  @Override public void mouseEntered(MouseEvent e)  { dispatchEvent(e); }
  @Override public void mouseExited(MouseEvent e)   { dispatchEvent(e); }
  @Override public void mousePressed(MouseEvent e)  {
    jTextArea.append(e.getPoint().toString() + "\n");
    dispatchEvent(e);
  }
  @Override public void mouseReleased(MouseEvent e) { dispatchEvent(e); }
};
gameGridPanel.addMouseMotionListener(convertMouseEventListener);
gameGridPanel.addMouseListener(convertMouseEventListener);

Another, If gameGridPanel has its own MouseListeners, I sugest to use ComponentDragScrollListener instead of ViewportDragScrollListener:

ComponentDragScrollListener l = new ComponentDragScrollListener(gameGridPanel);
gameGridPanel.addMouseMotionListener(l);
gameGridPanel.addMouseListener(l);
gameGridPanel.addHierarchyListener(l);

Upvotes: 2

Guillaume Polet
Guillaume Polet

Reputation: 47608

I am not sure what this is precisely doing but I can spot at least one thing: mousePressed mouseReleased and mouseExited are never invoked in your class ViewportDragScrollListener because you don't forward the events. After looking at the article, they don't use such technique and I think this might also be one part of your problems.

Consider adding the mouse listeners directly on the correct components instead of forwarding events like you do.

EDIT:

When you forward the event, you modify the source, but the point is still in the original component coordinate. Consider creating a new event instead with the following code:

e = SwingUtilities.convertMouseEvent((Component) e.getSource(), e, gridScrollPaneViewport);

Upvotes: 2

Related Questions