class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    valueChanged = Signal()

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def _restart_dialog(self, event=None, extra_str=""):
        """Displays the dialog informing user a restart is required.

        Paramters
        ---------
        event : Event
        extra_str : str
            Extra information to add to the message about needing a restart.
        """

        text_str = trans._(
            "napari requires a restart for image rendering changes to apply.")

        widget = ResetNapariInfoDialog(
            parent=self,
            text=text_str,
        )
        widget.exec_()

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        settings = get_settings()
        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.
        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in settings.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """
        schema = json.loads(setting['json_schema'])

        # Resolve allOf references
        definitions = schema.get("definitions", {})
        if definitions:
            for key, data in schema["properties"].items():
                if "allOf" in data:
                    allof = data["allOf"]
                    allof = [d["$ref"].rsplit("/")[-1] for d in allof]
                    for definition in allof:
                        local_def = definitions[definition]
                        schema["properties"][key]["enum"] = local_def["enum"]
                        schema["properties"][key]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""
        self._reset_dialog = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        self._reset_dialog.valueChanged.connect(self._reset_widgets)
        self._reset_dialog.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self.valueChanged.emit()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to settings."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        settings = get_settings()
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = settings.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        settings = get_settings()
        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)

        # Disable widgets that loaded settings from environment variables
        section = schema["section"]
        form_layout = form.widget.layout()
        for row in range(form.widget.layout().rowCount()):
            widget = form_layout.itemAt(row, form_layout.FieldRole).widget()
            name = widget._name
            disable = bool(
                settings._env_settings.get(section, {}).get(name, None))
            widget.setDisabled(disable)
            try:
                widget.opacity.setOpacity(0.3 if disable else 1)
            except AttributeError:
                # some widgets may not have opacity (such as the QtPluginSorter)
                pass

        # set state values for widget
        form.widget.state = values

        if section == 'experimental':
            # need to disable async if octree is enabled.
            if values['octree'] is True:
                form = self._disable_async(form, values)

        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _disable_async(self, form, values, disable=True, state=True):
        """Disable async if octree is True."""
        settings = get_settings()
        # need to make sure that if async_ is an environment setting, that we don't
        # enable it here.
        if (settings._env_settings['experimental'].get('async_', None)
                is not None):
            disable = True

        idx = list(values.keys()).index('async_')
        form_layout = form.widget.layout()
        widget = form_layout.itemAt(idx, form_layout.FieldRole).widget()
        widget.opacity.setOpacity(0.3 if disable else 1)
        widget.setDisabled(disable)

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def set_current_index(self, index: int):
        """
        Set the current page on the preferences by index.

        Parameters
        ----------
        index : int
            Index of page to set as current one.
        """
        self._list.setCurrentRow(index)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        settings = get_settings()
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in settings
            for setting_name, value in different_values.items():
                try:
                    setattr(settings._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict

                    if page == 'experimental':

                        if setting_name == 'octree':

                            # disable/enable async checkbox
                            widget = self._stack.currentWidget()
                            cstate = True if value is True else False
                            self._disable_async(widget,
                                                new_dict,
                                                disable=cstate)

                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                        elif setting_name == 'async_':
                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                except:  # noqa: E722
                    continue
Example #2
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
        "shortcuts": {
            "ui:widget": "shortcuts"
        },
        "extension2reader": {
            "ui:widget": "extension2reader"
        },
    }

    resized = Signal(QSize)

    def __init__(self, parent=None):
        from ...settings import get_settings

        super().__init__(parent)
        self.setWindowTitle(trans._("Preferences"))

        self._settings = get_settings()
        self._stack = QStackedWidget(self)
        self._list = QListWidget(self)
        self._list.setObjectName("Preferences")
        self._list.currentRowChanged.connect(self._stack.setCurrentIndex)

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_cancel.clicked.connect(self.reject)
        self._button_ok = QPushButton(trans._("OK"))
        self._button_ok.clicked.connect(self.accept)
        self._button_ok.setDefault(True)
        self._button_restore = QPushButton(trans._("Restore defaults"))
        self._button_restore.clicked.connect(self._restore_default_dialog)

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._button_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        self.setLayout(QHBoxLayout())
        self.layout().addLayout(left_layout, 1)
        self.layout().addWidget(self._stack, 3)

        # Build dialog from settings
        self._rebuild_dialog()

    def keyPressEvent(self, e: 'QKeyEvent'):
        if e.key() == Qt.Key_Escape:
            # escape key should just close the window
            # which implies "accept"
            e.accept()
            self.accept()
            return
        super().keyPressEvent(e)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def _rebuild_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # FIXME: this dialog should not need to know about the plugin manager
        from ...plugins import plugin_manager

        self._starting_pm_order = plugin_manager.call_order()
        self._starting_values = self._settings.dict(exclude={'schema_version'})

        self._list.clear()
        while self._stack.count():
            self._stack.removeWidget(self._stack.currentWidget())

        for field in self._settings.__fields__.values():
            if isinstance(field.type_, type) and issubclass(
                    field.type_, BaseModel):
                self._add_page(field)

        self._list.setCurrentRow(0)

    def _add_page(self, field: 'ModelField'):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        field : ModelField
            subfield for which to create a page.
        """
        from ..._vendor.qt_json_builder.qt_jsonschema_form import WidgetBuilder

        schema, values = self._get_page_dict(field)
        name = field.field_info.title or field.name

        form = WidgetBuilder().create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        # make settings follow state of the form widget
        form.widget.on_changed.connect(
            lambda d: getattr(self._settings, name.lower()).update(d))

        # need to disable async if octree is enabled.
        # TODO: this shouldn't live here... if there is a coupling/dependency
        # between these settings, it should be declared in the settings schema
        if (name.lower() == 'experimental'
                and values['octree'] and self._settings.env_settings().get(
                    'experimental', {}).get('async_') not in (None, '0')):
            form_layout = form.widget.layout()
            for i in range(form_layout.count()):
                wdg = form_layout.itemAt(i, form_layout.FieldRole).widget()
                if getattr(wdg, '_name') == 'async_':
                    wdg.opacity.setOpacity(0.3)
                    wdg.setDisabled(True)
                    break

        self._list.addItem(field.field_info.title or field.name)
        self._stack.addWidget(form)

    def _get_page_dict(self, field: 'ModelField') -> Tuple[dict, dict, dict]:
        """Provides the schema, set of values for each setting, and the
        properties for each setting."""
        ftype = cast('BaseModel', field.type_)
        schema = json.loads(ftype.schema_json())

        # find enums:
        for name, subfield in ftype.__fields__.items():
            if isinstance(subfield.type_, EnumMeta):
                enums = [s.value for s in subfield.type_]  # type: ignore
                schema["properties"][name]["enum"] = enums
                schema["properties"][name]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        setting = getattr(self._settings, field.name)
        with setting.enums_as_values():
            values = setting.dict()
        napari_config = getattr(setting, "NapariConfig", None)
        if hasattr(napari_config, 'preferences_exclude'):
            for val in napari_config.preferences_exclude:
                schema['properties'].pop(val, None)
                values.pop(val, None)

        return schema, values

    def _restore_default_dialog(self):
        """Launches dialog to confirm restore settings choice."""
        response = QMessageBox.question(
            self,
            trans._("Restore Settings"),
            trans._("Are you sure you want to restore default settings?"),
            QMessageBox.RestoreDefaults | QMessageBox.Cancel,
            QMessageBox.RestoreDefaults,
        )
        if response == QMessageBox.RestoreDefaults:
            self._settings.reset()
            self._rebuild_dialog()  # TODO: do we need this?

    def _restart_required_dialog(self):
        """Displays the dialog informing user a restart is required."""
        QMessageBox.information(
            self,
            trans._("Restart required"),
            trans.
            _("A restart is required for some new settings to have an effect."
              ),
        )

    def closeEvent(self, event: 'QCloseEvent') -> None:
        event.accept()
        self.accept()

    def reject(self):
        """Restores the settings in place when dialog was launched."""
        self._settings.update(self._starting_values)

        # FIXME: this dialog should not need to know about the plugin manager
        if self._starting_pm_order:
            from ...plugins import plugin_manager

            plugin_manager.set_call_order(self._starting_pm_order)
        super().reject()
Example #3
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    resized = Signal(QSize)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # Because there are multiple pages, need to keep a list of values sets.
        self._values_orig_set_list = []
        self._values_set_list = []
        for _key, setting in SETTINGS.schemas().items():
            schema = json.loads(setting['json_schema'])
            # Need to remove certain properties that will not be displayed on the GUI
            properties = schema.pop('properties')
            model = setting['model']
            values = model.dict()
            napari_config = getattr(model, "NapariConfig", None)
            if napari_config is not None:
                for val in napari_config.preferences_exclude:
                    properties.pop(val)
                    values.pop(val)

            schema['properties'] = properties
            self._values_orig_set_list.append(set(values.items()))
            self._values_set_list.append(set(values.items()))

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper set list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            self.check_differences(
                self._values_orig_set_list[n],
                self._values_set_list[n],
            )
        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, {})
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            set(d.items()),
            self._values_set_list[self._list.currentIndex().row()],
        ))

        return form

    def check_differences(self, new_set, values_set):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_set : set
            The set of new values, with tuples of key value pairs for each
            setting.
        values_set : set
            The old set of values.
        """

        page = self._list.currentItem().text().split(" ")[0].lower()
        different_values = list(new_set - values_set)

        if len(different_values) > 0:
            # change the values in SETTINGS
            for val in different_values:
                try:
                    setattr(SETTINGS._settings[page], val[0], val[1])
                    self._values_set_list[
                        self._list.currentIndex().row()] = new_set
                except:  # noqa: E722
                    continue
Example #4
0
class HeaderEditDialog(QDialog):

    # name of the current preset; whether to set this preset as default; list of Columns
    header_changed = Signal(str, bool, list)

    def __init__(self, parent, table_header):
        super().__init__(parent)

        self.table_header = table_header
        self.default_preset_name = None
        self.preset_name = table_header.preset_name
        self.columns = deepcopy(table_header.columns)
        self.setupUi()
        self.update_output()

    def setupUi(self):
        self.resize(240, 400)
        self.vbox = QVBoxLayout(self)
        self.presetLabel = QLabel(self)
        self.columnList = QListWidget(self)
        self.setAsDefaultCheckbox = QCheckBox("Set as default preset", self)
        self.vbox.addWidget(self.presetLabel)
        self.vbox.addWidget(self.columnList)
        self.vbox.addWidget(self.setAsDefaultCheckbox)

        self.columnList.setDragDropMode(QListWidget.InternalMove)
        self.columnList.setDefaultDropAction(Qt.MoveAction)
        self.columnList.setSelectionMode(QListWidget.ExtendedSelection)
        self.columnList.setAlternatingRowColors(True)
        self.columnList.installEventFilter(self)
        self.columnList.setContextMenuPolicy(Qt.CustomContextMenu)
        self.columnList.customContextMenuRequested.connect(self.open_menu)
        self.columnList.model().rowsMoved.connect(self.read_columns_from_list)

        # for a dumb qss hack to make selected checkboxes not white on a light theme
        self.columnList.setObjectName("ColumnList")

        buttons = QDialogButtonBox.Reset | QDialogButtonBox.Save | QDialogButtonBox.Cancel
        self.buttonBox = QDialogButtonBox(buttons, self)
        self.vbox.addWidget(self.buttonBox)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self.resetButton = self.buttonBox.button(QDialogButtonBox.Reset)
        self.resetButton.clicked.connect(self.reset_to_stock)

    def eventFilter(self, object, event):
        if event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return:
                self.toggle_selected_columns()
                return True
            if event.key() == Qt.Key_Delete:
                self.delete_selected()
                return True
        return False

    def update_output(self):
        self.presetLabel.setText("Preset: {}".format(self.preset_name))
        self.setAsDefaultCheckbox.setChecked(
            CONFIG['default_header_preset'] == self.preset_name)
        self.columnList.clear()
        for column in self.columns:
            ColumnListItem(self.columnList, column)

    def accept(self):
        self.read_columns_from_list()
        self.header_changed.emit(self.preset_name,
                                 self.setAsDefaultCheckbox.isChecked(),
                                 self.columns)
        self.done(0)

    def reject(self):
        self.done(0)

    def reset_to_stock(self):
        self.columns = deepcopy(DEFAULT_COLUMNS)
        self.update_output()

    def read_columns_from_list(self):
        new_columns = []
        for i in range(self.columnList.count()):
            item = self.columnList.item(i)
            new_columns.append(item.column)
        self.columns = new_columns

    def toggle_selected_columns(self):
        selected = self.columnList.selectedItems()
        for item in selected:
            value_now = item.data(Qt.CheckStateRole)
            item.setData(Qt.CheckStateRole, not value_now)
        self.columnList.reset(
        )  # @Improvement: is there a better way to update QListWidget?

    def open_menu(self, position):
        menu = QMenu(self)

        preset_menu = menu.addMenu('Presets')
        preset_menu.addAction('New preset', self.new_preset_dialog)
        preset_menu.addSeparator()

        preset_names = CONFIG.get_header_presets()

        if len(preset_names) == 0:
            action = preset_menu.addAction('No presets')
            action.setEnabled(False)
        else:
            delete_menu = menu.addMenu('Delete preset')
            for name in preset_names:
                preset_menu.addAction(name, partial(self.load_preset, name))
                delete_menu.addAction(name, partial(self.delete_preset, name))

        menu.addSeparator()
        menu.addAction('New column...', self.create_new_column_dialog)

        if len(self.columnList.selectedIndexes()) > 0:
            menu.addAction('Delete selected', self.delete_selected)

        menu.popup(self.columnList.viewport().mapToGlobal(position))

    def load_preset(self, name):
        new_columns = CONFIG.load_header_preset(name)
        if not new_columns:
            return

        self.columns = new_columns
        self.preset_name = name
        self.update_output()

    def new_preset_dialog(self):
        d = QInputDialog(self)
        d.setLabelText('Enter the new name for the new preset:')
        d.setWindowTitle('Create new preset')
        d.textValueSelected.connect(self.create_new_preset)
        d.open()

    def create_new_preset(self, name):
        if name in CONFIG.get_header_presets():
            show_warning_dialog(
                self, "Preset creation error",
                'Preset named "{}" already exists.'.format(name))
            return
        if len(name.strip()) == 0:
            show_warning_dialog(
                self, "Preset creation error",
                'This preset name is not allowed.'.format(name))
            return

        self.preset_name = name
        self.update_output()
        CONFIG.save_header_preset(name, self.columns)

    def delete_preset(self, name):
        CONFIG.delete_header_preset(name)
        if name == self.preset_name:
            self.columns = deepcopy(DEFAULT_COLUMNS)
            self.update_output()

    def create_new_column_dialog(self):
        d = CreateNewColumnDialog(self)
        d.add_new_column.connect(self.add_new_column)
        d.setWindowTitle('Create new column')
        d.open()

    def add_new_column(self, name, title):
        new_column = Column(name, title)
        # if the last column is message, insert this column before it (I think that makes sense?)
        if len(self.columns) == 0:
            self.columns.append(new_column)
        elif self.columns[-1].name in ('message', 'msg'):
            self.columns.insert(-1, new_column)
        else:
            self.columns.append(new_column)
        self.update_output()

    def delete_selected(self):
        selected = self.columnList.selectedItems()
        for item in selected:
            self.columnList.takeItem(self.columnList.row(item))
        self.read_columns_from_list()
        self.update_output()
Example #5
0
class CycleWindow(SiriusMainWindow):
    """Power supplies cycle window."""
    def __init__(self, parent=None, checked_accs=(), adv_mode=False):
        """Constructor."""
        super().__init__(parent)
        self.setObjectName('ASApp')
        cor = get_appropriate_color(section='AS')
        self.setWindowIcon(qta.icon('mdi.recycle', color=cor))
        self._is_adv_mode = adv_mode
        # Data structs
        self._psnames = get_psnames(isadv=self._is_adv_mode)
        self._timing = Timing()
        self._ps2cycle = list()
        self._ps_ready = list()
        self._ps_failed = list()
        self._checked_accs = checked_accs
        # Flags
        self._is_preparing = ''
        self._prepared_init_vals = {
            'timing': False,
            'ps_sofbmode': False,
            'ps_om_slowref': False,
            'ps_current': False,
            'ps_params': False,
            'ps_om_cycle': False,
            'trims': True
        }
        self._prepared = self._prepared_init_vals.copy()
        self._icon_check = qta.icon('fa5s.check')
        self._pixmap_check = self._icon_check.pixmap(
            self._icon_check.actualSize(QSize(16, 16)))
        self._icon_not = qta.icon('fa5s.times')
        self._pixmap_not = self._icon_not.pixmap(
            self._icon_not.actualSize(QSize(16, 16)))
        # Tasks
        self._step_2_task = {
            'save_timing': SaveTiming,
            'timing': PrepareTiming,
            'ps_sofbmode': PreparePSSOFBMode,
            'ps_om_slowref': PreparePSOpModeSlowRef,
            'ps_current': PreparePSCurrentZero,
            'ps_params': PreparePSParams,
            'ps_om_cycle': PreparePSOpModeCycle,
            'trims': CycleTrims,
            'cycle': Cycle,
            'restore_timing': RestoreTiming,
        }
        # Setup UI
        self._needs_update_setup = False
        self._setup_ui()
        self._update_setup_timer = QTimer(self)
        self._update_setup_timer.timeout.connect(self._update_setup)
        self._update_setup_timer.setInterval(250)
        self._update_setup_timer.start()
        self.setWindowTitle('PS Cycle')

    def _setup_ui(self):
        # central widget
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)

        # tree
        gb_tree = QGroupBox('Select power supplies:')
        self.pwrsupplies_tree = PVNameTree(self._psnames, ('sec', 'mag_group'),
                                           tuple(), self)
        self.pwrsupplies_tree.tree.setHeaderHidden(True)
        self.pwrsupplies_tree.tree.setColumnCount(1)
        glay_tree = QVBoxLayout(gb_tree)
        glay_tree.addWidget(self.pwrsupplies_tree)

        # commands
        lb_prep_ti = QLabel('<h4>Prepare Timing</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        ti_ch = [
            PVName(name).substitute(prefix=VACA_PREFIX)
            for name in self._timing.get_pvnames_by_psnames()
        ]
        self.ticonn_led = PyDMLedMultiConn(self, channels=ti_ch)

        self.save_timing_bt = QPushButton('1. Save Timing Initial State', self)
        self.save_timing_bt.setToolTip(
            'Save timing current state as initial state.')
        self.save_timing_bt.clicked.connect(
            _part(self._run_task, 'save_timing'))
        self.save_timing_bt.clicked.connect(self._set_lastcomm)

        self.prepare_timing_bt = QPushButton('2. Prepare Timing', self)
        self.prepare_timing_bt.setToolTip('Prepare EVG, triggers and events')
        self.prepare_timing_bt.clicked.connect(_part(self._run_task, 'timing'))
        self.prepare_timing_bt.clicked.connect(self._set_lastcomm)

        self.prepare_timing_lb = QLabel(self)
        self.prepare_timing_lb.setPixmap(self._pixmap_not)

        lb_prep_ps = QLabel('<h4>Prepare PS</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        self.psconn_led = PyDMLedMultiConn(self)

        self.set_ps_sofbmode_off_bt = QPushButton('3. Turn off PS SOFBMode',
                                                  self)
        self.set_ps_sofbmode_off_bt.setToolTip(
            'Turn off power supplies SOFBMode.')
        self.set_ps_sofbmode_off_bt.clicked.connect(
            _part(self._run_task, 'ps_sofbmode'))
        self.set_ps_sofbmode_off_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_sofbmode_off_lb = QLabel(self)
        self.set_ps_sofbmode_off_lb.setPixmap(self._pixmap_not)

        self.set_ps_opmode_slowref_bt = QPushButton(
            '4. Set PS OpMode to SlowRef', self)
        self.set_ps_opmode_slowref_bt.setToolTip(
            'Set power supplies OpMode to SlowRef.')
        self.set_ps_opmode_slowref_bt.clicked.connect(
            _part(self._run_task, 'ps_om_slowref'))
        self.set_ps_opmode_slowref_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_opmode_slowref_lb = QLabel(self)
        self.set_ps_opmode_slowref_lb.setPixmap(self._pixmap_not)

        self.set_ps_current_zero_bt = QPushButton('5. Set PS current to zero',
                                                  self)
        self.set_ps_current_zero_bt.setToolTip(
            'Set power supplies current to zero.')
        self.set_ps_current_zero_bt.clicked.connect(
            _part(self._run_task, 'ps_current'))
        self.set_ps_current_zero_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_current_zero_lb = QLabel(self)
        self.set_ps_current_zero_lb.setPixmap(self._pixmap_not)

        self.prepare_ps_params_bt = QPushButton('6. Prepare PS Parameters',
                                                self)
        self.prepare_ps_params_bt.setToolTip(
            'Check power supplies OpMode in SlowRef, check\n'
            'current is zero and configure cycle parameters.')
        self.prepare_ps_params_bt.clicked.connect(
            _part(self._run_task, 'ps_params'))
        self.prepare_ps_params_bt.clicked.connect(self._set_lastcomm)

        self.prepare_ps_params_lb = QLabel(self)
        self.prepare_ps_params_lb.setPixmap(self._pixmap_not)

        self.prepare_ps_opmode_bt = QPushButton('7. Prepare PS OpMode', self)
        self.prepare_ps_opmode_bt.setToolTip(
            'Set power supplies OpMode to Cycle.')
        self.prepare_ps_opmode_bt.clicked.connect(
            _part(self._run_task, 'ps_om_cycle'))
        self.prepare_ps_opmode_bt.clicked.connect(self._set_lastcomm)

        self.prepare_ps_opmode_lb = QLabel(self)
        self.prepare_ps_opmode_lb.setPixmap(self._pixmap_not)

        lb_cycle = QLabel('<h4>Cycle</h4>', self, alignment=Qt.AlignCenter)

        self.cycle_trims_bt = QPushButton('8. Cycle Trims', self)
        self.cycle_trims_bt.setToolTip(
            'Cycle trims:\nStep 1) CH, QS and QTrims\nStep 2) CV')
        self.cycle_trims_bt.clicked.connect(_part(self._run_task, 'trims'))
        self.cycle_trims_bt.clicked.connect(self._set_lastcomm)
        self.cycle_trims_bt.setVisible(False)

        self.cycle_trims_lb = QLabel(self)
        self.cycle_trims_lb.setPixmap(self._pixmap_check)
        self.cycle_trims_lb.setVisible(False)

        self.cycle_bt = QPushButton('8. Cycle', self)
        self.cycle_bt.setToolTip(
            'Check all configurations,\nenable triggers and run cycle.')
        self.cycle_bt.clicked.connect(_part(self._run_task, 'cycle'))
        self.cycle_bt.clicked.connect(self._set_lastcomm)
        self.cycle_bt.setEnabled(False)

        lb_rest_ti = QLabel('<h4>Restore Timing</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        self.restore_timing_bt = QPushButton('9. Restore Timing Initial State',
                                             self)
        self.restore_timing_bt.setToolTip('Restore timing initial state.')
        self.restore_timing_bt.clicked.connect(
            _part(self._run_task, 'restore_timing'))
        self.restore_timing_bt.clicked.connect(self._set_lastcomm)

        self._prepared_labels = {
            'timing': self.prepare_timing_lb,
            'ps_sofbmode': self.set_ps_sofbmode_off_lb,
            'ps_om_slowref': self.set_ps_opmode_slowref_lb,
            'ps_current': self.set_ps_current_zero_lb,
            'ps_params': self.prepare_ps_params_lb,
            'ps_om_cycle': self.prepare_ps_opmode_lb,
            'trims': self.cycle_trims_lb
        }

        gb_commsts = QGroupBox()
        gb_commsts.setStyleSheet("""
            QPushButton{min-height:1.5em;}
            QLabel{qproperty-alignment: AlignCenter;}""")
        lay_commsts = QGridLayout(gb_commsts)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 0, 0, 1, 2)
        lay_commsts.addWidget(lb_prep_ti, 1, 0)
        lay_commsts.addWidget(self.ticonn_led, 1, 1)
        lay_commsts.addWidget(self.save_timing_bt, 2, 0)
        lay_commsts.addWidget(self.prepare_timing_bt, 3, 0)
        lay_commsts.addWidget(self.prepare_timing_lb, 3, 1)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 4, 0)
        lay_commsts.addWidget(lb_prep_ps, 5, 0)
        lay_commsts.addWidget(self.psconn_led, 5, 1)
        lay_commsts.addWidget(self.set_ps_sofbmode_off_bt, 6, 0)
        lay_commsts.addWidget(self.set_ps_sofbmode_off_lb, 6, 1)
        lay_commsts.addWidget(self.set_ps_opmode_slowref_bt, 7, 0)
        lay_commsts.addWidget(self.set_ps_opmode_slowref_lb, 7, 1)
        lay_commsts.addWidget(self.set_ps_current_zero_bt, 8, 0)
        lay_commsts.addWidget(self.set_ps_current_zero_lb, 8, 1)
        lay_commsts.addWidget(self.prepare_ps_params_bt, 9, 0)
        lay_commsts.addWidget(self.prepare_ps_params_lb, 9, 1)
        lay_commsts.addWidget(self.prepare_ps_opmode_bt, 10, 0)
        lay_commsts.addWidget(self.prepare_ps_opmode_lb, 10, 1)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 11, 0)
        lay_commsts.addWidget(lb_cycle, 12, 0)
        lay_commsts.addWidget(self.cycle_trims_bt, 13, 0)
        lay_commsts.addWidget(self.cycle_trims_lb, 13, 1)
        lay_commsts.addWidget(self.cycle_bt, 14, 0)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 15, 0)
        lay_commsts.addWidget(lb_rest_ti, 16, 0)
        lay_commsts.addWidget(self.restore_timing_bt, 17, 0)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 18, 0)
        lay_commsts.setColumnStretch(0, 10)
        lay_commsts.setColumnStretch(1, 1)
        lay_commsts.setVerticalSpacing(12)
        lay_commsts.setHorizontalSpacing(6)

        self.label_lastcomm = QLabel('Last Command: ', self)
        self.clearhist_bt = QPushButton('Clear', self)
        self.clearhist_bt.clicked.connect(self._clear_lastcomm)
        lay_lc = QHBoxLayout()
        lay_lc.setContentsMargins(0, 0, 0, 0)
        lay_lc.addWidget(self.label_lastcomm, alignment=Qt.AlignLeft)
        lay_lc.addWidget(self.clearhist_bt, alignment=Qt.AlignRight)
        lay_lc.setStretch(0, 10)
        lay_lc.setStretch(1, 1)

        self.progress_list = QListWidget(self)
        self.progress_list.setObjectName('progresslist')
        self.progress_list.setStyleSheet('#progresslist{min-width:20em;}')
        self.progress_list.itemDoubleClicked.connect(self._open_ps_detail)
        self.progress_list.setSelectionMode(QAbstractItemView.MultiSelection)
        self.progress_list.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')

        self.progress_bar = MyProgressBar(self)

        lay_log = QVBoxLayout()
        lay_log.addLayout(lay_lc)
        lay_log.addWidget(self.progress_list)
        lay_log.addWidget(self.progress_bar)

        # connect tree signals
        self.pwrsupplies_tree.tree.doubleClicked.connect(self._open_ps_detail)
        self.pwrsupplies_tree.tree.itemChanged.connect(
            self._handle_checked_items_changed)
        self.pwrsupplies_tree.check_requested_levels(self._checked_accs)

        # layout
        layout = QGridLayout()
        layout.setVerticalSpacing(10)
        layout.setHorizontalSpacing(10)
        layout.addWidget(
            QLabel('<h3>PS Cycle</h3>', self, alignment=Qt.AlignCenter), 0, 0,
            1, 3)
        layout.addWidget(gb_tree, 1, 0)
        layout.addWidget(gb_commsts, 1, 1)
        layout.addLayout(lay_log, 1, 2)
        layout.setRowStretch(0, 1)
        layout.setRowStretch(1, 15)
        layout.setColumnStretch(0, 5)
        layout.setColumnStretch(1, 4)
        layout.setColumnStretch(2, 8)
        self.central_widget.setLayout(layout)

    # --- handle tasks ---

    def _run_task(self, control=''):
        if not self._check_connected(control):
            return
        pwrsupplies = self._get_ps_list()
        if not pwrsupplies:
            return

        if 'ps' in control and not self._verify_ps(pwrsupplies):
            return

        if control in self._step_2_task:
            task_class = self._step_2_task[control]
        else:
            raise NotImplementedError(
                "Task not defined for control '{}'".format(control))
        self._is_preparing = control

        self._handle_buttons_enabled(False)
        self.progress_list.clear()

        task = task_class(parent=self,
                          psnames=pwrsupplies,
                          timing=self._timing,
                          isadv=self._is_adv_mode)
        task.updated.connect(self._update_progress)
        duration = task.duration()

        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(duration)
        self.progress_bar.setValue(0)
        pal = self.progress_bar.palette()
        pal.setColor(QPalette.Highlight, self.progress_bar.default_color)
        self.progress_bar.setPalette(pal)

        self.update_bar = UpdateProgressBar(duration, self)
        self.update_bar.increment.connect(self.progress_bar.increment)

        task.start()
        self.update_bar.start()

    def _update_progress(self, text, done, warning=False, error=False):
        """Update automated cycle progress list and bar."""
        if done:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            curr_text = last_item.text()
            last_item.setText(curr_text + ' done.')
        elif 'Remaining time' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Remaining time' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Sent ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Sent ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Successfully checked ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Successfully checked ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Created connections ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Created connections ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        else:
            item = QListWidgetItem(text)
            if error:
                item.setForeground(errorcolor)
                self.update_bar.exit_task()
                pal = self.progress_bar.palette()
                pal.setColor(QPalette.Highlight,
                             self.progress_bar.warning_color)
                self.progress_bar.setPalette(pal)
                if self._is_preparing in self._prepared.keys():
                    self._prepared[self._is_preparing] = False
                cycle = all(self._prepared.values())
                self._handle_buttons_enabled(True, cycle=cycle)
            elif warning:
                item.setForeground(warncolor)
            elif 'finished' in text:
                self.update_bar.exit_task()
                self.progress_bar.setValue(self.progress_bar.maximum())
                if self._is_preparing == 'cycle':
                    self._prepared = {k: False for k in self._prepared.keys()}
                    if not self.cycle_trims_bt.isVisible():
                        self._prepared['trims'] = True
                    cycle = False
                else:
                    if self._is_preparing in self._prepared.keys():
                        self._prepared[self._is_preparing] = True
                    cycle = all(self._prepared.values())
                self._handle_buttons_enabled(True, cycle=cycle)

            self._handle_stslabels_content()

            self.progress_list.addItem(item)
            self.progress_list.scrollToBottom()

    def _handle_buttons_enabled(self, enable, cycle=False):
        self.save_timing_bt.setEnabled(enable)
        self.prepare_timing_bt.setEnabled(enable)
        self.set_ps_sofbmode_off_bt.setEnabled(enable)
        self.set_ps_opmode_slowref_bt.setEnabled(enable)
        self.set_ps_current_zero_bt.setEnabled(enable)
        self.prepare_ps_params_bt.setEnabled(enable)
        self.prepare_ps_opmode_bt.setEnabled(enable)
        self.cycle_trims_bt.setEnabled(enable)
        self.cycle_bt.setEnabled(cycle)
        self.restore_timing_bt.setEnabled(enable)
        self.clearhist_bt.setEnabled(enable)
        self.pwrsupplies_tree.setEnabled(enable)

    def _handle_stslabels_content(self):
        for prep, value in self._prepared.items():
            pixmap = self._pixmap_check if value else self._pixmap_not
            self._prepared_labels[prep].setPixmap(pixmap)

    def _set_lastcomm(self):
        sender_text = self.sender().text()
        self.label_lastcomm.setText('Last Command: ' + sender_text)

    def _clear_lastcomm(self):
        self.progress_bar.setValue(0)
        self.progress_list.clear()
        self.label_lastcomm.setText('Last Command: ')

    # --- handle ps selection ---

    def _get_ps_list(self):
        """Return list of power supplies to cycle."""
        # Get power supplies list
        pwrsupplies = self.pwrsupplies_tree.checked_items()
        if not pwrsupplies:
            QMessageBox.critical(self, 'Message', 'No power supply selected!')
            return False

        sections = get_sections(pwrsupplies)
        if 'BO' in sections and len(sections) > 1:
            QMessageBox.critical(self, 'Error',
                                 'Can not cycle Booster with other sectors!')
            return False

        create_task = CreateCyclers(parent=self, psnames=pwrsupplies)
        dlg = ProgressDialog('Creating cycles...', create_task, self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            return False

        return pwrsupplies

    def _handle_checked_items_changed(self, item):
        psname = PVName(item.data(0, Qt.DisplayRole))
        if not _re.match('.*-.*:.*-.*', psname):
            return

        if not self._is_adv_mode and psname.sec == 'SI' and \
                not psname.dev.startswith('FC'):
            psname2check = Filter.process_filters(self._psnames,
                                                  filters={
                                                      'sec': 'SI',
                                                      'dev': '(?!FC)'
                                                  })
            psname2check.remove(psname)
            state2set = item.checkState(0)
            self.pwrsupplies_tree.tree.blockSignals(True)
            for psn in psname2check:
                item2check = self.pwrsupplies_tree._item_map[psn]
                if item2check.checkState(0) != state2set:
                    item2check.setData(0, Qt.CheckStateRole, state2set)
            self.pwrsupplies_tree.tree.blockSignals(False)
        else:
            if (psname.sec in ['BO', 'SI'] and psname.dev in ['B', 'B1B2']):
                psname2check = PSSearch.get_psnames({
                    'sec': psname.sec,
                    'dev': 'B.*'
                })
                psname2check.remove(psname)
                item2check = self.pwrsupplies_tree._item_map[psname2check[0]]

                state2set = item.checkState(0)
                state2change = item2check.checkState(0)
                if state2change != state2set:
                    self.pwrsupplies_tree.tree.blockSignals(True)
                    item2check.setData(0, Qt.CheckStateRole, state2set)
                    self.pwrsupplies_tree.tree.blockSignals(False)

        self._prepared.update(self._prepared_init_vals)
        self._needs_update_setup = True

    def _update_setup(self):
        if not self._needs_update_setup:
            return
        self._needs_update_setup = False

        # update leds
        psnames = self.pwrsupplies_tree.checked_items()
        ti_ch = [
            PVName(name).substitute(prefix=VACA_PREFIX)
            for name in self._timing.get_pvnames_by_psnames(psnames)
        ]
        self.ticonn_led.set_channels(ti_ch)

        ps_ch = list()
        for name in psnames:
            ps_ch.append(
                PVName(name).substitute(prefix=VACA_PREFIX,
                                        propty='PwrState-Sts'))
        self.psconn_led.set_channels(ps_ch)

        # update buttons and self._prepared dict if not in advanced mode
        if not self._is_adv_mode:
            has_si = False
            for psn in PSSearch.get_psnames({'sec': 'SI', 'dis': 'PS'}):
                if psn not in self.pwrsupplies_tree._item_map:
                    continue
                item = self.pwrsupplies_tree._item_map[psn]
                has_si |= item.checkState(0) != 0

            if not has_si:
                self.cycle_bt.setText('8. Cycle')
                self.restore_timing_bt.setText(
                    '9. Restore Timing Initial State')
                self.cycle_trims_bt.setVisible(False)
                self.cycle_trims_lb.setVisible(False)
                self._prepared['trims'] = True
            else:
                self.cycle_bt.setText('9. Cycle')
                self.restore_timing_bt.setText(
                    '10. Restore Timing Initial State')
                self.cycle_trims_bt.setVisible(True)
                self.cycle_trims_lb.setVisible(True)
                self._prepared['trims'] = False

        self._handle_stslabels_content()
        self._handle_buttons_enabled(True)

    # --- auxiliary checks ---

    def _check_connected(self, control):
        if control in ['trims', 'cycle']:
            leds = [self.ticonn_led, self.psconn_led]
        elif 'timing' in control:
            leds = [
                self.ticonn_led,
            ]
        else:
            leds = [
                self.psconn_led,
            ]

        for led in leds:
            pvs_disconnected = set()
            for ch, v in led.channels2conn.items():
                if not v:
                    pvs_disconnected.add(ch)
            if pvs_disconnected:
                sttr = ''
                for item in pvs_disconnected:
                    sttr += item + '\n'
                QMessageBox.information(
                    self, 'Message',
                    'The following PVs are not connected:\n' + sttr)
                return False
        return True

    def _verify_ps(self, pwrsupplies):
        self._ps_failed = set()

        task = VerifyPS(parent=self, psnames=pwrsupplies)
        task.itemDone.connect(self._get_ps_not_ready_2_cycle)

        dlg = ProgressDialog('Verifying power supplies initial state...', task,
                             self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            self._handle_buttons_enabled(True)
            return False

        if self._ps_failed:
            text = 'Verify power state and interlocks' \
                ' of the following power supplies'
            dlg = PSStatusDialog(self._ps_failed, text, self)
            dlg.exec_()
            self._handle_buttons_enabled(True)
            return False

        return True

    def _get_ps_not_ready_2_cycle(self, psname, status):
        if not status:
            self._ps_failed.add(psname)

    def _open_ps_detail(self, item):
        if self.sender() == self.progress_list:
            text_split = item.data(Qt.DisplayRole).split(' ')
            psname = ''
            for text in text_split:
                if _re.match('.*-.*:.*-.*', text):
                    psname = text
            if not psname:
                return
        else:
            psname = item.data()
        if not _re.match('.*-.*:.*-.*', psname):
            if item.model().rowCount(item) == 1:
                psname = item.child(0, 0).data()
            else:
                return
        run_newprocess(['sirius-hla-as-ps-detail.py', psname])

    # --- events ---

    def keyPressEvent(self, evt):
        """Implement keyPressEvent."""
        if evt.matches(QKeySequence.Copy) and self.progress_list.underMouse():
            items = self.progress_list.selectedItems()
            items = '\n'.join([i.text() for i in items])
            QApplication.clipboard().setText(items)
        if evt.key() == Qt.Key_Escape and self.progress_list.underMouse():
            items = self.progress_list.clearSelection()
        super().keyPressEvent(evt)

    def closeEvent(self, ev):
        self._update_setup_timer.stop()
        super().closeEvent(ev)
Example #6
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""

        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.

        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in SETTINGS.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """

        schema = json.loads(setting['json_schema'])
        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = SETTINGS.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in SETTINGS
            for setting_name, value in different_values.items():
                try:
                    setattr(SETTINGS._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict
                except:  # noqa: E722
                    continue
Example #7
0
class UI(QMainWindow):
    buttons = {}

    right_navbar_button_names = [
        "add brain regions",
        "add cells",
        "add from file",
    ]

    central_column_button_names = [
        "take screenshot",
        "reset",
        "top",
        "side1",
        "side2",
        "front",
    ]

    def __init__(self, theme="dark", **kwargs):
        super().__init__()

        # Get palette
        self.palette = _themes[theme]
        self.theme = theme

        # set the title and icon of main window
        self.setWindowTitle("BRAINGLOBE - brainrender GUI")

        logo_path = resource_filename("brainrender.gui.icons",
                                      "BG_logo_mini.svg")
        self.setWindowIcon(QtGui.QIcon(logo_path))

        # set the size of window
        self.Width = 3000
        self.height = int(0.618 * self.Width)
        self.resize(self.Width, self.height)

        # Create UI
        self.get_icons()
        self.initUI()
        self.setStyleSheet(update_css(style, self.palette))

    def get_icons(self):
        """
        Gets the correct path to the icons
        depending on the theme chosen
        """
        self.palette["branch_closed_img"] = resource_filename(
            "brainrender.gui.icons", f"right_{self.theme}.svg")
        self.palette["branch_opened_img"] = resource_filename(
            "brainrender.gui.icons", f"down_{self.theme}.svg")
        self.palette["checked_img"] = resource_filename(
            "brainrender.gui.icons", f"checkedbox_{self.theme}.svg")
        self.palette["unchecked_img"] = resource_filename(
            "brainrender.gui.icons", f"box_{self.theme}.svg")

    def make_left_navbar(self):
        """
        Creates the structures tree hierarchy widget and populates
        it with structures names from the brainglobe-api's Atlas.hierarchy
        tree view.
        """
        # Create QTree widget
        treeView = QTreeView()
        treeView.setExpandsOnDoubleClick(False)
        treeView.setHeaderHidden(True)
        treeView.setStyleSheet(update_css(tree_css, self.palette))
        treeView.setWordWrap(False)

        treeModel = QStandardItemModel()
        rootNode = treeModel.invisibleRootItem()

        # Add element's hierarchy
        tree = self.scene.atlas.hierarchy
        items = {}
        for n, node in enumerate(tree.expand_tree()):
            # Get Node info
            node = tree.get_node(node)
            if node.tag in ["VS", "fiber tracts"]:
                continue

            # Get brainregion name
            name = self.scene.atlas._get_from_structure(
                node.identifier, "name")

            # Create Item
            item = StandardItem(
                name,
                node.tag,
                tree.depth(node.identifier),
                self.palette["text"],
            )

            # Get/assign parents
            parent = tree.parent(node.identifier)
            if parent is not None:
                if parent.identifier not in items.keys():
                    continue
                else:
                    items[parent.identifier].appendRow(item)

            # Keep track of added nodes
            items[node.identifier] = item
            if n == 0:
                root = item

        # Finish up
        rootNode.appendRow(root)
        treeView.setModel(treeModel)
        treeView.expandToDepth(2)
        self.treeView = treeView

        return treeView

    def make_right_navbar(self):
        """
        Creates the widgets in the right navbar.
        """
        # make layout
        layout = QVBoxLayout()

        # Add label
        layout.addWidget(QLabel("Add actors"))

        # Add buttons
        for bname in self.right_navbar_button_names:
            btn = QPushButton(bname.capitalize(), self)
            btn.setObjectName(bname.replace(" ", "_"))
            self.buttons[bname.replace(" ", "_")] = btn
            layout.addWidget(btn)

        # Add label
        lbl = QLabel("Actors")
        lbl.setObjectName("LabelWithBorder")
        layout.addWidget(lbl)

        # add list widget
        self.actors_list = QListWidget()
        self.actors_list.setObjectName("actors_list")
        layout.addWidget(self.actors_list)

        # Add label
        lbl = QLabel("Actor properties")
        lbl.setObjectName("LabelWithBorder")
        layout.addWidget(lbl)

        # Actor Alpha
        alphalabel = QLabel("Alpha")
        alphalabel.setObjectName("PropertyName")
        self.alpha_textbox = QLineEdit(self)
        self.alpha_textbox.setObjectName("Property")
        layout.addWidget(alphalabel)
        layout.addWidget(self.alpha_textbox)

        # Actor Color
        colorlabel = QLabel("Color")
        colorlabel.setObjectName("PropertyName")
        self.color_textbox = QLineEdit(self)
        self.color_textbox.setObjectName("Property")
        layout.addWidget(colorlabel)
        layout.addWidget(self.color_textbox)

        # Add label
        lbl = QLabel("Show structures tree")
        lbl.setObjectName("LabelWithBorder")
        layout.addWidget(lbl)

        btn = QPushButton("Show structures tree", self)
        self.buttons[btn.text().lower().replace(" ", "_")] = btn
        layout.addWidget(btn)

        # set spacing
        layout.addStretch(5)
        layout.setSpacing(20)

        # make widget
        widget = QWidget()
        widget.setObjectName("RightNavbar")
        widget.setLayout(layout)

        return widget

    def make_central_column(self):
        """
        Creates vtkWidget for the vedo plotter and a few
        useful buttons, for the central part of the GUI
        """
        # Create layout, add canvas and buttons
        layout = QVBoxLayout()
        layout.addWidget(self.vtkWidget)

        # Make buttons
        boxes = [QHBoxLayout(), QHBoxLayout()]
        for n, bname in enumerate(self.central_column_button_names):
            btn = QPushButton(bname.capitalize(), self)
            btn.setObjectName(bname.replace(" ", "_"))
            self.buttons[bname.replace(" ", "_")] = btn

            if n == 0:
                boxes[0].addWidget(btn)
            else:
                boxes[1].addWidget(btn)

        hlayout = QHBoxLayout()

        widget = QWidget()
        widget.setLayout(boxes[0])
        widget.setObjectName("ScreenshotButtonLayout")
        hlayout.addWidget(widget)

        widget = QWidget()
        widget.setLayout(boxes[1])
        hlayout.addWidget(widget)

        widget = QWidget()
        widget.setObjectName("CentralColumn_buttons")
        widget.setLayout(hlayout)

        layout.addWidget(widget)

        # make widget
        widget = QWidget()
        widget.setObjectName("CentralColumn")
        widget.setLayout(layout)

        return widget

    def initUI(self):
        """
        Define UI elements of the app's main window
        """
        # Create navbars
        self.treeView = self.make_left_navbar()

        # Make overall layout
        main_layout = QHBoxLayout()

        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.make_central_column())
        splitter.addWidget(self.make_right_navbar())

        splitter.setSizes([200, 700, 10])
        main_layout.addWidget(splitter)

        self.treeView.setHidden(True)

        # Create main window widget
        main_widget = QWidget()
        main_widget.setObjectName("MainWidget")
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
Example #8
0
class PSTestWindow(SiriusMainWindow):
    """PS test window."""
    def __init__(self, parent=None, adv_mode=False):
        """Constructor."""
        super().__init__(parent)
        self.setWindowTitle('PS/PU Test')
        self.setObjectName('ASApp')
        cor = get_appropriate_color(section='AS')
        self.setWindowIcon(qta.icon('mdi.test-tube', color=cor))

        # auxiliar data for initializing SI Fam PS
        self._is_adv_mode = adv_mode
        self._si_fam_psnames = PSSearch.get_psnames(filters={
            'sec': 'SI',
            'sub': 'Fam',
            'dis': 'PS'
        })

        # auxiliary data for SI fast correctors
        self._si_fastcorrs = PSSearch.get_psnames(filters={
            'sec': 'SI',
            'dis': 'PS',
            'dev': 'FC.*'
        })

        self._needs_update_setup = False
        self._setup_ui()
        self._update_setup_timer = QTimer(self)
        self._update_setup_timer.timeout.connect(self._update_setup)
        self._update_setup_timer.setInterval(250)
        self._update_setup_timer.start()

    def _setup_ui(self):
        # setup central widget
        self.central_widget = QFrame()
        self.central_widget.setStyleSheet("""
            #OkList {
                background-color: #eafaea;
            }
            #NokList {
                background-color: #ffebe6;
            }
            QLabel{
                max-height: 1.29em;
            }
            QTabWidget::pane {
                border-left: 2px solid gray;
                border-bottom: 2px solid gray;
                border-right: 2px solid gray;
            }""")
        self.setCentralWidget(self.central_widget)

        self.tab = QTabWidget(self)
        self.tab.setObjectName('ASTab')

        # # PS
        self.ps_wid = QWidget(self)
        lay_ps = QGridLayout(self.ps_wid)
        lay_ps.setContentsMargins(0, 9, 0, 0)
        lay_ps.setHorizontalSpacing(0)

        # PS selection
        self.ps_tree = PVNameTree(items=self._get_ps_tree_names(),
                                  tree_levels=('sec', 'mag_group'),
                                  parent=self)
        self.ps_tree.tree.setHeaderHidden(True)
        self.ps_tree.setSizePolicy(QSizePolicy.Preferred,
                                   QSizePolicy.Preferred)
        self.ps_tree.tree.setColumnCount(1)
        self.ps_tree.tree.doubleClicked.connect(self._open_detail)
        self.ps_tree.tree.itemChanged.connect(
            self._handle_checked_items_changed)

        gbox_ps_select = QGroupBox('Select PS: ', self)
        gbox_ps_select.setObjectName('select')
        gbox_ps_select.setStyleSheet("""
            #select{
                border-top: 0px solid transparent;
                border-left: 0px solid transparent;
                border-bottom: 0px solid transparent;
            }""")
        lay_ps_select = QVBoxLayout(gbox_ps_select)
        lay_ps_select.addWidget(self.ps_tree)
        lay_ps.addWidget(gbox_ps_select, 0, 0)
        lay_ps.setColumnStretch(0, 1)

        # PS commands
        self.checkcomm_ps_bt = QPushButton('Check Communication', self)
        self.checkcomm_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.checkcomm_ps_bt.clicked.connect(self._check_comm)
        self.checkcomm_ps_bt.setToolTip(
            'Check PS and DCLinks communication status (verify invalid alarms '
            'and, if LI, the value of Connected-Mon PV)')

        self.checkstatus_ps_bt = QPushButton('Show Status Summary', self)
        self.checkstatus_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.checkstatus_ps_bt.clicked.connect(_part(self._check_status, 'PS'))
        self.checkstatus_ps_bt.setToolTip(
            'Check PS and DCLinks interlock status and, if powered on, '
            'check if it is following reference')

        self.dsbltrigger_ps_bt = QPushButton('Disable PS triggers', self)
        self.dsbltrigger_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.dsbltrigger_ps_bt.clicked.connect(
            _part(self._set_check_trigger_state, 'PS', 'dsbl'))

        self.setsofbmode_ps_bt = QPushButton('Turn Off SOFBMode', self)
        self.setsofbmode_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.setsofbmode_ps_bt.clicked.connect(
            _part(self._set_check_fbp_sofbmode, 'off'))

        self.setslowref_ps_bt = QPushButton('Set PS and DCLinks to SlowRef',
                                            self)
        self.setslowref_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.setslowref_ps_bt.clicked.connect(self._set_check_opmode_slowref)

        self.currzero_ps_bt1 = QPushButton('Set PS Current to zero', self)
        self.currzero_ps_bt1.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.currzero_ps_bt1.clicked.connect(self._set_zero_ps)

        self.reset_ps_bt = QPushButton('Reset PS and DCLinks', self)
        self.reset_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.reset_ps_bt.clicked.connect(_part(self._reset_intlk, 'PS'))

        self.prep_sidclink_bt = QPushButton('Prepare SI Fam DCLinks', self)
        self.prep_sidclink_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.prep_sidclink_bt.clicked.connect(self._prepare_sidclinks)
        self.prep_sidclink_bt.setVisible(False)

        self.init_sips_bt = QPushButton('Initialize SI Fam PS', self)
        self.init_sips_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.init_sips_bt.clicked.connect(self._set_check_pwrstateinit)
        self.init_sips_bt.setVisible(False)

        self.aux_label = QLabel('')
        self.aux_label.setVisible(False)

        self.turnon_dcl_bt = QPushButton('Turn DCLinks On', self)
        self.turnon_dcl_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.turnon_dcl_bt.clicked.connect(
            _part(self._set_check_pwrstate_dclinks, 'on'))

        self.checkctrlloop_dcl_bt = QPushButton('Check DCLinks CtrlLoop', self)
        self.checkctrlloop_dcl_bt.clicked.connect(
            _part(self._set_lastcomm, 'PS'))
        self.checkctrlloop_dcl_bt.clicked.connect(
            _part(self._set_check_ctrlloop, 'dclink'))

        self.setvolt_dcl_bt = QPushButton('Set DCLinks Voltage', self)
        self.setvolt_dcl_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.setvolt_dcl_bt.clicked.connect(self._set_check_dclinks_capvolt)

        self.turnon_ps_bt = QPushButton('Turn PS On', self)
        self.turnon_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.turnon_ps_bt.clicked.connect(
            _part(self._set_check_pwrstate, 'PS', 'on', True))

        self.setcheckctrlloop_ps_bt = QPushButton('Check PS CtrlLoop', self)
        self.setcheckctrlloop_ps_bt.clicked.connect(
            _part(self._set_lastcomm, 'PS'))
        self.setcheckctrlloop_ps_bt.clicked.connect(
            _part(self._set_check_ctrlloop, 'pwrsupply'))

        self.test_ps_bt = QPushButton('Set PS Current to test value', self)
        self.test_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.test_ps_bt.clicked.connect(self._set_test_ps)

        self.currzero_ps_bt2 = QPushButton('Set PS Current to zero', self)
        self.currzero_ps_bt2.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.currzero_ps_bt2.clicked.connect(self._set_zero_ps)

        self.restoretrigger_ps_bt = QPushButton('Restore PS triggers', self)
        self.restoretrigger_ps_bt.clicked.connect(
            _part(self._set_lastcomm, 'PS'))
        self.restoretrigger_ps_bt.clicked.connect(
            _part(self._restore_triggers_state, 'PS'))

        gbox_ps_comm = QGroupBox('Commands', self)
        gbox_ps_comm.setObjectName('comm')
        gbox_ps_comm.setStyleSheet('#comm{border: 0px solid transparent;}')
        lay_ps_comm = QVBoxLayout(gbox_ps_comm)
        lay_ps_comm.setContentsMargins(20, 9, 20, 9)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Check</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.checkcomm_ps_bt)
        lay_ps_comm.addWidget(self.checkstatus_ps_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Prepare</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.dsbltrigger_ps_bt)
        lay_ps_comm.addWidget(self.setsofbmode_ps_bt)
        lay_ps_comm.addWidget(self.setslowref_ps_bt)
        lay_ps_comm.addWidget(self.currzero_ps_bt1)
        lay_ps_comm.addWidget(self.reset_ps_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(self.prep_sidclink_bt)
        lay_ps_comm.addWidget(self.init_sips_bt)
        lay_ps_comm.addWidget(self.aux_label)
        lay_ps_comm.addWidget(
            QLabel('<h4>Config DCLinks</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.turnon_dcl_bt)
        lay_ps_comm.addWidget(self.checkctrlloop_dcl_bt)
        lay_ps_comm.addWidget(self.setvolt_dcl_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Test</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.turnon_ps_bt)
        lay_ps_comm.addWidget(self.setcheckctrlloop_ps_bt)
        lay_ps_comm.addWidget(self.test_ps_bt)
        lay_ps_comm.addWidget(self.currzero_ps_bt2)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Restore</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.restoretrigger_ps_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps.addWidget(gbox_ps_comm, 0, 1)
        lay_ps.setColumnStretch(1, 1)

        self.tab.addTab(self.ps_wid, 'PS')

        # # PU
        self.pu_wid = QWidget(self)
        lay_pu = QGridLayout(self.pu_wid)
        lay_pu.setContentsMargins(0, 9, 0, 0)
        lay_pu.setHorizontalSpacing(0)

        # PU selection
        self.pu_tree = PVNameTree(items=self._get_pu_tree_names(),
                                  tree_levels=('sec', ),
                                  parent=self)
        self.pu_tree.tree.setHeaderHidden(True)
        self.pu_tree.setSizePolicy(QSizePolicy.Preferred,
                                   QSizePolicy.Preferred)
        self.pu_tree.tree.setColumnCount(1)
        self.pu_tree.tree.doubleClicked.connect(self._open_detail)

        gbox_pu_select = QGroupBox('Select PU: ', self)
        gbox_pu_select.setObjectName('select')
        gbox_pu_select.setStyleSheet("""
            #select{
                border-top: 0px solid transparent;
                border-left: 0px solid transparent;
                border-bottom: 0px solid transparent;
            }""")
        lay_pu_select = QVBoxLayout(gbox_pu_select)
        lay_pu_select.addWidget(self.pu_tree)
        lay_pu.addWidget(gbox_pu_select, 0, 0)
        lay_pu.setColumnStretch(0, 1)

        # PU commands
        self.checkstatus_pu_bt = QPushButton('Show Status Summary', self)
        self.checkstatus_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.checkstatus_pu_bt.clicked.connect(_part(self._check_status, 'PU'))
        self.checkstatus_pu_bt.setToolTip(
            'Check PU interlock status and, if powered on, '
            'check if it is following voltage setpoint')

        self.voltzero_pu_bt1 = QPushButton('Set PU Voltage to zero', self)
        self.voltzero_pu_bt1.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.voltzero_pu_bt1.clicked.connect(_part(self._set_zero_pu, False))

        self.reset_pu_bt = QPushButton('Reset PU', self)
        self.reset_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.reset_pu_bt.clicked.connect(_part(self._reset_intlk, 'PU'))

        self.turnon_pu_bt = QPushButton('Turn PU On', self)
        self.turnon_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.turnon_pu_bt.clicked.connect(
            _part(self._set_check_pwrstate, 'PU', 'on', True))

        self.enblpulse_pu_bt = QPushButton('Enable PU Pulse', self)
        self.enblpulse_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.enblpulse_pu_bt.clicked.connect(_part(self._set_check_pulse,
                                                   'on'))

        self.enbltrigger_pu_bt = QPushButton('Enable PU triggers', self)
        self.enbltrigger_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.enbltrigger_pu_bt.clicked.connect(
            _part(self._set_check_trigger_state, 'PU', 'on'))

        self.test_pu_bt = QPushButton('Set PU Voltage to test value', self)
        self.test_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.test_pu_bt.clicked.connect(self._set_test_pu)

        self.voltzero_pu_bt2 = QPushButton('Set PU Voltage to zero', self)
        self.voltzero_pu_bt2.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.voltzero_pu_bt2.clicked.connect(_part(self._set_zero_pu, True))

        self.restoretrigger_pu_bt = QPushButton('Restore PU triggers', self)
        self.restoretrigger_pu_bt.clicked.connect(
            _part(self._set_lastcomm, 'PU'))
        self.restoretrigger_pu_bt.clicked.connect(
            _part(self._restore_triggers_state, 'PU'))

        gbox_pu_comm = QGroupBox('Commands', self)
        gbox_pu_comm.setObjectName('comm')
        gbox_pu_comm.setStyleSheet('#comm{border: 0px solid transparent;}')
        lay_pu_comm = QVBoxLayout(gbox_pu_comm)
        lay_pu_comm.setContentsMargins(20, 9, 20, 9)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Check</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.checkstatus_pu_bt)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Prepare</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.voltzero_pu_bt1)
        lay_pu_comm.addWidget(self.reset_pu_bt)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Test</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.turnon_pu_bt)
        lay_pu_comm.addWidget(self.enblpulse_pu_bt)
        lay_pu_comm.addWidget(self.enbltrigger_pu_bt)
        lay_pu_comm.addWidget(self.test_pu_bt)
        lay_pu_comm.addWidget(self.voltzero_pu_bt2)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Restore</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.restoretrigger_pu_bt)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu.addWidget(gbox_pu_comm, 0, 1)
        lay_pu.setColumnStretch(1, 1)

        self.tab.addTab(self.pu_wid, 'PU')

        # lists
        self.label_lastcomm = QLabel('Last Command: ', self)
        self.ok_ps = QListWidget(self)
        self.ok_ps.setObjectName('OkList')
        self.ok_ps.doubleClicked.connect(self._open_detail)
        self.ok_ps.setSelectionMode(QAbstractItemView.MultiSelection)
        self.ok_ps.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')
        self.nok_ps = QListWidget(self)
        self.nok_ps.setObjectName('NokList')
        self.nok_ps.doubleClicked.connect(self._open_detail)
        self.nok_ps.setSelectionMode(QAbstractItemView.MultiSelection)
        self.nok_ps.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')
        self.clearlists_bt = QPushButton('Clear', self)
        self.clearlists_bt.clicked.connect(self._clear_lastcomm)
        self.ok_ps_aux_list = list()
        self.nok_ps_aux_list = list()
        hbox = QHBoxLayout()
        hbox.addWidget(self.label_lastcomm)
        hbox.addWidget(self.clearlists_bt, alignment=Qt.AlignRight)
        list_layout = QGridLayout()
        list_layout.setContentsMargins(0, 0, 0, 0)
        list_layout.setVerticalSpacing(6)
        list_layout.setHorizontalSpacing(9)
        list_layout.addLayout(hbox, 0, 0, 1, 2)
        list_layout.addWidget(
            QLabel('<h4>Ok</h4>', self, alignment=Qt.AlignCenter), 1, 0)
        list_layout.addWidget(self.ok_ps, 2, 0)
        list_layout.addWidget(
            QLabel('<h4>Failed</h4>', self, alignment=Qt.AlignCenter), 1, 1)
        list_layout.addWidget(self.nok_ps, 2, 1)

        # menu
        self.menu = self.menuBar()

        self.act_cycle = self.menu.addAction('Open Cycle Window')
        connect_newprocess(self.act_cycle,
                           'sirius-hla-as-ps-cycle.py',
                           parent=self)

        self.aux_comm = self.menu.addMenu('Auxiliary commands')

        # # auxiliary PS
        self.ps_menu = self.aux_comm.addMenu('PS')

        self.act_turnoff_ps = self.ps_menu.addAction('Turn PS Off')
        self.act_turnoff_ps.triggered.connect(_part(self._set_lastcomm, 'PS'))
        self.act_turnoff_ps.triggered.connect(
            _part(self._set_check_pwrstate, 'PS', 'off', True))

        self.act_turnoff_dclink = self.ps_menu.addAction('Turn DCLinks Off')
        self.act_turnoff_dclink.triggered.connect(
            _part(self._set_lastcomm, 'PS'))
        self.act_turnoff_dclink.triggered.connect(
            _part(self._set_check_pwrstate_dclinks, 'off'))

        self.act_setcurrent_ps = self.ps_menu.addAction('Set PS Current')
        self.act_setcurrent_ps.triggered.connect(
            _part(self._set_lastcomm, 'PS'))
        self.act_setcurrent_ps.triggered.connect(self._set_check_current)

        self.act_opmode_ps = self.ps_menu.addAction('Set PS OpMode')
        self.act_opmode_ps.triggered.connect(_part(self._set_lastcomm, 'PS'))
        self.act_opmode_ps.triggered.connect(self._set_check_opmode)

        # # auxiliary PU
        self.pu_menu = self.aux_comm.addMenu('PU')

        self.act_turnoff_pu = self.pu_menu.addAction('Turn PU Off')
        self.act_turnoff_pu.triggered.connect(_part(self._set_lastcomm, 'PU'))
        self.act_turnoff_pu.triggered.connect(
            _part(self._set_check_pwrstate, 'PU', 'off', True))

        self.act_dsblpulse_pu = self.pu_menu.addAction('Disable PU Pulse')
        self.act_dsblpulse_pu.triggered.connect(_part(self._set_lastcomm,
                                                      'PU'))
        self.act_dsblpulse_pu.triggered.connect(
            _part(self._set_check_pulse, 'off'))

        # layout
        lay = QGridLayout()
        lay.setHorizontalSpacing(15)
        lay.setVerticalSpacing(5)
        lay.addWidget(
            QLabel('<h3>Power Supplies Test</h3>',
                   self,
                   alignment=Qt.AlignCenter), 0, 0, 1, 3)
        lay.addWidget(self.tab, 1, 0)
        lay.addLayout(list_layout, 1, 1)
        lay.setColumnStretch(0, 2)
        lay.setColumnStretch(1, 2)
        lay.setRowStretch(0, 2)
        lay.setRowStretch(1, 18)
        self.central_widget.setLayout(lay)

    # ---------- commands ------------

    def _check_comm(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return
        dclinks = self._get_related_dclinks(devices, include_regatrons=True)
        devices.extend(dclinks)

        task0 = CreateTesters(devices, parent=self)
        task1 = CheckComm(devices, parent=self)
        task1.itemDone.connect(self._log)

        labels = [
            'Connecting to devices...',
            'Checking PS and DCLinks Comm. Status...'
        ]
        tasks = [task0, task1]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _check_status(self, dev_type):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
            if not devices:
                return
            dclinks = self._get_related_dclinks(devices,
                                                include_regatrons=True)
            devices.extend(dclinks)
            label1 = 'Reading PS and DCLinks Status...'
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
            if not devices:
                return
            label1 = 'Reading PU Status...'

        task0 = CreateTesters(devices, parent=self)
        task1 = CheckStatus(devices, parent=self)
        task1.itemDone.connect(self._log)

        labels = ['Connecting to devices...', label1]
        tasks = [task0, task1]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_trigger_state(self, dev_type, state):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
        if not devices:
            return

        task1 = SetTriggerState(parent=self,
                                dis=dev_type,
                                state=state,
                                devices=devices)
        task2 = CheckTriggerState(parent=self,
                                  dis=dev_type,
                                  state=state,
                                  devices=devices)
        task2.itemDone.connect(self._log)
        tasks = [task1, task2]

        labels = [
            'Disabling ' + dev_type + ' triggers...',
            'Checking ' + dev_type + ' trigger states...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_opmode_slowref(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return
        dclinks = self._get_related_dclinks(devices)
        devices.extend(dclinks)
        devices = [dev for dev in devices if 'LI-' not in dev]

        task0 = CreateTesters(devices, parent=self)
        task1 = SetOpMode(devices, state=_PSC.OpMode.SlowRef, parent=self)
        task2 = CheckOpMode(devices,
                            state=[
                                _PSC.States.SlowRef, _PSC.States.Off,
                                _PSC.States.Interlock
                            ],
                            parent=self)
        task2.itemDone.connect(self._log)

        labels = [
            'Connecting to devices...', 'Setting PS OpMode to SlowRef...',
            'Checking PS OpMode...'
        ]
        tasks = [task0, task1, task2]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _reset_intlk(self, dev_type):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
            if not devices:
                return
            dclinks = self._get_related_dclinks(devices,
                                                include_regatrons=True)
            devices.extend(dclinks)
            dev_label = 'PS and DCLinks'
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
            if not devices:
                return
            dev_label = 'PU'
        devices_not_rst = {
            dev
            for dev in devices
            if dev.sec != 'LI' and dev.dev not in ('FCH', 'FCV')
        }

        task0 = CreateTesters(devices, parent=self)
        task1 = ResetIntlk(devices_not_rst, parent=self)
        task2 = CheckIntlk(devices, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Reseting ' + dev_label + '...',
            'Checking ' + dev_label + ' Interlocks...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_pwrstate(self, dev_type, state, show=True):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if isinstance(dev_type, list):
            devices = list(dev_type)
            dev_type = devices[0].dis
        else:
            if dev_type == 'PS':
                devices = self._get_selected_ps()
            elif dev_type == 'PU':
                devices = self._get_selected_pu()
        if not devices:
            return

        if state == 'on' and dev_type == 'PS':
            dev2ctrl = list(set(devices) - set(self._si_fam_psnames))
        else:
            dev2ctrl = devices

        task0 = CreateTesters(devices, parent=self)
        task1 = SetPwrState(dev2ctrl, state=state, parent=self)
        task2 = CheckPwrState(devices, state=state, is_test=True, parent=self)
        task2.itemDone.connect(_part(self._log, show=show))
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...',
            'Turning ' + dev_type + ' ' + state + '...',
            'Checking ' + dev_type + ' powered ' + state + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _prepare_sidclinks(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        selected = self._get_selected_ps()
        ps2check = [ps for ps in selected if ps in self._si_fam_psnames]
        dclinks = self._get_related_dclinks(ps2check, include_regatrons=True)
        if not ps2check:
            return

        # if power state is on, do nothing
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(ps2check, state='on', is_test=False, show=False)
        if set(self.ok_ps_aux_list) == set(ps2check):
            for dev in dclinks:
                self._log(dev, True)
            return

        ps2act = list(self.nok_ps_aux_list)  # act in PS off
        dcl2act = self._get_related_dclinks(ps2act, include_regatrons=True)
        # print('act', ps2act, dcl2act)

        # if need initializing, check if DCLinks are turned off
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(dcl2act, state='off', is_test=False, show=False)
        if not self.nok_ps_aux_list:
            for dev in dclinks:
                self._log(dev, True)
            return

        dcl2ctrl = list(self.nok_ps_aux_list)  # control DCLink on
        dcl_ok = set(dclinks) - set(dcl2ctrl)
        ps2ctrl = set()  # get related psnames
        for dcl in dcl2ctrl:
            pss = PSSearch.conv_dclink_2_psname(dcl)
            ps2ctrl.update(pss)
        # print('ctrl', ps2ctrl, dcl2ctrl)

        # if some DCLink is on, make sure related PS are turned off
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(ps2ctrl, state='off', is_test=False, show=False)
        if self.nok_ps_aux_list:
            ps2ctrl = list(self.nok_ps_aux_list)

            self.ok_ps_aux_list.clear()
            self.nok_ps_aux_list.clear()
            self._set_zero_ps(ps2ctrl, show=False)

            self.ok_ps_aux_list.clear()
            self.nok_ps_aux_list.clear()
            self._set_check_pwrstate(dev_type=ps2ctrl, state='off', show=False)

            if self.nok_ps_aux_list:
                for dev in self.ok_ps_aux_list:
                    self._log(dev, True)
                for dev in self.nok_ps_aux_list:
                    self._log(dev, False)
                text = 'The listed PS seems to be taking too\n'\
                    'long to turn off.\n'\
                    'Try to execute this step once again.'
                QMessageBox.warning(self, 'Message', text)
                return

        # finally, turn DCLinks off
        self._set_check_pwrstate_dclinks(state='off',
                                         devices=dcl2ctrl,
                                         ps2check=ps2ctrl)
        # log DCLinks Ok
        for dev in dcl_ok:
            self._log(dev, True)

    def _set_check_pwrstateinit(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        selected = self._get_selected_ps()
        devices = [ps for ps in selected if ps in self._si_fam_psnames]
        if not devices:
            return

        # if power state is on, do nothing
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(devices, state='on', is_test=False, show=False)
        if len(self.ok_ps_aux_list) == len(devices):
            for dev in self.ok_ps_aux_list:
                self._log(dev, True)
            return

        # if need initializing, check if DCLinks are turned off before continue
        ps_ok = list(self.ok_ps_aux_list)
        ps2ctrl = list(self.nok_ps_aux_list)  # check PS off
        dcl2check = self._get_related_dclinks(ps2ctrl, include_regatrons=True)
        # print('set_check_pwrstateinit', ps2ctrl)

        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(dcl2check, state='off', is_test=False, show=False)
        if len(self.nok_ps_aux_list) > 0:
            for dev in self.ok_ps_aux_list:
                self._log(dev, True)
            for dev in self.nok_ps_aux_list:
                self._log(dev, False)
            QMessageBox.critical(
                self, 'Message', 'Make sure related DCLinks are turned\n'
                'off before initialize SI Fam PS!')
            return

        # list in Ok PS already on
        for dev in ps_ok:
            self._log(dev, True)

        # then, initialize SI Fam PS
        task0 = CreateTesters(ps2ctrl, parent=self)
        task1 = SetPwrState(ps2ctrl, state='on', parent=self)
        task2 = CheckOpMode(ps2ctrl,
                            state=_PSC.States.Initializing,
                            parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Initializing SI Fam PS...',
            'Checking SI Fam PS initialized...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_pulse(self, state):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_pu()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetPulse(devices, state=state, parent=self)
        task2 = CheckPulse(devices, state=state, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Turning PU Pulse ' + state + '...',
            'Checking PU Pulse ' + state + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_pwrstate_dclinks(self,
                                    state,
                                    devices=list(),
                                    ps2check=list()):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if not devices:
            pwrsupplies = self._get_selected_ps()
            if not pwrsupplies:
                return
            devices = self._get_related_dclinks(pwrsupplies,
                                                include_regatrons=True)
            ps2check = set()
            for dev in devices:
                ps2check.update(PSSearch.conv_dclink_2_psname(dev))
        if not devices:
            return

        if state == 'off':
            self.ok_ps_aux_list.clear()
            self.nok_ps_aux_list.clear()
            self._check_pwrstate(ps2check, state='offintlk', show=False)
            if len(self.nok_ps_aux_list) > 0:
                for dev in self.ok_ps_aux_list:
                    self._log(dev, True)
                for dev in self.nok_ps_aux_list:
                    self._log(dev, False)
                QMessageBox.critical(
                    self, 'Message', 'Make sure all related power supplies\n'
                    'are turned off before turning DCLinks off!')
                return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetPwrState(devices, state=state, parent=self)
        task2 = CheckPwrState(devices, state=state, is_test=True, parent=self)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to DCLinks...', 'Turning DCLinks ' + state + '...',
            'Checking DCLinks powered ' + state + '...'
        ]

        if state == 'on':
            task3 = CheckInitOk(devices, parent=self)
            task3.itemDone.connect(self._log)
            tasks.append(task3)
            labels.append('Wait DCLinks initialize...')
        else:
            task2.itemDone.connect(self._log)

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _check_pwrstate(self, devices, state, is_test=True, show=True):
        self.ok_ps.clear()
        self.nok_ps.clear()

        task0 = CreateTesters(devices, parent=self)

        if state == 'offintlk':
            text = 'off or interlock'
            task1 = CheckOpMode(devices,
                                state=[_PSC.States.Off, _PSC.States.Interlock],
                                parent=self)
        else:
            text = state
            task1 = CheckPwrState(devices,
                                  state=state,
                                  is_test=is_test,
                                  parent=self)
        task1.itemDone.connect(_part(self._log, show=show))
        tasks = [task0, task1]

        labels = [
            'Connecting to devices...',
            'Checking devices powered ' + text + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_ctrlloop(self, dev_type='pwrsupply'):
        self.ok_ps.clear()
        self.nok_ps.clear()
        pwrsupplies = self._get_selected_ps()
        if not pwrsupplies:
            return
        devices_set = {}
        if dev_type == 'pwrsupply':
            devices = {dev for dev in pwrsupplies if 'LI' not in dev}
            devices_set = {dev for dev in devices if dev.dev in ('FCH', 'FCV')}
        else:
            devices = self._get_related_dclinks(pwrsupplies)
        if not devices:
            return

        tasks = [
            CreateTesters(devices, parent=self),
        ]
        labels = [
            'Connecting to devices...',
        ]
        if devices_set:
            task1 = SetCtrlLoop(devices_set, parent=self)
            task2 = CheckCtrlLoop(devices, parent=self)
            task2.itemDone.connect(self._log)
            tasks.extend([task1, task2])
            labels.extend(
                ['Setting CtrlLoop...', 'Checking CtrlLoop state...'])
        else:
            task1 = CheckCtrlLoop(devices, parent=self)
            task1.itemDone.connect(self._log)
            tasks.append(task1)
            labels.append('Checking CtrlLoop state...')
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_dclinks_capvolt(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        pwrsupplies = self._get_selected_ps()
        if not pwrsupplies:
            return
        devices = self._get_related_dclinks(pwrsupplies,
                                            include_regatrons=True)
        dev_exc_regatrons = {
            dev
            for dev in devices
            if PSSearch.conv_psname_2_psmodel(dev) != 'REGATRON_DCLink'
        }
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCapBankVolt(dev_exc_regatrons, parent=self)
        task2 = CheckCapBankVolt(devices, parent=self)
        task2.itemDone.connect(self._log)
        labels = [
            'Connecting to devices...', 'Setting capacitor bank voltage...',
            'Checking capacitor bank voltage...'
        ]
        tasks = [task0, task1, task2]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_fbp_sofbmode(self, state):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        devices = [
            dev for dev in devices
            if PSSearch.conv_psname_2_psmodel(dev) == 'FBP'
        ]
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetSOFBMode(devices, state=state, parent=self)
        task2 = CheckSOFBMode(devices, state=state, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Turning PS SOFBMode ' + state + '...',
            'Checking PS SOFBMode ' + state + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_test_ps(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCurrent(devices, is_test=True, parent=self)
        task2 = CheckCurrent(devices, is_test=True, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Testing PS... Setting current...',
            'Testing PS... Checking current value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_zero_ps(self, devices=list(), show=True):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if not devices:
            devices = self._get_selected_ps()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCurrent(devices, parent=self)
        task2 = CheckCurrent(devices, parent=self)
        task2.itemDone.connect(_part(self._log, show=show))
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Setting current to zero...',
            'Checking current value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_current(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return

        value, ok = QInputDialog.getDouble(self, "Setpoint Input",
                                           "Insert current setpoint: ")
        if not ok:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCurrent(devices, value=value, parent=self)
        task2 = CheckCurrent(devices, value=value, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Setting current...',
            'Checking current value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_opmode(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        devices = [
            dev for dev in devices
            if dev.sec != 'LI' and dev.dev not in ('FCH', 'FCV')
        ]
        if not devices:
            return

        state, ok = QInputDialog.getItem(self,
                                         "OpMode Input",
                                         "Select OpMode: ",
                                         _PSE.OPMODES,
                                         editable=False)
        if not ok:
            return
        state2set = getattr(_PSC.OpMode, state)
        state2check = getattr(_PSC.States, state)

        task0 = CreateTesters(devices, parent=self)
        task1 = SetOpMode(devices, state=state2set, parent=self)
        task2 = CheckOpMode(devices, state=state2check, parent=self)
        task2.itemDone.connect(self._log)

        labels = [
            'Connecting to devices...',
            'Setting PS OpMode to ' + state + '...',
            'Checking PS OpMode in ' + state + '...'
        ]
        tasks = [task0, task1, task2]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_test_pu(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_pu()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetVoltage(devices, is_test=True, parent=self)
        task2 = CheckVoltage(devices, is_test=True, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Testing PU... Setting voltage...',
            'Testing PU... Checking voltage value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_zero_pu(self, check=True):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_pu()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetVoltage(devices, parent=self)
        tasks = [task0, task1]

        labels = ['Connecting to devices...', 'Setting voltage to zero...']

        if check:
            task2 = CheckVoltage(devices, parent=self)
            task2.itemDone.connect(self._log)
            tasks.append(task2)
            labels.append('Checking voltage value...')
        else:
            task1.itemDone.connect(self._log)

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _restore_triggers_state(self, dev_type):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
        if not devices:
            return

        task1 = SetTriggerState(parent=self,
                                restore_initial_value=True,
                                dis=dev_type,
                                devices=devices)
        task2 = CheckTriggerState(parent=self,
                                  restore_initial_value=True,
                                  dis=dev_type,
                                  devices=devices)
        task2.itemDone.connect(self._log)
        tasks = [task1, task2]

        labels = [
            'Restoring ' + dev_type + ' trigger states...',
            'Checking ' + dev_type + ' trigger states...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    # ---------- log updates -----------

    def _set_lastcomm(self, dev_type):
        sender_text = self.sender().text()
        self.label_lastcomm.setText('Last Command: ' + dev_type + ' - ' +
                                    sender_text)

    def _clear_lastcomm(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        self.label_lastcomm.setText('Last Command: ')

    def _log(self, name, status, show=True):
        if show:
            if status:
                self.ok_ps.addItem(name)
            else:
                self.nok_ps.addItem(name)
        else:
            if status:
                self.ok_ps_aux_list.append(name)
            else:
                self.nok_ps_aux_list.append(name)

    # ---------- devices control ----------

    def _get_ps_tree_names(self):
        # add LI, TB, BO, TS
        psnames = PSSearch.get_psnames({'sec': '(LI|TB|BO|TS)', 'dis': 'PS'})
        # add SI Fams
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'sub': 'Fam',
                'dis': 'PS',
                'dev': '(B.*|Q.*|S.*)'
            }))
        # add SI Corrs
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'sub': '[0-2][0-9].*',
                'dis': 'PS',
                'dev': '(CH|CV)'
            }))
        # add SI QTrims
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'sub': '[0-2][0-9].*',
                'dis': 'PS',
                'dev': '(QD.*|QF.*|Q[1-4])'
            }))
        # add SI QSkews
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'dis': 'PS',
                'dev': 'QS'
            }))
        # add SI Fast Corrs
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'dis': 'PS',
                'dev': 'FC.*'
            }))
        return psnames

    def _get_pu_tree_names(self):
        punames = PSSearch.get_psnames({
            'sec': '(TB|BO|TS|SI)',
            'dis': 'PU',
            'dev': '.*(Kckr|Sept).*'
        })
        return punames

    def _get_selected_ps(self):
        devices = self.ps_tree.checked_items()
        if not devices:
            QMessageBox.critical(self, 'Message', 'No power supply selected!')
            return False
        devices = [PVName(dev) for dev in devices]
        return devices

    def _get_selected_pu(self):
        devices = self.pu_tree.checked_items()
        if not devices:
            QMessageBox.critical(self, 'Message',
                                 'No pulsed power supply selected!')
            return False
        devices = [PVName(dev) for dev in devices]
        return devices

    def _get_related_dclinks(self, psnames, include_regatrons=False):
        if isinstance(psnames, str):
            psnames = [
                psnames,
            ]
        alldclinks = set()
        for name in psnames:
            if 'LI' in name:
                continue
            dclinks = PSSearch.conv_psname_2_dclink(name)
            if dclinks:
                dclink_model = PSSearch.conv_psname_2_psmodel(dclinks[0])
                if dclink_model != 'REGATRON_DCLink':
                    alldclinks.update(dclinks)
                elif include_regatrons:
                    for dcl in dclinks:
                        dcl_typ = PSSearch.conv_psname_2_pstype(dcl)
                        if dcl_typ == 'as-dclink-regatron-master':
                            alldclinks.add(dcl)
        alldclinks = [PVName(dev) for dev in alldclinks]
        return alldclinks

    def _open_detail(self, index):
        name = PVName(index.data())
        if name.dis == 'TI':
            app = QApplication.instance()
            wind = create_window_from_widget(HLTriggerDetailed,
                                             title=name,
                                             is_main=True)
            app.open_window(wind, parent=self, **{'device': name})
            return
        elif not _re.match('.*-.*:.*-.*', name):
            if index.model().rowCount(index) == 1:
                name = PVName(index.child(0, 0).data())
            else:
                return
        if name.dis == 'PS':
            run_newprocess(['sirius-hla-as-ps-detail.py', name])
        elif name.dis == 'PU':
            run_newprocess(['sirius-hla-as-pu-detail.py', name])

    # ---------- update setup ----------

    def _handle_checked_items_changed(self, item):
        devname = PVName(item.data(0, Qt.DisplayRole))
        if not _re.match('.*-.*:.*-.*', devname):
            return
        state2set = item.checkState(0)

        # ensure power supplies that share dclinks are checked together
        if devname in self._si_fam_psnames and not self._is_adv_mode:
            dclinks = PSSearch.conv_psname_2_dclink(devname)
            if dclinks:
                psname2check = set()
                for dcl in dclinks:
                    relps = PSSearch.conv_dclink_2_psname(dcl)
                    relps.remove(devname)
                    psname2check.update(relps)
                self.ps_tree.tree.blockSignals(True)
                for psn in psname2check:
                    item2check = self.ps_tree._item_map[psn]
                    if item2check.checkState(0) != state2set:
                        item2check.setData(0, Qt.CheckStateRole, state2set)
                self.ps_tree.tree.blockSignals(False)

        self._needs_update_setup = True

    def _update_setup(self):
        if not self._needs_update_setup:
            return
        self._needs_update_setup = False

        # show/hide buttons to initialize SI Fam PS
        has_sifam = False
        for psn in self._si_fam_psnames:
            item = self.ps_tree._item_map[psn]
            has_sifam |= item.checkState(0) != 0

        self.prep_sidclink_bt.setVisible(has_sifam)
        self.init_sips_bt.setVisible(has_sifam)
        self.aux_label.setVisible(has_sifam)

        # set CtrlLoop button label
        has_fast = False
        for psn in self._si_fastcorrs:
            item = self.ps_tree._item_map[psn]
            has_fast |= item.checkState(0) != 0

        text = 'Set(FC) and Check PS CtrlLoop' \
            if has_fast else 'Check PS CtrlLoop'
        self.setcheckctrlloop_ps_bt.setText(text)

    # ---------- events ----------

    def keyPressEvent(self, evt):
        """Implement keyPressEvent."""
        if evt.matches(QKeySequence.Copy):
            if self.ok_ps.underMouse():
                items = self.ok_ps.selectedItems()
            elif self.nok_ps.underMouse():
                items = self.nok_ps.selectedItems()
            items = '\n'.join([i.text() for i in items])
            QApplication.clipboard().setText(items)
        if evt.key() == Qt.Key_Escape:
            if self.ok_ps.underMouse():
                items = self.ok_ps.clearSelection()
            elif self.nok_ps.underMouse():
                items = self.nok_ps.clearSelection()
        super().keyPressEvent(evt)
