How to repaint QWidget encapsulated in QDeclarativeItem in QML? - c++

I work in a C++/QML environment and I use Qt 4.8 with QtQuick 1.0.
I have a QWidget derivated class, QCustomPlot, and I encapsulated it in a custom QDeclarativeItem derived class. I use a QGraphicsProxyWidget to embed the QWidget, and it appears nicely upon creation. I would like to update the chart periodically, but I simply cannot, no matter what I do it stays however I initiated it in the constructor.
Here is the code (somewhat simplified) I have:
flowgrafik.h:
class FlowGrafik : public QDeclarativeItem
{
Q_OBJECT
public:
explicit FlowGrafik(QDeclarativeItem *parent = 0);
~FlowGrafik();
void addFlow(double flow);
signals:
public slots:
private:
QCustomPlot * customPlot;
QGraphicsProxyWidget * proxy;
QVector<double> x, y;
};
flowgrafik.cpp:
FlowGrafik::FlowGrafik(QDeclarativeItem *parent) : QDeclarativeItem(parent)
{
customPlot = new QCustomPlot();
proxy = new QGraphicsProxyWidget(this);
proxy->setWidget(customPlot);
this->setFlag(QGraphicsItem::ItemHasNoContents, false);
customPlot->setGeometry(0,0,200,200);
/* WHAT I WRITE HERE WILL BE DISPLAYED */
// pass data points to graph:
customPlot->graph(0)->setData(x, y);
customPlot->replot();
}
FlowGrafik::~FlowGrafik()
{
delete customPlot;
}
void FlowGrafik::addFlow(double flow)
{
//THIS PART DOES NOT GET DISPLAYED
for (int i=0; i<99; ++i)
{
y[i] = y[i+1];
}
y[99] = flow;
customPlot->graph(0)->setData(x, y);
customPlot->replot();
this->update();
}
mainview.qml:
Rectangle {
id: flowGrafik
objectName: "flowGrafik"
x: 400
y: 40
width: 200
height: 200
radius: 10
FlowGrafik {
id: flowGrafikItem
}
}
I would really appreciate if anyone could tell me why my QCustomPlot QWidget does not replot.

Eventually the solution was to create a pointer in the C++ code that points at the QML item.
I accidentally created an other instance in the C++, modified that one, and expected the QML instance to change.
QDeclarativeView mainView;
mainView.setSource(QUrl("qrc:///qml/qml/mainview.qml"));
Flowgrafik * flowGrafik = mainView.rootObject()->findChild<QObject*>(QString("flowGrafikItem"));
//or if Flowgrafik was the main element: Flowgrafik * flowGrafik = mainView.rootObject();
flowGrafik->addFlow(x);

Related

Save QML image inside c++

