C++ QT Call C++ function in QJSEngine - c++

I'm trying to call a function which is implemented in C++ to call in a JS function, which gets evaluated with the QT QJSEngine.
So far I have tried this
class Person : public QObject {
Q_OBJECT
public:
Q_INVOKABLE cppFunction(int a);
};
My function evuation looks like this
QJSValue formulaFunction = jsEngine->evaluate("(function(" + functionArgList.join(",") + "){ " + formula + "})");
In formula I have a call to cppFunction and functionArgList provides the arguments, these work fine, I have checked them.
So, but somehow it doesn't get called, I don't get an error too. Am I missing something or should it work this way?

So, after some research I got it running.
class MyObject : public QObject{
Q_OBJECT
public:
Q_INVOKABLE MyObject() {}
Q_INVOKABLE bool hasChannel(int id);
};
QJSEngine engine;
QJSValue injectedObject;
injectedObject= engine.newQMetaObject(&MyObject::staticMetaObject);
engine.globalObject().setProperty("MyObject", injectedObject);
So, here I'm creating a new class which is inheriting from QObject.
The class has two methods, every method I want to invoke in the QJSEngine has to provide the Q_INVOKABLE Label in front of the method declaration.
An example JS code run by the engine would look like this
let myObject = new MyObject();
myObject.hasChannel(1234);
I don't know if the injected prefix is appropriate, but I couldn't think of a better one

Related

QJSEngine - exposing classes and throwing errors