Example #9
0
class MacReportWindow(SiriusMainWindow):
    """Machine Report Window."""
    def __init__(self, parent=None):
        """Init."""
        super().__init__(parent)
        self._macreport = MacReport()
        self._macreport.connector.timeout = 5 * 60

        self.setWindowIcon(qta.icon('fa.book', color='gray'))
        self.setWindowTitle('Machine Reports')

        self._fsi = '{:8d}'
        self._fs1 = '{:8.3f}'
        self._fs2 = '{:8.3f} ± {:8.3f}'
        self._fst1 = '{:02d}h{:02d}'
        self._fst2 = '{:02d}h{:02d} ± {:02d}h{:02d}'

        self._update_task = None

        self._setupUi()
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFocus(True)

    def _setupUi(self):
        cwid = QWidget(self)
        self.setCentralWidget(cwid)

        title = QLabel('<h3>Machine Reports</h3>',
                       self,
                       alignment=Qt.AlignCenter)

        self._timesel_gbox = self._setupTimePeriodSelWidget()
        self._timesel_gbox.setObjectName('timesel_gbox')
        self._timesel_gbox.setStyleSheet(
            "#timesel_gbox{min-height: 8em; max-height: 8em;}")

        self._progress_list = QListWidget(self)
        self._progress_list.setObjectName('progress_list')
        self._progress_list.setStyleSheet(
            "#progress_list{min-height: 8em; max-height: 8em;}")

        self._reports_wid = QTabWidget(cwid)
        self._reports_wid.setObjectName('ASTab')
        self._reports_wid.addTab(self._setupUserShiftStatsWidget(),
                                 'User Shift Stats')
        self._reports_wid.addTab(self._setupLightSourceUsageStats(),
                                 'Light Source Usage Stats')
        self._reports_wid.addTab(self._setupStoredCurrentStats(),
                                 'Stored Current Stats')

        self._pb_showraw = QPushButton(qta.icon('mdi.chart-line'),
                                       'Show Raw Data', self)
        self._pb_showraw.setEnabled(False)
        self._pb_showraw.clicked.connect(self._show_raw_data)

        self._pb_showpvsd = QPushButton(qta.icon('mdi.chart-line'),
                                        'Show Progrmd.vs.Delivered Hours',
                                        self)
        self._pb_showpvsd.setEnabled(False)
        self._pb_showpvsd.clicked.connect(self._show_progmd_vs_delivd)

        lay = QGridLayout(cwid)
        lay.setVerticalSpacing(10)
        lay.setHorizontalSpacing(10)
        lay.setContentsMargins(18, 9, 18, 9)
        lay.addWidget(title, 0, 0, 1, 3)
        lay.addWidget(self._timesel_gbox, 1, 0)
        lay.addWidget(self._progress_list,
                      1,
                      1,
                      1,
                      2,
                      alignment=Qt.AlignBottom)
        lay.addWidget(self._reports_wid, 2, 0, 1, 3)
        lay.addWidget(self._pb_showpvsd, 4, 0, alignment=Qt.AlignLeft)
        lay.addWidget(self._pb_showraw, 4, 2, alignment=Qt.AlignRight)

        self._updateUserShiftStats(setup=True)
        self._updateStoredCurrentStats(setup=True)
        self._updateLightSourceUsageStats(setup=True)

    def _setupTimePeriodSelWidget(self):
        tnow = Time.now()

        ld_tstart = QLabel('Time start: ')
        self.dt_start = QDateTimeEdit(tnow - 10 * 60, self)
        self.dt_start.setCalendarPopup(True)
        self.dt_start.setMinimumDate(Time(2020, 1, 1))
        self.dt_start.setDisplayFormat('dd/MM/yyyy hh:mm')

        ld_tstop = QLabel('Time stop: ')
        self.dt_stop = QDateTimeEdit(tnow, self)
        self.dt_stop.setCalendarPopup(True)
        self.dt_stop.setMinimumDate(Time(2020, 1, 1))
        self.dt_stop.setDisplayFormat('dd/MM/yyyy hh:mm')

        self.pb_search = QPushButton(qta.icon('fa5s.search'), 'Search', self)
        self.pb_search.clicked.connect(self._do_update)
        self.pb_search.setObjectName('pb_search')
        self.pb_search.setStyleSheet("""
            #pb_search{
                min-width:100px; max-width:100px;
                min-height:25px; max-height:25px;
                icon-size:20px;}
        """)

        wid = QGroupBox('Select interval: ', self)
        lay = QGridLayout(wid)
        lay.addWidget(ld_tstart, 0, 0)
        lay.addWidget(self.dt_start, 0, 1)
        lay.addWidget(ld_tstop, 1, 0)
        lay.addWidget(self.dt_stop, 1, 1)
        lay.addWidget(self.pb_search, 2, 1, alignment=Qt.AlignRight)
        return wid

    def _setupUserShiftStatsWidget(self):
        self.lb_uspt = LbData('')
        self.lb_usdt = LbData('')
        self.lb_ustt = LbData('')
        self.lb_uset = LbData('')
        self.lb_uspc = LbData('')
        self.lb_cav = LbData('')
        self.lb_cbav = LbData('')
        self.lb_ceav = LbData('')
        self.lb_tft = LbData('')
        self.lb_bdc = LbData('')
        self.lb_mttr = LbData('')
        self.lb_mtbf = LbData('')
        self.lb_reli = LbData('')
        self.lb_tsbt = LbData('')
        self.lb_tubt = LbData('')
        self.lb_mtbu = LbData('')
        self.lb_rsbt = LbData('')
        self.lb_itav = LbData('')

        wid = QWidget(self)
        lay = QGridLayout(wid)
        lay.setVerticalSpacing(0)
        lay.setHorizontalSpacing(0)
        lay.setAlignment(Qt.AlignTop)
        lay.addItem(QSpacerItem(120, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 0)
        lay.addWidget(LbHHeader('Programmed Time (h)'), 0, 1)
        lay.addWidget(self.lb_uspt, 0, 2)
        lay.addWidget(LbHHeader('Delivered Time (h)'), 1, 1)
        lay.addWidget(self.lb_usdt, 1, 2)
        lay.addWidget(LbHHeader('Total Time (h)'), 2, 1)
        lay.addWidget(self.lb_ustt, 2, 2)
        lay.addWidget(LbHHeader('Extra Time (h)'), 3, 1)
        lay.addWidget(self.lb_uset, 3, 2)
        lay.addWidget(LbHHeader('# Programmed Shifts'), 4, 1)
        lay.addWidget(self.lb_uspc, 4, 2)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA)'), 5, 1)
        lay.addWidget(self.lb_cav, 5, 2)
        lay.addWidget(
            LbHHeader('Current at the Beg. of the Shift (avg ± std) (mA)'), 6,
            1)
        lay.addWidget(self.lb_cbav, 6, 2)
        lay.addWidget(
            LbHHeader('Current at the End of the Shift (avg ± std) (mA)'), 7,
            1)
        lay.addWidget(self.lb_ceav, 7, 2)
        lay.addWidget(LbHHeader('Total Failures Time (h)'), 8, 1)
        lay.addWidget(self.lb_tft, 8, 2)
        lay.addWidget(LbHHeader('# Beam Dumps'), 9, 1)
        lay.addWidget(self.lb_bdc, 9, 2)
        lay.addWidget(LbHHeader('Time To Recover (avg ± std) (h)'), 10, 1)
        lay.addWidget(self.lb_mttr, 10, 2)
        lay.addWidget(LbHHeader('Time Between Failures (avg) (h)'), 11, 1)
        lay.addWidget(self.lb_mtbf, 11, 2)
        lay.addWidget(LbHHeader('Beam Reliability (%)'), 12, 1)
        lay.addWidget(self.lb_reli, 12, 2)
        lay.addWidget(LbHHeader('Total stable beam time (h)'), 13, 1)
        lay.addWidget(self.lb_tsbt, 13, 2)
        lay.addWidget(LbHHeader('Total unstable beam time (h)'), 14, 1)
        lay.addWidget(self.lb_tubt, 14, 2)
        lay.addWidget(LbHHeader('Time between unstable beams (avg) (h)'), 15,
                      1)
        lay.addWidget(self.lb_mtbu, 15, 2)
        lay.addWidget(LbHHeader('Relative stable beam time (%)'), 16, 1)
        lay.addWidget(self.lb_rsbt, 16, 2)
        lay.addWidget(LbHHeader('Injection time (avg ± std) (h)'), 17, 1)
        lay.addWidget(self.lb_itav, 17, 2)
        lay.addItem(QSpacerItem(120, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 3)
        return wid

    def _updateUserShiftStats(self, setup=False):
        w2r = {
            'uspt': [
                'usershift_progmd_time',
            ],
            'usdt': [
                'usershift_delivd_time',
            ],
            'ustt': [
                'usershift_total_time',
            ],
            'uset': [
                'usershift_extra_time',
            ],
            'uspc': [
                'usershift_progmd_count',
            ],
            'cav': ['usershift_current_average', 'usershift_current_stddev'],
            'cbav':
            ['usershift_current_beg_average', 'usershift_current_beg_stddev'],
            'ceav':
            ['usershift_current_end_average', 'usershift_current_end_stddev'],
            'tft': [
                'usershift_total_failures_time',
            ],
            'bdc': [
                'usershift_beam_dump_count',
            ],
            'mttr': [
                'usershift_time_to_recover_average',
                'usershift_time_to_recover_stddev'
            ],
            'mtbf': [
                'usershift_time_between_failures_average',
            ],
            'reli': [
                'usershift_beam_reliability',
            ],
            'tsbt': [
                'usershift_total_stable_beam_time',
            ],
            'tubt': [
                'usershift_total_unstable_beam_time',
            ],
            'mtbu': ['usershift_time_between_unstable_beams_average'],
            'rsbt': ['usershift_relative_stable_beam_time'],
            'itav': [
                'usershift_injection_time_average',
                'usershift_injection_time_stddev'
            ]
        }

        for wname, rname in w2r.items():
            wid = getattr(self, 'lb_' + wname)
            items = [getattr(self._macreport, n) for n in rname]
            if 'time' in rname[0] and 'relative' not in rname[0]:
                if len(items) == 2:
                    if items[0] not in [None, _np.inf]:
                        hour1 = int(items[0])
                        minu1 = int((items[0] - hour1) * 60)
                        hour2 = int(items[1])
                        minu2 = int((items[1] - hour2) * 60)
                        items = [hour1, minu1, hour2, minu2]
                        str2fmt = self._fst2
                    else:
                        str2fmt = self._fs1
                elif items[0] not in [None, _np.inf]:
                    hour = int(items[0])
                    minu = int((items[0] - hour) * 60)
                    items = [hour, minu]
                    str2fmt = self._fst1
                else:
                    str2fmt = self._fs1
            else:
                str2fmt = getattr(
                    self,
                    '_fsi' if 'count' in rname[0] else '_fs' + str(len(rname)))
            text = '' if any([i is None for i in items]) \
                else str2fmt.format(*items)
            wid.setText(text)
            if setup:
                wid.setToolTip(getattr(MacReport, rname[0]).__doc__)

    def _setupStoredCurrentStats(self):
        self.lb_user_mb_avg = LbData('')
        self.lb_user_mb_intvl = LbData('')
        self.lb_user_sb_avg = LbData('')
        self.lb_user_sb_intvl = LbData('')
        self.lb_user_tt_avg = LbData('')
        self.lb_user_tt_intvl = LbData('')
        self.lb_commi_mb_avg = LbData('')
        self.lb_commi_mb_intvl = LbData('')
        self.lb_commi_sb_avg = LbData('')
        self.lb_commi_sb_intvl = LbData('')
        self.lb_commi_tt_avg = LbData('')
        self.lb_commi_tt_intvl = LbData('')
        self.lb_condi_mb_avg = LbData('')
        self.lb_condi_mb_intvl = LbData('')
        self.lb_condi_sb_avg = LbData('')
        self.lb_condi_sb_intvl = LbData('')
        self.lb_condi_tt_avg = LbData('')
        self.lb_condi_tt_intvl = LbData('')
        self.lb_mstdy_mb_avg = LbData('')
        self.lb_mstdy_mb_intvl = LbData('')
        self.lb_mstdy_sb_avg = LbData('')
        self.lb_mstdy_sb_intvl = LbData('')
        self.lb_mstdy_tt_avg = LbData('')
        self.lb_mstdy_tt_intvl = LbData('')
        self.lb_stord_mb_avg = LbData('')
        self.lb_stord_mb_intvl = LbData('')
        self.lb_stord_sb_avg = LbData('')
        self.lb_stord_sb_intvl = LbData('')
        self.lb_stord_tt_avg = LbData('')
        self.lb_stord_tt_intvl = LbData('')

        wid = QWidget(self)
        lay = QGridLayout(wid)
        lay.setVerticalSpacing(0)
        lay.setHorizontalSpacing(0)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA) (MB)'), 1, 0)
        lay.addWidget(LbHHeader('Time in MB mode (h)'), 2, 0)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA) (SB)'), 3, 0)
        lay.addWidget(LbHHeader('Time in SB mode (h)'), 4, 0)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA) (SB+MB)'), 5, 0)
        lay.addWidget(LbHHeader('Total Time (h) (SB+MB)'), 6, 0)
        lay.addWidget(LbVHeader('Users'), 0, 1)
        lay.addWidget(self.lb_user_mb_avg, 1, 1)
        lay.addWidget(self.lb_user_mb_intvl, 2, 1)
        lay.addWidget(self.lb_user_sb_avg, 3, 1)
        lay.addWidget(self.lb_user_sb_intvl, 4, 1)
        lay.addWidget(self.lb_user_tt_avg, 5, 1)
        lay.addWidget(self.lb_user_tt_intvl, 6, 1)
        lay.addWidget(LbVHeader('Commissioning'), 0, 2)
        lay.addWidget(self.lb_commi_mb_avg, 1, 2)
        lay.addWidget(self.lb_commi_mb_intvl, 2, 2)
        lay.addWidget(self.lb_commi_sb_avg, 3, 2)
        lay.addWidget(self.lb_commi_sb_intvl, 4, 2)
        lay.addWidget(self.lb_commi_tt_avg, 5, 2)
        lay.addWidget(self.lb_commi_tt_intvl, 6, 2)
        lay.addWidget(LbVHeader('Conditioning'), 0, 3)
        lay.addWidget(self.lb_condi_mb_avg, 1, 3)
        lay.addWidget(self.lb_condi_mb_intvl, 2, 3)
        lay.addWidget(self.lb_condi_sb_avg, 3, 3)
        lay.addWidget(self.lb_condi_sb_intvl, 4, 3)
        lay.addWidget(self.lb_condi_tt_avg, 5, 3)
        lay.addWidget(self.lb_condi_tt_intvl, 6, 3)
        lay.addWidget(LbVHeader('Machine Study'), 0, 4)
        lay.addWidget(self.lb_mstdy_mb_avg, 1, 4)
        lay.addWidget(self.lb_mstdy_mb_intvl, 2, 4)
        lay.addWidget(self.lb_mstdy_sb_avg, 3, 4)
        lay.addWidget(self.lb_mstdy_sb_intvl, 4, 4)
        lay.addWidget(self.lb_mstdy_tt_avg, 5, 4)
        lay.addWidget(self.lb_mstdy_tt_intvl, 6, 4)
        lay.addWidget(LbVHeader('All Stored Beam'), 0, 5)
        lay.addWidget(self.lb_stord_mb_avg, 1, 5)
        lay.addWidget(self.lb_stord_mb_intvl, 2, 5)
        lay.addWidget(self.lb_stord_sb_avg, 3, 5)
        lay.addWidget(self.lb_stord_sb_intvl, 4, 5)
        lay.addWidget(self.lb_stord_tt_avg, 5, 5)
        lay.addWidget(self.lb_stord_tt_intvl, 6, 5)
        return wid

    def _updateStoredCurrentStats(self, setup=False):
        shifttype = {
            'mstdy': 'machinestudy',
            'commi': 'commissioning',
            'condi': 'conditioning',
            'stord': 'ebeam',
            'user': '******'
        }
        fillmode = {'mb': 'multibunch', 'sb': 'singlebunch', 'tt': 'total'}
        stats = {
            'avg': ['average', 'stddev'],
            'intvl': [
                'time',
            ]
        }

        for wsht, rsht in shifttype.items():
            for wfm, rfm in fillmode.items():
                for wstt, rstt in stats.items():
                    wid = getattr(self, 'lb_' + wsht + '_' + wfm + '_' + wstt)
                    pname = 'current_' + rsht + '_' + rfm + '_'
                    items = [getattr(self._macreport, pname + i) for i in rstt]
                    if 'time' in rstt[0] and items[0] is not None:
                        hour = int(items[0])
                        minu = int((items[0] - hour) * 60)
                        items = [hour, minu]
                    str2fmt = getattr(
                        self, '_fst1' if
                        ('time' in rstt[0]) else '_fs' + str(len(rstt)))
                    text = '' if any([i is None for i in items]) \
                        else str2fmt.format(*items)
                    wid.setText(text)
                    if setup:
                        wid.setToolTip(
                            getattr(MacReport, pname + rstt[0]).__doc__)

    def _setupLightSourceUsageStats(self):
        self.lb_mstdy_fail_intvl = LbData('')
        self.lb_mstdy_fail_pcntl = LbData('')
        self.lb_mstdy_oper_intvl = LbData('')
        self.lb_mstdy_oper_pcntl = LbData('')
        self.lb_mstdy_total_intvl = LbData('')
        self.lb_mstdy_total_pcntl = LbData('')
        self.lb_commi_fail_intvl = LbData('')
        self.lb_commi_fail_pcntl = LbData('')
        self.lb_commi_oper_intvl = LbData('')
        self.lb_commi_oper_pcntl = LbData('')
        self.lb_commi_total_intvl = LbData('')
        self.lb_commi_total_pcntl = LbData('')
        self.lb_condi_fail_intvl = LbData('')
        self.lb_condi_fail_pcntl = LbData('')
        self.lb_condi_oper_intvl = LbData('')
        self.lb_condi_oper_pcntl = LbData('')
        self.lb_condi_total_intvl = LbData('')
        self.lb_condi_total_pcntl = LbData('')
        self.lb_maint_fail_intvl = LbData('')
        self.lb_maint_fail_pcntl = LbData('')
        self.lb_maint_oper_intvl = LbData('')
        self.lb_maint_oper_pcntl = LbData('')
        self.lb_maint_total_intvl = LbData('')
        self.lb_maint_total_pcntl = LbData('')
        self.lb_user_fail_intvl = LbData('')
        self.lb_user_fail_pcntl = LbData('')
        self.lb_user_oper_intvl = LbData('')
        self.lb_user_oper_pcntl = LbData('')
        self.lb_user_total_intvl = LbData('')
        self.lb_user_total_pcntl = LbData('')
        self.lb_total_intvl = LbHHeader('Total Usage Time (h): - ')

        wid = QWidget(self)
        lay = QGridLayout(wid)
        lay.setVerticalSpacing(0)
        lay.setHorizontalSpacing(0)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(LbHHeader('Operational Time (h)'), 1, 0)
        lay.addWidget(LbHHeader('Operational Percentage (%)'), 2, 0)
        lay.addWidget(LbHHeader('Failures Time (h)'), 3, 0)
        lay.addWidget(LbHHeader('Failures Percentage (%)'), 4, 0)
        lay.addWidget(LbHHeader('Shift Time (h)'), 5, 0)
        lay.addWidget(LbHHeader('Shift Percentage (%)'), 6, 0)
        lay.addWidget(self.lb_total_intvl, 7, 0, 1, 6)
        lay.addWidget(LbVHeader('Users'), 0, 1)
        lay.addWidget(self.lb_user_oper_intvl, 1, 1)
        lay.addWidget(self.lb_user_oper_pcntl, 2, 1)
        lay.addWidget(self.lb_user_fail_intvl, 3, 1)
        lay.addWidget(self.lb_user_fail_pcntl, 4, 1)
        lay.addWidget(self.lb_user_total_intvl, 5, 1)
        lay.addWidget(self.lb_user_total_pcntl, 6, 1)
        lay.addWidget(LbVHeader('Commissioning'), 0, 2)
        lay.addWidget(self.lb_commi_oper_intvl, 1, 2)
        lay.addWidget(self.lb_commi_oper_pcntl, 2, 2)
        lay.addWidget(self.lb_commi_fail_intvl, 3, 2)
        lay.addWidget(self.lb_commi_fail_pcntl, 4, 2)
        lay.addWidget(self.lb_commi_total_intvl, 5, 2)
        lay.addWidget(self.lb_commi_total_pcntl, 6, 2)
        lay.addWidget(LbVHeader('Conditioning'), 0, 3)
        lay.addWidget(self.lb_condi_oper_intvl, 1, 3)
        lay.addWidget(self.lb_condi_oper_pcntl, 2, 3)
        lay.addWidget(self.lb_condi_fail_intvl, 3, 3)
        lay.addWidget(self.lb_condi_fail_pcntl, 4, 3)
        lay.addWidget(self.lb_condi_total_intvl, 5, 3)
        lay.addWidget(self.lb_condi_total_pcntl, 6, 3)
        lay.addWidget(LbVHeader('Machine Study'), 0, 4)
        lay.addWidget(self.lb_mstdy_oper_intvl, 1, 4)
        lay.addWidget(self.lb_mstdy_oper_pcntl, 2, 4)
        lay.addWidget(self.lb_mstdy_fail_intvl, 3, 4)
        lay.addWidget(self.lb_mstdy_fail_pcntl, 4, 4)
        lay.addWidget(self.lb_mstdy_total_intvl, 5, 4)
        lay.addWidget(self.lb_mstdy_total_pcntl, 6, 4)
        lay.addWidget(LbVHeader('Maintenance'), 0, 5)
        lay.addWidget(self.lb_maint_oper_intvl, 1, 5)
        lay.addWidget(self.lb_maint_oper_pcntl, 2, 5)
        lay.addWidget(self.lb_maint_fail_intvl, 3, 5)
        lay.addWidget(self.lb_maint_fail_pcntl, 4, 5)
        lay.addWidget(self.lb_maint_total_intvl, 5, 5)
        lay.addWidget(self.lb_maint_total_pcntl, 6, 5)
        return wid

    def _updateLightSourceUsageStats(self, setup=False):
        shifttype = {
            'mstdy': 'machinestudy',
            'commi': 'commissioning',
            'condi': 'conditioning',
            'maint': 'maintenance',
            'user': '******'
        }
        intervaltype = {
            'fail': '_failures',
            'oper': '_operational',
            'total': ''
        }
        for wst, rst in shifttype.items():
            for wit, rit in intervaltype.items():
                widt = getattr(self, 'lb_' + wst + '_' + wit + '_intvl')
                tname = 'lsusage_' + rst + rit + '_time'
                tval = getattr(self._macreport, tname)
                if tval is None:
                    text = ''
                else:
                    hour = int(tval)
                    minu = int((tval - hour) * 60)
                    text = self._fst1.format(hour, minu)
                widt.setText(text)
                if setup:
                    widt.setToolTip(getattr(MacReport, tname).__doc__)

                widp = getattr(self, 'lb_' + wst + '_' + wit + '_pcntl')
                pname = 'lsusage_' + rst + rit
                pval = getattr(self._macreport, pname)
                text = '' if pval is None else self._fs1.format(pval)
                widp.setText(text)
                if setup:
                    widp.setToolTip(getattr(MacReport, pname).__doc__)

        text = 'Total Usage Time (h): '
        if self._macreport.lsusage_total_time is not None:
            val = self._macreport.lsusage_total_time
            hour = int(val)
            minu = int((val - hour) * 60)
            text += self._fst1.format(hour, minu)
        self.lb_total_intvl.setText(text)

    def _do_update(self):
        if self.sender().text() == 'Abort':
            self._update_task.terminate()
            now = Time.now().strftime('%Y/%m/%d-%H:%M:%S')
            item = QListWidgetItem(now + '  Aborted.')
            self._progress_list.addItem(item)
            self._progress_list.scrollToBottom()
            self._setup_search_button()
        else:
            if self.dt_start.dateTime() >= self.dt_stop.dateTime() or \
                    self.dt_start.dateTime() > Time.now() or \
                    self.dt_stop.dateTime() > Time.now():
                QMessageBox.warning(self, 'Ops...',
                                    'Insert a valid time interval.')
                return

            self._macreport.timestamp_start = \
                self.dt_start.dateTime().toSecsSinceEpoch()
            self._macreport.timestamp_stop = \
                self.dt_stop.dateTime().toSecsSinceEpoch()

            self._progress_list.clear()
            self._pb_showraw.setEnabled(False)
            self._pb_showpvsd.setEnabled(False)
            self._setup_search_button()

            self._update_task = UpdateTask(self._macreport)
            self._update_task.updated.connect(self._update_progress)
            self._update_task.start()

    def _update_progress(self, message):
        item = QListWidgetItem(message)
        self._progress_list.addItem(item)
        self._progress_list.scrollToBottom()

        if 'Collected' in message:
            self._setup_search_button()
            self._updateUserShiftStats()
            self._updateStoredCurrentStats()
            self._updateLightSourceUsageStats()
            self._pb_showraw.setEnabled(True)
            self._pb_showpvsd.setEnabled(True)

    def _setup_search_button(self):
        if self.pb_search.text() == 'Abort':
            self.pb_search.setIcon(qta.icon('fa5s.search'))
            self.pb_search.setText('Search')
        else:
            self.pb_search.setIcon(
                qta.icon('fa5s.spinner', animation=qta.Spin(self.pb_search)))
            self.pb_search.setText('Abort')

    def _show_raw_data(self):
        fig = self._macreport.plot_raw_data()
        wid = MatplotlibWidget(fig)
        wid.setWindowTitle('Machine Reports - Raw Data (' +
                           str(self._macreport.time_start) + ' -> ' +
                           str(self._macreport.time_stop) + ')')
        wid.show()

    def _show_progmd_vs_delivd(self):
        fig = self._macreport.plot_progmd_vs_delivd_hours()
        wid = MatplotlibWidget(fig)
        wid.setWindowTitle(
            'Machine Reports - Programmed vs. Delivered Hours (' +
            str(self._macreport.time_start) + ' -> ' +
            str(self._macreport.time_stop) + ')')
        wid.show()