Setting exact view area (rect) of QGraphicsScene in QGraphicsView - a better fitInView()? - c++

I am failing in trying to restore a particular view (visible area) in a subclassed QGraphicsView on a subclassed QGraphicsScene.
The visible rectangle of the scene viewed should be
mapToScene( rect() ).boundingRect()
which is
QRectF(27.8261,26.9565 673.043x507.826)
I obtain these values in the destructor of QGraphicsView, they seem valid.
After restoring the window geometry, I am using said values in the first QGraphicsView::resizeEvent (the first event has an invalid old size QSize(-1, -1)) in the attempt to restore the area shown in view using
fitInView( QRectF(....) , Qt::IgnoreAspectRatio);
This triggers a number of scrollContentsBy events before showing the view, another resize and scroll event after, then the mainwindow showEvent fires, causing more resize scroll and resize events.
I am sure this sequence is neccessary for the GUI layout to build, but I am now confused as to how I could run fitInView once everything is set up.
Using a QTimer::singleShot to run the function well after the GUI is shown, I get approximately the desired result, but the matrix and viewed area differ:
ui->graphicsView->matrix();
ui->graphicsView->mapToScene( ui->graphicsView->rect()).boundingRect();
Before:
"[m11=1 m12=0 m21=0 m22=1 dx=0 dy=0] (x=27 y=27 w=774 h=508)"
Restored:
"[m11=0.954724 m12=0 m21=0 m22=0.954724 dx=0 dy=0] (x=17.8062 y=24.0907 w=810.705 h=532.091)"
So fitInView() does not serve my purpose very well - is there another, reliable way?
I'm using Qt 4.8.1 with MSVC2010
Another approach is to restore transform and window scroll position like so:
settings->setValue("view", mapToScene( rect() ).boundingRect() );
settings->setValue("transform", transform() );
settings->sync();
and restore them
QTransform trans = settings->value("transform", QTransform()).value<QTransform>();
QRectF view = settings->value( "view", QRectF() ).value<QRectF>();
setTransform( trans );
centerOn( view.center() );
But this method, too, has an offset.
Before
"[m11=4.96282 m12=0 m21=0 m22=4.96282 dx=0 dy=0] (x=29.6203 y=29.4188 w=155.96 h=104.981)"
After
"[m11=4.96282 m12=0 m21=0 m22=4.96282 dx=0 dy=0] (x=54.8076 y=53.8001 w=155.96 h=104.981)"
An offset also is present when scrollbars are hidden.
Moving the code to showEvent() does not affect result.

What is finally working: in showEvent, restore the transform, restore scroll-bar values:
setTransform( trans );
verticalScrollBar()->setValue( v );
horizontalScrollBar()->setValue( h );
Scroll bar visibility does not affect the view position and size, they are just "overlays".

Related

Qt: Custom QGraphicsItem not showing when boundingRect() center is out of view

I'm making a Diagram (Fluxogram) program and for days I'm stuck with this issue:
I have a custom QGraphicsScene that expands horizontally whenever I place an item to it's rightmost area. The problem is that my custom arrows (they inherit QGraphicsPathItem) disappear from the scene whenever it's boundingRect() center is scrolled off the view. Everytime the scene expands, both it's sceneRect() and the view's sceneRect() are updated as well.
I've:
set ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate)
the item flags QGraphicsItem::ItemIgnoresTransformations and QGraphicsItem::ItemSendsGeometryChanges, setActive(true) on the item as well, and everytime I add an arrow to the scene i call the update(sceneRect()) method. Still, everytime I scroll the view, as soon as the arrow's boundingRect() center moves away from the view, all the arrow disappears. If I scroll back and the boundingRect() center enters the view, all the arrow appears again.
Can someone give me a tip of what I might be missing? I've been using Qt's example project diagramscene as reference, so a lot of my code is similar (the "press item toolButton -> click on the scene" relation to insert items, the way they place the arrows to connect the objects,...).
In the meanwhile I'll try to make a minimal running example that can show what my issue is.
Your Arrow object inherits from QGraphicsPathItem, which I expect also implements the QGraphicsItem::shape function.
Override the shape function in your Arrow class, to return the shape of the item. This, along with the boundingRect is used to collision detection and detection of an item on-screen.
In addition, before changing the shape of an item by changing its boundingRect, you need to call prepareGeometryChange.
As the docs state: -
Prepares the item for a geometry change. Call this function before changing the bounding rect of an item to keep QGraphicsScene's index up to date.
So, in the Arrow class, store a QRectF called m_boundingRect and in the constructor: -
prepareGeometryChange();
m_boundingRect = QRectF(-x, -y, x*2, y*2);
Then return m_boundingRect in the boundingRect() function.
If this is still an issue, I expect it's something in QGraphicsPainterPath that's causing the problem, in which case, you can simply inherit from QGraphicsItem and store a QPainterPath with which you draw in the item's paint function and also return the painter path in shape().
You are making your life too complicated. Do not subclass QGraphicsPathItem just use it and update its path value every time position of anchors (from to) changes.

