Component stops firing mouse drag events when added to a window

267 Views Asked by At

I have a container in which components can be dragged. The problem I encountered is whenever I picked up a component and it was automatically added to a window, the component would stop firing drag events even if the mouse was still dragging on the handle. Then the next time I would click and drag on the handle (now in the floating window) it would continue dragging. Here's some basic code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Box.Filler;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;

/**
 *
 */
@SuppressWarnings("serial")
public class DraggableDemo extends JPanel {

    private class DraggablePanel extends JPanel {

        private class MyMouseAdapter extends MouseAdapter {
            private DraggablePanel floater;
            private Point dragOffset;

            @Override
            public void mouseDragged(MouseEvent e) {
                if (floater == null) {
                    startDragging(e);
                }

                Point transformedEventPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), owner);
                updateWindowLocation(transformedEventPoint);

            }

            private void startDragging(MouseEvent e) {
                Component component = e.getComponent();
                floater = (DraggablePanel) SwingUtilities.getAncestorOfClass(DraggablePanel.class, component);
                Point floaterAbsoluteLocation = SwingUtilities.convertPoint(floater.getParent(), floater.getLocation(), owner);
                Point transformedEventPoint = SwingUtilities.convertPoint(component, e.getPoint(), owner);
                // designate a drag offset so the component's corner doesn't teleport to mouse location
                dragOffset = new Point(transformedEventPoint.x - floaterAbsoluteLocation.x, transformedEventPoint.y - floaterAbsoluteLocation.y);

                swapComponents(getFiller(), floater);

                // place the floating component in a window
                window.add(floater);
                window.pack();
                floater.setBorder(new LineBorder(Color.YELLOW));
                updateWindowLocation(transformedEventPoint);
                window.setVisible(true);
            }

            private void updateWindowLocation(Point point) {
                Point p = new Point(point.x - dragOffset.x, point.y - dragOffset.y);
                SwingUtilities.convertPointToScreen(p, owner);
                window.setLocation(p);
            }

            private JComponent getFiller() {
                Dimension dimension = floater.getSize();
                JComponent filler = (JComponent) Box.createRigidArea(dimension);

                // border
                Border dashedBorder = BorderFactory.createDashedBorder(Color.gray, 3f, 3f, 2f, true);
                filler.setBorder(dashedBorder);
                filler.setOpaque(true);

                return filler;
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                Point transformedEventPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), owner);
                Component compDroppedOn = SwingUtilities.getDeepestComponentAt(owner, transformedEventPoint.x, transformedEventPoint.y);
                if (compDroppedOn instanceof Filler) {
                    window.remove(floater);
                    swapComponents(floater, compDroppedOn);
                    window.setVisible(false);
                    floater = null;
                }
            }
        }

        private JWindow window;
        private MyMouseAdapter mouseAdapter;
        private DraggableDemo owner;

        DraggablePanel(DraggableDemo owner) {
            this.owner = owner;
            JPanel smallPanel = new JPanel();
            smallPanel.setPreferredSize(new Dimension(200, 100));
            smallPanel.setBackground(Color.green);

            JPanel bigPanel = new JPanel();
            bigPanel.setPreferredSize(new Dimension(200, 400));
            bigPanel.setBackground(Color.red);

            setLayout(new BorderLayout());
            add(smallPanel, BorderLayout.NORTH);
            add(bigPanel, BorderLayout.CENTER);

            setBorder(new LineBorder(Color.blue));

            mouseAdapter = new MyMouseAdapter();
            smallPanel.addMouseListener(mouseAdapter);
            smallPanel.addMouseMotionListener(mouseAdapter);
            owner.addMouseListener(mouseAdapter);
            owner.addMouseMotionListener(mouseAdapter);

            window = new JWindow();
            window.setAlwaysOnTop(true);
        }
    }

    private GridBagConstraints gbc;

    /**
     * 
     */
    public DraggableDemo() {
        setLayout(new GridBagLayout());
        gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1;

        for (int i = 0; i < 2; i++) {
            add(new DraggablePanel(this), gbc);
        }
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        DraggableDemo newContentPane = new DraggableDemo();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    /**
     */
    private void swapComponents(Component toAdd, Component toRemove) {
        Container parent = toRemove.getParent();
        int index = parent.getComponentZOrder(toRemove);

        parent.remove(toRemove);
        parent.add(toAdd, gbc, index);

        revalidate();
        repaint();

    }

}

What I've tried:

The original container holds a few components per column, each firing different mouse events.

Initially I registered the mouse listener to the main container and got and moved the component based on coordinates, but that didn't satisfy the condition of mouse entering / exiting smaller components firing events themselves.

After this I attempted to register multiple mouse listeners, each doing its own thing, but I've learned that these would eat up events that would happen in the hierarchy.

Finally, I decided to register a single listener to every single component I needed to register to and based on the component instance returned from events, I would delegate these events to an appropriate mouse 'adapter'.

How can I fix the component in order to drag correctly when it is picked up? Like I've said, I can't register only to the main container, because then I wouldn't have access to events fired by smaller components, and I can't register multiple listeners because events would then be eaten up by the first one that fired.

1

There are 1 best solutions below

1
On

whenever I picked up a component and it was automatically added to a window, the component would stop firing drag events

It is not just the component that stops firing the drag event. It appear that no drag event is generated for any component:

In your createAndShowGUI() method, after the frame is visible, I added:

long eventMask = AWTEvent.MOUSE_MOTION_EVENT_MASK + AWTEvent.MOUSE_EVENT_MASK;

Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener()
{
    public void eventDispatched(AWTEvent e)
    {
        System.out.println(e.getID());
    }
}, eventMask);

Which should display all mouse events generated for any component. However, once the Window is displayed, no further events are generated for any component, confirming your problem.

Next I removed the above code and replaced it with a custom EventQueue to simply display every event that is generated:

EventQueue queue = new EventQueue()
{
    protected void dispatchEvent(AWTEvent event)
    {
        System.out.println(event);
        super.dispatchEvent(event);
    }
};

Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);

Now I do see all the mouse drag events.

So maybe you can create a custom EventQueue that handles mouse events that are generated on your DraggablePanel?