I am trying to display network image using qml and then save this image using c++ code,
Here is the qml code,
import QtQuick 2.3
import QtQuick.Window 2.2
import com.login 1.0
Window {
visible: true
width : 500
height: 500
Login{id: login}
MouseArea {
anchors.fill: parent
onClicked: {
// Qt.quit();
login.save(image);
}
}
Image {
id: image
source: "http://www.test.com/webp/gallery/4.jpg"
}
}
And inside my login class saving image like,
void Login::save( QQuickItem *item)
{
qDebug()<<"width: "<<item->width();
qDebug()<<"height: "<<item->height();
QQuickWindow *window = item->window();
QImage image = window->grabWindow();
QPixmap pix = QPixmap::fromImage(image);
pix.save("C:/Users/haris/Desktop/output.png");
}
I am getting the correct width and height of the image inside c++ class, but the problem is I cannot find a way to save the image item from QQuickItem.
Right now I am saving the image by grabing the window, which actually not giving the actual image size on output file, instead giving output file with current qml window size.
Basically I am following the code here saving QML image but it seems QDeclarativeItem is deprecated in Qt5, so I choose QQuickItem where as there is no paint option in QQuickItem.
Fortunately QQuickItem has a convenient grabToImage function which does that.
void Login::save( QQuickItem *item)
{
QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
connect(grabResult.data(), &QQuickItemGrabResult::ready, [=]() {
grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
//grabResult->image() gives the QImage associated if you want to use it directly in the program
});
}
Alternate solution without using lambdas:
void Login::save( QQuickItem *item)
{
QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
/* Need to store grabResult somewhere persistent to avoid the SharedPointer mechanism from deleting it */
...
connect(grabResult.data(), SIGNAL(ready()), this, SLOT(onAsynchroneousImageLoaded()));
}
void Login::onAsynchroneousImageLoaded() {
auto grabResult = qobject_cast<const QQuickItemGrabResult*>(sender());
if (grabResult) {
grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
} else {
//something went wrong
}
});
In a QObject-derived class (ImageSaver) register it as you would. It needs one member:
bool ImageSaver::saveImage(const QUrl &imageProviderUrl, const QString &filename){
qDebug() << Q_FUNC_INFO <<imageProviderUrl << filename;
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(imageProviderUrl.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageActualSize;
QSize imageRequestedSize;
QString imageId = imageProviderUrl.path().remove(0,1);
QImage image = imageProvider->requestImage(imageId, &imageActualSize, imageRequestedSize);
qDebug() << Q_FUNC_INFO << imageId << imageActualSize;
return image.save(filename);
}
then in QML:
ImageSaver { id: imageSaver}
...
imageSaver.saveImage(image.source, "my.png");
...
Whereas grabToImage will grab the item using the items's size, this can preserve the actual size of the image.

copying QGraphicsItem from one QGraphicsScene to another, items snap to (0,0)

I am trying to create items in one panel and add to a second panel. In the second panel I want them to be movable (and have context menu). The AddItem button should add the item from the RenderArea, to the existing list of items in the CollectionView (which may already have been moved)
In the code below, the ShapeView::addItem() is supposed to create a copy of the item from the RenderArea (where it can change shape, color etc but is not movable, starts at (0,0)), place it in the CollectionView, where it is movable. The RenderArea holds one item - once added to CollectionView, the RenderArea item should reset.
What is happening... I can't seem to be able to separate the item from the two classes.
When I add item, even if the items in
CollectionView have moved, their position resets to 0,0 (or whatever the initial position was in the RenderArea; but the adding
works properly, the individual item properties are correct, like shape and color; also - CollectionView items move, RenderArea item
doesn't).
I am posting all the relevant code:
class Item : public QGraphicsItem
{
Item() { setFlag(ItemIsMovable, false); }
Item::Item(Item &copyItem) { // copy constructor
setFlag(ItemIsMovable);
setPos(copyItem.pos()); // doesn't seem to work
QRectF boundingRect() const { return QRectF(-35, -30, 35, 20); }
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) {
painter->drawRect(boundingRect()); }
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsItem::mouseMoveEvent(event);
}
};
class CollectionView : public QGraphicsView
{
Q_OBJECT
public:
CollectionView(QWidget *parent = 0);
void update();
QList<Item*> *m_items;
};
CollectionView::CollectionView(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *s = new QGraphicsScene(this);
s->setSceneRect(-width()/2, -height()/2, width(), height());
setScene(s);
setViewportUpdateMode(BoundingRectViewportUpdate);
m_items = new QList<Item*>();
scene()->clear();
}
void CollectionView::update()
{
scene()->clear();
for(int i = 0; i< m_items->size(); i++)
{
Item* item = new Item(*m_items->at(i));
item->setPos(m_items->at(i)->p); // doesn't seem to work
scene()->addItem(item);
}
viewport()->update();
}
class RenderArea : public QGraphicsView
{
Q_OBJECT
public:
RenderArea(QWidget *parent = 0);
public:
Item* item;
};
RenderArea::RenderArea(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *s = new QGraphicsScene(this);
s->setSceneRect(-width()/2, -height()/2, width(), height());
setScene(s);
setViewportUpdateMode(BoundingRectViewportUpdate);
item = new Item();
s->addItem(item);
}
// this is the boss/coordinator class
class ShapeView : public QGraphicsView
{
Q_OBJECT
public:
ShapeView(QWidget *parent = 0);
CollectionView *collectionView;
private slots: // more slots corresponding to more controls
void addItem();
private:
QPushButton *addButton;
RenderArea *renderArea;
};
ShapeView::ShapeView(QWidget *parent)
: QGraphicsView(parent)
{
collectionView = new CollectionView(parent);
renderArea = new RenderArea(this);
addButton = new QPushButton(tr("Add Item"));
connect(addButton, SIGNAL(clicked()), this, SLOT(addItem()));
}
void ShapeView::addItem()
{
Item* item = new Item(*renderArea->item);
collectionView->m_items->append(item);
collectionView->update();
// place a new item on renderarea
renderArea->item = new Item();
renderArea->scene()->clear();
renderArea->scene()->addItem(renderArea->item);
renderArea->viewport()->update();
}
Something is wrong in either the way I copy the item, I just don't know what. I see no reason why, when adding items, the CollectionView items all snap to the (0,0) position. Perhaps the issue is in the copy constructor but I can't figure what is wrong.
I hope someone can help give me a hint
Update: It seems that the problem is in the CollectionView::update() function... adding a 'delete' at the end of the loop removes the item from the collection... Or in the ShapeView::addItem() function... Am I creating copy of the item or just reusing it ?
But how do I create a copy of the item ?
My assumption was that there was a connection between the items I paint on QGraphicsScene and their representation (as pointers to the objects). Wrong... In fact I misunderstood what the pointer was pointing at...
I updated the position of the items in my list after mouse move and everything is fine.
void CollectionView::mouseMoveEvent(QMouseEvent *event)
{
Item *currentItem = (Item*)itemAt(event->pos().x(), event->pos().y());
if(!currentItem) return;
QGraphicsView::mouseMoveEvent(event);
m_items->at(currentItem->z)->setPos(currentItem->pos());
}
The above fixes the code shown. But it is not the best fix.
The better solution to the problem - remove my list of items completely, since QGraphicsScene already holds a copy. (there was one benefit of handling my own list of items - bringForward and sendBackward for items was easier to implement since my z values were equal to item index)

Painting in graphicsview

I am using the graphics view to paint the graphicsitem in it. Earlier when I clicked the button the respective item was painted only once, to again paint the same entity I had topush the button again. To overcome this I constructed the signal to allow to add the entities multiple times without having the need to push the button again. But when I using vector to store the points.It does not append, limiting its capacity to 2 only. Following is my output and the code
circle.cpp
void circle::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton) {
if(mFirstClick){
x1 = e->pos().x();
y1 = e->pos().y();
mFirstClick = false;
mSecondClick = true;
}
else if(!mFirstClick && mSecondClick){
x2 = e->pos().x();
y2 = e->pos().y();
mPaintFlag = true;
mSecondClick = false;
update();
emit DrawFinished();
_store.set_point(e->pos());
store_point.push_back(_store);
qDebug() << _store.getValue();
qDebug() << "Size of vector =" << store_point.size() << "and" << store_point.capacity();
update();
}
}
mainwindow.cpp
void MainWindow::drawCircle(){
item2 = new circle;
scene->addItem(item2);
qDebug() << "Circle Created";
connect(item2, SIGNAL(DrawFinished()), this, SLOT(drawCircle()));
}
output
Circle Created
QPointF(60, 87)
Size of vector = 1 and 1
Circle Created
QPointF(77, 221)
Size of vector = 2 and 2
QPointF(333, 57)
Size of vector = 1 and 1
When I remove the signal DrawFinished(), the points store perfectly but the item gets painted only once. I need to pushthe button again:(. Following is the output after removing the signal.
QPointF(74, 80)
Size of vector = 1 and 1
QPointF(118, 165)
Size of vector = 2 and 2
QPointF(335, 97)
Size of vector = 3 and 4
What needs to be done to perfectly store the points as well as allow repainting. Please do help me to sort out all this.
Well, not sure if this would answer your request but a comment is too small to write what i want to tell you.
I don't really get what is the purpose of your signal DrawFinished(). Even if it's obvious thanks to the name, I don't think you need it.
If I sum up what you really want, you have a QGraphicView where you want to draw some shapes. Next to it, you have at least one (let's say 3) buttons to select which shapes you want to draw (Circle, Triangle, Rectangle).
Lets say you want to draw some circles, you click on the CircleButton, and then, click on the QGraphicView.
To my mind, I would create something like this:
Two classes, MainWindow and View, view which inherits from QGraphicView. Your three buttons are defined with Qt designer in your MainWindow class. So When you click on a button, you can emit a signal to notify the View.
In the View class you could have one vector for each shapes.
MainWindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void drawCircle();
void drawRectangle();
void drawTriangle();
private:
Ui::MainWindow *ui;
View view;
private slots:
void slotOnCircleButton();
void slotOnRectangleButton();
void slotOnTriangleButton();
};
MainWindow.cpp
[...]
void MainWindow::slotOnCircleButton()
{
emit(drawCircle());
}
[...]
View.h
class View : public QGraphicsView
{
Q_OBJECT
public:
explicit View(QWidget *parent = 0);
enum DrawingMode
{
UNDEFINED,
CIRCLE,
TRIANGLE,
RECTANGLE
}
signals:
public slots:
void slotOnDrawCircle();
void slotOnDrawRectangle();
void slotOnDrawTriangle();
private:
DrawingMode mode;
QVector<QPointF> vectorCircle;
QVector<QPointF> vectorTriangle;
QVector<QPointF> vectorRectangle;
};
View.cpp
[...]
void View::slotOnDrawCircle()
{
this->mode = CIRCLE;
}
[...]
void View::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton && this->mode != UNDEFINED)
{
qreal x1 = e->pos().x();
qreal y1 = e->pos().y();
if(this->mode == CIRCLE)
{
this->vectorCircle.append(e->pos());
}
else if(...)
[...]
// updatePainting();
}
}
When updating the view, you just have to travel throw your 3 vectors and drawing circle, rectangle or triangle.
This way you don't have such a spaghetti code, it's quite clear.
I didn't run the code so there is probable some minor mistakes, don't forget to make your connections and your initializations.

