Monday, January 27, 2014

Qt's Drag-and-Drop Architecture for Python and PyQt5
Pt. 6, Drag Moves

Once dragEnterEvent() has accepted the drag, the widget begins to receive a stream of calls to its dragMoveEvent() method. You don't need to implement this if you don't care where the drop happens upon your widget's rectangle. But here is an example of one.

    def dragMoveEvent(self, event):
        pos = event.pos()
        if pos != self.move_point:
            print('drag moving at {0} {1}'.format(pos.x(), pos.y()))
            self.move_point = pos
        # To illustrate forbidden areas, we mark the lower right quadrant as
        # invalid. The lower right quadrant is the rect with top-left at w/2,
        # h/2 and with size w/2, h/2. It doesn't make sense to specify this
        # over and over, but there's no other way.
        half_width = self.width()/2
        half_height = self.height()/2
        forbidden_rect = QRect(half_width,half_height,half_width,half_height)
        #event.ignore(forbidden_rect)
        if forbidden_rect.contains(pos):
            event.ignore()
        else:
            event.accept()

The first four lines implement a debugging display. These events are continuous and rapid, even if the mouse is not moving. (That's right: dragMoveEvent is called even when the mouse does not move!) For this reason we save the last-displayed point and only display again if the mouse has actually changed position.

You could track drag move events in order to change the appearance of the widget depending on the position of the cursor, for example changing its border, or somehow highlighting a child widget when the cursor was over it.

Another use is to put restrictions on the particular part of a widget that will receive the drop. The QDragMoveEvent reference claims that if you call event.ignore(rectangle), "Moves within the rectangle are not acceptable, and will be ignored." This does not seem to be true. If you enable the example line above, event.ignore(forbidden_rect), it has no effect on the behavior of the drag and drop operation. The drop will take place in the forbidden area if that's where the mouse is released.

What does make a difference is the explicit call to event.ignore() when the drag is moving within the forbidden rectangle. If the last call to dragMoveEvent before the mouse is released ends in event.ignore(), the drop doesn't happen. The cursor wanders away and the drag ends without a drop.

If you have changed the look of the widget at the start of the drag, or during the drag moves, you would like to change it back to normal if the drag doesn't happen. That's the purpose of this code:

    def dragLeaveEvent(self, event):
        print('drag leaving')
        event.accept()

This event is delivered in two cases: one, if the user drags the cursor out of the widget's boundary; and two, if the user releases the mouse button and your dragMoveEvent ends in event.ignore(). Either counts as the drag "leaving".

Next post: dropping a load.

No comments: