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

######################################################################################################################
# Copyright (C) 2017-2019 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 indexing settings window for .gdx export.

:author: A. Soininen (VTT)
:date:   26.11.2019
"""
from contextlib import contextmanager
import enum
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QWidget
from spinetoolbox.spine_io.exporters import gdx
from ..mvcmodels.indexing_table_model import IndexingTableModel


[docs]class IndexSettingsState(enum.Enum): """An enumeration indicating the state of the settings window."""
[docs] OK = enum.auto()
[docs] DOMAIN_MISSING_INDEXES = enum.auto()
[docs]class ParameterIndexSettings(QWidget): """A widget showing setting for a parameter with indexed values.""" def __init__(self, parameter_name, indexing_setting, available_domains, parent): """ Args: parameter_name (str): parameter's name indexing_setting (IndexingSetting): indexing settings for the parameter available_domains (dict): a dict from existing domain name to :class:`Records` parent (QWidget, optional): a parent widget """ from ..ui.parameter_index_settings import Ui_Form # pylint: disable=import-outside-toplevel super().__init__(parent) self._indexing_setting = indexing_setting self._state = IndexSettingsState.OK self._monitor_domains_combo_box = True self._using_pick_expression = False self._ui = Ui_Form() self._ui.setupUi(self) self._ui.box.setTitle(parameter_name) self._indexing_table_model = IndexingTableModel(indexing_setting.parameter) self._ui.index_table_view.setModel(self._indexing_table_model) self._available_domains = available_domains self._ui.domains_combo.addItems(sorted(available_domains.keys())) self._ui.domains_combo.currentTextChanged.connect(self._change_domain) self._ui.pick_expression_edit.textChanged.connect(self._update_index_list_selection) self._ui.move_domain_left_button.clicked.connect(self._move_indexing_domain_left) self._ui.move_domain_right_button.clicked.connect(self._move_indexing_domain_right) indexing_domain_name = indexing_setting.indexing_domain_name if indexing_domain_name is None and available_domains: indexing_domain_name = next(iter(available_domains)) if indexing_domain_name is not None: if self._ui.domains_combo.currentText() != indexing_domain_name: self._ui.domains_combo.setCurrentText(indexing_domain_name) else: self._change_domain(indexing_domain_name) picking = indexing_setting.picking if isinstance(picking, gdx.GeneratedPicking): self._ui.pick_expression_edit.setText(picking.expression) else: self._indexing_table_model.set_picking(picking) self._check_state() self._indexing_table_model.selection_changed.connect(self._check_state) self._indexing_table_model.manual_selection.connect(self._clear_pick_expression_silently) @property
[docs] def state(self): """widget's state""" return self._state
@state.setter def state(self, new_state): """Sets the state of the widget and possibly shows an error indicator.""" self._state = new_state if self._state == IndexSettingsState.DOMAIN_MISSING_INDEXES: self.error_message("Not enough selected indexes to index all values.") elif self._state == IndexSettingsState.OK: self.notification_message("Parameter successfully indexed.")
[docs] def indexing_domain_name(self): """ Returns the selected indexing domain's name Returns: str: domain name """ return self._ui.domains_combo.currentText()
[docs] def picking(self): """ Returns picking. Returns: Picking: picking """ if self._using_pick_expression: return gdx.GeneratedPicking(self._ui.pick_expression_edit.text()) return self._indexing_table_model.get_picking()
[docs] def set_domains(self, domains): """ Sets new domains and record keys. Args: domains (dict): mapping from domain name to records """ self._available_domains = domains current = self._ui.domains_combo.currentText() with _freely_update_domains_combo(self): self._ui.domains_combo.clear() self._ui.domains_combo.addItems(sorted(domains.keys())) if current in domains: self._ui.domains_combo.setCurrentText(current) else: self._ui.domains_combo.setCurrentIndex(-1)
[docs] def set_domains_combo_monitoring_enabled(self, enabled): """ Enables or disables monitoring of current text in domains combo box. Args: enabled (bool): True enables monitoring, False disables """ self._monitor_domains_combo_box = enabled
[docs] def update_domain_name(self, old_name, new_name): """ Renames a domain. Args: old_name (str): previous name new_name (str): new name """ index = self._ui.domains_combo.findText(old_name) self._ui.domains_combo.setItemText(index, new_name)
[docs] def update_records(self, domain_name): """ Updates existing domain's records. Args: domain_name (str): domain's name """ if domain_name == self._ui.domains_combo.currentText(): self._indexing_table_model.set_records(self._available_domains[domain_name])
[docs] def notification_message(self, message): """Shows a notification message on the widget.""" self._ui.message_label.setText(message)
[docs] def warning_message(self, message): """Shows a warning message on the widget.""" yellow_message = "<span style='color:#b89e00;white-space: pre-wrap;'>" + message + "</span>" self._ui.message_label.setText(yellow_message)
[docs] def error_message(self, message): """Shows an error message on the widget.""" red_message = "<span style='color:#ff3333;white-space: pre-wrap;'>" + message + "</span>" self._ui.message_label.setText(red_message)
@Slot()
[docs] def _check_state(self): """Updated the widget's state.""" mapped_values_balance = self._indexing_table_model.mapped_values_balance() if self._check_errors(mapped_values_balance): return if self._check_warnings(mapped_values_balance): return self.state = IndexSettingsState.OK
[docs] def _check_errors(self, mapped_values_balance): """Checks if the parameter is correctly indexed.""" if mapped_values_balance < 0: self.state = IndexSettingsState.DOMAIN_MISSING_INDEXES return True return False
[docs] def _check_warnings(self, mapped_values_balance): """Checks if there are non-fatal issues with parameter indexing.""" if mapped_values_balance > 0: self._state = IndexSettingsState.OK self.warning_message("Too many indexes selected. The excess indexes will not be used.") return True return False
[docs] def _update_indexing_domains_name(self): """Updates the model's header and the label showing the indexing domains.""" parameter = self._indexing_setting.parameter index_position = self._indexing_setting.index_position domain_name = self._ui.domains_combo.currentText() self._indexing_table_model.set_index_name(domain_name) name = "<b>{}</b>".format(domain_name if domain_name else "unnamed") label = ( "(" + ", ".join(parameter.domain_names[:index_position] + [name] + parameter.domain_names[index_position:]) + ")" ) self._ui.indexing_domains_label.setText(label)
@Slot()
[docs] def _clear_pick_expression_silently(self): """Clears the pick expression line edit.""" self._ui.pick_expression_edit.textChanged.disconnect(self._update_index_list_selection) self._ui.pick_expression_edit.clear() self._using_pick_expression = False self._ui.pick_expression_edit.textChanged.connect(self._update_index_list_selection)
@Slot(str)
[docs] def _change_domain(self, domain_name): """Change the domain used on the table.""" if not self._monitor_domains_combo_box: return if domain_name: self._indexing_table_model.set_records(self._available_domains[domain_name]) else: self._indexing_table_model.set_records(gdx.LiteralRecords([])) self._update_indexing_domains_name()
@Slot(str)
[docs] def _update_index_list_selection(self, expression): """Updates selection according to changed selection expression.""" if not expression: self._indexing_table_model.select_all() self._using_pick_expression = False return self._indexing_table_model.set_picking(gdx.GeneratedPicking(expression)) self._using_pick_expression = True
@Slot(bool)
[docs] def _move_indexing_domain_left(self, _): """Moves the indexing domain name left on the indexing label.""" if self._indexing_setting.index_position > 0: self._indexing_setting.index_position -= 1 self._update_indexing_domains_name()
@Slot(bool)
[docs] def _move_indexing_domain_right(self, _): """Moves the indexing domain name right on the indexing label.""" if self._indexing_setting.index_position < len(self._indexing_setting.parameter.domain_names): self._indexing_setting.index_position += 1 self._update_indexing_domains_name()
@contextmanager
[docs]def _freely_update_domains_combo(widget): """ A context manager which temporarily disables the monitoring of current text changes in domains combo box. Args: widget (ParameterIndexSettings): settings widget """ widget.set_domains_combo_monitoring_enabled(False) try: yield None finally: widget.set_domains_combo_monitoring_enabled(True)