Ignore mouse events over transparent parts of an svg image in qgraphicsview? - c++

I am working on a graphics view (using C++ and Qt) which contains quite a few svg images. I intercept clicks on them, but i'd like not to receive events (or to be able to ignore them) when mouse is over transparent parts of svg items.
Is it possible ?
Should svg files be specifically designed for such use ?
Is there some hidden Qt option i have not (yet) heard of ?

There's a CSS property which can be applied to SVG elements, pointer-events, though the default for this is visiblePainted:
The given element can be the target element for pointer events when the ‘visibility’ property is set to visible and when the pointer is over a "painted" area. The pointer is over a painted area if it is over the interior (i.e., fill) of the element and the ‘fill’ property has an actual value other than none or it is over the perimeter (i.e., stroke) of the element and the ‘stroke’ property is set to a value other than none.
Which would indicate that Qt graphics view doesn't support it.

Having no other choice but to find out the hard way the answer to my question, here is what i did :
looked for mousePressEvent definition in QGraphicsSvgItem.cpp. Found none.
looked for mousePressEvent definition in QGraphicsItem.cpp (ancestor of QGraphicsSvgItem). The method exists but no relevant action could be found there.
looked for mousePressEvent calls in QGraphicsItem.cpp. Found myself reading the code of QGraphicsItem::sceneEvent(), dispatcher of mouse events for a Qt graphics scene. There does not seem to be any sort of differentiating different zones of graphic items.
Hence the sad answer : Qt does not permit such a behaviour.

To complete other answers:
When re-implementing events, it is important to call the base-class event for default cases, if not, the event transparency over non-painted parts is lost.
E.g.
virtual void mouseReleaseEvent( QGraphicsSceneMouseEvent *e) override
{
if (/* any condition*/)
{
// Do some specific behaviour
}
else QGraphicsItem::mouseReleaseEvent(e);
}

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.

Prevent QGraphicsItem::itemAt() On a "Background Item"

