Source code for spinetoolbox.widgets.indexed_value_table_context_menu

######################################################################################################################
# Copyright (C) 2017-2021 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/>.
######################################################################################################################

"""
Context menus for parameter value editor widgets.

:author: A. Soininen (VTT)
:date:   5.7.2019
"""
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QInputDialog, QMenu

[docs]_INSERT_SINGLE_COLUMN_AFTER = "Insert column after"
[docs]_INSERT_SINGLE_ROW_AFTER = "Insert row after"
[docs]_INSERT_MULTIPLE_COLUMNS_AFTER = "Insert columns after..."
[docs]_INSERT_MULTIPLE_ROWS_AFTER = "Insert rows after..."
[docs]_INSERT_SINGLE_COLUMN_BEFORE = "Insert column before"
[docs]_INSERT_SINGLE_ROW_BEFORE = "Insert row before"
[docs]_INSERT_MULTIPLE_COLUMNS_BEFORE = "Insert columns before..."
[docs]_INSERT_MULTIPLE_ROWS_BEFORE = "Insert rows before..."
[docs]_OPEN_EDITOR = "Open value editor..."
[docs]_REMOVE_COLUMNS = "Remove columns"
[docs]_REMOVE_ROWS = "Remove rows"
[docs]_TRIM_COLUMNS = "Trim columns"
[docs]class ContextMenuBase(QMenu): """Context menu base for parameter value editor tables.""" def __init__(self, table_view, position): """ Args: table_view (QTableView): the view where the menu is invoked position (QPoint): menu's position on the table view """ super().__init__(table_view) self._table_view = table_view self._index = self._table_view.indexAt(position) self._in_expanse_row = self._table_view.model().is_expanse_row(self._index.row())
[docs] def _add_default_actions(self): """Adds default actions to the menu.""" self.addAction(_INSERT_SINGLE_ROW_BEFORE, self._insert_single_row_before) self.addAction(_INSERT_MULTIPLE_ROWS_BEFORE, self._insert_multiple_rows_before) self.addSeparator() self.addAction(_INSERT_SINGLE_ROW_AFTER, self._insert_single_row_after).setEnabled(not self._in_expanse_row) self.addAction(_INSERT_MULTIPLE_ROWS_AFTER, self._insert_multiple_rows_after).setEnabled( not self._in_expanse_row ) self.addSeparator() self.addAction(_REMOVE_ROWS, self._remove_rows).setEnabled(not self._in_expanse_row)
[docs] def _first_row(self): """ Returns the first selected row. Returns: int: index to the first row """ return min(s.top() for s in self._table_view.selectionModel().selection())
@Slot()
[docs] def _insert_multiple_rows_after(self): """Prompts for row count, then inserts new rows below the current selection.""" row_count = self._prompt_row_count() if row_count > 0: self._table_view.model().insertRows(self._last_row() + 1, row_count)
@Slot()
[docs] def _insert_multiple_rows_before(self): """Prompts for row count, then inserts new rows above the current selection.""" row_count = self._prompt_row_count() if row_count > 0: self._table_view.model().insertRows(self._first_row(), row_count)
@Slot()
[docs] def _insert_single_row_after(self): """Inserts a single row below the current selection.""" self._table_view.model().insertRow(self._last_row() + 1)
@Slot()
[docs] def _insert_single_row_before(self): """Inserts a single row above the current selection.""" self._table_view.model().insertRow(self._first_row())
[docs] def _last_row(self): """ Returns the last selected row. Returns: int: index to the last row """ return max(s.bottom() for s in self._table_view.selectionModel().selection())
[docs] def _prompt_row_count(self): """ Prompts for number of rows to insert. Returns: int: number of rows """ row_count, accepted = QInputDialog.getInt( self._table_view, "Enter number of rows", "Number of rows to insert", minValue=1 ) return row_count if accepted else 0
@Slot()
[docs] def _remove_rows(self): """Removes selected rows.""" ranges = _unique_row_ranges(self._table_view.selectionModel().selection()) for range_ in ranges: self._table_view.model().removeRows(range_[0], range_[1] - range_[0] + 1)
[docs]class ArrayTableContextMenu(ContextMenuBase): """Context menu for array editor tables.""" def __init__(self, editor, table_view, position): """ Args: editor (ArrayEditor): array editor widget table_view (QTableView): the view where the menu is invoked position (QPoint): menu's position """ super().__init__(table_view, position) self._array_editor = editor self.addAction(_OPEN_EDITOR, self._show_value_editor) self.addSeparator() self._add_default_actions() @Slot()
[docs] def _show_value_editor(self): """Opens the value element editor.""" self._array_editor.open_value_editor(self._index)
[docs]class IndexedValueTableContextMenu(ContextMenuBase): """Context menu for time series and time pattern editor tables.""" def __init__(self, table_view, position): """ Args: table_view (QTableView): the view where the menu is invoked position (QPoint): menu's position """ super().__init__(table_view, position) self._add_default_actions()
[docs]class MapTableContextMenu(ContextMenuBase): """Context menu for map editor tables.""" def __init__(self, editor, table_view, position): """ Args: editor (MapEditor): map editor widget table_view (QTableView): the view where the menu is invoked position (QPoint): table cell index """ super().__init__(table_view, position) self._map_editor = editor in_expanse_column = table_view.model().is_expanse_column(self._index.column()) self.addAction(_OPEN_EDITOR, self._show_value_editor) self.addSeparator() self._add_default_actions() self.addSeparator() self.addAction(_INSERT_SINGLE_COLUMN_BEFORE, self._insert_single_column_before) self.addAction(_INSERT_MULTIPLE_COLUMNS_BEFORE, self._insert_multiple_columns_before) self.addSeparator() self.addAction(_INSERT_SINGLE_COLUMN_AFTER, self._insert_single_column_after).setEnabled(not in_expanse_column) self.addAction(_INSERT_MULTIPLE_COLUMNS_AFTER, self._insert_multiple_columns_after).setEnabled( not in_expanse_column ) self.addSeparator() self.addAction(_REMOVE_COLUMNS, self._remove_columns).setEnabled(not in_expanse_column) self.addAction(_TRIM_COLUMNS, self._trim_columns)
[docs] def _first_column(self): """ Returns the first selected column. Returns: int: index to the first column """ return min(s.left() for s in self._table_view.selectionModel().selection())
@Slot()
[docs] def _insert_multiple_columns_after(self): """Prompts for column count, then inserts new columns right from the current selection.""" column_count = self._prompt_column_count() if column_count > 0: self._table_view.model().insertColumns(self._last_column() + 1, column_count)
@Slot()
[docs] def _insert_multiple_columns_before(self): """Prompts for column count, then inserts new columns left from the current selection.""" column_count = self._prompt_column_count() if column_count > 0: self._table_view.model().insertColumns(self._first_column(), column_count)
@Slot()
[docs] def _insert_single_column_before(self): """Inserts a single column left from the current selection.""" self._table_view.model().insertColumn(self._first_column())
@Slot()
[docs] def _insert_single_column_after(self): """Inserts a single column right from the current selection.""" self._table_view.model().insertColumn(self._last_column() + 1)
[docs] def _last_column(self): """ Returns the last selected column. Returns: int: index to the last column """ return max(s.right() for s in self._table_view.selectionModel().selection())
[docs] def _prompt_column_count(self): """ Prompts for number of column to insert. Returns: int: number of columns """ column_count, accepted = QInputDialog.getInt( self._table_view, "Enter number of columns", "Number of columns to insert", minValue=1 ) return column_count if accepted else 0
@Slot()
[docs] def _remove_columns(self): """Removes selected columns""" ranges = _unique_column_ranges(self._table_view.selectionModel().selection()) for range_ in ranges: self._table_view.model().removeColumns(range_[0], range_[1] - range_[0] + 1)
@Slot()
[docs] def _show_value_editor(self): """Opens the value element editor.""" self._map_editor.open_value_editor(self._index)
@Slot()
[docs] def _trim_columns(self): """Removes excessive columns from the table.""" self._table_view.model().trim_columns()
[docs]def _unique_row_ranges(selections): """ Merged ranges in given selections to unique ranges. Args: selections (list of QItemSelectionRange): selected ranges Returns: list of list: a list of [first_row, last_row] ranges """ return _merge_intervals([[s.top(), s.bottom()] for s in selections])
[docs]def _unique_column_ranges(selections): """ Merged ranges in given selections to unique ranges. Args: selections (list of QItemSelectionRange): selected ranges Returns: list of list: a list of [first_row, last_row] ranges """ return _merge_intervals([[s.left(), s.right()] for s in selections])
[docs]def _merge_intervals(intervals): """ Merges given intervals if they overlap. Args: intervals (list of list): a list of intervals in the form [first, last] Returns: list of list: merged intervals in the form [first, last] """ intervals.sort(key=lambda i: i[0]) merged_intervals = [intervals.pop(0)] while intervals: interval = intervals.pop(0) if interval[0] <= merged_intervals[-1][1]: merged_intervals[-1][1] = max(merged_intervals[-1][1], interval[1]) else: merged_intervals.append(interval) return merged_intervals