1

Context: We are developing an IDE for VR/AR effects editing. Users would like to have MACRO recording. Something similar to Selenim/AutoIT in spirit, but specific for this app with deep dataModel integration. Users are trained JS developers. Software GUI is Qt/QML (mostly qml)

Current Blocker

I need a way to uniquely identify a specific QQuickItem by its objectName:

  • across multiple restart of the app.
  • across platform (OS/Cpu/...)
  • name should be human readable enough for use in JS Macro
  • Should serialise enough information from QQuickItem/QEvent to post a valid event

It is acceptable to use CI static analysis to ensure all item have a valid unique name and detect name changes. The problem here is some button, for instance the number buttons have exactly the same fqn/path/name unless I use and ugly hack, renaming it by the text of its sub-component:

Component.onCompleted: {
    objectName = "button." + button.text
}

property bool keepObjectName: true

Here a fully qualified name based on metaObject()->className() is not enough, as it wont be unique. qml ids are not usable as per documentation.



Question: Is there a way in Qt to obtain such unique identifier?



Related Question:

Qt GUI event recording and playback

Abandoned Road:

print trace of signals/slots called

Reason: Most events in IDE do not use connect() the standard Qt way. Too Low level.



Draft Technical Solution / POC (mostly for reference)

https://doc.qt.io/qt-5/qtdoc-demos-calqlatr-example.html

Using the Qt QML calculator example here. Added this to main.cpp:

#include "Filter.h"
#include <unordered_map>

std::unordered_map<QString,QQuickItem*> map;

void install(QQuickItem* root, KeyPressEater* keyPressEater, const QString& fqn){
    if(!root->property("keepObjectName").isValid() || !root->property("keepObjectName").value<bool>()){
        qDebug("setObjectName: %s", root->metaObject()->className());
        qDebug("Object original name:%s", root->objectName().toStdString().c_str());
        root->setObjectName(root->metaObject()->className());
    }
    else{
        qDebug("unchanged Object Name: %s", root->objectName().toStdString().c_str());
    }
    auto fqnRoot = fqn.isEmpty() ? root->objectName() : fqn + "." + root->objectName();
    qDebug("Object fqn: %s", fqnRoot.toStdString().c_str());
    root->installEventFilter(keyPressEater);
    keyPressEater->insert(root,fqnRoot);

    for(auto* child: root->childItems()){
        QString fqn = fqnRoot + "." + child->metaObject()->className();
        install(child, keyPressEater,fqnRoot);
    }
    qDebug("\n\n\n");
}

Filter.h looks like this:

#ifndef FILTER_H
#define FILTER_H

#include <QQuickItem>
#include <QtCore>
#include <unordered_map>
#include <QQuickItem>

class KeyPressEater : public QObject
{
    Q_OBJECT
public:
    void insert(QQuickItem* item,const QString& str){
        if(map2.find(str) != map2.end())
        {
            qDebug("Duplicate object name:%s", str.toStdString().c_str());
        }
        else{
            qDebug("Object name:%s", str.toStdString().c_str());
            map.insert({item,str});
            map2.insert({str,item});
        }
    }
    void play(){
        playing = true;
        for(auto p: records){
            //p.second->event(p.first);
            QCoreApplication::postEvent(p.second, p.first);
            QCoreApplication::sendPostedEvents();
        }
        playing = false;
    }
protected:
    bool eventFilter(QObject *obj, QEvent *event)
    {
        if (event->type() == QEvent::MouseButtonPress) {
            //QMouseEvent *keyEvent = static_cast<QMouseEvent *>(event);
            qDebug("click");
            QQuickItem *item = static_cast<QQuickItem *>(obj);
            qDebug("%s",map[item].toStdString().c_str());
            qDebug("%s",item->metaObject()->className());

            if(!playing){
                records.push_back({event->clone(),item});
            }

            return false;
        } else {
            // standard event processing
            if(event->type() == QEvent::KeyPress){
                QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
                if(keyEvent->key() == 32){
                    play();
                }

            }
            return QObject::eventFilter(obj, event);
        }
    }

    std::unordered_map<QQuickItem*,QString> map;
    std::unordered_map<QString,QQuickItem*> map2;
    std::atomic<bool> playing = false;
    std::vector<std::pair<QEvent*,QObject*>> records;
};
#endif // FILTER_H

How to use:

Copy the above the main.cpp and new filter.h file in the calculator app example. Call install in main function:

install(view.rootObject(), &keyPressEater,"");
user2346536
  • 1,464
  • 2
  • 21
  • 43
  • 1
    What about adding the index? Like: `QString fqn = fqnRoot + "." + child->metaObject()->className() + root->childItems().indexOf(child);`. Obviously this might lead to confusion when "numeric" buttons aren't ordered, so maybe use hex or something – Amfasis Feb 11 '22 at 09:18
  • @Amfasis, true, in most part it would be stable. But the number button at index 1 has the number 7 written on it, so it could be very confusing. – user2346536 Feb 11 '22 at 09:59
  • 1
    Yes, maybe you can set the objectName property in QML to "7" and then this code would rewrite to the full FQN including that QML-given property? – Amfasis Feb 11 '22 at 10:27
  • @Amfasis , this is mostly what I am doing right now. but It will be cumbersome for developers to always think of setting a reasonable object name onCompleted, ensure it is unique, stable and readable. But I guess If no-one comes with a magic alternative, it will be the way... – user2346536 Feb 11 '22 at 10:32
  • 1
    You can just set the objectName for the items where it is confusing? And you make it unique by adding the parents names through your above code – Amfasis Feb 11 '22 at 11:14
  • @Amfasis, yes this is what I will likely end up doing unless I find a better way – user2346536 Feb 11 '22 at 12:44

0 Answers0