0

I'm new to swing, and i have the following problem:

I am using the openslide-java library to view whole slide images. There is a class called OpenSlideView that extends JPanel, and its purpose is to view the images. This class has the capability of zooming-in, out and navigating through an image using the mouse, as it utilizes its own mouse listener. I am overlaying two OpenSlideView objects in a JFrame, where one is visible and the other is not, and i want to be able to perform mouse actions on both of them at the same time, even if one of them is not visible. Plainly put, i want the not-visible JPanel to track down the mouse.

Is something like that possible? Even for the JPanel class and not an extension of it?

Thanks in advance.

P.S: I am attaching the code of the method that handles the mouse events for OpenSlideView objects, in case it helps.

   private void registerEventHandlers() {
    // mouse wheel
    addMouseWheelListener(new MouseWheelListener() {
        public void mouseWheelMoved(MouseWheelEvent e) {
            double ds1 = zoomHelper(OpenSlideView.this, e.getX(), e.getY(),
                    e.getWheelRotation());
            double ds2 = zoomHelper(otherView, e.getX(), e.getY(), e
                    .getWheelRotation());
            zoomHelper2(OpenSlideView.this, ds1, e.getX(), e.getY());
            zoomHelper2(otherView, ds2, e.getX(), e.getY());
            zoomHelper3(OpenSlideView.this, ds1);
            zoomHelper3(otherView, ds2);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    // mouse drag
    MouseAdapter ma = new MouseAdapter() {
        private SelectionMode selectionMode;

        private int oldX;

        private int oldY;

        private int slideStartX;

        private int slideStartY;

        private Path2D.Double freehandPath;

        @Override
        public void mousePressed(MouseEvent e) {
            // System.out.println(e);

            requestFocusInWindow();

            if (!SwingUtilities.isLeftMouseButton(e)) {
                return;
            }

            final int ellipseMask = MouseEvent.CTRL_DOWN_MASK
                    | MouseEvent.SHIFT_DOWN_MASK;
            final int freehandMask = MouseEvent.CTRL_DOWN_MASK;
            final int rectMask = MouseEvent.SHIFT_DOWN_MASK;

            if ((e.getModifiersEx() & ellipseMask) == ellipseMask) {
                selectionMode = SelectionMode.ELLIPSE;
            } else if ((e.getModifiersEx() & freehandMask) == freehandMask) {
                selectionMode = SelectionMode.FREEHAND;
            } else if ((e.getModifiersEx() & rectMask) == rectMask) {
                selectionMode = SelectionMode.RECT;
            } else {
                selectionMode = SelectionMode.NONE;
            }

            oldX = e.getX();
            oldY = e.getY();

            double ds = getDownsample();
            slideStartX = (int) ((oldX + viewPosition.x) * ds);
            slideStartY = (int) ((oldY + viewPosition.y) * ds);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) {
                return;
            }

            int relX = oldX - e.getX();
            int relY = oldY - e.getY();

            double ds = getDownsample();
            int dx = slideStartX;
            int dy = slideStartY;
            int dw = (int) ((e.getX() + viewPosition.x) * ds) - dx;
            int dh = (int) ((e.getY() + viewPosition.y) * ds) - dy;

            if (dw < 0) {
                dx += dw;
                dw = -dw;
            }
            if (dh < 0) {
                dy += dh;
                dh = -dh;
            }

            switch (selectionMode) {
            case NONE:
                translateHelper(OpenSlideView.this, relX, relY);
                translateHelper(otherView, relX, relY);
                repaintHelper(OpenSlideView.this);
                repaintHelper(otherView);
                break;

            case RECT:
                selectionBeingDrawn = new Rectangle(dx, dy, dw, dh);
                // System.out.println(selection);
                repaint();
                break;

            case FREEHAND:
                if (selectionBeingDrawn == null) {
                    // new selection
                    freehandPath = new Path2D.Double();
                    selectionBeingDrawn = freehandPath;

                    freehandPath.moveTo(slideStartX, slideStartY);
                }

                freehandPath.lineTo((e.getX() + viewPosition.x) * ds, (e
                        .getY() + viewPosition.y)
                        * ds);

                repaint();
                break;

            case ELLIPSE:
                selectionBeingDrawn = new Ellipse2D.Double(dx, dy, dw, dh);

                repaint();
                break;
            }
            oldX = e.getX();
            oldY = e.getY();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (selectionMode == SelectionMode.FREEHAND) {
                freehandPath.closePath();
            }
            selectionMode = SelectionMode.NONE;

            if (selectionBeingDrawn != null) {
                Rectangle bb = selectionBeingDrawn.getBounds();
                if (bb.height != 0 && bb.width != 0) {
                    selections.add(new DefaultAnnotation(selectionBeingDrawn));
                    selectionBeingDrawn = null;
                }
            }
            repaint();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            selectionsVisibleHelper(OpenSlideView.this, true);
            selectionsVisibleHelper(otherView, true);
        }

        @Override
        public void mouseExited(MouseEvent e) {
            selectionsVisibleHelper(OpenSlideView.this, false);
            selectionsVisibleHelper(otherView, false);
        }
    };
    addMouseListener(ma);
    addMouseMotionListener(ma);

    // keyboard
    InputMap inputMap = new InputMap();
    ActionMap actionMap = new ActionMap();

    inputMap.put(KeyStroke.getKeyStroke("SPACE"), "center");
    actionMap.put("center", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            centerHelper(OpenSlideView.this);
            centerHelper(otherView);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("UP"), "scroll up");
    inputMap.put(KeyStroke.getKeyStroke("W"), "scroll up");
    actionMap.put("scroll up", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            translateHelper(OpenSlideView.this, 0, -KEYBOARD_SCROLL_AMOUNT);
            translateHelper(otherView, 0, -KEYBOARD_SCROLL_AMOUNT);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("DOWN"), "scroll down");
    inputMap.put(KeyStroke.getKeyStroke("S"), "scroll down");
    actionMap.put("scroll down", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            translateHelper(OpenSlideView.this, 0, KEYBOARD_SCROLL_AMOUNT);
            translateHelper(otherView, 0, KEYBOARD_SCROLL_AMOUNT);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("LEFT"), "scroll left");
    inputMap.put(KeyStroke.getKeyStroke("A"), "scroll left");
    actionMap.put("scroll left", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            translateHelper(OpenSlideView.this, -KEYBOARD_SCROLL_AMOUNT, 0);
            translateHelper(otherView, -KEYBOARD_SCROLL_AMOUNT, 0);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("RIGHT"), "scroll right");
    inputMap.put(KeyStroke.getKeyStroke("D"), "scroll right");
    actionMap.put("scroll right", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            translateHelper(OpenSlideView.this, KEYBOARD_SCROLL_AMOUNT, 0);
            translateHelper(otherView, KEYBOARD_SCROLL_AMOUNT, 0);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("L"), "rotate left");
    actionMap.put("rotate left", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {

        }
    });

    inputMap.put(KeyStroke.getKeyStroke("R"), "rotate right");
    actionMap.put("rotate right", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {

        }
    });

    inputMap.put(KeyStroke.getKeyStroke("PLUS"), "zoom in");
    inputMap.put(KeyStroke.getKeyStroke("EQUALS"), "zoom in");
    actionMap.put("zoom in", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            double d1 = zoomHelper(OpenSlideView.this, -1);
            double d2 = zoomHelper(otherView, -1);
            zoomHelper2(OpenSlideView.this, d1);
            zoomHelper2(otherView, d2);
            zoomHelper3(OpenSlideView.this, d1);
            zoomHelper3(otherView, d2);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("MINUS"), "zoom out");
    actionMap.put("zoom out", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            double d1 = zoomHelper(OpenSlideView.this, 1);
            double d2 = zoomHelper(otherView, 1);
            zoomHelper2(OpenSlideView.this, d1);
            zoomHelper2(otherView, d2);
            zoomHelper3(OpenSlideView.this, d1);
            zoomHelper3(otherView, d2);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("Z"), "zoom to fit");
    actionMap.put("zoom to fit", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            // System.out.println("zoom");
            zoomToFit();
            centerSlidePrivate();
            paintBackingStore();
            repaint();
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("1"), "zoom to 1:1");
    actionMap.put("zoom to 1:1", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            // System.out.println("zoom 1:1");
            double d1 = zoomHelper(OpenSlideView.this, Integer.MIN_VALUE);
            double d2 = zoomHelper(otherView, Integer.MIN_VALUE);
            zoomHelper2(OpenSlideView.this, d1);
            zoomHelper2(otherView, d2);
            zoomHelper3(OpenSlideView.this, d1);
            zoomHelper3(otherView, d2);
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    inputMap.put(KeyStroke.getKeyStroke("BACK_QUOTE"), "toggle pins");
    actionMap.put("toggle pins", new AbstractAction() {
        public void actionPerformed(ActionEvent e) {
            selectionsAsPins = !selectionsAsPins;
            if (otherView != null) {
                otherView.selectionsAsPins = !otherView.selectionsAsPins;
            }
            repaintHelper(OpenSlideView.this);
            repaintHelper(otherView);
        }
    });

    // install as parents
    InputMap oldInputMap = getInputMap();
    ActionMap oldActionMap = getActionMap();
    inputMap.setParent(oldInputMap.getParent());
    oldInputMap.setParent(inputMap);
    actionMap.setParent(oldActionMap.getParent());
    oldActionMap.setParent(actionMap);
}
MrAr0s
  • 13
  • 5

2 Answers2

1

i want to be able to perform mouse actions on both of them at the same time

Doesn't make any sense. If a component is not visible why would you want to do that. Maybe you need to have your component keep its current state. So when you change the state of the top component you update the state of the hidden component. If the hidden component ever becomes visible it will automatically be painted in its current state.

Plainly put, i want the not-visible JPanel to track down the mouse.

An event is only dispatched to a single component.

However, you could use the Component.dispatchEvent(...) method to dispatch a new event to a separate component.

camickr
  • 321,443
  • 19
  • 166
  • 288
0

Yes, you can do this, but you'll need a somewhat different design.

First, take note of the API docs's characterization of MouseEvents:

An event which indicates that a mouse action occurred in a component. A mouse action is considered to occur in a particular component if and only if the mouse cursor is over the unobscured part of the component's bounds when the action happens. For lightweight components, such as Swing's components, mouse events are only dispatched to the component if the mouse event type has been enabled on the component. [...] If the mouse event type has not been enabled on the component, the corresponding mouse events are dispatched to the first ancestor that has enabled the mouse event type.

(Emphasis added.)

Thus, when one of your panels is behind another -- and thus obscured -- mouse events cannot occur in the obscured portion. Also, although the docs do not say it in so many words, what you should take from that is that MouseEvents are delivered to the component in which they occur itself, or to its nearest ancestor in the containment tree that accepts mouse events. Swing will not dispatch events to components, such as the obscured panel, that are not containment ancestors of the one in which the event occurred.

How, then, can you make sibling or cousin components respond to the same mouse events? There are at least two alternatives:

  1. Register the various mouse listeners on their nearest shared ancestor container instead of on the panels themselves. Make sure the panels are configured to not receive mouse events themselves. Have the listener registered on the ancestor notify the panels when events occur.

  2. Use a single listener object that knows about all the panels. Register that one object on each panel, and have it take appropriate action for each panel.

You'll note that those are not actually much different. The common theme is that exactly one listener will receive the event from Swing, and it will direct the appropriate panels to make the needed response.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157