With current Qt5 (surely this works with Qt6, too; I don't know if it works with Qt4) I found the following works very well if you want to stack several semi-opaque widgets on top of each other (such as custom drawing canvas or QMdiArea above a media viewer, etc).
General assumption is to view QObject parenting in the sense of event handling (if the child can't handle an event, the parent will deal with it).
In my case this looks similar to the following:
class MyCentralWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# helper class - initializes a layout suitable for widget stacking
class _Layout(QGridLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setContentsMargins(0, 0, 0, 0)
# set layout for this widget
layout = _Layout(self)
# initialize layer widgets
layer0 = ... # bottom most layer
layer1 = ...
layer2 = ...
layer3 = ... # top layer
# Stack entities on top of another
layout.addWidget(layer0, 0, 0) # this is shielded by layer1 and will never receive mouse events
layout.addWidget(layer1, 0, 0) # this will receive all mouse events not handled by layers above
# Parenting-based stacking using QGridLayout
# to enable proper event propagation (events flow in
# child->parent direction if unused by child)
_Layout(layer1).addWidget(layer2, 0, 0)
_Layout(layer2).addWidget(layer3, 0, 0)
# Resulting mouse event propagation: layer3 -> layer2 -> layer1
# possibly add some other floating widget for floating menus...
toolbox = MyToolbox(parent=self)
Now in order to propagate an event to a lower widget (e.g. from layer3 to layer2) you have to make sure the widget does not consume the events. Usually when a widget is not interested in an event it simply passes it to its parent by calling event() on its base (e.g. QMdiArea does it when no QMdiSubWindow or button is hit):
# example implementation of event() method
def event(self, e) -> bool:
if self.consumeEvent(e):
# return if event was consumed
return True
# event was not consumed - send to parent via super call
return super().event(e)