From 64e7661a3b6ed5501c6c9d9da6936c14a8360ab1 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 26 Oct 2026 16:02:00 +0100 Subject: [PATCH 1/4] Make sure all dialogs are handled the proper way CURA-13376 --- UM/Qt/QtApplication.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/UM/Qt/QtApplication.py b/UM/Qt/QtApplication.py index cb72d1822..debcaaeec 100644 --- a/UM/Qt/QtApplication.py +++ b/UM/Qt/QtApplication.py @@ -609,17 +609,19 @@ def _onWindowVisibleChange(self, visible: bool) -> None: if not visible: self._sub_windows.remove(self.sender()) - def createQmlSubWindow(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None, initial_properties: Dict[str, "QObject"] = {}) -> Optional["QQuickWindow"]: + def createQmlSubWindow(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None, initial_properties: Dict[str, "QObject"] = {}, manage_lifecycle: bool = True) -> Optional["QQuickWindow"]: """ Create a QML window from a QML file. This method uses createQmlComponent internally, but adds a few specific features for windows management: * The created object is a QQuickWindow instance * The transient parent of the window is explicitly set to be the main window - * The window ownership is handled and it will be destroyed as soon as it's no longer visible, - so the caller should not keep a reference to it + * When lifecycle management is enabled, the window ownership is handled and it will be destroyed as soon + as it's no longer visible, so the caller should not keep a reference to it :param qml_file_path: The absolute file path to the root qml file. :param context_properties: Optional dictionary containing the properties that will be set on the context of the qml instance before creation. + :param manage_lifecycle: Whether the application should retain the window until it is hidden and then release it. + Set to False when the caller retains and manages the window itself. :return: The created QQuickWindow instance, or None in case the creation failed (qml error) """ result = self.createQmlComponent(qml_file_path, context_properties, initial_properties) @@ -628,9 +630,10 @@ def createQmlSubWindow(self, qml_file_path: str, context_properties: Dict[str, " result.setTransientParent(self._main_window) - # Keep a link to the window so that it is not garbage-collected, then register it for destruction - self._sub_windows.append(result) - result.visibleChanged.connect(self._onWindowVisibleChange, Qt.ConnectionType.QueuedConnection) + if manage_lifecycle: + # Keep a link to the window so that it is not garbage-collected, then register it for destruction. + self._sub_windows.append(result) + result.visibleChanged.connect(self._onWindowVisibleChange, Qt.ConnectionType.QueuedConnection) return result From cea52cce06ba3086dce8ce917de7a9d269da4102 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 26 Oct 2026 16:51:47 +0100 Subject: [PATCH 2/4] Simplify setting the transient parent CURA-13376 We now set the transient parent on the top-level Dialog item, so that it does not require being created through createQmlSubWindow anymore. This is useful for dialogs that are created from QML. --- UM/Qt/QtApplication.py | 24 ++++++++++-------------- UM/Qt/qml/UM/Dialog.qml | 1 + 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/UM/Qt/QtApplication.py b/UM/Qt/QtApplication.py index debcaaeec..9bbec9879 100644 --- a/UM/Qt/QtApplication.py +++ b/UM/Qt/QtApplication.py @@ -457,6 +457,7 @@ def isShuttingDown(self) -> bool: return self._is_shutting_down def registerObjects(self, engine) -> None: #type: ignore #Don't type engine, because the type depends on the platform you're running on so it always gives an error somewhere. + engine.rootContext().setContextProperty("QtApplication", self) engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def makeRenderer(self) -> QtRenderer: @@ -468,8 +469,6 @@ def getRenderer(self) -> QtRenderer: return cast(QtRenderer, self._renderer) - mainWindowChanged = Signal() - def getMainWindow(self) -> Optional[MainWindow]: return self._main_window @@ -483,6 +482,9 @@ def setMainWindow(self, window: MainWindow) -> None: self._main_window.windowStateChanged.connect(self._onMainWindowStateChanged) self.mainWindowChanged.emit() + mainWindowChanged = Signal() + mainWindow = pyqtProperty(QQuickWindow, fget=getMainWindow, constant=True) + def setVisible(self, visible: bool) -> None: if self._main_window is not None: self._main_window.visible = visible @@ -609,31 +611,25 @@ def _onWindowVisibleChange(self, visible: bool) -> None: if not visible: self._sub_windows.remove(self.sender()) - def createQmlSubWindow(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None, initial_properties: Dict[str, "QObject"] = {}, manage_lifecycle: bool = True) -> Optional["QQuickWindow"]: + def createQmlSubWindow(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None, initial_properties: Dict[str, "QObject"] = {}) -> Optional["QQuickWindow"]: """ Create a QML window from a QML file. This method uses createQmlComponent internally, but adds a few specific features for windows management: * The created object is a QQuickWindow instance - * The transient parent of the window is explicitly set to be the main window - * When lifecycle management is enabled, the window ownership is handled and it will be destroyed as soon - as it's no longer visible, so the caller should not keep a reference to it + * The window ownership is handled and it will be destroyed as soon as it's no longer visible, + so the caller should not keep a reference to it :param qml_file_path: The absolute file path to the root qml file. :param context_properties: Optional dictionary containing the properties that will be set on the context of the qml instance before creation. - :param manage_lifecycle: Whether the application should retain the window until it is hidden and then release it. - Set to False when the caller retains and manages the window itself. :return: The created QQuickWindow instance, or None in case the creation failed (qml error) """ result = self.createQmlComponent(qml_file_path, context_properties, initial_properties) if result is None: return None - result.setTransientParent(self._main_window) - - if manage_lifecycle: - # Keep a link to the window so that it is not garbage-collected, then register it for destruction. - self._sub_windows.append(result) - result.visibleChanged.connect(self._onWindowVisibleChange, Qt.ConnectionType.QueuedConnection) + # Keep a link to the window so that it is not garbage-collected, then register it for destruction. + self._sub_windows.append(result) + result.visibleChanged.connect(self._onWindowVisibleChange, Qt.ConnectionType.QueuedConnection) return result diff --git a/UM/Qt/qml/UM/Dialog.qml b/UM/Qt/qml/UM/Dialog.qml index 465905230..0d3a5eeaa 100644 --- a/UM/Qt/qml/UM/Dialog.qml +++ b/UM/Qt/qml/UM/Dialog.qml @@ -20,6 +20,7 @@ Window minimumHeight: screenScaleFactor * 480; width: minimumWidth height: minimumHeight + transientParent: QtApplication.mainWindow property int margin: UM.Theme.getSize("default_margin").width property bool closeOnAccept: true; // Automatically close the window when the window is "accepted" (eg using the return key) From a8708dfb9f9905a033d97043443ceea2fa7fa45f Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 28 Oct 2026 09:00:36 +0100 Subject: [PATCH 3/4] Use `data` instead of `children`, which allows simplifying the dialogs CURA-13376 When using `data`, we can also set non-visual children elements, so now we don't have to declare a top-level Item for the dialogs that have various children. As suggested by @rburema --- UM/Qt/qml/UM/Dialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UM/Qt/qml/UM/Dialog.qml b/UM/Qt/qml/UM/Dialog.qml index 0d3a5eeaa..13d9374fd 100644 --- a/UM/Qt/qml/UM/Dialog.qml +++ b/UM/Qt/qml/UM/Dialog.qml @@ -26,7 +26,7 @@ Window property bool closeOnAccept: true; // Automatically close the window when the window is "accepted" (eg using the return key) property bool selfDestroy: false; // Automatically destroys the dialog when it has been hidden, useful if it has been created with component.createObject - default property alias contents: contentItem.children; + default property alias contents: contentItem.data; property alias loader: contentLoader From 46562ae52acfb6499e6b3f14dda86688ce1af81d Mon Sep 17 00:00:00 2001 From: HellAholic Date: Wed, 28 Oct 2026 11:30:24 +0100 Subject: [PATCH 4/4] Pin pytest and pytest-benchmark versions Pin development dependencies pytest to 9.1.1 and pytest-benchmark to 5.3.0. --- conandata.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index 29ece110b..3dcbf38cb 100644 --- a/conandata.yml +++ b/conandata.yml @@ -411,10 +411,12 @@ pip_requirements_core: pip_requirements_dev: any_os: - pytest: {} + pytest: + version: "9.1.1" twisted: version: "21.2.0" - pytest-benchmark: {} + pytest-benchmark: + version: "5.3.0" sip: {} python_translation_source_folders: