Search code examples
pythonqtqmlpyside2

How to create mouse press or click events in QML/Pyside2 without actually pressing the mouse


For the project that I am doing, I need to be able to create "artificial" mouse events that only QML can see without physically pressing the mouse down.

In my current implementation, I am trying to use "key pressed" events to trigger these artificial mouse events. I am currently using the function "Qt.createQmlObject" to create a MouseEvent and then feed that as parameter for a mouse clicked signal. However, I keep getting an error back saying that "MouseEvent is not a type." I am open to different implementations.

import QtQuick.Controls 2.12
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.11
import QtGraphicalEffects 1.12
import QtMultimedia 5.12
import QtQml.Models 2.12
import "../components"

Item{
   id: widget
   width: 600
   height: 600

   //MouseArea
   MouseArea{
       id: mouseArea
       anchors.fill: parent
       propagateComposedEvents: true
       hoverEnabled: true
       
       onClicked: {
           console.log("The mouse was clicked")
           widget.focus = true 
           mouse.accepted = false
       }

       onPressed: {
           console.log("The mouse was pressed")
           mouse.accept = false
       }

       onEntered:{
           widget.focus = true               
       }
   }

   //Defines key event changes
   Keys.onPressed: {
       if(event.key == Qt.Key_0){
           var mouEvent = Qt.createQmlObject('import QtQuick 2.12; import QtQuick.Controls 2.12; MouseEvent{x: 0; y: 0; accepted: false; button: Qt.LeftButton; buttons: Qt.LeftButton; flags: 0; modifiers: Qt.NoModifier; source: Qt.MouseEventSynthesizedByApplication; wasHeld: false}', mouseArea, "error")
           mouseArea.clicked(mouEvent)
       }
   }
}

Solution

  • MouseEvent is not an element that can or should be created, in simple terms MouseEvent is a wrapper over QMouseEvent that allows obtaining the event information.

    Nor does invoking a signal imply that the event will be transmitted, instead you have to use the Qt Event System which cannot be used from QML but from C++ (in your case it is equivalent to python).

    So the logic is to create a class that notifies the event to Qt, and Qt has to send the event to the item through the window as shown below:

    from PySide2.QtCore import (
        QCoreApplication,
        QEvent,
        QObject,
        QPointF,
        Qt,
        QTimer,
        QUrl,
        Slot,
    )
    from PySide2.QtGui import QGuiApplication, QMouseEvent
    from PySide2.QtQuick import QQuickItem, QQuickView
    
    
    class MouseEmulator(QObject):
        @Slot(QQuickItem, Qt.MouseButton)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF, int)
        def mouseClick(self, item, button, modifier=Qt.NoModifier, pos=QPointF(), delay=-1):
            self.mousePress(item, button, modifier, pos, delay)
            self.mouseRelease(item, button, modifier, pos, 2 * delay)
    
        @Slot(QQuickItem, Qt.MouseButton)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF, int)
        def mousePress(self, item, button, modifier=Qt.NoModifier, pos=QPointF(), delay=-1):
            self._send_mouse_events(
                QEvent.MouseButtonPress, item, button, modifier, pos, delay
            )
    
        @Slot(QQuickItem, Qt.MouseButton)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF, int)
        def mouseRelease(
            self, item, button, modifier=Qt.NoModifier, pos=QPointF(), delay=-1
        ):
            self._send_mouse_events(
                QEvent.MouseButtonRelease, item, button, modifier, pos, delay
            )
    
        @Slot(QQuickItem, Qt.MouseButton)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF)
        @Slot(QQuickItem, Qt.MouseButton, Qt.KeyboardModifier, QPointF, int)
        def mouseDClick(
            self, item, button, modifier=Qt.NoModifier, pos=QPointF(), delay=-1
        ):
            self.mousePress(item, button, modifier, pos, delay)
            self.mouseRelease(item, button, modifier, pos, 2 * delay)
            self.mousePress(item, button, modifier, pos, 3 * delay)
            self._send_mouse_events(
                QEvent.MouseButtonDblClick, item, button, pos, 4 * delay
            )
            self.mouseRelease(item, button, modifier, pos, 5 * delay)
    
        def _send_mouse_events(self, type_, item, button, modifier, pos, delay):
            window = item.window()
            if pos.isNull():
                pos = item.boundingRect().center()
            sp = item.mapToScene(pos).toPoint()
            event = QMouseEvent(
                type_, pos, window.mapToGlobal(sp), button, button, modifier
            )
            if delay < 0:
                delay = 0
    
            def on_timeout():
                QCoreApplication.instance().notify(window, event)
    
            QTimer.singleShot(delay, on_timeout)
    
    
    def main():
        import os
        import sys
    
        CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
        app = QGuiApplication(sys.argv)
        mouse_emulator = MouseEmulator()
        view = QQuickView()
        view.rootContext().setContextProperty("mouse_emulator", mouse_emulator)
        filename = os.path.join(CURRENT_DIR, "main.qml")
        view.setSource(QUrl.fromLocalFile(filename))
        view.show()
    
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    
    import QtQuick 2.12
    
    Item{
       id: widget
       width: 600
       height: 600
    
       MouseArea{
           id: mouseArea
           anchors.fill: parent
           propagateComposedEvents: true
           hoverEnabled: true
           
           onClicked: {
               console.log("The mouse was clicked")
               widget.focus = true 
               mouse.accepted = false
           }
    
           onPressed: {
               console.log("The mouse was pressed")
               mouse.accept = false
           }
    
           onEntered:{
               widget.focus = true               
           }
       }
    
       Keys.onPressed: {
            if(event.key == Qt.Key_0){
                mouse_emulator.mouseClick(widget, Qt.LeftButton)
            }
       }
    }