How does one retrieve selected area from QGraphicsView?

I need my QGraphicsView to react on user selection - that is, change display when user selects area inside it. How can i do that?
As far as i can tell, selection in Qt Graphics framework normaly works through selection of items. I haven't found any methods/properties that touch on selected area, save for QGraphicsVliew::rubberBandSelectionMode, which does not help.
After some going through documentation, i found different solution.
In QGraphicsView there is a rubberbandChanged signal, that contained just the information i wanted to use. So, i handled it in a slot, resulting in the handler of following form:
void
MyImplementation::rubberBandChangedHandler(QRect rubberBandRect, QPointF fromScenePoint, QPointF toScenePoint)
{
// in default mode, ignore this
if(m_mode != MODE_RUBBERBANDZOOM)
return;
if(rubberBandRect.isNull())
{
// selection has ended
// zoom onto it!
auto sceneRect = mapToScene(m_prevRubberband).boundingRect();
float w = (float)width() / (float)sceneRect.width();
float h = (float)height() / (float)sceneRect.height();
setImageScale(qMin(w, h) * 100);
// ensure it is visible
ensureVisible(sceneRect, 0, 0);
positionText();
}
m_prevRubberband = rubberBandRect;
}
To clarify: my implementation zooms on selected area. To that effect class contains QRect called m_prevRubberband. When user stop selection with rubberband, parameter rubberBandRect is null, and saved value of rectangle can be used.
On related note, to process mouse events without interfering with rubber band handling, m_prevRubberband can be used as a flag (by checking it on being null). However, if mouseReleaseEvent is handled, check must be performed before calling default event handler, because it will set m_prevRubberband to null rectangle.
You can use qgraphicsscenemouseevent.
On MousePress save the current position and on MouseRelease you can compute a bounding rect using the current position and the MousePress position.
This gives you the selected area.
If you need custom shapes you could track the mouse movement (MouseMove) to get the shape.
An Example that uses qgraphicsscenemouseevent can be found here.

Scaling graphics in Qt

I am writing a scheduling-type application using Qt/C++ and want to display weekly schedules in one part of the window, and have this rendering scale as the window size increases. The renders will be composed of rectangles with text in them, and as the display area increases the rectangles should scale nicely while the text should remain the same size.
I have experimented with QGraphicsScene and QGraphicsView and I can make rectangles and text scale; however, the rectangle scaling seems ugly (stretches the outline) and I don't want text to scale at all.
I suspect that I might want to resize the scene to the display area and re-draw the rectangles and text; however, I am not sure how to do this - QGraphicsScene doesn't seem to respond to resizeEvent. Is this even the right approach?
I'm not sure what the ugly rectangle scaling is about (a screenshot might help me understand better what you meant there), but if you don't want the text parts to scale, you can accomplish that by calling setFlag(ItemIgnoresTransformations, true) on your QTextGraphicItem objects.
As far as automatically rescaling the rectangles in response to a window resize, you might take a look at the documentation of the QGraphicsView::fitInView() method:
Scales the view matrix and scrolls the scroll bars to ensure that the
scene rectangle rect fits inside the viewport [...] It's common to
call fitInView() from inside a reimplementation of resizeEvent(), to
ensure that the whole scene, or parts of the scene, scales
automatically to fit the new size of the viewport as the view is
resized. Note though, that calling fitInView() from inside
resizeEvent() can lead to unwanted resize recursion, if the new
transformation toggles the automatic state of the scrollbars. You can
toggle the scrollbar policies to always on or always off to prevent
this (see horizontalScrollBarPolicy() and verticalScrollBarPolicy()).

QT mouse event handling problem

