Connecting QQuickImageProvider - c++

I have to connect a QQuickImageProvider with a class to pass an image that the image provider must return, but I am not finding a way to do that.
I have a class called provedorImagem.cpp with a virtual requestImage function implemented and I have also a class called processaImagem.cpp that is the class to perform modification at the image.
The provedorImagem class is passed to engine as a provider: engine.addImageProvider("provedor", provedorImg) in main.cpp
What I need is a way to connect a slot in the provider in main.cpp with a signal in processaImagem.cpp. Doing that the processaImagem.cpp can send the image I must return to Qml to provedorImagem.cpp and send it back to Qml.
Could someone help me?
Code below
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "processaimagem.h"
#include "provedorimagem.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");
QQmlApplicationEngine engine;
provedorImagem *provedorImg = new provedorImagem;
//------------ I have to create a connection here between the provider slot and a signal in processaImagem with the image to provide -----------------
engine.addImageProvider("provedor", provedorImg);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
processaImagem.h
#ifndef PROCESSAIMAGEM_H
#define PROCESSAIMAGEM_H
#include <QObject>
#include <QImage>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickImageProvider>
#include "provedorimagem.h"
class processaImagem : public QObject
{
Q_OBJECT
public slots:
QString recebeImagem(const QString &caminho);
public:
processaImagem(QObject *parent = 0);
QImage carregaImagem(const QString &caminho);
signals:
void enviaImagem(QImage);
};
#endif // PROCESSAIMAGEM_H
processaImagem.cpp
#include "processaimagem.h"
#include <QDebug>
processaImagem::processaImagem(QObject *parent)
{
}
QString processaImagem::recebeImagem(const QString &caminho)
{
QImage imagem = this->carregaImagem(caminho);
QString caminhoRetorno;
if(imagem.isNull())
{
qDebug() << "Erro ao receber a imagem";
}
else
{
qDebug() << "Imagem recebida";
caminhoRetorno = "image://provedor/imagemEditada";
}
return caminhoRetorno;
}
QImage processaImagem::carregaImagem(const QString &caminho)
{
QUrl caminhoImagem(caminho);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageSize;
QString imageId = caminhoImagem.path().remove(0, 1);
QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);
if(imagem.isNull())
{
qDebug() << "Erro ao carregar a imagem";
imagem = QImage();
}
else
{
qDebug() << "Imagem carregada";
}
return imagem;
}
provedorimagem.h
#ifndef PROVEDORIMAGEM_H
#define PROVEDORIMAGEM_H
#include <QImage>
#include <QQuickImageProvider>
class provedorImagem : public QQuickImageProvider
{
public:
provedorImagem();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
void carregaImagem();
public slots:
void carregaImagem(QImage imagemRecebida);
private:
QImage imagem;
};
#endif // PROVEDORIMAGEM_H
provedorimagem.cpp
#include "provedorimagem.h"
#include <QDebug>
provedorImagem::provedorImagem() : QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage provedorImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
if(imagem.isNull())
{
qDebug() << "Erro ao prover a imagem";
}
else
{
qDebug() << "Imagem provida";
}
return imagem;
}
void provedorImagem::carregaImagem(QImage imagemRecebida)
{
imagem = imagemRecebida;
}

Answering my own question
Problem solved. Here is the solution step by step:
1 - Create a class that inherits from QQuickImageProvider and QObject and inside it create a Image member (QImage) that is the image to be provided.
class provedorImagem : public QObject, public QQuickImageProvider
Implement the virtual requestImage method. This is the method that will return the image to Qml
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)
Create a method to load the provider’s image to return
void provedorImagem::carregaImagem(QImage imagemRecebida)
{
imagem = imagemRecebida;
}
Now set it as the engine image provider in the main.cpp file
provedorImagem *provedorImg = new provedorImagem;
engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);
2 - Create another class that inherits from QObject.
class processaImagem : public QObject
Inside this class you must implement a method that will get the image from camera provider, perform the image modifications and return the modified image.
PS: The p_caminhoImagem is a property that I created inside the processaImagem class that receives the camera preview path.
QImage processaImagem::carregaImagem()
{
QUrl caminhoImagem(p_caminhoImagem);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageSize;
QString imageId = caminhoImagem.path().remove(0, 1);
QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);
if(imagem.isNull())
{
imagem = QImage();
}
else
{
//Perform the modifications
}
return imagem;
}
3 - Now is the main part. The image requestImage provider method must receive the modified image from the processaImagem class to provide it to QML. To do it the provider class pointer must be accessible to the QML file, so, in the main.cpp file just make the pointer available to QML as a property
engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);
and register the processaImagem class as a QML type
qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");
Now we link it inside the QML file
ProvedorImagem.carregaImagem(processaImagem.carregaImagem());
4 - It is done. Now just request the image from the provider:
imagemPreview.source = "image://provedor/imagemEditada_" + camera.numeroImagem.toString();

