Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions bec_widgets/utils/bec_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
if TYPE_CHECKING: # pragma: no cover
from bec_lib.endpoints import EndpointInfo

from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.rpc_server import RPCServer


Expand All @@ -42,6 +43,7 @@ def __init__(self, cb: Callable, cb_info: dict | None = None):
self.cb_info = cb_info

self.cb = cb
self.cb_owner = louie.saferef.safe_ref(cb.__self__) if hasattr(cb, "__self__") else None
self.cb_ref = louie.saferef.safe_ref(cb)
self.cb_signal.connect(self.cb)
self.topics = set()
Expand Down Expand Up @@ -240,6 +242,22 @@ def disconnect_all(self, *args, **kwargs):
# pylint: disable=protected-access
self.disconnect_topics(self.client.connector._topics_cb)

def disconnect_owner(self, owner: BECWidget):
"""
Disconnect all slots owned by a particular widget.

Args:
owner(BECWidget): The owner widget whose slots should be disconnected
"""
slots_to_disconnect = []
for connected_slot in self._registered_slots.values():
if connected_slot.cb_owner is not None and connected_slot.cb_owner() == owner:
slots_to_disconnect.append(connected_slot)
for slot in slots_to_disconnect:
topics = slot.topics.copy()
for topic in topics:
self.disconnect_slot(slot.cb, topic)

def start_cli_server(self, gui_id: str | None = None):
"""
Start the CLI server.
Expand Down
1 change: 1 addition & 0 deletions bec_widgets/utils/bec_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ def closeEvent(self, event):
"""Wrap the close even to ensure the rpc_register is cleaned up."""
try:
if not self._destroyed:
self.bec_dispatcher.disconnect_owner(self)
self.cleanup()
self._destroyed = True
finally:
Expand Down
46 changes: 43 additions & 3 deletions bec_widgets/utils/ui_loader.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from bec_lib.logger import bec_logger
from qtpy import PYSIDE6
from qtpy.QtCore import QFile, QIODevice
from qtpy.QtCore import QEvent, QFile, QIODevice, QObject

from bec_widgets.utils.plugin_utils import get_designer_plugin

Expand All @@ -9,16 +9,56 @@
if PYSIDE6:
from qtpy.QtUiTools import QUiLoader

class _LoadedUiCloser(QObject):
"""Forward root close events to widgets instantiated by ``QUiLoader``.

Destroying a parent widget does not guarantee ``closeEvent`` is delivered to
every child widget. Some of our designer plugins rely on ``closeEvent`` /
``cleanup`` to unregister callbacks, so explicitly close loaded descendants
when the loaded form itself is closed.
"""

def __init__(self, root_widget):
super().__init__(root_widget)
self._root_widget = root_widget
self._widgets = []
root_widget.installEventFilter(self)

def register_widget(self, widget):
if widget is None or widget is self._root_widget:
return
self._widgets.append(widget)

def eventFilter(self, watched, event):
if watched is self._root_widget and event.type() == QEvent.Close:
for widget in reversed(self._widgets):
try:
widget.close()
except RuntimeError:
continue
return super().eventFilter(watched, event)

class CustomUiLoader(QUiLoader):
def __init__(self, baseinstance):
super().__init__(baseinstance)
self.baseinstance = baseinstance
self._closer = _LoadedUiCloser(baseinstance) if baseinstance is not None else None

def createWidget(self, class_name, parent=None, name=""):
if parent is None and self.baseinstance is not None:
return self.baseinstance

widget_parent = parent if parent is not None else self.baseinstance
widget = get_designer_plugin(class_name, raise_on_missing=False)
if widget is not None:
return widget(self.baseinstance)
return super().createWidget(class_name, self.baseinstance, name)
created_widget = widget(widget_parent)
created_widget.setObjectName(name)
else:
created_widget = super().createWidget(class_name, widget_parent, name)

if self._closer is not None:
self._closer.register_widget(created_widget)
return created_widget


class UILoader:
Expand Down
Loading