QGraphicsView not repainting on scroll event - c++

QT 4.7
I have a QGraphicsView / QGraphicsScene. The scene has a custom QGraphicsItem the whole scene is not displayed at one time, so it has a viewport.
I'm overriding the paint() method of my QGraphicsItem as follows:
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
painter->setClipRect(option->exposedRect);
painter->save();
// Default identity matrix
QTransform transform;
// Apply some transform later
// The next line breaks stuff
painter->setTransform(transform);
// m_image is just a preloaded pixmap (i.e. watermark on all of these items).
painter->drawImage(QPoint(0,0), this->m_image);
painter->restore();
// Snip, do more drawing...
}
If I ever try to setTransform on the QPainter (i.e. if I'm trying to rotate the item), the view stops repainting the scene as a response to the horizontal or vertical scrollbars used to pan. The view also stops resizing the scene when I zoom in or zoom out.
The view will refresh if I resize the window or drag the window offscreen and then back onscreen. I've been looking over the QPainter Documentation as well as the examples and I can't quite figure out what I'm doing wrong. I'm assuming it's something to do with the coordinate system.

A guess:
The QPainter that comes to your paint method already has a transform on it that takes into account the viewport attributes (scale, rotation, etc.). When you call setTransform within your paint method you are blowing all that away. You probably want to perform a matrix operation on the existing transform rather than creating a new one.
Since you are calling setClipRect on your painter but then trying to paint under a completely different transform, you are painting outside your clip rect and nothing is happening.
It works when you resize or drag the window off screen because that forces a "redraw everything," so your clip rect includes your painting area in your alternate transform. Although I'm surprised it would appear in the correct location.

Related

Qt propagates paint events on backgroung items

I do have 2 QDeclarativeItems.
void BackgroundLayer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->drawImage( QRectF(dx1, dy1, dx2-dx1, dy2-dy1), shownImage, QRectF(sx1, sy1, sx2-sx1, sy2-sy1) );
}
void ForegroundLayer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QPen pen(Qt::red, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter->setPen(pen);
painter->drawLine(p1, p2);
}
QML
Rectangle {
width: 1920
height: 1080
BackgroundLayer{
id: background_layer
anchors.fill: parent
}
ForegroundLayer {
id: foreground_layer
anchors.fill: parent
}
}
Drawing on ForegroundLayer triggers BackgroundLayer paint event, causing it to repaint the whole image. As a result, drawing works slow. Is it possible to avoid this and repaint the image only when it's really needed?
Why do you expect any other kind of behavior? Qt doesn't keep the images of every declarative item for you, it'd be prohibitively expensive in terms of memory. You have the option of enabling this, though: perhaps you should. See the cacheMode documentation.
When any item needs to be updated, everything underneath and intersecting the update rectangle has to be repainted too, in the Z order from bottom to top. If there are any widgets underneath the QGraphicsView and if the view itself is translucent, then these widgets will have to be repainted as well.
If you have knowledge exactly of what area needs to be updated, you should use that knowledge: call QGraphicsItem::update(const QRectF &) to indicate the bounds of what needs updating. Otherwise, with a null rectangle, the update region spans the whole item.
Also ensure that the QGraphicsView's updateMode is set to MinimalViewportUpdate.
Under the covers, all QGraphicsItem instances and all QWidget instances all paint on an internal QImage that is then blitted or swapped into the underlying native window. They paint in back-to-front Z order, and the only widgets or items that are skipped are those that are completely contained under an opaque widget or item.
Short answer : Just use a QPixmap converted once from shownImage
void BackgroundLayer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->drawPixmap( QRectF(dx1, dy1, dx2-dx1, dy2-dy1), shownPixmap, QRectF(sx1, sy1, sx2-sx1, sy2-sy1) );
}
Explanation :
painting pixmaps on screen is much faster than images. The other choices may not work or are way too complex.
Paint events propagate from the top widgets to their children, recursively.
Basically Qt is given a rectangle to paint, and every widget inside this rectangle will receive a paint event.
I am sure what you want to achieve might be doable in some specific cases with widget attribute hacking, but I fail to see how you can do it here without having old paint artifacts from the ForegroundLayer.
Let say you have two lines AB et CD.
After the first call to paint you only want to see line AB
After the p1, p2 have been updated you only want to see line CD
In order to prevent you from seeing the line AB when painting CD, Qt has to clear the entire background in the rectangle being painted. If for some reason BackgroundLayer doesn't paint, the background image will disappear.

