I'm trying to create a BuildingTile class which has QGraphicsRectItem as base.
In this BuildingTile I'm trying to add QGraphicsEllipseItems and a QGraphicsSimpleTextItem but these do not use my BuildingTile's coordinate system although they say on http://doc.qt.io/qt-5/graphicsview.html: "Child coordinates are relative to the parent's coordinates. If the child is untransformed, the difference between a child coordinate and a parent coordinate is the same as the distance between the items in parent coordinates."
I would be really glad if someone could help me with this.
The header:
class BuildingTile : public QGraphicsRectItem
{
private:
Building* m_building;
bool m_empty;
QGraphicsSimpleTextItem* m_name;
QList<QGraphicsEllipseItem*> m_colonists;
public:
BuildingTile(qreal x, qreal y, QColor color, QString name, Building* m_building = 0);
bool isEmpty() const {return m_empty;}
void setEmpty(bool empty) {m_empty = empty;}
void setBuilding(Building* building) {m_building = building;}
};
The constructor:
BuildingTile::BuildingTile(qreal x, qreal y, QColor color, QString name, Building *building) : QGraphicsRectItem(x,y,150,75)
{
m_building = building;
setBrush(color);
for(int i = 0; i<3; i++)
{
QGraphicsEllipseItem* item = new QGraphicsEllipseItem(10+i*35, 40, 25, 25, this);
m_colonists.append(item);
item->setBrush(QColor(255,255,255));
}
m_name = new QGraphicsSimpleTextItem(name, this);
m_name->setPos(10,10);
}
MainWindow constructor:
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
{
QGraphicsScene* scene = new QGraphicsScene;
BuildingTile* item = new BuildingTile(0, 0, QColor(203,130,232), "small market");
scene->addItem(item);
item = new BuildingTile(150, 0, QColor(91,161,212), "indigo plant");
scene->addItem(item);
item = new BuildingTile(300, 0, QColor(120,113,107), "coffee roaster");
scene->addItem(item);
QGraphicsView* view = new QGraphicsView;
view->setScene(scene);
view->setAlignment(Qt::AlignTop | Qt::AlignLeft);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(view);
setLayout(layout);
}
All your BuildingTile items have their origin at the scene's origin, i.e. (0, 0) in scene coordinates.
For example (your second BuildingTile item):
item = new BuildingTile(150, 0, QColor(91,161,212), "indigo plant");
scene->addItem(item);
This creates a BuildingTile item located at (0, 0), containing a rectangle located at (150,0) of its own coordinate system. You're changing the position of the rectangle in its own coordinate system, but not the position of the rect's coordinate system in relation to its parent (the scene).
Now you create ellipses and labels in relation to the BuildingTile coordinate systems, which are all identical and located at (0,0) in "global" scene coordinates, so you end up with scene coordinates (10, 10) for all labels.
To achieve what you want, do:
item = new BuildingTile(0, 0, QColor(91,161,212), "indigo plant");
scene->addItem(item);
item->setPos(150, 0);
Related
I have a QGraphicsItem with an embedded QWidget, this QWidget have a QPushButton in it.
I'm trying to map the center of the QPushButton to the QGraphicsScene coordinates, so for example, I can add a Circle to the center of the QPushButton.
With the help from another post I was able to find the center of the QPushButton, but it doesn't correspond to its actual position in the QGraphicsScene.
What I tried:
Getting the QRect of the button and then its center, finally mapping to the global coordinates of the view.
Getting the QRect of the button, then its center and mapping it to the QGraphicsItem.
Getting the QRect of the button, then its center, mapping it to the QGraphicsItem and than mapping it to the scene.
In general, I tried mapping it to Scene, to Global and to Item but it always looks incorrect. And the farther I move the QGraphicsItem, the less accurate it gets. Here the circle is supposed to be positioned at the center of the "B" button:
Qwidget:
class NodeFrame : public QFrame
{
public:
NodeFrame();
QRect getButtonRect()
{
return layout->itemAt(0)->geometry();
}
private:
QPushButton* button = nullptr;
QHBoxLayout* layout = nullptr;
};
NodeFrame::NodeFrame()
{
setFixedSize(200,80);
// Creates and add a QPushButton to the frame.
// I need the position of this button on the QGraohicsScene
button = new QPushButton("B");
button->setFixedSize(40,20);
layout = new QHBoxLayout();
layout->addWidget(button);
layout->setAlignment(Qt::AlignCenter);
setLayout(layout);
}
QGraphicsItem:
class Node : public QGraphicsItem
{
public:
Node();
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QRect getButtonRect()
{
return frame->getButtonRect();
}
NodeFrame* frame = nullptr;
};
Node::Node()
{
setFlag(ItemIsMovable);
// Create a GraphicsProxyWidget to insert the nodeFrame into the scene
auto proxyWidget = new QGraphicsProxyWidget(this);
frame = new NodeFrame();
proxyWidget->setWidget(frame);
// Center the widget(frame) at the center of the QGraphicsItem
proxyWidget->setPos(boundingRect().center() - proxyWidget->boundingRect().center());
}
QRectF Node::boundingRect() const
{
return QRectF(-10, -10, 280, 150);
}
void Node::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QPainterPath path;
path.addRoundedRect(boundingRect(), 10, 10);
painter->drawPath(path);
}
main:
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// Create scene and view
auto scene = new QGraphicsScene();
auto view = new QGraphicsView(scene);
view->setMinimumSize(800, 800);
// Create the QGraphicsItem and add it to the scene
auto item = new Node();
scene->addItem(item);
item->setPos(50, 50);
auto btnRect = item->getButtonRect();
auto center = view->mapToGlobal(btnRect.center());
auto circle = new QGraphicsEllipseItem();
circle->setRect(QRectF(center.x(), center.y(), 25, 25));
scene->addItem(circle);
// Show the the view
view->show();
return app.exec();
}
Appreciate any help.
Solved. This was caused by two things:
1: QRectF getButtonRect() was returning layout->itemAt(0)->geometry(), (index 0 being the first and only widget in the layout) but button->frameGeometry() seems to be a more accurate visual representation of the button's geometry.
2: When adding the widget to the graphic item using QGraphicsProxyWidget, I was adjusting the position of the widget inside the graphic item using:
proxyWidget->setPos(boundingRect().center() - proxyWidget->boundingRect().center());
This was changing the position (obviously) of the widget inside the graphic item, so visually it didn't align with the result given by button->frameGeometry().
I want to overlay my QFrame(Draw several lines) onto a QGridLayout, to show some lines connecting some grids on the layout. But I can't find my lines with following code.
QHBoxLayout *basicLayout = new QHBoxLayout(parent);
QVBoxLayout *mapLayout = new QVBoxLayout(); // Grid Map
rightBarLayout = new QVBoxLayout(); // Game status bar
layout = new QGridLayout();
lines = new lineMask(); // A mask overlaying grid to draw lines
layout->addWidget(lines, 0, 0, -1, -1,
Qt::AlignRight);
mapLayout->addLayout(layout);
basicLayout->addLayout(mapLayout);
basicLayout->addLayout(rightBarLayout);
basicLayout->addWidget(lines);
And in lines:
class lineMask: public QFrame
{
private:
QVector<QVector<QLine>> lineGroup;
void paintEvent(QPaintEvent *) override;
QVector<bool> seen;
unsigned int num;
public:
lineMask();
int addLines(QVector<QLine> &);
void removeLines(int idx);
};
I am new to the Qt and I am confused how to use the QGraphicsScene. If I add for example 10 ellipses to the scene and animate them along the path, how would I do it? It gets even more complicated, because if I have 10 ellipses drawn along the ellipse and I want to animate those ellipses so they move away from the cente of the ellipse they are on. In the picture you can see the ellipses. Those are drawn with QPainter I haven't figured out how to add them to scene, but I would like the grey ellipses to move in between the inner and outter circle. I have gone through some examples, but can't really fit any of those to my situation.
Code used to draw ellipses with QPainter:
QPainter drElps;
drElps.setBrush(QBrush(QColor(0xaf, 0xaf, 0xaa)));
drElps.setPen(QPen(QColor(0xaf, 0xaf, 0xaa), 2));
double nrElps = 30;
int degree = (int)(360./nrElps);
painter.translate(QPointF(big_elps_circle_center));
while(--nrElps){
painter.drawEllipse(0, -60, 3, 3);
painter.rotate(degree);
}
painter.translate(QPOintF(back_to_origin));
Qt has a set of classes oriented to the animations, for this you must first create an object that inherits from QGraphicsObject, in this class you must implement the methods paint and boundingRect.
ellipseobject.h
#ifndef ELLIPSEOBJECT_H
#define ELLIPSEOBJECT_H
#include <QGraphicsObject>
class EllipseObject : public QGraphicsObject
{
Q_OBJECT
public:
EllipseObject(QGraphicsItem * parent = 0);
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
QRectF boundingRect() const;
};
#endif // ELLIPSEOBJECT_H
ellipseobject.cpp
#include "ellipseobject.h"
#include <QPainter>
EllipseObject::EllipseObject(QGraphicsItem *parent):QGraphicsObject(parent)
{
}
void EllipseObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(QColor(0xaf, 0xaf, 0xaa));
painter->setBrush(QBrush(QColor(0xaf, 0xaf, 0xaa)));
QRectF rect = boundingRect();
painter->drawEllipse(rect);
}
QRectF EllipseObject::boundingRect() const
{
return QRectF(-4, -4, 8, 8);
}
Then we can use the QPropertyAnimation class, with this we will animate the position.
EllipseObject *item = new EllipseObject;
QPropertyAnimation *animation = new QPropertyAnimation(item, "pos");
for(int j = 0; j < p; j++){
animation->setKeyValueAt( 1.0*j/(p-1),
(r+ delta*sin(2*M_PI*j/p) )*QPointF(qSin(2*M_PI*i/number_of_items), qCos(2*M_PI*i/number_of_items)));
}
animation->setDuration(2000);
Since we have several elements that are animated in parallel, we can use QParallelAnimationGroup to handle each of the animations.
group = new QParallelAnimationGroup(this);
[...]
group->addAnimation(animation);
If we want to be continuous we can make the following code.
group->start();
connect(group, &QParallelAnimationGroup::finished,[=](){
group->start();
});
The complete code is here.
If you draw them with QPainter then you are on your on. The point of QGraphivsScene is to inherit from QGraphicsItem, paint what you need to paint on the paintEvent, add them to the scene.
you are drawint more than 1 item on your paintEvent - so you are on your own - and QGraphivsScene cannot help you there.
Now, how to do that Correctly:
Inherit from QGraphicsScene or QGraphivsView
Inherit from QGraphicsEllipseItem and create a MovableEllipseItem
in the MovableEllipseItem constructor, create a QTimer, connect it's timeout() signal with a move slot that you will define
on your move slot, do the calculations on where the ellipse should be and move(x,y) it.
You can use a custom Graphics Item, the QGraphicsScene::advance mechanism, together with a QTimeLine to control the animation.
The outline is like this:
QGraphicsScene *sc = new QGraphicsScene;
QTimeLine *tl = new QTimeLine(1000);
tl->setFrameRange(-20, 20);
connect(tl, &QTimeLine::frameChanged, sc, &QGraphicsScene::advance);
connect(tl, &QTimeLine::finished, tl, &QTimeLine::toggleDirection);
connect(tl, &QTimeLine::finished, tl, &QTimeLine::start);
for (int i = 0; i < 30; ++i) {
sc->addItem(new AnimItem(360./30*i, tl));
}
In the custom AnimItem, you implement the drawing/animation logic. A good base would be QGraphicsEllipseItem. For example:
AnimItem::AnimItem(qreal angle, QTimeLine *timer, QGraphicsItem *parent)
: QGraphicsEllipseItem(0, 0, 3, 3, parent),
m_timer(timer)
{
QTransform t;
t.rotate(angle);
t.translate(0, -120);
setTransform(t);
setBrush(QBrush(QColor(0xaf, 0xaf, 0xaa)));
}
void AnimItem::advance(int phase)
{
if (phase == 1) {
QTransform t = transform();
t.translate(0, m_timer->currentFrame()/5);
setTransform(t);
}
}
I am trying to achieve this layout:
where Widget1 is some widget (central widget of QMainWindow) and I want to add second widget Widget2 over it but it should be in left bottom corner of Widget1.
EDIT: my previous description wasn't very useful so I will try to describe it in more details.
I am inheriting QWidget class (class MyClass : public QWidget) and creating my own widget where I in void MyClass ::paintEvent(QPaintEvent *event) draw something on screen.
MyClass is then centralWidget of my QMainWindow.
Now on top of that I want to add smaller widget (Widget2 in image) where I would display some video (here I am not asking how to display video only how to add this Widget2 to my view).
Main thing here is that Widget2 is inside (floating in) Widget1.
EDIT2: Previous code I posted is rubbish.
Use QGridLayout to set the position of the widget:
QGridLayout* layout = new QGridLayout(this);
// 2x2 layout
QWidget* green = new QWidget(this);
green->setStyleSheet("background:green;");
QWidget* yellow = new QWidget(this);
yellow->setStyleSheet("background:yellow;");
QWidget* red = new QWidget(this);
red->setStyleSheet("background:red;");
QWidget* blue = new QWidget(this);
blue->setStyleSheet("background:blue;");
layout->addWidget(green, 0, 0); // Top-Left
layout->addWidget(yellow, 0, 1); // Top-Right
layout->addWidget(red, 1, 0); // Bottom-Left
layout->addWidget(blue, 1, 1); // Bottom-Right
ui->centralWidget->setLayout(layout);
Will give you something like that:
So, custom your own widget using QGridLayout and set the position of your widget inside it.
Set another widget as parent with black background:
QGridLayout* layout = new QGridLayout(this);
// 2x2 layout
QWidget* green = new QWidget(this);
green->setStyleSheet("background:green;");
QWidget* yellow = new QWidget(this);
yellow->setStyleSheet("background:yellow;");
QWidget* red = new QWidget(this);
red->setStyleSheet("background:red;");
QWidget* blue = new QWidget(this);
blue->setStyleSheet("background:blue;");
layout->addWidget(green, 0, 0); // Top-Left
layout->addWidget(yellow, 0, 1); // Top-Right
layout->addWidget(red, 1, 0); // Bottom-Left
layout->addWidget(blue, 1, 1); // Bottom-Right
QWidget* mainWidget = new QWidget(this);
mainWidget->setStyleSheet("background:black;");
mainWidget->setLayout(layout);
QHBoxLayout* centralLayout = new QHBoxLayout(this);
centralLayout->addWidget(mainWidget);
ui->centralWidget->setLayout(centralLayout);
I have the following code:
QGraphicsScene* pScene = new QGraphicsScene(this);
ui->graphicsView->setScene(pScene);
pScene->addRect(0, 0, 200, 200);
QGraphicsRectItem* pRect1 = pScene->addRect(40, 40, 100, 100);
QGraphicsRectItem* pRect2 = new QGraphicsRectItem(20, 20, 19, 19, pRect1);
QPointF pf1 = pRect1->pos();
QPointF pf2 = pRect2->pos();
QPointF pf3 = pRect2->mapFromParent(pRect1->pos());
QPointF pf4 = pRect2->mapToParent(pRect2->pos());
QPointF spf1 = pRect1->scenePos();
QPointF spf2 = pRect2->scenePos();
Nothing special, just a QGraphicsView with QGraphicsScene and a few QGraphicsRectItem(s).
Question: Why do all the points (pf1, pf2, pf3, pf4 and even spf1, spf2) equal QPointF(0.0, 0.0) after execution?
I'm using Qt 5.4.1.
From my understanding spf1 must be QPointF(40.0, 40.0) and spf2 must be QPointF(20.0, 20.0).
If you will look in the documentation then you will find:
QGraphicsRectItem *QGraphicsScene::addRect(const QRectF &rect, const
QPen &pen = QPen(), const QBrush &brush = QBrush())
Creates and adds a rectangle item to the scene, and returns the item
pointer. The geometry of the rectangle is defined by rect, and its pen
and brush are initialized to pen and brush. Note that the item's
geometry is provided in item coordinates, and its position is
initialized to (0, 0). For example, if a QRect(50, 50, 100, 100) is
added, its top-left corner will be at (50, 50) relative to the origin
in the items coordinate system
.As result, the coordinate of the Item is (0,0) but you can set it with:
pRect1->setPos(QPoint(30,30));
and look what will happen. Hope it will help you to understand QGraphicsScene coordinate system!
P.S.:
QGraphicsRectItem* pRect1 = pScene->addRect(0, 0, 100, 100);
QGraphicsRectItem* pRect2 = pScene->addRect(0, 0, 20, 20);
pRect1->setPos(QPoint(40,40));
pRect2->setPos(QPoint(20,20));