mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-10-29 08:21:27 +01:00
feat(advanced_dock_area): UI/UX for profile management improved, saving directories logic adjusted
This commit is contained in:
1 parent
fc75cd2b0c
commit
178295d8df
8 files changed
+2269
-541
No files matched your search
@@ -4,18 +4,15 @@ import os
|
||||
from typing import Literal, cast
|
||||
|
||||
import PySide6QtAds as QtAds
|
||||
from bec_lib import bec_logger
|
||||
from PySide6QtAds import CDockManager, CDockWidget
|
||||
from qtpy.QtCore import Signal
|
||||
from qtpy.QtCore import QTimer, Signal
|
||||
from qtpy.QtGui import QPixmap
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QDialog,
|
||||
QHBoxLayout,
|
||||
QInputDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@@ -37,14 +34,30 @@ from bec_widgets.utils.toolbars.toolbar import ModularToolBar
|
||||
from bec_widgets.utils.widget_state_manager import WidgetStateManager
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
|
||||
SETTINGS_KEYS,
|
||||
is_profile_readonly,
|
||||
list_profiles,
|
||||
open_settings,
|
||||
profile_path,
|
||||
default_profile_path,
|
||||
get_last_profile,
|
||||
is_quick_select,
|
||||
load_default_profile_screenshot,
|
||||
load_user_profile_screenshot,
|
||||
now_iso_utc,
|
||||
open_default_settings,
|
||||
open_user_settings,
|
||||
profile_origin,
|
||||
profile_origin_display,
|
||||
read_manifest,
|
||||
set_profile_readonly,
|
||||
restore_user_from_default,
|
||||
set_last_profile,
|
||||
set_quick_select,
|
||||
user_profile_path,
|
||||
write_manifest,
|
||||
)
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.settings.dialogs import (
|
||||
RestoreProfileDialog,
|
||||
SaveProfileDialog,
|
||||
)
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.settings.workspace_manager import (
|
||||
WorkSpaceManager,
|
||||
)
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.toolbar_components.workspace_actions import (
|
||||
WorkspaceConnection,
|
||||
workspace_bundle,
|
||||
@@ -65,6 +78,8 @@ from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECStatus
|
||||
from bec_widgets.widgets.utility.logpanel import LogPanel
|
||||
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class DockSettingsDialog(QDialog):
|
||||
|
||||
@@ -79,62 +94,6 @@ class DockSettingsDialog(QDialog):
|
||||
layout.addWidget(self.prop_editor)
|
||||
|
||||
|
||||
class SaveProfileDialog(QDialog):
|
||||
"""Dialog for saving workspace profiles with read-only option."""
|
||||
|
||||
def __init__(self, parent: QWidget, current_name: str = ""):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Save Workspace Profile")
|
||||
self.setModal(True)
|
||||
self.resize(400, 150)
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Name input
|
||||
name_row = QHBoxLayout()
|
||||
name_row.addWidget(QLabel("Profile Name:"))
|
||||
self.name_edit = QLineEdit(current_name)
|
||||
self.name_edit.setPlaceholderText("Enter profile name...")
|
||||
name_row.addWidget(self.name_edit)
|
||||
layout.addLayout(name_row)
|
||||
|
||||
# Read-only checkbox
|
||||
self.readonly_checkbox = QCheckBox("Mark as read-only (cannot be overwritten or deleted)")
|
||||
layout.addWidget(self.readonly_checkbox)
|
||||
|
||||
# Info label
|
||||
info_label = QLabel("Read-only profiles are protected from modification and deletion.")
|
||||
info_label.setStyleSheet("color: gray; font-size: 10px;")
|
||||
layout.addWidget(info_label)
|
||||
|
||||
# Buttons
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.addStretch(1)
|
||||
self.save_btn = QPushButton("Save")
|
||||
self.save_btn.setDefault(True)
|
||||
cancel_btn = QPushButton("Cancel")
|
||||
self.save_btn.clicked.connect(self.accept)
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
btn_row.addWidget(self.save_btn)
|
||||
btn_row.addWidget(cancel_btn)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
# Enable/disable save button based on name input
|
||||
self.name_edit.textChanged.connect(self._update_save_button)
|
||||
self._update_save_button()
|
||||
|
||||
def _update_save_button(self):
|
||||
"""Enable save button only when name is not empty."""
|
||||
self.save_btn.setEnabled(bool(self.name_edit.text().strip()))
|
||||
|
||||
def get_profile_name(self) -> str:
|
||||
"""Get the entered profile name."""
|
||||
return self.name_edit.text().strip()
|
||||
|
||||
def is_readonly(self) -> bool:
|
||||
"""Check if the profile should be marked as read-only."""
|
||||
return self.readonly_checkbox.isChecked()
|
||||
|
||||
|
||||
class AdvancedDockArea(BECWidget, QWidget):
|
||||
RPC = True
|
||||
PLUGIN = False
|
||||
@@ -151,6 +110,7 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
|
||||
# Define a signal for mode changes
|
||||
mode_changed = Signal(str)
|
||||
profile_changed = Signal(str)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -190,12 +150,18 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
self._setup_toolbar()
|
||||
self._hook_toolbar()
|
||||
|
||||
# Popups
|
||||
self.save_dialog = None
|
||||
self.manage_dialog = None
|
||||
|
||||
# Place toolbar and dock manager into layout
|
||||
self._root_layout.addWidget(self.toolbar)
|
||||
self._root_layout.addWidget(self.dock_manager, 1)
|
||||
|
||||
# Populate and hook the workspace combo
|
||||
self._refresh_workspace_list()
|
||||
self._current_profile_name = None
|
||||
self._pending_autosave_skip: tuple[str, str] | None = None
|
||||
|
||||
# State manager
|
||||
self.state_manager = WidgetStateManager(self)
|
||||
@@ -211,6 +177,23 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
|
||||
# Apply the requested mode after everything is set up
|
||||
self.mode = mode
|
||||
QTimer.singleShot(
|
||||
0, self._fetch_initial_profile
|
||||
) # To allow full init before loading profile and prevent segfault on exit
|
||||
|
||||
def _fetch_initial_profile(self):
|
||||
# Restore last-used profile if available; otherwise fall back to combo selection
|
||||
combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
last = get_last_profile()
|
||||
if last and (
|
||||
os.path.exists(user_profile_path(last)) or os.path.exists(default_profile_path(last))
|
||||
):
|
||||
init_profile = last
|
||||
else:
|
||||
init_profile = combo.currentText()
|
||||
if init_profile:
|
||||
self.load_profile(init_profile)
|
||||
combo.setCurrentText(init_profile)
|
||||
|
||||
def _make_dock(
|
||||
self,
|
||||
@@ -494,11 +477,6 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
self.lock_workspace = not editable
|
||||
self._editable = editable
|
||||
|
||||
# Sync the toolbar lock toggle with current mode
|
||||
lock_action = self.toolbar.components.get_action("lock").action
|
||||
lock_action.setChecked(not editable)
|
||||
lock_action.setVisible(editable)
|
||||
|
||||
attach_all_action = self.toolbar.components.get_action("attach_all").action
|
||||
attach_all_action.setVisible(editable)
|
||||
|
||||
@@ -689,63 +667,64 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
self._locked = value
|
||||
self._apply_dock_lock(value)
|
||||
self.toolbar.components.get_action("save_workspace").action.setVisible(not value)
|
||||
self.toolbar.components.get_action("delete_workspace").action.setVisible(not value)
|
||||
for dock in self.dock_list():
|
||||
dock.setting_action.setVisible(not value)
|
||||
|
||||
def _write_snapshot_to_settings(self, settings, save_preview: bool = True) -> None:
|
||||
settings.setValue(SETTINGS_KEYS["geom"], self.saveGeometry())
|
||||
settings.setValue(SETTINGS_KEYS["state"], b"")
|
||||
settings.setValue(SETTINGS_KEYS["ads_state"], self.dock_manager.saveState())
|
||||
self.dock_manager.addPerspective(self.windowTitle())
|
||||
self.dock_manager.savePerspectives(settings)
|
||||
self.state_manager.save_state(settings=settings)
|
||||
write_manifest(settings, self.dock_list())
|
||||
if save_preview:
|
||||
ba = self.screenshot_bytes()
|
||||
if ba and len(ba) > 0:
|
||||
settings.setValue(SETTINGS_KEYS["screenshot"], ba)
|
||||
settings.setValue(SETTINGS_KEYS["screenshot_at"], now_iso_utc())
|
||||
|
||||
logger.info(f"Workspace snapshot written to settings: {settings.fileName()}")
|
||||
|
||||
@SafeSlot(str)
|
||||
def save_profile(self, name: str | None = None):
|
||||
"""
|
||||
Save the current workspace profile.
|
||||
|
||||
On first save of a given name:
|
||||
- writes a default copy to states/default/<name>.ini with tag=default and created_at
|
||||
- writes a user copy to states/user/<name>.ini with tag=user and created_at
|
||||
On subsequent saves of user-owned profiles:
|
||||
- updates both the default and user copies so restore uses the latest snapshot.
|
||||
Read-only bundled profiles cannot be overwritten.
|
||||
|
||||
Args:
|
||||
name (str | None): The name of the profile. If None, a dialog will prompt for a name.
|
||||
name (str | None): The name of the profile to save. If None, prompts the user.
|
||||
"""
|
||||
if not name:
|
||||
# Use the new SaveProfileDialog instead of QInputDialog
|
||||
dialog = SaveProfileDialog(self)
|
||||
if dialog.exec() != QDialog.Accepted:
|
||||
return
|
||||
name = dialog.get_profile_name()
|
||||
readonly = dialog.is_readonly()
|
||||
|
||||
# Check if profile already exists and is read-only
|
||||
if os.path.exists(profile_path(name)) and is_profile_readonly(name):
|
||||
suggested_name = f"{name}_custom"
|
||||
reply = QMessageBox.warning(
|
||||
self,
|
||||
"Read-only Profile",
|
||||
f"The profile '{name}' is marked as read-only and cannot be overwritten.\n\n"
|
||||
f"Would you like to save it with a different name?\n"
|
||||
f"Suggested name: '{suggested_name}'",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes,
|
||||
)
|
||||
if reply == QMessageBox.Yes:
|
||||
# Show dialog again with suggested name pre-filled
|
||||
dialog = SaveProfileDialog(self, suggested_name)
|
||||
if dialog.exec() != QDialog.Accepted:
|
||||
return
|
||||
name = dialog.get_profile_name()
|
||||
readonly = dialog.is_readonly()
|
||||
def _profile_exists(profile_name: str) -> bool:
|
||||
return profile_origin(profile_name) != "unknown"
|
||||
|
||||
# Check again if the new name is also read-only (recursive protection)
|
||||
if os.path.exists(profile_path(name)) and is_profile_readonly(name):
|
||||
return self.save_profile()
|
||||
else:
|
||||
return
|
||||
else:
|
||||
# If name is provided directly, assume not read-only unless already exists
|
||||
readonly = False
|
||||
if os.path.exists(profile_path(name)) and is_profile_readonly(name):
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Read-only Profile",
|
||||
f"The profile '{name}' is marked as read-only and cannot be overwritten.",
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
return
|
||||
initial_name = name or ""
|
||||
quickselect_default = is_quick_select(name) if name else False
|
||||
|
||||
current_profile = getattr(self, "_current_profile_name", "") or ""
|
||||
dialog = SaveProfileDialog(
|
||||
self,
|
||||
current_name=initial_name,
|
||||
current_profile_name=current_profile,
|
||||
name_exists=_profile_exists,
|
||||
profile_origin=profile_origin,
|
||||
origin_label=profile_origin_display,
|
||||
quick_select_checked=quickselect_default,
|
||||
)
|
||||
if dialog.exec() != QDialog.Accepted:
|
||||
return
|
||||
|
||||
name = dialog.get_profile_name()
|
||||
quickselect = dialog.is_quick_select()
|
||||
origin_before_save = profile_origin(name)
|
||||
overwrite_default = dialog.overwrite_existing and origin_before_save == "settings"
|
||||
# Display saving placeholder
|
||||
workspace_combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
workspace_combo.blockSignals(True)
|
||||
@@ -753,42 +732,75 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
workspace_combo.setCurrentIndex(0)
|
||||
workspace_combo.blockSignals(False)
|
||||
|
||||
# Save the profile
|
||||
settings = open_settings(name)
|
||||
settings.setValue(SETTINGS_KEYS["geom"], self.saveGeometry())
|
||||
settings.setValue(
|
||||
SETTINGS_KEYS["state"], b""
|
||||
) # No QMainWindow state; placeholder for backward compat
|
||||
settings.setValue(SETTINGS_KEYS["ads_state"], self.dock_manager.saveState())
|
||||
self.dock_manager.addPerspective(name)
|
||||
self.dock_manager.savePerspectives(settings)
|
||||
self.state_manager.save_state(settings=settings)
|
||||
write_manifest(settings, self.dock_list())
|
||||
# Create or update default copy controlled by overwrite flag
|
||||
should_write_default = overwrite_default or not os.path.exists(default_profile_path(name))
|
||||
if should_write_default:
|
||||
ds = open_default_settings(name)
|
||||
self._write_snapshot_to_settings(ds)
|
||||
if not ds.value(SETTINGS_KEYS["created_at"], ""):
|
||||
ds.setValue(SETTINGS_KEYS["created_at"], now_iso_utc())
|
||||
# Ensure new profiles are not quick-select by default
|
||||
if not ds.value(SETTINGS_KEYS["is_quick_select"], None):
|
||||
ds.setValue(SETTINGS_KEYS["is_quick_select"], False)
|
||||
|
||||
# Set read-only status if specified
|
||||
if readonly:
|
||||
set_profile_readonly(name, readonly)
|
||||
# Always (over)write the user copy
|
||||
us = open_user_settings(name)
|
||||
self._write_snapshot_to_settings(us)
|
||||
if not us.value(SETTINGS_KEYS["created_at"], ""):
|
||||
us.setValue(SETTINGS_KEYS["created_at"], now_iso_utc())
|
||||
# Ensure new profiles are not quick-select by default (only if missing)
|
||||
if not us.value(SETTINGS_KEYS["is_quick_select"], None):
|
||||
us.setValue(SETTINGS_KEYS["is_quick_select"], False)
|
||||
|
||||
# set quick select
|
||||
if quickselect:
|
||||
set_quick_select(name, quickselect)
|
||||
|
||||
settings.sync()
|
||||
self._refresh_workspace_list()
|
||||
if current_profile and current_profile != name and not dialog.overwrite_existing:
|
||||
self._pending_autosave_skip = (current_profile, name)
|
||||
else:
|
||||
self._pending_autosave_skip = None
|
||||
workspace_combo.setCurrentText(name)
|
||||
self._current_profile_name = name
|
||||
self.profile_changed.emit(name)
|
||||
set_last_profile(name)
|
||||
combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
combo.refresh_profiles(active_profile=name)
|
||||
|
||||
def load_profile(self, name: str | None = None):
|
||||
"""
|
||||
Load a workspace profile.
|
||||
|
||||
Args:
|
||||
name (str | None): The name of the profile. If None, a dialog will prompt for a name.
|
||||
Before switching, persist the current profile to the user copy.
|
||||
Prefer loading the user copy; fall back to the default copy.
|
||||
"""
|
||||
# FIXME this has to be tweaked
|
||||
if not name:
|
||||
if not name: # Gui fallback if the name is not provided
|
||||
name, ok = QInputDialog.getText(
|
||||
self, "Load Workspace", "Enter the name of the workspace profile to load:"
|
||||
)
|
||||
if not ok or not name:
|
||||
return
|
||||
settings = open_settings(name)
|
||||
|
||||
prev_name = getattr(self, "_current_profile_name", None)
|
||||
skip_pair = getattr(self, "_pending_autosave_skip", None)
|
||||
if prev_name and prev_name != name:
|
||||
if skip_pair and skip_pair == (prev_name, name):
|
||||
self._pending_autosave_skip = None
|
||||
else:
|
||||
us_prev = open_user_settings(prev_name)
|
||||
self._write_snapshot_to_settings(us_prev, save_preview=False)
|
||||
|
||||
# Choose source settings: user first, else default
|
||||
if os.path.exists(user_profile_path(name)):
|
||||
settings = open_user_settings(name)
|
||||
elif os.path.exists(default_profile_path(name)):
|
||||
settings = open_default_settings(name)
|
||||
else:
|
||||
QMessageBox.warning(self, "Profile not found", f"Profile '{name}' not found.")
|
||||
return
|
||||
|
||||
# Rebuild widgets and restore states
|
||||
for item in read_manifest(settings):
|
||||
obj_name = item["object_name"]
|
||||
widget_class = item["widget_class"]
|
||||
@@ -806,8 +818,6 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
geom = settings.value(SETTINGS_KEYS["geom"])
|
||||
if geom:
|
||||
self.restoreGeometry(geom)
|
||||
# No window state for QWidget-based host; keep for backwards compat read
|
||||
# window_state = settings.value(SETTINGS_KEYS["state"]) # ignored
|
||||
dock_state = settings.value(SETTINGS_KEYS["ads_state"])
|
||||
if dock_state:
|
||||
self.dock_manager.restoreState(dock_state)
|
||||
@@ -815,6 +825,42 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
self.state_manager.load_state(settings=settings)
|
||||
self._set_editable(self._editable)
|
||||
|
||||
self._current_profile_name = name
|
||||
self.profile_changed.emit(name)
|
||||
set_last_profile(name)
|
||||
combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
combo.refresh_profiles(active_profile=name)
|
||||
|
||||
@SafeSlot()
|
||||
@SafeSlot(str)
|
||||
def restore_user_profile_from_default(self, name: str | None = None):
|
||||
"""
|
||||
Overwrite the user copy of *name* with the default 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.
|
||||
"""
|
||||
target = name or getattr(self, "_current_profile_name", None)
|
||||
if not target:
|
||||
return
|
||||
|
||||
current_pixmap = None
|
||||
if self.isVisible():
|
||||
current_pixmap = QPixmap()
|
||||
ba = bytes(self.screenshot_bytes())
|
||||
current_pixmap.loadFromData(ba)
|
||||
if current_pixmap is None or current_pixmap.isNull():
|
||||
current_pixmap = load_user_profile_screenshot(target)
|
||||
default_pixmap = load_default_profile_screenshot(target)
|
||||
|
||||
if not RestoreProfileDialog.confirm(self, current_pixmap, default_pixmap):
|
||||
return
|
||||
|
||||
restore_user_from_default(target)
|
||||
self.delete_all()
|
||||
self.load_profile(target)
|
||||
|
||||
@SafeSlot()
|
||||
def delete_profile(self):
|
||||
"""
|
||||
@@ -825,17 +871,6 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
if not name:
|
||||
return
|
||||
|
||||
# Check if profile is read-only
|
||||
if is_profile_readonly(name):
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Read-only Profile",
|
||||
f"The profile '{name}' is marked as read-only and cannot be deleted.\n\n"
|
||||
f"Read-only profiles are protected from modification and deletion.",
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
return
|
||||
|
||||
# Confirm deletion for regular profiles
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
@@ -848,7 +883,7 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
file_path = profile_path(name)
|
||||
file_path = user_profile_path(name)
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except FileNotFoundError:
|
||||
@@ -860,15 +895,69 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
Populate the workspace combo box with all saved profile names (without .ini).
|
||||
"""
|
||||
combo = self.toolbar.components.get_action("workspace_combo").widget
|
||||
active_profile = getattr(self, "_current_profile_name", None)
|
||||
if hasattr(combo, "refresh_profiles"):
|
||||
combo.refresh_profiles()
|
||||
combo.refresh_profiles(active_profile)
|
||||
else:
|
||||
# Fallback for regular QComboBox
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
|
||||
list_quick_profiles,
|
||||
)
|
||||
|
||||
combo.blockSignals(True)
|
||||
combo.clear()
|
||||
combo.addItems(list_profiles())
|
||||
quick_profiles = list_quick_profiles()
|
||||
items = list(quick_profiles)
|
||||
if active_profile and active_profile not in items:
|
||||
items.insert(0, active_profile)
|
||||
combo.addItems(items)
|
||||
if active_profile:
|
||||
idx = combo.findText(active_profile)
|
||||
if idx >= 0:
|
||||
combo.setCurrentIndex(idx)
|
||||
if active_profile and active_profile not in quick_profiles:
|
||||
combo.setToolTip("Active profile is not in quick select")
|
||||
else:
|
||||
combo.setToolTip("")
|
||||
combo.blockSignals(False)
|
||||
|
||||
################################################################################
|
||||
# Dialog Popups
|
||||
################################################################################
|
||||
|
||||
@SafeSlot()
|
||||
def show_workspace_manager(self):
|
||||
"""
|
||||
Show the workspace manager dialog.
|
||||
"""
|
||||
manage_action = self.toolbar.components.get_action("manage_workspaces").action
|
||||
if self.manage_dialog is None or not self.manage_dialog.isVisible():
|
||||
self.manage_widget = WorkSpaceManager(
|
||||
self, target_widget=self, default_profile=self._current_profile_name
|
||||
)
|
||||
self.manage_dialog = QDialog(modal=False)
|
||||
|
||||
self.manage_dialog.setWindowTitle("Workspace Manager")
|
||||
self.manage_dialog.setMinimumSize(1200, 500)
|
||||
self.manage_dialog.layout = QVBoxLayout(self.manage_dialog)
|
||||
self.manage_dialog.layout.addWidget(self.manage_widget)
|
||||
self.manage_dialog.finished.connect(self._manage_dialog_closed)
|
||||
self.manage_dialog.show()
|
||||
self.manage_dialog.resize(300, 300)
|
||||
manage_action.setChecked(True)
|
||||
else:
|
||||
# If already open, bring it to the front
|
||||
self.manage_dialog.raise_()
|
||||
self.manage_dialog.activateWindow()
|
||||
manage_action.setChecked(True) # keep it toggle
|
||||
|
||||
def _manage_dialog_closed(self):
|
||||
self.manage_widget.close()
|
||||
self.manage_widget.deleteLater()
|
||||
self.manage_dialog.deleteLater()
|
||||
self.manage_dialog = None
|
||||
self.toolbar.components.get_action("manage_workspaces").action.setChecked(False)
|
||||
|
||||
################################################################################
|
||||
# Mode Switching
|
||||
################################################################################
|
||||
@@ -913,6 +1002,15 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
"""
|
||||
Cleanup the dock area.
|
||||
"""
|
||||
# before cleanup save current profile (user copy)
|
||||
name = getattr(self, "_current_profile_name", None)
|
||||
if name:
|
||||
us = open_user_settings(name)
|
||||
self._write_snapshot_to_settings(us)
|
||||
set_last_profile(name)
|
||||
if self.manage_dialog is not None:
|
||||
self.manage_dialog.reject()
|
||||
self.manage_dialog = None
|
||||
self.delete_all()
|
||||
self.dark_mode_button.close()
|
||||
self.dark_mode_button.deleteLater()
|
||||
@@ -920,7 +1018,7 @@ class AdvancedDockArea(BECWidget, QWidget):
|
||||
super().cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
@@ -1,21 +1,181 @@
|
||||
import os
|
||||
"""
|
||||
Utilities for managing AdvancedDockArea profiles stored in INI files.
|
||||
|
||||
Policy:
|
||||
- All created/modified profiles are stored under the BEC settings root: <base_path>/profiles/{default,user}
|
||||
- Bundled read-only defaults are discovered in BW core states/default and plugin bec_widgets/profiles but never written to.
|
||||
- Lookup order when reading: user → settings default → app or plugin bundled default.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from bec_lib.client import BECClient
|
||||
from bec_lib.plugin_helper import plugin_package_name, plugin_repo_path
|
||||
from pydantic import BaseModel, Field
|
||||
from PySide6QtAds import CDockWidget
|
||||
from qtpy.QtCore import QSettings
|
||||
from qtpy.QtCore import QByteArray, QDateTime, QSettings, Qt
|
||||
from qtpy.QtGui import QPixmap
|
||||
|
||||
MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
_DEFAULT_PROFILES_DIR = os.path.join(os.path.dirname(__file__), "states", "default")
|
||||
_USER_PROFILES_DIR = os.path.join(os.path.dirname(__file__), "states", "user")
|
||||
|
||||
ProfileOrigin = Literal["module", "plugin", "settings", "unknown"]
|
||||
|
||||
|
||||
def profiles_dir() -> str:
|
||||
path = os.environ.get("BECWIDGETS_PROFILE_DIR", _USER_PROFILES_DIR)
|
||||
def module_profiles_dir() -> str:
|
||||
"""Return the read-only module-bundled profiles directory (no writes here)."""
|
||||
return os.path.join(MODULE_PATH, "containers", "advanced_dock_area", "profiles")
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _plugin_repo_root() -> Path | None:
|
||||
try:
|
||||
return Path(plugin_repo_path())
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _plugin_display_name() -> str | None:
|
||||
repo_root = _plugin_repo_root()
|
||||
if not repo_root:
|
||||
return None
|
||||
repo_name = repo_root.name
|
||||
if repo_name:
|
||||
return repo_name
|
||||
try:
|
||||
pkg = plugin_package_name()
|
||||
except ValueError:
|
||||
return None
|
||||
return pkg.split(".")[0] if pkg else None
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def plugin_profiles_dir() -> str | None:
|
||||
"""Return the read-only plugin-bundled profiles directory if available."""
|
||||
repo_root = _plugin_repo_root()
|
||||
if not repo_root:
|
||||
return None
|
||||
|
||||
candidates = [repo_root.joinpath("bec_widgets", "profiles")]
|
||||
try:
|
||||
package_root = repo_root.joinpath(*plugin_package_name().split("."))
|
||||
candidates.append(package_root.joinpath("bec_widgets", "profiles"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
for candidate in candidates:
|
||||
if candidate.is_dir():
|
||||
return str(candidate)
|
||||
return None
|
||||
|
||||
|
||||
def _settings_profiles_root() -> str:
|
||||
"""Return the writable profiles root provided by BEC client (or env fallback)."""
|
||||
client = BECClient()
|
||||
bec_widgets_settings = client._service_config.config.get("bec_widgets_settings")
|
||||
bec_widgets_setting_path = (
|
||||
bec_widgets_settings.get("base_path") if bec_widgets_settings else None
|
||||
)
|
||||
default_path = os.path.join(bec_widgets_setting_path, "profiles")
|
||||
root = os.environ.get("BECWIDGETS_PROFILE_DIR", default_path)
|
||||
os.makedirs(root, exist_ok=True)
|
||||
return root
|
||||
|
||||
|
||||
def default_profiles_dir() -> str:
|
||||
path = os.path.join(_settings_profiles_root(), "default")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
def profile_path(name: str) -> str:
|
||||
return os.path.join(profiles_dir(), f"{name}.ini")
|
||||
def user_profiles_dir() -> str:
|
||||
path = os.path.join(_settings_profiles_root(), "user")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
def default_profile_path(name: str) -> str:
|
||||
return os.path.join(default_profiles_dir(), f"{name}.ini")
|
||||
|
||||
|
||||
def user_profile_path(name: str) -> str:
|
||||
return os.path.join(user_profiles_dir(), f"{name}.ini")
|
||||
|
||||
|
||||
def module_profile_path(name: str) -> str:
|
||||
return os.path.join(module_profiles_dir(), f"{name}.ini")
|
||||
|
||||
|
||||
def plugin_profile_path(name: str) -> str | None:
|
||||
directory = plugin_profiles_dir()
|
||||
if not directory:
|
||||
return None
|
||||
return os.path.join(directory, f"{name}.ini")
|
||||
|
||||
|
||||
def profile_origin(name: str) -> ProfileOrigin:
|
||||
"""
|
||||
Determine where a profile originates from.
|
||||
|
||||
Returns:
|
||||
ProfileOrigin: "module" for bundled BEC profiles, "plugin" for beamline plugin bundles,
|
||||
"settings" for user-defined ones, and "unknown" if no backing files are found.
|
||||
"""
|
||||
if os.path.exists(module_profile_path(name)):
|
||||
return "module"
|
||||
plugin_path = plugin_profile_path(name)
|
||||
if plugin_path and os.path.exists(plugin_path):
|
||||
return "plugin"
|
||||
if os.path.exists(user_profile_path(name)) or os.path.exists(default_profile_path(name)):
|
||||
return "settings"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def is_profile_read_only(name: str) -> bool:
|
||||
"""Return True when the profile originates from bundled module or plugin directories."""
|
||||
return profile_origin(name) in {"module", "plugin"}
|
||||
|
||||
|
||||
def profile_origin_display(name: str) -> str | None:
|
||||
"""Return a human-readable label for the profile's origin."""
|
||||
origin = profile_origin(name)
|
||||
if origin == "module":
|
||||
return "BEC Widgets"
|
||||
if origin == "plugin":
|
||||
return _plugin_display_name()
|
||||
if origin == "settings":
|
||||
return "User"
|
||||
return None
|
||||
|
||||
|
||||
def delete_profile_files(name: str) -> bool:
|
||||
"""
|
||||
Delete the profile files from the writable settings directories.
|
||||
|
||||
Removes both the user and default copies (if they exist) and clears the last profile
|
||||
metadata when applicable. Returns True when at least one file was removed.
|
||||
"""
|
||||
if is_profile_read_only(name):
|
||||
return False
|
||||
|
||||
removed = False
|
||||
for path in {user_profile_path(name), default_profile_path(name)}:
|
||||
try:
|
||||
os.remove(path)
|
||||
removed = True
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
if removed and get_last_profile() == name:
|
||||
set_last_profile(None)
|
||||
|
||||
return removed
|
||||
|
||||
|
||||
SETTINGS_KEYS = {
|
||||
@@ -23,29 +183,90 @@ SETTINGS_KEYS = {
|
||||
"state": "mainWindow/State",
|
||||
"ads_state": "mainWindow/DockingState",
|
||||
"manifest": "manifest/widgets",
|
||||
"readonly": "profile/readonly",
|
||||
"created_at": "profile/created_at",
|
||||
"is_quick_select": "profile/quick_select",
|
||||
"screenshot": "profile/screenshot",
|
||||
"screenshot_at": "profile/screenshot_at",
|
||||
"last_profile": "app/last_profile",
|
||||
}
|
||||
|
||||
|
||||
def list_profiles() -> list[str]:
|
||||
return sorted(os.path.splitext(f)[0] for f in os.listdir(profiles_dir()) if f.endswith(".ini"))
|
||||
# Collect profiles from writable settings (default + user)
|
||||
defaults = {
|
||||
os.path.splitext(f)[0] for f in os.listdir(default_profiles_dir()) if f.endswith(".ini")
|
||||
}
|
||||
users = {os.path.splitext(f)[0] for f in os.listdir(user_profiles_dir()) if f.endswith(".ini")}
|
||||
|
||||
# Also consider read-only defaults from core module and beamline plugin repositories
|
||||
read_only_sources: dict[str, tuple[str, str]] = {}
|
||||
sources: list[tuple[str, str | None]] = [
|
||||
("module", module_profiles_dir()),
|
||||
("plugin", plugin_profiles_dir()),
|
||||
]
|
||||
for origin, directory in sources:
|
||||
if not directory or not os.path.isdir(directory):
|
||||
continue
|
||||
for filename in os.listdir(directory):
|
||||
if not filename.endswith(".ini"):
|
||||
continue
|
||||
name, _ = os.path.splitext(filename)
|
||||
read_only_sources.setdefault(name, (origin, os.path.join(directory, filename)))
|
||||
|
||||
for name, (_origin, src) in sorted(read_only_sources.items()):
|
||||
# Ensure a copy in the settings default directory so existing code paths work unchanged
|
||||
dst_default = default_profile_path(name)
|
||||
if not os.path.exists(dst_default):
|
||||
os.makedirs(os.path.dirname(dst_default), exist_ok=True)
|
||||
shutil.copyfile(src, dst_default)
|
||||
# Ensure a user copy exists to allow edits in the writable settings area
|
||||
dst_user = user_profile_path(name)
|
||||
if not os.path.exists(dst_user):
|
||||
os.makedirs(os.path.dirname(dst_user), exist_ok=True)
|
||||
shutil.copyfile(src, dst_user)
|
||||
# Minimal metadata touch-up to align with existing expectations
|
||||
s = open_user_settings(name)
|
||||
if not s.value(SETTINGS_KEYS["created_at"], ""):
|
||||
s.setValue(SETTINGS_KEYS["created_at"], now_iso_utc())
|
||||
|
||||
defaults |= set(read_only_sources.keys())
|
||||
users |= set(read_only_sources.keys())
|
||||
|
||||
# Return union of all discovered names
|
||||
return sorted(defaults | users)
|
||||
|
||||
|
||||
def is_profile_readonly(name: str) -> bool:
|
||||
"""Check if a profile is marked as read-only."""
|
||||
settings = open_settings(name)
|
||||
return settings.value(SETTINGS_KEYS["readonly"], False, type=bool)
|
||||
def open_default_settings(name: str) -> QSettings:
|
||||
return QSettings(default_profile_path(name), QSettings.IniFormat)
|
||||
|
||||
|
||||
def set_profile_readonly(name: str, readonly: bool) -> None:
|
||||
"""Set the read-only status of a profile."""
|
||||
settings = open_settings(name)
|
||||
settings.setValue(SETTINGS_KEYS["readonly"], readonly)
|
||||
settings.sync()
|
||||
def open_user_settings(name: str) -> QSettings:
|
||||
return QSettings(user_profile_path(name), QSettings.IniFormat)
|
||||
|
||||
|
||||
def open_settings(name: str) -> QSettings:
|
||||
return QSettings(profile_path(name), QSettings.IniFormat)
|
||||
def _app_settings() -> QSettings:
|
||||
"""Return app-wide settings file for AdvancedDockArea metadata."""
|
||||
return QSettings(os.path.join(_settings_profiles_root(), "_meta.ini"), QSettings.IniFormat)
|
||||
|
||||
|
||||
def get_last_profile() -> str | None:
|
||||
"""Return the last-used profile name if stored, else None."""
|
||||
s = _app_settings()
|
||||
name = s.value(SETTINGS_KEYS["last_profile"], "", type=str)
|
||||
return name or None
|
||||
|
||||
|
||||
def set_last_profile(name: str | None) -> None:
|
||||
"""Persist the last-used profile name (or clear it if None)."""
|
||||
s = _app_settings()
|
||||
if name:
|
||||
s.setValue(SETTINGS_KEYS["last_profile"], name)
|
||||
else:
|
||||
s.remove(SETTINGS_KEYS["last_profile"])
|
||||
|
||||
|
||||
def now_iso_utc() -> str:
|
||||
return QDateTime.currentDateTimeUtc().toString(Qt.ISODate)
|
||||
|
||||
|
||||
def write_manifest(settings: QSettings, docks: list[CDockWidget]) -> None:
|
||||
@@ -77,3 +298,197 @@ def read_manifest(settings: QSettings) -> list[dict]:
|
||||
)
|
||||
settings.endArray()
|
||||
return items
|
||||
|
||||
|
||||
def restore_user_from_default(name: str) -> None:
|
||||
"""Overwrite the user profile with the default baseline (keep default intact)."""
|
||||
src = default_profile_path(name)
|
||||
dst = user_profile_path(name)
|
||||
if not os.path.exists(src):
|
||||
return
|
||||
preserve_quick_select = is_quick_select(name)
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
shutil.copyfile(src, dst)
|
||||
s = open_user_settings(name)
|
||||
if not s.value(SETTINGS_KEYS["created_at"], ""):
|
||||
s.setValue(SETTINGS_KEYS["created_at"], now_iso_utc())
|
||||
if preserve_quick_select:
|
||||
s.setValue(SETTINGS_KEYS["is_quick_select"], True)
|
||||
|
||||
|
||||
def is_quick_select(name: str) -> bool:
|
||||
"""Return True if profile is marked to appear in quick-select combo."""
|
||||
s = (
|
||||
open_user_settings(name)
|
||||
if os.path.exists(user_profile_path(name))
|
||||
else (open_default_settings(name) if os.path.exists(default_profile_path(name)) else None)
|
||||
)
|
||||
if s is None:
|
||||
return False
|
||||
return s.value(SETTINGS_KEYS["is_quick_select"], False, type=bool)
|
||||
|
||||
|
||||
def set_quick_select(name: str, enabled: bool) -> None:
|
||||
"""Set/unset the quick-select flag on the USER copy (creates it if missing)."""
|
||||
s = open_user_settings(name)
|
||||
s.setValue(SETTINGS_KEYS["is_quick_select"], bool(enabled))
|
||||
|
||||
|
||||
def list_quick_profiles() -> list[str]:
|
||||
"""List only profiles that have quick-select enabled (user wins over default)."""
|
||||
names = list_profiles()
|
||||
return [n for n in names if is_quick_select(n)]
|
||||
|
||||
|
||||
def _file_modified_iso(path: str) -> str:
|
||||
try:
|
||||
mtime = os.path.getmtime(path)
|
||||
return QDateTime.fromSecsSinceEpoch(int(mtime), Qt.UTC).toString(Qt.ISODate)
|
||||
except Exception:
|
||||
return now_iso_utc()
|
||||
|
||||
|
||||
def _manifest_count(settings: QSettings) -> int:
|
||||
n = settings.beginReadArray(SETTINGS_KEYS["manifest"])
|
||||
settings.endArray()
|
||||
return int(n or 0)
|
||||
|
||||
|
||||
def _load_screenshot_from_settings(settings: QSettings) -> QPixmap | None:
|
||||
data = settings.value(SETTINGS_KEYS["screenshot"], None)
|
||||
if not data:
|
||||
return None
|
||||
|
||||
buf = None
|
||||
if isinstance(data, QByteArray):
|
||||
buf = data
|
||||
elif isinstance(data, (bytes, bytearray, memoryview)):
|
||||
buf = bytes(data)
|
||||
elif isinstance(data, str):
|
||||
try:
|
||||
buf = QByteArray(data.encode("latin-1"))
|
||||
except Exception:
|
||||
buf = None
|
||||
|
||||
if buf is None:
|
||||
return None
|
||||
|
||||
pm = QPixmap()
|
||||
ok = pm.loadFromData(buf)
|
||||
return pm if ok and not pm.isNull() else None
|
||||
|
||||
|
||||
class ProfileInfo(BaseModel):
|
||||
name: str
|
||||
author: str = "BEC Widgets"
|
||||
notes: str = ""
|
||||
created: str = Field(default_factory=now_iso_utc)
|
||||
modified: str = Field(default_factory=now_iso_utc)
|
||||
is_quick_select: bool = False
|
||||
widget_count: int = 0
|
||||
size_kb: int = 0
|
||||
user_path: str = ""
|
||||
default_path: str = ""
|
||||
origin: ProfileOrigin = "unknown"
|
||||
is_read_only: bool = False
|
||||
|
||||
|
||||
def get_profile_info(name: str) -> ProfileInfo:
|
||||
"""
|
||||
Return merged metadata for a profile as a validated Pydantic model.
|
||||
Prefers the USER copy; falls back to DEFAULT if the user copy is missing.
|
||||
"""
|
||||
u_path = user_profile_path(name)
|
||||
d_path = default_profile_path(name)
|
||||
origin = profile_origin(name)
|
||||
prefer_user = os.path.exists(u_path)
|
||||
read_only = origin in {"module", "plugin"}
|
||||
s = (
|
||||
open_user_settings(name)
|
||||
if prefer_user
|
||||
else (open_default_settings(name) if os.path.exists(d_path) else None)
|
||||
)
|
||||
if s is None:
|
||||
if origin == "module":
|
||||
author = "BEC Widgets"
|
||||
elif origin == "plugin":
|
||||
author = _plugin_display_name() or "Plugin"
|
||||
elif origin == "settings":
|
||||
author = "User"
|
||||
else:
|
||||
author = ""
|
||||
return ProfileInfo(
|
||||
name=name,
|
||||
author=author,
|
||||
notes="",
|
||||
created=now_iso_utc(),
|
||||
modified=now_iso_utc(),
|
||||
is_quick_select=False,
|
||||
widget_count=0,
|
||||
size_kb=0,
|
||||
user_path=u_path,
|
||||
default_path=d_path,
|
||||
origin=origin,
|
||||
is_read_only=read_only,
|
||||
)
|
||||
|
||||
created = s.value(SETTINGS_KEYS["created_at"], "", type=str) or now_iso_utc()
|
||||
src_path = u_path if prefer_user else d_path
|
||||
modified = _file_modified_iso(src_path)
|
||||
count = _manifest_count(s)
|
||||
try:
|
||||
size_kb = int(os.path.getsize(src_path) / 1024)
|
||||
except Exception:
|
||||
size_kb = 0
|
||||
settings_author = s.value("profile/author", "", type=str) or None
|
||||
if origin == "module":
|
||||
author = "BEC Widgets"
|
||||
elif origin == "plugin":
|
||||
author = _plugin_display_name() or "Plugin"
|
||||
elif origin == "settings":
|
||||
author = "User"
|
||||
else:
|
||||
author = settings_author or "user"
|
||||
|
||||
return ProfileInfo(
|
||||
name=name,
|
||||
author=author,
|
||||
notes=s.value("profile/notes", "", type=str) or "",
|
||||
created=created,
|
||||
modified=modified,
|
||||
is_quick_select=is_quick_select(name),
|
||||
widget_count=count,
|
||||
size_kb=size_kb,
|
||||
user_path=u_path,
|
||||
default_path=d_path,
|
||||
origin=origin,
|
||||
is_read_only=read_only,
|
||||
)
|
||||
|
||||
|
||||
def load_profile_screenshot(name: str) -> QPixmap | None:
|
||||
"""Load the stored screenshot pixmap for a profile from settings (user preferred)."""
|
||||
u_path = user_profile_path(name)
|
||||
d_path = default_profile_path(name)
|
||||
s = (
|
||||
open_user_settings(name)
|
||||
if os.path.exists(u_path)
|
||||
else (open_default_settings(name) if os.path.exists(d_path) else None)
|
||||
)
|
||||
if s is None:
|
||||
return None
|
||||
return _load_screenshot_from_settings(s)
|
||||
|
||||
|
||||
def load_user_profile_screenshot(name: str) -> QPixmap | None:
|
||||
"""Load the screenshot from the user profile copy, if available."""
|
||||
if not os.path.exists(user_profile_path(name)):
|
||||
return None
|
||||
return _load_screenshot_from_settings(open_user_settings(name))
|
||||
|
||||
|
||||
def load_default_profile_screenshot(name: str) -> QPixmap | None:
|
||||
"""Load the screenshot from the default profile copy, if available."""
|
||||
if not os.path.exists(default_profile_path(name)):
|
||||
return None
|
||||
return _load_screenshot_from_settings(open_default_settings(name))
|
||||
Whitespace-only changes.
@@ -0,0 +1,325 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Literal
|
||||
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtGui import QPixmap
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QDialog,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from bec_widgets import SafeSlot
|
||||
|
||||
|
||||
class SaveProfileDialog(QDialog):
|
||||
"""Dialog for saving workspace profiles with quick select option."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: QWidget | None = None,
|
||||
current_name: str = "",
|
||||
current_profile_name: str = "",
|
||||
*,
|
||||
name_exists: Callable[[str], bool] | None = None,
|
||||
profile_origin: (
|
||||
Callable[[str], Literal["module", "plugin", "settings", "unknown"]] | None
|
||||
) = None,
|
||||
origin_label: Callable[[str], str | None] | None = None,
|
||||
quick_select_checked: bool = False,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Save Workspace Profile")
|
||||
self.setModal(True)
|
||||
self.resize(400, 160)
|
||||
|
||||
self._name_exists = name_exists or (lambda _: False)
|
||||
self._profile_origin = profile_origin or (lambda _: "unknown")
|
||||
self._origin_label = origin_label or (lambda _: None)
|
||||
self._current_profile_name = current_profile_name.strip()
|
||||
self._previous_name_before_overwrite = current_name
|
||||
self._block_name_signals = False
|
||||
self._block_checkbox_signals = False
|
||||
self.overwrite_existing = False
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Name input
|
||||
name_row = QHBoxLayout()
|
||||
name_row.addWidget(QLabel("Profile Name:"))
|
||||
self.name_edit = QLineEdit(current_name)
|
||||
self.name_edit.setPlaceholderText("Enter profile name...")
|
||||
name_row.addWidget(self.name_edit)
|
||||
layout.addLayout(name_row)
|
||||
|
||||
# Overwrite checkbox
|
||||
self.overwrite_checkbox = QCheckBox("Overwrite current profile")
|
||||
self.overwrite_checkbox.setEnabled(bool(self._current_profile_name))
|
||||
self.overwrite_checkbox.toggled.connect(self._on_overwrite_toggled)
|
||||
layout.addWidget(self.overwrite_checkbox)
|
||||
|
||||
# Quick-select checkbox
|
||||
self.quick_select_checkbox = QCheckBox("Include in quick selection.")
|
||||
self.quick_select_checkbox.setChecked(quick_select_checked)
|
||||
layout.addWidget(self.quick_select_checkbox)
|
||||
|
||||
# Buttons
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.addStretch(1)
|
||||
self.save_btn = QPushButton("Save")
|
||||
self.save_btn.setDefault(True)
|
||||
cancel_btn = QPushButton("Cancel")
|
||||
self.save_btn.clicked.connect(self.accept)
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
btn_row.addWidget(self.save_btn)
|
||||
btn_row.addWidget(cancel_btn)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
# Enable/disable save button based on name input
|
||||
self.name_edit.textChanged.connect(self._on_name_changed)
|
||||
self._update_save_button()
|
||||
|
||||
@SafeSlot(bool)
|
||||
def _on_overwrite_toggled(self, checked: bool):
|
||||
if self._block_checkbox_signals:
|
||||
return
|
||||
if not self._current_profile_name:
|
||||
return
|
||||
|
||||
self._block_name_signals = True
|
||||
if checked:
|
||||
self._previous_name_before_overwrite = self.name_edit.text()
|
||||
self.name_edit.setText(self._current_profile_name)
|
||||
self.name_edit.selectAll()
|
||||
else:
|
||||
if self.name_edit.text().strip() == self._current_profile_name:
|
||||
self.name_edit.setText(self._previous_name_before_overwrite or "")
|
||||
self._block_name_signals = False
|
||||
self._update_save_button()
|
||||
|
||||
@SafeSlot(str)
|
||||
def _on_name_changed(self, _: str):
|
||||
if self._block_name_signals:
|
||||
return
|
||||
text = self.name_edit.text().strip()
|
||||
if self.overwrite_checkbox.isChecked() and text != self._current_profile_name:
|
||||
self._block_checkbox_signals = True
|
||||
self.overwrite_checkbox.setChecked(False)
|
||||
self._block_checkbox_signals = False
|
||||
self._update_save_button()
|
||||
|
||||
def _update_save_button(self):
|
||||
"""Enable save button only when name is not empty."""
|
||||
self.save_btn.setEnabled(bool(self.name_edit.text().strip()))
|
||||
|
||||
def get_profile_name(self) -> str:
|
||||
"""Return the entered profile name."""
|
||||
return self.name_edit.text().strip()
|
||||
|
||||
def is_quick_select(self) -> bool:
|
||||
"""Return whether the profile should appear in quick select."""
|
||||
return self.quick_select_checkbox.isChecked()
|
||||
|
||||
def _generate_unique_name(self, base: str) -> str:
|
||||
candidate_base = base.strip() or "profile"
|
||||
suffix = "_custom"
|
||||
candidate = f"{candidate_base}{suffix}"
|
||||
counter = 1
|
||||
while self._name_exists(candidate) or self._profile_origin(candidate) != "unknown":
|
||||
candidate = f"{candidate_base}{suffix}_{counter}"
|
||||
counter += 1
|
||||
return candidate
|
||||
|
||||
def accept(self):
|
||||
name = self.get_profile_name()
|
||||
if not name:
|
||||
return
|
||||
|
||||
self.overwrite_existing = False
|
||||
origin = self._profile_origin(name)
|
||||
if origin in {"module", "plugin"}:
|
||||
source_label = self._origin_label(name)
|
||||
if origin == "module":
|
||||
provider = source_label or "BEC Widgets"
|
||||
else:
|
||||
provider = (
|
||||
f"the {source_label} plugin repository"
|
||||
if source_label
|
||||
else "the plugin repository"
|
||||
)
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Read-only profile",
|
||||
(
|
||||
f"'{name}' is a default profile provided by {provider} and cannot be overwritten.\n"
|
||||
"Please choose a different name."
|
||||
),
|
||||
)
|
||||
suggestion = self._generate_unique_name(name)
|
||||
self._block_name_signals = True
|
||||
self.name_edit.setText(suggestion)
|
||||
self.name_edit.selectAll()
|
||||
self._block_name_signals = False
|
||||
self._block_checkbox_signals = True
|
||||
self.overwrite_checkbox.setChecked(False)
|
||||
self._block_checkbox_signals = False
|
||||
return
|
||||
if origin == "settings":
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Overwrite profile",
|
||||
(
|
||||
f"A profile named '{name}' already exists.\n\n"
|
||||
"Overwriting will update both the saved profile and its restore default.\n"
|
||||
"Do you want to continue?"
|
||||
),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
if reply != QMessageBox.Yes:
|
||||
suggestion = self._generate_unique_name(name)
|
||||
self._block_name_signals = True
|
||||
self.name_edit.setText(suggestion)
|
||||
self.name_edit.selectAll()
|
||||
self._block_name_signals = False
|
||||
self._block_checkbox_signals = True
|
||||
self.overwrite_checkbox.setChecked(False)
|
||||
self._block_checkbox_signals = False
|
||||
return
|
||||
self.overwrite_existing = True
|
||||
|
||||
super().accept()
|
||||
|
||||
|
||||
class PreviewPanel(QGroupBox):
|
||||
"""Resizable preview pane that scales its pixmap with aspect ratio preserved."""
|
||||
|
||||
def __init__(self, title: str, pixmap: QPixmap | None, parent: QWidget | None = None):
|
||||
super().__init__(title, parent)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self._original: QPixmap | None = pixmap if (pixmap and not pixmap.isNull()) else None
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
# layout.setContentsMargins(0,0,0,0) # leave room for group title and frame
|
||||
|
||||
self.image_label = QLabel()
|
||||
self.image_label.setAlignment(Qt.AlignCenter)
|
||||
self.image_label.setMinimumSize(360, 240)
|
||||
self.image_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
layout.addWidget(self.image_label, 1)
|
||||
|
||||
if self._original:
|
||||
self._update_scaled_pixmap()
|
||||
else:
|
||||
self.image_label.setText("No preview available")
|
||||
self.image_label.setStyleSheet(
|
||||
self.image_label.styleSheet() + "color: rgba(255,255,255,0.6); font-style: italic;"
|
||||
)
|
||||
|
||||
def setPixmap(self, pixmap: QPixmap | None):
|
||||
self._original = pixmap if (pixmap and not pixmap.isNull()) else None
|
||||
if self._original:
|
||||
self.image_label.setText("")
|
||||
self._update_scaled_pixmap()
|
||||
else:
|
||||
self.image_label.setPixmap(QPixmap())
|
||||
self.image_label.setText("No preview available")
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
if self._original:
|
||||
self._update_scaled_pixmap()
|
||||
|
||||
def _update_scaled_pixmap(self):
|
||||
if not self._original:
|
||||
return
|
||||
size = self.image_label.size()
|
||||
if size.width() <= 0 or size.height() <= 0:
|
||||
return
|
||||
scaled = self._original.scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.image_label.setPixmap(scaled)
|
||||
|
||||
|
||||
class RestoreProfileDialog(QDialog):
|
||||
"""
|
||||
Confirmation dialog that previews the current profile screenshot against the default baseline.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, parent: QWidget | None, current_pixmap: QPixmap | None, default_pixmap: QPixmap | None
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Restore Profile to Default")
|
||||
self.setModal(True)
|
||||
self.resize(880, 480)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
info_label = QLabel(
|
||||
"Restoring will discard your custom layout and replace it with the default profile."
|
||||
)
|
||||
info_label.setWordWrap(True)
|
||||
layout.addWidget(info_label)
|
||||
|
||||
preview_row = QHBoxLayout()
|
||||
layout.addLayout(preview_row)
|
||||
|
||||
current_preview = PreviewPanel("Current", current_pixmap, self)
|
||||
default_preview = PreviewPanel("Default", default_pixmap, self)
|
||||
|
||||
# Equal expansion left/right
|
||||
preview_row.addWidget(current_preview, 1)
|
||||
|
||||
arrow_label = QLabel("\u2192")
|
||||
arrow_label.setAlignment(Qt.AlignCenter)
|
||||
arrow_label.setStyleSheet("font-size: 32px; padding: 0 16px;")
|
||||
arrow_label.setMinimumWidth(40)
|
||||
arrow_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
||||
preview_row.addWidget(arrow_label)
|
||||
|
||||
preview_row.addWidget(default_preview, 1)
|
||||
|
||||
# Enforce equal stretch for both previews
|
||||
preview_row.setStretch(0, 1)
|
||||
preview_row.setStretch(1, 0)
|
||||
preview_row.setStretch(2, 1)
|
||||
|
||||
warn_label = QLabel(
|
||||
"This action cannot be undone. Do you want to restore the default layout now?"
|
||||
)
|
||||
warn_label.setWordWrap(True)
|
||||
layout.addWidget(warn_label)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.addStretch(1)
|
||||
restore_btn = QPushButton("Restore")
|
||||
restore_btn.setDefault(True)
|
||||
cancel_btn = QPushButton("Cancel")
|
||||
restore_btn.clicked.connect(self.accept)
|
||||
cancel_btn.clicked.connect(self.reject)
|
||||
btn_row.addWidget(restore_btn)
|
||||
btn_row.addWidget(cancel_btn)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
# Make the previews take most of the vertical space on resize
|
||||
layout.setStretch(0, 0) # info label
|
||||
layout.setStretch(1, 1) # preview row
|
||||
layout.setStretch(2, 0) # warning label
|
||||
layout.setStretch(3, 0) # buttons
|
||||
|
||||
@staticmethod
|
||||
def confirm(
|
||||
parent: QWidget | None, current_pixmap: QPixmap | None, default_pixmap: QPixmap | None
|
||||
) -> bool:
|
||||
dialog = RestoreProfileDialog(parent, current_pixmap, default_pixmap)
|
||||
return dialog.exec() == QDialog.Accepted
|
||||
@@ -0,0 +1,404 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
|
||||
from bec_qthemes import material_icon
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtGui import QPixmap
|
||||
from qtpy.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSplitter,
|
||||
QStyledItemDelegate,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QToolButton,
|
||||
QTreeWidget,
|
||||
QTreeWidgetItem,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from bec_widgets import BECWidget, SafeSlot
|
||||
from bec_widgets.utils.colors import apply_theme, get_accent_colors
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
|
||||
delete_profile_files,
|
||||
get_profile_info,
|
||||
is_quick_select,
|
||||
list_profiles,
|
||||
load_profile_screenshot,
|
||||
set_quick_select,
|
||||
)
|
||||
|
||||
|
||||
class WorkSpaceManager(BECWidget, QWidget):
|
||||
RPC = False
|
||||
PLUGIN = False
|
||||
COL_ACTIONS = 0
|
||||
COL_NAME = 1
|
||||
COL_AUTHOR = 2
|
||||
HEADERS = ["Actions", "Profile", "Author"]
|
||||
|
||||
def __init__(
|
||||
self, parent=None, target_widget=None, default_profile: str | None = None, **kwargs
|
||||
):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
self.target_widget = target_widget
|
||||
self.accent_colors = get_accent_colors()
|
||||
self._init_ui()
|
||||
if self.target_widget is not None and hasattr(self.target_widget, "profile_changed"):
|
||||
self.target_widget.profile_changed.connect(self.on_profile_changed)
|
||||
if default_profile is not None:
|
||||
self._select_by_name(default_profile)
|
||||
self._show_profile_details(default_profile)
|
||||
|
||||
def _init_ui(self):
|
||||
self.root_layout = QHBoxLayout(self)
|
||||
self.splitter = QSplitter(Qt.Horizontal, self)
|
||||
self.root_layout.addWidget(self.splitter)
|
||||
|
||||
# Init components
|
||||
self._init_profile_table()
|
||||
self._init_profile_details_tree()
|
||||
self._init_screenshot_preview()
|
||||
|
||||
# Build two-column layout
|
||||
left_col = QVBoxLayout()
|
||||
left_col.addWidget(self.profile_table, 1)
|
||||
left_col.addWidget(self.profile_details_tree, 0)
|
||||
|
||||
self.save_profile_button = QPushButton("Save current layout as new profile", self)
|
||||
self.save_profile_button.clicked.connect(self.save_current_as_profile)
|
||||
left_col.addWidget(self.save_profile_button)
|
||||
self.save_profile_button.setEnabled(self.target_widget is not None)
|
||||
|
||||
# Wrap left widgets into a panel that participates in splitter sizing
|
||||
left_panel = QWidget(self)
|
||||
left_panel.setLayout(left_col)
|
||||
left_panel.setMinimumWidth(220)
|
||||
|
||||
# Make the screenshot preview expand to fill remaining space
|
||||
self.screenshot_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
self.right_box = QGroupBox("Profile Screenshot Preview", self)
|
||||
right_col = QVBoxLayout(self.right_box)
|
||||
right_col.addWidget(self.screenshot_label, 1)
|
||||
|
||||
self.splitter.addWidget(left_panel)
|
||||
self.splitter.addWidget(self.right_box)
|
||||
self.splitter.setStretchFactor(0, 0)
|
||||
self.splitter.setStretchFactor(1, 1)
|
||||
self.splitter.setSizes([350, 650])
|
||||
|
||||
def _init_profile_table(self):
|
||||
self.profile_table = QTableWidget(self)
|
||||
self.profile_table.setColumnCount(len(self.HEADERS))
|
||||
self.profile_table.setHorizontalHeaderLabels(self.HEADERS)
|
||||
self.profile_table.setAlternatingRowColors(True)
|
||||
self.profile_table.verticalHeader().setVisible(False)
|
||||
|
||||
# Enforce row selection, single-select, and disable edits
|
||||
self.profile_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.profile_table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.profile_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
|
||||
# Ensure the table expands to use vertical space in the left panel
|
||||
self.profile_table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
header = self.profile_table.horizontalHeader()
|
||||
header.setStretchLastSection(False)
|
||||
header.setDefaultAlignment(Qt.AlignCenter)
|
||||
|
||||
class _CenterDelegate(QStyledItemDelegate):
|
||||
def initStyleOption(self, option, index):
|
||||
super().initStyleOption(option, index)
|
||||
option.displayAlignment = Qt.AlignCenter
|
||||
|
||||
self.profile_table.setItemDelegate(_CenterDelegate(self.profile_table))
|
||||
|
||||
header.setSectionResizeMode(self.COL_ACTIONS, QHeaderView.ResizeToContents)
|
||||
header.setSectionResizeMode(self.COL_NAME, QHeaderView.Stretch)
|
||||
header.setSectionResizeMode(self.COL_AUTHOR, QHeaderView.ResizeToContents)
|
||||
self.render_table()
|
||||
self.profile_table.itemSelectionChanged.connect(self._on_table_selection_changed)
|
||||
self.profile_table.cellClicked.connect(self._on_cell_clicked)
|
||||
|
||||
def _init_profile_details_tree(self):
|
||||
self.profile_details_tree = QTreeWidget(self)
|
||||
self.profile_details_tree.setHeaderLabels(["Field", "Value"])
|
||||
# Keep details compact so the table can expand
|
||||
self.profile_details_tree.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
||||
|
||||
def _init_screenshot_preview(self):
|
||||
self.screenshot_label = QLabel(self)
|
||||
self.screenshot_label.setMinimumHeight(160)
|
||||
self.screenshot_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
def render_table(self):
|
||||
self.profile_table.setRowCount(0)
|
||||
for profile in list_profiles():
|
||||
self._add_profile_row(profile)
|
||||
|
||||
def _add_profile_row(self, name: str):
|
||||
row = self.profile_table.rowCount()
|
||||
self.profile_table.insertRow(row)
|
||||
|
||||
actions_items = QWidget(self)
|
||||
actions_items.profile_name = name
|
||||
actions_items_layout = QHBoxLayout(actions_items)
|
||||
actions_items_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
info = get_profile_info(name)
|
||||
|
||||
# Flags
|
||||
is_active = (
|
||||
self.target_widget is not None
|
||||
and getattr(self.target_widget, "_current_profile_name", None) == name
|
||||
)
|
||||
quick = info.is_quick_select
|
||||
is_read_only = info.is_read_only
|
||||
|
||||
# Play (green if active)
|
||||
self._make_action_button(
|
||||
actions_items,
|
||||
"play_circle",
|
||||
"Switch to this profile",
|
||||
self.switch_profile,
|
||||
filled=is_active,
|
||||
color=(self.accent_colors.success if is_active else None),
|
||||
)
|
||||
|
||||
# Quick-select (yellow if enabled)
|
||||
self._make_action_button(
|
||||
actions_items,
|
||||
"star",
|
||||
"Include in quick selection",
|
||||
self.toggle_quick_select,
|
||||
filled=quick,
|
||||
color=(self.accent_colors.warning if quick else None),
|
||||
)
|
||||
|
||||
# Delete (red, disabled when read-only)
|
||||
delete_button = self._make_action_button(
|
||||
actions_items,
|
||||
"delete",
|
||||
"Delete this profile",
|
||||
self.delete_profile,
|
||||
color=self.accent_colors.emergency,
|
||||
)
|
||||
if is_read_only:
|
||||
delete_button.setEnabled(False)
|
||||
delete_button.setToolTip("Bundled profiles are read-only and cannot be deleted.")
|
||||
|
||||
actions_items_layout.addStretch()
|
||||
|
||||
self.profile_table.setCellWidget(row, self.COL_ACTIONS, actions_items)
|
||||
self.profile_table.setItem(row, self.COL_NAME, QTableWidgetItem(name))
|
||||
self.profile_table.setItem(row, self.COL_AUTHOR, QTableWidgetItem(info.author))
|
||||
|
||||
def _make_action_button(
|
||||
self,
|
||||
parent: QWidget,
|
||||
icon_name: str,
|
||||
tooltip: str,
|
||||
slot: callable,
|
||||
*,
|
||||
filled: bool = False,
|
||||
color: str | None = None,
|
||||
):
|
||||
button = QToolButton(parent=parent)
|
||||
button.setIcon(material_icon(icon_name, filled=filled, color=color))
|
||||
button.setToolTip(tooltip)
|
||||
button.clicked.connect(partial(slot, parent.profile_name))
|
||||
parent.layout().addWidget(button)
|
||||
return button
|
||||
|
||||
def _select_by_name(self, name: str) -> None:
|
||||
for row in range(self.profile_table.rowCount()):
|
||||
item = self.profile_table.item(row, self.COL_NAME)
|
||||
if item and item.text() == name:
|
||||
self.profile_table.selectRow(row)
|
||||
break
|
||||
|
||||
def _current_selected_profile(self) -> str | None:
|
||||
rows = self.profile_table.selectionModel().selectedRows()
|
||||
if not rows:
|
||||
return None
|
||||
row = rows[0].row()
|
||||
item = self.profile_table.item(row, self.COL_NAME)
|
||||
return item.text() if item else None
|
||||
|
||||
def _show_profile_details(self, name: str) -> None:
|
||||
info = get_profile_info(name)
|
||||
self.profile_details_tree.clear()
|
||||
entries = [
|
||||
("Name", info.name),
|
||||
("Author", info.author or ""),
|
||||
("Created", info.created or ""),
|
||||
("Modified", info.modified or ""),
|
||||
("Quick select", "Yes" if info.is_quick_select else "No"),
|
||||
("Widgets", str(info.widget_count)),
|
||||
("Size (KB)", str(info.size_kb)),
|
||||
("User path", info.user_path or ""),
|
||||
("Default path", info.default_path or ""),
|
||||
]
|
||||
for k, v in entries:
|
||||
self.profile_details_tree.addTopLevelItem(QTreeWidgetItem([k, v]))
|
||||
self.profile_details_tree.expandAll()
|
||||
|
||||
# Render screenshot preview from profile INI
|
||||
pm = load_profile_screenshot(name)
|
||||
if pm is not None and not pm.isNull():
|
||||
scaled = pm.scaled(
|
||||
self.screenshot_label.width() or 800,
|
||||
self.screenshot_label.height() or 450,
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation,
|
||||
)
|
||||
self.screenshot_label.setPixmap(scaled)
|
||||
else:
|
||||
self.screenshot_label.setPixmap(QPixmap())
|
||||
|
||||
@SafeSlot()
|
||||
def _on_table_selection_changed(self):
|
||||
name = self._current_selected_profile()
|
||||
if name:
|
||||
self._show_profile_details(name)
|
||||
|
||||
@SafeSlot(int, int)
|
||||
def _on_cell_clicked(self, row: int, column: int):
|
||||
item = self.profile_table.item(row, self.COL_NAME)
|
||||
if item:
|
||||
self._show_profile_details(item.text())
|
||||
|
||||
##################################################
|
||||
# Public Slots
|
||||
##################################################
|
||||
@SafeSlot(str)
|
||||
def on_profile_changed(self, name: str):
|
||||
"""Keep the manager in sync without forcing selection to the active profile."""
|
||||
selected = self._current_selected_profile()
|
||||
self.render_table()
|
||||
if selected:
|
||||
self._select_by_name(selected)
|
||||
self._show_profile_details(selected)
|
||||
|
||||
@SafeSlot(str)
|
||||
def switch_profile(self, profile_name: str):
|
||||
self.target_widget.load_profile(profile_name)
|
||||
try:
|
||||
self.target_widget.toolbar.components.get_action(
|
||||
"workspace_combo"
|
||||
).widget.setCurrentText(profile_name)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not update workspace combo box. {e}")
|
||||
pass
|
||||
self.render_table()
|
||||
self._select_by_name(profile_name)
|
||||
self._show_profile_details(profile_name)
|
||||
|
||||
@SafeSlot(str)
|
||||
def toggle_quick_select(self, profile_name: str):
|
||||
enabled = is_quick_select(profile_name)
|
||||
set_quick_select(profile_name, not enabled)
|
||||
self.render_table()
|
||||
if self.target_widget is not None:
|
||||
self.target_widget._refresh_workspace_list()
|
||||
name = self._current_selected_profile()
|
||||
if name:
|
||||
self._show_profile_details(name)
|
||||
|
||||
@SafeSlot()
|
||||
def save_current_as_profile(self):
|
||||
if self.target_widget is None:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Save Profile",
|
||||
"No workspace is associated with this manager. Attach a workspace to save profiles.",
|
||||
)
|
||||
return
|
||||
|
||||
self.target_widget.save_profile()
|
||||
# AdvancedDockArea will emit profile_changed which will trigger table refresh,
|
||||
# but ensure the UI stays in sync even if the signal is delayed.
|
||||
self.render_table()
|
||||
current = getattr(self.target_widget, "_current_profile_name", None)
|
||||
if current:
|
||||
self._select_by_name(current)
|
||||
self._show_profile_details(current)
|
||||
|
||||
@SafeSlot(str)
|
||||
def delete_profile(self, profile_name: str):
|
||||
info = get_profile_info(profile_name)
|
||||
if info.is_read_only:
|
||||
QMessageBox.information(
|
||||
self, "Delete Profile", "This profile is read-only and cannot be deleted."
|
||||
)
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Delete Profile",
|
||||
(
|
||||
f"Delete the profile '{profile_name}'?\n\n"
|
||||
"This will remove both the user and default copies."
|
||||
),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
try:
|
||||
removed = delete_profile_files(profile_name)
|
||||
except OSError as exc:
|
||||
QMessageBox.warning(
|
||||
self, "Delete Profile", f"Failed to delete profile '{profile_name}': {exc}"
|
||||
)
|
||||
return
|
||||
|
||||
if not removed:
|
||||
QMessageBox.information(
|
||||
self, "Delete Profile", "No writable profile files were found to delete."
|
||||
)
|
||||
return
|
||||
|
||||
if self.target_widget is not None:
|
||||
if getattr(self.target_widget, "_current_profile_name", None) == profile_name:
|
||||
self.target_widget._current_profile_name = None
|
||||
if hasattr(self.target_widget, "_refresh_workspace_list"):
|
||||
self.target_widget._refresh_workspace_list()
|
||||
|
||||
self.render_table()
|
||||
remaining_profiles = list_profiles()
|
||||
if remaining_profiles:
|
||||
next_profile = remaining_profiles[0]
|
||||
self._select_by_name(next_profile)
|
||||
self._show_profile_details(next_profile)
|
||||
else:
|
||||
self.profile_details_tree.clear()
|
||||
self.screenshot_label.setPixmap(QPixmap())
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
name = self._current_selected_profile()
|
||||
if not name:
|
||||
return
|
||||
pm = load_profile_screenshot(name)
|
||||
if pm is None or pm.isNull():
|
||||
return
|
||||
scaled = pm.scaled(
|
||||
self.screenshot_label.width() or 800,
|
||||
self.screenshot_label.height() or 450,
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation,
|
||||
)
|
||||
self.screenshot_label.setPixmap(scaled)
|
||||
@@ -1,234 +0,0 @@
|
||||
[BECMainWindowNoRPC.AdvancedDockArea]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=false
|
||||
baseSize=@Size(0 0)
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0\x66\x80\x4\x95[\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x14Qt.ContextMenuPolicy\x94\x93\x94\x8c\x12\x44\x65\x66\x61ultContextMenu\x94\x86\x94R\x94.)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0U\x80\x4\x95J\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\xeQt.FocusPolicy\x94\x93\x94\x8c\aNoFocus\x94\x86\x94R\x94.)
|
||||
font=@Variant(\0\0\0@\0\0\0$\0.\0\x41\0p\0p\0l\0\x65\0S\0y\0s\0t\0\x65\0m\0U\0I\0\x46\0o\0n\0t@*\0\0\0\0\0\0\xff\xff\xff\xff\x5\x1\0\x32\x10)
|
||||
geometry=@Rect(0 29 2075 974)
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.InputMethodHint\x94\x93\x94\x8c\aImhNone\x94\x86\x94R\x94.)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0]\x80\x4\x95R\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.LayoutDirection\x94\x93\x94\x8c\vLeftToRight\x94\x86\x94R\x94.)
|
||||
locale=@Variant(\0\0\0\x12\0\0\0\n\0\x65\0n\0_\0\x43\0H)
|
||||
lock_workspace=false
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mode=developer
|
||||
mouseTracking=false
|
||||
palette=@Variant(\0\0\0\x44\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0)
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
styleSheet=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
updatesEnabled=true
|
||||
visible=true
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowIcon="@Variant(\0\0\0\x45\0\0\0\x1\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0,\0\0\0,\b\x6\0\0\0\x1e\x84Z\x1\0\0\x1\x81iCCPsRGB IEC61966-2.1\0\0(\x91u\x91\xb9KCA\x10\x87\xbf\x1c\x1ehB\x4-,,\x82\x44\xab(^\x4m\x4\x13\x44\x85 !F\xf0j\x92g\xe!\xc7\xe3\xbd\x88\x4[\xc1\x36\xa0 \xdax\x15\xfa\x17h+X\v\x82\xa2\bb\x9dZ\xd1\x46\xc3s\x9e\x11\x12\xc4\xcc\x32;\xdf\xfevg\xd8\x9d\x5k$\xad\x64t\xfb\0\x64\xb2y-<\xe5w/,.\xb9\x9bJ4\xd0\x88\x13;#QEW'B\xa1 u\xed\xe3\x1\x8b\x19\xef\xfa\xccZ\xf5\xcf\xfdk\xad\xabq]\x1K\xb3\xf0\xb8\xa2jy\xe1i\xe1\xe0\x46^5yW\xb8\x43IEW\x85\xcf\x85\xbd\x9a\\P\xf8\xde\xd4\x63\x15.\x99\x9c\xac\xf0\x97\xc9Z$\x1c\0k\x9b\xb0;Y\xc3\xb1\x1aVRZFX^\x8e'\x93^W~\xef\x63\xbe\xc4\x11\xcf\xce\xcfI\xec\x16\xef\x42'\xcc\x14~\xdc\xcc\x30I\0\x1f\x83\x8c\xc9\xec\xa3\x8f!\xfa\x65\x45\x9d\xfc\x81\x9f\xfcYr\x92\xab\xc8\xacR@c\x8d$)\xf2xE]\x97\xeaq\x89\t\xd1\xe3\x32\xd2\x14\xcc\xfe\xff\xed\xab\x9e\x18\x1e\xaaTw\xf8\xa1\xe1\xc5\x30\xdez\xa0i\a\xca\x45\xc3\xf8<6\x8c\xf2\t\xd8\x9e\xe1*[\xcd\xcf\x1d\xc1\xe8\xbb\xe8\xc5\xaa\xe6\x39\x4\xd7\x16\\\\W\xb5\xd8\x1e\\nC\xe7\x93\x1a\xd5\xa2?\x92M\xdc\x9aH\xc0\xeb\x19\x38\x17\xa1\xfd\x16Z\x96+=\xfb\xdd\xe7\xf4\x11\"\x9b\xf2U7\xb0\x7f\0\xbdr\xde\xb5\xf2\r+\xf4g\xcbwA\x5\xc7\0\0\0\tpHYs\0\0=\x84\0\0=\x84\x1\xd5\xac\xaft\0\0\x6\xdaIDATX\x85\xed\x99mp\x15W\x19\xc7\x7fgor\xc3\xbd\xb9I/\x91$\xb7XB^J\x81\x84*F\xd3\x32\x86ON\xd5\nh\xb4\x36\x43p\xa0\xadT3\x9d\x86\xfa\xc1R-\x1a\x64\xca@\xeb\xe0\x94\xce\xd8\xb4\xe\x38\x3\xbe\xd1J\x98I\xc6\t\x16\x11\x61\x98\x90\x12\xa0)\x4\xa5-\xa0\xd4\xb6\xa9\x91\x4\b$$$\xdc\xdd=\xc7\xf\xbb\xf7}s\xefM\x88\xce\x38\xc3\x7f\x66gw\xcfy\xces\xfe\xe7\x7f\x9e}\xce\xd9]\xb8\x8d\xdb\xf8\xff\x86H\xc7\x66\xfb\xa7\x1f\x99\x93\xe5r\xd7(\xb4O\xe9(\x7fP\xa1\x19h\x18\n\x82\x80\x1\x18\x12\fD\xf8^W\x1a:`(\xd0\xb1\xe\x43\n\xeb\f\x4\x95\xc0\x4\x19\x84k\x86\x14\x7f\xbd\xa1\x8f\xfd\xe1\xf4\xfb\xbf\xfc;\xa0&M\xf8\x95\x5\x35\xb3\n\xb2\xf2\xb7*\xa1=\xa4\x83\x66u.\xd0U\x88\x94M\xc0>[\xe5V\xbdn\xd7\x1bv\xb9\x8e \xdc><\x90\x88\xaf\xa0\x12\xd2P\xb2\xe5\xfa\xf5K\xdf\xbf\xd0\xfb\xfb\x9e\xf1\x38i\xe3U\xec\xacX^U\xe4\xc9?\x91\xa1\x89\x87\x93\xd9M\b!y\x1c\x34\x14\xa0!\xb4\x87\xa7\xf9\n\xdf*-}\xacj<\x17\x8e\x44\x9a\xe6/\x99]\xe8\xcdi\x13\x88\xc0\x94\x10\r1U\xb1\xb7\x8e\x96\x42\x14\x66\x64\xdc\xd1\x16\b|\xbd\xd8\xa9\xde\x89\xb0\xab\xd4[\xf8\x92&D\xe1-qt\xea\xcc\xa5QQu7\xbe\\OR;!D\xa1\x37\xe7\xae\xad\x80+\xa1.\xbe\xe0\x17\xf3\x97.\xb8\xc7w\xe7i]\b-\x14_\xae|?\xca\x33\r\x13+\xf6\f\xac\xd8\x8c\xbf\xbe\xd2{\x95\xb1\x9b\x86\x63\fKM\xa3~\xf3*\x16/\xab\xa2\xe7\xc2\x45~\xb8\xba\x89\xfe\xcb\xd7\x93=\xfr\xe4\xc6\xbf\x16\xf6}\xbc\xe7o\xd1\xfc\x32\xe2\t\xcf\xf2\xe4\xd5 \"\xcag\x97\x4X\xf8\xf2\x1a\x84\x96:\x8c/\x1c=K\xebO^O(\xd7\\\x1a\xdf\xdd\xbc\x8a\xea\xa5Vh\xce*\v\xf0\xd3\x1dkX\xbb\xba\x89\xbeK\xd7\x63l\xa3\xc2[sg\xe5\x7f\r\x88!\x1c\xcf\x42\xcb\xd4\\\xf7\x46\x17\x64\xfa}\x16Y\xa5PI\xe\x80\xbc\xd9\xf9\x31\xcf\x93\0\x34-BV)\xc5\xa9\x8e\xf7\x30\f\x93\xa2\xb2\0?\xdb\xb1\x86\xbc\x19\x39\x31\x4\x62\xa6\\h\xf7\xc6s\x8cWX\b\x81?^\xa1\xd0\xc8\xf|s\x13\xba\x94\x91\xa9\xc6\x9a\xc2\x39\xcb\xaaXT\xff`B\x87\xc2%X\xbdi\x15\xf7\xdb\xca\x1e\xd8\xdd\xc1\xb6\xcd{\xb8\xef\x8b\vyz\xcb\xa3\xcc.\v\xf0\xd2\xce\x35<\xf5\xedW\x12\x94\xb6\x31=~\f\xf1\n\v\xa5\xc6Oa\xc6X\x10\x63L\xb7\xcf\x41\x8cQ\x1d},\x88\xd4\xcd\x4[M\xd3xt\xd3#Qd\x8f\xb0\xe3\xf9=(\x5\x1d\xfb\xbb\xd9\xf2\x83\xdf`\x18&\xc5\x65\x1^\xde\xd9\xc0'\xe2\x94\xb6U\xd2R\x11\x46\xa1\xc6M:\xe9,\x8b!\xc3o=\xb7\x92\xfbl\xb2\aw\x1f\xe1\xd7\x9b\x9bQ2\x12\x30G\xf6\x9f\xe2\x85g\"\xa4_\xfdU\x3\xb9\xb9\xde\xe4\xcb\x9c\x13\xe1\xa9\x80\x10P\xf9`%\0\xc3\xd7\x46hm\xda\x8bRDFl\x9f\xdb\xfts\xf2\xd8y\0J\xca\x2\xcc\x99;3\xa5(SNX\x1J\xc2\x9e\xe7\x9b\x91R\xe2\xf3g\xb3v[\x3\xbe\\oL\n\x10\x42\xf0\xbd\xc6Z\xaa\xaa\xe7\x1\xb0\xaf\xad\x8b\xee\xb7\xdfO\xe9\x7f\xca\t\x87\x14\xeal=\xcak\x1b_GJIqy\x11\xcfno \xfb\xe\xafM\x16\x9el\xac\xe5\xabu\x8b\x11\x42\xb0\xbf\xad\x8b\x8d\xebvaJ\x99\xd2\x7f\x42\x1e\x8e z\xe-\x14V\x97\x63H\x85\t\x98\xd6n\vSA^\xa9\xf3\n~\xb4\xa5\x13S\xc1\xaa\r+(\xa9(\xa2q{\x3\xcf\xd5\xbf\xca\xf2\xa7\x96\xb2\xa4n1\0\a\xda\xba\xd8\xb4n\x17\xa6\xa9\xc2\xbdN\x92p,Y!\x4\x95?^\x91\xd4\x99\x32\x63\x15R@Gk'&\xf0\xd8\x86\x15\x94V\x14\xd1\xf4\xc7\xf5\xe4\xf8\xb3\x1\xf8\xcb\xde.^\xf8\xd1.\xa4\x94\t\xfdM\x82\xb0\x85\x1b\x1f]b\xe4\xc3>\\>\xf\xa8\x88\x2N\xe7\xf3\x87\xcf\x38\xfaho\xe9\xc4\x44\xf0\xf8\x86\xba\x30\xd9\x43{\xbb\xd8\xb2\xeewae\xd3\x45J\xc2\xc1+\x83\x9cx\xe2\xe7\x8e{X\xa7\xfd\xf0xJ\x1dn\xe9\x44*X\xddXK\xfb\xbeSl]\xff\x1aRN\x8clJ\xc2i\xe7\xdd\x34q\xa8\xb5\x93?\xb7\x1e#\xa8@&\x19\\2$\xcd\x12\x13\x1f\x7fj(uk\x8eS\x86\x84\xdb\xef\x63\xd1\xc6\xc7\xc9\xf4Y\xab\x90\xf3!P\nN\xee=N\xc7\xee\xf6p\xdb\x65O.\xa1\xbc\xba<\xd6VE\xb5\xb1\xcb\xfa\xfb\ai|\xe6\xb7\xe8\x63zJ\xcdS\x12\xf6\xdf\x33\x8b\xc0\xa2\x8aTf\x96\x33o\x16oF\x11\xae~\xe8\xf3\xf8\v\x1c\xf7R1\x98\xf\xdc=w&\xdd\xa7?L\xddGZL\0\xa5\x14o\xbd\xd8\x8ci\xe7\x61I$\xf\xdf\xf5\xd9\x39\xcc{\xa0\x92\x8cL\x97\xe3lw\xbe\xd1\xc5\xf9\xee\x7f\"\x1\x33\xaa\x9d\xcb\x9d\x41\xfd\xda\x1a\x84\x10\x64\x66$\xbc\\L\x9cp\xcc\xf4(8\xd7|\x98\xa0T\tYB\x2\xf3\x1e\xa8Llc\xe3\xdd\Line truncated
|
||||
windowIconText=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x11Qt.WindowModality\x94\x93\x94\x8c\bNonModal\x94\x86\x94R\x94.)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=Advanced Dock Area
|
||||
|
||||
[BECMainWindowNoRPC.AdvancedDockArea.CDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.BECQueue.dockWidgetScrollArea.qt_scrollarea_viewport.BECQueue]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=false
|
||||
baseSize=@Size(0 0)
|
||||
compact_view=false
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0\x66\x80\x4\x95[\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x14Qt.ContextMenuPolicy\x94\x93\x94\x8c\x12\x44\x65\x66\x61ultContextMenu\x94\x86\x94R\x94.)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
enabled=true
|
||||
expand_popup=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0U\x80\x4\x95J\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\xeQt.FocusPolicy\x94\x93\x94\x8c\aNoFocus\x94\x86\x94R\x94.)
|
||||
font=@Variant(\0\0\0@\0\0\0$\0.\0\x41\0p\0p\0l\0\x65\0S\0y\0s\0t\0\x65\0m\0U\0I\0\x46\0o\0n\0t@*\0\0\0\0\0\0\xff\xff\xff\xff\x5\x1\0\x32\x10)
|
||||
geometry=@Rect(0 0 1252 897)
|
||||
hide_toolbar=false
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.InputMethodHint\x94\x93\x94\x8c\aImhNone\x94\x86\x94R\x94.)
|
||||
label=BEC Queue
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0]\x80\x4\x95R\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.LayoutDirection\x94\x93\x94\x8c\vLeftToRight\x94\x86\x94R\x94.)
|
||||
locale=@Variant(\0\0\0\x12\0\0\0\n\0\x65\0n\0_\0\x43\0H)
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
palette=@Variant(\0\0\0\x44\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0)
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
styleSheet=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
tooltip=BEC Queue status
|
||||
updatesEnabled=true
|
||||
visible=true
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowIcon="@Variant(\0\0\0\x45\0\0\0\x1\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0,\0\0\0,\b\x6\0\0\0\x1e\x84Z\x1\0\0\x1\x81iCCPsRGB IEC61966-2.1\0\0(\x91u\x91\xb9KCA\x10\x87\xbf\x1c\x1ehB\x4-,,\x82\x44\xab(^\x4m\x4\x13\x44\x85 !F\xf0j\x92g\xe!\xc7\xe3\xbd\x88\x4[\xc1\x36\xa0 \xdax\x15\xfa\x17h+X\v\x82\xa2\bb\x9dZ\xd1\x46\xc3s\x9e\x11\x12\xc4\xcc\x32;\xdf\xfevg\xd8\x9d\x5k$\xad\x64t\xfb\0\x64\xb2y-<\xe5w/,.\xb9\x9bJ4\xd0\x88\x13;#QEW'B\xa1 u\xed\xe3\x1\x8b\x19\xef\xfa\xccZ\xf5\xcf\xfdk\xad\xabq]\x1K\xb3\xf0\xb8\xa2jy\xe1i\xe1\xe0\x46^5yW\xb8\x43IEW\x85\xcf\x85\xbd\x9a\\P\xf8\xde\xd4\x63\x15.\x99\x9c\xac\xf0\x97\xc9Z$\x1c\0k\x9b\xb0;Y\xc3\xb1\x1aVRZFX^\x8e'\x93^W~\xef\x63\xbe\xc4\x11\xcf\xce\xcfI\xec\x16\xef\x42'\xcc\x14~\xdc\xcc\x30I\0\x1f\x83\x8c\xc9\xec\xa3\x8f!\xfa\x65\x45\x9d\xfc\x81\x9f\xfcYr\x92\xab\xc8\xacR@c\x8d$)\xf2xE]\x97\xeaq\x89\t\xd1\xe3\x32\xd2\x14\xcc\xfe\xff\xed\xab\x9e\x18\x1e\xaaTw\xf8\xa1\xe1\xc5\x30\xdez\xa0i\a\xca\x45\xc3\xf8<6\x8c\xf2\t\xd8\x9e\xe1*[\xcd\xcf\x1d\xc1\xe8\xbb\xe8\xc5\xaa\xe6\x39\x4\xd7\x16\\\\W\xb5\xd8\x1e\\nC\xe7\x93\x1a\xd5\xa2?\x92M\xdc\x9aH\xc0\xeb\x19\x38\x17\xa1\xfd\x16Z\x96+=\xfb\xdd\xe7\xf4\x11\"\x9b\xf2U7\xb0\x7f\0\xbdr\xde\xb5\xf2\r+\xf4g\xcbwA\x5\xc7\0\0\0\tpHYs\0\0=\x84\0\0=\x84\x1\xd5\xac\xaft\0\0\x6\xdaIDATX\x85\xed\x99mp\x15W\x19\xc7\x7fgor\xc3\xbd\xb9I/\x91$\xb7XB^J\x81\x84*F\xd3\x32\x86ON\xd5\nh\xb4\x36\x43p\xa0\xadT3\x9d\x86\xfa\xc1R-\x1a\x64\xca@\xeb\xe0\x94\xce\xd8\xb4\xe\x38\x3\xbe\xd1J\x98I\xc6\t\x16\x11\x61\x98\x90\x12\xa0)\x4\xa5-\xa0\xd4\xb6\xa9\x91\x4\b$$$\xdc\xdd=\xc7\xf\xbb\xf7}s\xefM\x88\xce\x38\xc3\x7f\x66gw\xcfy\xces\xfe\xe7\x7f\x9e}\xce\xd9]\xb8\x8d\xdb\xf8\xff\x86H\xc7\x66\xfb\xa7\x1f\x99\x93\xe5r\xd7(\xb4O\xe9(\x7fP\xa1\x19h\x18\n\x82\x80\x1\x18\x12\fD\xf8^W\x1a:`(\xd0\xb1\xe\x43\n\xeb\f\x4\x95\xc0\x4\x19\x84k\x86\x14\x7f\xbd\xa1\x8f\xfd\xe1\xf4\xfb\xbf\xfc;\xa0&M\xf8\x95\x5\x35\xb3\n\xb2\xf2\xb7*\xa1=\xa4\x83\x66u.\xd0U\x88\x94M\xc0>[\xe5V\xbdn\xd7\x1bv\xb9\x8e \xdc><\x90\x88\xaf\xa0\x12\xd2P\xb2\xe5\xfa\xf5K\xdf\xbf\xd0\xfb\xfb\x9e\xf1\x38i\xe3U\xec\xacX^U\xe4\xc9?\x91\xa1\x89\x87\x93\xd9M\b!y\x1c\x34\x14\xa0!\xb4\x87\xa7\xf9\n\xdf*-}\xacj<\x17\x8e\x44\x9a\xe6/\x99]\xe8\xcdi\x13\x88\xc0\x94\x10\r1U\xb1\xb7\x8e\x96\x42\x14\x66\x64\xdc\xd1\x16\b|\xbd\xd8\xa9\xde\x89\xb0\xab\xd4[\xf8\x92&D\xe1-qt\xea\xcc\xa5QQu7\xbe\\OR;!D\xa1\x37\xe7\xae\xad\x80+\xa1.\xbe\xe0\x17\xf3\x97.\xb8\xc7w\xe7i]\b-\x14_\xae|?\xca\x33\r\x13+\xf6\f\xac\xd8\x8c\xbf\xbe\xd2{\x95\xb1\x9b\x86\x63\fKM\xa3~\xf3*\x16/\xab\xa2\xe7\xc2\x45~\xb8\xba\x89\xfe\xcb\xd7\x93=\xfr\xe4\xc6\xbf\x16\xf6}\xbc\xe7o\xd1\xfc\x32\xe2\t\xcf\xf2\xe4\xd5 \"\xcag\x97\x4X\xf8\xf2\x1a\x84\x96:\x8c/\x1c=K\xebO^O(\xd7\\\x1a\xdf\xdd\xbc\x8a\xea\xa5Vh\xce*\v\xf0\xd3\x1dkX\xbb\xba\x89\xbeK\xd7\x63l\xa3\xc2[sg\xe5\x7f\r\x88!\x1c\xcf\x42\xcb\xd4\\\xf7\x46\x17\x64\xfa}\x16Y\xa5PI\xe\x80\xbc\xd9\xf9\x31\xcf\x93\0\x34-BV)\xc5\xa9\x8e\xf7\x30\f\x93\xa2\xb2\0?\xdb\xb1\x86\xbc\x19\x39\x31\x4\x62\xa6\\h\xf7\xc6s\x8cWX\b\x81?^\xa1\xd0\xc8\xf|s\x13\xba\x94\x91\xa9\xc6\x9a\xc2\x39\xcb\xaaXT\xff`B\x87\xc2%X\xbdi\x15\xf7\xdb\xca\x1e\xd8\xdd\xc1\xb6\xcd{\xb8\xef\x8b\vyz\xcb\xa3\xcc.\v\xf0\xd2\xce\x35<\xf5\xedW\x12\x94\xb6\x31=~\f\xf1\n\v\xa5\xc6Oa\xc6X\x10\x63L\xb7\xcf\x41\x8cQ\x1d},\x88\xd4\xcd\x4[M\xd3xt\xd3#Qd\x8f\xb0\xe3\xf9=(\x5\x1d\xfb\xbb\xd9\xf2\x83\xdf`\x18&\xc5\x65\x1^\xde\xd9\xc0'\xe2\x94\xb6U\xd2R\x11\x46\xa1\xc6M:\xe9,\x8b!\xc3o=\xb7\x92\xfbl\xb2\aw\x1f\xe1\xd7\x9b\x9bQ2\x12\x30G\xf6\x9f\xe2\x85g\"\xa4_\xfdU\x3\xb9\xb9\xde\xe4\xcb\x9c\x13\xe1\xa9\x80\x10P\xf9`%\0\xc3\xd7\x46hm\xda\x8bRDFl\x9f\xdb\xfts\xf2\xd8y\0J\xca\x2\xcc\x99;3\xa5(SNX\x1J\xc2\x9e\xe7\x9b\x91R\xe2\xf3g\xb3v[\x3\xbe\\oL\n\x10\x42\xf0\xbd\xc6Z\xaa\xaa\xe7\x1\xb0\xaf\xad\x8b\xee\xb7\xdfO\xe9\x7f\xca\t\x87\x14\xeal=\xcak\x1b_GJIqy\x11\xcfno \xfb\xe\xafM\x16\x9el\xac\xe5\xabu\x8b\x11\x42\xb0\xbf\xad\x8b\x8d\xebvaJ\x99\xd2\x7f\x42\x1e\x8e z\xe-\x14V\x97\x63H\x85\t\x98\xd6n\vSA^\xa9\xf3\n~\xb4\xa5\x13S\xc1\xaa\r+(\xa9(\xa2q{\x3\xcf\xd5\xbf\xca\xf2\xa7\x96\xb2\xa4n1\0\a\xda\xba\xd8\xb4n\x17\xa6\xa9\xc2\xbdN\x92p,Y!\x4\x95?^\x91\xd4\x99\x32\x63\x15R@Gk'&\xf0\xd8\x86\x15\x94V\x14\xd1\xf4\xc7\xf5\xe4\xf8\xb3\x1\xf8\xcb\xde.^\xf8\xd1.\xa4\x94\t\xfdM\x82\xb0\x85\x1b\x1f]b\xe4\xc3>\\>\xf\xa8\x88\x2N\xe7\xf3\x87\xcf\x38\xfaho\xe9\xc4\x44\xf0\xf8\x86\xba\x30\xd9\x43{\xbb\xd8\xb2\xeewae\xd3\x45J\xc2\xc1+\x83\x9cx\xe2\xe7\x8e{X\xa7\xfd\xf0xJ\x1dn\xe9\x44*X\xddXK\xfb\xbeSl]\xff\x1aRN\x8clJ\xc2i\xe7\xdd\x34q\xa8\xb5\x93?\xb7\x1e#\xa8@&\x19\\2$\xcd\x12\x13\x1f\x7fj(uk\x8eS\x86\x84\xdb\xef\x63\xd1\xc6\xc7\xc9\xf4Y\xab\x90\xf3!P\nN\xee=N\xc7\xee\xf6p\xdb\x65O.\xa1\xbc\xba<\xd6VE\xb5\xb1\xcb\xfa\xfb\ai|\xe6\xb7\xe8\x63zJ\xcdS\x12\xf6\xdf\x33\x8b\xc0\xa2\x8aTf\x96\x33o\x16oF\x11\xae~\xe8\xf3\xf8\v\x1c\xf7R1\x98\xf\xdc=w&\xdd\xa7?L\xddGZL\0\xa5\x14o\xbd\xd8\x8ci\xe7\x61I$\xf\xdf\xf5\xd9\x39\xcc{\xa0\x92\x8cL\x97\xe3lw\xbe\xd1\xc5\xf9\xee\x7f\"\x1\x33\xaa\x9d\xcb\x9d\x41\xfd\xda\x1a\x84\x10\x64\x66$\xbc\\L\x9cp\xcc\xf4(8\xd7|\x98\xa0T\tYB\x2\xf3\x1e\xa8Llc\xe3\xdd\Line truncated
|
||||
windowIconText=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x11Qt.WindowModality\x94\x93\x94\x8c\bNonModal\x94\x86\x94R\x94.)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
|
||||
[BECMainWindowNoRPC.AdvancedDockArea.CDockManager.ads%3A%3ACDockSplitter.ads%3A%3ACDockAreaWidget.Waveform.dockWidgetScrollArea.qt_scrollarea_viewport.Waveform]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=false
|
||||
auto_range_x=true
|
||||
auto_range_y=true
|
||||
baseSize=@Size(0 0)
|
||||
color_palette=plasma
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0\x66\x80\x4\x95[\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x14Qt.ContextMenuPolicy\x94\x93\x94\x8c\x12\x44\x65\x66\x61ultContextMenu\x94\x86\x94R\x94.)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
curve_json=[]
|
||||
enable_fps_monitor=false
|
||||
enable_popups=true
|
||||
enable_side_panel=false
|
||||
enable_toolbar=true
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0U\x80\x4\x95J\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\xeQt.FocusPolicy\x94\x93\x94\x8c\aNoFocus\x94\x86\x94R\x94.)
|
||||
font=@Variant(\0\0\0@\0\0\0$\0.\0\x41\0p\0p\0l\0\x65\0S\0y\0s\0t\0\x65\0m\0U\0I\0\x46\0o\0n\0t@*\0\0\0\0\0\0\xff\xff\xff\xff\x5\x1\0\x32\x10)
|
||||
geometry=@Rect(0 0 798 897)
|
||||
inner_axes=true
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.InputMethodHint\x94\x93\x94\x8c\aImhNone\x94\x86\x94R\x94.)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0]\x80\x4\x95R\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.LayoutDirection\x94\x93\x94\x8c\vLeftToRight\x94\x86\x94R\x94.)
|
||||
legend_label_size=9
|
||||
locale=@Variant(\0\0\0\x12\0\0\0\n\0\x65\0n\0_\0\x43\0H)
|
||||
lock_aspect_ratio=false
|
||||
max_dataset_size_mb=10
|
||||
maximumSize=@Size(16777215 16777215)
|
||||
minimal_crosshair_precision=3
|
||||
minimumSize=@Size(0 0)
|
||||
mouseTracking=false
|
||||
outer_axes=false
|
||||
palette=@Variant(\0\0\0\x44\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0)
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
skip_large_dataset_check=false
|
||||
skip_large_dataset_warning=false
|
||||
statusTip=
|
||||
styleSheet=
|
||||
tabletTracking=false
|
||||
title=
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
updatesEnabled=true
|
||||
visible=true
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowIcon="@Variant(\0\0\0\x45\0\0\0\x1\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0,\0\0\0,\b\x6\0\0\0\x1e\x84Z\x1\0\0\x1\x81iCCPsRGB IEC61966-2.1\0\0(\x91u\x91\xb9KCA\x10\x87\xbf\x1c\x1ehB\x4-,,\x82\x44\xab(^\x4m\x4\x13\x44\x85 !F\xf0j\x92g\xe!\xc7\xe3\xbd\x88\x4[\xc1\x36\xa0 \xdax\x15\xfa\x17h+X\v\x82\xa2\bb\x9dZ\xd1\x46\xc3s\x9e\x11\x12\xc4\xcc\x32;\xdf\xfevg\xd8\x9d\x5k$\xad\x64t\xfb\0\x64\xb2y-<\xe5w/,.\xb9\x9bJ4\xd0\x88\x13;#QEW'B\xa1 u\xed\xe3\x1\x8b\x19\xef\xfa\xccZ\xf5\xcf\xfdk\xad\xabq]\x1K\xb3\xf0\xb8\xa2jy\xe1i\xe1\xe0\x46^5yW\xb8\x43IEW\x85\xcf\x85\xbd\x9a\\P\xf8\xde\xd4\x63\x15.\x99\x9c\xac\xf0\x97\xc9Z$\x1c\0k\x9b\xb0;Y\xc3\xb1\x1aVRZFX^\x8e'\x93^W~\xef\x63\xbe\xc4\x11\xcf\xce\xcfI\xec\x16\xef\x42'\xcc\x14~\xdc\xcc\x30I\0\x1f\x83\x8c\xc9\xec\xa3\x8f!\xfa\x65\x45\x9d\xfc\x81\x9f\xfcYr\x92\xab\xc8\xacR@c\x8d$)\xf2xE]\x97\xeaq\x89\t\xd1\xe3\x32\xd2\x14\xcc\xfe\xff\xed\xab\x9e\x18\x1e\xaaTw\xf8\xa1\xe1\xc5\x30\xdez\xa0i\a\xca\x45\xc3\xf8<6\x8c\xf2\t\xd8\x9e\xe1*[\xcd\xcf\x1d\xc1\xe8\xbb\xe8\xc5\xaa\xe6\x39\x4\xd7\x16\\\\W\xb5\xd8\x1e\\nC\xe7\x93\x1a\xd5\xa2?\x92M\xdc\x9aH\xc0\xeb\x19\x38\x17\xa1\xfd\x16Z\x96+=\xfb\xdd\xe7\xf4\x11\"\x9b\xf2U7\xb0\x7f\0\xbdr\xde\xb5\xf2\r+\xf4g\xcbwA\x5\xc7\0\0\0\tpHYs\0\0=\x84\0\0=\x84\x1\xd5\xac\xaft\0\0\x6\xdaIDATX\x85\xed\x99mp\x15W\x19\xc7\x7fgor\xc3\xbd\xb9I/\x91$\xb7XB^J\x81\x84*F\xd3\x32\x86ON\xd5\nh\xb4\x36\x43p\xa0\xadT3\x9d\x86\xfa\xc1R-\x1a\x64\xca@\xeb\xe0\x94\xce\xd8\xb4\xe\x38\x3\xbe\xd1J\x98I\xc6\t\x16\x11\x61\x98\x90\x12\xa0)\x4\xa5-\xa0\xd4\xb6\xa9\x91\x4\b$$$\xdc\xdd=\xc7\xf\xbb\xf7}s\xefM\x88\xce\x38\xc3\x7f\x66gw\xcfy\xces\xfe\xe7\x7f\x9e}\xce\xd9]\xb8\x8d\xdb\xf8\xff\x86H\xc7\x66\xfb\xa7\x1f\x99\x93\xe5r\xd7(\xb4O\xe9(\x7fP\xa1\x19h\x18\n\x82\x80\x1\x18\x12\fD\xf8^W\x1a:`(\xd0\xb1\xe\x43\n\xeb\f\x4\x95\xc0\x4\x19\x84k\x86\x14\x7f\xbd\xa1\x8f\xfd\xe1\xf4\xfb\xbf\xfc;\xa0&M\xf8\x95\x5\x35\xb3\n\xb2\xf2\xb7*\xa1=\xa4\x83\x66u.\xd0U\x88\x94M\xc0>[\xe5V\xbdn\xd7\x1bv\xb9\x8e \xdc><\x90\x88\xaf\xa0\x12\xd2P\xb2\xe5\xfa\xf5K\xdf\xbf\xd0\xfb\xfb\x9e\xf1\x38i\xe3U\xec\xacX^U\xe4\xc9?\x91\xa1\x89\x87\x93\xd9M\b!y\x1c\x34\x14\xa0!\xb4\x87\xa7\xf9\n\xdf*-}\xacj<\x17\x8e\x44\x9a\xe6/\x99]\xe8\xcdi\x13\x88\xc0\x94\x10\r1U\xb1\xb7\x8e\x96\x42\x14\x66\x64\xdc\xd1\x16\b|\xbd\xd8\xa9\xde\x89\xb0\xab\xd4[\xf8\x92&D\xe1-qt\xea\xcc\xa5QQu7\xbe\\OR;!D\xa1\x37\xe7\xae\xad\x80+\xa1.\xbe\xe0\x17\xf3\x97.\xb8\xc7w\xe7i]\b-\x14_\xae|?\xca\x33\r\x13+\xf6\f\xac\xd8\x8c\xbf\xbe\xd2{\x95\xb1\x9b\x86\x63\fKM\xa3~\xf3*\x16/\xab\xa2\xe7\xc2\x45~\xb8\xba\x89\xfe\xcb\xd7\x93=\xfr\xe4\xc6\xbf\x16\xf6}\xbc\xe7o\xd1\xfc\x32\xe2\t\xcf\xf2\xe4\xd5 \"\xcag\x97\x4X\xf8\xf2\x1a\x84\x96:\x8c/\x1c=K\xebO^O(\xd7\\\x1a\xdf\xdd\xbc\x8a\xea\xa5Vh\xce*\v\xf0\xd3\x1dkX\xbb\xba\x89\xbeK\xd7\x63l\xa3\xc2[sg\xe5\x7f\r\x88!\x1c\xcf\x42\xcb\xd4\\\xf7\x46\x17\x64\xfa}\x16Y\xa5PI\xe\x80\xbc\xd9\xf9\x31\xcf\x93\0\x34-BV)\xc5\xa9\x8e\xf7\x30\f\x93\xa2\xb2\0?\xdb\xb1\x86\xbc\x19\x39\x31\x4\x62\xa6\\h\xf7\xc6s\x8cWX\b\x81?^\xa1\xd0\xc8\xf|s\x13\xba\x94\x91\xa9\xc6\x9a\xc2\x39\xcb\xaaXT\xff`B\x87\xc2%X\xbdi\x15\xf7\xdb\xca\x1e\xd8\xdd\xc1\xb6\xcd{\xb8\xef\x8b\vyz\xcb\xa3\xcc.\v\xf0\xd2\xce\x35<\xf5\xedW\x12\x94\xb6\x31=~\f\xf1\n\v\xa5\xc6Oa\xc6X\x10\x63L\xb7\xcf\x41\x8cQ\x1d},\x88\xd4\xcd\x4[M\xd3xt\xd3#Qd\x8f\xb0\xe3\xf9=(\x5\x1d\xfb\xbb\xd9\xf2\x83\xdf`\x18&\xc5\x65\x1^\xde\xd9\xc0'\xe2\x94\xb6U\xd2R\x11\x46\xa1\xc6M:\xe9,\x8b!\xc3o=\xb7\x92\xfbl\xb2\aw\x1f\xe1\xd7\x9b\x9bQ2\x12\x30G\xf6\x9f\xe2\x85g\"\xa4_\xfdU\x3\xb9\xb9\xde\xe4\xcb\x9c\x13\xe1\xa9\x80\x10P\xf9`%\0\xc3\xd7\x46hm\xda\x8bRDFl\x9f\xdb\xfts\xf2\xd8y\0J\xca\x2\xcc\x99;3\xa5(SNX\x1J\xc2\x9e\xe7\x9b\x91R\xe2\xf3g\xb3v[\x3\xbe\\oL\n\x10\x42\xf0\xbd\xc6Z\xaa\xaa\xe7\x1\xb0\xaf\xad\x8b\xee\xb7\xdfO\xe9\x7f\xca\t\x87\x14\xeal=\xcak\x1b_GJIqy\x11\xcfno \xfb\xe\xafM\x16\x9el\xac\xe5\xabu\x8b\x11\x42\xb0\xbf\xad\x8b\x8d\xebvaJ\x99\xd2\x7f\x42\x1e\x8e z\xe-\x14V\x97\x63H\x85\t\x98\xd6n\vSA^\xa9\xf3\n~\xb4\xa5\x13S\xc1\xaa\r+(\xa9(\xa2q{\x3\xcf\xd5\xbf\xca\xf2\xa7\x96\xb2\xa4n1\0\a\xda\xba\xd8\xb4n\x17\xa6\xa9\xc2\xbdN\x92p,Y!\x4\x95?^\x91\xd4\x99\x32\x63\x15R@Gk'&\xf0\xd8\x86\x15\x94V\x14\xd1\xf4\xc7\xf5\xe4\xf8\xb3\x1\xf8\xcb\xde.^\xf8\xd1.\xa4\x94\t\xfdM\x82\xb0\x85\x1b\x1f]b\xe4\xc3>\\>\xf\xa8\x88\x2N\xe7\xf3\x87\xcf\x38\xfaho\xe9\xc4\x44\xf0\xf8\x86\xba\x30\xd9\x43{\xbb\xd8\xb2\xeewae\xd3\x45J\xc2\xc1+\x83\x9cx\xe2\xe7\x8e{X\xa7\xfd\xf0xJ\x1dn\xe9\x44*X\xddXK\xfb\xbeSl]\xff\x1aRN\x8clJ\xc2i\xe7\xdd\x34q\xa8\xb5\x93?\xb7\x1e#\xa8@&\x19\\2$\xcd\x12\x13\x1f\x7fj(uk\x8eS\x86\x84\xdb\xef\x63\xd1\xc6\xc7\xc9\xf4Y\xab\x90\xf3!P\nN\xee=N\xc7\xee\xf6p\xdb\x65O.\xa1\xbc\xba<\xd6VE\xb5\xb1\xcb\xfa\xfb\ai|\xe6\xb7\xe8\x63zJ\xcdS\x12\xf6\xdf\x33\x8b\xc0\xa2\x8aTf\x96\x33o\x16oF\x11\xae~\xe8\xf3\xf8\v\x1c\xf7R1\x98\xf\xdc=w&\xdd\xa7?L\xddGZL\0\xa5\x14o\xbd\xd8\x8ci\xe7\x61I$\xf\xdf\xf5\xd9\x39\xcc{\xa0\x92\x8cL\x97\xe3lw\xbe\xd1\xc5\xf9\xee\x7f\"\x1\x33\xaa\x9d\xcb\x9d\x41\xfd\xda\x1a\x84\x10\x64\x66$\xbc\\L\x9cp\xcc\xf4(8\xd7|\x98\xa0T\tYB\x2\xf3\x1e\xa8Llc\xe3\xdd\Line truncated
|
||||
windowIconText=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x11Qt.WindowModality\x94\x93\x94\x8c\bNonModal\x94\x86\x94R\x94.)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
x_entry=
|
||||
x_grid=false
|
||||
x_label=
|
||||
x_limits=@Variant(\0\0\0\x1a\0\0\0\0\0\0\0\0?\xf0\0\0\0\0\0\0)
|
||||
x_log=false
|
||||
x_mode=auto
|
||||
y_grid=false
|
||||
y_label=
|
||||
y_limits=@Variant(\0\0\0\x1a\0\0\0\0\0\0\0\0?\xf0\0\0\0\0\0\0)
|
||||
y_log=false
|
||||
|
||||
[BECMainWindowNoRPC.AdvancedDockArea.ModularToolBar.QWidget.DarkModeButton]
|
||||
acceptDrops=false
|
||||
accessibleDescription=
|
||||
accessibleIdentifier=
|
||||
accessibleName=
|
||||
autoFillBackground=false
|
||||
baseSize=@Size(0 0)
|
||||
contextMenuPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0\x66\x80\x4\x95[\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x14Qt.ContextMenuPolicy\x94\x93\x94\x8c\x12\x44\x65\x66\x61ultContextMenu\x94\x86\x94R\x94.)
|
||||
cursor=@Variant(\0\0\0J\0\0)
|
||||
dark_mode_enabled=false
|
||||
enabled=true
|
||||
focusPolicy=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0U\x80\x4\x95J\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\xeQt.FocusPolicy\x94\x93\x94\x8c\aNoFocus\x94\x86\x94R\x94.)
|
||||
font=@Variant(\0\0\0@\0\0\0$\0.\0\x41\0p\0p\0l\0\x65\0S\0y\0s\0t\0\x65\0m\0U\0I\0\x46\0o\0n\0t@*\0\0\0\0\0\0\xff\xff\xff\xff\x5\x1\0\x32\x10)
|
||||
geometry=@Rect(0 0 40 40)
|
||||
inputMethodHints=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.InputMethodHint\x94\x93\x94\x8c\aImhNone\x94\x86\x94R\x94.)
|
||||
layoutDirection=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0]\x80\x4\x95R\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.LayoutDirection\x94\x93\x94\x8c\vLeftToRight\x94\x86\x94R\x94.)
|
||||
locale=@Variant(\0\0\0\x12\0\0\0\n\0\x65\0n\0_\0\x43\0H)
|
||||
maximumSize=@Size(40 40)
|
||||
minimumSize=@Size(40 40)
|
||||
mouseTracking=false
|
||||
palette=@Variant(\0\0\0\x44\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1??MMQQWW\0\0\x1\x1\x66\x66MMQQWW\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0\x1\x1\xff\xff\xbf\xbf\xbf\xbf\xbf\xbf\0\0\x1\x1\xff\xff\xa9:\xa9:\xa9:\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\xf8\xf8\xf9\xf9\xfa\xfa\0\0\x1\x1\xff\xff\0\0\0\0\0\0\0\0\x1\x1\x7f\x7f\x61\x61\x9e\x9e\xef\xef\0\0\x1\x1\xff\xffMMQQWW\0\0\x1\x1\xff\xff\x1a\x1ass\xe8\xe8\0\0\x1\x1\xff\xff\x66\x66\0\0\x98\x98\0\0\x1\x1\xff\xff\xf5\xf5\xf5\xf5\xf5\xf5\0\0)
|
||||
sizeIncrement=@Size(0 0)
|
||||
sizePolicy=@Variant(\0\0\0K\0\0\0U)
|
||||
statusTip=
|
||||
styleSheet=
|
||||
tabletTracking=false
|
||||
toolTip=
|
||||
toolTipDuration=-1
|
||||
updatesEnabled=true
|
||||
visible=true
|
||||
whatsThis=
|
||||
windowFilePath=
|
||||
windowIcon="@Variant(\0\0\0\x45\0\0\0\x1\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0,\0\0\0,\b\x6\0\0\0\x1e\x84Z\x1\0\0\x1\x81iCCPsRGB IEC61966-2.1\0\0(\x91u\x91\xb9KCA\x10\x87\xbf\x1c\x1ehB\x4-,,\x82\x44\xab(^\x4m\x4\x13\x44\x85 !F\xf0j\x92g\xe!\xc7\xe3\xbd\x88\x4[\xc1\x36\xa0 \xdax\x15\xfa\x17h+X\v\x82\xa2\bb\x9dZ\xd1\x46\xc3s\x9e\x11\x12\xc4\xcc\x32;\xdf\xfevg\xd8\x9d\x5k$\xad\x64t\xfb\0\x64\xb2y-<\xe5w/,.\xb9\x9bJ4\xd0\x88\x13;#QEW'B\xa1 u\xed\xe3\x1\x8b\x19\xef\xfa\xccZ\xf5\xcf\xfdk\xad\xabq]\x1K\xb3\xf0\xb8\xa2jy\xe1i\xe1\xe0\x46^5yW\xb8\x43IEW\x85\xcf\x85\xbd\x9a\\P\xf8\xde\xd4\x63\x15.\x99\x9c\xac\xf0\x97\xc9Z$\x1c\0k\x9b\xb0;Y\xc3\xb1\x1aVRZFX^\x8e'\x93^W~\xef\x63\xbe\xc4\x11\xcf\xce\xcfI\xec\x16\xef\x42'\xcc\x14~\xdc\xcc\x30I\0\x1f\x83\x8c\xc9\xec\xa3\x8f!\xfa\x65\x45\x9d\xfc\x81\x9f\xfcYr\x92\xab\xc8\xacR@c\x8d$)\xf2xE]\x97\xeaq\x89\t\xd1\xe3\x32\xd2\x14\xcc\xfe\xff\xed\xab\x9e\x18\x1e\xaaTw\xf8\xa1\xe1\xc5\x30\xdez\xa0i\a\xca\x45\xc3\xf8<6\x8c\xf2\t\xd8\x9e\xe1*[\xcd\xcf\x1d\xc1\xe8\xbb\xe8\xc5\xaa\xe6\x39\x4\xd7\x16\\\\W\xb5\xd8\x1e\\nC\xe7\x93\x1a\xd5\xa2?\x92M\xdc\x9aH\xc0\xeb\x19\x38\x17\xa1\xfd\x16Z\x96+=\xfb\xdd\xe7\xf4\x11\"\x9b\xf2U7\xb0\x7f\0\xbdr\xde\xb5\xf2\r+\xf4g\xcbwA\x5\xc7\0\0\0\tpHYs\0\0=\x84\0\0=\x84\x1\xd5\xac\xaft\0\0\x6\xdaIDATX\x85\xed\x99mp\x15W\x19\xc7\x7fgor\xc3\xbd\xb9I/\x91$\xb7XB^J\x81\x84*F\xd3\x32\x86ON\xd5\nh\xb4\x36\x43p\xa0\xadT3\x9d\x86\xfa\xc1R-\x1a\x64\xca@\xeb\xe0\x94\xce\xd8\xb4\xe\x38\x3\xbe\xd1J\x98I\xc6\t\x16\x11\x61\x98\x90\x12\xa0)\x4\xa5-\xa0\xd4\xb6\xa9\x91\x4\b$$$\xdc\xdd=\xc7\xf\xbb\xf7}s\xefM\x88\xce\x38\xc3\x7f\x66gw\xcfy\xces\xfe\xe7\x7f\x9e}\xce\xd9]\xb8\x8d\xdb\xf8\xff\x86H\xc7\x66\xfb\xa7\x1f\x99\x93\xe5r\xd7(\xb4O\xe9(\x7fP\xa1\x19h\x18\n\x82\x80\x1\x18\x12\fD\xf8^W\x1a:`(\xd0\xb1\xe\x43\n\xeb\f\x4\x95\xc0\x4\x19\x84k\x86\x14\x7f\xbd\xa1\x8f\xfd\xe1\xf4\xfb\xbf\xfc;\xa0&M\xf8\x95\x5\x35\xb3\n\xb2\xf2\xb7*\xa1=\xa4\x83\x66u.\xd0U\x88\x94M\xc0>[\xe5V\xbdn\xd7\x1bv\xb9\x8e \xdc><\x90\x88\xaf\xa0\x12\xd2P\xb2\xe5\xfa\xf5K\xdf\xbf\xd0\xfb\xfb\x9e\xf1\x38i\xe3U\xec\xacX^U\xe4\xc9?\x91\xa1\x89\x87\x93\xd9M\b!y\x1c\x34\x14\xa0!\xb4\x87\xa7\xf9\n\xdf*-}\xacj<\x17\x8e\x44\x9a\xe6/\x99]\xe8\xcdi\x13\x88\xc0\x94\x10\r1U\xb1\xb7\x8e\x96\x42\x14\x66\x64\xdc\xd1\x16\b|\xbd\xd8\xa9\xde\x89\xb0\xab\xd4[\xf8\x92&D\xe1-qt\xea\xcc\xa5QQu7\xbe\\OR;!D\xa1\x37\xe7\xae\xad\x80+\xa1.\xbe\xe0\x17\xf3\x97.\xb8\xc7w\xe7i]\b-\x14_\xae|?\xca\x33\r\x13+\xf6\f\xac\xd8\x8c\xbf\xbe\xd2{\x95\xb1\x9b\x86\x63\fKM\xa3~\xf3*\x16/\xab\xa2\xe7\xc2\x45~\xb8\xba\x89\xfe\xcb\xd7\x93=\xfr\xe4\xc6\xbf\x16\xf6}\xbc\xe7o\xd1\xfc\x32\xe2\t\xcf\xf2\xe4\xd5 \"\xcag\x97\x4X\xf8\xf2\x1a\x84\x96:\x8c/\x1c=K\xebO^O(\xd7\\\x1a\xdf\xdd\xbc\x8a\xea\xa5Vh\xce*\v\xf0\xd3\x1dkX\xbb\xba\x89\xbeK\xd7\x63l\xa3\xc2[sg\xe5\x7f\r\x88!\x1c\xcf\x42\xcb\xd4\\\xf7\x46\x17\x64\xfa}\x16Y\xa5PI\xe\x80\xbc\xd9\xf9\x31\xcf\x93\0\x34-BV)\xc5\xa9\x8e\xf7\x30\f\x93\xa2\xb2\0?\xdb\xb1\x86\xbc\x19\x39\x31\x4\x62\xa6\\h\xf7\xc6s\x8cWX\b\x81?^\xa1\xd0\xc8\xf|s\x13\xba\x94\x91\xa9\xc6\x9a\xc2\x39\xcb\xaaXT\xff`B\x87\xc2%X\xbdi\x15\xf7\xdb\xca\x1e\xd8\xdd\xc1\xb6\xcd{\xb8\xef\x8b\vyz\xcb\xa3\xcc.\v\xf0\xd2\xce\x35<\xf5\xedW\x12\x94\xb6\x31=~\f\xf1\n\v\xa5\xc6Oa\xc6X\x10\x63L\xb7\xcf\x41\x8cQ\x1d},\x88\xd4\xcd\x4[M\xd3xt\xd3#Qd\x8f\xb0\xe3\xf9=(\x5\x1d\xfb\xbb\xd9\xf2\x83\xdf`\x18&\xc5\x65\x1^\xde\xd9\xc0'\xe2\x94\xb6U\xd2R\x11\x46\xa1\xc6M:\xe9,\x8b!\xc3o=\xb7\x92\xfbl\xb2\aw\x1f\xe1\xd7\x9b\x9bQ2\x12\x30G\xf6\x9f\xe2\x85g\"\xa4_\xfdU\x3\xb9\xb9\xde\xe4\xcb\x9c\x13\xe1\xa9\x80\x10P\xf9`%\0\xc3\xd7\x46hm\xda\x8bRDFl\x9f\xdb\xfts\xf2\xd8y\0J\xca\x2\xcc\x99;3\xa5(SNX\x1J\xc2\x9e\xe7\x9b\x91R\xe2\xf3g\xb3v[\x3\xbe\\oL\n\x10\x42\xf0\xbd\xc6Z\xaa\xaa\xe7\x1\xb0\xaf\xad\x8b\xee\xb7\xdfO\xe9\x7f\xca\t\x87\x14\xeal=\xcak\x1b_GJIqy\x11\xcfno \xfb\xe\xafM\x16\x9el\xac\xe5\xabu\x8b\x11\x42\xb0\xbf\xad\x8b\x8d\xebvaJ\x99\xd2\x7f\x42\x1e\x8e z\xe-\x14V\x97\x63H\x85\t\x98\xd6n\vSA^\xa9\xf3\n~\xb4\xa5\x13S\xc1\xaa\r+(\xa9(\xa2q{\x3\xcf\xd5\xbf\xca\xf2\xa7\x96\xb2\xa4n1\0\a\xda\xba\xd8\xb4n\x17\xa6\xa9\xc2\xbdN\x92p,Y!\x4\x95?^\x91\xd4\x99\x32\x63\x15R@Gk'&\xf0\xd8\x86\x15\x94V\x14\xd1\xf4\xc7\xf5\xe4\xf8\xb3\x1\xf8\xcb\xde.^\xf8\xd1.\xa4\x94\t\xfdM\x82\xb0\x85\x1b\x1f]b\xe4\xc3>\\>\xf\xa8\x88\x2N\xe7\xf3\x87\xcf\x38\xfaho\xe9\xc4\x44\xf0\xf8\x86\xba\x30\xd9\x43{\xbb\xd8\xb2\xeewae\xd3\x45J\xc2\xc1+\x83\x9cx\xe2\xe7\x8e{X\xa7\xfd\xf0xJ\x1dn\xe9\x44*X\xddXK\xfb\xbeSl]\xff\x1aRN\x8clJ\xc2i\xe7\xdd\x34q\xa8\xb5\x93?\xb7\x1e#\xa8@&\x19\\2$\xcd\x12\x13\x1f\x7fj(uk\x8eS\x86\x84\xdb\xef\x63\xd1\xc6\xc7\xc9\xf4Y\xab\x90\xf3!P\nN\xee=N\xc7\xee\xf6p\xdb\x65O.\xa1\xbc\xba<\xd6VE\xb5\xb1\xcb\xfa\xfb\ai|\xe6\xb7\xe8\x63zJ\xcdS\x12\xf6\xdf\x33\x8b\xc0\xa2\x8aTf\x96\x33o\x16oF\x11\xae~\xe8\xf3\xf8\v\x1c\xf7R1\x98\xf\xdc=w&\xdd\xa7?L\xddGZL\0\xa5\x14o\xbd\xd8\x8ci\xe7\x61I$\xf\xdf\xf5\xd9\x39\xcc{\xa0\x92\x8cL\x97\xe3lw\xbe\xd1\xc5\xf9\xee\x7f\"\x1\x33\xaa\x9d\xcb\x9d\x41\xfd\xda\x1a\x84\x10\x64\x66$\xbc\\L\x9cp\xcc\xf4(8\xd7|\x98\xa0T\tYB\x2\xf3\x1e\xa8Llc\xe3\xdd\Line truncated
|
||||
windowIconText=
|
||||
windowModality=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0Y\x80\x4\x95N\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x11Qt.WindowModality\x94\x93\x94\x8c\bNonModal\x94\x86\x94R\x94.)
|
||||
windowModified=false
|
||||
windowOpacity=1
|
||||
windowTitle=
|
||||
|
||||
[BECMainWindowNoRPC.AdvancedDockArea.dockSettingsAction]
|
||||
autoRepeat=true
|
||||
checkable=false
|
||||
checked=false
|
||||
enabled=true
|
||||
font=@Variant(\0\0\0@\0\0\0$\0.\0\x41\0p\0p\0l\0\x65\0S\0y\0s\0t\0\x65\0m\0U\0I\0\x46\0o\0n\0t@*\0\0\0\0\0\0\xff\xff\xff\xff\x5\x1\0\x32\x10)
|
||||
icon="@Variant(\0\0\0\x45\0\0\0\x1\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0,\0\0\0,\b\x6\0\0\0\x1e\x84Z\x1\0\0\0\tpHYs\0\0\v\x13\0\0\v\x13\x1\0\x9a\x9c\x18\0\0\x4\xc9IDATX\x85\xed\x99]lSu\x18\xc6\x7f\xef\xe9\x6\x85\xb0\xa0\xc1\x8b\xe9\x2\x46\xe3`\xa0!\xa0\x88\x6\x35*\x9a\xb9\x96\xb6\v\xed*\x82\x80\x46\x12\xe3\x95\x1a\x14I\xc4\x8b\xde(~$&&\x10/d\x17\x80\xdf]7i\xb7u \xc1%\x9a\x18\xd4\xa0\xf1\x3\x18j\"D\x3\xc6\xc4\x44\x18\xee\xa3=\xff\xd7\v$t]{\xce\x61\xad\xf3\x66\xcf\xddy\xdf\xe7<\xefs\xcey\xfb?\xef\xf9\x17\xa6\x30\x85\x31\x90j\x88\xb4\x84\xa2\xab\x5iu\xe2(\xba\xaf\xaf\xbb\xb3\xab\xd2Z5\x95\n\0 \xb2\x1a\xd8\xe0\x42\xb2\x80\x8a\r[\x95\n\0\b,\xf4\xc0i\xaa\x46\xadj\x18\x16\xbc\x99i\xa2\n-X\xb1\xe1P(~\r0\xcb\x3\xb5.\x10\x88\x36TZ\xcf\xd1p(\x14o\b\x6\xe3\xf5N\x9c\x9c\xcf\xb8\xb6\xc3\x45H\xad:r\x83\xc1x}(\x14w\xbc(G\xc3y1;\xd5gN\x6\xc3\xb1]\xcd\xe1\xd8\xb8\xc7\x1e\x88\xc4\x96Z\x86-\xde\xec\x82\xaal[\xd5\xda\xb6\xa2\x38\xde\x12\x89/h\tG\xdfR\x9f\x39i\x8b\xd9\xe1\xa4Q\xb6\xa7VE\xa2\xf7\x1a\x95\x43\x45\xe1\x8c\x31\xf2Z~\x9a}tZNv \xf2\x90W\xb3\x63\xabJ\x97\xed\xb3\x9f\xf0\xd9\xd2\xa8*[\x4\"\x85^,\xd1\x95=\xe9\xceO<\x1b\x8e\xc7\xe3\xbe\xc1\x11\xf3\x15\xca\x92\x32%\x87\x1\xff\x84\xccz\xd1\x10\xbe\x99\x35\xddZ\x96L&\xed\xe2T\xc9\x96\x18\x1c\x31\x8f\x38\x98\xa5l\xa1\xcb\x43y\re\xc9\xf9!\xddX*5\xee\xeG\"\x91\xba\x9c\xd6\x9e\0\x1c\x7fl\x93\x80\x33\x43~\xab\xb1?\x99\x1c,\f\x8e\xbb\xc3\x39\xad\xdd\xca\xffo\x16\xa0~\xe6\x88n-\xe\x8e\xb9\xc3\xe1p|^\x1e\x33@u\x1ey50\\\x83\xb5 \x93I\x9e\xba\x18\x18s\x87m\xb1\x1f\xa6\x12\xb3\xa2{\xc0\xba\xa7Vr\xf5\x62[W\x83\xb9\xf\xf8`\xc2z\xe0\xcf\x61\xd6\x15\x6\xc6\f?\xcbo^\xfc\xca\xe1#\xdf\x8e\xa2\xb2\x1d\xa8\xbd\f\xe1?Ty\xb4/\xd3\xd9[\x14?\x3\x1cj\x89\xb4}(\xaa\xed\xc0\x15\x97\xa1\x39\x82\xf0l_:\xb5\xb3\x30XrYk\tGo\x15\xe4}\xe0z/\xcajh\xe9\xebI\xedw\xe2\x4\xc3\xb1\x98\x42\x87G\xb3\xdfY\xc6\xac\xeb\xe9\xe9\xfa\xbe\x38QrY\xeb\xcbt~I\xde\xbf\x14x\xd7MY\xa0\xdd\xcd,@o&\x95\xc2K{(o\f\x9d\xab[^\xca,8\xcc\xc3\xd9\xec;g\x81\xf5\x81p\xb4\x1\xe4\xeer<\xa3V\xbb\xab\x89\x7f!b\xdaU\xad\x35\x65\xf3\xca\xc1\xde\xee\xd4\xd3N\x1an\xd3\x9a\"\xe2\x38\x89\xf9kr\xc7\\4.\x89\xe5\xc4\x91\xab\x16W\xbaiTe\x80\x9fL\xb8\x19\x16T\a\x9d\b\xc3\xf9\xda\xaa\x8d\x97\xa8\xfc\xe5\xa6Q\xb6\x87\xef\x8f\xc7gO\x1b\x36o*\x94\xed_\0K\xcc&\xe0s\xb7\x42\0\xaa\xd6&\x17\xc6\xca`8\xb6+?2\xf3\xa9\x3\a\xf6\x9e/\xc5(\xb9\xac\x5[c\xb7\x61xO\xe1:OF\xaa\xbd\xac\t'\x8c\xea\xda\xfd\x99\xce#\xc5)_\xe1\x41\"\x91\xb0\x66\xd4]\xf5\x1c\xca\xdb\xc0\x1cO\xe2\x80\b\xf\xdc\x30\x7f\xd1\xf?\x9d\x38\xf6\x63\xa9|K\xa4-\n\xb4\xe3\xfd-:G\x90\xc7\x1a\x9b\x16\xfe\xbd~\xed\x9a\xc3\xfd\xfd\xfdz\xe9Z\n\x10\b\xb7=\xf\xfa\xa2W\xa3\xc5P\xd8+X\xed\x62\x33`\xfbm\x91Qk\x11\xa2\x8f\v<8QM\x90m\xd9L\xc7K\x17\x8f\xc6\xf4\xb0m\xe5\xf6\xfaL\xcd\v\xc0\x8c\tI\xc3\x6\x30\x1b\xd4\aVN@\xd4\xfd$g\f\x91\x37{\n\x3\x63Z\xe2\xe7\x81\x81\xb3\x8d\v\x16NwzQL2\xb6g{:\xd3\x85\x81q\xcb\xda\x90\xdf\xf7*pz\xd2,\x95\xc7\xe9!\xbf\xf5Zq\xd0W\x1c\xf8\xe5\xe8\xd1\xd1\xf9\xf3o\xfc\x13\xc1q\xaf\xec\xbf\x86\xa2O\x1e\xecJ}Q\x1c/\xf9\xe2X\xbe\xec\xa6\xdd\xc0\xd7\xez\xc3U\xf0\xe4\xa4q\xe4\xf6[\x16\xef)\x95(i8\x91H\x18U\xd9\\\x14V\xe0#\xcb\x92;\xec\x1as-\x90\x9a\x98OP\xd8G\xde\x9a\x87\xe8\x9d\n\xfbJ\xd8\xda\x9cH$L\xa9s\x1d\xf7\xba\x2\xe1X\x17\x10\0v\xabX\xaf\xf7\xa5\x93\x3\x85\xf9U\xadm+T\xf5\x65U\xee\xf2\x62T\x84Om[\xb6\xed\xef\xe9\xf8\xec\x82\xef\vh\xe\xc7\x9a,\xf4\x19\x41\x36\"\xd2\x93MwD\xcbj8\x15\b\x85\xe2\r\x96\x35\x92O\xa7\xd3\xbf\x97\xbf\xa8\xb6\x66P\xd7y\xf8\x82\x61\xd3\xdc\x9b\xee\xfa\xb8\\>\x18\x8c\xd7[\x16\xbe\xee\xee\xe4o\x13\x32\xec\x5\xcd\xad\xads}\xa6\xe6\x94;\x13\xc8\xeb\xdcl\xb6\xf3\xd7J\xeaUc\a^\x2\xe1\xd8Y\xdcw0\xcf\x65\x33\xa9\xd9\x14\xb4\xc2\x44P\x8dyX\x81\xe3\x1ex\xc7\xa9\xd0,Ti\x80Wp\xff\xea\x10\xf5\xfc\x65\xe2\x84\xaa\xfc\xc7\x61!)\xd0Q'\x8e\x81\xb4S~\nS\x98 \xfe\x1\x1\xb5\x93\xa4\x97\x89\xb7\xcb\0\0\0\0IEND\xae\x42`\x82)"
|
||||
iconText=Dock settings
|
||||
iconVisibleInMenu=false
|
||||
menuRole=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0`\x80\x4\x95U\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\rPySide6.QtGui\x94\x8c\x10QAction.MenuRole\x94\x93\x94\x8c\x11TextHeuristicRole\x94\x86\x94R\x94.)
|
||||
priority=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0]\x80\x4\x95R\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\rPySide6.QtGui\x94\x8c\x10QAction.Priority\x94\x93\x94\x8c\xeNormalPriority\x94\x86\x94R\x94.)
|
||||
shortcut=
|
||||
shortcutContext=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0`\x80\x4\x95U\0\0\0\0\0\0\0\x8c\bbuiltins\x94\x8c\agetattr\x94\x93\x94\x8c\xePySide6.QtCore\x94\x8c\x12Qt.ShortcutContext\x94\x93\x94\x8c\xeWindowShortcut\x94\x86\x94R\x94.)
|
||||
shortcutVisibleInContextMenu=false
|
||||
statusTip=
|
||||
text=Dock settings
|
||||
toolTip=Dock settings
|
||||
visible=true
|
||||
whatsThis=
|
||||
|
||||
[Perspectives]
|
||||
1\Name=test
|
||||
1\State="@ByteArray(\0\0\x1\xb3x\xdau\x90\x41O\xc3\x30\f\x85\xef\xfc\n+\xf7\xd1\xae\x12h\x87\x34\xd3V\xd8\x11\x98\xba\xb1sh\xcc\x14\xadMP\x92V\x3\xf1\xe3q\n\x8aV\x4\xa7\xd8\xcf/\xdfK\xcc\x97\xe7\xae\x85\x1\x9d\xd7\xd6\x94l~\x9d\x33@\xd3X\xa5\xcd\xb1\x64\xfb\xdd\x66\xb6`K\xc1\xb7\x61\xa5\x6i\x1aTw\xb6\x39\xd1\xac~\xf7\x1;xN\x17\x19\xec=\xba\xd4\x13\xa6\xb2&HmH\x89\x63\xc1S\xf\x9b\xd6\xca\x30\x6\xe4\xa4\xd7o\xad\xe\x81\xe4G\xa7\x91,a\x4|F@oB\xc9\n\xf2\xac\x1cJ\xd8\xc9\x97\x11\x5U\xef\x1c\xc6\xd1\x41\xe\xf8j]G\x8e\x83VG\f\xf0 ;\xbc\xd0\xa1j\xadG\x15\x83\x32\xc1\xb3\x88\x99\xc0\x8a\v\xd8\xfa\xbe\xda\xf6\xd8\xe3oX\xd2\xa7\xb0\x89\xe7\xc9z\x1d\xdf\x8dnm\xcf\x7f\xa7\xd6\xfa\x3\xbdX\xe4\x5\xcc\x8b\x9b[\xe0\xd9\xb7@\xe7\xcf\xff\xa9L+\xa2\xfa\x9f\x95\x8b\xab/_\xa2\x8f\x42)"
|
||||
size=1
|
||||
|
||||
[mainWindow]
|
||||
DockingState="@ByteArray(\0\0\x1\xb3x\xdau\x90\x41O\xc3\x30\f\x85\xef\xfc\n+\xf7\xd1\xae\x12h\x87\x34\xd3V\xd8\x11\x98\xba\xb1sh\xcc\x14\xadMP\x92V\x3\xf1\xe3q\n\x8aV\x4\xa7\xd8\xcf/\xdfK\xcc\x97\xe7\xae\x85\x1\x9d\xd7\xd6\x94l~\x9d\x33@\xd3X\xa5\xcd\xb1\x64\xfb\xdd\x66\xb6`K\xc1\xb7\x61\xa5\x6i\x1aTw\xb6\x39\xd1\xac~\xf7\x1;xN\x17\x19\xec=\xba\xd4\x13\xa6\xb2&HmH\x89\x63\xc1S\xf\x9b\xd6\xca\x30\x6\xe4\xa4\xd7o\xad\xe\x81\xe4G\xa7\x91,a\x4|F@oB\xc9\n\xf2\xac\x1cJ\xd8\xc9\x97\x11\x5U\xef\x1c\xc6\xd1\x41\xe\xf8j]G\x8e\x83VG\f\xf0 ;\xbc\xd0\xa1j\xadG\x15\x83\x32\xc1\xb3\x88\x99\xc0\x8a\v\xd8\xfa\xbe\xda\xf6\xd8\xe3oX\xd2\xa7\xb0\x89\xe7\xc9z\x1d\xdf\x8dnm\xcf\x7f\xa7\xd6\xfa\x3\xbdX\xe4\x5\xcc\x8b\x9b[\xe0\xd9\xb7@\xe7\xcf\xff\xa9L+\xa2\xfa\x9f\x95\x8b\xab/_\xa2\x8f\x42)"
|
||||
Geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\x1d\0\0\b\x1a\0\0\x3\xea\0\0\0\0\0\0\0\0\xff\xff\xff\xff\xff\xff\xff\xff\0\0\0\x1\0\0\0\0\xf\0\0\0\0\0\0\0\0\x1d\0\0\b\x1a\0\0\x3\xea)
|
||||
State=@Variant(\0\0\0\x7f\0\0\0\x18PySide::PyObjectWrapper\0\0\0\0\xf\x80\x4\x95\x4\0\0\0\0\0\0\0\x43\0\x94.)
|
||||
|
||||
[manifest]
|
||||
widgets\1\closable=true
|
||||
widgets\1\floatable=true
|
||||
widgets\1\movable=true
|
||||
widgets\1\object_name=BECQueue
|
||||
widgets\1\widget_class=BECQueue
|
||||
widgets\2\closable=true
|
||||
widgets\2\floatable=true
|
||||
widgets\2\movable=true
|
||||
widgets\2\object_name=PositionerBox
|
||||
widgets\2\widget_class=PositionerBox
|
||||
widgets\3\closable=true
|
||||
widgets\3\floatable=true
|
||||
widgets\3\movable=true
|
||||
widgets\3\object_name=Waveform
|
||||
widgets\3\widget_class=Waveform
|
||||
widgets\size=3
|
||||
+67
-64
@@ -1,17 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from bec_qthemes import material_icon
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtWidgets import QComboBox, QSizePolicy, QWidget
|
||||
from qtpy.QtGui import QFont
|
||||
from qtpy.QtWidgets import QComboBox, QSizePolicy
|
||||
|
||||
from bec_widgets import SafeSlot
|
||||
from bec_widgets.utils.toolbars.actions import MaterialIconAction, WidgetAction
|
||||
from bec_widgets.utils.toolbars.bundles import ToolbarBundle, ToolbarComponents
|
||||
from bec_widgets.utils.toolbars.connections import BundleConnection
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
|
||||
is_profile_readonly,
|
||||
list_profiles,
|
||||
)
|
||||
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import list_quick_profiles
|
||||
|
||||
|
||||
class ProfileComboBox(QComboBox):
|
||||
@@ -21,22 +18,47 @@ class ProfileComboBox(QComboBox):
|
||||
super().__init__(parent)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
|
||||
def refresh_profiles(self):
|
||||
"""Refresh the profile list with appropriate icons."""
|
||||
def refresh_profiles(self, active_profile: str | None = None):
|
||||
"""
|
||||
Refresh the profile list and ensure the active profile is visible.
|
||||
|
||||
current_text = self.currentText()
|
||||
Args:
|
||||
active_profile(str | None): The currently active profile name.
|
||||
"""
|
||||
|
||||
current_text = active_profile or self.currentText()
|
||||
self.blockSignals(True)
|
||||
self.clear()
|
||||
|
||||
lock_icon = material_icon("edit_off", size=(16, 16), convert_to_pixmap=False)
|
||||
quick_profiles = list_quick_profiles()
|
||||
quick_set = set(quick_profiles)
|
||||
|
||||
for profile in list_profiles():
|
||||
if is_profile_readonly(profile):
|
||||
self.addItem(lock_icon, f"{profile}")
|
||||
# Set tooltip for read-only profiles
|
||||
self.setItemData(self.count() - 1, "Read-only profile", Qt.ToolTipRole)
|
||||
else:
|
||||
self.addItem(profile)
|
||||
items = list(quick_profiles)
|
||||
if active_profile and active_profile not in quick_set:
|
||||
items.insert(0, active_profile)
|
||||
|
||||
for profile in items:
|
||||
self.addItem(profile)
|
||||
idx = self.count() - 1
|
||||
|
||||
# Reset any custom styling
|
||||
self.setItemData(idx, None, Qt.FontRole)
|
||||
self.setItemData(idx, None, Qt.ToolTipRole)
|
||||
self.setItemData(idx, None, Qt.ForegroundRole)
|
||||
|
||||
if active_profile and profile == active_profile:
|
||||
tooltip = "Active workspace profile"
|
||||
if profile not in quick_set:
|
||||
font = QFont(self.font())
|
||||
font.setItalic(True)
|
||||
font.setBold(True)
|
||||
self.setItemData(idx, font, Qt.FontRole)
|
||||
self.setItemData(idx, self.palette().highlight().color(), Qt.ForegroundRole)
|
||||
tooltip = "Active profile (not in quick select)"
|
||||
self.setItemData(idx, tooltip, Qt.ToolTipRole)
|
||||
self.setCurrentIndex(idx)
|
||||
elif profile not in quick_set:
|
||||
self.setItemData(idx, "Not in quick select", Qt.ToolTipRole)
|
||||
|
||||
# Restore selection if possible
|
||||
index = self.findText(current_text)
|
||||
@@ -44,6 +66,14 @@ class ProfileComboBox(QComboBox):
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
self.blockSignals(False)
|
||||
if active_profile and self.currentText() != active_profile:
|
||||
idx = self.findText(active_profile)
|
||||
if idx >= 0:
|
||||
self.setCurrentIndex(idx)
|
||||
if active_profile and active_profile not in quick_set:
|
||||
self.setToolTip("Active profile is not in quick select")
|
||||
else:
|
||||
self.setToolTip("")
|
||||
|
||||
|
||||
def workspace_bundle(components: ToolbarComponents) -> ToolbarBundle:
|
||||
@@ -56,17 +86,6 @@ def workspace_bundle(components: ToolbarComponents) -> ToolbarBundle:
|
||||
Returns:
|
||||
ToolbarBundle: The workspace toolbar bundle.
|
||||
"""
|
||||
# Lock icon action
|
||||
components.add_safe(
|
||||
"lock",
|
||||
MaterialIconAction(
|
||||
icon_name="lock_open_right",
|
||||
tooltip="Lock Workspace",
|
||||
checkable=True,
|
||||
parent=components.toolbar,
|
||||
),
|
||||
)
|
||||
|
||||
# Workspace combo
|
||||
combo = ProfileComboBox(parent=components.toolbar)
|
||||
components.add_safe("workspace_combo", WidgetAction(widget=combo, adjust_size=False))
|
||||
@@ -83,31 +102,31 @@ def workspace_bundle(components: ToolbarComponents) -> ToolbarBundle:
|
||||
)
|
||||
# Delete workspace icon
|
||||
components.add_safe(
|
||||
"refresh_workspace",
|
||||
"reset_default_workspace",
|
||||
MaterialIconAction(
|
||||
icon_name="refresh",
|
||||
icon_name="undo",
|
||||
tooltip="Refresh Current Workspace",
|
||||
checkable=False,
|
||||
parent=components.toolbar,
|
||||
),
|
||||
)
|
||||
# Delete workspace icon
|
||||
# Workspace Manager icon
|
||||
components.add_safe(
|
||||
"delete_workspace",
|
||||
"manage_workspaces",
|
||||
MaterialIconAction(
|
||||
icon_name="delete",
|
||||
tooltip="Delete Current Workspace",
|
||||
checkable=False,
|
||||
icon_name="manage_accounts",
|
||||
tooltip="Manage",
|
||||
checkable=True,
|
||||
parent=components.toolbar,
|
||||
label_text="Manage",
|
||||
),
|
||||
)
|
||||
|
||||
bundle = ToolbarBundle("workspace", components)
|
||||
bundle.add_action("lock")
|
||||
bundle.add_action("workspace_combo")
|
||||
bundle.add_action("save_workspace")
|
||||
bundle.add_action("refresh_workspace")
|
||||
bundle.add_action("delete_workspace")
|
||||
bundle.add_action("reset_default_workspace")
|
||||
bundle.add_action("manage_workspaces")
|
||||
return bundle
|
||||
|
||||
|
||||
@@ -128,56 +147,40 @@ class WorkspaceConnection(BundleConnection):
|
||||
def connect(self):
|
||||
self._connected = True
|
||||
# Connect the action to the target widget's method
|
||||
self.components.get_action("lock").action.toggled.connect(self._lock_workspace)
|
||||
self.components.get_action("save_workspace").action.triggered.connect(
|
||||
self.target_widget.save_profile
|
||||
)
|
||||
self.components.get_action("workspace_combo").widget.currentTextChanged.connect(
|
||||
self.target_widget.load_profile
|
||||
)
|
||||
self.components.get_action("refresh_workspace").action.triggered.connect(
|
||||
self._refresh_workspace
|
||||
self.components.get_action("reset_default_workspace").action.triggered.connect(
|
||||
self._reset_workspace_to_default
|
||||
)
|
||||
self.components.get_action("delete_workspace").action.triggered.connect(
|
||||
self.target_widget.delete_profile
|
||||
self.components.get_action("manage_workspaces").action.triggered.connect(
|
||||
self.target_widget.show_workspace_manager
|
||||
)
|
||||
|
||||
def disconnect(self):
|
||||
if not self._connected:
|
||||
return
|
||||
# Disconnect the action from the target widget's method
|
||||
self.components.get_action("lock").action.toggled.disconnect(self._lock_workspace)
|
||||
self.components.get_action("save_workspace").action.triggered.disconnect(
|
||||
self.target_widget.save_profile
|
||||
)
|
||||
self.components.get_action("workspace_combo").widget.currentTextChanged.disconnect(
|
||||
self.target_widget.load_profile
|
||||
)
|
||||
self.components.get_action("refresh_workspace").action.triggered.disconnect(
|
||||
self._refresh_workspace
|
||||
self.components.get_action("reset_default_workspace").action.triggered.disconnect(
|
||||
self._reset_workspace_to_default
|
||||
)
|
||||
self.components.get_action("delete_workspace").action.triggered.disconnect(
|
||||
self.target_widget.delete_profile
|
||||
self.components.get_action("manage_workspaces").action.triggered.disconnect(
|
||||
self.target_widget.show_workspace_manager
|
||||
)
|
||||
self._connected = False
|
||||
|
||||
@SafeSlot(bool)
|
||||
def _lock_workspace(self, value: bool):
|
||||
"""
|
||||
Switches the workspace lock state and change the icon accordingly.
|
||||
"""
|
||||
setattr(self.target_widget, "lock_workspace", value)
|
||||
self.components.get_action("lock").action.setChecked(value)
|
||||
icon = material_icon(
|
||||
"lock" if value else "lock_open_right", size=(20, 20), convert_to_pixmap=False
|
||||
)
|
||||
self.components.get_action("lock").action.setIcon(icon)
|
||||
|
||||
@SafeSlot()
|
||||
def _refresh_workspace(self):
|
||||
def _reset_workspace_to_default(self):
|
||||
"""
|
||||
Refreshes the current workspace.
|
||||
"""
|
||||
combo = self.components.get_action("workspace_combo").widget
|
||||
current_workspace = combo.currentText()
|
||||
self.target_widget.load_profile(current_workspace)
|
||||
self.target_widget.restore_user_profile_from_default()
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user