Source code for qudi.tools.config_editor.global_widgets
# -*- coding: utf-8 -*-
"""
QWidget serving as editor for the default global configuration section
.. Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
.. distribution and on <https://github.com/Ulm-IQO/qudi-core/>
..
.. This file is part of qudi.
..
.. Qudi is free software: you can redistribute it and/or modify it under the terms of
.. the GNU Lesser General Public License as published by the Free Software Foundation,
.. either version 3 of the License, or (at your option) any later version.
..
.. Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
.. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
.. See the GNU Lesser General Public License for more details.
..
.. You should have received a copy of the GNU Lesser General Public License along with qudi.
.. If not, see <https://www.gnu.org/licenses/>.
"""
__all__ = [
'GlobalConfigWidget',
'GlobalOptionsWidget',
'RemoteServerWidget',
'CustomOptionsWidget',
]
from typing import Dict, Union, Mapping, Optional, Any
from PySide2 import QtCore, QtWidgets
from qudi.util.widgets.separator_lines import HorizontalLine
from qudi.util.widgets.path_line_edit import PathLineEdit
from qudi.tools.config_editor.custom_widgets import CustomOptionsWidget
from qudi.core.config.schema import config_schema
[docs]
class RemoteServerWidget(QtWidgets.QWidget):
"""Remote modules server configuration editor widget."""
[docs]
def __init__(
self,
config: Optional[Mapping[str, Union[int, str]]] = None,
parent: Optional[QtWidgets.QWidget] = None,
) -> None:
super().__init__(parent=parent)
# Create main layout
layout = QtWidgets.QGridLayout()
layout.setColumnStretch(1, 1)
self.setLayout(layout)
# server enable flag
label = QtWidgets.QLabel('Remote modules server:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.enable_checkbox = QtWidgets.QCheckBox()
self.enable_checkbox.setChecked(True)
self.enable_checkbox.setToolTip(
'Whether qudi should start a remote modules server at all.\nYou will not be able to '
'communicate with remote qudi instances if this is disabled.'
)
self.enable_checkbox.toggled.connect(self._toggle_editors)
layout.addWidget(label, 0, 0)
layout.addWidget(self.enable_checkbox, 0, 1)
# Create server config editors in their own layout
self._server_layout = QtWidgets.QGridLayout()
self._server_layout.setColumnStretch(1, 1)
layout.addLayout(self._server_layout, 1, 0, 1, 2)
label = QtWidgets.QLabel('Host address:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.host_lineedit = QtWidgets.QLineEdit()
self.host_lineedit.setToolTip(
'The host address to share qudi modules with other qudi instances'
)
self._server_layout.addWidget(label, 0, 0)
self._server_layout.addWidget(self.host_lineedit, 0, 1)
label = QtWidgets.QLabel('Port:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.port_spinbox = QtWidgets.QSpinBox()
self.port_spinbox.setToolTip(
'Port number for the remote modules server to bind to'
)
self.port_spinbox.setRange(0, 65535)
self.port_spinbox.setValue(12345)
self._server_layout.addWidget(label, 1, 0)
self._server_layout.addWidget(self.port_spinbox, 1, 1)
label = QtWidgets.QLabel('Certificate file:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.certfile_lineedit = PathLineEdit(
dialog_caption='Select SSL Certificate File', follow_symlinks=True
)
self.certfile_lineedit.setPlaceholderText('No certificate')
self.certfile_lineedit.setToolTip(
'SSL certificate file path for the remote module server'
)
self._server_layout.addWidget(label, 2, 0)
self._server_layout.addWidget(self.certfile_lineedit, 2, 1)
label = QtWidgets.QLabel('Key file:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.keyfile_lineedit = PathLineEdit(
dialog_caption='Select SSL Key File', follow_symlinks=True
)
self.keyfile_lineedit.setPlaceholderText('No key')
self.keyfile_lineedit.setToolTip(
'SSL key file path for the remote module server'
)
self._server_layout.addWidget(label, 3, 0)
self._server_layout.addWidget(self.keyfile_lineedit, 3, 1)
self.set_config(config)
@property
def config(self) -> Union[None, Dict[str, Union[int, str]]]:
if self.enable_checkbox.isChecked():
host = self.host_lineedit.text().strip()
cfg = {
'address': host if host else 'localhost',
'port': self.port_spinbox.value(),
}
try:
cfg['certfile'] = self.certfile_lineedit.paths[0]
except IndexError:
pass
try:
cfg['keyfile'] = self.keyfile_lineedit.paths[0]
except IndexError:
pass
return cfg
return None
[docs]
def set_config(
self, config: Union[None, Mapping[str, Union[None, int, str]]]
) -> None:
if config is None:
self.enable_checkbox.setChecked(False)
else:
self.enable_checkbox.setChecked(True)
host = config.get('address', None)
port = config.get('port', None)
certfile = config.get('certfile', None)
keyfile = config.get('keyfile', None)
if host is None:
host = 'localhost'
if port is None:
port = 12345
if certfile is None or keyfile is None:
certfile = keyfile = ''
self.host_lineedit.setText(host)
self.port_spinbox.setValue(port)
self.certfile_lineedit.setText(certfile)
self.keyfile_lineedit.setText(keyfile)
@QtCore.Slot(bool)
def _toggle_editors(self, enabled: bool) -> None:
widget_iterator = (
self._server_layout.itemAt(ii).widget()
for ii in range(self._server_layout.count())
)
for widget in widget_iterator:
widget.setVisible(enabled)
[docs]
class GlobalOptionsWidget(QtWidgets.QWidget):
""" """
[docs]
def __init__(
self,
config: Optional[Mapping[str, Any]] = None,
parent: Optional[QtWidgets.QWidget] = None,
) -> None:
super().__init__(parent=parent)
# Create main layout
layout = QtWidgets.QGridLayout()
layout.setColumnStretch(1, 1)
self.setLayout(layout)
# Create module server editor
self.remote_server_editor = RemoteServerWidget()
layout.addWidget(self.remote_server_editor, 0, 0, 1, 2)
# Add separator
layout.addWidget(HorizontalLine(), 1, 0, 1, 2)
# Create local module server port editor
label = QtWidgets.QLabel('Namespace server port:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.namespace_port_spinbox = QtWidgets.QSpinBox()
self.namespace_port_spinbox.setToolTip(
'Port number for the local namespace server'
)
self.namespace_port_spinbox.setRange(0, 65535)
layout.addWidget(label, 2, 0)
layout.addWidget(self.namespace_port_spinbox, 2, 1)
# Create flag editor to enforce remote calls by value
label = QtWidgets.QLabel('Force remote calls by value:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.force_calls_by_value_checkbox = QtWidgets.QCheckBox()
self.force_calls_by_value_checkbox.setToolTip(
'Will force all arguments from remote calls to qudi API methods to pass by value\n'
'(serialized -> sent to qudi -> de-serialized).'
)
layout.addWidget(label, 3, 0)
layout.addWidget(self.force_calls_by_value_checkbox, 3, 1)
# Create flag editor to hide manager window upon startup
label = QtWidgets.QLabel('Hide manager window:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.hide_manager_window_checkbox = QtWidgets.QCheckBox()
self.hide_manager_window_checkbox.setToolTip(
'Whether to suppress the qudi module manager window at startup.'
)
layout.addWidget(label, 4, 0)
layout.addWidget(self.hide_manager_window_checkbox, 4, 1)
# Create flag editor to auomatically create a data sub-directory for each day
label = QtWidgets.QLabel('Create daily data directories:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.daily_data_dirs_checkbox = QtWidgets.QCheckBox()
self.daily_data_dirs_checkbox.setToolTip(
'Whether to automatically create daily sub-directories in the data directory for file '
'based data storage facilities'
)
layout.addWidget(label, 5, 0)
layout.addWidget(self.daily_data_dirs_checkbox, 5, 1)
# Create default data path editor
label = QtWidgets.QLabel('Default data directory:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.data_directory_lineedit = PathLineEdit(
dialog_caption='Select Default Data Directory', select_directory=True
)
self.data_directory_lineedit.setPlaceholderText(
'Default "<UserHome>/qudi/Data/"'
)
self.data_directory_lineedit.setToolTip(
'Default data directory for qudi modules to save ' 'measurement data into.'
)
layout.addWidget(label, 6, 0)
layout.addWidget(self.data_directory_lineedit, 6, 1)
# Create startup modules editor
label = QtWidgets.QLabel('Startup Modules:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.startup_lineedit = QtWidgets.QLineEdit()
self.startup_lineedit.setPlaceholderText('No startup modules')
self.startup_lineedit.setToolTip(
'Modules to be automatically activated on qudi startup.\n'
'Separate multiple module names with commas.'
)
layout.addWidget(label, 7, 0)
layout.addWidget(self.startup_lineedit, 7, 1)
# Create stylesheet file path editor
label = QtWidgets.QLabel('Stylesheet:')
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.stylesheet_lineedit = PathLineEdit(
dialog_caption='Select QSS Stylesheet',
filters='Stylesheets (*.qss)',
follow_symlinks=True,
)
self.stylesheet_lineedit.setPlaceholderText('Platform dependent Qt default')
self.stylesheet_lineedit.setToolTip(
'File path for qudi QSS stylesheet to use.\nIf just a file name is given, the file '
'must be found in the qudi artwork resources.'
)
layout.addWidget(label, 8, 0)
layout.addWidget(self.stylesheet_lineedit, 8, 1)
# Get default config from JSON schema
global_props = config_schema()['properties']['global']['properties']
self._config_defaults = {
name: prop.get('default', None) for name, prop in global_props.items()
}
# Fixme: Remove deprecated option manually
del self._config_defaults['extension_paths']
self.set_config(config)
@property
def config(self) -> Dict[str, Any]:
config = {
'remote_modules_server': self.remote_server_editor.config,
'namespace_server_port': self.namespace_port_spinbox.value(),
'force_remote_calls_by_value': self.force_calls_by_value_checkbox.isChecked(),
'hide_manager_window': self.hide_manager_window_checkbox.isChecked(),
'daily_data_dirs': self.daily_data_dirs_checkbox.isChecked(),
'startup_modules': [
mod.strip()
for mod in self.startup_lineedit.text().split(',')
if mod.strip()
],
}
try:
config['default_data_dir'] = self.data_directory_lineedit.paths[0]
except IndexError:
config['default_data_dir'] = self._config_defaults['default_data_dir']
try:
config['stylesheet'] = self.stylesheet_lineedit.paths[0]
except IndexError:
config['stylesheet'] = self._config_defaults['stylesheet']
return config
[docs]
def set_config(self, config: Union[None, Mapping[str, Any]]):
if config is None:
config = dict()
config = {
name: config.get(name, default)
for name, default in self._config_defaults.items()
}
self.remote_server_editor.set_config(config['remote_modules_server'])
self.namespace_port_spinbox.setValue(config['namespace_server_port'])
self.force_calls_by_value_checkbox.setChecked(
config['force_remote_calls_by_value']
)
self.hide_manager_window_checkbox.setChecked(config['hide_manager_window'])
self.daily_data_dirs_checkbox.setChecked(config['daily_data_dirs'])
default_data_dir = config['default_data_dir']
self.data_directory_lineedit.setText(
'' if default_data_dir is None else default_data_dir
)
stylesheet = config['stylesheet']
self.stylesheet_lineedit.setText('' if stylesheet is None else stylesheet)
self.startup_lineedit.setText(','.join(config['startup_modules']))
[docs]
class GlobalConfigWidget(QtWidgets.QWidget):
""" """
[docs]
def __init__(
self,
config: Optional[Mapping[str, Any]] = None,
parent: Optional[QtWidgets.QWidget] = None,
) -> None:
super().__init__(parent=parent)
# Create main layout
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
# Create Caption
label = QtWidgets.QLabel('Global Configuration')
label.setAlignment(QtCore.Qt.AlignCenter)
font = label.font()
font.setBold(True)
font.setPointSize(font.pointSize() + 4)
label.setFont(font)
layout.addWidget(label)
# Create default config editor
self.default_options_widget = GlobalOptionsWidget()
layout.addWidget(self.default_options_widget)
# Remember default global config option names
self._default_option_names = frozenset(self.default_options_widget.config)
# Add separator
layout.addWidget(HorizontalLine())
# Create custom option editor
self.custom_options_widget = CustomOptionsWidget(
forbidden_names=self._default_option_names
)
layout.addWidget(self.custom_options_widget)
layout.addStretch(1)
self.set_config(config)
@property
def config(self) -> Dict[str, Any]:
config = self.default_options_widget.config
config.update(self.custom_options_widget.config)
return config
[docs]
def set_config(self, config: Union[None, Mapping[str, Any]]) -> None:
if config is None:
self.default_options_widget.set_config(None)
self.custom_options_widget.set_config(None)
else:
default_config = {
name: value
for name, value in config.items()
if name in self._default_option_names
}
custom_config = {
name: value
for name, value in config.items()
if name not in self._default_option_names
}
self.default_options_widget.set_config(default_config)
self.custom_options_widget.set_config(custom_config)