How to map from MainWindow to a QGraphicsItem? - qt

I am attempting to spawn a custom QGraphicsItem inside my scene, but am unsure of how exactly to map the point from its origin to the Item. My location comes from a dropEvent in my MainWindow:
void MainWindow::dropEvent(QDropEvent *event)
{
QPointF dropPos = ui->GraphicsView->mapFrom(this, event->pos());
vModule *module = new vModule(dropPos);
ui->GraphicsView->scene->addItem(module);
}
This is my vModule.cpp:
vModule::vModule(QPointF dropPos)
{
QPointF pos = mapFromScene(dropPos);
setX(pos.x());
setY(pos.y());
// ...
}
// Event handler implementation, shouldn't be relevant
And my vModule.h:
class vModule : public QObject, public QGraphicsItem
{
public:
explicit vModule(QPointF dropPos);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
QRectF boundingRect() const;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
}
This is the closest to what I logically thought the conversion would be, but it is quite wrong in practice, more wrong than simply passing the position from the event as-is (which is a constant difference in position from the main window?). Can anyone correct my error?

In the constructor of vModule, you're calling mapFromScene before the widget has even been added to the scene.
Add the Widget first then set its position. Also, if you think about it, you're going from global (screen) coordinates to local widget coordinates. This function may be of use, assuming the drop event coordinates are in screen space: -
QPoint QWidget::mapFromGlobal(const QPoint & pos) const
which Qt help defines as: -
Translates the global screen coordinate pos to widget coordinates.

Related

How to draw something with QPainter when the button is pushed

I am working on my project from programming and I need to draw, for example, a circle every time the pushButton is pressed using QPainter. This is the first problem, and the second one here is that I need some information to be sent to the drawing function too, for example, int vector, and being able to draw so many circles, as there are elements in the vector with radii of the elements itself. I have found some code based on signals and slots.
The sender:
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
The reciever:
void receiveData(int x, int y) {
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
QPoint point(x, y);
painter.drawEllipse(x, y, 100, 100);
data.append(point);
}
The connection itself in main.cpp
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
But the code doesn't suit for my exact task with buttons and doesn't even want to draw anythig, just any circle at all. Howewer, in debugger the code executes properly, and I am relatively new to Qt and C++ so I can't figure out by myself, where the problem is and how to solve my task.Can someone please do a minimal of code or simply explain to me, where exactly the problem is? Need to solve the problem as soon as possible. Thank you.
Upd: any possible solution with or without QPainter would be good now.
Qt Forum users gave me an answer.
Quote:
From the QPainter class description:
Warning: When the paintdevice is a widget, QPainter can only be used inside a
paintEvent() function or in a function called by paintEvent().
You can force calling paintEvent() by invoking update(), so you must connect the onclicked() signal of your button to the update() slot of the widget you're drawing on.
For your second problem, the data can be a member variable.
Here's an example:
// mywidget.h
#include <QVector>
#include <QPoint>
// other includes and the constructor...
protected:
virtual void paintEvent(QPaintEvent *event);
private slots:
void onButtonClicked();
private:
QPushButton* mButton;
QVector<QPoint> mCirclesData;
// mywidget.cpp
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
mButton = new QPushButton(this);
// customise your button...
connect(mButton, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
}
//...
void MyWidget::onButtonClicked(){
int x = qrand() % 200, y = x;
mCirclesData << QPoint(x,y);
update(); // force calling paintEvent
}
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
painter.drawEllipse(mCirclesData.last().x(), mCirclesData.last().y(), 100, 100);
}
``

QGraphicsItem::itemChange notified for position change but not for size change

I've got a class derived from QGraphicsEllipseItem in which I need to know when its position or size changes in any way. I handle resizing with a mouse and calls to QGraphicsEllipse::setRect.
OK, so I dutifully overrode the itemChange() method in the class and then was careful to set the ItemSendsGeometryChanges flag after creating it
// Returns a human readable string for any GraphicsItemChange enum value
inline std::string EnumName(QGraphicsItem::GraphicsItemChange e);
// Simple test ellipse class
class MyEllipse : public QGraphicsEllipseItem
{
public:
MyEllipse(int x, int y, int w, int h) : QGraphicsEllipseItem(x, y, w, h)
{
setFlags(
QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemIsMovable
| QGraphicsItem::ItemSendsGeometryChanges);
}
// QGraphicItem overrides
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override
{
std::stringstream oss;
oss << "ItemChange " << EnumName(change) << std::endl;
OutputDebugString(oss.str().c_str());
return __super::itemChange(change, value);
}
};
My main code creates one of these, adds it to the scene and then tries moving/resizing it.
And while I do always receive notifications after calling setPos() on the ellipse, I get NO notification after calling setRect(). I can use setRect to completely change the ellipse's geometry but my itemChange override is never called. Not with any flags.
Now obviously changing the item's rect is changing its geometry, so what am I missing?
Is there some other flag I should set? Some other way to change the size of the ellipse I should use? Some other notification virtual I can override?
The problem is that QGraphicsItem's position is not related with QGraphicsEllipseItem's rectangle. The first one is a position of the item relative to it's parent item or, if it is NULL, to it's scene. The last one is a rectangle relative to the item position where an ellipse should be drawn. The scene and QGraphicsItem's core don't know about any changes of it.
Let's take a look at this test:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
QGraphicsEllipseItem item(10, 20, 30, 40);
scene.addItem(&item);
qDebug() << item.pos() << item.scenePos() << item.boundingRect();
item.setRect(110, 120, 30, 40);
qDebug() << item.pos() << item.scenePos() << item.boundingRect();
view.resize(500, 500);
view.show();
return app.exec();
}
Output is:
QPointF(0,0) QPointF(0,0) QRectF(9.5,19.5 31x41)
QPointF(0,0) QPointF(0,0) QRectF(109.5,119.5 31x41)
Possible ways out:
use setTransform. Transform matrix changes are tracked by standard QGraphicsitems, and itemChange will receive corresponding change. But I guess that non-ident matrices can decrease performance (didn't check).
implement your own function like setRect in which you will track geometry changes manually.
subclass QGraphicsItem, not QGraphicsEllipseItem. In this case you can prevent untrackable geometry changes as they are performed through your rules. It looks like this:
class EllipseItem: public QGraphicsItem
{
public:
// Here are constructors and a lot of standard things for
// QGraphicsItem subclassing, see Qt Assistant.
...
// This method is not related with QGraphicsEllipseItem at all.
void setRect(const QRectF &newRect)
{
setPos(newRect.topLeft());
_width = newRect.width();
_height = newRect.height();
update();
}
QRectF boundingRect() const override
{
return bRect();
}
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option,
QWidget * widget = nullptr) override
{
painter->drawRect(bRect());
}
private:
qreal _width;
qreal _height;
QRectF bRect() const
{
return QRectF(0, 0, _width, _height);
}
};
You also should track item transformations and moves through QGraphicsItem::itemChange.

