How can I set the display range of a QGraphicItemGroup? - c++

I have a QGraphicsItemGroup aggregating several child items, and I want to show only part of the group.(not the numbers of child items, area). Just like the image here.
I want to show the display area.
To do that, I have tried the override the QGraphicsItemGroup::boundingRect(). However, nothing have happened. And i find this in QT docs, maybe this is the reason why doesn't work.
The boundingRect() function of QGraphicsItemGroup returns the bounding rectangle of all items in the item group.
Also, I know I can change the size of QGraphicsView to make it work. However I put the View as CentralWidget, as I also need to display other object in the View, I can not change the size of the View.
How can I set the display range of a QGraphicItemGroup?

To perform this task we can overwrite shape() by returning a QPainterPath that defines the visible region, so that it spreads to its children we enable the flag ItemClipsChildrenToShape:
class GraphicsItemGroup: public QGraphicsItemGroup{
public:
GraphicsItemGroup(QGraphicsItem * parent = 0):QGraphicsItemGroup(parent){
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
}
QPainterPath shape() const
{
if(mShape.isEmpty())
return QGraphicsItemGroup::shape();
return mShape;
}
void setShape(const QPainterPath &shape){
mShape = shape;
update();
}
private:
QPainterPath mShape;
};
Example:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setLayout(new QVBoxLayout);
QGraphicsView view;
QPushButton button("click me");
w.layout()->addWidget(&view);
w.layout()->addWidget(&button);
view.setScene(new QGraphicsScene);
GraphicsItemGroup group;
view.scene()->addItem(&group);
auto ellipse = new QGraphicsEllipseItem(QRectF(0, 0, 100, 100));
ellipse->setBrush(Qt::red);
auto rect = new QGraphicsRectItem(QRect(150, 150, 100, 100));
rect->setBrush(Qt::blue);
group.addToGroup(ellipse);
group.addToGroup(rect);
QObject::connect(&button, &QPushButton::clicked, [&group](){
QPainterPath shape;
if(group.shape().boundingRect() == group.boundingRect()){
shape.addRect(0, 50, 250, 150);
}
group.setShape(shape);
});
w.show();
return a.exec();
}
Output:
The complete example can be found in the following link.

Related

How to use the QGraphicsItem::setPos() function