How to access widgets coordinates in QGridLayout

I am in the process of putting Qlabels in a QGridLayout to arrange them nicely in a class derived from QWidget. However, when I try to access the coordinates, it always returns (0,0)
Here is my code :
class brick : public QWidget
{
Q_OBJECT
public:
explicit brick(QWidget *parent);
...
private:
QLabel* label;
QPixmap* brickPic;
}
brick::brick(QWidget *parent) :
QWidget(parent)
{
rect=new QRect();
label=new QLabel;
brickPic=new QPixmap(100,15);
brickPic->fill(QColor(255,0,0));
label->setPixmap(*brickPic);
}
class Grid : public QWidget
{
QOBJECT
public:
void fill_grid(QWidget* parent);
...
private:
QGridLayout* grid;
}
void Grid::fill_grid(QWidget* parent)
{
for (int i=0;i<10;i++)
{
for (int j=0;j<12;j++)
{
brick* fillingBrick=new brick(parent);
grid->addWidget(fillingBrick->getLabel(),j,i);
qDebug()<<fillingBrick->parentWidget();
brickVector.push_back(fillingBrick);
}
}
}
These are to be shown as I said before in the following class derived from QWidget :
class render_area : public QWidget
{
Q_OBJECT
public:
render_area(QWidget *parent = 0);
Grid* getGrid() const;
...
private:
Grid* grid;
QRect* bar;
...
}
render_area::render_area(QWidget *parent)
:QWidget(parent)
{
setFocusPolicy(Qt::StrongFocus);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
bar=new QRect(width()/2,height()+50,150,10);
grid=new Grid(this);
grid->fill_grid(this);
std::cout<<"Grid filled !"<<std::endl;
qDebug()<<grid->getBrick(5)->pos();
...
}
The qDebug()<<fillingBrick->parentWidget() returns render_area which is good but qDebug()<<grid->getBrick(5)->pos() returns QPoint(0,0) which is not.
I wonder how I can get pos() or x() and y() to return the coordinates in render_area inside of which every brick is placed.
I tried with cellRect from QGridLayout, MapToParent() inherited from QWidget, forcing render_area to show(); but no luck so far.
Edit : this is the loop I'm talking about :
void render_area::update_timer()
{
int x1,x2,y1,y2;
bar->getCoords(&x1,&y1,&x2,&y2);
if(is_fixed==false)
{
//gestion du sol
if(yc+diametre>height())
{
timer.stop();
QDialog* gameOver=new QDialog;
gameOver->setLayout(new QGridLayout);
gameOver->layout()->addWidget(new QPushButton("Ok"));
gameOver->setGeometry(500,500,300,150);
gameOver->show();
}
if(xc>x1 && xc<x1+150)
{
if(yc+diametre>y1)
{
vyc=-vyc;
}
}
//plafond
for (int i=0;i<12;i++)
{
for(int j=0;j<10;j++)
{
grid->getBrick(i,j)->setRect(grid->getGrid()->cellRect(i,j));
}
}
for(int i=0;i<widgets.size();i++)
{
if(widgets.at(i)->getRect()->intersects(ballRectangle))
{
std::cout<<i<<std::endl;
widgets.at(i)->getPixmap()->fill(QColor(255,255,255));
widgets.at(i)->getLabel()->setPixmap(*(widgets.at(i)->getPixmap()));
delete widgets.at(i);
vyc=-vyc;
}
//qDebug()<<grid->getBrick(i,j)->getRect();
}
//bord droit
if(xc+diametre>width())
{
vxc=-vxc;
}
//bord gauche
if(xc<0)
{
vxc=-vxc;
}
//integration (Euler explicite)
xc=xc+dt*vxc;
yc=yc+dt*vyc;
}
repaint();
}
The widget positions and sizes are determined by the layout, and are calculated in calls from event loop sometime after you call show() on the window widget, and sometime before that window becomes visible.
Since you want to implement a brick breaker game, there are two approaches:
Use the graphics view/scene system.
Use widgets, like you want to do.
Your widget-based approach is excellent to demonstrate that the solution to your problems can be solved naturally by not checking brick positions at all.
You will be updating the ball position in a timer event, or using the animation system. In either case, the ball widget will receive a moveEvent with the new position. There, you can easily calculate the intersections of the ball rectangle with each of the brick widget rectangles. Since they are children of the same parent, they'll be in the same coordinate system. At that time, all the positions will be current. Upon detecting a collision, you reflect the ball's direction, and literally delete the brick widget. You can also start playing a "hit" sound effect.
It's reasonably easy to use the QObject property system to mark different brick widgets depending on their behavioral traits. All this can be done without having to subclass the base widget, which could be a QFrame for example.

