I am trying to update an QImage from c++ to QML,
I am using the approach from: https://www.huber.xyz/?p=477
I implemented the QQuickPaintedItem - the initial QImage is shown, but I don't find a way to update the QImage from c++ if I receive a signal there (FileWatcher).
My implementation looks like:
QML:
ImageItem {
id: liveImageItem
height: parent.height
width: parent.width
objectName: "liveImageItem"
}
I register the image with:
qmlRegisterType<QUIQImageItem>("imageItem", 1, 0, "ImageItem");
The implementation of the Image:
ImageItem::ImageItem(QQuickItem *parent) : QQuickPaintedItem(parent) {
qDebug() << Q_FUNC_INFO << "initializing new item, parent is: " << parent;
this->current_image = QImage(":/resources/images/logo.png");
}
void ImageItem::paint(QPainter *painter) {
qDebug() << Q_FUNC_INFO << "paint requested...";
QRectF bounding_rect = boundingRect();
QImage scaled = this->current_image.scaledToHeight(bounding_rect.height());
QPointF center = bounding_rect.center() - scaled.rect().center();
if (center.x() < 0)
center.setX(0);
if (center.y() < 0)
center.setY(0);
painter->drawImage(center, scaled);
}
QImage ImageItem::image() const {
qDebug() << Q_FUNC_INFO << "image requested...";
return this->current_image;
}
void ImageItem::setImage(const QImage &image) {
qDebug() << Q_FUNC_INFO << "setting new image...";
this->current_image = image;
emit imageChanged();
update();
}
How could I get the reference of the ImageItem on c++ side to manage an update of the Image via setImage?
Is this way possible or should I try another solution?
I tried to get the item by
QList<ImageItem*> res = engine->findChildren<ImageItem*>();
and also:
QList<ImageItem*> res = engine->findChildren<ImageItem*>("liveImageItem");
the list of ImageItems (res) is always empty.
In general you should avoid modifying an item created in QML from C ++ directly, before that I will improve your implementation by adding the image as qproperty:
*.h
#ifndef IMAGEITEM_H
#define IMAGEITEM_H
#include <QImage>
#include <QQuickPaintedItem>
class ImageItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
public:
ImageItem(QQuickItem *parent = nullptr);
QImage image() const;
void setImage(const QImage &image);
void paint(QPainter *painter);
signals:
void imageChanged();
private:
QImage m_image;
};
#endif // IMAGEITEM_H
*.cpp
#include "imageitem.h"
#include <QDebug>
#include <QPainter>
ImageItem::ImageItem(QQuickItem *parent):QQuickPaintedItem(parent)
{
qDebug() << Q_FUNC_INFO << "initializing new item, parent is: " << parent;
setImage(QImage(":/resources/images/logo.png"));
}
QImage ImageItem::image() const
{
qDebug() << Q_FUNC_INFO << "image requested...";
return m_image;
}
void ImageItem::setImage(const QImage &image)
{
qDebug() << Q_FUNC_INFO << "setting new image...";
if(image == m_image)
return;
m_image = image;
emit imageChanged();
update();
}
void ImageItem::paint(QPainter *painter)
{
if(m_image.isNull())
return;
qDebug() << Q_FUNC_INFO << "paint requested...";
QRectF bounding_rect = boundingRect();
QImage scaled = m_image.scaledToHeight(bounding_rect.height());
QPointF center = bounding_rect.center() - scaled.rect().center();
if (center.x() < 0)
center.setX(0);
if (center.y() < 0)
center.setY(0);
painter->drawImage(center, scaled);
}
In this part I will answer your direct question, although it is not the best because if you do not know how to handle you could have problems, for example if you set the item in a StackView Page since they are created and deleted every time you change pages.
QObject *obj = engine.rootObjects().first()->findChild<QObject*>("liveImageItem");
if(obj){
QImage image = ...;
QQmlProperty::write(obj, "image", image);
}
Example:
main.cpp
#include "imageitem.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QTime>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qsrand(QTime::currentTime().msec());
qmlRegisterType<ImageItem>("com.eyllanesc.org", 1, 0, "ImageItem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QObject *obj = engine.rootObjects().first()->findChild<QObject*>("liveImageItem");
QTimer timer;
if(obj){
QObject::connect(&timer, &QTimer::timeout, [obj](){
QImage image(100,100, QImage::Format_ARGB32);
image.fill(QColor(qrand()%255, qrand()%255, qrand()%255));
QQmlProperty::write(obj, "image", image);
});
timer.start(1000);
}
return app.exec();
}
For me a better idea is to implement a Helper and make the connection in QML:
#include "imageitem.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQmlContext>
#include <QTime>
#include <QTimer>
class Helper: public QObject{
Q_OBJECT
Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)
public:
QImage image() const{ return m_image; }
void setImage(const QImage &image){
if(m_image == image)
return;
m_image = image;
emit imageChanged();
}
signals:
void imageChanged();
private:
QImage m_image;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qsrand(QTime::currentTime().msec());
qmlRegisterType<ImageItem>("com.eyllanesc.org", 1, 0, "ImageItem");
QGuiApplication app(argc, argv);
Helper helper;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("helper", &helper);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&helper](){
QImage image(100,100, QImage::Format_ARGB32);
image.fill(QColor(qrand()%255, qrand()%255, qrand()%255));
helper.setImage(image);
});
timer.start(1000);
return app.exec();
}
#include "main.moc"
*.qml
...
ImageItem{
id: liveImageItem
height: parent.height
width: parent.width
}
Connections{
target: helper
onImageChanged: liveImageItem.image = helper.image
}
...
To slightly improve upon #eyllanesc's solution, the Helper class should probably hold the state while the ImageItem should just be a dumb representation of the image.
Also, you don't need the separate Connection element.
My setup is then as follows:
LiveImage.h
#ifndef LIVEIMAGE_H
#define LIVEIMAGE_H
#include <QImage>
#include <QQuickPaintedItem>
#include <QPainter>
class LiveImage : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QImage image MEMBER m_image WRITE setImage)
// Just storage for the image
QImage m_image;
public:
explicit LiveImage(QQuickItem *parent = nullptr);
void setImage(const QImage &image);
void paint(QPainter *painter) override;
};
#endif // LIVEIMAGE_H
LiveImage.cpp
#include "LiveImage.h"
LiveImage::LiveImage(QQuickItem *parent) : QQuickPaintedItem(parent), m_image{}
{}
void LiveImage::paint(QPainter *painter)
{
painter->drawImage(0, 0, m_image);
}
void LiveImage::setImage(const QImage &image)
{
// Update the image
m_image = image;
// Redraw the image
update();
}
ImageProvider.h
#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H
#include <QObject>
#include <QImage>
class ImageProvider : public QObject
{
Q_OBJECT
Q_PROPERTY(QImage image MEMBER m_image READ image WRITE setImage NOTIFY imageChanged)
QImage m_image;
public:
explicit ImageProvider(QObject *parent = nullptr);
void setImage(QImage const &image);
QImage image() const;
signals:
void imageChanged();
};
#endif // IMAGEPROVIDER_H
ImageProvider.cpp
#include "ImageProvider.h"
ImageProvider::ImageProvider(QObject *parent)
: QObject(parent)
{}
void ImageProvider::setImage(QImage const &image)
{
m_image = image;
emit imageChanged();
}
QImage ImageProvider::image() const
{
return m_image;
}
And then in you main function, register the LiveImage as an instantiable QML type, and make instances of ImageProvider available from QML as well:
qmlRegisterType<LiveImage>("MyApp.Images", 1, 0, "LiveImage");
ImageProvider provider{};
engine.rootContext()->setContextProperty("LiveImageProvider", &provider);
QTimer::singleShot(1000, [&provider](){
QImage image{480, 480, QImage::Format_ARGB32};
image.fill(Qt::yellow);
provider.setImage(std::move(image));
});
Finally, your QML would look like this:
import MyApp.Images
...
LiveImage {
width: 480
height: 480
x: 0
y: 0
image: LiveImageProvider.image
}
Related
I'm trying to create a simple notepad-style Qt application. Since I think I might want a lot of control over exactly how text is rendered, I'm trying to write a custom QAbstractTextDocumentLayout class. The code below compiles and runs, but the text area is blank, no matter how much I type. The debug statements in my draw() function correctly show the entered text, so I know my input is in some form making it all the way to the draw() function, but it's not being rendered.
I just want to tweak this in a minimal way so I can at least see some text being rendered, even if the implementation is incomplete. I also haven't been able to find any documentation whatsoever on writing custom QAbstractTextDocumentLayout, so if anyone had a reference I'd also appreciate that.
main.cpp
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QTextEdit>
#include <QKeyEvent>
#include "./DocumentLayout.cpp"
class MainContent : public QWidget
{
Q_OBJECT
QTextEdit textEdit;
DocumentLayout layout;
public:
MainContent() : textEdit(this), layout(textEdit.document())
{
int innerPadding = 20;
textEdit.document()->setDocumentMargin(innerPadding);
int textColor = 0xD8DEE9;
int backgroundColor = 0x2E3440;
int selectionColor = 0x2E3440;
int selectionBackgroundColor = 0x81A1C1;
QPalette palette = textEdit.palette();
palette.setColor(QPalette::Base, QColor(backgroundColor));
palette.setColor(QPalette::Text, QColor(textColor));
palette.setColor(QPalette::Highlight, QColor(selectionBackgroundColor));
palette.setColor(QPalette::HighlightedText, QColor(selectionColor));
textEdit.setPalette(palette);
textEdit.setFrameStyle(QFrame::NoFrame);
textEdit.document()->setDocumentLayout(&layout);
};
void resizeEvent(QResizeEvent *event)
{
textEdit.setGeometry(0, 0, this->width(), this->height());
}
void keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_W && event->modifiers() == Qt::ControlModifier)
{
qApp->quit();
}
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow window;
window.setWindowTitle("Hello, world!");
window.resize(400, 500);
MainContent content;
window.setCentralWidget(&content);
window.show();
return app.exec();
}
#include "main.moc"
DocumentLayout.cpp
#include <QAbstractTextDocumentLayout>
#include <QPainter>
#include <QTextBlock>
#include <QDebug>
class DocumentLayout : public QAbstractTextDocumentLayout
{
public:
DocumentLayout(QTextDocument *document) : QAbstractTextDocumentLayout(document)
{
}
void draw(QPainter *painter, const PaintContext &context)
{
QTextDocument *document = this->document();
QTextBlock block = document->begin();
QPointF position(20, 20);
painter->setPen(context.palette.color(QPalette::Text));
QTextLayout *layout = block.layout();
layout->draw(painter, position);
position.ry() += layout->boundingRect().height();
qDebug() << "drew" << block.text() << "at" << position;
}
int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
{
return 0;
}
int pageCount() const
{
return 1;
}
QSizeF documentSize() const
{
return QSizeF(400, 800);
}
QRectF frameBoundingRect(QTextFrame *frame) const
{
return QRectF(0, 0, 400, 800);
}
QRectF blockBoundingRect(const QTextBlock &block) const
{
return QRectF(0, 0, 400, 800);
}
void documentChanged(int from, int charsRemoved, int charsAdded)
{
}
};
This topic might be little lengthy since I have to explain the premise before procceding with the probleme at hand.Firstly my main goal is to have this application in which the user is capable of drag-n-dropping commands from a toolbar in order to form workflows which are send and executed on a remote server.Currently i am working on the client part in qt and it is driving me nuts.
This is my code:
draglabel.h
#ifndef DRAGLABEL_H
#define DRAGLABEL_H
#include <QLabel>
class QDragEnterEvent;
class QDragMoveEvent;
class QFrame;
class DragLabel : public QLabel
{
public:
DragLabel(const QString &text, QWidget *parent);
QString labelText() const;
private:
QString m_labelText;
};
#endif // DRAGLABEL_H
draglabel.c
#include "draglabel.h"
#include <QtWidgets>
DragLabel::DragLabel(const QString &text, QWidget *parent)
: QLabel(parent)
{
QFontMetrics metric(font());
QSize size = metric.size(Qt::TextSingleLine, text);
QImage image(size.width() + 12, size.height() + 12, QImage::Format_ARGB32_Premultiplied);
image.fill(qRgba(0, 0, 0, 0));
QFont font;
font.setStyleStrategy(QFont::ForceOutline);
QLinearGradient gradient(0, 0, 0, image.height()-1);
gradient.setColorAt(0.0, Qt::white);
gradient.setColorAt(0.2, QColor(200, 200, 255));
gradient.setColorAt(0.8, QColor(200, 200, 255));
gradient.setColorAt(1.0, QColor(127, 127, 200));
QPainter painter;
painter.begin(&image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(gradient);
painter.drawRoundedRect(QRectF(0.5, 0.5, image.width()-1, image.height()-1),
25, 25, Qt::RelativeSize);
painter.setFont(font);
painter.setBrush(Qt::black);
painter.drawText(QRect(QPoint(6, 6), size), Qt::AlignCenter, text);
painter.end();
setPixmap(QPixmap::fromImage(image));
m_labelText = text;
}
QString DragLabel::labelText() const
{
return m_labelText;
}
dragwidget.h
#ifndef DRAGWIDGET_H
#define DRAGWIDGET_H
#include <QWidget>
#include <QFrame>
#include <vector>
#include <set>
#include "draglabel.h"
using namespace std;
class QDragEnterEvent;
class QDropEvent;
class DragWidget : public QFrame
{
public:
DragWidget(QWidget *parent = nullptr);
void setMode(int desiredMode);
void changePairingMode();
void showAvailableCommands();
void initDrawingLayout();
vector<tuple<QString,QString>> actCommands;
vector<tuple<QString,QString>> execCommands;
vector<pair<int,int>>waitingForPair;
int pairingMode=0;
QFrame*drawingCon;
private:
int widgetMode=1;
protected:
void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
void dragMoveEvent(QDragMoveEvent *event) Q_DECL_OVERRIDE;
void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
};
#endif // DRAGWIDGET_H
dragwidget.cpp
#include "draglabel.h"
#include "dragwidget.h"
#include "arrowhead.h"
#include <QtWidgets>
#include <QWidget>
#include <QFrame>
#include <QColor>
#include <tuple>
using namespace std;
static inline QString dndProcMimeType() { return QStringLiteral("application/x-fridgemagnet"); }
DragWidget::DragWidget(QWidget *parent)
: QFrame(parent)
{
drawingCon=new QFrame(this);
QPalette newPalette = palette();
newPalette.setColor(QPalette::Window, Qt::white);
setPalette(newPalette);
setWindowTitle(tr("Drag-and-Drop"));
setMinimumSize(300,300);
setAcceptDrops(true);
setFrameStyle(QFrame::Sunken | QFrame::StyledPanel);
drawingCon->setPalette(newPalette);
drawingCon->setWindowTitle(tr("Drag-and-Drop"));
drawingCon->setMinimumSize(350,350);
drawingCon->setAcceptDrops(false);
drawingCon->show();
}
void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat(dndProcMimeType())) {
if (children().contains(event->source())) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else if (event->mimeData()->hasText()) {
event->acceptProposedAction();
} else {
event->ignore();
}
}
void DragWidget::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat(dndProcMimeType())) {
if (children().contains(event->source())) {
if(widgetMode==1)
{
event->setDropAction(Qt::MoveAction);
event->accept();
}
else {
event->ignore();
}
} else {
if(widgetMode==1)
{
event->acceptProposedAction();
}
else
{
if(widgetMode==1)
{
event->accept();
}
else {
event->ignore();
}
}
}
} else if (event->mimeData()->hasText()) {
event->acceptProposedAction();
} else {
event->ignore();
}
}
void DragWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat(dndProcMimeType())) {
const QMimeData *mime = event->mimeData();
QByteArray itemData = mime->data(dndProcMimeType());
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QString text;
QPoint offset;
dataStream >> text >> offset;
DragLabel *newLabel = new DragLabel(text, this);
newLabel->move(event->pos() - offset);
newLabel->show();
newLabel->setAttribute(Qt::WA_DeleteOnClose);
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
tuple<QString,QString> addTest;
addTest=make_tuple(text,"");
actCommands.push_back(make_tuple(text,""));
for(auto it:actCommands)
qDebug()<<get<0>(it)<<" "<<get<1>(it);
event->acceptProposedAction();
}
} else {if (event->mimeData()->hasText()) {
if(widgetMode==1)
{
event->accept();
}
else {
event->ignore();
}
event->acceptProposedAction();
}
}
}
void DragWidget::mousePressEvent(QMouseEvent *event)
{
DragLabel *child = static_cast<DragLabel*>(childAt(event->pos()));
if(!pairingMode){
if (!child)
return;
QPoint hotSpot = event->pos() - child->pos();
if(widgetMode==1)
qDebug()<<child->labelText();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << child->labelText() << QPoint(hotSpot);
QMimeData *mimeData = new QMimeData;
mimeData->setData(dndProcMimeType(), itemData);
mimeData->setText(child->labelText());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(*child->pixmap());
drag->setHotSpot(hotSpot);
child->hide();
if (drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction) == Qt::MoveAction)
child->close();
else {
child->show();
}
}
else {
if(widgetMode==1)
{
DragLabel *child = static_cast<DragLabel*>(childAt(event->pos()));
if (!child)
return;
qDebug()<<"Facem pair cu:"<<child->labelText();
waitingForPair.push_back(make_pair(child->x(),child->y()));
if(waitingForPair.size()==2) {
ArrowHead *line=new ArrowHead(waitingForPair.at(0).first,waitingForPair.at(0).second,waitingForPair.at(1).first,waitingForPair.at(1).second,drawingCon);
line->show();
waitingForPair.erase(waitingForPair.begin(),waitingForPair.begin()+1);
qDebug()<<"Tragem linie";
}
}
}
}
void DragWidget::setMode(int desiredMode)
{
widgetMode=desiredMode;
}
void DragWidget::showAvailableCommands()
{
DragLabel*grep=new DragLabel("grep",this);
grep->move(this->x(),this->y());
grep->show();
DragLabel*cat=new DragLabel("cat",this);
grep->move(this->x()+40,this->y());
cat->show();
DragLabel*wc=new DragLabel("wc",this);
wc->move(this->x()+90,this->y());
wc->show();
}
void DragWidget::changePairingMode()
{
if(pairingMode==1)
pairingMode=0;
else {
pairingMode=1;
}
}
mainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include <QTextEdit>
#include "dragwidget.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
protected:
virtual void closeEvent(QCloseEvent *event) override;
private slots:
void handleButton();
void closeAppButton();
void pairButton();
private:
QPushButton *executeCode;
QPushButton *pairCommands;
QPushButton *closeApp;
QTextEdit *inputUser;
QTextEdit *outputServer;
DragWidget * commandLayout=new DragWidget();
DragWidget * availableLayout=new DragWidget();
};
#endif // MAINWINDOW_H
mainWindow.cpp
#include "mainwindow.h"
#include "draglabel.h"
#include "dragwidget.h"
#include <QCoreApplication>
#include <QApplication>
#include <QHBoxLayout>
#include <QPushButton>
#include <QCloseEvent>
#include <QTextEdit>
#include <QFrame>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
executeCode=new QPushButton("Execute");
closeApp=new QPushButton("Close");
pairCommands=new QPushButton("Pair");
connect(closeApp, SIGNAL (released()), this, SLOT (closeAppButton()));
connect(pairCommands, SIGNAL (released()), this, SLOT (pairButton()));
void pairButton();
QHBoxLayout * horizontalLayout=new QHBoxLayout();
commandLayout->setMode(1);
availableLayout->setMode(2);
horizontalLayout->addWidget(commandLayout);
horizontalLayout->addWidget(availableLayout);
availableLayout->showAvailableCommands();
QVBoxLayout*inputBoxes=new QVBoxLayout();
inputUser=new QTextEdit();
outputServer=new QTextEdit();
inputBoxes->addWidget(inputUser);
inputBoxes->addWidget(outputServer);
horizontalLayout->addLayout(inputBoxes);
QVBoxLayout*withButtons=new QVBoxLayout();
withButtons->addLayout(horizontalLayout);
withButtons->addWidget(pairCommands);
withButtons->addWidget(executeCode);
withButtons->addWidget(closeApp);
withButtons->addWidget(new QFrame());
setCentralWidget(new QWidget);
centralWidget()->setLayout(withButtons);
}
void MainWindow::handleButton()
{
}
void MainWindow::closeEvent(QCloseEvent *event)
{
event->accept();
}
void MainWindow::closeAppButton()
{
exit(EXIT_SUCCESS);
}
void MainWindow::pairButton()
{
commandLayout->changePairingMode();
qDebug()<<commandLayout->pairingMode;
}
Note:It might seem idiotic but i have the same class for the "toolbar",from where you're supposed to drag commands and also for part where you are supposed to drag commands and pair them.
This is mostly modified code of the fridge-magnets example on the qt website.
The problem that is giving headaches is drawing lines between dragwidget, I have tried drawing everything in the same QFrame but that proved to be disastrous since the whole pixelMap of the instance dragWidget is overwritten at every draw.The solution with which i came up is to overlay a supplimentary QFrame over my dragWidget in order to draw lines there and everyone to be happy,but as always misfortune strikes at every step.When i am trying to click on the command widget everything's fine but clicking on anything other than a DragLabel results in a segfault due to clicking on the QFrame due to childAt() returning the address of the QFrame overlayed on the first instance of dragWdiget();
My main question is: How can i overcome this obstacle
You should use qobject_cast instead of static_cast.
Add Q_OBJECT macro in each class declaration:
class DragLabel : public QLabel
{
Q_OBJECT
public:
//... class declaration ...
}
class DragWidget : public QFrame
{
Q_OBJECT
public:
//... class declaration ...
}
Then use qobject_cast instead of static_cast for childAt(), for example :
DragLabel *child = qobject_cast<DragLabel*>(childAt(event->pos()));
if(!child){
....
}
I'm trying to pass QImage to QML using Image Provider but all the time I'm getting error:
Failed to get image from provider: image://provider/im.
I'm writing a simple Media Player app, and I want to display cover image from Media Player metadata:
QVariant variant = _cpp_player->metaData(QMediaMetaData::CoverArtImage);
QImage image = variant.value();
I'm sending that QImage by signal, which is connected with slot in Image Provider class. That signal also invokes reload function in main.qml.
Here is the entire code:
main.cpp
#include <QUrl>
#include "player.h"
#include "imageprovider.h"
#include "imageprocessor.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
Player player;
QQmlApplicationEngine engine;
ImageProvider *imageProvider = new ImageProvider();
engine.rootContext()->setContextProperty("player", &player);
engine.addImageProvider("provider", imageProvider);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
imageprovider.h
#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H
#include <QObject>
#include <QImage>
#include <QQuickImageProvider>
class ImageProvider : public QObject, public QQuickImageProvider
{
Q_OBJECT
public:
explicit ImageProvider(): QQuickImageProvider(QQuickImageProvider::Image) {}
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
public slots:
void imageChanged(QImage image);
private:
QImage _image;
};
#endif // IMAGEPROVIDER_H
imageprovider.cpp
#include "imageprovider.h"
#include <QDebug>
QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
if(_image.isNull())
{
qDebug() << "Image is null";
}
return _image;
}
void ImageProvider::imageChanged(QImage image)
{
_image = image;
}
player.h
#ifndef PLAYER_H
#define PLAYER_H
#include <QObject>
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QMediaObject>
#include <QMediaMetaData>
#include <QList>
#include <QStringList>
#include "imageprovider.h"
class Player : public QObject
{
Q_OBJECT
Q_PROPERTY(QString songName READ songName WRITE setSongName NOTIFY songNameChanged)
Q_PROPERTY(QStringList list READ list NOTIFY playListUpdated)
QMediaPlayer *_cpp_player;
QMediaPlaylist *_cpp_playlist;
QString _songName;
QStringList strList;
ImageProvider *_cpp_provider;
public:
explicit Player(QObject *parent = nullptr);
QString songName() const;
QStringList list() const;
void setSongName(const QString &songName);
void addToPlayList(const QList<QUrl> &url);
Q_INVOKABLE void openFileDialog();
Q_INVOKABLE void positionChangedFromQML(const int &val);
Q_INVOKABLE void playListPositionChanged(const int &index);
public slots:
void playButtonClicked();
void pauseButtonClicked();
void stopButtonClicked();
void nextButtonClicked();
void previousButtonClicked();
void currentPlayListPosition(int index);
void positionChanged(qint64 pos);
void durationChanged(qint64 dur);
void stateChanged(QMediaPlayer::MediaStatus state);
signals:
void songNameChanged();
void posChanged(qint64 pos);
void songDuration(qint64 dur);
void songTime1(QString tim);
void songTime2(QString tim2);
void playListIsNotEmpty();
void playListUpdated();
void imageChanged(QImage image);
};
#endif // PLAYER_H
player.cpp
#include "player.h"
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QMediaContent>
#include <QtDebug>
#include <QDateTime>
#include <QFileDialog>
class QMediaPlaylist;
class QMediaPlayer;
Player::Player(QObject *parent) : QObject(parent)
{
_cpp_player = new QMediaPlayer;
_cpp_playlist = new QMediaPlaylist;
_cpp_provider = new ImageProvider;
_cpp_player->setPlaylist(_cpp_playlist);
connect(_cpp_player, &QMediaPlayer::durationChanged, this, &Player::durationChanged);
connect(_cpp_player, &QMediaPlayer::positionChanged, this, &Player::positionChanged);
connect(this, &Player::imageChanged, _cpp_provider, &ImageProvider::imageChanged);
}
void Player::addToPlayList(const QList<QUrl> &url)
{
for (auto &el: url) {
_cpp_playlist->addMedia(el);
}
}
Q_INVOKABLE void Player::openFileDialog()
{
QFileDialog fileDialog;
fileDialog.setFileMode(QFileDialog::ExistingFiles);
fileDialog.setDirectory("/home");
QStringList formatList = {"*.mp3", "*.flac", "*.wav"};
fileDialog.setNameFilters(formatList);
if (fileDialog.exec() == QDialog::Accepted)
{
addToPlayList(fileDialog.selectedUrls());
for(int i = 0; i<fileDialog.selectedUrls().size(); ++i){
strList.append(fileDialog.selectedUrls().at(i).fileName());
emit playListIsNotEmpty();
emit playListUpdated();
}
}
}
QString Player::songName() const
{
return _songName;
}
QStringList Player::list() const
{
return strList;
}
void Player::setSongName(const QString &songName)
{
_songName = songName;
emit songNameChanged();
}
void Player::currentPlayListPosition(int index)
{
if(index!=-1){
setSongName(_cpp_player->metaData(QMediaMetaData::Title).toString());
}
}
void Player::durationChanged(qint64 dur)
{
stateChanged(QMediaPlayer::LoadedMedia);
emit songDuration(dur);
}
void Player::stateChanged(QMediaPlayer::MediaStatus state)
{
if (state == QMediaPlayer::LoadedMedia){
if (_cpp_player->isMetaDataAvailable())
{
setSongName(_cpp_player->metaData(QMediaMetaData::Title).toString());
QVariant variant = _cpp_player->metaData(QMediaMetaData::CoverArtImage);
QImage image = variant.value<QImage>();
emit imageChanged(image);
}
else
{
qDebug() << "No metadata.";
}
}
}
Q_INVOKABLE void Player::positionChangedFromQML(const int &val)
{
_cpp_player->setPosition(val);
}
void Player::playListPositionChanged(const int &index)
{
_cpp_playlist->setCurrentIndex(index);
}
void Player::positionChanged(qint64 pos)
{
emit posChanged(pos);
emit songTime1(QDateTime::fromTime_t((uint)pos/1000).toUTC().toString("mm:ss"));
emit songTime2((QDateTime::fromTime_t((uint)((_cpp_player->duration()/1000) - pos/1000))).toUTC().toString("mm:ss"));
}
void Player::playButtonClicked()
{
if(!_cpp_playlist->isEmpty()){
_cpp_player->play();
}
}
void Player::pauseButtonClicked()
{
_cpp_player->pause();
}
void Player::stopButtonClicked()
{
_cpp_player->stop();
}
void Player::nextButtonClicked()
{
if(_cpp_playlist->nextIndex()!=-1){
_cpp_playlist->next();
}
else
{
_cpp_player->stop();
_cpp_player->play();
}
}
void Player::previousButtonClicked()
{
if(_cpp_playlist->previousIndex()!=-1){
_cpp_playlist->previous();
}
else{
_cpp_player->stop();
_cpp_player->play();
}
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
Window {
Connections{
target: player
onPosChanged:{
newSlider.value = pos
}
onSongDuration:{
newSlider.stepSize = 1000
newSlider.to = dur
}
onSongTime1: {
newSlider.time1 = tim;
}
onSongTime2:{
newSlider.time2 = tim2;
}
onPlayListIsNotEmpty:{
toolBar.playState = "playClicked"
}
onImageChanged:{
cover.reload();
}
}
visible: true
width: 900
height: 600
title: "Media Player"
ToolBar {
id: toolBar
}
PlayListView{
id: playList
visible: false
}
AnotherSlider{
id:newSlider
anchors.bottom: toolBar.top
anchors.bottomMargin: 40
anchors.horizontalCenter: parent.horizontalCenter
sliderWidth: toolBar.width - 40
onMoved: {
player.positionChangedFromQML(value)
}
}
Rectangle{
id: albumImage
width: newSlider.width/2 - 10
height: newSlider.width/2 - 10
property int imageId: 0
Image {
id: cover
anchors.fill:albumImage
source: "image://provider/im"
function reload(){
var oldSource = source;
source = ""
source = oldSource
}
}
color: "white"
border.color: "black"
border.width: 5
radius: 5
anchors.left: newSlider.left
anchors.bottom: newSlider.top
anchors.bottomMargin: 40
}
}
What is wrong? How can I solve that?
I am displaying opencv video using QT multithreading concept as suggested here
I also wish to update private "path" variable of worker thread class on button click. So I added
connect(this, SIGNAL(send_to_worker(QString)),workers[view], SLOT(get_from_main(QString)));
However, get_from_main(QString) function never gets called.
May I please know what is the safest method to update worker thread class variable from mainwindow ?
Here is full code..
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QString path, int id);
~Worker();
public slots:
void readVideo(QString path = "");
void get_from_main(QString path);
signals:
// frame and index of label which frame will be displayed
void frameFinished(cv::Mat frame, int index);
void finished(int index);
private:
QString filepath;
int index;
};
//worker.cpp
#include "worker.h"
#include <QDebug>
#include <QThread>
#include <QTime>
Worker::Worker(QString path, int id) : filepath(path), index(id)
{
}
Worker::~Worker()
{
}
void Worker::get_from_main(QString path)
{
qDebug() << "updating";
}
void Worker::readVideo(QString path)
{
if (path.length() > 0)
filepath = path;
cv::VideoCapture cap(filepath.toStdString());
if (! cap.isOpened())
{
qDebug() << "Can't open video file " << filepath;
emit finished(index);
return;
}
cv::Mat frame;
while (true)
{
cap >> frame;
if (frame.empty())
{
frame = cv::Mat(cv::Size(720, 576), CV_8UC3, cv::Scalar(192, 0, 0));
emit frameFinished(frame, index);
break;
}
emit frameFinished(frame.clone(), index);
QThread::msleep(30);
}
emit finished(index);
}
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <opencv2/opencv.hpp>
#include "worker.h"
#define MAX_NUM_CAM 8
namespace Ui {
class MainWindow;
}
class QThread;
class QLabel;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void init();
private slots:
void displayFrame(cv::Mat frame, int index);
void file_open_clicked();
signals:
send_to_worker(QString path);
private:
Ui::MainWindow *ui;
int numCams;
QLabel *labels[MAX_NUM_CAM];
QThread* threads[MAX_NUM_CAM];
Worker* workers[MAX_NUM_CAM];
};
#endif // MAINWINDOW_H
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QThread>
#include <QLabel>
#include <QGridLayout>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qRegisterMetaType< cv::Mat >("cv::Mat");
qDebug() << "Main thread " << QThread::currentThreadId();
init();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::init()
{
QGridLayout *grid = new QGridLayout;
int numCols = 2;
numCams = 4;
int row = 0, col = 0;
for (int i = 0; i < numCams; i++)
{
labels[i] = new QLabel;
row = i / numCols;
col = i % numCols;
grid->addWidget(labels[i], row, col);
threads[i] = new QThread;
workers[i] = new Worker(QString("/home/shang/Videos/%1.mp4").arg(i+1), i);
workers[i]->moveToThread(threads[i]);
connect(workers[i], SIGNAL(frameFinished(cv::Mat, int)), this, SLOT(displayFrame(cv::Mat,int)));
connect(threads[i], SIGNAL(started()), workers[i], SLOT(readVideo()));
connect(workers[i], SIGNAL(finished(int)), threads[i], SLOT(quit()));
connect(workers[i], SIGNAL(finished(int)), workers[i], SLOT(deleteLater()));
connect(threads[i], SIGNAL(finished()), threads[i], SLOT(deleteLater()));
threads[i]->start();
}
this->centralWidget()->setLayout(grid);
}
void MainWindow::file_open_clicked(){
QString Path = QFileDialog::getSaveFileName( this,tr("OpenVideo"),"","Video (*.avi)");
if(Path.isEmpty())
return;
view =3;
connect(this, SIGNAL(send_to_worker(QString)),workers[view], SLOT(get_from_main(QString)));
emit this->send_to_worker(recorder_Path);
}
void MainWindow::displayFrame(cv::Mat frame, int index)
{
QPixmap p = QPixmap::fromImage(QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888).rgbSwapped());
p = p.scaled(QSize(frame.cols/2, frame.rows/2));
labels[index]->setPixmap(p);
}
Qt + OpenCV play videos with std::thread
You're trying to perform an operation periodically whilst still processing events -- that's simply calling out for a QTimer.
(Note that the following code is untested.)
Change your Worker class to make use of a QTimer rather than a loop that blocks the event queue...
class Worker: public QObject {
Q_OBJECT;
public:
Worker (QString path, int id);
~Worker();
public slots:
void readVideo(QString path = "");
void get_from_main(QString path);
signals:
// frame and index of label which frame will be displayed
void frameFinished(cv::Mat frame, int index);
void finished(int index);
private:
QString filepath;
int index;
QTimer timer;
cv::VideoCapture cap;
};
Worker::Worker (QString path, int id)
: filepath(path)
, index(id)
, timer(this)
, cap(filepath.toStdString())
{
/*
* Connect QTimer::timeout to the readVideo slot that will read a
* single frame on each signal at 30ms intervals.
*/
connect(&timer, &QTimer::timeout, this, &Worker::readVideo);
timer.start(30);
}
Worker::~Worker ()
{
}
void Worker::get_from_main (QString path)
{
qDebug() << "updating";
filepath = path;
cap = cv::VideoCapture(filepath);
if (!cap.isOpened()) {
qDebug() << "Can't open video file " << filepath;
emit finished(index);
}
}
void Worker::readVideo ()
{
cv::Mat frame;
cap >> frame;
if (frame.empty())
{
frame = cv::Mat(cv::Size(720, 576), CV_8UC3, cv::Scalar(192, 0, 0));
emit frameFinished(frame, index);
break;
}
emit frameFinished(frame.clone(), index);
}
Now Worker::readVideo simply reads a single frame from the capture and then returns to the event loop.
Also remove the line...
connect(threads[i], SIGNAL(started()), workers[i], SLOT(readVideo()));
from MainWindow::init. As I stated above this is untested and probably needs a lot more error checking. But it should give you a good idea as to what's required.
I'm trying to create a program that accepts images through drag and drop and shows those images on my UI, then I want to rename them and save them.
I got the drag and drop to work, but I have some issues with my Image placement and I can't seem to find where I'm making my mistake.
In the image you see my UI during runtime, in the top left you can see a part of the image I dragged into the green zone(this is my drag and drop zone that accepts images). The position I actually want it to be in should be the red square. The green zone is a Dynamic created object called Imagehandler that I created to handle the drag and drop of the images. The Red square is my own class that inherits from QLabel, I called it myiconclass. This class should hold the actual image data.
I think my mistake has to do with the layouts, but I can't see it.
Could I get some help with this please?
Imagehandler.h
#ifndef IMAGEHANDLER_H
#define IMAGEHANDLER_H
#include <QObject>
#include <QWidget>
#include <QLabel>
#include <QDrag>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QList>
#include <QDebug>
//this class is designed to help me take in the images with drag and drop
class ImageHandler : public QWidget
{
Q_OBJECT
public:
explicit ImageHandler(QWidget *parent = nullptr);
QList<QImage> getImageListMemory() const;
void setImageListMemory(const QList<QImage> &value);
QList<QUrl> getUrlsMemory() const;
void setUrlsMemory(const QList<QUrl> &value);
private:
//QWidget Icon;
QLabel Icon;
QList <QImage> imageListMemory;
QList <QUrl> urlsMemory;
protected:
void dragEnterEvent(QDragEnterEvent * event);
void dragLeaveEvent(QDragLeaveEvent * event);
void dragMoveEvent(QDragMoveEvent * event);
void dropEvent(QDropEvent * event);
signals:
void transferImageSignal(QList <QImage>);
public slots:
};
#endif // IMAGEHANDLER_H
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QImageReader>
#include <QList>
#include <QWidget>
#include <QLabel>
#include <myiconclass.h>
#include <imagehandler.h>
#include <QGridLayout>
#include <QDebug>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QList<QImage> getImageListMemory() const;
void setImageListMemory(const QList<QImage> &value);
private:
Ui::MainWindow *ui;
QLabel Icon;
QList <QImage> imageListMemory;
QList <QUrl> urlsMemory;
QList<QWidget *> labelList;
ImageHandler * ImageHandlerMemory;
QGridLayout * grid2;
QList <MyIconClass *> memory;
signals:
public slots:
void setIconSlot(QList <QImage>);
};
#endif // MAINWINDOW_H
myiconclass.h
#ifndef MYICONCLASS_H
#define MYICONCLASS_H
#include <QWidget>
#include <QLabel>
//this class is based on a Qlabel and is only made so it can help me with the actual images, gives me more members if I need it
class MyIconClass : public QLabel
{
Q_OBJECT
public:
explicit MyIconClass(QWidget *parent = nullptr);
int getMyNumber() const;
void setMyNumber(int value);
private:
int myNumber;
signals:
public slots:
};
#endif // MYICONCLASS_H
imagehandler.cpp
#include "imagehandler.h"
ImageHandler::ImageHandler(QWidget *parent) : QWidget(parent)
{
setAcceptDrops(true);
}
QList<QImage> ImageHandler::getImageListMemory() const
{
return imageListMemory;
}
void ImageHandler::setImageListMemory(const QList<QImage> &value)
{
imageListMemory = value;
}
QList<QUrl> ImageHandler::getUrlsMemory() const
{
return urlsMemory;
}
void ImageHandler::setUrlsMemory(const QList<QUrl> &value)
{
urlsMemory = value;
}
void ImageHandler::dragEnterEvent(QDragEnterEvent * event)
{
event->accept();
}
void ImageHandler::dragLeaveEvent(QDragLeaveEvent * event)
{
event->accept();
}
void ImageHandler::dragMoveEvent(QDragMoveEvent * event)
{
event->accept();
}
void ImageHandler::dropEvent(QDropEvent * event)
{
QList <QImage> imageList2;
QList <QUrl> urls;
QList <QUrl>::iterator i;
urls = event->mimeData()->urls();
//imageList.append(event->mimeData()->imageData());
foreach (const QUrl &url, event->mimeData()->urls())
{
QString fileName = url.toLocalFile();
qDebug() << "Dropped file:" << fileName;
qDebug()<<url.toString();
QImage img;
if(img.load(fileName))
{
imageList2.append(img);
}
}
emit transferImageSignal(imageList2);
this->setUrlsMemory(urls);
this->setImageListMemory(imageList2);
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ImageHandler * handler = new ImageHandler(this);
handler->show();
QGridLayout *grid = new QGridLayout;
grid->addWidget(handler, 0, 0);
ui->groupBoxIcon->setLayout(grid);
ImageHandlerMemory = handler;
//connect(handler,SIGNAL(handler->transferImageSignal(QList <QUrl>)),this,SLOT(setIconSlot(QList <QUrl>)));
connect(handler,SIGNAL(transferImageSignal(QList<QImage>)),this,SLOT(setIconSlot(QList<QImage>)));
}
MainWindow::~MainWindow()
{
delete ui;
}
QList<QImage> MainWindow::getImageListMemory() const
{
return imageListMemory;
}
void MainWindow::setImageListMemory(const QList<QImage> &value)
{
imageListMemory = value;
}
void MainWindow::setIconSlot(QList<QImage> images)
{
printf("succes!");
this->setImageListMemory(images); //save the images to memory
QGridLayout *grid = new QGridLayout; //create the grid layout I want my images to be in
// create counters to remember the row and column in the grid
int counterRow =0;
int counterColumn =0;
int counter3 =0;
int counterImages = 0;
//iterate over each image in the list
QList <QImage>::iterator x;
for(x = imageListMemory.begin(); x != imageListMemory.end(); x++)
{
MyIconClass * myLabel = new MyIconClass(this); //create an object of my own class (which is a Qlabel with an int member)
QPixmap pixmap(QPixmap::fromImage(*x)); //create a pixmap from the image in the iteration
myLabel->setPixmap(pixmap); //set the pixmap on my label object
myLabel->show();
memory.append(myLabel); //add it to the memory so I can recal it
counterImages++;
}
while(counter3 < images.count())
{
grid2->addWidget(memory.value(counter3), counterRow, counterColumn);
counterColumn++;
counter3++;
if(counterColumn >= 5)
{
counterRow++;
counterColumn =0;
}
}
if(ImageHandlerMemory->layout() == 0)
{
ImageHandlerMemory->setLayout(grid2);
}
}
myiconclass.cpp
#include "myiconclass.h"
MyIconClass::MyIconClass(QWidget *parent) : QLabel(parent)
{
}
int MyIconClass::getMyNumber() const
{
return myNumber;
}
void MyIconClass::setMyNumber(int value)
{
myNumber = value;
}
As Benjamin T said I had to change this:
MyIconClass * myLabel = new MyIconClass(this);
into this:
MyIconClass * myLabel = new MyIconClass(ImageHandlerMemory);
Thanks Benjamin!
PS, I also had to add this line in my mainwindow constructor:
grid2 = new QGridLayout;