I can't figure out how the setPos() function of the QGraphicsItem class works.
My Rect class has no parent, so its origin is relative to the scene.
I try to put the rectangle back at (0, 0) after it is moved with the mouse but it is placed in a different place depending on where I had moved it.
I suppose that means that the origin of the scene moves but what causes this change?
class Rect : public QGraphicsItem {
public:
Rect(): QGraphicsItem()
{
setFlag(ItemIsMovable);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->drawRect(0, 0, 20, 20);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
{
setPos(0, 0);
update();
QGraphicsItem::mouseReleaseEvent(event);
}
QRectF boundingRect() const
{
return QRectF(0, 0, 20, 20);
}
private:
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
Rect obj;
scene.addItem(&obj);
view.show();
return a.exec();
}
When you create a QGraphicsView you initially accept the default settings. A standard setting is, for example, that it is horizontally centered.
Another factor is that the default area size is probably up to the maximum size.
what you can do set a custom size for the scene. You do that with graphicsView->setSceneRect(0,0,300,300); (for example)
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
ui->graphicsView->setSceneRect(0,0, 300,300);
rectItem = new QGraphicsRectItem(0,0, 100, 100);
rectItem->setPen(QPen(Qt::darkMagenta, 2));
rectItem->setBrush(QGradient(QGradient::SaintPetersburg));
rectItem->setPos(190,10);
scene->addItem(rectItem);
So in summary: if you want to work with fixed values. maybe it is better to know the total size. (that was not clear from your code, that's why I gave this example)

adding a custom widget in Layout at execution

I'm under Ubuntu 18.04 and using last QtCreator with last Qt5 framework
I'm trying to setup a vertical layout with at top an horizontal nested layout and under a custom made widget (based on clock exemple for the moment)
I setup a fresh widget project but as I dont understand yet how to get acess to nested layout from c++ code I removed automaticaly created mainform and created layout at execution.
If I use my custom widget as window it works if I use a window and add my custom widget it works but if I add a layout, add my custom widget to this layout and call setLayout on window it disappear...
I tried almost all orders : set the layout first or after adding my widget.
I tried to call show() on my widget or not befor or after adding it to layout
I tried to add the nested layer first or last nothing change
I 've read several time exemples and manual about layout and nested one
I can see my nested layout but not my widget
here's my main :
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setWindowTitle("WOUND : Windings Organiser with Usefull and Neat Drawings");
QVBoxLayout* hl=new QVBoxLayout;// top vertical layout
QHBoxLayout* slotqueryLayout=new QHBoxLayout; // nested first horizontal layout
QLabel *slotqueryLabel = new QLabel("Slot number:");
QLineEdit *slotqueryEdit = new QLineEdit("48");
slotqueryLayout->addWidget(slotqueryLabel);
slotqueryLayout->addWidget(slotqueryEdit);
hl->addLayout(slotqueryLayout);
WindingViewer* wv=new WindingViewer(&w); // my widget is just a simple canvas to draw things
hl->addWidget(wv);
wv->show(); // dont know if it's needed but if I remove it it dont change anything / tried to do it before or after adding to layout
w.setLayout(hl); // if called before adding content it dont change
w.show();
return a.exec();
}
and here you can find my custom widget:
class WindingViewer : public QWidget
{
Q_OBJECT
public:
explicit WindingViewer(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
public :
int SlotNumber;
public slots:
};
and
WindingViewer::WindingViewer(QWidget *parent) : QWidget(parent)
{
SlotNumber=3;
resize(200, 200);
}
void WindingViewer::paintEvent(QPaintEvent *)
{
int side = qMin(width(), height());
QColor SlotColor(127, 127, 127);
QPainter painter(this);
static const QPoint slotpolygonext[4] = {
QPoint(-2,85),
QPoint(-3,95),
QPoint(3, 95),
QPoint(2, 85)
};
static const QPoint slotpolygonint[5] = {
QPoint(-1,75),
QPoint(-2,85),
QPoint(2, 85),
QPoint(1, 75),
QPoint(-1,75),
};
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(width() / 2, height() / 2);
painter.scale(side / 200.0, side / 200.0);
painter.setPen(SlotColor);
for (int i = 0; i < SlotNumber; ++i) {
painter.drawPolyline(slotpolygonext,4);
painter.drawPolyline(slotpolygonint,5);
painter.rotate(360.0/SlotNumber);
}
}
I hope the question is clear enough. I've search for an answer here and over internet before posting. I found few things but nothing totaly related.
The custom widget is part of the window, you could see if manually with the mouse you make the height increase.
And then why is it hidden?
The layouts handle size policies and use the sizeHint() function of the widgets to obtain the default size, and in your case the sizeHint() is not implemented because it is not observed when the window is displayed, the solution is to implement that method:
*.h
public:
explicit WindingViewer(QWidget *parent = nullptr);
QSize sizeHint() const override;
*.cpp
QSize WindingViewer::sizeHint() const
{
return QSize(200, 200);
}

Hide area of QGraphicsItem that is out of boundary

I have a QGraphicsPixmap item in a QGraphicsScene. The item has flags set to ItemIsMovable, and ItemIsSelectable. How do I ensure that when the item is moved out of a certain boundary - it can be a QGraphicsScene or just a fixed frame size at fixed coordinates - the part becomes hidden?
Eg.
The left part of the basketball becomes hidden.
You have to use setClipPath().
In the following code I have created a class that inherits from QGraphicsPixmapItem (the same could do with other classes that inherit from QGraphicsItem) and I created the method setBoundaryPath() that receives a QPainterPath that indicates the visible area, for example in the code use:
QPainterPath path;
path.addRect(QRectF(100, 100, 400, 200));
That QPainterPath is a rectangle whose topleft is the point (100, 100) of the QGraphicsScene with size of 400 in width and 200 in height.
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
class GraphicsPixmapItem: public QGraphicsPixmapItem{
public:
GraphicsPixmapItem(const QPixmap & pixmap, QGraphicsItem *parent = 0):
QGraphicsPixmapItem(pixmap, parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
if(!m_boundaryPath.isEmpty()){
QPainterPath path = mapFromScene(m_boundaryPath);
if(!path.isEmpty())
painter->setClipPath(path);
}
QGraphicsPixmapItem::paint(painter, option, widget);
}
QPainterPath boundaryPath() const{
return m_boundaryPath;
}
void setBoundaryPath(const QPainterPath &boundaryPath){
m_boundaryPath = boundaryPath;
update();
}
private:
QPainterPath m_boundaryPath;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene scene(0, 0, 600, 400);
view.setScene(&scene);
view.setBackgroundBrush(QBrush(Qt::gray));
GraphicsPixmapItem *p_item = new GraphicsPixmapItem(QPixmap(":/ball.png"));
p_item->setPos(100, 100);
// Define the area that will be visible
QPainterPath path;
path.addRect(QRectF(100, 100, 400, 200));
p_item->setBoundaryPath(path);
scene.addItem(p_item);
// the item is added to visualize the intersection
QGraphicsPathItem *path_item = scene.addPath(path, QPen(Qt::black), QBrush(Qt::white));
path_item->setZValue(-1);
view.show();
return a.exec();
}
You can find the example code in this link.

How to change size of QGraphicsPixmap using animation?

I have class named Pixmap deriving from QGraphicsPixmapItem and StartScreen class deriving from QGraphicsScene. I want to use animations (QPropertyAnimation class) to resize displayed image in certain time range. Other actions like setting position or rotation aren't problem but I couldn't find any property like size (e.g. setSize() method). How can I do that in the other way? Thanks for advance.
StartScreen::StartScreen(int windowWidth, int windowHeight)
{
setSceneRect(0, 0, windowWidth, windowHeight);
setBackgroundBrush(QBrush(QImage(":/images/background.png")));
Pixmap * logo = new Pixmap(":/images/logo.png");
addItem(logo);
logo->setPos((windowWidth - logo->pixmap().width()) / 2, (windowWidth - logo->pixmap().width()) / 2 - 75);
//QPropertyAnimation * animation = new QPropertyAnimation(logo, "");
}
QPropertyAnimation applies to Qt Properties, but only objects that inherit from QObject have Qt properties, so if you want to use animations you can use QGraphicsObject and create your own item, or create a class that inherits the item you want and QObject.
class Pixmap: public QObject, public QGraphicsPixmapItem{
Q_OBJECT
Q_PROPERTY(qreal scale READ scale WRITE setScale)
Q_PROPERTY(qreal rotation READ rotation WRITE setRotation)
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
public:
using QGraphicsPixmapItem::QGraphicsPixmapItem;
};
In the previous example, take advantage of the fact that QGraphicsItem, and therefore its derived classes, have the methods pos(), setPos(), scale(), setScale(), rotation() and setRotation(), so only use them in Q_PROPERTY.
In the next part I show an example:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView w;
QGraphicsScene scene(0, 0, 640, 480);
w.setScene(&scene);
w.show();
Pixmap* logo = new Pixmap(QPixmap(":/image.jpg"));
scene.addItem(logo);
QSequentialAnimationGroup group;
QPropertyAnimation animation_scale(logo, "scale");
animation_scale.setDuration(1000);
animation_scale.setStartValue(2.0);
animation_scale.setEndValue(0.1);
QPropertyAnimation animation_pos(logo, "pos");
animation_pos.setDuration(1000);
animation_pos.setStartValue(QPointF(0, 0));
animation_pos.setEndValue(QPointF(100, 100));
/**
* it must indicate the center of rotation,
* in this case it will be the center of the item
*/
logo->setTransformOriginPoint(logo->boundingRect().center());
QPropertyAnimation animation_rotate(logo, "rotation");
animation_rotate.setDuration(1000);
animation_rotate.setStartValue(0);
animation_rotate.setEndValue(360);
group.addAnimation(&animation_scale);
group.addAnimation(&animation_pos);
group.addAnimation(&animation_rotate);
group.start();
return a.exec();
}
#include "main.moc"
In the following link there is an example
Or you can use QVariantAnimation instead of QPropertyAnimation:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView w;
QGraphicsScene scene(0, 0, 640, 480);
w.setScene(&scene);
w.show();
QGraphicsPixmapItem* logo = new QGraphicsPixmapItem(QPixmap(":/image.jpg"));
scene.addItem(logo);
QSequentialAnimationGroup group;
QVariantAnimation animation_scale;
animation_scale.setDuration(1000);
animation_scale.setStartValue(2.0);
animation_scale.setEndValue(0.5);
QObject::connect(&animation_scale, &QVariantAnimation::valueChanged, [logo](const QVariant &value){
logo->setScale(value.toReal());
});
animation_scale.start();
QVariantAnimation animation_pos;
animation_pos.setDuration(1000);
animation_pos.setStartValue(QPointF(0, 0));
animation_pos.setEndValue(QPointF(100, 100));
QObject::connect(&animation_pos, &QVariantAnimation::valueChanged, [logo](const QVariant &value){
logo->setPos(value.toPointF());
});
/**
* it must indicate the center of rotation,
* in this case it will be the center of the item
*/
logo->setTransformOriginPoint(logo->boundingRect().center());
QVariantAnimation animation_rotate;
animation_rotate.setDuration(1000);
animation_rotate.setStartValue(0);
animation_rotate.setEndValue(360);
QObject::connect(&animation_rotate, &QVariantAnimation::valueChanged, [logo](const QVariant &value){
logo->setRotation(value.toReal());
});
group.addAnimation(&animation_scale);
group.addAnimation(&animation_pos);
group.addAnimation(&animation_rotate);
group.start();
return a.exec();
}
In the following link there is an example

