Monday, January 27, 2014

Qt's Drag-and-Drop Architecture for Python and PyQt5
Pt. 5, Detecting a Drop

Let's begin to code up a drop target widget. The code that follows contains some debugging displays. With those stripped out there isn't a lot of code.

class TargWidj(QLabel):
    '''A simple class that can detect an incoming drag
    and accept it, but only if it's a Copy of plain text.'''
    def __init__(self,text):
        super().__init__()
        self.setAcceptDrops(True)
        self.setText(text)
        self.move_point = QPoint(-1,-1)

The example drop target is another QLabel with a few extra members. The important step is setting self.setAcceptDrops(True). If this property is not set, or if it is set to False during execution, no drop-related events will be reported.

    def dragEnterEvent(self, event):
        actions = event.possibleActions()
        self.move_point = event.pos()
        msg1 = 'drag enters at {0} {1}'.format(event.pos().x(), event.pos().y())
        msg2 = 'kbd mods {0:0x} buttons {1:0x}'.format(
            int(event.keyboardModifiers()), int(event.mouseButtons()) )
        print(msg1,msg2,'offering',xlate_actions(actions))
        if event.mimeData().hasText():
            if actions & Qt.CopyAction :
                event.acceptProposedAction()
            else :
                print(' -- setting copy action')
                event.setDropAction(Qt.CopyAction)
                event.accept()

The first sign that a drag is is underway is a call to this event handler. It tells you that a dragging cursor has crossed the boundary of this widget. The first five lines above would not appear in your production code. Here they display something like

drag enters at 0 38 kbd mods 0 buttons 1 offering actions: Copy Move

The real code starts with actions = event.possibleActions(). The QDragEvent class offers a confusing array of "actions" inherited from QDropEvent.

First, event.possibleActions() returns the set of actions that were passed-in to the exec_(actions) method of the Drag object (or its equivalent in some other app!). These are the actions that the source widget is offering, or permitting, or expecting, or something like that.

Then, event.proposedAction() is one of the "possible" actions, usually just Qt.MoveAction, and I can't say why this action is singled out as a "proposal".

event.dropAction() is the action that will be performed at drop time. If you don't agree with dropAction, you can call event.setDropAction() and set a different one.

In our example, we are insisting on doing a Copy. So if Copy is offered we call event.acceptProposedAction(). If not, we set the drop action to Qt.CopyAction and call event.accept().

Why are there two different ways of accepting the event? I don't know. What I do know is that if dragEnterEvent() exits without accepting the event in one of those two ways, it has rejected the drop, and no further events related to this drop will be presented to this widget. The drop is over, as far as this widget is concerned.

In the next post we look at what happens after the drag is accepted.

No comments: