PyQt fully supports standard drag and drop
operations on all platforms. This includes both Windows OLE drag
and drop, and the two X11 standards: XDND (which uses MIME) and
the legacy Motif Drag'n'Drop protocol.
MIME, which you may know as a way to encode
all kinds of datatypes for e-mail, is used to encode the dragged
data in Qt. This is a very flexible standard, regulated by IANA
(http://www.isi.edu/in-notes/iana/assignments/media-types/). This
means that almost any kind of data can be handled by the Qt drag
and drop mechanism, not just text or images.
Handling drops
Our Kalam editor
must be able to accepts drops from other applications, so we
will have to implement this functionality.
Incoming drop operations are easy enough to
handle. Basically, you call
self.setAcceptDrops(TRUE) for the widget or
widgets that should accept drop events, and then actually handle
those drop events.
The widget that should
accept the drops is our KalamView class.
But setting self.setAcceptDrops(TRUE) in
the constructor of KalamView won't work.
This is because KalamView doesn't
actually handle the drops; rather, they are handled by the
QMultiLineEdit class, which is
encapsulated by KalamView.
The easiest solution is to extend the small
subclass of QMultiLineEdit (which we
already created) to handle drop events.
Example 23-1. Handling drop events
"""
kalamview.py - the editor view component for Kalam
copyright: (C) 2001, Boudewijn Rempt
email: boud@rempt.xs4all.nl
"""
from qt import *
import kalamconfig
from resources import TRUE, FALSE
class KalamMultiLineEdit(QMultiLineEdit):
def __init__(self, *args):
apply(QMultiLineEdit.__init__,(self,) + args)
self.setAcceptDrops(TRUE)
def dragEnterEvent(self, e):
e.accept(QTextDrag.canDecode(e))
def dropEvent(self, e):
t=QString()
if QTextDrag.decode(e, t): # fills t with decoded text
self.insert(t)
self.emit(SIGNAL("textChanged()"), ())
def event(self, e):
if e.type() == QEvent.KeyPress:
QMultiLineEdit.keyPressEvent(self, e)
return TRUE
else:
return QMultiLineEdit.event(self, e)
How does this bit of code work? First, you
can see that the custom widget accepts drop events:
self.setAcceptDrops(TRUE).
The dragEnterEvent()
method is fired whenever something is dragged over the widget.
In this function we can determine whether the object that is
dragged over our application is something we'd like to accept.
If the function
QTextDrag.canDecode() returns true, then we
know we can get some text out of the data the drop event
carries. We accept the event, which means that whenever
the cursor enters the widget, the shape will change to indicate
that the contents can be dropped.
If the user releases the mouse button, the
function dropEvent() is called. This
presents us with a QDropEvent object,
which we can decode to get the contents.
However, here we come across one of the
inconsistencies of the PyQt bindings to Qt. Sometimes a function
wants to return two values: a boolean to indicate whether the
operation was successful, and the result of the operation. In
the case of QFontDialog, the results are
returned in a tuple:
(font, ok) = QFontDialog.getFont(self, "FontDialog")
Likewise,
QTextDrag.decode wants to return two
values: a boolean and a string. However, here you need to first
create a QString, and pass that to
QTextDrag.decode, which fills it with the
text, while returning a boolean indicating whether the decoding
went well.
However, having got the text by hook or by
crook, we can insert it in the view, and tell the world that the
text has changed (so other views on the text can update
themselves).
You can now select and drag that text to a
Kalam document. If you drag a file,
only the filename will be inserted, because a filename can also
be decoded as text. If you want to open the file instead, you
still have some work to do.