QGraphicsView possible bug?

The example code is from my project. I've tried to make it as short as possible and to the point.
The overlay is used to draw over all the other widgets in the app. This works for most widgets, but today I've started to notice that QAbstractScrollArea subclasses are giving me a hard time. The problem is that the overlay appears not on top, and whatever drawing that happens is blocked.
#include <QtGui/QApplication>
#include <QtGui/QVBoxLayout>
#include <QtGui/QGraphicsView>
#include <QtGui/QPushButton>
class View : public QGraphicsView{
public:
View(){
//delete viewport(); setViewport(new QWidget);
}
};
class Widget : public QWidget{
QWidget* overlay_;
public:
Widget(){
resize(512, 512);
QVBoxLayout* layout = new QVBoxLayout;
QPushButton* button = new QPushButton(" Click Me! ");
layout->addWidget(button);
layout->addWidget(new View);
overlay_ = new QWidget(this);
overlay_->installEventFilter(this);
connect(button, SIGNAL(clicked()),
overlay_, SLOT(show()));
overlay_->hide();
setLayout(layout);
}
bool eventFilter(QObject* target, QEvent* event){
if(target == overlay_){
if(event->type() == QEvent::Paint && overlay_->isVisible()){
overlay_->resize(size());
QPainter painter(overlay_);
painter.setPen(QPen(QColor(1, 102, 192, 255), 1, Qt::SolidLine,
Qt::FlatCap, Qt::MiterJoin));
painter.drawRect(rect().adjusted(60, 0, -60, 0));
return true;
}
}
}
};
int main(int argc, char *argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
To fix this in this example and have overlay go on top of View, you'll need to uncomment the commented line at the top. So my question is this: why do I need to delete and assign a new viewport widget in the constructor in order for overlay not get overdrawn?
This isn't a bug with QGraphicsView, it will happen if you use a standard QScrollArea as well.
The issue, I think, is the order in which Qt draws child widgets. Sibling widgets are drawn in the order they are added to the parent (although you can't rely on this).
The reason that resetting the viewport "solved" the problem is because when you do that you create a new QWidget that has no background to be the viewport. The QGraphicsView is still being drawn over the overlay_, it just has a transparent viewport. Notice how it's still drawn behind the pushbutton, however.
If you want to draw an overlay only over the QGraphicsView, you can override QGraphicsView::paintEvent() and do it there. If you want to draw the overlay over your entire widget, I would embed your layout inside a second QWidget and then try using QWidget::raise() to force the overlay visually to the top.