How to keep the same scene portion in view during resize?

I am using QGraphicsView to show something like function graphs. QGraphicsView keeps its transformation during resize, so the visible portion of the scene changes.
I need to keep the view exactly the same during resize - aspect ratio don't have to be kept since I allow independent scaling on X and Y axes.
This does not work:
void resizeEvent(QResizeEvent *event) override
{
QRectF rect = mapToScene(this->viewport()->rect()).boundingRect();
QGraphicsView::resizeEvent(event);
fitInView(rect);
}
Problem is in getting the proper view rectangle. This solution zooms out on resize.
Is there some simple way to achieve this?
I had the exact same problem. You will need to scale the view manually when its size changes, and restore the old center point. In python:
def resizeEvent(self, event):
w_ratio = self.rect().width() / event.oldSize().width()
h_ratio = self.rect().height() / event.oldSize().height()
self.scale(w_ratio, h_ratio)
self.centerOn(self.old_center)
You obviously need to set self.old_center earlier, e.g. in showEvent(). If the view can be translated by the user you will also need to update self.old_center in mouseReleaseEvent().
def showEvent(self, event):
self.old_center = self.mapToScene(self.rect().center())
def mouseReleaseEvent(self, event):
super(MyView, self).mouseReleaseEvent(event)
self.old_center = self.mapToScene(self.rect().center())
After making your custom class which inherits from QGraphicsView, you should reimplement resizeEvent( QResizeEvent *event ) in your custom QGraphicsView like:
void MyView::resizeEvent(QResizeEvent *event)
{
QRectF rect = this->scene()->itemsBoundingRect();
fitInView(rect);
QGraphicsView::resizeEvent(event);
}
This way the view will always display the whole scene. I.e. if the window size is changed and the graphicsView is resized, The scene gets scaled and you can see everything appropriately.
What about keeping the scene rectangle visualized by this view in the view with fitInView?
void resizeEvent(QResizeEvent *event) override
{
QGraphicsView::resizeEvent(event);
fitInView(this->sceneRect());
}
I realize this is quite old, but I came across the same challenge and found a solution that worked well.
I think the issue is that within the resize event, the viewport coords (mapped to the scene) are being fetched. In this case, the viewport has already changed (and thus the displayed scene), then fitInView is called with some slightly updated scene coordinates and so the display scene actually changes. This turns into a cascade like effect.
What we want to do is always pass the same scene rect to fitInView during the resize event. I was able to accomplish this by locally storing the scene coords when I first load the image and then anytime the displayed scene changes (pan/zoom/reset view, etc). Then when the resize event is called, we're always passing the same scene coordinates and don't getting caught in that change loop.

QGraphicsView and QGraphicsScene coordinate systems

in Qt 4.8 i have create a QGraphicsView and a DynamicRadarScene(derived from QGraphicsScene):
QGraphicsView* view = new QGraphicsView;
view->setMinimumSize(800, 600);
DynamicRadarScene* _scene = new DynamicRadarScene(mode, channel_types, this);
view->setScene(_scene);
What is the coordinate system of QGraphicsScene? (0,0) is from upper left corner?
How can i draw an item in the upper right corner of the scene (i have set it 800x600: view->setMinimumSize(800, 600);)?
If i resize the widget and so i resize the QGraphicsView, how can move the item i have drawn before to remain in the upper left corner?
Yes, the upper left corner is generally the coordinate of (0,0) in a graphics scene. What you need to consider is that a QGraphicsView is like a window looking into a world (the QGraphicsScene). You set the view to look at an area or the scene, which may be all or just part of the scene.
Once the scene and view are setup, you can then add QGraphicsItems / QGraphicsObjects or instances of classes derived from those by using functions such as QGraphicsScene::addItem. These items are positioned in the scene and draw themselves.
i (sic) resize the widget and so i resize the QGraphicsView
You can change the QGraphicsView position and dimensions, but then the items in the scene will remain in the same place within the scene. Usually you would set up the scene and view and then move / resize the graphics items / objects within the scene, often with the setPos function: QGraphicsItem::setPos(). This sets the position of the item, relative to its parent. If the item has no parent, it sets the position of the item in the scene.
QGraphicsScene has property sceneRect. If it is not set then it is auto adjusted depending on scene content. This can give a filling that origin of coordinating is in some strange place or even that it is mobile.
Set this property. See also this.