I hope you are trying to send a image from qt(c++) code to display in qml.
Please look the below camera application source. Passing the camera image to qml using QQuickPaintedItem.
https://github.com/econsysqtcam/qtcam.git
Have a look at videostreaming.cpp/ videostreaming.h and their connection to qml. Hope that helps

For image provider to work one of the below methods needs to be implemented:
virtual QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize);
virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize);
virtual QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize);
Derived from the example above.
QImage provedorImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
// the id has value like "imagemEditada" or the last word in URL
if(imagem.isNull())
{
qDebug() << "Erro ao prover a imagem";
}
else if (caminhoRetorno.endsWith(id, Qt::CaseInsensitive)) // that will be a check for id after "provedor"
{
qDebug() << "Imagem provida";
return imagem; // provide an image
}
return QImage(); // no match with id from url
}
And your QML code should have something like:
Image {
// // //
source: "image://provedor/imagemEditada"
}
There is a nice complete example in Qt docs.
#GuiDupas, from your corrected question it seems that you would like to recognize the image provided. The method to check the last word in URL with QString::endsWith is for the demo of course.

Related

Is there a way to update my image included in a QQuickImageProvider based class from another c++ with a signal or something similar?

I have a imageprovider object based in QQuickImageProvider class that with a requestImage function generates a QR image that is created with a qrencode library. That Image painted is showed in a qml page that I show in a display.
The code works well and paints the QR Image successfully whith the default string, but I want to update or refresh the QR image everytime I receive a new string to encript and show in display.
QQuickImageProvider doesn't have a public function to connect from another object, or maybe I don't know if exists.
There is a way to update the image everytime I receive a new data event?
There is a similar question in Example but I can't understand the way is resolved.
Here is the code I use:
imageprovider.h
#ifndef IMAGE_PROVIDER_H
#define IMAGE_PROVIDER_H
#include <QQuickImageProvider>
#include <QPixmap>
#include <QPainter>
class ImageProvider : public QQuickImageProvider
{
public:
ImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
void loadData(QString newdata);
private:
QString data;
};
#endif // IMAGE_PROVIDER_H
imageprovider.cpp
#include "imageprovider.h"
#include <QPainter>
#include <qrencode.h>
ImageProvider::ImageProvider() : QQuickImageProvider(QQuickImageProvider::Image) {
}
QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) {
int width = 120;
int height = 120;
QImage img(requestedSize.width() > 0 ? requestedSize.width() : width,
requestedSize.height() > 0 ? requestedSize.height() : height,
QImage::Format_RGB32);
//QRcode *qr = QRcode_encodeString("HELLO WORLD", 1, QR_ECLEVEL_L, QR_MODE_8, 1);
QRcode *qr = QRcode_encodeString(data.toStdString().c_str(), 1, QR_ECLEVEL_L, QR_MODE_8, 1);
if(0!=qr){
QPainter painter(&img);
QColor fg("black");
QColor bg("white");
painter.setBrush(bg);
painter.setPen(Qt::NoPen);
painter.drawRect(0,0,120,120);
painter.setBrush(fg);
const int s=qr->width>0?qr->width:1;
const double w=120;
const double h=120;
const double aspect=w/h;
const double scale=((aspect>1.0)?h:w)/s;
for(int y=0;y<s;y++){
const int yy=y*s;
for(int x=0;x<s;x++){
const int xx=yy+x;
const unsigned char b=qr->data[xx];
if(b &0x01){
const double rx1=x*scale, ry1=y*scale;
QRectF r(rx1, ry1, scale, scale);
painter.drawRects(&r,1);
}
}
}
QRcode_free(qr);
}
return img;
}
void ImageProvider::loadData(QString newdata)
{
data = newdata;
}
main.cpp
QGuiApplication app(argc, argv);
...
ImageProvider ImageProvider;
engine.addImageProvider(QLatin1String("ImageProvider"), &ImageProvider);
QRImage.qml
import QtQuick 2.6
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.1
Image {
id: qr
source: "image://ImageProvider/"
sourceSize.width: 120
sourceSize.height: 120
cache: false
}
What I typically do in this case is to simply change the URL of the image provider including some random number, which is then ignored in the provider itelf. So change "image://ImageProvider/something" to "image://ImageProvider/somethingelse". I typically use a random number. This way, when the URL changes, the provider is queried again and your implementation of requestImage(...) is re-evaluated.
You can set source to an empty string and then set it back to the original URL. This will force the image to be refreshed.
function refreshImage()
{
let source = qr.source;
qr.source = "";
qr.source = source;
}

