0
votes

I'm working on a Qt/Qml application.

I'm currently trying to emulate drag behavior through Qt on drag QML elements (Listview, Flickable...) for tests purpose.

I have a specific issue that I wanted to solve with the most generic solution possible, my component is a non-interactive ListView, nested by an interactive ListView, nested itself with a MouseArea :

ListView {
    anchors.fill: parent
    interactive: false
    ListView {
        anchors.fill: parent
        MouseArea {
             ...
        }
    }
}

So. My idea was : take a QML object, local coordinates (x, y) where the movement starts, find it's most nested child at position and apply the movement (dx, dy) to this child. If I'm understanding correctly how QT/QML works, it should send the event to parent if not used by the child, and Flickable components should be able to detect drags and catch them.

void xx::touchAndDrag(QObject *object, const int x, const int y, const int dx, const int dy)
{

    timer = new QTimer(this);
    timerIteration = 0;
    deltaPoint = new QPointF(dx, dy);
    parentItem = qobject_cast<QQuickItem *>(object);
    item = parentItem;
    startPoint = QPointF(x, y);
    QPointF tempPoint = startPoint;

    for( ;; ) {
        //Find the most nested child at coordinate
        QQuickItem* child = item->childAt(tempPoint.x(), tempPoint.y());
        if(child) {
            item = child;
            tempPoint = child->mapFromItem(parentItem, tempPoint);
            qDebug() << "child found " << item;
        } else {
            break;
        }
    }

    timer->setInterval(movementDuration / nbIteration);
    qDebug() << "interval " << timer->interval();
    timer->setSingleShot(false);
    connect(timer, SIGNAL(timeout()), this, SLOT(mouseMove()));

    // Send press event at starting point
    QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, item->mapFromItem(parentItem, startPoint), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
    qDebug() << "press " << e;
    qApp->postEvent(item, e);

    timer->start();
}

void xx::mouseMove()
{
    timerIteration++;
    QMouseEvent *e;
    if(timerIteration < nbIteration) {
        int x = startPoint.x() + deltaPoint->x() * timerIteration / nbIteration;
        int y = startPoint.y() + deltaPoint->y() * timerIteration / nbIteration;

        QPointF point = QPointF(x, y);
        // Send moveEvent
        e = new QMouseEvent(QEvent::MouseMove, item->mapFromItem(parentItem, point), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
        qDebug() << "move! " << e ;
    }else {
        //End reached, send end event
        QPointF point = QPointF(startPoint.x() + deltaPoint->x(), startPoint.y() + deltaPoint->y());
        e = new QMouseEvent(QEvent::MouseButtonRelease, item->mapFromItem(parentItem, point), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
        timer->stop();
        qDebug() << "end! " << e ;
    }

    qApp->postEvent(item, e);
}

So... It is not working. What happens? From my tests:

-If I remove the nested child part, and give directly the interactive (to drag) QML Component (here the nested ListView), the result is good. But this is not a good solution for me, since I would have exactly to know which component should react. However, this seems to override the "interactive: false" of a component, which is a bad idea when the purpose is... to test the component.

-The mouse area receives all events. The mouseX property is updated. This is an issue. With non simulated event, MouseArea should receive the press event, some move event, then events should captured by the ListView / Flickable. Even worst, even if positions are way different (400px) between mousePress and mouseRelease events, Qt detects a MouseClicked event and triggers it on QML side...

So, not sure where to go from there. I could do some crappy child detection (checks the type of the nested child, and only accepts the good one), but I'm not very happy with it. Any idea?

1

1 Answers

0
votes

Ok, so I went a bit deeper in QT code, and I understood my mistake: To use sendEvent correctly, you have to pass them to the root view (on my project it is a QQuickView, but it can also be a QWindow). If you do not send the event on the root, it won't filter the mouseEvent as expected (through childMouseEventFilter() method)

I also discover than the MouseRelease event needs a NoButton parameter. So my code works now and looks like this:

    //Set mouse timer
    mouseTimer = new QTimer(this);
    mouseTimer->setInterval(m_movementDuration / m_nbIteration);
    mouseTimer->setSingleShot(false);
    connect(mouseTimer, SIGNAL(timeout()), this, SLOT(mouseMove()));
}

void xx::pressAndDrag(QObject *object, const int x, const int y, const int dx, const int dy)
{
    m_pressAndDragCompleted = false;

    //Reset timer iteration
    m_timerIteration = 0;

    //Keep all coordinates
    startPoint = ((QQuickItem *) object)->mapToScene(QPointF(x, y));
    deltaPoint = new QPointF(dx, dy);

    QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, startPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
    qApp->postEvent(parent(), e);

    //Start timer
    mouseTimer->start();
}

void xx::mouseMove()
{
    m_timerIteration++;
    if (m_timerIteration < m_nbIteration + 2) {
        //Move mouse
        int x = startPoint.x() + deltaPoint->x() * m_timerIteration / m_nbIteration;
        int y = startPoint.y() + deltaPoint->y() * m_timerIteration / m_nbIteration;

        QMouseEvent *e = new QMouseEvent(QEvent::MouseMove, QPointF(x, y), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );

        qApp->postEvent(parent(), e);
        // Let some time to not trigger a flick
    } else if (m_timerIteration - m_nbIteration >= 400 / mouseTimer->interval()) {
        //End movement
        QPointF point = QPointF(startPoint.x() + deltaPoint->x(), startPoint.y() + deltaPoint->y());
        QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
        qApp->postEvent(parent(), e);

        //Stop timer
        mouseTimer->stop();
        m_pressAndDragCompleted = true;
    }
}

If it can help. :)