mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-07-25 12:42:58 +02:00
feat: add support for enabling/disabling experimental features in the app
This commit is contained in:
@@ -4,6 +4,8 @@ Utilities for managing BECDockArea profiles stored in INI files.
|
||||
Policy:
|
||||
- All created/modified profiles are stored under the BEC settings root:
|
||||
<base_path>/profiles/{baseline,runtime}
|
||||
- App-wide dock-area metadata and feature flags are stored in
|
||||
<base_path>/profiles/_meta.ini.
|
||||
- Bundled read-only baselines are discovered in BW core profiles and plugin
|
||||
bec_widgets/profiles but never written to.
|
||||
- Lookup order when reading: runtime → settings baseline → app or plugin bundled baseline.
|
||||
@@ -463,6 +465,7 @@ SETTINGS_KEYS = {
|
||||
"manifest": "manifest/widgets",
|
||||
"created_at": "profile/created_at",
|
||||
"is_quick_select": "profile/quick_select",
|
||||
"experimental_features": "app/experimental_features",
|
||||
"screenshot": "profile/screenshot",
|
||||
"screenshot_at": "profile/screenshot_at",
|
||||
"last_profile": "app/last_profile",
|
||||
@@ -661,6 +664,28 @@ def set_last_profile(
|
||||
s.remove(key)
|
||||
|
||||
|
||||
def is_experimental_features_enabled() -> bool:
|
||||
"""
|
||||
Check whether experimental features are enabled globally.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when the global experimental flag is enabled.
|
||||
"""
|
||||
s = _app_settings()
|
||||
return s.value(SETTINGS_KEYS["experimental_features"], False, type=bool)
|
||||
|
||||
|
||||
def set_experimental_features_enabled(enabled: bool) -> None:
|
||||
"""
|
||||
Persist the global experimental-features flag.
|
||||
|
||||
Args:
|
||||
enabled (bool): ``True`` to enable experimental features, ``False`` to disable.
|
||||
"""
|
||||
s = _app_settings()
|
||||
s.setValue(SETTINGS_KEYS["experimental_features"], bool(enabled))
|
||||
|
||||
|
||||
def now_iso_utc() -> str:
|
||||
"""
|
||||
Return the current UTC timestamp formatted in ISO 8601.
|
||||
|
||||
@@ -12,6 +12,7 @@ from qtpy.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QStyle,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@@ -22,6 +23,10 @@ from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
from bec_widgets.utils.ui_loader import UILoader
|
||||
from bec_widgets.widgets.containers.dock_area.profile_utils import (
|
||||
is_experimental_features_enabled,
|
||||
set_experimental_features_enabled,
|
||||
)
|
||||
from bec_widgets.widgets.containers.main_window.addons.hover_widget import HoverWidget
|
||||
from bec_widgets.widgets.containers.main_window.addons.notification_center.notification_banner import (
|
||||
BECNotificationBroker,
|
||||
@@ -343,6 +348,12 @@ class BECMainWindow(BECWidget, QMainWindow):
|
||||
widget_tree_action.triggered.connect(self._show_widget_hierarchy_dialog)
|
||||
theme_menu.addAction(widget_tree_action)
|
||||
|
||||
experimental_features_action = QAction("Enable Experimental Features", self, checkable=True)
|
||||
experimental_features_action.setChecked(is_experimental_features_enabled())
|
||||
experimental_features_action.toggled.connect(self._toggle_experimental_features)
|
||||
theme_menu.addAction(experimental_features_action)
|
||||
self._experimental_features_action = experimental_features_action
|
||||
|
||||
# Set the default theme
|
||||
if hasattr(self.app, "theme") and self.app.theme:
|
||||
theme_name = self.app.theme.theme.lower()
|
||||
@@ -417,6 +428,19 @@ class BECMainWindow(BECWidget, QMainWindow):
|
||||
"""
|
||||
apply_theme(theme) # emits theme_updated and applies palette globally
|
||||
|
||||
@SafeSlot(bool)
|
||||
def _toggle_experimental_features(self, enabled: bool):
|
||||
set_experimental_features_enabled(enabled)
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Restart Required",
|
||||
"Experimental features require a restart to take effect. Restart the application now?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
self.app.quit()
|
||||
|
||||
def _show_widget_hierarchy_dialog(self):
|
||||
if self._widget_hierarchy_dialog is None:
|
||||
dialog = WidgetHierarchyDialog(root_widget=None, parent=self)
|
||||
|
||||
@@ -22,6 +22,7 @@ from bec_widgets.widgets.containers.dock_area.profile_utils import (
|
||||
SETTINGS_KEYS,
|
||||
baseline_profile_path,
|
||||
get_profile_info,
|
||||
is_experimental_features_enabled,
|
||||
is_profile_read_only,
|
||||
is_quick_select,
|
||||
list_profiles,
|
||||
@@ -32,6 +33,7 @@ from bec_widgets.widgets.containers.dock_area.profile_utils import (
|
||||
read_manifest,
|
||||
restore_runtime_from_baseline,
|
||||
runtime_profile_path,
|
||||
set_experimental_features_enabled,
|
||||
set_quick_select,
|
||||
write_manifest,
|
||||
)
|
||||
@@ -1443,6 +1445,20 @@ class TestWorkSpaceManager:
|
||||
assert is_quick_select(name) is (not initial)
|
||||
assert target.refresh_calls >= 1
|
||||
|
||||
def test_global_experimental_features_flag_persists(self):
|
||||
assert is_experimental_features_enabled() is False
|
||||
|
||||
set_experimental_features_enabled(True)
|
||||
|
||||
assert is_experimental_features_enabled() is True
|
||||
meta_path = os.path.join(os.environ["BECWIDGETS_PROFILE_DIR"], "_meta.ini")
|
||||
assert (
|
||||
QSettings(meta_path, QSettings.IniFormat).value(
|
||||
SETTINGS_KEYS["experimental_features"], type=bool
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
def test_save_current_as_profile_with_target(self, qtbot, workspace_manager_target):
|
||||
name = "profile_save"
|
||||
self._create_profiles([name])
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import os
|
||||
import webbrowser
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from qtpy.QtCore import QEvent, QPoint, QPointF
|
||||
from qtpy.QtCore import QEvent, QPoint, QPointF, QSettings
|
||||
from qtpy.QtGui import QEnterEvent
|
||||
from qtpy.QtWidgets import QApplication, QFrame, QLabel
|
||||
from qtpy.QtWidgets import QApplication, QFrame, QLabel, QMessageBox
|
||||
|
||||
from bec_widgets.widgets.containers.dock_area.profile_utils import SETTINGS_KEYS
|
||||
from bec_widgets.widgets.containers.main_window.addons.hover_widget import (
|
||||
HoverWidget,
|
||||
WidgetTooltip,
|
||||
@@ -112,6 +114,35 @@ def test_hidden_scan_progress_parent_blocks_children_namespace(bec_main_window):
|
||||
assert nested_progress.parent_id == hidden_progress.gui_id
|
||||
|
||||
|
||||
def test_experimental_features_menu_action_persists_flag(
|
||||
monkeypatch, tmp_path, qtbot, mocked_client
|
||||
):
|
||||
monkeypatch.setenv("BECWIDGETS_PROFILE_DIR", str(tmp_path))
|
||||
widget = BECMainWindow(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
|
||||
action = widget._experimental_features_action
|
||||
assert action.isCheckable()
|
||||
assert action.isChecked() is False
|
||||
|
||||
quit_calls = []
|
||||
monkeypatch.setattr(widget.app, "quit", lambda: quit_calls.append(True))
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes)
|
||||
|
||||
action.setChecked(True)
|
||||
action.setChecked(False)
|
||||
|
||||
meta_path = os.path.join(str(tmp_path), "_meta.ini")
|
||||
assert (
|
||||
QSettings(meta_path, QSettings.Format.IniFormat).value(
|
||||
SETTINGS_KEYS["experimental_features"], type=bool
|
||||
)
|
||||
is False
|
||||
)
|
||||
assert quit_calls == [True, True]
|
||||
|
||||
|
||||
#################################################################
|
||||
# Tests for BECMainWindow Addons
|
||||
#################################################################
|
||||
|
||||
Reference in New Issue
Block a user