Custom QGraphicsPixmapItem doesn't display pixmap

I have made a custom QGraphicsPixMap QGraphicsItem as I need to have functions that trigger when someone clicks on it! The header for this is seen below:
#ifndef NEWGPIXMAP_H
#define NEWGPIXMAP_H
#include <QObject>
#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
class NewGPixmap : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
public:
NewGPixmap();
void mousePressEvent(QGraphicsSceneMouseEvent *event);
// void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
// void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
};
#endif // NEWGPIXMAP_H
And the .cpp for this can be seen here:
#include "newgpixmap.h"
#include <iostream>
#include <QPointF>
NewGPixmap::NewGPixmap()
{
}
void NewGPixmap::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QPointF pos = event->pos();
int x = pos.x();
int y = pos.y();
std::cout<<x<<" "<<y<<std::endl;
}
When I try and update the pixmap in the QGraphicsScene (stored in a variable called "scene") I get no errors however the PixMap isn't displayed.
This is the code I used prior to implementing my own QGraphicsPixMap which worked fine:
void MainWindow::on_openImage_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Choose File"));
QImage image(fileName);
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(image));
scene->clear();
scene->addItem(item);
}
And now that I am using my own QGraphicsPixMap implementation, this is what code I have:
void MainWindow::on_openImage_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Choose File"));
QImage image(fileName);
NewGPixmap item;
item.setPixmap(QPixmap::fromImage(image));
scene->clear();
scene->addItem(&item);
}
The problem is that the pixmap wont load into the QGraphicsScene.
Thanks for your help!
Variables are destroyed when their scope ends, in your case "NewGPixmap item;" it is a local variable so it will be deleted when finished running on_openImage_triggered. If you want the object to not depend on the life cycle of the variable then you must use a pointer like your example with QGraphicsPixmapItem:
void MainWindow::on_openImage_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Choose File"));
QImage image(fileName);
NewGPixmap *item = new NewGPixmap;
item->setPixmap(QPixmap::fromImage(image));
scene->clear();
scene->addItem(item);
}

Qt Qml connect to a signal of a QObject property of a Context Property

