mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-05-13 01:55:46 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ad0e46d98 | |||
| 9d92f8b53a | |||
| c1d5069a48 | |||
| 0b1f0b4c26 | |||
| cc825972c2 | |||
| 17865a2c33 |
@@ -1,6 +1,30 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
|
||||||
|
## v3.8.1 (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **web_links**: Update documentation links in BECWebLinksMixin
|
||||||
|
([`9d92f8b`](https://github.com/bec-project/bec_widgets/commit/9d92f8b53a6ffe57a9dffad797580228023bf6e1))
|
||||||
|
|
||||||
|
|
||||||
|
## v3.8.0 (2026-05-01)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **dock_area**: Change to show_dialo=False for CLI profile baseline restore
|
||||||
|
([`0b1f0b4`](https://github.com/bec-project/bec_widgets/commit/0b1f0b4c262ff31469b7114b9f00bf0a7b85e8f2))
|
||||||
|
|
||||||
|
- **dock_area**: Cli call load_profile has restore_baseline kwarg
|
||||||
|
([`cc82597`](https://github.com/bec-project/bec_widgets/commit/cc825972c202cd9ded32f8b2d1ce5f822c2ebdba))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **dock_area**: Add CLI restore current profile from baseline with optional confirmation dialog
|
||||||
|
([`17865a2`](https://github.com/bec-project/bec_widgets/commit/17865a2c338a4a1f944659dde4ec05c25a8dd963))
|
||||||
|
|
||||||
|
|
||||||
## v3.7.3 (2026-05-01)
|
## v3.7.3 (2026-05-01)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -358,15 +358,31 @@ class BECDockArea(RPCBase):
|
|||||||
|
|
||||||
@rpc_timeout(None)
|
@rpc_timeout(None)
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def load_profile(self, name: "str | None" = None):
|
def load_profile(self, name: "str | None" = None, restore_baseline: "bool" = False):
|
||||||
"""
|
"""
|
||||||
Load a workspace profile.
|
Load a workspace profile.
|
||||||
|
|
||||||
Before switching, persist the current profile to the runtime copy.
|
Before switching, persist the current profile to the runtime copy.
|
||||||
Prefer loading the runtime copy; fall back to the baseline copy.
|
Prefer loading the runtime copy; fall back to the baseline copy. When
|
||||||
|
``restore_baseline`` is True, first overwrite the runtime copy with the
|
||||||
|
baseline profile and then load it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str | None): The name of the profile to load. If None, prompts the user.
|
name (str | None): The name of the profile to load. If None, prompts the user.
|
||||||
|
restore_baseline (bool): If True, restore the runtime copy from the
|
||||||
|
baseline before loading. Defaults to False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_timeout(None)
|
||||||
|
@rpc_call
|
||||||
|
def restore_baseline_profile(self, name: "str | None" = None, show_dialog: "bool" = False):
|
||||||
|
"""
|
||||||
|
Overwrite the runtime copy of *name* with the baseline.
|
||||||
|
If *name* is None, target the currently active profile.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str | None): The name of the profile to restore. If None, uses the current profile.
|
||||||
|
show_dialog (bool): If True, ask for confirmation before restoring.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@rpc_call
|
@rpc_call
|
||||||
@@ -1366,15 +1382,31 @@ class DockAreaView(RPCBase):
|
|||||||
|
|
||||||
@rpc_timeout(None)
|
@rpc_timeout(None)
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def load_profile(self, name: "str | None" = None):
|
def load_profile(self, name: "str | None" = None, restore_baseline: "bool" = False):
|
||||||
"""
|
"""
|
||||||
Load a workspace profile.
|
Load a workspace profile.
|
||||||
|
|
||||||
Before switching, persist the current profile to the runtime copy.
|
Before switching, persist the current profile to the runtime copy.
|
||||||
Prefer loading the runtime copy; fall back to the baseline copy.
|
Prefer loading the runtime copy; fall back to the baseline copy. When
|
||||||
|
``restore_baseline`` is True, first overwrite the runtime copy with the
|
||||||
|
baseline profile and then load it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str | None): The name of the profile to load. If None, prompts the user.
|
name (str | None): The name of the profile to load. If None, prompts the user.
|
||||||
|
restore_baseline (bool): If True, restore the runtime copy from the
|
||||||
|
baseline before loading. Defaults to False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_timeout(None)
|
||||||
|
@rpc_call
|
||||||
|
def restore_baseline_profile(self, name: "str | None" = None, show_dialog: "bool" = False):
|
||||||
|
"""
|
||||||
|
Overwrite the runtime copy of *name* with the baseline.
|
||||||
|
If *name* is None, target the currently active profile.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str | None): The name of the profile to restore. If None, uses the current profile.
|
||||||
|
show_dialog (bool): If True, ask for confirmation before restoring.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@rpc_call
|
@rpc_call
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class BECDockArea(DockAreaWidget):
|
|||||||
"list_profiles",
|
"list_profiles",
|
||||||
"save_profile",
|
"save_profile",
|
||||||
"load_profile",
|
"load_profile",
|
||||||
|
"restore_baseline_profile",
|
||||||
"delete_profile",
|
"delete_profile",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -819,16 +820,21 @@ class BECDockArea(DockAreaWidget):
|
|||||||
|
|
||||||
@SafeSlot()
|
@SafeSlot()
|
||||||
@SafeSlot(str)
|
@SafeSlot(str)
|
||||||
|
@SafeSlot(str, bool)
|
||||||
@rpc_timeout(None)
|
@rpc_timeout(None)
|
||||||
def load_profile(self, name: str | None = None):
|
def load_profile(self, name: str | None = None, restore_baseline: bool = False):
|
||||||
"""
|
"""
|
||||||
Load a workspace profile.
|
Load a workspace profile.
|
||||||
|
|
||||||
Before switching, persist the current profile to the runtime copy.
|
Before switching, persist the current profile to the runtime copy.
|
||||||
Prefer loading the runtime copy; fall back to the baseline copy.
|
Prefer loading the runtime copy; fall back to the baseline copy. When
|
||||||
|
``restore_baseline`` is True, first overwrite the runtime copy with the
|
||||||
|
baseline profile and then load it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str | None): The name of the profile to load. If None, prompts the user.
|
name (str | None): The name of the profile to load. If None, prompts the user.
|
||||||
|
restore_baseline (bool): If True, restore the runtime copy from the
|
||||||
|
baseline before loading. Defaults to False.
|
||||||
"""
|
"""
|
||||||
if name == "":
|
if name == "":
|
||||||
return
|
return
|
||||||
@@ -850,6 +856,9 @@ class BECDockArea(DockAreaWidget):
|
|||||||
us_prev = open_runtime_settings(prev_name, namespace=namespace)
|
us_prev = open_runtime_settings(prev_name, namespace=namespace)
|
||||||
self._write_snapshot_to_settings(us_prev, save_preview=True)
|
self._write_snapshot_to_settings(us_prev, save_preview=True)
|
||||||
|
|
||||||
|
if restore_baseline:
|
||||||
|
restore_runtime_from_baseline(name, namespace=namespace)
|
||||||
|
|
||||||
settings = None
|
settings = None
|
||||||
if any(os.path.exists(path) for path in runtime_profile_candidates(name, namespace)):
|
if any(os.path.exists(path) for path in runtime_profile_candidates(name, namespace)):
|
||||||
settings = open_runtime_settings(name, namespace=namespace)
|
settings = open_runtime_settings(name, namespace=namespace)
|
||||||
@@ -896,30 +905,34 @@ class BECDockArea(DockAreaWidget):
|
|||||||
|
|
||||||
@SafeSlot()
|
@SafeSlot()
|
||||||
@SafeSlot(str)
|
@SafeSlot(str)
|
||||||
def restore_runtime_profile_from_baseline(self, name: str | None = None):
|
@SafeSlot(str, bool)
|
||||||
|
@rpc_timeout(None)
|
||||||
|
def restore_baseline_profile(self, name: str | None = None, show_dialog: bool = False):
|
||||||
"""
|
"""
|
||||||
Overwrite the runtime copy of *name* with the baseline.
|
Overwrite the runtime copy of *name* with the baseline.
|
||||||
If *name* is None, target the currently active profile.
|
If *name* is None, target the currently active profile.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str | None): The name of the profile to restore. If None, uses the current profile.
|
name (str | None): The name of the profile to restore. If None, uses the current profile.
|
||||||
|
show_dialog (bool): If True, ask for confirmation before restoring.
|
||||||
"""
|
"""
|
||||||
target = name or getattr(self, "_current_profile_name", None)
|
target = name or getattr(self, "_current_profile_name", None)
|
||||||
if not target:
|
if not target:
|
||||||
return
|
return
|
||||||
namespace = self.profile_namespace
|
namespace = self.profile_namespace
|
||||||
|
|
||||||
current_pixmap = None
|
if show_dialog:
|
||||||
if self.isVisible():
|
current_pixmap = None
|
||||||
current_pixmap = QPixmap()
|
if self.isVisible():
|
||||||
ba = bytes(self.screenshot_bytes())
|
current_pixmap = QPixmap()
|
||||||
current_pixmap.loadFromData(ba)
|
ba = bytes(self.screenshot_bytes())
|
||||||
if current_pixmap is None or current_pixmap.isNull():
|
current_pixmap.loadFromData(ba)
|
||||||
current_pixmap = load_runtime_profile_screenshot(target, namespace=namespace)
|
if current_pixmap is None or current_pixmap.isNull():
|
||||||
baseline_pixmap = load_baseline_profile_screenshot(target, namespace=namespace)
|
current_pixmap = load_runtime_profile_screenshot(target, namespace=namespace)
|
||||||
|
baseline_pixmap = load_baseline_profile_screenshot(target, namespace=namespace)
|
||||||
|
|
||||||
if not RestoreProfileDialog.confirm(self, current_pixmap, baseline_pixmap):
|
if not RestoreProfileDialog.confirm(self, current_pixmap, baseline_pixmap):
|
||||||
return
|
return
|
||||||
|
|
||||||
restore_runtime_from_baseline(target, namespace=namespace)
|
restore_runtime_from_baseline(target, namespace=namespace)
|
||||||
self.delete_all()
|
self.delete_all()
|
||||||
|
|||||||
@@ -235,4 +235,4 @@ class WorkspaceConnection(BundleConnection):
|
|||||||
"""
|
"""
|
||||||
Refreshes the current workspace.
|
Refreshes the current workspace.
|
||||||
"""
|
"""
|
||||||
self.target_widget.restore_runtime_profile_from_baseline()
|
self.target_widget.restore_baseline_profile(show_dialog=True)
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import webbrowser
|
|||||||
class BECWebLinksMixin:
|
class BECWebLinksMixin:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def open_bec_docs():
|
def open_bec_docs():
|
||||||
webbrowser.open("https://beamline-experiment-control.readthedocs.io/en/latest/")
|
webbrowser.open("https://bec-project.github.io/bec_docs/")
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def open_bec_widgets_docs():
|
|
||||||
webbrowser.open("https://bec.readthedocs.io/projects/bec-widgets/en/latest/")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def open_bec_bug_report():
|
def open_bec_bug_report():
|
||||||
|
|||||||
@@ -355,17 +355,13 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|||||||
|
|
||||||
bec_docs = QAction("BEC Docs", self)
|
bec_docs = QAction("BEC Docs", self)
|
||||||
bec_docs.setIcon(help_icon)
|
bec_docs.setIcon(help_icon)
|
||||||
widgets_docs = QAction("BEC Widgets Docs", self)
|
|
||||||
widgets_docs.setIcon(help_icon)
|
|
||||||
bug_report = QAction("Bug Report", self)
|
bug_report = QAction("Bug Report", self)
|
||||||
bug_report.setIcon(bug_icon)
|
bug_report.setIcon(bug_icon)
|
||||||
|
|
||||||
bec_docs.triggered.connect(BECWebLinksMixin.open_bec_docs)
|
bec_docs.triggered.connect(BECWebLinksMixin.open_bec_docs)
|
||||||
widgets_docs.triggered.connect(BECWebLinksMixin.open_bec_widgets_docs)
|
|
||||||
bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report)
|
bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report)
|
||||||
|
|
||||||
help_menu.addAction(bec_docs)
|
help_menu.addAction(bec_docs)
|
||||||
help_menu.addAction(widgets_docs)
|
|
||||||
help_menu.addAction(bug_report)
|
help_menu.addAction(bug_report)
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "bec_widgets"
|
name = "bec_widgets"
|
||||||
version = "3.7.3"
|
version = "3.8.1"
|
||||||
description = "BEC Widgets"
|
description = "BEC Widgets"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
@@ -58,6 +58,7 @@ dev = [
|
|||||||
]
|
]
|
||||||
qtermwidget = ["pyside6_qtermwidget"]
|
qtermwidget = ["pyside6_qtermwidget"]
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|||||||
@@ -1421,7 +1421,7 @@ class TestAdvancedDockAreaRestoreAndDialogs:
|
|||||||
patch.object(advanced_dock_area, "delete_all") as mock_delete_all,
|
patch.object(advanced_dock_area, "delete_all") as mock_delete_all,
|
||||||
patch.object(advanced_dock_area, "load_profile") as mock_load_profile,
|
patch.object(advanced_dock_area, "load_profile") as mock_load_profile,
|
||||||
):
|
):
|
||||||
advanced_dock_area.restore_runtime_profile_from_baseline()
|
advanced_dock_area.restore_baseline_profile(show_dialog=True)
|
||||||
|
|
||||||
assert mock_restore.call_count == 1
|
assert mock_restore.call_count == 1
|
||||||
args, kwargs = mock_restore.call_args
|
args, kwargs = mock_restore.call_args
|
||||||
@@ -1455,16 +1455,41 @@ class TestAdvancedDockAreaRestoreAndDialogs:
|
|||||||
with patch(
|
with patch(
|
||||||
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
|
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
|
||||||
) as mock_restore:
|
) as mock_restore:
|
||||||
advanced_dock_area.restore_runtime_profile_from_baseline()
|
advanced_dock_area.restore_baseline_profile(show_dialog=True)
|
||||||
|
|
||||||
mock_restore.assert_not_called()
|
mock_restore.assert_not_called()
|
||||||
|
|
||||||
|
def test_restore_runtime_profile_from_baseline_without_dialog(self, advanced_dock_area):
|
||||||
|
profile_name = "alignment_scan"
|
||||||
|
helper = profile_helper(advanced_dock_area)
|
||||||
|
helper.open_baseline(profile_name).sync()
|
||||||
|
helper.open_runtime(profile_name).sync()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
|
||||||
|
) as mock_confirm,
|
||||||
|
patch(
|
||||||
|
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
|
||||||
|
) as mock_restore,
|
||||||
|
patch.object(advanced_dock_area, "delete_all") as mock_delete_all,
|
||||||
|
patch.object(advanced_dock_area, "load_profile") as mock_load_profile,
|
||||||
|
):
|
||||||
|
advanced_dock_area.restore_baseline_profile(profile_name, show_dialog=False)
|
||||||
|
|
||||||
|
mock_confirm.assert_not_called()
|
||||||
|
mock_restore.assert_called_once_with(
|
||||||
|
profile_name, namespace=advanced_dock_area.profile_namespace
|
||||||
|
)
|
||||||
|
mock_delete_all.assert_called_once()
|
||||||
|
mock_load_profile.assert_called_once_with(profile_name)
|
||||||
|
|
||||||
def test_restore_runtime_profile_from_baseline_no_target(self, advanced_dock_area, monkeypatch):
|
def test_restore_runtime_profile_from_baseline_no_target(self, advanced_dock_area, monkeypatch):
|
||||||
advanced_dock_area._current_profile_name = None
|
advanced_dock_area._current_profile_name = None
|
||||||
with patch(
|
with patch(
|
||||||
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
|
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
|
||||||
) as mock_confirm:
|
) as mock_confirm:
|
||||||
advanced_dock_area.restore_runtime_profile_from_baseline()
|
advanced_dock_area.restore_baseline_profile(show_dialog=True)
|
||||||
mock_confirm.assert_not_called()
|
mock_confirm.assert_not_called()
|
||||||
|
|
||||||
def test_refresh_workspace_list_with_refresh_profiles(self, advanced_dock_area):
|
def test_refresh_workspace_list_with_refresh_profiles(self, advanced_dock_area):
|
||||||
@@ -1862,6 +1887,43 @@ class TestWorkspaceProfileOperations:
|
|||||||
widget_map = advanced_dock_area.widget_map()
|
widget_map = advanced_dock_area.widget_map()
|
||||||
assert "test_widget" in widget_map
|
assert "test_widget" in widget_map
|
||||||
|
|
||||||
|
def test_load_profile_default_does_not_restore_baseline(self, advanced_dock_area):
|
||||||
|
"""Regular profile loading should not restore the runtime copy."""
|
||||||
|
profile_name = "load_without_baseline_restore"
|
||||||
|
helper = profile_helper(advanced_dock_area)
|
||||||
|
helper.open_runtime(profile_name).sync()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
|
||||||
|
) as mock_restore:
|
||||||
|
advanced_dock_area.load_profile(profile_name)
|
||||||
|
|
||||||
|
mock_restore.assert_not_called()
|
||||||
|
assert advanced_dock_area._current_profile_name == profile_name
|
||||||
|
|
||||||
|
def test_load_profile_restores_baseline_without_dialog(self, advanced_dock_area):
|
||||||
|
"""CLI loading can restore the runtime copy from baseline without confirmation."""
|
||||||
|
profile_name = "alignment_scan"
|
||||||
|
helper = profile_helper(advanced_dock_area)
|
||||||
|
helper.open_baseline(profile_name).sync()
|
||||||
|
helper.open_runtime(profile_name).sync()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
|
||||||
|
) as mock_confirm,
|
||||||
|
patch(
|
||||||
|
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
|
||||||
|
) as mock_restore,
|
||||||
|
):
|
||||||
|
advanced_dock_area.load_profile(profile_name, restore_baseline=True)
|
||||||
|
|
||||||
|
mock_confirm.assert_not_called()
|
||||||
|
mock_restore.assert_called_once_with(
|
||||||
|
profile_name, namespace=advanced_dock_area.profile_namespace
|
||||||
|
)
|
||||||
|
assert advanced_dock_area._current_profile_name == profile_name
|
||||||
|
|
||||||
def test_load_profile_materializes_runtime_namespace_fallback(self, advanced_dock_area):
|
def test_load_profile_materializes_runtime_namespace_fallback(self, advanced_dock_area):
|
||||||
"""Loading a runtime fallback copies it into the active namespace before opening."""
|
"""Loading a runtime fallback copies it into the active namespace before opening."""
|
||||||
profile_name = "load_runtime_namespace_fallback"
|
profile_name = "load_runtime_namespace_fallback"
|
||||||
|
|||||||
@@ -247,12 +247,10 @@ def test_bec_weblinks(monkeypatch):
|
|||||||
monkeypatch.setattr(webbrowser, "open", fake_open)
|
monkeypatch.setattr(webbrowser, "open", fake_open)
|
||||||
|
|
||||||
BECWebLinksMixin.open_bec_docs()
|
BECWebLinksMixin.open_bec_docs()
|
||||||
BECWebLinksMixin.open_bec_widgets_docs()
|
|
||||||
BECWebLinksMixin.open_bec_bug_report()
|
BECWebLinksMixin.open_bec_bug_report()
|
||||||
|
|
||||||
assert opened_urls == [
|
assert opened_urls == [
|
||||||
"https://beamline-experiment-control.readthedocs.io/en/latest/",
|
"https://bec-project.github.io/bec_docs/",
|
||||||
"https://bec.readthedocs.io/projects/bec-widgets/en/latest/",
|
|
||||||
"https://github.com/bec-project/bec_widgets/issues",
|
"https://github.com/bec-project/bec_widgets/issues",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user