Events and signals in Qt's QGraphicsItem: How is this *supposed* to work? - qt

Like other primitives in Qt, QGraphicsItems can handle mouse events and the like. Sweet! Now say I need for an event on one QGraphicsItem to be propagated to some other QGraphicsItems in the same scene. I can think of two ways that one might approach this:
(A) The Naive Approach - Signaling
Concept : Connect sibling QGraphicsItems together with signals. Event handlers on QGraphicsItem call emit()s that evoke coordinated responses on other QGraphicItems. This follows the general design pattern established throughout the Qt framework.
Implementation : For reasons that I do not fully grasp, QGraphicsItems cannot emit() signals. It has been suggested that derived classes that also inherit from QGraphicsObject may be able to work around this. It seems to me, though, that the exclusion of emit() on QGraphicsItems was probably an intentional design decision on the part of the Qt devs and, therefore, multiple inheritance is probably not the Right Solution.
(B) Container-Level Event Handling
Concept : QGraphicsItems always exist in the context of a container of type QGraphicsScene. Events that in (A) were handled at the level of the QGraphicsItem are instead handled by an object inheriting from QGraphicsScene. This object also implements the logic for coordinating responses between sibling QGraphicsItems.
Implementation : QGraphicsScene definitely has the ability to handle events that would otherwise make their way down to QGraphicsItems. QGraphicsScene also provides the itemsAt() method for determining which of the things in it are affected by positional events, like mouse clicks. Still, building up considerable logic within a container class for coordinated action among containees feels like a failure to encapsulate properly. Bad practice? Maybe, but this seems to be the way it's done in at least one official example.
Questions
What's the Right Solution here? If not A or B, then is it something else that I haven't thought of?
Why did the Qt devs allow QGraphicsItems to receive events but not send signals? This seems like a major exception to the design pattern used throughout the framework.
An extension of this problem is communication between QGraphicsItems and higher-order container classes, like the main application. How is that meant to be addressed?

Signaling is not part of QGraphicItem because they do not inherit from QObjects. This was a design decision for performance reasons, to allow very large and fast scenes. If you decide you really need special cases for signals, QGraphicsWidget was created to fill this gap. It does inherit from QObject and lets you have a mixture of QWidget and QGraphicsItem functionality. Though it is recommended that you avoid this if your scenes are even moderately sizable.
Another option which might be relevant to your situation is to make use of the sceneEventFilter method. You can set one item to receive events for another and decide if they should be propagated or not:
http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qgraphicsitem.html#sceneEventFilter
One item can be set as the filter for multiple objects. And it can identify each individual item and event to respond to.
Generally though you should make use of the scene for coordination across its objects. That is already the pattern being used for events (the scene coordinating delivery of all events to the items).
Also, it seems that your option A is not possible because QGraphicsItem does not even have an emit method. You would need to compose a QObject instance inside it as a member and use that to emit signals. Something along the lines of myItem.qobject.emit(). Otherwise, you would have to inherit your own completely custom one from QGraphicsObject
Update 1: Addressing your main comment update
Your specific situation is a rectangle with "hot corners". I would see this being a custom QGraphicsItem. You would probably subclass QGraphicsRectItem, and then compose the child hot corner items inside as children items (setParentItem()). Now your rectangle item knows about its children and can act on them directly. You could set the rectangle item to be the sceneEventFilter for the children and handle their events directly. No need to go back up to the scene. Let all this logic live in the class.
Update 2: Addressing your added question #3
Propagating communications up beyond the scene to QWidget's has a couple approaches I can think of:
This is a situation where you can consider if you want to use a QGraphicsObject subclass as your root item, and then compose the rest of your objects as children (the rect, then the hot corners as children of the rect). This would allow the object to emit signals. For clarity they would probably still be connected to the scene, and then the higher order container of the scene would connect to the scene. You would have to choose this approach on a case by case, depending on the complexity of your scene and whether the QGraphicsObject has any performance impact on it. You should probably avoid this if you will have a great number of these instances.
You could define a callback for your rect class, for which the scene can set. Either something like: graphicsRect.resizedCallback as an attribute, or a setter graphicsRect.setResizedCallback(cbk). In your rect class, you would just call that when appropriate. If the callback it set, it can be used to call something on your scene directly. The rect class still has no knowledge of that logic. It just calls a callback.
Those are just some suggestions. I'm sure there are other ways.