So this may seem like a strange setup. I have a C++ object that inherits from QObject called "MasterGuiLogic" for simplicity. It is created with a pointer to another object called "MainEventBroker" which as you might guess handles all of my applications events. The MasterGuiLogic object is registered with qml as a context property so that it's properties can be used anywhere in my qml. So main.cpp looks like this:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MasterEventBroker *MainEventBroker = new MasterEventBroker();
MasterGuiLogic *MainGuiLogic = new MasterGuiLogic(*MainEventBroker);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("MasterGuiLogic", &(*MainGuiLogic));
engine.load(QUrl(QLatin1String("qrc:/QmlGui/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
MasterGuiLogic creates an instance of another class called SerialCom, which is set as a Q_PROPERTY so that it's properties and public slots can be reached in qml through the MasterGuiLogic property.
MasterGuiLogic.h:
class MasterGuiLogic : public QObject
{
Q_OBJECT
Q_PROPERTY(GuiLogic_SerialCom* serialCom READ serialCom CONSTANT)
public:
MasterEventBroker *eventBroker;
explicit MasterGuiLogic(MasterEventBroker &ev, QObject *parent = nullptr);
GuiLogic_SerialCom* serialCom() const {
return Gui_SerialCom;
}
private:
GuiLogic_SerialCom *Gui_SerialCom;
MasterGuiLogic.cpp:
MasterGuiLogic::MasterGuiLogic(MasterEventBroker &ev, QObject *parent) : QObject(parent)
{
this->eventBroker = &ev;
this->Gui_SerialCom = new GuiLogic_SerialCom(this);
}
SerialCom.h:
//Forward Declare our parent
class MasterGuiLogic;
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList portNames READ portNames NOTIFY portNamesChanged)
Q_PROPERTY(bool connectedToPort READ connectedToPort NOTIFY connectedToPortChanged)
public:
MasterGuiLogic *parent;
explicit GuiLogic_SerialCom(MasterGuiLogic *parent = nullptr);
std::map<QString, QSerialPortInfo> portsMap;
QStringList portNames() {
return _portNames;
}
bool connectedToPort() {
return _connectedToPort;
}
private:
QStringList _portNames;
bool _connectedToPort = false;
signals:
void portNamesChanged(const QStringList &);
void connectedToPortChanged(const bool &);
public slots:
void connectToPort(const QString portName);
void disconnectFromPort(const QString portName);
};
SerialCom.cpp:
GuiLogic_SerialCom::GuiLogic_SerialCom(MasterGuiLogic *parent) : QObject(qobject_cast<QObject *>(parent))
{
this->parent = parent;
QList<QSerialPortInfo> allPorts = QSerialPortInfo::availablePorts();
for (int i = 0; i < allPorts.size(); ++i) {
this->_portNames.append(allPorts.at(i).portName());
this->portsMap[allPorts.at(i).portName()] = allPorts.at(i);
}
emit portNamesChanged(_portNames);
}
void GuiLogic_SerialCom::connectToPort(const QString portName) {
//TODO: Connect To Port Logic Here;
//Set Connected
this->_connectedToPort = true;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
void GuiLogic_SerialCom::disconnectFromPort(const QString portName) {
//TODO: DisConnect To Port Logic Here;
//Set DisConnected
this->_connectedToPort = false;
emit connectedToPortChanged(this->_connectedToPort);
qDebug() << portName;
}
So from qml it's pretty easy to read any of these properties and even send signals from qml to c++
For example, this works just fine:
connectCom.onClicked: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
MasterGuiLogic.serialCom.connectToPort(comPort.currentText);
} else {
MasterGuiLogic.serialCom.disconnectFromPort(comPort.currentText);
}
}
The problem is, I can't seem to find a way to connect to signals that are emitted from SerialCom. I thought I would be able to do something like this:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
if (MasterGuiLogic.serialCom.connectedToPort === false) {
connectCom.text = "Disconnect";
comPort.enabled = false;
} else {
connectCom.text = "Connect";
comPort.enabled = true;
}
}
}
This should listen to the boolean property on SerialCom to change, but I get the following error:
QQmlApplicationEngine failed to load component
qrc:/QmlGui/main.qml:21 Type Page1 unavailable
qrc:/QmlGui/Page1.qml:49 Invalid attached object assignment
This just means that I can't "connect" using the target line above. Is there any other way I can connect to signals from a Q_PROPERTY of type QObject inside a ContextProperty?
First of all what should &(*MainGuiLogic) mean?
You are dereferencing and referencing again the MainGuiLogic? Why?
context->setContextProperty("MasterGuiLogic", MainGuiLogic); will be enought.
But registering MasterGuiLogic as Type and adding the Object named MasterGuiLogic can overide themself in QML world.
Set it like context->setContextProperty("MyGuiLogic", MainGuiLogic); to eleminate this behavior.
Also don't pass references between C++ and QML worlds like:
void connectedToPortChanged(**const bool &**);.
Just use atomic type and values (const bool);
and give it a name, to be able to use it as named value in QML:
void connectedToPortChanged(bool connected)
Here is an example with the structure like yours, which works. Just click in window and look in output console.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QObject>
class GuiLogic_SerialCom : public QObject
{
Q_OBJECT
public:
GuiLogic_SerialCom(){}
signals:
void connectedToPortChanged(bool connected);
public slots:
void connectToPort(const QString & portName);
};
class MasterGuiLogic : public QObject
{
Q_OBJECT
public:
MasterGuiLogic();
Q_PROPERTY(GuiLogic_SerialCom * serialCom READ serialCom CONSTANT)
GuiLogic_SerialCom* serialCom() const {return test;}
Q_INVOKABLE void generate_signal();
private:
GuiLogic_SerialCom * test;
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QDebug>
MasterGuiLogic::MasterGuiLogic()
{
this->test = new GuiLogic_SerialCom();
}
void MasterGuiLogic::generate_signal()
{
qDebug() << __FUNCTION__ << "Calling serialcom to gen signal";
this->test->connectToPort("88");
}
void GuiLogic_SerialCom::connectToPort(const QString &portName)
{
qDebug() << __FUNCTION__ << "got signal" << portName;
emit this->connectedToPortChanged(true);
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "test.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterUncreatableType<MasterGuiLogic>("GrblCom", 1, 0, "MasterGuiLogic", "");
qmlRegisterUncreatableType<GuiLogic_SerialCom>("GrblCom", 1, 0, "GuiLogic_SerialCom", "");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("Test"), new MasterGuiLogic());
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import GrblCom 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea
{
anchors.fill: parent
onClicked:
{
Test.generate_signal();
}
}
Connections
{
target: Test.serialCom
onConnectedToPortChanged:
{
console.log("Got signal from SerialCom in QML. passed bool value is: " + connected);
}
}
}
Ok, I found the problem... The provided answers, while indeed helpful for other reasons, were not the correct solution. After going over the code by #Xplatforms, I couldn't figure out what the difference was between what I was doing and what he did.... until I saw this in my own code:
Connections: {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
There isn't supposed to be a colon(:) there...
Connections {
target: MasterGuiLogic.serialCom;
onConnectedToPortChanged: {
...
}
}
Never try programming while sleepy...lol

'QML Connections: Cannot assign to non-existent Property' despite the opposite being true

So, I checked the following questions, which seemed most similar to my existing problem:
QML: Using cpp signal in QML always results in "Cannot assign to non-existent property"
Unfortunately that did not help. (Nor did any other solution that I could find on stackoverflow/google/qt forums, etc)
I kept getting the following two errors:
qrc:/view.qml:30:9: QML Connections: Cannot assign to non-existent
property "onNewFrameReceived" qrc:/view.qml:31: ReferenceError:
imageProvide is not defined
Here is my code (edited down to make it, into a 'Minimum Working Example').
The only files important should be:
main.cpp
view.qml
imageprovidervm.cpp
imageprovidervm.h
I included the imagesource class, just to be complete, in case someone wants to compile this on his own as well.
Q1. So, I don't understand why even after setting the context property in main.cpp, the following error appears.
qrc:/view.qml:31: ReferenceError: imageProvide is not defined
What is funny is that, intellisense/autocomplete seems to detect imageProvide completely correctly.
Q2. Even though in my imageprovider.h, I added properties (newimage) and signals (newFrameReceived) that should be seen in the qml file, still I get the following error. Also, the Qt intellisense/autocomplete fails to show my defined signal (onNewFrameReceived) here.
qrc:/view.qml:30:9: QML Connections: Cannot assign to non-existent
property "onNewFrameReceived"
Additional info: Debugging and stopping on a break point in the qml file at line 31, shows in the "locals and expressions" of the qtcreator that I have only 2 signals available here, namely "objectNameChanged" and "targetChanged".
Why ???
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "imageprovidervm.h"
#include "imagesource.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext *context = new QQmlContext(engine.rootContext());
auto model = std::make_shared<ImageSource>();
auto vm = new ImageProviderVM(model);
engine.addImageProvider(QLatin1String("imageProvider"), vm);
context->setContextProperty("imageProvide", vm );
model->generateImages();
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}
view.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQml.Models 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("&Open")
onTriggered: console.log("Open action triggered");
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
Rectangle {
Connections {
target: imageProvide
onNewFrameReceived: image.reload();
}
anchors.fill: parent
Column {
Image {
id: image
source: "image://imageProvider/images.jpeg?id=" + Math.random()
cache: false
asynchronous: true
function reload() {
var oldSource = source;
source = "";
source = oldSource;
}
}
}
}
Label {
text: qsTr("Hello World")
anchors.centerIn: parent
}
}
imageprovidervm.h
#ifndef IMAGEPROVIDERVM_H
#define IMAGEPROVIDERVM_H
#include <QQuickImageProvider>
#include <QObject>
#include "imagesource.h"
class ImageProviderVM : public QObject, public QQuickImageProvider
{
Q_OBJECT
public:
ImageProviderVM(std::shared_ptr<ImageSource> model);
~ImageProviderVM();
virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
virtual QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize) override;
// Properties
Q_PROPERTY(QImage newimage READ getNewImage NOTIFY newFrameReceived)
// Signals
signals:
void newFrameReceived();
private:
QImage getNewImage() const;
QPixmap m_pixmap;
QImage m_image;
std::shared_ptr<ImageSource> m_model;
};
#endif // IMAGEPROVIDERVM_H
imageprovidervm.cpp
#include "imageprovidervm.h"
#include <functional>
#include <QPixmap>
#include <QDebug>
ImageProviderVM::ImageProviderVM()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
ImageProviderVM::ImageProviderVM(std::shared_ptr<ImageSource> model)
: QQuickImageProvider (QQuickImageProvider::Image)
, m_pixmap()
, m_model(model)
{
m_model->subscribeNewPixMap([this](QPixmap pixmap) {
qDebug() << "setting m_pixmap";
if (pixmap.size().isValid()) {
m_pixmap = pixmap;
}
else
qDebug() << "is it NULL ??? " << pixmap.isNull();
});
m_model->subscribeNewImage([this](QImage image) {
qDebug() << "setting m_image";
if (image.size().isValid()) {
m_image = image;
emit newFrameReceived();
}
else
qDebug() << "is it NULL ??? " << image.isNull();
});
qDebug() << "imageproviderVM constructed";
}
ImageProviderVM::~ImageProviderVM()
{
}
QPixmap ImageProviderVM::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
// look into the parameters id, size and requestedSize once the rest of the structure is there
return m_pixmap;
}
QImage ImageProviderVM::requestImage(const QString & id, QSize * size, const QSize & requestedSize)
{
return m_image;
}
QQuickTextureFactory * ImageProviderVM::requestTexture(const QString & id, QSize * size, const QSize & requestedSize)
{
// return QQuickTextureFactory::createTexture();
}
QImage ImageProviderVM::getNewImage() const
{
return m_image;
}
imagesource.h
#ifndef IMAGESOURCE_H
#define IMAGESOURCE_H
#include <QImage>
#include <boost/signals2.hpp>
class ImageSource
{
public:
ImageSource();
void generateImages();
void generatePixmaps(const QString &id, QSize *size, const QSize &requestedSize);
typedef boost::signals2::signal<void (QPixmap)> NewPixMapDelegate;
boost::signals2::connection subscribeNewPixMap(NewPixMapDelegate::slot_function_type f);
typedef boost::signals2::signal<void (QImage)> NewImageDelegate;
boost::signals2::connection subscribeNewImage(NewImageDelegate::slot_function_type f);
private:
NewPixMapDelegate m_newPixMap;
NewImageDelegate m_newImage;
};
#endif // IMAGESOURCE_H
imagesource.cpp
#include "imagesource.h"
#include <QPixmap>
#include <QPainter>
#include <thread>
ImageSource::ImageSource()
{
}
boost::signals2::connection ImageSource::subscribeNewImage(NewImageDelegate::slot_function_type f)
{
return m_newImage.connect(f);
}
void ImageSource::generateImages()
{
std::thread t([this]() {
auto image = QImage("/home/junaid/testing_ground/fourthtime/images.jpeg");
m_newImage(image);
/// useless wait. just simulating that another image comes after sometime and so on onwards.
int random_wait = 2; //sec
sleep(random_wait);
image = QImage("/home/junaid/Downloads/pnggrad16rgb.png");
m_newImage(image);
});
t.detach();
}
boost::signals2::connection ImageSource::subscribeNewPixMap(NewPixMapDelegate::slot_function_type f)
{
return m_newPixMap.connect(f);
}
void ImageSource::generatePixmaps(const QString &id, QSize *size, const QSize &requestedSize)
{
int width = 100;
int height = 50;
if (size) {
*size = QSize(width, height);
}
QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width,
requestedSize.height() > 0 ? requestedSize.height() : height);
pixmap.fill(QColor(id).rgba());
// write the color name
QPainter painter(&pixmap);
QFont f = painter.font();
f.setPixelSize(20);
painter.setFont(f);
painter.setPen(Qt::black);
if (requestedSize.isValid())
painter.scale(requestedSize.width() / width, requestedSize.height() / height);
painter.drawText(QRectF(0, 0, width, height), Qt::AlignCenter, id);
m_newPixMap(pixmap);
}
and here is the CMake file:
cmake_minimum_required(VERSION 2.8.12)
project(non_existent_property LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 COMPONENTS Core Quick REQUIRED)
file( GLOB SRCS *.cpp *.h )
add_executable(${PROJECT_NAME} "qml.qrc" ${SRCS})
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick "pthread")
So, here is how the working main.cpp looks. I was mislead by the QmlContext that I was creating, and with help from #GrecKo's and jpnurmi's comments, I understood that I had to set the property for the root context.
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "imageprovidervm.h"
#include "imagesource.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// QQmlContext *context = new QQmlContext(engine.rootContext());
auto model = std::make_shared<ImageSource>();
auto vm = new ImageProviderVM(model);
engine.addImageProvider(QLatin1String("imageProvider"), vm);
engine.rootContext()->setContextProperty("imageProvide", vm );
model->generateImages();
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}

