Drag and Drop, will it be implemented?


#1

Hello Jules,

I notice “not implemented” in DragAndDropContainer::performExternalDragDropOfFiles and DragAndDropContainer::performExternalDragDropOfText

Any intentions of implementing this in the near future, or will it remain unsupported on Linux?

Thanks


#2

No immediate plans… Not being much of a linux guru, I often leave this kind of thing until people suggest bits of code as a starting point, because although it’s usually simple enough to write the code for windowing tasks, the almost total lack of documentation means that it can be a PITA to figure out where to begin.


#3

Well, I do love a challenge! :slight_smile:

Let’s start by reviewing the existing implementation. Looking at the JuceDemo->DnD, it’s not fully functional. Internal dnd works, but it doesn’t react to incoming files from external applications. After reviewing the specs (http://www.newplanetsoftware.com/xdnd/), working through juce_linux_Windowing.cpp and looking at a lot of code from other implementations, I think I’ve got it fixed with the patch below.

It has been tested successfully with the following filemanagers:
[list]
[]Dolphin (KDE default)[/]
[]Konqueror (older KDE/QT default)[/]
[]Nautilus (GNOME/GTK default)[/]
[]Thunar (XFCE/Enlightenment default)[/]
[]PCMan (LXDE default)[/]
[/list]

Please let me know if you have any questions or comments.

I intend to continue by fixing TextDragAndDropTarget to actually receive text (like text/plain) and not treat it like incoming files. That might depend somewhat on your acceptance of the patch below, so I’ll be very grateful for a prompt review!

Oh and one more thing: Some implementations (read QT/KDE) don’t actually make the dragged filenames available until the very drop, so callbacks fileDragEnter/fileDragMove/fileDragExit promised at http://www.rawmaterialsoftware.com/api/classFileDragAndDropTarget.html may or may not be called during the DnD operation. In essence, they get called with GTK-based drag-sources, but not with QT-based.

[code]diff --git a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
index b206b82…bf57b86 100644
— a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
+++ b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
@@ -2304,6 +2304,7 @@ private:
dragAndDropCurrentMimeType = 0;
dragAndDropSourceWindow = 0;
srcMimeTypeAtomList.clear();

  •    finishAfterDropDataReceived = false;
    

    }

    void sendDragAndDropMessage (XClientMessageEvent& msg)
    @@ -2366,23 +2367,23 @@ private:
    (int) clientMsg.data.l[2] & 0xffff);
    dropPos -= getScreenPosition();

  •    if (dragInfo.position != dropPos)
    
  •    {
    
  •        dragInfo.position = dropPos;
    
  •        const Atoms& atoms = Atoms::get();
    
  •        Atom targetAction = atoms.XdndActionCopy;
    
  •    const Atoms& atoms = Atoms::get();
    
  •    Atom targetAction = atoms.XdndActionCopy;
    
  •        for (int i = numElementsInArray (atoms.allowedActions); --i >= 0;)
    
  •    for (int i = numElementsInArray (atoms.allowedActions); --i >= 0;)
    
  •    {
    
  •        if ((Atom) clientMsg.data.l[4] == atoms.allowedActions[i])
           {
    
  •            if ((Atom) clientMsg.data.l[4] == atoms.allowedActions[i])
    
  •            {
    
  •                targetAction = atoms.allowedActions[i];
    
  •                break;
    
  •            }
    
  •            targetAction = atoms.allowedActions[i];
    
  •            break;
           }
    
  •    }
    
  •        sendDragAndDropStatus (true, targetAction);
    
  •    sendDragAndDropStatus (true, targetAction);
    
  •    if (dragInfo.position != dropPos)
    
  •    {
    
  •        dragInfo.position = dropPos;
    
           if (dragInfo.files.size() == 0)
               updateDraggedFileList (clientMsg);
    

@@ -2395,8 +2396,20 @@ private:
void handleDragAndDropDrop (const XClientMessageEvent& clientMsg)
{
if (dragInfo.files.size() == 0)

  •    {
    
  •        // no data, transaction finished in handleDragAndDropSelection()
    
  •        finishAfterDropDataReceived = true;
           updateDraggedFileList (clientMsg);
    
  •    }
    
  •    else
    
  •    {
    
  •        // already have the data
    
  •        handleDragAndDropDataReceived();
    
  •    }
    
  • }

  • void handleDragAndDropDataReceived()

  • {
    DragInfo dragInfoCopy (dragInfo);

    sendDragAndDropFinish();
    

@@ -2422,7 +2435,7 @@ private:

     dragAndDropSourceWindow = clientMsg.data.l[0];
  •    if ((clientMsg.data.l[1] & 1) != 0)
    
  •    if ((clientMsg.data.l[1] & 1) != 0) // more than three supported mime types
       {
           ScopedXLock xlock;
           GetXProperty prop (dragAndDropSourceWindow, Atoms::get().XdndTypeList, 0, 0x8000000L, false, XA_ATOM);
    

@@ -2440,7 +2453,7 @@ private:
}
}

  •    if (srcMimeTypeAtomList.size() == 0)
    
  •    if (srcMimeTypeAtomList.size() == 0) // no mime types yet, look up the (max) three sent in the message
       {
           for (int i = 2; i < 5; ++i)
               if (clientMsg.data.l[i] != None)
    

@@ -2456,8 +2469,10 @@ private:
const Atoms& atoms = Atoms::get();
for (int i = 0; i < srcMimeTypeAtomList.size() && dragAndDropCurrentMimeType == 0; ++i)
for (int j = 0; j < numElementsInArray (atoms.allowedMimeTypes); ++j)

  •        {
               if (srcMimeTypeAtomList[i] == atoms.allowedMimeTypes[j])
                   dragAndDropCurrentMimeType = atoms.allowedMimeTypes[j];
    
  •        }
    
       handleDragAndDropPosition (clientMsg);
    
    }
    @@ -2466,7 +2481,7 @@ private:
    {
    dragInfo.files.clear();
  •    if (evt.xselection.property != 0)
    
  •    if (evt.xselection.property != None) // we are getting data in response to request from updateDraggedFileList
       {
           StringArray lines;
    

@@ -2495,6 +2510,9 @@ private:

         dragInfo.files.trim();
         dragInfo.files.removeEmptyStrings();
  •        if (finishAfterDropDataReceived)
    
  •            handleDragAndDropDataReceived(); // finish the DnD transaction
       }
    
    }

@@ -2513,11 +2531,13 @@ private:
windowH,
clientMsg.data.l[2]);
}

  •    // now we expect a SelectionNotify to send us back the files
    

    }

    DragInfo dragInfo;
    Atom dragAndDropCurrentMimeType;
    Window dragAndDropSourceWindow;

  • bool finishAfterDropDataReceived;

    Array srcMimeTypeAtomList;

