mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-04 16:02:51 +01:00
fix(CLI): change the default behavior of launching the profiles in CLI
This commit is contained in:
@@ -8,26 +8,38 @@ from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
def dock_area(object_name: str | None = None, profile: str | None = None) -> AdvancedDockArea:
|
||||
def dock_area(
|
||||
object_name: str | None = None, profile: str | None = None, start_empty: bool = False
|
||||
) -> AdvancedDockArea:
|
||||
"""
|
||||
Create an advanced dock area using Qt Advanced Docking System.
|
||||
|
||||
Args:
|
||||
object_name(str): The name of the advanced dock area.
|
||||
profile(str|None): Optional profile to load; if None the last profile is restored.
|
||||
profile(str|None): Optional profile to load; if None the "general" profile is used.
|
||||
start_empty(bool): If True, start with an empty dock area when loading specified profile.
|
||||
|
||||
Returns:
|
||||
AdvancedDockArea: The created advanced dock area.
|
||||
|
||||
Note:
|
||||
The "general" profile is mandatory and will always exist. If manually deleted,
|
||||
it will be automatically recreated.
|
||||
"""
|
||||
# Default to "general" profile when called from CLI without specifying a profile
|
||||
effective_profile = profile if profile is not None else "general"
|
||||
|
||||
widget = AdvancedDockArea(
|
||||
object_name=object_name,
|
||||
restore_initial_profile=(profile is None),
|
||||
restore_initial_profile=True,
|
||||
root_widget=True,
|
||||
profile_namespace="bec",
|
||||
init_profile=effective_profile,
|
||||
start_empty=start_empty,
|
||||
)
|
||||
logger.info(
|
||||
f"Created advanced dock area with profile: {effective_profile}, start_empty: {start_empty}"
|
||||
)
|
||||
if profile:
|
||||
widget.load_profile(profile)
|
||||
logger.info(f"Created advanced dock area with profile: {profile}")
|
||||
return widget
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,10 @@ from bec_widgets.utils.round_frame import RoundedFrame
|
||||
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
|
||||
from bec_widgets.utils.ui_loader import UILoader
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import AdvancedDockArea
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import list_profiles
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
|
||||
get_last_profile,
|
||||
list_profiles,
|
||||
)
|
||||
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
|
||||
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow, BECMainWindowNoRPC
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
@@ -182,7 +185,6 @@ class LaunchTile(RoundedFrame):
|
||||
class LaunchWindow(BECMainWindow):
|
||||
RPC = True
|
||||
TILE_SIZE = (250, 300)
|
||||
DEFAULT_WORKSPACE_OPTION = "Last used workspace"
|
||||
USER_ACCESS = ["show_launcher", "hide_launcher"]
|
||||
|
||||
def __init__(
|
||||
@@ -345,6 +347,7 @@ class LaunchWindow(BECMainWindow):
|
||||
def _refresh_dock_area_profiles(self, preserve_selection: bool = True) -> None:
|
||||
"""
|
||||
Refresh the dock-area profile selector, optionally preserving the selection.
|
||||
Sets the combobox to the last used profile or "general" if no selection preserved.
|
||||
|
||||
Args:
|
||||
preserve_selection(bool): Whether to preserve the current selection or not.
|
||||
@@ -361,19 +364,49 @@ class LaunchWindow(BECMainWindow):
|
||||
profiles = list_profiles("bec")
|
||||
selector.blockSignals(True)
|
||||
selector.clear()
|
||||
selector.addItem(self.DEFAULT_WORKSPACE_OPTION)
|
||||
for profile in profiles:
|
||||
selector.addItem(profile)
|
||||
|
||||
if not selected_text or selected_text == self.DEFAULT_WORKSPACE_OPTION:
|
||||
idx = 0
|
||||
else:
|
||||
if selected_text:
|
||||
# Try to preserve the current selection
|
||||
idx = selector.findText(selected_text, Qt.MatchFlag.MatchExactly)
|
||||
if idx < 0:
|
||||
idx = 0
|
||||
selector.setCurrentIndex(idx)
|
||||
if idx >= 0:
|
||||
selector.setCurrentIndex(idx)
|
||||
else:
|
||||
# Selection no longer exists, fall back to last profile or "general"
|
||||
self._set_selector_to_default_profile(selector, profiles)
|
||||
else:
|
||||
# No selection to preserve, use last profile or "general"
|
||||
self._set_selector_to_default_profile(selector, profiles)
|
||||
selector.blockSignals(False)
|
||||
|
||||
def _set_selector_to_default_profile(self, selector: QComboBox, profiles: list[str]) -> None:
|
||||
"""
|
||||
Set the selector to the last used profile or "general" as fallback.
|
||||
|
||||
Args:
|
||||
selector(QComboBox): The combobox to set.
|
||||
profiles(list[str]): List of available profiles.
|
||||
"""
|
||||
# Try to get last used profile
|
||||
last_profile = get_last_profile(namespace="bec")
|
||||
if last_profile and last_profile in profiles:
|
||||
idx = selector.findText(last_profile, Qt.MatchFlag.MatchExactly)
|
||||
if idx >= 0:
|
||||
selector.setCurrentIndex(idx)
|
||||
return
|
||||
|
||||
# Fall back to "general" profile
|
||||
if "general" in profiles:
|
||||
idx = selector.findText("general", Qt.MatchFlag.MatchExactly)
|
||||
if idx >= 0:
|
||||
selector.setCurrentIndex(idx)
|
||||
return
|
||||
|
||||
# If nothing else, select first item
|
||||
if selector.count() > 0:
|
||||
selector.setCurrentIndex(0)
|
||||
|
||||
def launch(
|
||||
self,
|
||||
launch_script: str,
|
||||
@@ -541,17 +574,14 @@ class LaunchWindow(BECMainWindow):
|
||||
|
||||
def _open_dock_area(self):
|
||||
"""
|
||||
Open Advanced Dock Area using the selected profile (if any).
|
||||
Open Advanced Dock Area using the selected profile.
|
||||
"""
|
||||
tile = self.tiles.get("dock_area")
|
||||
if tile is None or tile.selector is None:
|
||||
profile = None
|
||||
else:
|
||||
selection = tile.selector.currentText().strip()
|
||||
if not selection or selection == self.DEFAULT_WORKSPACE_OPTION:
|
||||
profile = None
|
||||
else:
|
||||
profile = selection
|
||||
profile = selection if selection else None
|
||||
return self.launch("dock_area", profile=profile)
|
||||
|
||||
def _open_widget(self):
|
||||
|
||||
@@ -293,7 +293,7 @@ class AdvancedDockArea(RPCBase):
|
||||
|
||||
@rpc_timeout(None)
|
||||
@rpc_call
|
||||
def load_profile(self, name: "str | None" = None):
|
||||
def load_profile(self, name: "str | None" = None, start_empty: "bool" = False):
|
||||
"""
|
||||
Load a workspace profile.
|
||||
|
||||
@@ -302,6 +302,7 @@ class AdvancedDockArea(RPCBase):
|
||||
|
||||
Args:
|
||||
name (str | None): The name of the profile to load. If None, prompts the user.
|
||||
start_empty (bool): If True, load a profile without any widgets. Danger of overwriting the dynamic state of that profile.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
|
||||
@@ -304,7 +304,7 @@ class BECGuiClient(RPCBase):
|
||||
geometry: tuple[int, int, int, int] | None = None,
|
||||
launch_script: str = "dock_area",
|
||||
profile: str | None = None,
|
||||
empty: bool = False,
|
||||
start_empty: bool = False,
|
||||
**kwargs,
|
||||
) -> client.AdvancedDockArea:
|
||||
"""Create a new top-level dock area.
|
||||
@@ -314,24 +314,24 @@ class BECGuiClient(RPCBase):
|
||||
wait(bool, optional): Whether to wait for the server to start. Defaults to True.
|
||||
geometry(tuple[int, int, int, int] | None): The geometry of the dock area (pos_x, pos_y, w, h).
|
||||
launch_script(str): The launch script to use. Defaults to "dock_area".
|
||||
profile(str | None): The profile name to load. If None, restores the last used profile.
|
||||
profile(str | None): The profile name to load. If None, loads the "general" profile.
|
||||
Use a profile name to load a specific saved profile.
|
||||
empty(bool): If True, start with an empty dock area without loading any profile.
|
||||
This takes precedence over the profile argument. Defaults to False.
|
||||
start_empty(bool): If True, start with an empty dock area when loading specified profile.
|
||||
**kwargs: Additional keyword arguments passed to the dock area.
|
||||
|
||||
Returns:
|
||||
client.AdvancedDockArea: The new dock area.
|
||||
|
||||
Note:
|
||||
The "general" profile is mandatory and will always exist. If manually deleted,
|
||||
it will be automatically recreated.
|
||||
|
||||
Examples:
|
||||
>>> gui.new() # Restore last used profile
|
||||
>>> gui.new(profile="my_profile") # Load specific profile, if profile do not exist, the new profile is created empty with specified name
|
||||
>>> gui.new(empty=True) # Start with empty dock area
|
||||
>>> gui.new(name="custom_dock", empty=True) # Named empty dock area
|
||||
>>> gui.new() # Start with the "general" profile
|
||||
>>> gui.new(profile="my_profile") # Load specific profile, if profile does not exist, the new profile is created empty with specified name
|
||||
>>> gui.new(start_empty=True) # Start with "general" profile but empty dock area
|
||||
>>> gui.new(profile="my_profile", start_empty=True) # Start with "my_profile" profile but empty dock area
|
||||
"""
|
||||
if empty:
|
||||
# Use a unique non-existent profile name to ensure empty start
|
||||
profile = "__empty__"
|
||||
if not self._check_if_server_is_alive():
|
||||
self.start(wait=True)
|
||||
if wait:
|
||||
@@ -342,6 +342,7 @@ class BECGuiClient(RPCBase):
|
||||
name=name,
|
||||
geometry=geometry,
|
||||
profile=profile,
|
||||
start_empty=start_empty,
|
||||
**kwargs,
|
||||
) # pylint: disable=protected-access
|
||||
return widget
|
||||
@@ -351,6 +352,7 @@ class BECGuiClient(RPCBase):
|
||||
name=name,
|
||||
geometry=geometry,
|
||||
profile=profile,
|
||||
start_empty=start_empty,
|
||||
**kwargs,
|
||||
) # pylint: disable=protected-access
|
||||
return widget
|
||||
|
||||
@@ -126,6 +126,8 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
auto_save_upon_exit: bool = True,
|
||||
enable_profile_management: bool = True,
|
||||
restore_initial_profile: bool = True,
|
||||
init_profile: str | None = None,
|
||||
start_empty: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
self._profile_namespace_hint = profile_namespace
|
||||
@@ -135,6 +137,8 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
self._auto_save_upon_exit = auto_save_upon_exit
|
||||
self._profile_management_enabled = enable_profile_management
|
||||
self._restore_initial_profile = restore_initial_profile
|
||||
self._init_profile = init_profile
|
||||
self._start_empty = start_empty
|
||||
super().__init__(
|
||||
parent,
|
||||
default_add_direction=default_add_direction,
|
||||
@@ -184,7 +188,8 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
|
||||
def _ensure_initial_profile(self) -> bool:
|
||||
"""
|
||||
Ensure at least one workspace profile exists for the current namespace.
|
||||
Ensure the "general" workspace profile always exists for the current namespace.
|
||||
The "general" profile is mandatory and will be recreated if deleted.
|
||||
If list_profile fails due to file permission or corrupted profiles, no action taken.
|
||||
|
||||
Returns:
|
||||
@@ -197,12 +202,13 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
logger.warning(f"Unable to enumerate profiles for namespace '{namespace}': {exc}")
|
||||
return False
|
||||
|
||||
if existing_profiles:
|
||||
# Always ensure "general" profile exists
|
||||
name = "general"
|
||||
if name in existing_profiles:
|
||||
return False
|
||||
|
||||
name = "general"
|
||||
logger.info(
|
||||
f"No profiles found for namespace '{namespace}'. Bootstrapping '{name}' workspace."
|
||||
f"Profile '{name}' not found in namespace '{namespace}'. Creating mandatory '{name}' workspace."
|
||||
)
|
||||
|
||||
self._write_profile_settings(name, namespace, save_preview=False)
|
||||
@@ -215,30 +221,37 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
namespace = self.profile_namespace
|
||||
init_profile = None
|
||||
instance_id = self._last_profile_instance_id()
|
||||
if instance_id:
|
||||
inst_profile = get_last_profile(
|
||||
namespace=namespace, instance=instance_id, allow_namespace_fallback=False
|
||||
)
|
||||
if inst_profile and self._profile_exists(inst_profile, namespace):
|
||||
init_profile = inst_profile
|
||||
if not init_profile:
|
||||
last = get_last_profile(namespace=namespace)
|
||||
if last and self._profile_exists(last, namespace):
|
||||
init_profile = last
|
||||
else:
|
||||
text = combo.currentText()
|
||||
init_profile = text if text else None
|
||||
if not init_profile:
|
||||
if self._profile_exists("general", namespace):
|
||||
init_profile = "general"
|
||||
|
||||
# First priority: use init_profile if explicitly provided
|
||||
if self._init_profile:
|
||||
init_profile = self._init_profile
|
||||
else:
|
||||
# Try to restore from last used profile
|
||||
instance_id = self._last_profile_instance_id()
|
||||
if instance_id:
|
||||
inst_profile = get_last_profile(
|
||||
namespace=namespace, instance=instance_id, allow_namespace_fallback=False
|
||||
)
|
||||
if inst_profile and self._profile_exists(inst_profile, namespace):
|
||||
init_profile = inst_profile
|
||||
if not init_profile:
|
||||
last = get_last_profile(namespace=namespace)
|
||||
if last and self._profile_exists(last, namespace):
|
||||
init_profile = last
|
||||
else:
|
||||
text = combo.currentText()
|
||||
init_profile = text if text else None
|
||||
if not init_profile:
|
||||
# Fall back to "general" profile which is guaranteed to exist
|
||||
if self._profile_exists("general", namespace):
|
||||
init_profile = "general"
|
||||
if init_profile:
|
||||
# Defer initial load to the event loop so child widgets exist before state restore.
|
||||
QTimer.singleShot(0, lambda: self._load_initial_profile(init_profile))
|
||||
|
||||
def _load_initial_profile(self, name: str) -> None:
|
||||
"""Load the initial profile after construction when the event loop is running."""
|
||||
self.load_profile(name)
|
||||
self.load_profile(name, start_empty=self._start_empty)
|
||||
combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
combo.blockSignals(True)
|
||||
combo.setCurrentText(name)
|
||||
@@ -807,8 +820,9 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
self.save_profile(name, show_dialog=True)
|
||||
|
||||
@SafeSlot(str)
|
||||
@SafeSlot(str, bool)
|
||||
@rpc_timeout(None)
|
||||
def load_profile(self, name: str | None = None):
|
||||
def load_profile(self, name: str | None = None, start_empty: bool = False):
|
||||
"""
|
||||
Load a workspace profile.
|
||||
|
||||
@@ -817,6 +831,7 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
|
||||
Args:
|
||||
name (str | None): The name of the profile to load. If None, prompts the user.
|
||||
start_empty (bool): If True, load a profile without any widgets. Danger of overwriting the dynamic state of that profile.
|
||||
"""
|
||||
if not name: # Gui fallback if the name is not provided
|
||||
name, ok = QInputDialog.getText(
|
||||
@@ -849,7 +864,7 @@ class AdvancedDockArea(DockAreaWidget):
|
||||
# Clear existing docks and remove all widgets
|
||||
self.delete_all()
|
||||
|
||||
if name == "__empty__":
|
||||
if start_empty:
|
||||
self._finalize_profile_change(name, namespace)
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user