How to cancel drag-and-drop with ESC key?

Apologies if this is obvious but I couldn't find it anywhere.  How can I allow the user to press ESC to cancel a drag-and-drop operation (with DragAndDropContainer and DragAndDropTarget)?

1 Like

Coincidentally, I just hit this same requirement for tracktion, so will figure something out v. soon!

Is there a way to achieve this behavior currently?

It works already doesn’t it?
The only time I’ve not seen it work is with the standalone target for a plugin, where you need to have the file over the app window for Escape to work (on macOS 10.12 at least).

Not sure about the file drag and drop, but we’re using DragAndDropContainer and DragAndDropTarget for re-ordering UI items and there’s no way to cancel a drag once you start it (without just dropping the item back in the same location or off to the side)

1 Like

Could you listen for an esc keypress and use that to toggle what the result of isInterestedInDragSource would be?

I havre the same problem. Using the Escape key to cancel a drag needs to be implemented as part of the framework. It is default behaviour that just must be there. Imaging you start a drag accidentally and all areas you could drop it at have a meaning and perform an an operation.

I have the same problem. Using the Escape key to cancel a drag needs to be implemented as part of the framework. It is default behaviour that just must be there. Imaging you start a drag accidentally and all areas you could drop it at have a meaning and perform an an operation.

4 Likes

There is one tricky workaround to cancel a drag when using DragAndDropContainer and DragAndDropTarget: generate a mouse up event programatically when some key is pressed (for example Esc). On Windows there is SendInput function (I did not implement this workaround on Mac but should be something similar). You have also to inform your components to ignore itemDropped otherwise this fake mouse up event will cause a regular drop.

#include <windows.h>
#include <iostream>

void simMouseLeftUp()
{
	INPUT Input = { 0 };
	Input.type = INPUT_MOUSE;
	Input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
	SendInput(1, &Input, sizeof(INPUT));
};

That’s why it needs to be handled internally. The OS does it this way.

1 Like

Has this been implemented yet?

I’m on JUCE 5.4.4 and it seems that drag operations cannot be aborted with ESC.

If adding that appears to be too much of a breaking change, at least allow it as an opt-in solution when creating the DragAndDropTarget and/or when initiating the drag gesture with startDragging()

Answering my own question: no, it has not been implemented even in the most recent version of JUCE.

To check that I have switched to the current develop tip and fired the JUCE DemoRunner.
The WidgetsDemo has a “Drag and Drop” tab with a ListBox that shows the same exact issue: any drag operation initiated on it cannot be aborted with the ESC key.

BUT

Looking at the code in juce_DragAndDropContainer.cpp, there is in fact this code already in place:

bool keyPressed (const KeyPress& key) override
{
    if (key == KeyPress::escapeKey)
    {
        dismissWithAnimation (true);
        deleteSelf();
        return true;
    }

    return false;
}

So it seems that at a certain point in time, the ESC key was actually supposed to do its job.
I’ll keep investigating…

Ok, first issue I see is that the DragImageComponent that should handle the keyPressed(), returns false from its getWantsKeyboardFocus() method.

So, in its constructor I added:

    setInterceptsMouseClicks (false, false);
    setAlwaysOnTop (true);
    setWantsKeyboardFocus (true);    // < This
}

Then, just to make sure that the component has keyboard focus while being dragged, I’ve added the following to timerCallback()

void timerCallback() override
{
    if (isShowing() && ! hasKeyboardFocus (false))
        grabKeyboardFocus ();

It may seem overkill to repeat it over and over, but for now it’ll do.

Surprisingly, this still doesn’t solve the issue and the keystroke still isn’t triggering keyPressed() at all.

To understand why, I added an assertion right after grabbing the focus, to check whether the component actually got the focus as intended:

void timerCallback() override
{
    if (isShowing() && ! hasKeyboardFocus (false))
    {
        grabKeyboardFocus ();
        jassert (hasKeyboardFocus (false)); // < triggered if focus is not obtained for some reason
    }

Launched the demo again and the assertion is hit as soon as I start the drag operation. There’s something preventing the DragImageComponent from retaining the focus that it is trying to grab.

When ListBox starts a drag operation, it does so by allowing the drag to extend beyond the boundaries of the DragAndDropContainer, for dropping on an external target.
That behavior is controlled by the last argument of startDragAndDrop() (juce_ListBox.cpp, line 128),

owner.startDragAndDrop (e, rowsToDrag, dragDescription, true);

When it is true, the DragImageComponent is added to the desktop to allow being dragged externally.
I suspect that addition to the desktop causes some issue with focus, so I temporarly change that to:

owner.startDragAndDrop (e, rowsToDrag, dragDescription, false);

And in those conditions the ESC key finally does its job.

Now I wonder if all my previous changes to DragImageComponent were unnecessary, but it turns out that if I revert them, the ESC key stops working again, so they are also required after all.

Unfortunately, the ListBox code cannot be patched as I did, because it has its own very valid reason to allow external drops.

I hope some ComponentPeer magic can be done to allow DragImageComponent to retain its focus even when it is a desktop component.

I’m on macOS (Catalina) and I’m not fluent enough it its native programming to have an idea about that.

3 Likes

We are trying to accomplish the same thing. We tried adding a DragAndDropContainer::cancelAllDragOperations to JUCE that calls DragAndDropContainer::DragImageComponent::dismissWithAnimation for all active drags.

At first it looked at that this would work, however if you call dragAndDropContainer->startDragging in a Component::mouseDrag method it will restart the dragging operation when you move your mouse again.

It would be great if this could be handled internally as i don’t seem to be able to find a way to get this to work. Indeed as suggested above somehow a mouseUp should be generated so consecutive mouseDrag callbacks won’t be fired.

Maybe someone from the JUCE team can help out? Did you guys finally manage to build this into Tracktion?

I found this in juce_win32_DragAndDrop, so somehow this should be possible. If we can only let the JuceDropSource know the escape key has been pressed.

namespace DragAndDropHelpers
{
    //==============================================================================
    struct JuceDropSource   : public ComBaseClassHelper<IDropSource>
    {
        JuceDropSource() {}

        JUCE_COMRESULT QueryContinueDrag (BOOL escapePressed, DWORD keys) override
        {
            if (escapePressed)
                return DRAGDROP_S_CANCEL;

            if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0)
                return DRAGDROP_S_DROP;

            return S_OK;
        }

        JUCE_COMRESULT GiveFeedback (DWORD) override
        {
            return DRAGDROP_S_USEDEFAULTCURSORS;
        }
    };

Surprised this still isn’t in!

So, I added a call cancelDragging() to the container to set a flag so itemDropped() was not called in mouseUp().

Seems quite simple, or am I missing something as to why this can’t simply be added to the codebase?

Thanks

3 Likes

There’s a fix for this issue out on develop

4 Likes

great, will check it out

Is there anyway to detect the DragImageComponent? I have code that finds the current focused component and draws a focus rect around it. Somehow I now need to ignore the DragImageComponent.

I just checked develop. This does not work (at least not in the Standalone plugin host).
When will a fix for this basic requirement be available?

The thread started in 2013. Seems to be quite a challenge!