How to animate the outline of a QGraphicsItem in real time?

I am designing a timer with Qt. With QGraphicsEllipseItem, I drew a circle and now I need to animate the QPen around this circle (change color) every second. I found QGraphicsPathItem, but I need some examples on how to move forward. Can anyone show me an example?
You have two problems:
QGraphicsEllipseItem is not a QObject so QPropertyAnimation can't be used directly on this item
QGraphicsItemAnimation doesn't cover property you want to animate.
What you can do?
IMO best approach is to provide some custom QObject on which you could do this animation. You can inherit QObject or use fake QGraphicsObject (which is a QObject).
class ShapeItemPenAnimator : public QGraphicsObject {
Q_OBJECT
private:
QAbstractGraphicsShapeItem *mParent;
QPropertyAnimation *mAnimation;
public:
QPROPERTY(QColor penColor
READ penColor
WRITE setPenColor)
explicit ShapeItemPenAnimator(QAbstractGraphicsShapeItem * parent)
: QGraphicsObject(parent)
, mParent(parent) {
setFlags(QGraphicsItem::ItemHasNoContents);
mAnimation = new QPropertyAnimation(this, "penColor", this);
}
QColor penColor() const {
return mParent->pen().color();
}
public slots:
void setPenColor(const QColor &color) {
QPen pen(mParent->pen());
pen.setColor(color);
mParent->setPen(pen);
}
public:
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0) {
}
QRectF boundingRect() const {
return QRectF();
}
QPropertyAnimation *animation() const {
return mAnimation;
}
}
Now you just attach this object to your QGraphicsEllipseItem and set animation you need.
// yourEllipse
ShapeItemPenAnimator *animator = new ShapeItemPenAnimator(yourEllipse);
animator->animation()->setEndValue(....);
animator->animation()->setStartValue(....);
animator->animation()->setDuration(....);
animator->animation()->setEasingCurve(....);
There are several classes helping with animations of QGraphicsItem in Qt. I suggest looking into QGraphicsItemAnimation and QPropertyAnimation. You can use the second one to animate the color of an item. Here is an example of using QPropertyAnimation:
How to make Qt widgets fade in or fade out?

How to handle right mouse click event for QGraphicsItem?

I have a class derived from QGraphicsItem, which is basically like this:
class MyRect: public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
public:
explicit MyRect(QObject *parent = 0);
MyRect(QColor fillColor, float val, QString txt = "", bool isLeaf = false);
int width, height;
protected:
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
virtual QRectF boundingRect() const;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
};
The problem is the mouseReleaseEvent and mousePressEvent only accept QGraphicsSceneMouseEvent argument which cannot detect right button click. I know there is a mousePressEvent(QMouseEvent *event) version that I can use, but it seems not work for QGraphicsItem....Just cannot get stuck here...I appreciate your answer.
Try re-implementing QGraphicsItem::contextMenuEvent and checking the QGraphicsSceneContextMenuEvent::Reason for if the event was caused by a mouse click.

Why is the widget not getting resizeEvent?

I am trying to implement a new custom scrollbar. In order to do so, I have placed it onto the parent manually with this->setParent(area);
class QMacScrollBar : public QScrollBar
{
Q_OBJECT
public:
QMacScrollBar(QAbstractScrollArea *area, Qt::Orientation orientation, QWidget *parent = 0);
virtual void paintEvent ( QPaintEvent * );
QAbstractScrollArea *area;
signals:
public slots:
virtual void resizeEvent(QResizeEvent *event);
};
QMacScrollBar::QMacScrollBar(QAbstractScrollArea *area, Qt::Orientation orientation, QWidget *parent) :
QScrollBar(area)
{
this->setMouseTracking(true);
this->setOrientation(orientation);
this->setParent(area);
this->area = area;
}
void QMacScrollBar::resizeEvent(QResizeEvent *event){
QRect geometry = QRect(QPoint(0, 0), event->size());
if (area != NULL){
geometry.setHeight(area->height());
geometry.moveRight(area->width());
geometry.adjust(-5, 0, -5, 0);
this->setGeometry(geometry);
}
QScrollBar::resizeEvent(event);
}
I do get the first few resizes, but none after that. So far my guess is that when I resize the window, the QScrollArea doesnt actually resize, it just hides more of the viewport. I need the resize event because I need to move the place that I display the bar. How do I fix this?
EDIT: The only resizes I am getting are the ones that I spawn via connecting to the underlying scrollbar's setRange signal.

Resources