Source code for spinetoolbox.project_items.exporter.widgets.parameter_merging_settings

######################################################################################################################
# Copyright (C) 2017-2020 Spine project consortium
# This file is part of Spine Toolbox.
# Spine Toolbox is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################

"""
Parameter merging settings widget.

:author: A. Soininen (VTT)
:date:   19.2.2020
"""

from PySide2.QtCore import QAbstractListModel, QItemSelectionModel, QModelIndex, Qt, Signal, Slot
from PySide2.QtWidgets import QWidget
from spinetoolbox.spine_io.exporters.gdx import MergingSetting
from .merging_error_flag import MergingErrorFlag

[docs]_ERROR_MESSAGE = "<span style='color:#ff3333;white-space: pre-wrap;'>{}</span>"
[docs]class ParameterMergingSettings(QWidget): """A widget for configure parameter merging."""
[docs] removal_requested = Signal("QVariant")
"""Emitted when the settings widget wants to get removed from the parent window.""" def __init__(self, entity_class_infos, parent, parameter_name=None, merging_setting=None): """ Args: entity_class_infos (list): list of EntityClassInfo objects parent (QWidget): a parent widget parameter_name (str): merged parameter name of None for widget merging_setting (MergingSetting): merging settings or None for empty widget """ from ..ui.parameter_merging_settings import Ui_Form super().__init__(parent) self._error_flags = ( MergingErrorFlag.DOMAIN_NAME_MISSING | MergingErrorFlag.PARAMETER_NAME_MISSING | MergingErrorFlag.NO_PARAMETER_SELECTED ) self._parameter_name = "" self._selected_domain_row = None self._index_position = 0 self._ui = Ui_Form() self._ui.setupUi(self) self._ui.parameter_name_edit.textChanged.connect(self._update_parameter_name) self._ui.remove_button.clicked.connect(self._remove_self) self._domain_names_model = _DomainNameListModel(entity_class_infos) self._ui.domains_list_view.setModel(self._domain_names_model) self._ui.domains_list_view.selectionModel().selectionChanged.connect(self._handle_domain_selection_change) self._parameter_name_list_model = _ParameterNameListModel([]) self._ui.parameter_name_list_view.setModel(self._parameter_name_list_model) self._ui.domain_name_edit.textChanged.connect(self._update_indexing_domain_name) self._ui.move_domain_left_button.clicked.connect(self._move_domain_left) self._ui.move_domain_right_button.clicked.connect(self._move_domain_right) self._ui.message_label.setText("") if parameter_name is not None: self._ui.parameter_name_edit.setText(parameter_name) if merging_setting is not None: domain_index = self._domain_names_model.index_for(merging_setting.previous_set) self._ui.domains_list_view.selectionModel().select(domain_index, QItemSelectionModel.Select) self._ui.domain_name_edit.setText(merging_setting.new_domain_name) self._ui.domain_description_edit.setText(merging_setting.new_domain_description) self._index_position = merging_setting.index_position self._reset_indexing_domains_label() self._check_state() @property
[docs] def error_flags(self): return self._error_flags
@property
[docs] def parameter_name(self): """Name of the merged parameter.""" return self._parameter_name
[docs] def merging_setting(self): """Constructs the MergingSetting object from the widget's contents.""" parameter_names = self._parameter_name_list_model.selected() domain_name = self._ui.domain_name_edit.text() domain_description = self._ui.domain_description_edit.text() entity_class_info = self._domain_names_model.item_at(self._selected_domain_row) previous_set = entity_class_info.name previous_domain_names = entity_class_info.domain_names setting = MergingSetting(parameter_names, domain_name, domain_description, previous_set, previous_domain_names) setting.index_position = self._index_position return setting
[docs] def update(self, entity_class_infos): """Updates the settings after database commit.""" selected_entity_class = self._domain_names_model.item_at(self._selected_domain_row) if not selected_entity_class.name in [info.name for info in entity_class_infos]: self.removal_requested.emit(self) return self._ui.domains_list_view.selectionModel().selectionChanged.disconnect() self._domain_names_model.update(entity_class_infos) domain_index = self._domain_names_model.index_for(selected_entity_class.name) self._ui.domains_list_view.selectionModel().select(domain_index, QItemSelectionModel.Select) self._selected_domain_row = domain_index.row() self._ui.domains_list_view.selectionModel().selectionChanged.connect(self._handle_domain_selection_change) entity_class_info = self._domain_names_model.item_at(self._selected_domain_row) self._parameter_name_list_model.update(entity_class_info.parameter_names) if self._index_position >= len(entity_class_info.domain_names): self._index_position = len(entity_class_info.domain_names) - 1 self._reset_indexing_domains_label(domain_names=entity_class_info.domain_names)
[docs] def _check_state(self): """Updates the message label according to widget's error state.""" if self._error_flags & MergingErrorFlag.PARAMETER_NAME_MISSING: self._ui.message_label.setText(_ERROR_MESSAGE.format("Parameter name missing.")) elif self._error_flags & MergingErrorFlag.DOMAIN_NAME_MISSING: self._ui.message_label.setText(_ERROR_MESSAGE.format("Domain name missing.")) elif self._error_flags & MergingErrorFlag.NO_PARAMETER_SELECTED: self._ui.message_label.setText(_ERROR_MESSAGE.format("No domain selected.")) else: self._ui.message_label.setText("")
[docs] def _clear_flag(self, state): """Clears a state flag.""" if not self._error_flags: return self._error_flags &= ~state self._check_state()
[docs] def _set_flag(self, state): """Sets a state flag.""" self._error_flags |= state self._check_state()
[docs] def _reset_indexing_domains_label(self, domain_name=None, domain_names=None): """Rewrites the contents of indexing_domains_label.""" if domain_name is None: domain_name = self._ui.domain_name_edit.text() bold_name = "<b>{}</b>".format(domain_name if domain_name else "unnamed") if domain_names is None: if self._selected_domain_row is not None: domain_names = self._domain_names_model.item_at(self._selected_domain_row).domain_names else: domain_names = list() label = ( "(" + ", ".join(domain_names[: self._index_position] + [bold_name] + domain_names[self._index_position :]) + ")" ) self._ui.indexing_domains_label.setText(label)
@Slot(str)
[docs] def _update_parameter_name(self, name): """Updates the merged parameter name.""" self._parameter_name = name if not name: self._set_flag(MergingErrorFlag.PARAMETER_NAME_MISSING) else: self._clear_flag(MergingErrorFlag.PARAMETER_NAME_MISSING)
@Slot(bool)
[docs] def _remove_self(self, _): """Requests removal from the parent window.""" self.removal_requested.emit(self)
@Slot("QItemSelection", "QItemSelection")
[docs] def _handle_domain_selection_change(self, selected, _): """Resets the settings after another item has been selected in domains_list_view.""" self._selected_domain_row = selected.indexes()[0].row() entity_class_info = self._domain_names_model.item_at(self._selected_domain_row) domain_names = entity_class_info.domain_names self._index_position = len(domain_names) self._parameter_name_list_model.reset(entity_class_info.parameter_names) self._reset_indexing_domains_label(domain_names=domain_names) self._clear_flag(MergingErrorFlag.NO_PARAMETER_SELECTED)
@Slot(str)
[docs] def _update_indexing_domain_name(self, name): """Resets indexing_domains_label.""" self._reset_indexing_domains_label(domain_name=name) if not name: self._set_flag(MergingErrorFlag.DOMAIN_NAME_MISSING) else: self._clear_flag(MergingErrorFlag.DOMAIN_NAME_MISSING)
@Slot(bool)
[docs] def _move_domain_left(self, _): """Moves the new indexing domain left in indexing_domains_label.""" if self._index_position > 0: self._index_position -= 1 self._reset_indexing_domains_label()
@Slot(bool)
[docs] def _move_domain_right(self, _): """Moves the new indexing domain left in indexing_domains_label.""" if self._selected_domain_row is None: return domain_names = self._domain_names_model.item_at(self._selected_domain_row).domain_names if domain_names and self._index_position < len(domain_names): self._index_position += 1 self._reset_indexing_domains_label()
[docs]class _DomainNameListModel(QAbstractListModel): """ Model for domains_list_view. Stores EntityClassInfo objects displaying the entity name in domains_list_view. """ def __init__(self, entity_classes): """ Args: entity_classes (list): a list of EntityClassObjects """ super().__init__() self._entity_classes = entity_classes
[docs] def data(self, index, role=Qt.DisplayRole): """Returns model's data for given index.""" if role != Qt.DisplayRole or not index.isValid(): return None return self._entity_classes[index.row()].name
[docs] def headerData(self, section, orientation): """Returns None.""" return None
[docs] def index_for(self, set_name): """Returns the QModelIndex for given set name.""" try: row = [entity_class.name for entity_class in self._entity_classes].index(set_name) except ValueError: return QModelIndex() else: return self.index(row, 0)
[docs] def item_at(self, row): """Returns the EntityClassInfo object at given row.""" return self._entity_classes[row]
[docs] def rowCount(self, parent=QModelIndex()): """Returns the size of the model.""" return len(self._entity_classes)
[docs] def update(self, entity_classes): """Updates the model.""" self.beginResetModel() self._entity_classes = entity_classes self.endResetModel()
[docs]class _ParameterNameListModel(QAbstractListModel): """Model for parameter_name_list_view.""" def __init__(self, names): """ Args: names (list): list of parameter names to show in the view """ super().__init__() self._names = names self._selected = len(names) * [True]
[docs] def data(self, index, role=Qt.DisplayRole): """Returns the model's data.""" if not index.isValid(): return None if role == Qt.DisplayRole: return self._names[index.row()] if role == Qt.CheckStateRole: return Qt.Checked if self._selected[index.row()] else Qt.Unchecked return None
[docs] def flags(self, index): """Returns flags for given index.""" if not index.isValid(): return Qt.NoItemFlags return Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled
[docs] def headerData(self, section, orientation): """Returns None.""" return None
[docs] def reset(self, names): """Resets the model's contents when a new index is selected in domains_list_view.""" self.beginResetModel() self._names = names self._selected = len(names) * [True] self.endResetModel()
[docs] def rowCount(self, parent=QModelIndex()): """Returns the number of parameter names.""" return len(self._names)
[docs] def select(self, names): """Selects parameters for inclusion in the merged parameter.""" for i, existing_name in enumerate(self._names): self._selected[i] = existing_name in names
[docs] def selected(self): """Returns a list of the selected parameters.""" return [name for name, select in zip(self._names, self._selected) if select]
[docs] def setData(self, index, value, role=Qt.EditRole): """Selects or deselects the parameter at given index for inclusion in the merged parameter.""" if role != Qt.CheckStateRole or not index.isValid(): return False self._selected[index.row()] = value == Qt.Checked return True
[docs] def update(self, names): """Updates the parameter names keeping the previous selection where it makes sense.""" self.beginResetModel() updated_selection = len(names) * [True] for index, name in enumerate(names): try: old_index = self._names.index(name) except ValueError: continue else: updated_selection[index] = self._selected[old_index] self._names = names self._selected = updated_selection self.endResetModel()