[/code]


#4

Cool, thanks! That looks like a pretty simple patch, I’ll check something in shortly…


#5

Great, thank you. This is what I’m hoping to do for Linux DnD:

Complete the existing implementation
[list]
[] Implement return of “silent rectangle” to XdndPosition messages (basically tells the source “as long as you stay within this rect, don’t bother sending XdndPosition messages unless your control keys change”).[/]
[] Handle dropped url:s, like from Firefox[/]
[] Handle dropped text (TextDragAndDropTarget)[/][/list]
Hopefully
[list]
[] Ability to act as Xdnd source, implement performExternalDragDropOfFiles() & performExternalDragDropOfText()[/][/list]
Suggestions and comments are very welcome. I also have a question:

During the DnD transaction, there is a negotiation of “Action”. Action is “what should I do once the file is received - copy it / move it / link to it /ask the user / private action”. Somewhat like right-dragging in MS Windows. My question: Is this implemented somewhere at all in JUCE, like callbacks or other info about suggested action after a file drop?


#6

No. Apart from saying whether or not the target wants it, there’s no extra information.


#7

Hello Jules,

This patch adds Linux support for TextDragAndDropTarget, and extends FileDragAndDropTarget support for other data-urls than “file://…”, like dropping a www-url from Firefox.

[code]diff --git a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
index 3eadc86…a0acc92 100644
— a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
+++ b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
@@ -60,8 +60,12 @@ struct Atoms
XdndActionCopy = getCreating (“XdndActionCopy”);
XdndActionDescription = getCreating (“XdndActionDescription”);

  •    allowedMimeTypes[0]             = getCreating ("text/plain");
    
  •    allowedMimeTypes[1]             = getCreating ("text/uri-list");
    
  •    /* text types */
    
  •    allowedMimeTypes[0]             = getCreating ("UTF8_STRING");
    
  •    allowedMimeTypes[1]             = getCreating ("text/plain;charset=utf-8");
    
  •    allowedMimeTypes[2]             = getCreating ("text/plain");
    
  •    /* file types */
    
  •    allowedMimeTypes[3]             = getCreating ("text/uri-list");
    
       allowedActions[0]               = getCreating ("XdndActionMove");
       allowedActions[1]               = XdndActionCopy;
    