I suggest B unless you have relatively few QGraphicsItems. I believe QGraphicsItems are not QObjects because there is a certain amount of overhead associated with QObjects. The QGraphicsView framework was designed to allow fast insertion and deletion of many (e.g., thousands) QGraphicsItems into a scene, so a lighter-weight approach was preferred.
I would look to the concept of parenting in QGraphicsItems. QGraphicsItems can have parents and children, and this has several effects similar to parenting amongst QObjects. For example, if you move a parent QGraphicsItem, its children will move with it, and if you delete a parent, its children will be deleted. You can access a QGraphicsItem's parent using QGraphicsItem::parentItem(), and children using QGraphicsItem::childItems(). So, you can easily access sibling items like this:
QList<QGraphicsItem *> mySiblings = this->parentItem()->childItems();
Note that mySiblings includes this.

Related

Qt: DefaultContextMenu vs CustomContextMenu

What's the difference between setting ContextMenuPolicy to DefaultContextMenu or CustomContextMenu? From what I can tell, they're the same things? Either way, you get to create a custom context menu if needed.
They are certainly not the same.
While they both could create a custom menu, their behavior is quite different. Consider the QWidget documentation:
The default value of this property is Qt::DefaultContextMenu, which means the contextMenuEvent() handler is called.
This means that you need to specifically override contextMenuEvent() in order to eventually show the context menu. This usually means that subclassing is required and only the widget will handle that event (which could also be ignored) and possibly its menu.
Note that overriding contextMenuEvent() could be done also for special cases of the event management, from the simplest one (accepting it, to avoid propagation to the parent) to more complex situations that need to set up some aspects before letting the default implementation to handle the event as usual.
With Qt::CustomContextMenu, the signal customContextMenuRequested() is emitted.
This means that any object can connect to that signal (and potentially more than once), even a completely unrelated one, and eventually do something from there. This also means that there is absolutely no access to the event, which will always be considered as accepted.

Pyqt: How do you get pointers of a child from adding the parent to graphics scene?

using QGraphicsScene.addWidget(my_parent)
Returns a pointer of the proxy of the my_parent widget in the Qgraphicsscene.
Does the Qgraphicsscene also automatically provide proxies for all the children of my_parent?
Could you call from a child of my_parent to find the corresponding proxy?
I am trying to understand if there is an easy way to keep track of proxies of children of a widget without explicitly adding the children into the scene. I.E. how to retrieve the pointers.
I am aware of the methods to find items of the graphicsscene but am not sure if all children are added as items and would prefer to store my own pointers to the proxy objects.

Best practice to handle multiple documents in QMdiArea?

I need to design and develop a kind of graphics piece of software, which can edit customized files containing graphical elements, etc.
I expect the piece of software to contain many documents thanks to the QMdiArea which is actually my central widget inside of my QMainWindow.
For each document, I will need both a QGraphicsView and a QGraphicsScene as well, since they work together.
Now, my question is, should I inherit QGraphicsView with a protected/private member to its own QGraphicsScene, or should I create a class which inherits QWidget and handles instances of QGraphicsView / QGraphicsScene ?
Or is there any solution left that I didn't think about?
First off, I don't think you need a QWidget to manage the QGraphicsScene and QGraphicsView. With that in mind, the "best practice" is typically to avoid subclassing if possible. Eventually you might have to subclass QGraphicsView (if you want to change its default functionality), but nothing in your question implies that you need to right now. Also note that there is a function QGraphicsView::scene() that returns the view's current scene, so there is no need to make the scene a member (it already is).
If you ever need to access a particular view or scene, you can do something like this:
MainWindow::onActionClearActiveWindow() // just an example
{
QMdiArea *myMdiArea = static_cast<QMdiArea*>(centralWidget());
QGraphicsView *activeView = static_cast<QGraphicsView*>(myMdiArea->widget());
QGraphicsScene *activeScene = activeView->scene();
activeScene->clear();
}
See also QMdiArea::subWindowList() which returns a list of all the sub windows.