I am trying to set up a GUI using plugins. In my current phase I need the user to click and specify locations of points.
The plugin architecture I have setup requires the plugin to return QGraphicsItem that the plugin wishes to use. In the main program (which future plugin writers won't have access to), I set a background image to guide clicks. The plugins can sneakily access the scene() using an itemChange() and installing an eventFilter() on the scene being set. This allows for the plugins to have access to scene clicks and process whatever information it needs. However, the background picture is being returned by itemAt() calls.
I wish to prevent this background Pixmap from returning with an itemAt() for all future plugins. Is this possible?
Tried:
setEnabled(false); //No success
This doesn't answer your question directly, but it should solve your problem: I suggest reimplementing QGraphicsScene::drawBackground() and drawing your pixmap there. That's what the method is for.
A similar approach would be to put the pixmap in a resources file, then use style sheets to set it as the background of either the QGraphicsView or its viewport widget (I think these have slightly different effects).
To make an item invisible to itemAt() (or any of the other collision detection methods) you can make the boundingRect() method return a QRectF with zero width and height.
This though has the side effect of making any of the partial viewport update modes of QGraphicsView malfunction, because QGV doesn't have any idea where the item is going to paint itself. This can be remedied by having QGV do full updates on every repaint (setViewportUpdateMode(QGraphicsView::FullViewportUpdate)). Depending on your scene this might be an acceptable compromise. If you're using the OpenGL graphics backend the viewport updates will always be full updates anyway.
After so many years, here is the answer.
Make your own subclass of QGraphicsItem and override the shape() method
QPainterPath shape() const override
{
return {};
}
Ref to docs: https://doc.qt.io/qt-6/qgraphicsitem.html#shape
The shape is used for many things, including collision detection, hit tests, and for the QGraphicsScene::items() functions.
So this will effectively make the item invisible for itemAt() and others.
And you still have the boundingRect() which is used to filter what items should be painted.

How to make only part of a button react on user actions (click/hower/etc) based on graphical shape in Qt?

The question is a bit difficult to explain, but I will try.
I have an image of a button:
And I also have some animations of it, such as (gif with a single loop, reload it to view the animation!):
Also, I have some kind of a pattern:
So, now the question: how to make the button react (for example on clicks/hower/etc) only when the mouse is INSIDE the black part of the pattern, and disallow such reactions when the mouse is out of the scope of this pattern? Without modifiyng the graphical files of course. And of course the pattern itself should be invisible.
I want to make that with help of C++ and Qt.
Just please indicate me at least the path, which functions/classes of Qt should I look for! Because now I am completely lost.
One way I can think of doing this, is installing an event filter on the particular button, by calling the void QObject::installEventFilter ( QObject * filterObj ) method of the button. Documentation can be found here.
What it boils down to, is creating a QObject specifically for the purpose of filtering mouse events, based on a binary image. All you have to do is override the bool QObject::eventFilter ( QObject * watched, QEvent * event ) method of this new object, and then install that object as the filter for the button. As part of the constructor for this filter class, pass it a reference to the binary image. This binary image reference will sit as a member variable in the filter object, and we can use it to decide which pixels of the button respond to mouse events.
In the eventFilter method:
Check the event type, if it is not a mouse event, don't handle the event yourself, pass it on to the parent class
If it is a mouse event, then you're in business.
Get the x and y position of the mouse event using the corresponding methods x() and y()
Look into the binary image stored as a member variable. If the binary image is true at that point, do one thing, otherwise do something else
This filter class should be generic enough that you can create multiple instances of it with different binary images for different buttons/widgets.
Hope this helps.
There is a simple way to do this:
http://developer.qt.nokia.com/doc/qt-4.8/qwidget.html#setMask
Masked widgets receive mouse events only on their visible portions.
This will also mask the drawing, however, so you may want to try an alternate route:
Subclassing QPushButton,
rewrite the mousePressEvent to clip against a mask,
rewrite the paintEvent.
I list rewriting the paint event last because you may be able to get the desired effect without it. However, if you wish to have animation, it may be necessary.
I would also suggest using a polygon mask (via QPolygon) rather than a bit mask and containsPoint(...) to determine if a mouse event occurs inside the masked area. In my experience it's a bit less painful, and easy to debug: You can add the code to draw the polygon over the graphic, removing that code when you're satisfied it's no longer needed.

How can we create custom slider?

I want to create a slider which contain a different slider handle and i want to paint it according to slider handle position in the slider.
You could use QProxyStyle to ovrride drawComplexControl method - you will have to draw entire control on your own, as there is no separate flags in QStyle::ControlElement for parts of QSlider.
maybe you should look at this: http://doc.qt.io/qt-4.8/stylesheet-examples.html#customizing-qslider
If I understand you correctly, you want a slider that changes not only its position but also its appearance as you slide, right? For example, a mix of QDial and QSlider, ie. a slider with a turning knob.
If so, you will need to subclass either QSlider or QAbstractSlider (or QDial) and do the painting in your own paintEvent(). Note, however, that you will loose all style-awareness unless you care about that yourself (and that is an interesting topic in itself, see http://doc.qt.io/qt-4.8/style-reference.html for more info).
The Qt demos and examples, or the QSlider/QDial source code itself may serve as examples on how to overload a paintEvent().

How to do precise mouse event handling in QGraphicsScene when ItemClipsChildrenToShape is enabled?

I have a QGraphicsItem that clips its child items (I enabled its ItemClipsChildrenToShape flag). I noticed that clipping makes assigning the mouse event to the children items imprecise: instead of the precise shape of the items their bounding rectangles are used for detecting which item is located at the specific position so the children receive mouse events in their whole bounding rectangle. When clipping is not enabled it works fine as expected.
Setting the bounding region granularity of the child items to 1.0 didn't help. I'm using qt 4.5.0.
The program that I tested this issue with is available at http://pastebin.com/m3d0cfb53
I could not find anything about this behaviour in the qt docs. I'd like to know whether it's a bug in qt and whether there's a workaround for it.
I know this is an old question, but I ran into the same problem.
The documentation for QGraphicsItems says:
The shape() function is used for many things, including collision detection, hit tests, and for the QGraphicsScene::items() functions.
The default implementation calls boundingRect() to return a simple rectangular shape, but subclasses can reimplement this function to return a more accurate shape for non-rectangular items.
So overriding the shape() function with a QPainterPath fixed the problem for me.