Search code examples
pythonpyside2

How to get my selection box to move properly using PySide2?


Im working on a maya GUI and I created a drag selection box using QRubberBand. Now i'm trying to move it when I hold 'Alt'. The problem is that when I move the QRubberBand, it moves from the origin of the box, like this Currently.

What I'm trying to do is get it moving from the end point of the box, like this expected behaviour

.

Here is the code:

from PySide2 import QtCore, QtGui, QtWidgets


class Ui_Form(QtWidgets.QWidget):
    def __init__(self):
        super(Ui_Form, self).__init__()
        self.rubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
        self.setupUi(self)

    def mousePressEvent(self, event):        
        if event.button() == QtCore.Qt.LeftButton:
            if event.modifiers() == QtCore.Qt.ShiftModifier:
                print 'shift is selected'
            elif event.modifiers() == QtCore.Qt.CTRL:
                print 'Ctrl is selected'
            else:
                print 'Reseting selection'

            self.origin = event.pos()           
            self.drag_selection = QtCore.QRect(self.origin, QtCore.QSize())
            self.rubberBand.setGeometry(self.drag_selection)
            self.rubberBand.show()

    def mouseMoveEvent(self, event):
        if event.modifiers() == QtCore.Qt.AltModifier:
            self.rubberBand.move(event.pos())
            self.origin = self.rubberBand.pos()
        else:
            self.drag_selection = QtCore.QRect(self.origin, event.pos()).normalized()
            self.rubberBand.setGeometry(self.drag_selection)

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.rubberBand.hide() 
            if event.modifiers() == QtCore.Qt.CTRL:
                print 'Removing from selection'                
            else:
                print 'Selecting'

Solution

  • It's trickier than it seems because you can't simply set the position of the rubber band to the cursor, as it would pop it instead of translating it like one would expect.

    So instead I opted to use the rubber band's geometry as it was more consistent no matter what direction the user drags in. The idea is when the user pressed the alt key, we save the mouse's current position so that we can calculate a delta as the mouse moves around. With this delta we can move the geometry and set it without getting any pops. It's also possible that the user may repeatedly press and release alt to size the rubber band, move it, resize it, move it, and so on.. So we have to take that into account otherwise it will pop.

    Here's a full example that seems to work. Hopefully the comments make it easy to follow the logic:

    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    class Ui_Form(QtWidgets.QWidget):
    
        def __init__(self):
            super(Ui_Form, self).__init__()
            self.rubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
    
            # Create variables that will be handling the moving logic.
            self.sourceGeo = None
            self.altPoint = None
            self.delta = None
    
        def mousePressEvent(self, event):        
            if event.button() == QtCore.Qt.LeftButton:
                self.origin = event.pos()
                self.drag_selection = QtCore.QRect(self.origin, QtCore.QSize())
                self.rubberBand.setGeometry(self.drag_selection)
                self.rubberBand.show()
    
        # Must implement this event to detect when alt gets released
        def keyReleaseEvent(self, event):
            if event.key() == QtCore.Qt.Key_Alt:
                if self.delta is not None:
                    # This is important: Add delta to origin so that it shifts it over.
                    # This is needed if the user repeatedly pressed alt to move it, otherwise it would pop.
                    self.origin += self.delta
    
                    # Reset the rest of the variables.
                    self.sourceGeo = None
                    self.altPoint = None
                    self.delta = None
    
        def mouseMoveEvent(self, event):
            if event.modifiers() == QtCore.Qt.AltModifier:
                # Get the point where alt is pressed and the selection's current geometry.
                if self.altPoint is None:
                    self.sourceGeo = self.rubberBand.geometry()
                    self.altPoint = event.pos()
    
                self.delta = event.pos() - self.altPoint  # Calculate difference from the point alt was pressed to where the cursor is now.
                newGeo = QtCore.QRect(self.sourceGeo)  # Create a copy
                newGeo.moveTopLeft(self.sourceGeo.topLeft() + self.delta)  # Apply the delta onto the geometry to move it.
                self.rubberBand.setGeometry(newGeo)  # Move the selection!
            else:
                self.drag_selection = QtCore.QRect(self.origin, event.pos()).normalized()
                self.rubberBand.setGeometry(self.drag_selection)
    
        def mouseReleaseEvent(self, event):
            if event.button() == QtCore.Qt.LeftButton:
                self.rubberBand.hide() 
    
    
    inst = Ui_Form()
    inst.show()
    

    Example