Skip to content
Merged
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
20 changes: 11 additions & 9 deletions bec_widgets/applications/main_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
available_screen_geometry,
main_app_size_for_screen,
)
from bec_widgets.widgets.containers.dock_area.profile_utils import is_experimental_features_enabled
Comment thread
d-perl marked this conversation as resolved.
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow


Expand Down Expand Up @@ -63,7 +64,7 @@ def _add_views(self):
self.add_section("BEC Applications", "bec_apps")
self.dock_area = DockAreaView(self)
self.device_manager = DeviceManagerView(self)
# self.developer_view = DeveloperView(self) #TODO temporary disable until the bugs with BECShell are resolved
self.developer_view = None
self.admin_view = AdminView(self)

self.add_view(icon="widgets", title="Dock Area", widget=self.dock_area, mini_text="Docks")
Expand All @@ -73,14 +74,15 @@ def _add_views(self):
widget=self.device_manager,
mini_text="DM",
)
# TODO temporary disable until the bugs with BECShell are resolved
# self.add_view(
# icon="code_blocks",
# title="IDE",
# widget=self.developer_view,
# mini_text="IDE",
# exclusive=True,
# )
if is_experimental_features_enabled():
self.developer_view = DeveloperView(self)
self.add_view(
icon="code_blocks",
title="IDE",
widget=self.developer_view,
mini_text="IDE",
exclusive=True,
)
self.add_view(
icon="admin_panel_settings",
title="Admin View",
Expand Down
25 changes: 25 additions & 0 deletions bec_widgets/widgets/containers/dock_area/profile_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
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.
Expand Down Expand Up @@ -463,6 +465,7 @@ def delete_profile_files(name: str, namespace: str | None = None) -> bool:
"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",
Expand Down Expand Up @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions bec_widgets/widgets/containers/main_window/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
QHBoxLayout,
QLabel,
QMainWindow,
QMessageBox,
QStyle,
QVBoxLayout,
QWidget,
Expand All @@ -22,6 +23,10 @@
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,
Expand Down Expand Up @@ -343,6 +348,12 @@ def _setup_menu_bar(self):
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)
Comment thread
d-perl marked this conversation as resolved.
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()
Expand Down Expand Up @@ -417,6 +428,19 @@ def change_theme(self, theme: str):
"""
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)
Expand Down
16 changes: 16 additions & 0 deletions tests/unit_tests/test_dock_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
SETTINGS_KEYS,
baseline_profile_path,
get_profile_info,
is_experimental_features_enabled,
is_profile_read_only,
is_quick_select,
list_profiles,
Expand All @@ -32,6 +33,7 @@
read_manifest,
restore_runtime_from_baseline,
runtime_profile_path,
set_experimental_features_enabled,
set_quick_select,
write_manifest,
)
Expand Down Expand Up @@ -1443,6 +1445,20 @@ def test_toggle_quick_select_updates_flag(self, qtbot, workspace_manager_target)
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])
Expand Down
19 changes: 19 additions & 0 deletions tests/unit_tests/test_main_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from bec_widgets.applications.main_app import BECMainApp
from bec_widgets.applications.views.view import ViewBase
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.containers.dock_area.profile_utils import (
is_experimental_features_enabled,
set_experimental_features_enabled,
)

from .client_mocks import mocked_client

Expand Down Expand Up @@ -136,6 +140,21 @@ def test_view_content_widget_is_hidden_from_namespace(app_with_spies):
assert app.dock_area.content is app.dock_area.dock_area


def test_developer_view_is_experimental_by_default(monkeypatch, tmp_path, qtbot, mocked_client):
Comment thread
d-perl marked this conversation as resolved.
monkeypatch.setenv("BECWIDGETS_PROFILE_DIR", str(tmp_path))
set_experimental_features_enabled(False)

app = BECMainApp(client=mocked_client, anim_duration=ANIM_TEST_DURATION, show_examples=False)
qtbot.addWidget(app)
app.show()
qtbot.waitExposed(app)

assert is_experimental_features_enabled() is False
assert (
not hasattr(app, "developer_view") or app.sidebar.components.get("developer_view") is None
)


# def test_developer_plotting_area_parent_id_uses_view_namespace(app_with_spies): #TODO temp disabled due to disabled IDE view
# app, _, _, _ = app_with_spies
# plotting_area = app.developer_view.developer_widget.plotting_ads
Expand Down
35 changes: 33 additions & 2 deletions tests/unit_tests/test_main_widnow.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)
Comment thread
d-perl marked this conversation as resolved.

action.setChecked(True)
action.setChecked(False)

meta_path = os.path.join(str(tmp_path), "_meta.ini")
assert (
QSettings(meta_path, QSettings.Format.IniFormat).value(
Comment thread
d-perl marked this conversation as resolved.
SETTINGS_KEYS["experimental_features"], type=bool
)
is False
)
assert quit_calls == [True, True]


#################################################################
# Tests for BECMainWindow Addons
#################################################################
Expand Down
Loading