from importlib import import_module
from pathlib import Path
from typing import Union, TYPE_CHECKING
import numpy as np
from qtpy import QtCore, QtWidgets, QtGui
import qt_themes
from pymodaq_gui.managers.action_manager import QAction
from pymodaq_gui.utils.custom_app import CustomApp
from pymodaq_gui.utils import Dock
from pymodaq_gui.utils.widgets import LabelWithFont
from pymodaq_gui.utils.styling import create_font, create_icon
from pymodaq_gui.plotting.utils.plot_utils import DetachablePanel
from pymodaq_gui.utils.widgets.widget_with_label_title import WidgetWithLabelTitle
from pymodaq_utils.utils import ThreadCommand
from pymodaq_utils.config import GlobalConfig as Config
if TYPE_CHECKING:
from pymodaq.control_modules.utils import ControlModule
config = Config()
[docs]
class ControlModuleUI(CustomApp):
""" Base Class for ControlModules UIs
Attributes
----------
command_sig: Signal[Threadcommand]
This signal is emitted whenever some actions done by the user has to be
applied on the main module. Possible commands are:
See specific implementation
See Also
--------
:class:`daq_move_ui.DAQ_Move_UI`, :class:`daq_viewer_ui.DAQ_Viewer_UI`
"""
command_sig = QtCore.Signal(ThreadCommand)
# Common icon name for initialization action
INIT_ICON = 'cable'
def __init__(self, app: 'ControlModule', parent, title, settings_dock: Dock = None,):
super().__init__(parent, title=title)
self._app = app
self.settings_dock: Dock = settings_dock
self.config = config
self._ini_state = False
self._settings_widget = WidgetWithLabelTitle(self.title, closable=True, attachable=True,
expand_subwidget=True)
self._settings_widget.sig_close.connect(lambda: self.show_settings(False))
self._settings_widget.closeEvent = lambda event: self.set_action_checked('show_settings', False)
self._settings_panel = DetachablePanel(
self._settings_widget, self.settings_dock, f'{self.title} settings',
detached=self.config('pymodaq', 'control_modules', 'settings_as_popup'),
layout_config_path=('pymodaq', 'control_modules', 'settings_dock_layout'),
is_shown=lambda: self.is_action_checked('show_settings'))
@property
def app(self) -> 'ControlModule':
return self._app
def quit_fun(self) -> bool:
res = super().quit_fun()
return res
def add_setting_tree(self, tree):
self._settings_widget.insert_widget(tree)
# ---- Common action setup methods ----
def _setup_name_widget(self, toolbar: QtWidgets.QToolBar = None) -> None:
"""Add the module name label to the toolbar
Parameters
----------
toolbar: QToolBar
The toolbar to add the widget to. If None, uses default toolbar.
"""
self.add_widget('name', LabelWithFont(f'{self.title}', font_name="Tahoma",
font_size=14, isbold=True, isitalic=True),
toolbar=toolbar)
def set_init_color(self, color: QtGui.QColor):
self.get_action('name').widget.set_color(color)
def _setup_init_action(self, toolbar: QtWidgets.QToolBar = None,
action_name: str = 'init',
display_name: str = 'Initialize',
tip: str = 'Connect to selected module') -> None:
"""Add the initialization action to the toolbar
Parameters
----------
toolbar: QToolBar
The toolbar to add the action to. If None, uses default toolbar.
action_name: str
The internal name for the action (e.g., 'ini_actuator', 'ini_detector')
display_name: str
The display name for the action
tip: str
Tooltip text
"""
self._init_action_name = action_name
self.add_action(action_name, display_name, self.INIT_ICON, checkable=True,
tip=tip, icon_color=self.get_theme().red,
icon_checked_color=self.get_theme().green,
toolbar=toolbar)
@property
def init_action(self) -> QAction:
return self.get_action(self._init_action_name)
def _setup_settings_action(self, toolbar: QtWidgets.QToolBar = None) -> None:
"""Add the show settings action to the toolbar
Parameters
----------
toolbar: QToolBar
The toolbar to add the action to. If None, uses default toolbar.
"""
self.add_action('show_settings', 'Show Settings', 'settings', "Show Settings",
checkable=True, icon_checked_color=self.get_theme().green,
toolbar=toolbar)
[docs]
def update_init_icon(self, initialized: bool, action_name: str = 'init') -> None:
"""Update the initialization action icon based on state
Parameters
----------
initialized: bool
Whether the module is initialized
action_name: str
The name of the init action
"""
if self.has_action(action_name):
self.get_action(action_name).set_icon(self.INIT_ICON, self.get_theme().green if
initialized else self.get_theme().red)
[docs]
def enable_actions(self, status=True, all_except=()):
"""Enable or disable all toolbar actions, optionally excluding some.
Parameters
----------
status: bool
True to enable, False to disable.
all_except: tuple of str
Action names to leave unchanged.
"""
for action in self.actions_names:
if action not in all_except:
self.set_action_enabled(action, status)
def _show_settings(self, show: bool = True):
"""Slot connected to the show_settings action."""
self._settings_panel.show(show)
[docs]
def show_settings(self, show=True):
"""Programmatically show/hide the settings widget. API entry."""
if self.is_action_checked('show_settings') != show:
self.get_action('show_settings').trigger()
def _connect_common_actions(self):
"""Connect actions that are common to all control module UIs.
Connects `show_settings` → `_show_settings` and the init action → `send_init`.
Subclasses should call this at the start of their `connect_things()`.
"""
if 'show_settings' in self.actions_names:
self.connect_action('show_settings', self._show_settings)
if hasattr(self, '_init_action_name') and self._init_action_name in self.actions_names:
self.connect_action(self._init_action_name, self.send_init)
[docs]
def close(self):
"""Close and clean up the settings widget. Subclasses should call super().close()."""
if self._settings_widget is not None:
self._settings_widget.close()
[docs]
def do_init(self, do_init=True):
"""Programmatically press the Init button
API entry
Parameters
----------
do_init: bool
will fire the Init button depending on the argument value and the button check state
"""
raise NotImplementedError
[docs]
def send_init(self, checked: bool):
"""Should be implemented to send to the main app the fact that someone (un)checked init."""
raise NotImplementedError
def register_uis(parent_module_name: str = 'pymodaq.control_modules.daq_move_ui'):
uis = []
try:
module = import_module(f'{parent_module_name}.uis')
path = Path(module.__path__[0])
for file in path.iterdir():
if file.is_file() and 'py' in file.suffix and file.stem != '__init__':
try:
uis.append(import_module(f'.{file.stem}', module.__name__))
except (ModuleNotFoundError, Exception) as e:
pass
except ModuleNotFoundError:
pass
finally:
return uis