1

we are currently working on fixes for a project that has to be finished next week, so we are kinda running out of time (don't worry, all I want to say is that we do not have the time to toss away all our code and start again fresh...).

We have a JPanel (="page") with a bunch of other JPanels (which act as nodes of a graph) on it. The user can connect the nodes by either clicking on the first and then on the second node to connect or by dragging a line from one to another. But the last one is not working as we expected:

If we drag the connection line out of the node where it was started (e.g. to the page or any other node), the mouseDragged event we are using to create the line does not end on a mouseRelease. It works just fine if I release the mouse over the node where I started to drag, but I cant create any connections with that.

To be able to draw the connection to the page, we are dispatching the events until they reach the page where they will be handled.

Funny thing: everything works, except the fact that we have an endless chain of "mouse dragged" outputs if we put a System.out.println("mouse dragged"); in the code of mouseDragged(). Connecting itself works to.

So, my question is: how do I end a mouseDragged event if the mouseReleased occurs outside the component where the mouseDragged initially started? Is there a way to abort the mouseDragged event?

Is it possible/necessary to "fake" an interrupting mouseRelease event?

Hope there is a solution that will not make cook more coffee....

Greetings, Flo

EDIT: ok, got some code for you:

Interface implemented by the nodes and the page, makes things easier:

import java.awt.event.MouseEvent;

/**
 * Interface which can be used together with MouseEventListenerAdapter to
 * easily receive mouse events.
 *
 * @author flo
 */
public interface MouseEventsInterface {
    /**
     * Called when a mouse clicked event is received.
     *
     * @param e Triggered mouse event.
     */
    void onMouseClicked(MouseEvent e);
    /**
     * Called when a mouse dragged event is received.
     *
     * @param e Triggered mouse event.
     */
    void onMouseDragged(MouseEvent e);
    [and so on...]
    /**
     * Called when a mouse released event is received.
     *
     * @param e Triggered mouse event.
     */
    void onMouseReleased(MouseEvent e);
}

The Adapter to the events

/**
 * Class which forwards mouse events to a MouseEventsInterface which have
 * originally been targeted to a MouseInputAdapter.
 *
 * @author flo
 *
 * @param <M> The interface to which events are sent.
 */
public class MouseEventListenerAdapter<M extends MouseEventsInterface> extends MouseInputAdapter {
    /**
     * Interface which receives the events which are forwarded by this class.
     */
    private M mei;

    /**
     * Creates an adapter which forwards all events to the MouseEventsInterface.
     *
     * @param mei The receiver of the forwarded events.
     */
    public MouseEventListenerAdapter(final M mei) {
        this.mei = mei;
    }

    @Override
    public final void mousePressed(final MouseEvent e) {
        this.mei.onMousePressed(e);
    }
    @Override
    public final void mouseClicked(final MouseEvent e) {
        this.mei.onMouseClicked(e);
    }
    @Override
    public final void mouseDragged(final MouseEvent e) {
        this.mei.onMouseDragged(e);
    }
    @Override
    public final void mouseMoved(final MouseEvent e) {
        this.mei.onMouseMoved(e);
    }
    @Override
    public final void mouseReleased(final MouseEvent e) {
        this.mei.onMouseReleased(e);
    }

    /**
     * Returns the receiver of the forwarded {@link MouseEvent MouseEvents}.
     * @return the receiver of the forwarded {@link MouseEvent MouseEvents}.
     */
    protected final M getEventReceiver() {
        return this.mei;
    }
}

how we are dispatching the events, the first part is used if the event comes from another panel inside the node. The NodeConnectorMouseEvent is a regular MouseEvent with the ability to store all the components it has been dispachted from...

/**
 * Dispatch mouse event.
 *
 * @param e the e
 */
private void dispatchMouseEvent(final MouseEvent e) {
    if (e instanceof NodeConnectorMouseEvent) {
        ((NodeConnectorMouseEvent) e).addDispatchingComponent(this);
    } else {
        // f.e. moved over IOPort, PortPanel
        e.setSource(this);
    }

    this.getParent().dispatchEvent(e);
}

some of the page's code:

@Override
public final void onMouseDragged(final MouseEvent e) {
    System.out.println("Mouse: dragged");
    if (isDragGestureActive) {
        if (helperConnection != null && e instanceof NodeConnectorMouseEvent) {
            NodeConnectorMouseEvent evt = (NodeConnectorMouseEvent) e;
            if (containsIoPort(evt) != null && weAreDragging) {
                Point absPosition = calcAbsolutePosition(evt);

                // Discard drag events that would jump farther than 15 pixels,
                //but only if we actually weren't dragging
                if (weAreDragging
                        && (Math.abs(absPosition.x - lastDragPosition.x) < 15
                                && Math.abs(absPosition.y - lastDragPosition.y) < 15)) {
                    updateHelperConnection(absPosition);
                }

                lastDragPosition = absPosition;
            }
        } else if (e.getSource() instanceof EditorNode) {
            moveSelectedNodes((EditorNode) e.getSource(), e.getX() - grabPosition.x, e.getY() - grabPosition.y);
        } else {
            return;
        }
    } else {
        return;
    }
}
@Override
public final void onMouseReleased(final MouseEvent e) {
    System.out.println("Mouse: released");
    if (e instanceof NodeConnectorMouseEvent) {
        NodeConnectorMouseEvent evt = (NodeConnectorMouseEvent) e;
        IOPort port = containsIoPort(evt);
        if (port != null) {
            weAreDragging = false;
            lastDragPosition = new Point(0, 0);
            onPortSelected(port);
        }
    } else if (e.getSource() instanceof EditorNode) {
        setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    } else {
        finishHelperConnection(null);
    }
    isDragGestureActive = false;
}

There is a lot more than that but I guess that was the interesting part. Thanks for your help!

mKorbel
  • 109,525
  • 20
  • 134
  • 319
flo
  • 11
  • 1
  • 3
  • It should end as expected without special code. Are you sure that you're handling both MouseMotionListener and MouseListener events? It's kind of hard to say more without code... – Hovercraft Full Of Eels Feb 26 '12 at 01:00
  • 1
    if allowed, you might consider to decorate the parent of the nodes with a JLayer (jdk7) or JXLayer (jdk6) - vou would implement all the mouse tracking and line-painting in a custom LayerUI, that'll considerably simplify your logic. – kleopatra Feb 26 '12 at 13:49

3 Answers3

2

I'm not sure I understand your approach, but two things come to mind:

  • You can consume() an "event so that it will not be processed in the default manner by the source which originated it."

  • You can push() your own EventQueue and override dispatchEvent() to modify any event en route to its final destination. There's a related example here, and Global Event Dispatching is an excellent guide.

Addendum: Although not applicable to the immediate problem, there are other approaches to multiple selection of nodes:

  • GraphPanel uses mouse-drag or shift-click for multiple selections, and it uses right-click to see a context menu.

  • JGraph uses shift-click for multiple selections; com.mxgraph.examples.swing.GraphEditor is an example.

  • This example suggests how JInternalFrame might be used.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • `consume()` sounds promising, I was trying to play around with it, but it is actually not doing what I was thinking it does :( - introducing a boolean kill-switch in the `onMouseDragged(...)`: well.... it consumes the current event but there seem to be more in the queue that are still fired.... - saving the dragged event and consuming it in `onMouseReleased(..)` is just the same, it consumes one of the events out of the queue... I don't have any idea how I can prevent the events from being enqueued or how to "delete" events from the queue, if I don't need them anymore... – flo Feb 26 '12 at 09:46
  • I've added a link to *Global Event Dispatching*, which may help with deleting, and I've elaborated for reference. – trashgod Feb 26 '12 at 16:07
0

From what I noticed, when a drag is performed, MouseEvents are no longer dispatched to the listeners. Instead you can use a DragSourceListener, which has several useful methods. The one you are interested in is probably dragDropEnd, which is called when the drag operation ends, either successfully or not.

Andrei Vajna II
  • 4,642
  • 5
  • 35
  • 38
0

Some things I'm wondering about: 1. On your page's code, you have lastDragPosition = new Point(0, 0); Why? 2. Why don't you just use the mouseExited(MouseEvent e) method to simulate a break in the drag and just have a thread running that checks whether you already dragged or not, and if you already finished the dragging, connect the points

Avogadro
  • 353
  • 2
  • 11
  • 1.: there we are just setting back some variables on the end of the dragging process. We are displaying a line form the starting point of the drag to the current mouse position, thats where we need them. 2. mouseExit wouldn't work, I tried that, but sometimes it seems the mouse is faster than the nodes if moving one arround, this will course an unwanted interrupt of the drag... – flo Feb 26 '12 at 09:18