Drawing a scalable QIcon using QPainter

I want to design a new QIcon and want it to look like a fixed text with a rounded rectangle around it
.-----.
| Phy |
`-----ยด
The icon is supposed to scale without the "pixel-blocks" effect when painting on a QPainter that eventually has a scale transformation applied (for example when I paint into a widget that is part of a QGraphicsView with a scale applied on its scene).
Therefor, I have difficulties knowing how I should paint my QIcon. If I do it in the following way, I will paint a QPixmap that always has a fixed amount of pixels, thus introducing the pixel-blocks effect inevitably when the scale is large enough
void MyWidget::drawIcon(QPainter *painter, QPoint pos) {
QPixmap pixmap = icon.pixmap(QSize(22, 22),
isEnabled() ? QIcon::Normal
: QIcon::Disabled,
isChecked() ? QIcon::On
: QIcon::Off);
painter->drawPixmap(pos, pixmap);
}
What I am looking for is a way similar to how QFont with drawText works. Regardless on how large my scale is, when I draw fonts it always looks sharp and I cannot detect individual pixels.
I imagine that I could tell QPainter to paint my icon into a given pixel rectangle, and QPainter transforms the rectangle itself before letting my QIconEngine::paint render the item into a possibly larger rectangle or pixmap. But I see no way how I could do something like this.
Am I just being stupid and not seeing the obvious solution?
I was indeed completely dump. I can just use QIcon::paint and pass it the rectangle. It will correctly delegate the request to the icon engine.
I do this by creating my icons/images as SVG files, and using QSvgRenderer to paint them onto a QPainter. The required classes are in the SVG module.

Force update drawForeground on QGraphicsScene

I want to show a little image on my mouse position.
So i did that:
void AreaScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
MapData::pEnd.setX(event->scenePos().x());
MapData::pEnd.setY(event->scenePos().y());
this->update(0, 0, this->width(), this->height());
}
The pEnd is my point.
On the drawForeground i did that:
void AreaScene::drawForeground(QPainter *painter, const QRectF &rect){
qDebug() << "called";
if(MapData::tileIndex!=-1&&MapData::pEnd.x()!=-1){
painter->drawPixmap(MapData::pEnd.x(),MapData::pEnd.y(), *MapData::tileImage, (((int)(MapData::tileIndex%(MapData::tileImage->width()/MapData::tileSize.x())))*MapData::tileSize.y()),
(((int)(MapData::tileIndex/(MapData::tileImage->width()/MapData::tileSize.x())))*MapData::tileSize.x()),
MapData::tileSize.x(), MapData::tileSize.y());
}
}
Note:
The tile index is the position of the subrectangle on the tileImage (QPixelMap)
So i get the points, the image and the subrectange inside it.
It works if i keep pressing the right or the left mouse buttons it updates, but i want to update it when i move the mouse, i know the drawForeground is not even called at all.
Is there a way to call it, force to update so i can show the little tile on the screen?
The another option (i think) is change the mouse icon to the tile image, but i did a little research and didn't find a way to do that.
Thanks ppl
Call setMouseTracking(true); on the QGraphicsView that is displaying the scene. That will tell the view to generate mouse move events whenever the mouse is hovered over the view. Otherwise, the view will only generate mouse move events when you click and drag while holding down a mouse button.