PySide (Qt 4.8.6) skips mouse events

I have a QDialog and some controls in it. On middle_mouse_button_pressed event I pick widget under cursor and start moving it inside the dialog window. On middle_mouse_button_release set it back in current position.

Actually works fine, but sometimes Qt skips mouse events, randomly. I tried to move items quick and slow, seems like not depend on speed.
Has anybody had similar problem?

Code snippet


def mousePressEvent(self, event):
    if event.button() == QtCore.Qt.MiddleButton:
        self.on_update()

    return QtGui.QDialog.mousePressEvent(self, event)

def mouseReleaseEvent(self, event):
    if event.button() == QtCore.Qt.MiddleButton:
        self.on_update()

    return QtGui.QDialog.mouseReleaseEvent(self, event)

def mouseMoveEvent(self, event):
    if self.handle_move_event:
        self.on_update()

    return QtGui.QDialog.mouseMoveEvent(self, event)

I believe QT can lose events if it is already inside another callback. Perhaps it is busy executing mouseMoveEvent while mousePress or mouseRelease happens? I don’t know what self.on_update() does, but perhaps you need to look into using QThread for the update function? I’m guessing here, and I don’t have a lot of experience with QThreads.

self.on_update() isn’t resetting the UI in any way is it?

Here is a prototype


import sys
from PySide import QtGui, QtCore


class MainFrame(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.setWindowTitle('Movable buttons')
        self.setMinimumWidth(200)

        b1 = QtGui.QPushButton('1')
        b2 = QtGui.QPushButton('2')
        b3 = QtGui.QPushButton('3')
        b4 = QtGui.QPushButton('4')

        self._main_layout = QtGui.QVBoxLayout()
        self._main_layout.addWidget(b1)
        self._main_layout.addWidget(b2)
        self._main_layout.addWidget(b3)
        self._main_layout.addWidget(b4)

        self.setLayout(self._main_layout)

        self._moving_widget = None
        self._mouse_start_pos = None
        self._mouse_last_pos = None

        self._margin = self._main_layout.getContentsMargins()[1]
        self._min_pos = self._margin - 2
        self._max_pos = self._min_pos

        self.show()

    def on_mouse_pressed(self, event):
        for i in range(self._main_layout.count()):
            widget = self._main_layout.itemAt(i).widget()

            if widget.underMouse():
                self._moving_widget = widget
                layout_height = self._main_layout.geometry().height()
                self._max_pos = layout_height - self._moving_widget.height() - self._margin + 2

                self._mouse_start_pos = event.globalPos()
                self._mouse_last_pos = event.globalPos()
                self._moving_widget.raise_()
                return

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton:
            self.on_mouse_pressed(event)

        return QtGui.QWidget.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton and self._moving_widget:
            self._moving_widget = None
            self._main_layout.update()

        return QtGui.QWidget.mouseReleaseEvent(self, event)

    def update_order(self):
        wa_y = self._moving_widget.pos().y()
        wa_h = self._moving_widget.height()
        wa_i = self._main_layout.indexOf(self._moving_widget)

        for i in range(self._main_layout.count()):
            next_widget = self._main_layout.itemAt(i).widget()

            if next_widget == self._moving_widget:
                continue

            wb_y = next_widget.pos().y()
            wb_h = next_widget.height()
            wb_i = self._main_layout.indexOf(next_widget)

            b_a = wb_y <= wa_y <= wb_y + wb_h and wa_i < wb_i
            a_b = wa_y <= wb_y <= wa_y + wa_h and wa_i > wb_i

            if a_b or b_a:
                self._main_layout.insertWidget(wa_i, next_widget)
                self._main_layout.insertWidget(wb_i, self._moving_widget)
                return

    def mouseMoveEvent(self, event):
        if self._moving_widget:
            widget_glob_pos = self.mapToGlobal(self._moving_widget.pos())
            mouse_pos = event.globalPos()
            mouse_mov = mouse_pos - self._mouse_last_pos
            mouse_mov.setX(0)

            widget_new_pos = self.mapFromGlobal(widget_glob_pos + mouse_mov)
            if self._min_pos <= widget_new_pos.y() <= self._max_pos:
                self._moving_widget.move(widget_new_pos)
                self._mouse_last_pos = mouse_pos
                self.update_order()

        return QtGui.QWidget.mouseMoveEvent(self, event)


def main():
    app = QtGui.QApplication(sys.argv)
    win = MainFrame()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

I see that you’re doing a .raise_() on the widget. Could it be that the mouse up event gets consumed by another widget because the stack order has changed?

EDIT: you’re also calling .update() on the widget. I wasn’t sure if you’d wrapped this with your own code. Hmmm…

I still think your mouse up event might be getting lost because it is busy handling mouse move events.

Thanks, btribble!. I gonna do more tests to spot the problem.

You might be able to check for mouse release manually in the mouse move event by checking the mouse buttons directly. You might need to enable mouse move events ouside of button presses with SetMouseTracking()

Hi btribble,
just came to say you thanks for tips. I found the problem. It occurred at the time when the mouse was over the control that can receive focus (comboBox in my case) and the event has been redirected to this element. Now i have everything working correctly. Cheers.

Awesome! Thanks for the follow up.