PDA

View Full Version : Putting all widgets in QGraphicsView using QGraphicsProxyWidgets - Good idea?



Jay-D
29th March 2013, 08:31
Hi,

I’m planning to write a little application (mainly Windows compatible) using PyQt and need some help to find the right approach. The app shall contain a QLabel to show an image, a frameless window with some QPushButtons and a QListView. Now the problem is, that both the QLabel and the QListView shall be rotated by a fixed angle around the Z-Axis. The idea is to make the whole app look like a photograph and a notepad laying underneath some sort of technical device (the main widget in the center). All three widgets shall form a compound, that has a fixed size and can be moved as one window, however I want to preserve the “clattered” look and make it frameless, the main widget shall have minimize/close buttons.

I managed to make the notepad look like somebody had just thrown it on the desktop by using a transformed QGraphicsProxyWidget inside a frameless, transparent QGraphicsView, which is pretty satisfying as I didn’t have to bother about rotating the area receiving mouse events. Now I’m wondering, how to connect this part with the rest. The possibilities I could think of are:



Putting all three widgets in QGraphicsProxyWidgets and use the hidden QGraphicsView to keep everything together

Combining my current notepad-setup with a single frameless QWidget for the center part and a QLabel showing a rotated image with alpha channel (the image is going to be altered with PIL* anyway)

Using my QGraphicsProxyWidget approach on both QLabel and QListView independently and combine them with the main widget.

Something completely different?



Two “features” I’d like to have are a drop-shadow for the whole app (I can recall having problems to get a QGraphicsEffect to work simultaneously on different widgets in the past) and a mask for the whole app, so the parts that are inside the bounds of a widget but invisible don’t receive mouse events. I’d also like the app to open up without much delay, but I have no idea if showing a rotated image in a QLabel would be faster or slower than rotating the whole QLabel inside a QGraphicsView (the image is going to be swapped a few times at runtime).

I hope someone can clarify which way to take or point out the pros and cons of the different approaches. Thanks in advance.

*Python Image Library

d_stranz
31st March 2013, 20:18
It is difficult for me to visualize what you intend your app to look like, other than a "notepad thrown on a desktop" with "some sort of technical device" on top of it. What the heck is a "technical device"? A miniaturized tactical nuclear weapon? An iPhone? A slide rule? The possibilities are endless.

However, if all of these things need to be manipulated as a single unit (as well as being individually moved around), I think I would take the first approach of putting the widgets inside of proxies and embedding them into the graphics scene / view. Otherwise, you'll have to do all the work of calculating and propagating the transformations yourself. But it also depends on what your application is to do. Can you interactively write on the notepad (after moving the nuclear weapon / iPhone / slide rule out of the way)?

I don't think there will be much performance difference between showing a rotated image in a QLabel vs. showing the QLabel rotated. I think it boils down to going through the same QPainter transformation in either case.

Edit - and I think you could dispense with your QLabel / proxy pair altogether. There are QGraphicsItem classes designed for displaying images.

Jay-D
1st April 2013, 10:38
First of all, thanks for your answer d_stranz.

