views:

537

answers:

3

Hi all,

I've written a little file-transfer application written in C++ using Qt 4.x ... it logs into a server, shows the user a list of files available on the server, and lets the user upload or download files.

This all works fine; you can even drag a file in from the desktop (or from an open folder), and when you drop the file icon into the server-files-list-view, the dropped file gets uploaded to the server.

Now I have a request for the opposite action as well... my users would like to be able to drag a file out of the server-files-list-view and onto the desktop, or into an open folder window, and have that file get downloaded into that location.

That seems like a reasonable request, but I don't know how to implement it. Is there a way for a Qt application to find out the directory corresponding to where "drop event" occurred, when the icon was dropped onto the desktop or into an open folder window? Ideally this would be a Qt-based platform-neutral mechanism, but if that doesn't exist, then platform-specific mechanisms for MacOS/X and Windows (XP or higher) would suffice.

Any ideas?

+1  A: 

I think you are going about that in the wrong way.

You don't care where the drop goes to, you just know it was dropped. In the DropEvent, download the file to a temporary location and then set the mime data to be what was downloaded. Granted this may end up with a second copy to the hard drive, it will be cross platform from the start. You may be able to optimize it afterward with platform specific calls.

Take a look at Dropsite example to see how the mime data works from other sources...

Edit:

It looks like the double copy method is the standard if you don't write a shell extension (for windows at least). Filezilla and 7zip both do that, they return a "text/uri-list" mime type with a temp location. This way explorer copies the data from the temp location to the real one. Your application can do the same, create a temp file (or just the name) with no data. Using the delayed encoding example, create the data on the drop. This whole operation is very platform specific it appears. On Linux (running KDE) I can drag and drop just based on mime type. It appears that windows is not as flexible.

Adam W
First problem is, I don't get a DropEvent, because the icon was dropped onto the desktop (or into a file folder) and was not dropped on my application's window.Second problem is that I *do* need to know where the icon was dropped, because that is the directory that the user wants the file to appear in.
Jeremy Friesner
Btw I looked at the DropSite example, it isn't relevant because it only functions as a drop site, and not as a drag source.
Jeremy Friesner
Please read before down voting. The dropsite example shows you how to use mime types from any source. You can launch your program and then drop onto that one to see what mime types are listed.As for the first part, you let the OS handle the drop. You provide it the mime type and data, it will place it where you dropped it. Just like dragging items from one explorer window to another.Please read the Drag and Drop documentation as well as http://bit.ly/bDooQe
Adam W
I read it, but I don't think the answer applies to the problem at hand. In particular, the OS doesn't know how to download the file, so it can't handle the drop. I can't include the file's data directly in the drag-object, because the file exists only on the server at the time of the drag. I can't download the data before or during the drag, because the file might be large (e.g. several gigabytes).
Jeremy Friesner
Btw thanks for your help... I've removed the downvote.
Jeremy Friesner
If you look at: http://doc.trolltech.com/4.6/draganddrop-delayedencoding.html as well as dropping a file from explorer into the dropsite example you may be able to find the magic combination of mime types you need to create the files. You may have to trigger off the mouserlease event not the dropevent since like you said, you don't get a drop.
Adam W
I added more to my answer, it looks like cross platform drag and drop will not be possible without a double copy on the client machine. However, you can write a shell extension for windows to allow you to directly drag and drop to explorer.
Adam W
I think the problem still is, I can't just "create the data" on the drop, because the data isn't present locally. I have to download the data, a process which can take a long time (i.e. minutes/hours)... much too long to simply have retrieveData() block until the download has completed.
Jeremy Friesner
Yes that is the tricky part, I'm not sure if you will be able to launch a thread and use http://doc.qt.nokia.com/4.6/qfuturesynchronizer.html to wait for the download while still keeping the GUI responsive.
Adam W
+1  A: 

You just implement the drag part. You don't need to know where the drop happen because the application receiving it will handle it and decide where to store your file.

You create a QMimeData, not sure which type but from what i saw here and here, maybe "application/octet-stream" with your file as data in a QByteArray or "text/uri-list" with url to your file.

You create a QDrag, and use its setMimeData() method and exec() (not sure about which QT::DropAction to choose).

Here is an example (disclamer : i did it with color not files) :

void DraggedWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        start_pos = event->pos();
}

void DraggedWidget::mouseMoveEvent(QMouseEvent *event)
{    
    if (event->buttons() & Qt::LeftButton) {
        int distance = (event->pos() - start_pos).manhattanLength();
        if (distance >= QApplication::startDragDistance())
        {
            /* Drag */
            QMimeData *mime_data = new QMimeData;

            mime_data->setData( ... );

            QDrag *drag = new QDrag(this);
            drag->setMimeData(mime_data);
            drag->exec(Qt::CopyAction);
        }
    }
}

Sorry it's quite incomplete, i hope it helps a little.

Leiaz
The problem is that the data in the file is not available until after the download has completed, and a multi-gigabyte download might take an hour or more. So I need to know where the drop has happened so that my app can start a download thread to write data to that location. A system that relies on the data already being locally present at the time of the drag won't work for me, AFAICT.Including a URI to the file might work, but only if the file can be accessed via a protocol the OS knows how to download (e.g. HTTP?). Currently the server uses a proprietary protocol. Hmm....
Jeremy Friesner
+1  A: 

Look at QMimeData and its documentation, it has a virtual function

virtual QVariant retrieveData  ( const QString & mimetype, QVariant::Type type ) const

this means to do you drag to the outside you implement this functions accordingly

class DeferredMimeData : public QMimeData
{
  DeferredMimeData(QString downloadFilename) : m_filename(downloadFilename)

  virtual QVariant retrieveData (const QString & mimetype, QVariant::Type type) const 
  {
    if (mimetype matches expected && type matches expected)
    {
       perform download with m_filename
    }
  }
}

The delayed encoding examples shows this principle.

You will probably also have to override hasFormat and formats to provide the appropriate types, application/octet-stream probably being the one that might get you the most play, you will probably have to read up on how windows specifically handles drag and drop using mime types.

I don't know how you will supply the file name under which the file is saved, but you will probably have to get into the windows side of things. Looking at the source of QWindowsMime might also help. There might me a multiple step process where you will get requests for text/uri-list data for the filenames and then application/octet-stream for the data.

Hope this helps

Harald Scheirich
This looks like it would almost work, except that I suspect it would freeze the GUI of my application until retrieveData() returned. Given that a download could take a long time (minutes or hours) that would be sub-optimal. :/
Jeremy Friesner
I would say that this does not preclude either doing the download in a separate thread or letting the GUI handle events via QCoreApplication::processEvents().I have to admit though I was trying to hack the delayed encoding example to deposit files on the desktop and had not much success with it
Harald Scheirich
A separate thread wouldn't help, since retrieveData() would still need to block because it returns the data in its return value, and thus could not return until the download was complete. Calling processEvents() from within retrieveData() might possibly work, but I wouldn't trust it... I've been burned by that technique in the past.
Jeremy Friesner