Greetings all,
As seen in the picture
I have an extended QWidget object (which draws the cell images and some countour data) inside a QScrollBar.
User can zoom in/out the Image (QWidget size is changed according to the zoomed size of the QImage ) using mouse wheel.
I process the events (mouseMoveEvent(),wheelEvent()..etc) by implementing the listener methods in QWidget.
My problem is ,I can only perform zooming (and other events) when the mouse pointer is over the QWidget.
If the mouse point is over the QScrollBar (the gray area in the image) ,those events are consumed by the QScroolBar.
Any tips,
[Edit] Sorry I was refering to QScrollArea , not QScrollBar.
thanks,
umanga
I'm uncertain if you want the scroll wheel to only ever be used for zooming the image or if you want the scroll wheel to control zooming when the image is smaller than the scroll area viewport and then use the scroll wheel to do scrolling when the image is larger than the scroll area viewport. In either case, you should be able to customize how the wheel is handled with the following:
Since I've not actually tried this one, I'm not sure if it will work. The hope is that if you install an event filter and set ignore on the event, the event will still be propagated back to your image widget. This will allow you to leave your current mouse handling in the image widget intact.
bool YourImageWidget::eventFilter(QObject *obj, QEvent *event)
{
if((obj == scrollAreaPointer) && (event->type() == QEvent::Wheel))
{
if(!scrollAreaShouldHandleWheel)
{
event->ignore();
}
}
return false; // always pass the event back to the scroll area
}
The scrollAreaShouldHandleWheel flag is a boolean you would set from your image widget based on whether or not you want the scroll area to handle the wheel events.
Somewhere in your code you would install your image widget as an event filter for the scrollarea.
scrollArea->installEventFilter(imageWidget);
If this doesn't work, you can always use this filter to make sure your widget gets the event and handle it directly and then return true so that the scroll area won't be able to receive the event.
I recommend you use QGraphicsScene and QGraphicsView. The graphics framework already provides a lot of useful features (including viewport transformation). And the QGraphicsView is a scroll area.
have you done grabMouse() for Qwidget i.e for the one which you display image?

How to set an initial size of a QScrollArea?

I know that this is a very specific C++ and Qt related question, but maybe someone can help me, anyway ...
See the code below: I want to display an image within a scroll area. The view port of the scroll area shall have a defined initial size. That means, if the image's size is bigger than the initial size of the view port, scroll bars will be visible, otherwise not.
// create label for displaying an image
QImage image( ":/test.png" );
QLabel *label = new QLabel( this );
label->setPixmap( image.toPixmap() );
// put label into scroll area
QScollArea *area = new QScrollArea( this );
area->setWidget( label );
// set the initial size of the view port
// NOTE: This is what I'd like to do, but this method does not exist :(
area->setViewPortSize( QSize( 300, 300 ) );
It shall be possible to resize the whole application so that the view port will get another size than the initial one.
Unfortunatelly I was not able to find out, how to set the size of the view port. Qt's layout mechanism seems to set a default size for the view port, but up to now I was not able to change it. Setting a new size with
area->setMinimumSize( QSize( 300, 300 ) );
will actually set the demanded size, but then the scroll area looses the ability to get resized to a size smaller than 300x300.
Any ideas?
I think that you are looking at the problem the wrong way. The QScrollArea is just a widget that you put in a frame or QMainWindow. The size of the widget is controlled by the layout of the widget that contains it.
Take a look at this example from Trolltech: Image Viewer Example
You can try:
class MyScrollArea : public QScrollArea
{
virtual QSize sizeHint() const { return QSize( 300, 300 ); }
};
// create label for displaying an image
QImage image( ":/test.png" );
Label *label = new QLabel;
label->setPixmap( image.toPixmap() );
// put label into scroll area
QScollArea *area = new MyScrollArea( this );
area->setWidget( label );
However layout and Qt is amazingly Voodoo. It is IMO its least functional part.
if that doesn't work, try calling QWidget::resize() on various widgets.
Is the scroll area the top level widget? If so, simply call
area->resize(300,300);
If it's inside a hierarchy you need to resize the toplevel appropriately (complex), or set the minimumSize of the area. You could also try to experiment with the LayoutPolicy - assuming the sizeHint is QSize(300,300) you can give it the appropriate size policy according to what's defined in https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum
I don't think you can do exactly that very easily, which is (if I'm reading correctly), size the widget so that the internal area is 300x300. You might be able to fudge it, however, since a scroll area is a type of frame, which inherits from QWidget. This means you could just call area->resize( 300 + fudge, 300 + fudge ), where your fudge values account for the extra bit taken up by the frame's drawing.
I'm not sure this would work in a dynamically resizable dialog, however. I haven't ever done anything quite like this.
If you're trying to display an image inside a scroll area, your best bet isn't going with a label.
You should try using a QGraphicsView/QGraphicsScene/QGraphicPixmapItem (instead of the Scroll Area and label). The performance is far better when displaying images. The scroll area and label will re-draw the image very poorly as you move around using the scroll bars.
For example, you have a ".ui" file with a QGraphicsView on the gui called "qgvImageView" and a QImage called "image"...
QGraphicsScene *scene = new QGraphicsScene(qgvImageView);
QPixmap pixTmp(QPixmap::fromImage(image));
QGraphicsPixmapItem * ppixItem = scene->addPixmap( pixTmp );
ppixItem->setPos(0,0);
Check out the QT Documentation. BTW: This was introduced in Qt 4.2
I'm not sure if this will specifically fix the problem, but there is a chance that the QGraphicsView will react better to what you're trying to do.
How about using
area->setGeometry(int x, int y, int w, int h);