Pamhyr2/src/View/InitialConditions/Table.py

311 lines
8.2 KiB
Python

# Table.py -- Pamhyr
# Copyright (C) 2023-2024 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
import traceback
from tools import trace, timer
from functools import reduce
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.InitialConditions.UndoCommand import (
SetCommand, AddCommand, DelCommand,
SortCommand, MoveCommand, InsertCommand,
DuplicateCommand, GenerateCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, reach=None, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self._reach = reach.reach
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
self.editor.addItems(
list(
map(
lambda p: p.display_name(),
self._reach.profiles
)
)
)
self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
return self.editor
def setEditorData(self, editor, index):
value = index.data(Qt.DisplayRole)
self.editor.currentTextChanged.connect(self.currentItemChanged)
def setModelData(self, editor, model, index):
text = str(editor.currentText())
val = reduce(
lambda acc, p: p if text == p.display_name() else acc,
self._reach.profiles,
None
)
model.setData(index, val)
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup:
if editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class InitialConditionTableModel(PamhyrTableModel):
def __init__(self, reach=None, **kwargs):
self._reach = reach
super(InitialConditionTableModel, self).__init__(**kwargs)
def _setup_lst(self):
self._lst = self._data.river.initial_conditions.get(self._reach)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] == "rk":
val = self._lst.get(row)
section = val[self._headers[column]]
if section is None:
return ""
return section.display_name()
elif self._headers[column] == "speed":
z = self._lst.get(row)["elevation"]
q = self._lst.get(row)["discharge"]
profile = self._lst.get(row)["rk"]
if profile is not None:
speed = profile.speed(q, z)
return f"{speed:.4f}"
return ""
elif self._headers[column] not in ["name", "comment"]:
v = self._lst.get(row)[self._headers[column]]
return f"{v:.4f}"
else:
return self._lst.get(row)[self._headers[column]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
row = index.row()
column = index.column()
try:
if self._headers[column] is not None:
self._undo.push(
SetCommand(
self._lst, row, self._headers[column], value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def add(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row - 1)
self._undo.push(
AddCommand(
self._lst, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._lst, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def sort(self, _reverse, parent=QModelIndex()):
self.layoutAboutToBeChanged.emit()
self._undo.push(
SortCommand(
self._lst, False
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def move_up(self, row, parent=QModelIndex()):
if row <= 0:
return
target = row + 2
self.beginMoveRows(parent, row - 1, row - 1, parent, target)
self._undo.push(
MoveCommand(
self._lst, "up", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def move_down(self, row, parent=QModelIndex()):
if row > len(self._lst):
return
target = row
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
self._undo.push(
MoveCommand(
self._lst, "down", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def paste(self, index, header, data):
if len(header) != 0:
logger.error("Unexpected header in IC past data")
return
if len(data) == 0:
logger.error("Empty data")
return
if len(data[0]) != 3:
logger.error(f"Unexpected data size: [{data[0]}, ...]")
return
self.layoutAboutToBeChanged.emit()
self._undo.push(
InsertCommand(
self._lst, index,
list(
map(
lambda d: self._lst.new_from_data(*d),
data
)
)
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def import_from_results(self, index, results):
if results is None:
logger.error("No results data")
return
self.layoutAboutToBeChanged.emit()
ts = max(results.get("timestamps"))
res_reach = results.river.get_reach_by_geometry(
self._reach.reach
)
data = list(
map(
lambda p: [
p.geometry.rk,
p.get_ts_key(ts, "Q"),
p.get_ts_key(ts, "Z"),
],
res_reach.profiles
)
)
self._undo.push(
InsertCommand(
self._lst, index,
list(
map(
lambda d: self._lst.new_from_data(*d),
data
)
)
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
def generate(self, generator, param, option):
self._undo.push(
GenerateCommand(
self._lst, generator, param, option
)
)
self.layoutChanged.emit()