[PyQt] (self-made class) handles mouse click logic

I wrote a class that I think is pretty good to simplify the mouse information in mousePressEvent, mouseMoveEvent and mouseReleaseEvent.

The functions include the following:
  • Current mouse status, including left/middle/right mouse button and click/double-click/lift
  • Mouse anti-shake (only when the mouse has moved beyond a certain level), the sensitivity can be set;
  • Mouse long press (triggered when the mouse is long pressed and no movement occurs), the duration can be set;
  • Mouse double-click (the time interval between two clicks is small enough to be judged as a double-click), the duration can be set;
  • Mouse offset is only valid when the mouse is pressed. It can return the total offset since the click or the relative offset from the last mouse event.
Supplement:

This self-made class will cause ambiguity when pressing multiple keys, that is, it cannot handle difficult operations, such as right-clicking and dragging and then left-clicking to cause chaos. Originally, I wanted to rewrite the code to fill this flaw, but when I think about it, it’s a bit strange. Under what circumstances is this weird operation needed?

Homemade class XJ_MouseStatus:

#XJ_MouseStatus.py
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import QPoint,Qt,QObject
from PyQt5.QtGui import QMouseEvent

__all__=['XJ_MouseStatus']
class XJ_MouseStatus(QObject):#mousePressEvent, mouseMoveEvent and mouseReleaseEvent are specially provided. Only handles single keys (multi-key behavior please control in external code)
    longClick=pyqtSignal()#Triggered when the mouse remains in place and long pressed

    __antiJitter=5# Anti-shake, when the Manhattan distance between the mouse click position and the current mouse position does not exceed this value, the mouse will still be regarded as motionless.
    __doubleClickInterval=500#Double-click interval (ms)
    __longPressInterval=500#Long press interval (ms)
    __record={<!-- -->
        'lastPress':None,#Information when last pressed
        'lastMouse':None,#Last mouse information
        'currMouse':None,#Current mouse information
        }
    __press=[QMouseEvent.MouseButtonRelease,QMouseEvent.MouseButtonPress,QMouseEvent.MouseButtonDblClick]#For laziness
    __move=False# Used to determine whether to long press
    __timerID=0#The corresponding timer when the mouse is pressed
    class __Data:
        pos=None#mouse position
        btn=None#Mouse buttons (left, middle, right)
        pressStatus=None#The mouse is currently pressed (single double click/lift)
        timeStamp=None#Mouse event time moment
        def __init__(self,event):
            self.pos=event.globalPos()#pos is not used here to prevent violent death
            self.btn=event.button()
            self.pressStatus=event.MouseButtonRelease
            self.timeStamp=event.timestamp()

    def __init__(self,*arg):
        super().__init__(*arg)
        record=self.__record.copy()
        fakeEvent=QMouseEvent(QMouseEvent.MouseButtonRelease,QPoint(0,0),Qt.NoButton,Qt.NoButton,Qt.NoModifier)
        data=self.__Data(fakeEvent)
        data.timeStamp-=self.__doubleClickInterval#Small defense to avoid triggering double-click behavior when clicking at the start
        record['lastMouse']=data
        record['currMouse']=data
        record['lastPress']=data
        self.__record=record
    def timerEvent(self,event):
        record=self.__record
        press=self.__press
        tId=event.timerId()
        cId=self.__timerID
        self.killTimer(event.timerId())
        if(cId==tId):#Current timer
            if(not self.__move and record['currMouse'].pressStatus!=press[0]):#No movement occurs, the mouse is not lifted, and the long press signal is triggered
                self.longClick.emit()

    def Set_DoubleClickInterval(self,interval):#Set the double-click interval (ms)
        self.__doubleClickInterval=interval
    def Set_LongPressInterval(self,interval):#Set the long press interval (ms)
        self.__longPressInterval=interval
    def Set_AntiJitter(self,val):#Set anti-shake value
        self.__antiJitter=val if val>0 else 0

    def Get_Position(self):#Return the mouse coordinates. It is the screen coordinate (global), and you need to use QWidget.mapFromGlobal(QPoint) to convert it to the relative coordinate of the control.
        return self.__record['currMouse'].pos
    def Get_PressButtonStatus(self):#Return the current mouse button (left, middle, right) and pressed status (click/double-click/lift)
        return self.__record['currMouse'].btn,self.__record['currMouse'].pressStatus
    def Get_MoveDelta(self,total=True,strict=True):#Return the mouse movement amount (valid only when the mouse is pressed), which is a QPoint object
        press=self.__press
        record=self.__record
        data_curr=record['currMouse']
        if(data_curr.pressStatus!=press[0]):#Indicates that the mouse is pressed
            if(not strict or self.__move):#In strict mode, the movement amount is only calculated when movement occurs.
                p1=record['currMouse'].pos
                if(total):
                    p2=record['lastPress'].pos
                else:
                    p2=record['lastMouse'].pos
                return QPoint(p1.x()-p2.x(),p1.y()-p2.y())
        return QPoint(0,0)
    def Get_HasMoved(self): #Determine whether movement has occurred (after all, it is a bit troublesome to use Get_MoveDelta to determine the occurrence of movement. It is better to have one more function
        return self.__move

    def Opt_Update(self,event):#Update status
        press=self.__press
        record=self.__record
        data_curr=self.__Data(event)
        if(event.type()==press[1] or event.type()==press[2]):#Single/double click
            self.__move=False
            data_old=record['lastPress']
            data_curr.pressStatus=press[1]
            if(data_old.btn==data_curr.btn):# Press the same key
                if(data_curr.timeStamp-data_old.timeStamp<self.__doubleClickInterval):#Within the time interval
                    if(data_old.pressStatus!=press[2]):#No double-click
                        data_curr.pressStatus=press[2]#Double click
            record['lastPress']=data_curr
            record['lastMouse']=data_curr
            record['currMouse']=data_curr
            self.__timerID=self.startTimer(self.__longPressInterval)
        else:#Move/Lift
            data_curr.btn=event.buttons()
            data_curr.pressStatus=record['lastMouse'].pressStatus
            if(event.type()==press[0]):#Lift up
                if(data_curr.btn==Qt.NoButton):#Ensure that it is set to Release when no button is pressed
                    data_curr.pressStatus=press[0]
                    data_curr.btn=event.button()
            else:#Move(QMouseEvent.MouseMove)
                if(data_curr.pressStatus!=press[0] and not self.__move):#Determine whether dragging has occurred
                    delta=self.Get_MoveDelta(strict=False)
                    if(abs(delta.x()) + abs(delta.y())>self.__antiJitter):
                        self.__move=True
                        record['currMouse'].pos=record['lastPress'].pos
            record['lastMouse']=record['currMouse']
            record['currMouse']=data_curr

