17

I have a mouse listener. It has some code to respond to mouseUp and mouseDown events. This works correctly.

However, as soon as I add a DragSource, my mouseDown event is no longer delivered -- until I release the mouse button!

This is trivial to reproduce - below is a simple program which contains a plain shell with just a mouse listener and a drag listener. When I run this (on a Mac), and I press and hold the mouse button, nothing happens - but as soon as I release the mouse button, I instantly see both the mouse down and mouse up events delivered. If I comment out the drag source, then the mouse events are delivered the way they should be.

I've searched for others with similar problems, and the closest I've found to an explanation is this:

https://bugs.eclipse.org/bugs/show_bug.cgi?id=26605#c16 "If you hook drag detect, the operating system needs to eat mouse events until it determines that you have either dragged or not."

However, I don't understand why that's true -- why must the operating system eat mouse events to determine if I have a drag or not? The drag doesn't start until I have a mouse -move- event with the button pressed.

More importantly: Can anyone suggest a workaround? (I tried dynamically adding and removing my drag source when the mouse is pressed, but then I couldn't get drag & drop to function properly since it never saw the initial key press - and I can't find a way to programmatically initiate a drag.)

Here's the sample program:

    package swttest;

    import org.eclipse.swt.dnd.DND;
    import org.eclipse.swt.dnd.DragSource;
    import org.eclipse.swt.dnd.DragSourceEvent;
    import org.eclipse.swt.dnd.DragSourceListener;
    import org.eclipse.swt.events.MouseEvent;
    import org.eclipse.swt.events.MouseListener;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.swt.widgets.Shell;

    public class SwtTest {
        public static void main(String[] args) {
            final Display display = new Display();
            final Shell shell = new Shell(display);
            shell.addMouseListener(new MouseListener() {
                public void mouseUp(MouseEvent e) {
                    System.out.println("mouseUp");
                }

                public void mouseDown(MouseEvent e) {
                    System.out.println("mouseDown");
                }

                public void mouseDoubleClick(MouseEvent e) {
                    System.out.println("mouseDoubleClick");
                }
            });
            DragSourceListener dragListener = new DragSourceListener() {

                public void dragFinished(DragSourceEvent event) {
                    System.out.println("dragFinished");

                }

                public void dragSetData(DragSourceEvent event) {
                    System.out.println("dragSetData");

                }

                public void dragStart(DragSourceEvent event) {
                    System.out.println("dragStart");
                }
            };
            DragSource dragSource = new DragSource(shell, DND.DROP_COPY | DND.DROP_MOVE);
            dragSource.addDragListener(dragListener);
            shell.pack();
            shell.open();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch())
                    display.sleep();
            }
            display.dispose();
        }
    }
Tor Norbye
  • 9,062
  • 3
  • 29
  • 24
  • I've tried it and your code is working on Windows. Could be OS-specific bug – nanda Oct 13 '10 at 11:49
  • I've just tried this on Ubuntu 10.04 and it sort-of works. Press the left mouse button you get no event. Move the mouse you get `mouseDown` and `dragStart` *at the same time*. When you let go you get the `mouseUp` event. If you hold the mouse still and press the left button, you get a `mouseDown` after a noticeable 1-2 second delay. Either way, `mouseDown` is always seen before letting go of the mouse. Must be an SWT on Mac issue? – richq Oct 17 '10 at 14:18
  • Thank you both - it sounds like this is definitely platform specific. However, it sounds like it's pretty broken on Linux too -- a one second delay before mouseDown is delivered won't work since the reason I'm trying to switch some handling from mouseUp to mouseDown is to make the UI feel more responsive... – Tor Norbye Oct 18 '10 at 16:20
  • I was able to test this on Windows today (Vista, with the latest version of SWT) and I don't see what nanda sees - for me, it's broken in the same way that it is on Linux: The mouseDown event is not delivered immediately; it isn't held indefinitely like on the Mac, it's instead delivered after a second (but that seems to be because on Windows it considers holding the button for a second a drag, so after one second it publishes a dragStart event, which of course also triggers a mouseDown just like it does on the Mac.) – Tor Norbye Oct 19 '10 at 16:02
  • Life saver question! Thank you Tor! – RAY Aug 17 '11 at 08:15

2 Answers2

8

To answer your specific question about why this happens -- on Cocoa we don't consider a drag to have started until the mouse has moved a few pixels. This ensures against 'accidental' drags if you're sloppy with the clicks. On Linux and Win32 the window toolkit can do the drag detection. If you just hold down the button the detection times out and the mouse down is delivered. On Cocoa we have no time out, which is why nothing happens until the drag is detected or a mouse up happens.

That's a lot of detail, but the conclusion is that the behavior is inconsistent, and we should always be able to deliver the mouse down immediately, without waiting for the drag detection to complete.

I don't see a workaround, since this is happening before the Control sees the event.

See this bug which has patches for win32, gtk and cocoa SWT.

Scott K.
  • 1,761
  • 15
  • 26
  • Wow! A patch, for all platforms no less, within a day or two! Impressive! Thank you! – Tor Norbye Oct 22 '10 at 15:01
  • Apparently the delayed MouseDown during a drag is intentional. For those playing along at home, please watch the above-mentioned Eclipse bug for more details. – Scott K. Oct 22 '10 at 17:08
1

I had faced the same problem and found a solution. Once you attach a DragSource to your custom widget, the event loop will be blocked in that widget's mouse down hook and will eat mouse move events to detect a drag. (I've only looked into the GTK code of SWT to find this out, so it may work a little differently on other platforms, but my solution works on GTK, Win32 and Cocoa.) In my situation, I wasn't so much interested in detecting the mouse down event right when it happened, but I was interested in significantly reducing the drag detection delay, since the whole purpose of my Canvas implementation was for the user to drag stuff. To turn off the event loop blocking and built-in drag detection, all you have to do is:

setDragDetect(false);

In my code, I am doing this before attaching the DragSource. As you already pointed out, this will leave you with the problem that you can't initiate a drag anymore. But I have found a solution for that as well. Luckily, the drag event generation is pure Java and not platform specific in SWT (only the drag detection is). So you can just generate your own DragDetect event at a time when it is convenient for you. I have attached a MouseMoveListener to my Canvas, and it stores the last mouse position, the accumulated drag distance and whether or not it already generated a DragDetect event (among other useful things). This is the mouseMove() implementation:

public void mouseMove(MouseEvent e) {
    if (/* some condition that tell you are expecting a drag*/) {

        int deltaX = fLastMouseX - e.x;
        int deltaY = fLastMouseY - e.y;

        fDragDistance += deltaX * deltaX + deltaY * deltaY;

        if (!fDragEventGenerated && fDragDistance > 3) {
            fDragEventGenerated = true;

            // Create drag event and notify listeners.
            Event event = new Event();
            event.type = SWT.DragDetect;
            event.display = getDisplay();
            event.widget = /* your Canvas class */.this;
            event.button = e.button;
            event.stateMask = e.stateMask;
            event.time = e.time;
            event.x = e.x;
            event.y = e.y;
            if ((getStyle() & SWT.MIRRORED) != 0)
                event.x = getBounds().width - event.x;

            notifyListeners(SWT.DragDetect, event);
        }
    }

    fLastMouseX = e.x;
    fLastMouseY = e.y;
}

And that will replace the built-in, blocking drag detection for you.

stippi
  • 855
  • 8
  • 15