How to scale a QGraphicsEllipseItem with the Zoom Level of QGraphicsView - c++

I have a QGraphicsView on a widget which im adding a number of items to.
some of these are polygons and some are ellipses (Both QGraphicsItems) when i zoom the Graphics view
void Test::on_verticalSlider_sliderMoved(int position)
{
ui->graphicsView->scale(1.1,1.1);
}
the ellipses just get bigger and bigger but i want them to shrink so that they basically stay the same shape no matter how far zoomed in i am, so basically i want the polygons to be zoomed in on but not the points i have plotted onto these polygons...if that makes sense
dont know it makes it easier but all of the ellipse points plotted at any one time are within the same QGraphicsItemGroup so there could be a flag i can set on the entire group to do this?
anyway i would be greatful for any help with this
thanks
EDIT ------ CODE SAMPLE
item is the QGraphicsEllipseItem, and m_group_point is a QGraphicsItemGroup
item->setParentItem(m_group_point.get());
then i add the group to a scene
m_scene2->addItem(m_group_point.get());
then add that scene to the view
ui->graphicsView->setScene(m_scene2.get());
they are .get because they are shared pointers

well, ignoreTransformation indeed a proper way to go.
QGraphicsItem::ItemIgnoresTransformations: The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the view is transformed. When set, the item's view geometry and scene geometry will be maintained separately. You must call deviceTransform() to map coordinates and detect collisions in the view. By default, this flag is disabled. This flag was introduced in Qt 4.3.
About stay in the middle, you should move ellipses within scene to proper position, so they have proper scene coordinates, and then you instruct to ignore transformation of the view, so they will ignore any zooming/rotation/etc as mentioned in documentation.

The Qt docs states this about the flag QGraphicsItem::ItemIgnoresTransformations: -
The item ignores inherited transformations (i.e., its position is
still anchored to its parent, but the parent or view rotation, zoom or
shear transformations are ignored).
Which is what you want. You've added your items to a QGraphcisItemGroup. For this, Qt help states: -
QGraphicsItemGroup ignores the ItemIgnoresTransformations flag on its
children (i.e., with respect to the geometry of the group item, the
children are treated as if they were transformable).
Reading into this, the QGraphicsItem uses the ItemIgnoreTransformations flag based on its parent, which in your case is the QGraphicsGroup, but this class ignores the flag on its children, which is likely to be causing the issue you're seeing.
Therefore, do not set the flag on the parent group, but on its children.

Try setting the ItemIgnoresTransformations flag on the objects that you want to keep constant-sized.

Related

correct way of updating a QGraphicsView from QGraphicsScene