Setting text below the icon rather than on right in QTableView?

I'm adding images as items to a QTableView, I'm also adding a specific text to each images, problem is the text is shown beside the image or the icon, but I want QTableView to show it below the image or the icon. My code snippet is as below:
QStandardItemModel * model = new QStandardItemModel(NumOfRow, 3, this);
Then comes this part which is in the loop
//
QStandardItem * itm = new QStandardItem;
itm->setIcon(image);
itm->setText(text);
model->setItem(row, column, itm);
//
Then this part outside the loop
ui->listOfImages->setModel(model);
ui->listOfImages->setStyleSheet(QString("icon-size: 150px 150px"));
ui->listOfImages->verticalHeader()->setVisible(false);
ui->listOfImages->horizontalHeader()->setVisible(false);
for(int i=0; i<=rowPointer; i++)
{
ui->listOfImages->setRowHeight(i,150);
}
for(int j=0; j<3; j++)
{
ui->listOfImages->setColumnWidth(j,150);
}
Could you say me if there is any way to put the name below the icon rather than in the right side of the icon?
Thanks
Well, I would try to handle the text alignment with the custom QStandardItemModel sub class. Here is the sample model, that implements it:
class ItemModel : public QStandardItemModel
{
public:
ItemModel(int rows, int columns, QObject *parent = 0)
:
QStandardItemModel(rows, columns, parent)
{}
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::TextAlignmentRole) {
return Qt::AlignBottom; // <- Make alignment look different, i.e.
// <- text at the bottom.
} else {
return QStandardItemModel::data(index, role);
}
}
};
So, instead of using the Qt class, you will need to create an instance of this custom class:
QStandardItemModel * model = new ItemModel(NumOfRow, 3, this);
The rest will remain the same.