How do I propagate a mousePressEvent() to a group of QGraphicsItems?

I'm writing a program that uses the Qt Graphics View framework. I have subclassed QGraphicsItem to a class that includes other QGraphicsItem (or other subclasses of it). This class is the parent of the included QGraphicsItem; the idea is to work with composite objects.
From the docs it seems to be a conflict in what I try to achieve:
Calling ignore() in mousePressEvent will make my object unmovable. I want to move it.
Calling accept() in mousePressEvent will prevent the event from being propagated to the child object. Some of the child objects should react to mouse events.
How can I make this work?
I think your interpretation of the documentation is incorrect.
Calling ignore() in mousePressEvent will make my object unmovable.
I don't believe that is true. To me it looks like calling ignore() is like the object saying "I have assessed this event. I have taken all actions I want to in response to this event. I have also decided it was not intended for me, so I will now pass it on to the next object underneath me". I can't find anything which suggests the ignore event will unset the QGraphicsItem::ItemIsMovable flag (which is what decides if the QGraphicsItem is movable or not).
I don't see why you couldn't make your object move and ignore() the event, but I would advise that this is not a sensible approach (in most instances: obviously you may have cause for it).
Calling accept() in mousePressEvent will prevent the event from being propagated to the child object.
I believe this is true, but the parent can still modify its children. My understanding is calling accept() is like the object saying "I have assessed this event. I have taken all actions I want to in response to this event (which may include modifying my children). I have also decided that the event was intended for me, so I will not be passing the event on".
In your parent QGraphicsItem, you might try to
MyObject::mousePressEvent ( QGraphicsSceneMouseEvent * event )
{
QGraphicsItem::mousePressEvent(event);
event->ignore();
}
This would allow normal processing of the mouse event (i.e. make your object moveable), but then ignoring it so that it is propagated.
The logic would need to be more robust, though, because there is a high risk of side effects if a parent and child respond to the same mouse event.
Send QCoreApplication::postEvent(child, mouseEvent) to child objects.

How to subclass a widget to add more elements to it?

I'm trying to make a subclass of QTableView that has an embedded QLineEdit at the top for filtering the results as-you-type. I need my table to have the same API as a normal QTableView, so I want to subclass it rather than subclassing QWidget and adding a QLineEdit and QTableView to it.
I thought I could just re-implement paintEvent(QPaintEvent*), alter the QPaintEvent's rect() to start a bit lower (the height of a QLineEdit, so it draws under it) and then pass it thru to QTableView::paintEvent(), but the QPaintEvent argument only dictates what region needs to be repainted, not the region where the widget should be painted.
Anything you do in this regard is going to be hacky and result in just as much work (probably more work) as manually mapping all of the signals and slots to a child widget. You'll need to do a lot more than just change the paint events, you'd also have to adjust all of the mouse events, adjust any update rectangles, etc.
Alternatively you could just take the QTableView class from the Qt source and modify it directly (though that will probably break the LGPL and require you to publish your source if you don't have a commercial license.) But the easiest clean method is going to be implementing a container widget with the QTableView as a child.
I would try overriding the paintEvent, changing the widget::pos to be abit lower than it is and call QTableView::paintEvent()
I have to agree with Daniel: I don't think this is the right approach. You will likely want to create a custom widget with a line edit for performing the filtering. Otherwise, you will be entering into the challenging world of Qt hacking.
If you really need to provide access to the QTableView interface then simply add a public get method that returns a reference to the table.
This is somewhat similar to how Qt provides a QTabWidget class that inherits QWidget but has a private QTabBar that it uses internally. One significant difference is that it provides a protected tabBar() accessor rather than a public one.

Resources