Source code for spinetoolbox.project_items.exporter.mvcmodels.indexing_table_model

######################################################################################################################
# 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/>.
######################################################################################################################

"""
Contains :class:`IndexingTableModel`.

:author: A. Soininen (VTT)
:date:   25.8.2020
"""
from PySide2.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal
from spinetoolbox.spine_io.exporters import gdx


[docs]class IndexingTableModel(QAbstractTableModel): """ A table model for parameter_value indexing. First column contains the proposed new index keys. The rest of the columns contain the parameter values for each set of existing index keys. Only selected new index keys are used for indexing. Unselected rows are left empty. """
[docs] selection_changed = Signal()
"""Emitted after the values have been spread over the selected indexes."""
[docs] manual_selection = Signal()
"""Emitted when the selection has been changed by setData().""" def __init__(self, parameter): """ Args: parameter (Parameter): a parameter to model """ super().__init__() self._records = gdx.LiteralRecords([]) self._index_name = "" self._parameter_values = list(parameter.values) self._parameter_nonexpanded_indexes = list(parameter.indexes) self._selected = list() self._rows_shown = 0 self._values = [list() for _ in range(len(self._parameter_values))] self.records_operational = True
[docs] def get_picking(self): """ Turns the checked record into picking. Returns: FixedPicking: picked records """ return gdx.FixedPicking(list(self._selected))
[docs] def canFetchMore(self, parent): """Returns True if more rows are available to show.""" return self._rows_shown < len(self._records)
[docs] def clear(self): """Clears the model.""" self.beginResetModel() self._records = gdx.LiteralRecords([]) self._selected = list() self._values = [list() for _ in range(len(self._parameter_values))] self._rows_shown = 0 self.endResetModel() self.selection_changed.emit()
[docs] def columnCount(self, parent=QModelIndex()): """Returns the number of columns.""" return len(self._parameter_values) + 1
[docs] def data(self, index, role=Qt.DisplayRole): """Returns data associated with given model index and role.""" if role in (Qt.DisplayRole, Qt.ToolTipRole): row = index.row() column = index.column() if column == 0: try: value = self._records.records[row][0] except gdx.GdxExportException: self.records_operational = False return None else: self.records_operational = True return value return self._values[column - 1][row] if index.column() == 0 and role == Qt.CheckStateRole: return Qt.Checked if self._selected[index.row()] else Qt.Unchecked return None
[docs] def headerData(self, section, orientation, role=Qt.DisplayRole): """Returns header data.""" if role != Qt.DisplayRole: return None if orientation == Qt.Vertical: return section + 1 if section == 0: return self._index_name return ", ".join(self._parameter_nonexpanded_indexes[section - 1])
[docs] def fetchMore(self, parent): """Inserts a number of new rows to the table.""" remainder = len(self._records) - self._rows_shown fetch_size = min(remainder, 100) end = self._rows_shown + fetch_size self.beginInsertRows(parent, self._rows_shown, end - 1) for column in self._values: column += fetch_size * [None] start = self._rows_shown self._rows_shown = end self._spread_values_over_selected_rows(start) self.endInsertRows()
[docs] def flags(self, index): """Returns flags for given index.""" flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled if index.column() == 0: flags = flags | Qt.ItemIsUserCheckable return flags
[docs] def mapped_values_balance(self): """ Returns the balance between available indexes and parameter values. Zero means that there is as many indexes available as there are values, i.e. the parameter is 'perfectly' indexed. A positive value means there are more indexes than values while a negative value means there are not enough indexes for all values. Returns: int: mapped values' balance """ count = sum(1 for selected in self._selected if selected) return count - len(self._parameter_values[0].values) if self._parameter_values else 0
[docs] def rowCount(self, parent=QModelIndex()): """Return the number of rows.""" return self._rows_shown
[docs] def select_all(self): """Selects all indexes.""" self._selected = len(self._records) * [True] top_left = self.index(0, 0) bottom_right = self.index(self._rows_shown - 1, 0) self.dataChanged.emit(top_left, bottom_right, [Qt.CheckStateRole]) self._spread_values_over_selected_rows(0) self.selection_changed.emit()
[docs] def setData(self, index, value, role=Qt.EditRole): """Sets the checked state for given index.""" if role != Qt.CheckStateRole: return False row = index.row() self._selected[row] = value == Qt.Checked self.dataChanged.emit(index, index, [Qt.CheckStateRole]) self._spread_values_over_selected_rows(row) self.selection_changed.emit() self.manual_selection.emit() return True
[docs] def set_index_name(self, name): """Sets the indexing domain name.""" self._index_name = name self.headerDataChanged.emit(Qt.Horizontal, 0, 0)
[docs] def set_records(self, records, pick_list=None): """Overwrites all new indexes.""" self.beginResetModel() self._records = records self._selected = pick_list if pick_list is not None else len(records) * [True] self._values = [list() for _ in range(len(self._parameter_values))] self._rows_shown = 0 self.selection_changed.emit() self.endResetModel()
[docs] def set_picking(self, picking): """ Selects the indexes specified by picking. Args: picking (Picking): picking """ if picking is None: picking = gdx.FixedPicking(len(self._records) * [True]) pick = picking.pick try: self._selected = [pick(i) for i in range(len(self._records))] except gdx.GdxExportException: return top_left = self.index(0, 0) bottom_right = self.index(self._rows_shown - 1, 0) self.dataChanged.emit(top_left, bottom_right, [Qt.CheckStateRole]) self._spread_values_over_selected_rows(0) self.selection_changed.emit()
[docs] def _spread_values_over_selected_rows(self, first_row): """Repopulates the table according to selected indexes.""" value_start = sum(1 for is_selected in self._selected[:first_row] if is_selected) for i, parameter_value in enumerate(self._parameter_values): value_index = value_start value_length = len(parameter_value) values = list(parameter_value.values) column = self._values[i] for j in range(first_row, self._rows_shown): is_selected = self._selected[j] if is_selected and value_index < value_length: column[j] = str(values[value_index]) value_index += 1 else: column[j] = None top_left = self.index(first_row, 1) bottom_right = self.index(self._rows_shown - 1, len(self._values)) self.dataChanged.emit(top_left, bottom_right, [Qt.DisplayRole, Qt.ToolTipRole])