@@ -89,12 +93,18 @@ struct Atoms
XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList,
XdndActionDescription, XdndActionCopy,
allowedActions[5],

  •     allowedMimeTypes[2];
    
  •     allowedMimeTypes[4];
    

    static const unsigned long DndVersion;

    static Atom getIfExists (const char* name) { return XInternAtom (display, name, True); }
    static Atom getCreating (const char* name) { return XInternAtom (display, name, False); }

  • static bool isMimeTypeFile (const Atom a) { return getName (a).equalsIgnoreCase (“text/uri-list”); }

  • static String getName (const Atom a)

  • {

  •    if (a == None) return "None";
    
  •    return String (XGetAtomName(display, a));
    
  • }
    };

const unsigned long Atoms::DndVersion = 3;
@@ -2349,10 +2359,11 @@ private:
{
sendDragAndDropLeave();

  •        if (dragInfo.files.size() > 0)
    
  •        if (dragInfo.files.size() > 0 || dragInfo.text.isNotEmpty())
               handleDragExit (dragInfo);
    
           dragInfo.files.clear();
    
  •        dragInfo.text = String::empty;
       }
    
    }

@@ -2385,17 +2396,17 @@ private:
{
dragInfo.position = dropPos;

  •        if (dragInfo.files.size() == 0)
    
  •        if (dragInfo.files.size() == 0 && dragInfo.text.isEmpty())
               updateDraggedFileList (clientMsg);
    
  •        if (dragInfo.files.size() > 0)
    
  •        if (dragInfo.files.size() > 0 || dragInfo.text.isNotEmpty())
               handleDragMove (dragInfo);
       }
    

    }

    void handleDragAndDropDrop (const XClientMessageEvent& clientMsg)
    {

  •    if (dragInfo.files.size() == 0)
    
  •    if (dragInfo.files.size() == 0 && dragInfo.text.isEmpty())
       {
           // no data, transaction finished in handleDragAndDropSelection()
           finishAfterDropDataReceived = true;
    

@@ -2414,13 +2425,14 @@ private:
sendDragAndDropFinish();
resetDragAndDrop();

  •    if (dragInfoCopy.files.size() > 0)
    
  •    if (dragInfoCopy.files.size() > 0 || dragInfoCopy.text.isNotEmpty())
           handleDragDrop (dragInfoCopy);
    

    }

    void handleDragAndDropEnter (const XClientMessageEvent& clientMsg)
    {
    dragInfo.files.clear();

  •    dragInfo.text = String::empty;
       srcMimeTypeAtomList.clear();
    
       dragAndDropCurrentMimeType = 0;
    

@@ -2477,6 +2489,7 @@ private:
void handleDragAndDropSelection (const XEvent& evt)
{
dragInfo.files.clear();

  •    dragInfo.text = String::empty;
    
       if (evt.xselection.property != None)
       {
    

@@ -2502,11 +2515,18 @@ private:
lines.addLines (dropData.toString());
}

  •        for (int i = 0; i < lines.size(); ++i)
    
  •            dragInfo.files.add (URL::removeEscapeChars (lines[i].fromFirstOccurrenceOf ("file://", false, true)));
    
  •        if (Atoms::isMimeTypeFile (dragAndDropCurrentMimeType))
    
  •        {
    
  •            for (int i = 0; i < lines.size(); ++i)
    
  •                dragInfo.files.add (URL::removeEscapeChars (lines[i].replace ("file://", String::empty, true)));
    
  •        dragInfo.files.trim();
    
  •        dragInfo.files.removeEmptyStrings();
    
  •            dragInfo.files.trim();
    
  •            dragInfo.files.removeEmptyStrings();
    
  •        }
    
  •        else
    
  •        {
    
  •            dragInfo.text = lines.joinIntoString("\n");
    
  •        }
    
           if (finishAfterDropDataReceived)
               handleDragAndDropDataReceived();
    

@@ -2518,7 +2538,7 @@ private:
dragInfo.files.clear();

     if (dragAndDropSourceWindow != None
  •         && dragAndDropCurrentMimeType != 0)
    
  •         && dragAndDropCurrentMimeType != None)
       {
           ScopedXLock xlock;
           XConvertSelection (display,
    

[/code]


#8

Thanks, I’ll have a look shortly.

BTW, none of the patches you’ve posted have ever worked for me… They all just give me error messages when I try to apply them. It’s not a line-endings problem, is there a linux/mac format difference? (seems unlikely)


#9

What kind of error messages?

My workflow is to branch of the current tip, apply a clean version of my additions (you know, filtering out all the DBG and std::cout), then git diff master…new_branch and paste that in the forum.

You’re sure it’s not line endings some how?


#10

Yeah, it’s odd…

Hunk #1 FAILED at 60.
patch: **** malformed patch at line 37: };

I thought it was line-endings, but tried changing them and it still failed.

No idea what’s wrong, but don’t have time to investigate more deeply. Will post some merged code soon…


#11

Next patch, I’ll try copying it from the forum and applying it myself to see. Perhaps there’s something in that process that causes something.

Other peoples patches apply ok, copied from the forum?

Am currently working on externalDragAndDrop, ie acting as an Xdnd Source. It will take some more work and will be a larger patch.


#12

Jules,

Just merged with tip including the fixes for Linux DnD (a25acde). There is one thing from my patch that I believe got lost: In order to receive other incoming text/uri-list than “file://…”, like www-urls dragged from a browser, the below patch (or something similar) needs to be applied.

[code]diff --git a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
index a207b61…4c189a6 100644
— a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
+++ b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
@@ -2516,7 +2516,7 @@ private:
if (Atoms::isMimeTypeFile (dragAndDropCurrentMimeType))
{
for (int i = 0; i < lines.size(); ++i)

  •                dragInfo.files.add (URL::removeEscapeChars (lines[i].fromFirstOccurrenceOf ("file://", false, true)));
    
  •                dragInfo.files.add (URL::removeEscapeChars (lines[i].replace ("file://", String::empty, true)));
    
               dragInfo.files.trim();
               dragInfo.files.removeEmptyStrings();
    

[/code]


#13

Ah, right, thanks!


#14

BTW, does that last minimal patch apply or are you still getting errors? In that case it almost has to be eol, don’t you think?

Am preparing to post a larger patch to add performExternalDragDropOf{Files,Text} and would love to solve our git-patch problem first.


#15

I didn’t try applying the patch, I just changed the code by hand. Maybe just email me the whole file next time?


#16

But LoL Jules, that’s sooo 1995! :wink:

[Edit]
So, the complete file has been emailed. Let’s post updates here.


#17

Yeah, I know, but for me to drop the file in and review the changes in my git client can’t go wrong, whereas the last time I tried to apply your diff, I wasted half an hour messing about with line-endings trying to make it work, then gave up and merged it all by hand…

Anyway, thanks for the changes, I’ll have a look asap!