Passing QClipboard to QML

I am trying to make the clipboard available to some QML code.
Passing the clipboard object seems to work, but then I can't call its methods.
Here's an example of a QClipboard working fine in C++, being passed to QML, and in the debug output it still being a QClipboard object but losing its functions.
main.cpp
#include <QApplication>
#include <QClipboard>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include <QtQml>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("clipboard", QApplication::clipboard());
qDebug() << QApplication::clipboard()->text(); // This correctly prints the copied text
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.3
ApplicationWindow {
visible: true
MouseArea {
acceptedButtons: Qt.MiddleButton
anchors.fill: parent
onClicked: {
console.log(clipboard)
console.log(clipboard.getText())
}
}
}
qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
test.pro
TEMPLATE = app
VERSION = 1.0
QT += qml widgets gui
QMAKE_LFLAGS += -Wl,--as-needed
SOURCES += main.cpp
RESOURCES += qml.qrc
QObject's functions need to be slots if you want to call them from QML. QClipboard declares no slots, so you can't call its functions directly. You can create a proxy, though:
qclipboardproxy.hpp
#ifndef QCLIPBOARDPROXY_HPP
#define QCLIPBOARDPROXY_HPP
#include <QObject>
class QClipboard;
class QClipboardProxy : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text NOTIFY textChanged)
public:
explicit QClipboardProxy(QClipboard*);
QString text() const;
signals:
void textChanged();
private:
QClipboard* clipboard;
};
#endif // QCLIPBOARDPROXY_HPP
qclipboardproxy.cpp
#include "qclipboardproxy.hpp"
#include <QClipboard>
QClipboardProxy::QClipboardProxy(QClipboard* c) : clipboard(c)
{
connect(c, &QClipboard::dataChanged, this, QClipboardProxy::textChanged)
}
QString QClipboardProxy::text()
{
return clipboard->text();
}
and in
main.cpp
engine.rootContext()->setContextProperty("clipboard",
new QClipboardProxy(QGuiApplication::clipboard()));
Now you can call:
console.log(clipboard.text)
or even
someProperty: clipboard.text
Inspired by #krzaq's solution you could even make the proxy a QML instantiable type
class ClipboardProxy : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ dataText WRITE setDataText NOTIFY dataChanged)
Q_PROPERTY(QString selectionText READ selectionText WRITE setSelectionText NOTIFY selectionChanged)
public:
explicit ClipboardProxy(QObject *parent = 0);
void setDataText(const QString &text);
QString dataText() const;
void setSelectionText(const QString &text);
QString selectionText() const;
signals:
void dataChanged();
void selectionChanged();
};
ClipboardProxy::ClipboardProxy(QObject *parent)
: QObject(parent)
{
QClipboard *clipboard = QGuiApplication::clipboard();
connect(clipboard, &QClipboard::dataChanged,
this, ClipboardProxy::dataChanged);
connect(clipboard, &QClipboard::selectionChanged,
this, ClipboardProxy::selectionChanged);
}
void ClipboardProxy::setDataText(const QString &text)
{
QGuiApplication::clipboard()->setText(text, QClipboard::Clipboard);
}
QString ClipboardProxy::dataText() const
{
return QGuiApplication::clipboard()->text(QClipboard::Clipboard);
}
void ClipboardProxy::setSelectionText(const QString &text)
{
QGuiApplication::clipboard()->setText(text, QClipboard::Selection);
}
QString ClipboardProxy::selectionText() const
{
return QGuiApplication::clipboard()->text(QClipboard::Selection);
}
Register as a type in main()
qmlRegisterType<ClipboardProxy>("Clipboard", 1, 0, "Clipboard");
Use as a type in QML
import QtQuick 2.4
import Clipboard 1.0
Column {
Clipboard {
id: clipboard
onDataChanged: console.log("Clipboard data changed");
onSelectionChanged: console.log("Clipboard selection changed");
}
Text {
text: clipboard.text
}
TextInput {
onEditingFinished: clipboard.text = text;
}
}