Opened 18 years ago

Last modified 16 years ago

#22 assigned enhancement

Implement the Drag & Drop support

Reported by: dmik Owned by: dmik
Priority: normal Milestone: qt-os2-3.3.1-rc07
Component: kernel Version:
Severity: normal Keywords: drag drop
Cc:

Description

I've decided to implement the Drag & Drop support before the Release Candidate 07 comes out. This is because many existing Qt apps use d&d (at least partly) which makes it difficult to port them with QT_NO_DRAGANDDROP defined.

Change History (20)

comment:1 Changed 18 years ago by dmik

I've discovered a strange behavior regarding DM_RENDER. It seems that both WPS and xWorkplace acting like drag sources expect this message to be sent to them before the drag target returns from the DM_DROP handler.

When the target sends WM_RENDER to WPS after it returns from DM_DROP, WPS simply replies FALSE to this message and does nothing. If we send WM_RENDER from within DM_DROP, it behaves differently: replies with DMFL_RENDERRETRY (at least in case when we request <DRM_OS2FILE,whatever>).

When the target sends WM_RENDER to xWorkplace after it returns from DM_DROP (trying to render an XCenter widget for instance), PM simply hangs until we kill the target application. If WM_RENDER is sent from within DM_DROP, it works as expected (a file with XCenter object's settings is created at the specified location). It's important to notice that when the target is WPS (the Desktop, for instance), xWorkplace also works as expected; this makes it even more obvious that WPS sends WM_RENDER from within its DM_DROP handler and expects the same behavior from others.

This behavior is very strange to me, because PMREF clearly states in the description of DM_DROP that:

"The receiver must immediately remove any target emphasis and post a private message to itself to initiate the data transfer conversations needed to complete the operation."

Moreover, in the section named "Responding to the DM_DROP Message" we can read at the end of the first paragraph:

"...The target should immediately remove any target emphasis. The data transfers must not be done before responding to the DM_DROP message."

Btw, EPM seems to expect DM_RENDER ath the right time, at least it renders the selected text to a specified file w/o problems, when we send DM_RENDER to it outside DM_DROP. On the other hand, dropping an XCenter object to EPM causes it to crash.

comment:2 Changed 18 years ago by dmik

Just for record. xWorkPlace has one more D&D related problem. In the DM_RENDER handler (DwgtRender?() in xworkplace\src\shared\center.c), it *sends* the WM_RENDERCOMPLETE message instead of posting it. This is totally wrong because the target gets WM_RENDERCOMPLETE while being inside the DrgSendTransferMsg?(..., DM_RENDER,...) call, so an attempt to free d&d resources in reply will definitely crash it.

comment:3 Changed 18 years ago by dmik

Oops! I've got how the DRM_SHAREDMEM rendering mechanism works (at least, how WPS uses it).

If you specify "<DRM_SHAREDMEM,DRF_POINTERDATA>" in the hstrSelectedRMF field of the DRAGTRANSFER structure passed to the drag source when sending a DM_RENDER event, it returns you a shared memory pointer in the hstrRenderToName field in reply to this message. This memory object contains the dropped item data prepended by a 4-byte data length value. For example, if you drag a file object, this memory object will simply contain the file contents. After the memory object is no more necessary, you simply call DosFreeMem? on it, and that's all.

This rendering mechanism is obviously much more convenient than DRM_OS2FILE, especially when it comes to dropping items not intended to be copied or moved as regular files. I plan to use this mechanism as a native one to implement MIME data exchange in Qt D&D.

comment:4 Changed 18 years ago by dmik

Important remark about DRM_SHAREDMEM: it is valid to access the shared memory pointed to by hstrRenderToName only when DM_RENDERCOMPLETE is received from the drag item hwnd. Before it happens, chacnces are high that hstrRenderToName will not yet contain a valid pointer (because it seems that reading a file into memory is asynchronous, the DM_RENDER reply is sent *before* it starts). Anyway, accessing rendered data is allowed only after DM_RENDERCOMPLETE according to PMREF.

comment:5 Changed 18 years ago by dmik

I've just discovered that Mozilla (Firefox etc.) also accepts DM_RENDER only from within the DM_DROP handler of the target and crashes otherwise... Not that nice. It seems that this a common practice.

The above means that in order to implement the source-rendered D&D in Qt I'll have to do *everything* "synchronously", i.e. from within the DM_DROP handler before returning a reply: send DM_RENDER, wait for DM_RENDERCOMPLETE, and send DM_ENDCONVERSATION. The explanation:

  1. The drop target in Qt can only access D&D data from within the QDropEvent handler using its encodedData().
  2. Therefore, DM_RENDER cannot appear outside the QDropEvent handler (since we don't know the format we want until encodedData() is called).
  3. Since we have to send DM_RENDER from within DM_DROP (according to the common practice), we have to send QDropEvent from within it as well.
  4. Sending QDropEvent from DM_DROP, in turn, means that any data transfer must complete before the QDropEvent handler returns (because encodedData() is of course synchronous by design, so we cannot return from DM_DROP until the QDropEvent handler is finished).

This is what I will try right now. I really hope this will work.

comment:6 Changed 18 years ago by dmik

Well, as I expected, not all apps like this "synchronous" approach. WPS/xWP/Mozilla/OpenOffice work fine with it, while EPM hangs up on its UI thread (stops fetching messages from the queue) until the target application waiting for DM_RENDERCOMPLETE from it gets closed. It seems that EPM wants to have itself returned from DrgDrag?() before it posts DM_RENDERCOMPLETE.

Since WPS/xWP/Mozilla/OpenOffice are more important to function properly than EPM (and since I haven't found another app behaving like EPM), I will still go for the "synchronous" approach (although it violates PMREF) and will do everything from DM_DROP. At least for the time being.

Note that it would be ideal to use a mix of two approaches (i.e. only send DM_RENDER from within DM_DROP, then return from it shortly and handle DM_RENDERCOMPLETE in a normal way on a subsequent iteration) -- this technique works with all apps I tried (including EPM). But this is definitely not applicable to Qt because of the reasons explained above.

comment:7 Changed 18 years ago by dmik

In bot Qt/Win32 and Qt/Linux? the following 'accept drop' logic applies to drag&drop events:

  1. If the target widget doesn't process QDragMoveEvents (i.e. it doesn't overload the dragMoveEvent() method), then an answer to the (first and the only) QDragEnterEvent defines the further accept state until QDragLeaveEvent/QDropEvent happens, subsequent drop action changes (using keyboard modifiers) are completely ignored:
    1. If the first answer is accept() (ignore()), then any drop action will be accepted (rejected) everything is ok.
    2. If the first answer is acceptAction() for action XXX, then any drop action will be actually accepted, not only XXX as one might expect.
    3. If the first answer is acceptAction(false) for action XXX, then any drop action will be actually rejected, not only XXX as one might expect.
  2. If the target widget does process QDragMoveEvents, then everything works as expected: acceptAction() can accept/reject a specicic set of actions, taking dynamic action changes into account (and providing the correct visual feedback).

I will implement the same in Qt/OS2.

comment:8 Changed 18 years ago by dmik

Important remark about the 'accept drop' logic. There are situations possible when the source cannot provide the current operation, even if the target accepts it (for example, the trashcan object that can never be copied). In this case, the target answer (accept/reject) is completely ignored and the drop is always rejected (Qt/Win32 behaves in the same way). Note that rules stated in the item 1 above don't apply; source-initiated rejects (and visible feedback) are always instantly updated regardless of whether the target processes QDragMoveEvent.

comment:9 Changed 18 years ago by dmik

Status: newassigned

Note that encodedData() in both QDrageEnterEvent and QDragMoveEvent will return nothing in the OS/2 implementation (according to PMREF, we cannot transfer data before DM_DROP occurs, nor will we support this for Qt-initiated drag sources).

comment:10 Changed 18 years ago by dmik

According to my tests, DrgFreeDragtransfer() appears to be bogus: when the drag source attempts to free the DRAGTRANSFER structure passed to it in DM_RENDERPREPARE/DM_RENDER by another process, the shared memory object is not actually released until DrgFreeDragtransfer() is called for the second time. Really weird. I wrote a method, qt_DrgFreeDragtransfer() that tries to fix this problem.

comment:11 Changed 18 years ago by dmik

WPS acting as a drop target is a real dumb. It will blindly choose the first DRF associated with the DRM_OS2FILE mechanism when such an item is dropped to it, even if it has absolutely no idea what this particular DRF means. For example, given an item's RMF spec like (DRM_OS2FILE)x(WPS_YOU_ARE_STUPID,DRF_TEXT,BLABLABLA), it will just ask the drag source to render the item as <DRM_OS2FILE,WPS_YOU_ARE_STUPID>.

Sigh. Will have to introduce a dirty hack for that: QPMCoopDragWorker will make sure that DRM_TEXT, if present, is always the first in the list. Stupid hack for a stupid bug.

comment:12 Changed 18 years ago by dmik

No, it was fixed in QTextDrag instead. QTextDrag compiled for OS/2 reports "text/whatever" w/o any charset as the first supported mime. This will place the <DRM_OS2FILE,DRF_TEXT> pair first in the DRM list, which in turn will make WPS happy.

comment:13 Changed 18 years ago by dmik

Just for the recored. Allocating and freeing DRAGTRANSFER structures is a real mess. It's absolutely unclear how they can be reused for multiple items and/or render requests. My practice shows, that they cannot be reused at all, especially when the source and the target are the same process: if we have multiple items and use the same DRAGTRANSFER for all of them, the source will call DrgFreeDragtransfer?() every time that will eventually destroy the memory object before the target finishes to work with it, so that the next DrgFreeDragtransfer?() will generate a segfault in PMCTLS. Also note that using a number > 1 as an argument to DrgAllocDragtransfer?() won't help because that will still allocate a single memory object. Thus, we will always allocate a new struct per every item. It seems to work.

comment:14 Changed 18 years ago by dmik

One more remark. Mozilla seems to send DM_RENDERCOMPLETE *before* it finishes saving an URL to the specified file. More over, it seems it cannot finish writing at all until we return from DM_DROP... This is because the procedure of saving an URL to a file is asynchronous, but for some reason it cannot run until Mozilla returns from DrgDrag?() for some reason. We cannot do anything with it. The bad thing is that it will leave our tempoaray file (because it's impossible to delete an open file).

comment:15 Changed 18 years ago by dmik

I've checked in the current Drag'n'Drop implementation. Drag'n'drop works by itself, however it's not yet fully functional. Things to do are:

  • Provide the correct HPS for wigets during the drag operation (using DrgGetPS) to avoid screen corruption happening if widgets dynamically update their contents.
  • Implement setting a drag object's pixmap (drag image).

Not that much comparing to what has already been done.

comment:16 Changed 16 years ago by komh

Hello Dmitry

Any idea, when in Drag'n'Drop can be finished, as QT designer and some other futures depend only on this non-implemented part.

Unfortunately, I can't help in this area.

Best Regards
Dmitry Froloff

comment:17 Changed 16 years ago by dmik

Hi Dmitry!

Hmm, which feature exactly do you mean? I can already successfully build Qt Designer and other tools like Qt Linguist with the SVN trunk. The reason why I don't check in the automatic build of Qt Designer and friends yet is that there are some redraw problems in some widgets observed in Qt Designer that I want to fix first.

comment:18 Changed 16 years ago by rudi

Hi dmik,

which redraw problems are you referring to ? What I saw is, that the form window in Designer would not correctly paint when resized.

Please take a look at tools/designer/designer/formwindow.cpp.

There is something called "windowsRepaintWorkaround". It looks like we should enable this for Q_WS_PM as well and adapt flickerfree_update() accordingly.

comment:19 Changed 16 years ago by komh

Hi dmik,

I've found some more redraw problems in Designer myself. For example, when resizing a tab widget. It looks very similar to the formwindow issue. Maybe there is a fix for both. Especially since the "windowsRepaintWorkaround" is not the greatest solution on earth...

Besides that, Designer works reasonable well. What took me a while to find out is, that some of the plugins would not load due to DLL names beeing to long. So I went through the plugin source directories and added "os2:TARGET = <short_dll_name>" to most of the *.pro files. Also some source files are missing "os2" as additional platform name, so that OS/2 specific libs, headers and defines can be entered in Designer's project settings dialog.

comment:20 Changed 16 years ago by rudi

Oops, was not logged in when doing the previous post.

BTW, I did not see any Drag and Drop related problems in Designer, but have to test more...

Note: See TracTickets for help on using tickets.