I have implemented a QGraphicsScene to contain instances of QGraphicsItem of a few different classes implemented by me. What I still don't understand is how the QGraphicsView associated with this scene is updated.
When I call addItem(QGraphicsItem) the scene seems to redraw displaying the newly added items.
When I change properties of my QGraphicsItem instances (e.g. calling setVisible(bool)) it seems that the view is not updated automatically.
I currently call QGraphicsScene::update() without any arguments to draw the new state. this works in most cases, however in some of them it fails ( QGraphicsItem's paint() method is not called. I set a breakpoint there but whether it is reached or not depends on the zoom level in the QGraphicsView. I have to zoom out to a certain level for it to get called again).
It doesn't work when I pass the bounding box of the changed item (scene coordinates) to QGraphicsScene::update(QRect). I can't see objects changing.
Can anybody give me a hint what is the correct way of implementing this? How do I notify the view correctly of changes in certain areas, what does the behaviour depend on. I think there's something wrong with my assumptions about coordinate systems. Thanks a lot for your explanations.

Qt QGraphicView and QGraphicScene mouse position

I have project which display inner structure of ELF binary files. I use QGraphicsView and QGraphicsScene. So I can display blocks on graphics scene, but I need after mouse click on single block display content of this block. I am trying use function:
void MainWindow::mousePressEvent(QMouseEvent* event)
{
ui->textEdit->append(QString("x = %1, y = %2").arg(event->Pos().x()).arg(event->Pos().y()));
}
But position is tracking in whole window. Problem is, that width and height of window is dynamical and height of QGraphicsScene may be greater than window's. So I can't track single block.
I tryed some function like:
mapFromScene, mapToScene, mapFromGlobal, ... but I don't know, how theese functions works, but position what I get wasn't right.
It occurred to me that position should be calculated by scrollbar of QGraphicalView and size of window, but it's hard to calculated precisely and it's not correct solution.
Some idea, how get true position on graphic scene? Thanks for any help.
QGraphics, like other graphics frameworks, uses multiple coordinate systems and you need to consider which system you're working in when dealing with coordinates.
We can think of a QGraphicsSceneas a world, within which there are objects that are represented by QGraphicsItems ( or QGraphicsObject's. if you want them to use signals and slots).
To view an area of the world, we use one or more QGraphicsViews.
In order to map between the different coordinate systems, Qt provides the useful mapTo and mapFrom functions.
Starting at the top, with a QGraphicsView, we can convert a coordinate and use QGraphicsView::mapToScene to get the coordinate in the scene's coordinate system. From this, if an item resides at that coordinate, we can get the coordinate relative to the item, using QGraphicsItem::mapFromScene.
So, with the three levels (view, scene and item) think about which system the coordinate is in and where you want to be dealing with it.
how get true position on graphic scene?
Hopefully you should now be thinking that the coordinate you have been provided in the MainWindow is in the view's coordinate system and you can map to the scene's coordinate system.
QPointF scenePoint = mapToScene(event->Pos());
However, this is overcomplicating things and you'll find it easier to work directly with the scene and the items it contains, by overloading the item's own mouseMove/Press/Release events, which provide QGraphicsScene coordinates.
I solved this problem with make my object, which inherit QGraphicView (MyQGraphicView) and then I declared function mousePressedEvent for this object. This help me.

Why could a custom QGraphicsItem cause the scene or view to move unless boundingRect is empty?

My Qt 4.8.1 C++ class to draw a crosshair subclasses QGraphicsItem and implements its paint() and boundingRect() methods.
The paint() method consists of two drawLine() and one drawText() calls. The origin is centered.
boundingRect() also respects these coordinates (-,-,+,+) and also the half-pen-width in each direction.
When creating, moving (or positioning) and then adding the object to the (visible) scene, the view shifts a few pixels so that the scene contents move slightly to the bottom right.
If the boundingRect() returns an empty QRectF(), this shift does not happen.
additional elements do not cause further movement of the scene.
a prior update() of the scene does not change the behaviour
a larger boundingRect seems to increase movement
changing the view's transform does not change the behaviour
itemChange() is called numerous times but does not seem to provide a clue (in my eyes):
Crosshair::Crosshair being created, id= "1" at QPointF(63.6, 88.487) scenePos= QPointF(33.6, 58.487)
Crosshair::Crosshair position QPointF(63.6, 88.487)
Crosshair::itemChange ItemFlagsChange QVariant(uint, 1)
Crosshair::itemChange ItemFlagsHaveChanged QVariant(uint, 1)
Crosshair::itemChange ItemFlagsChange QVariant(uint, 33)
Crosshair::itemChange ItemFlagsHaveChanged QVariant(uint, 33)
Crosshair::itemChange ItemFlagsChange QVariant(uint, 2081)
Crosshair::itemChange ItemFlagsHaveChanged QVariant(uint, 2081)
[...]
Crosshair::itemChange ItemPositionChange QVariant(QPointF, QPointF(63.6, 88.487) )
Crosshair::itemChange ItemPositionHasChanged QVariant(QPointF, QPointF(63.6, 88.487) )
Crosshair::itemChange ItemSceneChange QVariant(QGraphicsScene*, )
Crosshair::itemChange ItemSceneHasChanged QVariant(QGraphicsScene*, )
[...]
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
Crosshair::itemChange ItemVisibleChange QVariant(bool, true)
Crosshair::itemChange ItemVisibleHasChanged QVariant(bool, true)
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
...
I might be able to create a minimal example tomorrow, but maybe someone is able to guess my mistake even from this general description of the problem.
Alternatively, hints towards further debugging are of course very welcome!
Edit
Thanks Riateche for the advice.
Some further debug outputs reveal that the sceneRect() indeed is changed, it expands exactly the size of the Crosshair's bounding rect, e.g. from QRectF(0,0 1680x636) to QRectF(-11.5,-11.5 1691.5x647.5). The scene's itemsBoundingRect stays the same.
Now I failed to mention previously that the view has been scaled/transformed with fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); and noticed that the shift indeed does not occur when the scene is viewed at 100% (e.g. resetTransform()).
But I still do not understand how/why the sceneRect() changes when I am adding an element within the boundingRect(). It must have to do with my custom implementation, because adding an ellipse does not cause a shift of the scene/view.
Ok, here we go: I also failed to mention that I use setFlag(QGraphicsItem::ItemIgnoresTransformations); so that the crosshair stays the same size regardless of scale. This apparently causes the boundingrect to "stay" at the scene's origin when added, and ignores the setPos().
To be continued ...
A scene has a bounding rectangle which is by default the smallest rect containing all items. If you add something to a scene outside of this rectangle, the bounding rect will be expanded.
A view respects the scene's rect. If the scene's rect is smaller than the view's viewport size, the view's scrollbars get hidden and the scene contents appear in the center of the viewport. So, if you add an item, scene's bounding rect changes and all contents get shifted. If the viewport is smaller than bounding rect then scrollbars appear.
If you want to avoid this behavior, you can use QGraphicsView::setSceneRect or QGraphicsScene::setSceneRect to fix the rect position. Note that everything outside of the set rectangle will be unavailable and invisible in the view.
Also consider switching to QGraphicsPathItem (and QGraphicsScene::addPath). It can be used to draw crosshairs.
I was trying whether the problem could be worked around with
QRectF save = scene->sceneRect();
scene->addItem( myGraphicsItem );
scene->setSceneRect( save );
as Riateche suggests on a different note, and it works.
However, I unfortunately still don't fully understand the cause of this.
Here's what the documentation has to say about this flag:
QGraphicsItem::ItemIgnoresTransformations
0x20
The item ignores
inherited transformations (i.e., its position is still anchored to its
parent, but the parent or view rotation, zoom or shear transformations
are ignored). This flag is useful for keeping text label items
horizontal and unscaled, so they will still be readable if the view is
transformed. When set, the item's view geometry and scene geometry
will be maintained separately. You must call deviceTransform() to map
coordinates and detect collisions in the view. By default, this flag
is disabled. This flag was introduced in Qt 4.3. Note: With this flag
set you can still scale the item itself, and that scale transformation
will influence the item's children.
Here is some possibly related information:
https://bugreports.qt-project.org/browse/QTBUG-19039
Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on

Overlay items in qtgraphicsscene

I would like to draw some dynamical overlay elements in a scene.
Usually I would go for the paintEvent in the view or foreground painting for doing overlays, but this time I need to interact with these elements: they are like every other item in the scene... Mostly, this choice comes from the fact that these items are somewhat complex, and they share the rendering with other QGraphicsItem(s).
The problem is that, being overlayed, they shall be treated a bit differently than any other element in the scene: rendered in view coord space, not in scene coord space, and ignore changes in the view rect, dragging, dropping, rubber band selection, and such.
The question is somewhat general: how could I use the scene for a thing like this? Is there a better option for doing overlays e.g. using multiple scenes and rendering them all, in some magic way, in a single view widget?

Question regarding image tiling in a QGraphicsView

This is related to one of my other questions.
If I am tiling a large image by creating a separate QGraphicsItem (with the raster data as its pixmap), how do I keep track of the QGraphicsItem's position within the scene? Obviously for raster data, it is important to keep all the tiles "touching" to make a continuous image and they also have to be in the right place so the image doesnt look jumbled.
Does each tile have to have positioning methods that move it in relation to it's neighbors on the top/left/bottom/right? This seems kind of clunky. Is there a better way to make them all move together?
In other words, if I pan the scene with scroll bars, or pick up the image and drag/move it around in the scene, I want all the tiles to also move and stay in the right position relative to each other.
What is the best approach for controlling the layout, which tiles need to be rendered (i.e. only the visible ones), and populating the data only once it is needed? Also, once a tile has been rendered, is the data from it ever dropped, and repopulated from the image file, say if it stays out of view for a while, then comes back later if someone pans to that section?
There are (more than) 2 ways of doing this:
Use QGraphicsItemGroup which
handles grouping of your tile items
for you. It moves, selects, updates
it's group members as if they are
one. I've never used it but from the
doc, it seems to work with typical
applications.
Paint the tiles yourself in the
paint() of one custom item. This
gives you total control on how to
place and draw the tiles while the
item truly acts as one item since it
is, well, one item. This is what I
do.