I am trying to create a standard JS library that is mostly shaped like Qbs (which uses deprecated QScriptEngine) with QJSEngine, so people who make Qt software can add things like file-operations to their plugin JS environment.
You can see the repo here
I've got basic classes exposed to the JS engine, like this:
QJSEngine jsEngine;
jsEngine.installExtensions(QJSEngine::AllExtensions);
jsEngine.globalObject().setProperty("BinaryFile", jsEngine.newQMetaObject(&Qbs4QJS::BinaryFile::staticMetaObject));
but I can's seem to figure out how to get a reference to the QJSEngine, inside a function, so I can throw an error:
Q_INVOKABLE BinaryFile(const QString &filePath, QIODevice::OpenModeFlag mode = QIODevice::ReadOnly) {
m_file = new QFile(filePath);
if (!m_file->open(mode)) {
// how do I get jsEngine, here
jsEngine->throwError(m_file->errorString());
}
}
I'd like it if I could somehow derive the calling engine from inside the function, so the class could be exposed to several separate engine instances, for example.
I saw QScriptable and it's engine() method, but couldn't figure out how to use it.
I added
Depends { name: "Qt.script" }
in my qbs file, and
#include <QtScript>
but it still isn't throwing the error with this (just fails silently):
#include <QObject>
#include <QString>
#include <QFile>
#include <QIODevice>
#include <QFileInfo>
#include <QtScript>
namespace Qbs4QJS {
class BinaryFile : public QObject, protected QScriptable
{
Q_OBJECT
public:
Q_ENUM(QIODevice::OpenModeFlag)
Q_INVOKABLE BinaryFile(const QString &filePath, QIODevice::OpenModeFlag mode = QIODevice::ReadOnly) {
m_file = new QFile(filePath);
// should check for false and throw error with jsEngine->throwError(m_file->errorString());
if (!m_file->open(mode)){
context()->throwError(m_file->errorString());
}
}
private:
QFile *m_file = nullptr;
};
} // end namespace Qbs4QJS
I may be confused about it, too, but it seems like it's using QScriptEngine, which I'm trying to get away from.
What is the best way to accomplish the task of adding a class that QJSEngine can use, which has cpp-defined methods that can throw errors in the calling engine?
The object under construction does not have any association with QJSEngine yet. So you can only do one of the following alternatives:
Store the engine instance in a static variable if you can ensure that there is only ever one instance of QJSEngine in your whole application.
Store the engine instance in a thread-local variable (QThreadStorage) if you can ensure that there is only one engine per thread.
Set the current active engine in the current thread right before evaluating your JS code since. This might be the easiest and yet robust solution.
Retrieve the engine from a QJSValue parameter.
Implement a JS wrapper for the constructor
Solution 4.: Passing the engine implicitly via a QJSValue parameter.
I assume that your throwing constructor always has a parameter. QJSValue has a (deprecated) method engine() which you then could use. You can replace any parameter in a Q_INVOKABLE method with QJSValue instead of using QString and friends.
class TextFileJsExtension : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE TextFileJsExtension(const QJSValue &filename);
};
TextFileJsExtension::TextFileJsExtension(const QJSValue &filename)
{
QJSEngine *engine = filename.engine();
if (engine)
engine->throwError(QLatin1String("blabla"));
}
I guess there is a reason why it is deprecated, so you could ask the QML team, why and what alternative you could use.
Solution 5 Implement a JS wrapper for the constructor
This builds upon solution 4. and works even for parameter-less constructors. Instead of registering your helper class directly like this:
engine->globalObject().setProperty("TextFile", engine->newQMetaObject(&TextFile::staticMetaObject));
You could write an additional generator class and a constructor wrapper in JS. Evaluate the wrapper and register this function as the constructor for your class. This wrapper function would pass all desired arguments to the factory method. Something like this:
engine->evaluate("function TextFile(path) { return TextFileCreator.createObject(path);
TextFileCreator is a helper class that you would register as singleton. The createObject() method would then finally create the TextFile object and pass the engine as a paremter:
QJSValue TextFileCreator::createObject(const QString &path)
{
QJSEngine *engine = qmlEngine(this);
return engine->createQObject(new TextFile(engine, filePath));
}
This gives you access to the QJSEngine in the TextFile constructor and you can call throwError().

Qt - C++ - Function of the child accessed through parent with connect

Sorry if the title isn't really explicit. Here is the example, a classA has several child, childA1, childA2 etc.. stored in a list of classA :
QList<ClassA> listA;
listA << childA1() << childA2();
The childA1 has the function start() which doesn't exist in classA. So if I try :
listA[0].start();
The compiler says that classA has no member start().
start() being a public slot, I've done :
connect(this, SIGNAL(signalStart()), listA[0], SLOT(start()));
And it's working. So my question is how can he know this function start() after being stored as classA in the list ? Is there a way to know the "original" type ?
Qt signals/slots work using introspection (the ability to list methods and properties of any QObject at runtime).
Qt utilizes the Meta-Object Compiler tool to have introspection capabilities in C++. You may want to have a look at this blog post for more information on the internals of Qt signals/slots.
You can use introspection without signals/slots. So, in your example, You don't need to define the signal signalStart() just to be able to invoke the start() method. Instead, You can do something like this:
QMetaObject::invokeMethod(listA[0], "start");
This will look for the function named start in the specified QObject (regardless of the pointer type), and invoke it.
Here is a fully working example of using introspection:
#include <QtCore>
QTextStream& qOut(){static QTextStream out(stdout); return out;}
class MyObject : public QObject{
Q_OBJECT
public:
explicit MyObject(QObject* parent= nullptr):QObject(parent){}
~MyObject(){}
//Object has a slot named func
Q_SLOT void func(){ qOut() << "Hello func!\n"; }
};
int main(int , char*[]){
QObject* object = new MyObject();
//print the object's class name (this is the real class name!)
qOut() << object->metaObject()->className() << "\n";
//look for a function named func in the object, and invoke it
QMetaObject::invokeMethod(object, "func");
return 0;
}
//run MOC on this CPP file
#include "main.moc"
Firstly, you should store pointers of your objects in QList.
Then you could use dynamic_cast to determine which child type your object is:
A *obj = listA[0];
if (A1 *a1 = dynamic_cast<A1 *>(obj)) {
// This is an A1 object, you can call start()
a1->start();
} else if (A2 *a2 = dynamic_cast<A2 *>(obj)) {
// This is an A2 object
}

How to build a generic method to connect the notifySignal of different Q_PROPERTY types to a void slot(QVariant) from the property char * name?

I'm trying to write a method, with two parameters : the Q_PROPERTY name (char *) and the QObject * associated with, that permit to connect the notifySignal (if exists) of the Q_PROPERTY, to a void slot(QVariant), or to a slot dynamically builded which will call a void method(QVariant). The signature of the signal can vary according to the type of the parameter.
How can I achieve that in Qt 5? Maybe it's impossible, but I will not stop searching while I'm not sure of that.
So I think I have 3 solutions:
building dynamically a slot of the exact signature of the signal, from the signal name, and call a method(QVariant) in it, using the old Qt connect way:
connect(sender, SIGNAL (valueChanged(QString,QString)),
receiver, SLOT (updateValue(QString)) );
using the new Qt 5 connect system:
connect(sender, &Sender::valueChanged,receiver, &Receiver::updateValue );
building all slots signatures that can be used with QVariant.
Althougt, I don't know how to build a slot dynamically that call a specified method for the first solution ; I don't know how to retrieve the function pointer from the QMetaMethod of the notifySignal, for the second solution ; maybe the last solution is the best way, and easy to achieve, but it seams a bit extreme.
What do you think about it?
For "building a slot dynamically" there are private APIs in Qt (look for QMetaObjectBuilder or similar classes). However that doesn't solve the problem of the connection.
Note that QObject::connect is overloaded to take QMetaMethods as signals and slots. So you can easily build a "receiver" class object (for the only purpose of remembering which property it's acting upon):
class Receiver : public QObject {
Q_OBJECT
public:
explicit Receiver(const char *property, QObject *parent = 0)
: QObject(parent),
m_property(property)
{}
signals:
void propertyChangedForObject(const char *property, QObject *object);
public slots:
void onPropertyChanged() {
emit propertyChangedForObject(m_property, sender());
}
private:
const char * const m_property;
};
And hook it up in your method.

Compiler recognizing wrong function with same name

Im implementing a new slot which just has to call the method reset(). My new class is subclassing QAbstractListModel in which QAbstractListModel::reset() exists.
//stationlist.h
class StationListModel : public QAbstractListModel
{
Q_OBJECT
...
public slots:
void dataChanged();
//stationlist.cpp
...
void StationListModel::dataChanged()
{
reset();
}
However, in the implementation the method reset() is recognized as QTextStream::reset() and doesn't compile because of this. What could be the cause for such behaviour?
Thanks to the comment, the conclusion is that the method QAbstractListModel::reset() doesn't exist.
It is here only still available fer backwards compatibility http://qt-project.org/doc/qt-5.1/qtcore/qabstractitemmodel-compat.html#reset.
I believe that QTextStream::reset() is just something that the QtCreator offered as a global autocomplete.
The solution is to use non deprecated method.

Create a custom slot in C++, Qt5

in python we write custom slots quite easily by passing in the function to be called when a signal is generated.
While in C++ connect function requires us to pass the address of the slot function or so i figured. How do i do that. I tried using this but did'nt work.
Python code::
class imviu(QtGui.QWidget):
def __init__(self):
super(imvui,self).__init__()
self.btn=QtGui.QPushButton('Browse')
btn.clicked.connect(self.openimg)
def openimg(self):
#do something
C++ code::
class imviu: public QWidget
{
public:
imviu(QWidget *parent=0);
QPushButton *btn=new QPushButton("Browse");
void openimg(void);
};
imviu::imviu(QWidget *parent)
:QWidget(parent)
{
connect(btn, SIGNAL(clicked()),this,SLOT(openimg()));//this does'nt work:QObject::connect: No such slot QWidget::openimg()
}
void imviu::openimg()
{
//do something
}
In order to use signals and slots, you need to have the Q_OBJECT macro in your class as well as identifying which functions should be the signals and the slots. Have a look at the documentation for a more in-depth explanation.
After this, you need to set up the project file so that MOC can generate the necessary code.
Your class definition should look like this:
class imviu: public QWidget
{
Q_OBJECT
public:
imviu(QWidget *parent=0);
public slots:
void openimg();
private:
QPushButton *btn;
};