It was never my intention to give you headaches about how my app might look, but how could you possibly find out about my nuclear weapon based doomsday device plan?
Actually it's just a frameless window with some buttons, and two slightly rotated widgets stacked behind it on either side (image and notepad). The notepad cannot be written on, it's just there for the user to select entries and rearrange their order.
The whole app should move as one and the single parts aren't supposed to be movable individually.
Since my main widget in the center is frameless, I overwrote the mousePress-, mouseRelease- and mouseMoveEvents to make it movable by clicking and dragging on any empty space. I started with the "putting-everything-in-proxies" approach, and wanted to call QGraphicsView's move(), whenever those events are fired on the main widget, so that it would look like all three embedded widgets would move as one, but it seems that QGraphicsProxy doesn't pass the mouseReleaseEvent down to the embedded widget (I also had to setMouseTracking(True) on the widget to make the mouseMoveEvent fire, which wasn't necessary when I first tried my widget's mouse move functionality outside the whole Proxy-View stuff).
EDIT2: Solved the mouseReleaseEvent problem: I just had to add event.accept() to the mousePressEvent, as explained here (http://stackoverflow.com/questions/2617887/qt-graphics-scene-mouse-event-propagation)

So I can move the whole app, but since the mouseReleaseEvent is never fired, it doesn't stop following the cursor. How should I go about making the whole GraphicsView (i.e. all three widgets as one) move when the user clicks and drags the center widget?

Another strange thing is, that python crashes after I close the application. I could narrow it down to QGraphicsProxyWidget, so that even if I just run this "pythonified" example from the docs, python still crashes, but only after I close the perfectly functional GrapicsView window:


if __name__ == '__main__':

app = QApplication(sys.argv)

scene = QGraphicsScene()
proxy = scene.addWidget(QListView())
view = QGraphicsView(scene)
view.show()

sys.exit(app.exec_())
What am I missing here? And I apologize if this should go into a new thread, just notify me.
EDIT: The embedded dialogs demo from the PyQt example works like a charm.


I think you could dispense with your QLabel / proxy pair altogether. There are QGraphicsItem classes designed for displaying images.
That of course sounds way better and I'm ashamed I didn't think of it myself. Thanks.

wysota
1st April 2013, 12:07
Hmm... maybe you should use QtQuick?

Jay-D
1st April 2013, 12:25
Well, aside from the mysterious crash, everything is working pretty fine right now, so I don't think I should switch to QtQuick, which I never used before. Seeing how the embedded dialogs example works without the crash after closing it, there MUST be a way to overcome that last obstacle.

d_stranz
2nd April 2013, 00:23
I am guessing that the crash is due to the widget wrapped by the graphics proxy being deleted (and thus left as a dangling invalid pointer inside the proxy) prior to the proxy being deleted. When the proxy tries to delete the wrapped pointer, it blows up. Just conjecture, because I have no idea how you've implemented it or how it maps into Python. But in my experience, crashes on apps closing tend to be due to something holding a pointer and trying to delete it when it already has been somewhere else.

So, I would check the use scenario for QGraphicsProxyWidget to see if it takes ownership of the QWidget; if it does, make sure that you are not deleting the QWidget elsewhere (either directly, or giving it another QWidget as parent).

Edit: note this, from the QGraphicsProxyWidget description:


Alternatively, you can start by creating a new QGraphicsProxyWidget item, and then call setWidget() to embed a QWidget later. The widget() function returns a pointer to the embedded widget. QGraphicsProxyWidget shares ownership with QWidget, so if either of the two widgets are destroyed, the other widget will be automatically destroyed as well.

So, if this is what's happening, then the scene could be trying to destroy the graphics proxy after it has already been destroyed. Try connecting a slot to the QGraphicProxyWidget's destroyed() signal; in that slot, remove the proxy from the scene and see if that fixes the crash.

wysota
2nd April 2013, 00:29
Giving a widget as a parent is safe because when an object is deleted, it is being detached from the parent so the parent will not delete it again.

@Jay-D: about switching to Qt Quick -- it's a matter of doing things in a more convenient way. It's only a suggestion to take a closer look at a technology that might give you additional abilities to what you're doing right now.

d_stranz
2nd April 2013, 00:34
Giving a widget as a parent is safe because when an object is deleted, it is being detached from the parent so the parent will not delete it again.

Yes, I knew that. Since your post and my edit crossed in the aether, what about my conjecture concerning the proxy / widget / scene interaction? One would think that if a QWidget deletion resulted in the deletion of its graphics proxy, that would also clean up the scene.

wysota
2nd April 2013, 01:06
I don't think the widget deletes its proxy. Unless of course you reparent it to the widget. This is just my guess, of course. As for the original problem it seems to me it is enough to see the debugger backtrace from the crash. All should be obvious then.

Jay-D
2nd April 2013, 04:32
Thanks for the answers, I managed to get rid of the error. Instead of letting my center widget call qApp.quit(), I now let it call another function, that loops through the QGraphicsProxyWidgets and close()s them and finally close the QGraphicsView. I'm really not an expert in PyQt's parent relationship, right now I'm creating my widgets without parents and then put them into QGraphicProxyWidgets (setWidget()), which seems to work fine. Should I reparent them somehow?

Anyways, everything works just the way I want it right now, and looking at my original question, I think it's the best approach, as I don't have to worry much about propagating events between the three embedded widgets and everything is wrapped up inside the QGraphicsView, which means I have only one entry in the taskbar.

And I think I should really give QtQuick a try, the summary sounds nice, thanks for pointing me to it, wysota.

d_stranz
2nd April 2013, 17:39
Should I reparent them somehow?

Doesn't seem like it, although if Qt is doing the parenting properly in the first place, you shouldn't have to loop through anything to destroy objects manually. Simply deleting the scene (if it is heap allocated) or allowing it to go out of scope (if stack allocated) should result in all items in the scene being cleanly destroyed and all views being disconnected from the scene. I have never had need to use QGraphicsProxyWidget, so I don't know what happens in that case. Probably would help to comment out your manual cleanup code and see what happens (using the debugger) when Qt goes through its normal shutdown.