QJSEngine - exposing classes and throwing errors - c++

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().

Related

C++ QT Call C++ function in QJSEngine

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

Access QML root object from C++ QML type

I have a custom QML type written in C++, the classname is MyCustomType and it's located in the files mycustomtype.h and mycustomtype.cpp.
In the main.cpp file the QML type is made available:
qmlRegisterType<MyCustomType>("MyCustomType", 1, 0, "MyCustomType");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("../qml/main.qml")));
In the main.cpp file I can get access to the engine's root object like this:
rootObject = static_cast<QQuickWindow *> (engine.rootObjects().first());
My issue is that I need access to that rootObject from within the MyCustomType class in the mycustomtype.cpp file. Is that possible?
The only way I could think of was to pass the rootObject to the constructor. But since the MyCustomType is instatiated in the QML document, and not in C++ code, this solution won't work.
Any ideas?
I found a solution based on GrecKo's comment.
Instead of making MyCustomType extend QObject, I made it extend QQuickItem. Then it's possible to call window() from anywhere in that class and get the root object. This is simple and it works.
mycustomtype.h:
class MyCustomType : public QQuickItem
{
Q_OBJECT
public:
explicit MyCustomType(QQuickItem *parent = 0);
}
mycustomtype.cpp
MyCustomType::MyCustomType(QQuickItem *parent) : QQuickItem(parent)
{
QQuickWindow *rootObject = window();
}
Method 1: Passing the root object
You can use setContextProperty to make a variable globally available in QML:
engine.rootContext ().setContextProperty("rootObject", QVariant::fromValue(engine.rootObjects().first()));
Now you can access the root object in QML using rootObject and pass it to MyCustomType during construction.
Method 2: Make the root object statically available
You can make the root object statically available. One clean solution is to create a singleton class with all your global data members.

How do I get ui from a widget in order to connect it in another class?

I need to connect a QPushButton (startListeningPushButton) from my StartWindow to a slot in my MainController. I still have a few questions:
Should I make a pointer of Ui::startWidget ui, because by default Qt created it as a normal variable?
Is getStartWindow() the right way to get the StartWindow from ViewController?
What would be the right way to get startListeningPushButton from StartWindow (is my getter right)?
This is my code:
MainController.cpp:
MainController::MainController()
{
connect(m_viewController.getStartWindow()->getStartListeningPushButton, &QPushButton::clicked, this, &MainController::bla)
}
ViewController.cpp:
StartWindow* ViewController::getStartWindow()
{
return &startWindow;
}
StartWindow.cpp:
QPushButton* StartWindow::getStartListeningPushButton()
{
return ui->fStartListeningPushButton;
}
StartWindow.h:
#ifndef STARTWINDOW_H
#define STARTWINDOW_H
#include "ui_startwindow.h"
class StartWindow : public QWidget
{
Q_OBJECT
public:
StartWindow(QWidget *parent = 0);
~StartWindow();
QPushButton* getStartListeningPushButton();
private:
Ui::startWidget *ui;
};
#endif // STARTWINDOW_H
If you are using Qt Designer and Qt IDE generated such code that it's object not a pointer I don't think that you should make it pointer.
Yeah, returning a pointer to QWidget (StartWindow in your case) is pretty OK.
Your getter is OK.
Seems like you have mistype in your connect, it should look like this:
QObject::connect(m_viewController.getStartWindow()->getStartListeningPushButton(), SIGNAL(clicked()),
this, SLOT(bla()));
It's unclear if you have and then what is your problem.
The only thing I doubt would work is the first parameter of your call to connect:
m_viewController.getStartWindow()->getStartListeningPushButton should actually be m_viewController.getStartWindow()->getStartListeningPushButton() (to have the function be called so that you get the pointer to expected QPushButton and pass it to the connect function).
In the connect function:
First and third parameter must be of type QObject*. So this can either be this pointer (if current class is derived from QObject), or any class attribute of type QObject* (ui->fStartListeningPushButton) or a function call returning QObject* (m_viewController.getStartWindow()->getStartListeningPushButton()). But m_viewController.getStartWindow()->getStartListeningPushButton (with no ()) does not make sense here).
Second parameter must be a signal (declared in header file class using signals: keyword. You don't need implement any code here, you just declare the signal and Qt MOC mechanism implements it silently). Valid syntax for this parameter is &QPushButton::clicked or SIGNAL(clicked()) (Qt4 syntax, still valid in Qt5).
Fourth parameter must be a slot (declared in header file class using slots: keyword, and implemented by you). Valid syntax for this parameter is &MainController::bla or SLOT(bla()) (Qt4 syntax, still valid in Qt5).
There's actually a fifth optional parameter to use when you'll start dealing with threads.

C++ Creating Global Reference of an Object

I am trying to create a global reference of an object but it seems fails or I am getting another error in Qt C++.
I have a class called 'System' which holds many objects as members. I want to access System's members from everywhere include members of System. Here is my code below:
// System.h
class System
{
public:
Obj1* m_obj1;
Obj2* m_obj2;
System();
~System();
static System* GetGlobalReference();
}
// System.cpp
static System* GlobalReference = 0;
System::System()
{
if (!GlobalReference) GlobalReference = this;
m_obj1 = new Obj1();
m_obj2 = new Obj2();
}
System* System::GetGlobalReference()
{
return GlobalReference;
}
// main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
System* system = new System();
MainWindow window;
window.showMaximized();
return app.exec();
}
//Obj1.h
class Obj1 : public QObject
{
Q_OBJECT
public:
Obj1() : QObject() {}
~Obj1();
public slots:
void Import();
}
// Obj1.cpp
void Obj1::Import()
{
QString path = QFileDialog::getOpenFileName(
0,
QString("Import file..."),
QString("C:\\"),
QString("JPEG File (*.jpg)"),
0,
0);
if (System::GetGlobalReference())
System::GetGlobalReference()->m_obj2->Import(path); // error here
else
// System::GlobalReference is null
}
It seems reference is not null but I get an error during runtime "Access violation reading location..." What is wrong?
Btw Obj1 is a QObject and Import method is a public slot, can the error be related with this?
Edit: Debuugger last step is here in QGenericAtomic.h
T load(const T &_q_value) Q_DECL_NOTHROW
{
return _q_value; // -> Debugger stops here
}
Edit2: I've used Singleton pattern as the answers says but my problem still continues.
System::GetInstance()->GetObj1()->Import(path); // after this line
in "_q_value" it says ""
If you wish to have global variables, I would recommend using a singleton instead.
Global variables in C++ are declared using extern, not static. See the reference for more information.
If you want only one instance of your System class, you should use the Singleton pattern.
But, the Singleton pattern should be used when you want an unique instance of a class, the reason should not be when you want to have an object global. Even if using this pattern, your instance is accessible from everywhere.
Look at this article about Singleton design pattern, it may be useful in your case.
Also, in C++ the declaration of global variable is done with extern, not static.
I've solved my problem. The problem was caused by Obj1->Import method but during debug in qt, debugger is not accessing inside the method when I press F11(Step Into). I cannot figure it out why?

