Cody Smith Cody Smith - 6 months ago 12
Java Question

Getting correct mouse position inside a JScrollPane

Here's my issue:

enter image description here

I'm basically creating a drop-down menu when an element in a JTree is right clicked, problem is that JTree is inside a JScrollPane and the more scrolled the pane becomes, the further off my popup menu goes.

The right click fires a MouseEvent which gets intercepted in the following code, the results of which is to create a new popup menu.

@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {

int row = tree.getClosestRowForLocation(e.getX(), e.getY());
tree.setSelectionRow(row);

TreePath path = tree.getPathForRow(row);

DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();

// If this object is a Search...
if(node.getUserObject().getClass() == Search.class) {
jp = new ItemEditPopUpMenu(tree, row, true);
} else {
jp = new ItemEditPopUpMenu(tree, row, false);
}

jp.show(this, e.getX(), e.getY());
}
}


As you can see, all I'm doing is grabbing the position from the MouseEvent and using it as the location in which the popup menu is to be created in the following line:

jp.show(this, e.getX(), e.getY());


Now it's been brought to my attention that this is a relative position to the element that sent the event, and that explains the issue, the JScrollPanel gets offset from the JFrame when it is scrolled, the question is how can I tell how far?

I just don't know and that's what I need help with. Thanks so much in advance!

Answer

I don't seem to have an issue.

If I use the view ports "view" as the reference to the popup, the popup shows exactly where the mouse clicked.

I can only imagine that you are not passing the reference to the tree to the popup...

enter image description here

I thought it was going to be a mess of point conversions, but as it turns out, it was simple as can be...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestScrollPane {

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

    public TestScrollPane() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new TestPane()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<Point> points;
        private JPopupMenu pm;

        public TestPane() {
            pm = new JPopupMenu();
            pm.add(new JLabel("Suprise"));
            points = new ArrayList<>(3);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    Point p = e.getPoint();
                    points.clear();
                    points.add(p);
                    // View port
                    Container parent = getParent();
                    Point pp = SwingUtilities.convertPoint(TestPane.this, p, parent);
                    points.add(pp);
                    // ScrollPane...
                    parent = parent.getParent();
                    Point ppp = SwingUtilities.convertPoint(TestPane.this, p, parent);
                    points.add(ppp);

                    pm.show(TestPane.this, p.x, p.y);

                    repaint();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(1000, 1000);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (points.size() == 3) {
                g2d.setColor(Color.RED);
                Point p = points.get(0);
                g2d.fillOval(p.x - 2, p.y - 2, 4, 4);

                g2d.setColor(Color.GREEN);
                p = points.get(1);
                g2d.fillOval(p.x - 2, p.y - 2, 4, 4);

                g2d.setColor(Color.BLUE);
                p = points.get(2);
                g2d.fillOval(p.x - 2, p.y - 2, 4, 4);
            }
            g2d.dispose();
        }
    }
}

Just to be sure I wasn't missing something, here's a version using a JTree

enter image description here

(You can ditch the custom cell rendering for testing, this was some scrap code I had laying around)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;

public class TestCustomTreeNode {

    public static void main(String args[]) {
        new TestCustomTreeNode();
    }
    private JPopupMenu pm;

    public TestCustomTreeNode() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                pm = new JPopupMenu();
                pm.add(new JLabel("Suprise"));

                JFrame f = new JFrame("JTree Sample");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                JPanel pnlMain = new JPanel(new BorderLayout());
                pnlMain.setBackground(Color.white);

                createTree(pnlMain);

                f.setContentPane(new JScrollPane(pnlMain));

                f.setSize(300, 200);
                f.setVisible(true);
            }
        });
    }

    private void createTree(JPanel pnlMain) {
        Employee bigBoss = new Employee(Employee.randomName(), true);
        Employee[] level1 = new Employee[5];
        bigBoss.employees = level1;

        for (int i = 0; i < level1.length; i++) {
            level1[i] = new Employee(Employee.randomName(), true);
        }


        for (int i = 0; i < level1.length; i++) {
            Employee employee = level1[i];
            if (employee.isBoss) {
                int count = 5;
                employee.employees = new Employee[count];

                for (int j = 0; j < employee.employees.length; j++) {
                    employee.employees[j] = new Employee(Employee.randomName(), false);
                }
            }
        }

        CustomTreeNode root = new CustomTreeNode(loadResource("/pirate.png"), bigBoss);
        root.setUserObject("Root");
        DefaultTreeModel model = new DefaultTreeModel(root);

        for (Employee employee : bigBoss.employees) {
            CustomTreeNode boss = new CustomTreeNode(loadResource("/angel.png"), employee);
            root.add(boss);
            if (employee.isBoss) {
                for (Employee employee1 : employee.employees) {
                    CustomTreeNode emp = new CustomTreeNode(loadResource("/devil.png"), employee1);
                    boss.add(emp);
                }
            }
        }

        JTree tree = new JTree(model);
        tree.setCellRenderer(new CustomeTreeCellRenderer());
        pnlMain.add(tree, BorderLayout.CENTER);

        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    pm.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

    }

    protected ImageIcon loadResource(String name) {

        ImageIcon image = null;
        try {
            image = new ImageIcon(ImageIO.read(getClass().getResource(name)));
            System.out.println(name + " - " + image);
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        return image;

    }

    public static class Employee {

        public String name;
        public int id;
        public boolean isBoss;
        public Employee[] employees;

        public Employee(String name, boolean isBoss) {
            this.name = name;
            this.isBoss = isBoss;
            this.id = new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE);
        }

        @Override
        public String toString() {
            return this.name;
        }

        static String randomName() {
            String chars = "abcdefghijklmnopqrstuvwxyz";
            StringBuilder builder = new StringBuilder();
            Random r = new Random(System.currentTimeMillis());
            int length = r.nextInt(10) + 1;
            for (int i = 0; i < length; i++) {
                builder.append(chars.charAt(r.nextInt(chars.length())));
            }

            return builder.toString();
        }
    }

    public class CustomTreeNode extends DefaultMutableTreeNode {

        /**
         * The icon which is displayed on the JTree object. open, close, leaf icon.
         */
        private ImageIcon icon;

        public CustomTreeNode(ImageIcon icon) {
            this.icon = icon;
        }

        public CustomTreeNode(ImageIcon icon, Object userObject) {
            super(userObject);
            this.icon = icon;
        }

        public CustomTreeNode(ImageIcon icon, Object userObject, boolean allowsChildren) {
            super(userObject, allowsChildren);
            this.icon = icon;
        }

        public ImageIcon getIcon() {
            return icon;
        }

        public void setIcon(ImageIcon icon) {
            this.icon = icon;
        }
    }

    class CustomeTreeCellRenderer extends DefaultTreeCellRenderer {

        public CustomeTreeCellRenderer() {
        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {

//            if (!leaf) {
            CustomTreeNode node = (CustomTreeNode) value;

            if (node.getIcon() != null) {
                System.out.println(node + " - " + node.getIcon());
                setClosedIcon(node.getIcon());
                setOpenIcon(node.getIcon());
                setLeafIcon(node.getIcon());
            } else {
                System.out.println(node + " - default");
                setClosedIcon(getDefaultClosedIcon());
                setLeafIcon(getDefaultLeafIcon());
                setOpenIcon(getDefaultOpenIcon());
            }
//            }

            super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

            return this;
        }
    }
}

nb: You might need to change the mouse event as the popup trigger can be different for different platforms, this works just fine on Windows

From this, I can only conclude, that the component reference you are passing to JPopupMenu#show is not the one that generated the mouse events.