Test code and running results:

Some enumerations related to the mouse:
  • Click QMouseEvent.MouseButtonPress
  • Double-click QMouseEvent.MouseButtonDblClick
  • LiftQMouseEvent.MouseButtonRelease
  • Left buttonQt.LeftButton
  • Middle buttonQt.MidButton
  • Right clickQt.RightButton
#Main.py
importsys
from PyQt5.QtWidgets import QApplication,QWidget
from XJ_MouseStatus import *

class Test(QWidget):
    __mouseStatus=None
    def __init__(self,*arg):
        super().__init__(*arg)
        ms=XJ_MouseStatus()
        ms.longClick.connect(lambda:print("<LongClick!>"))
        self.__mouseStatus=ms
    def __EasyPrint(self):
        press={<!-- -->
            QMouseEvent.MouseButtonRelease:"Release",
            QMouseEvent.MouseButtonPress:"Press",
            QMouseEvent.MouseButtonDblClick:"DblClick",}
        button={<!-- -->
            Qt.LeftButton:'Left',
            Qt.MidButton:'Middle',
            Qt.RightButton:'Right',}
        tPoint=lambda point:(point.x(),point.y())
        tBtn=lambda btn:[button[key] for key in button if key & amp;btn]
        tBtnStatus=lambda status:(tBtn(status[0]),press[status[1]])

        ms=self.__mouseStatus
        pos=tPoint(self.mapFromGlobal(ms.Get_Position()))
        moveDelta=tPoint(ms.Get_MoveDelta())
        btnStatus=tBtnStatus(ms.Get_PressButtonStatus())
        print(f'pos{<!-- -->pos},\tdelta{<!-- -->moveDelta},\t{<!-- -->btnStatus[0]}-{<!-- -->btnStatus[1]}')
        if(btnStatus[1]=='Release'):
            print()
    def mousePressEvent(self,event):
        self.__mouseStatus.Opt_Update(event)
        self.__EasyPrint()
    def mouseMoveEvent(self,event):
        self.__mouseStatus.Opt_Update(event)
        self.__EasyPrint()
    def mouseReleaseEvent(self,event):
        self.__mouseStatus.Opt_Update(event)
        self.__EasyPrint()


if __name__=='__main__':
    app = QApplication(sys.argv)

    t=Test()
    t.show()

    sys.exit(app.exec())

Run results

This article was published on CSDN and may not be reproduced without personal permission: https://blog.csdn.net/weixin_44733774/article/details/134349820