Object::connect: No such signal when obj is returned as shared_ptr

Using Qt's signal & slot, I get error:
Object::connect: No such signal FvOverlayPolygon::mouseHoveredOnElemSig(rf::AbstractFvOverlay*)
My other connects work fine and I've checked everything I can think of (refered to 20 ways to debug Qt signals and slots too). Because I personally for the first time use shared_ptr for Qt for this sample, I suspect there might be something wrong in how I use shared_ptr. I really appreciate your opinions.
concreteFvOverlay.cpp
#include "rf_common/abstractFvOverlay.h"
void FvScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
rf::AbstractFvOverlay::Ptr item_overlay;
item_overlay = this->createOverlay(myItemMenu, &pixmap_overlay); // this inherits Class A
bool con1 = connect(item_overlay.get(), SIGNAL(mouseHoveredOnElemSig(rf::AbstractFvOverlay*)), this, SLOT(mouseHoveredOnElem(rf::AbstractFvOverlay*)));
}
}
This overlay is instantiated in this:
abstractFvOverlay.cpp
boost::shared_ptr<rf::AbstractFvOverlay> FvPolygonScene::createOverlay(QMenu *menu, QPixmap *pixmap_overlay)
{
return boost::shared_ptr<rf::AbstractFvOverlay>(new FvOverlayPolygon(menu, *pixmap_overlay));
}
overlay.h
#include <QGraphicsItem>
#include <boost/shared_ptr.hpp>
class AbstractFvOverlay : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
AbstractFvOverlay(QMenu *contextMenu, QGraphicsItem *parent, QGraphicsScene *scene);
virtual ~AbstractFvOverlay();
typedef boost::shared_ptr<AbstractFvOverlay> Ptr;
signals:
void mouseHoveredOnElemSig(AbstractFvOverlay *i);
For your interest, the reason I use shared_ptr here is I want to do interface-based programming (not sure if this is an official way to call this style but what I mean is defining behavior in abstract classes and only for some behaviors I describe them in concrete classes, which Java allow).
You must use full scope names of types in signal declaration even if you're in same scope. Replace signal void mouseHoveredOnElemSig(AbstractFvOverlay *i); with void mouseHoveredOnElemSig(rf::AbstractFvOverlay *i); or use AbstractFvOverlay without scope in your connect.