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]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