Pamhyr2/src/View/Frictions/Table.py

312 lines
9.4 KiB
Python

# Table.py -- Pamhyr
# Copyright (C) 2023-2026 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 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.Frictions.UndoCommand import (
SetNameCommand, SetBeginCommand, SetEndCommand,
SetBeginStricklerCommand, SetEndStricklerCommand,
AddCommand, DelCommand, SortCommand,
MoveCommand, PasteCommand, DuplicateCommand, ReplaceDataCommand,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.Tools.CoefficientDisplay import display_strickler
from View.Frictions.translate import *
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, study=None,
mode="stricklers", trad=None, display_manning=True,
parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._study = study
self._trad = trad
self._mode = mode
self._display_manning = display_manning
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
if self._mode == "stricklers":
self.editor.addItem(self._trad["not_defined"], None)
for strickler in self._study.river.stricklers.stricklers:
self.editor.addItem(
display_strickler(strickler, self._display_manning),
strickler
)
elif self._mode == "rk":
self.editor.addItems(
list(map(str, self._data.reach.get_rk()))
)
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):
value = editor.currentData() if self._mode == "stricklers" else \
str(editor.currentText())
model.setData(index, value)
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 FrictionTableModel(PamhyrTableModel):
def __init__(self, *args, display_manning=False, **kwargs):
self.display_manning = display_manning
super(FrictionTableModel, self).__init__(*args, **kwargs)
def _setup_lst(self):
self._lst = self._data.frictions
self._study = self._opt_data
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] == "name":
return self._lst.get(row).name
elif self._headers[column] == "begin_rk":
return self._lst.get(row).begin_rk
elif self._headers[column] == "end_rk":
return self._lst.get(row).end_rk
elif self._headers[column] == "begin_strickler":
value = self._lst.get(row).begin_strickler
if value is None:
return self._trad["not_defined"]
return display_strickler(value, self.display_manning)
elif self._headers[column] == "end_strickler":
value = self._lst.get(row).end_strickler
if value is None:
return self._trad["not_defined"]
return display_strickler(value, self.display_manning)
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
if self.is_same_data(index, value):
return False
row = index.row()
column = index.column()
try:
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._lst, row, value
)
)
elif self._headers[column] == "begin_rk":
self._undo.push(
SetBeginCommand(
self._lst, row, value
)
)
elif self._headers[column] == "end_rk":
self._undo.push(
SetEndCommand(
self._lst, row, value
)
)
elif self._headers[column] == "begin_strickler":
strickler = value if hasattr(value, "minor") else \
self._study.river.strickler(value)
self._undo.push(
SetBeginStricklerCommand(
self._lst, row, strickler
)
)
elif self._headers[column] == "end_strickler":
strickler = value if hasattr(value, "minor") else \
self._study.river.strickler(value)
self._undo.push(
SetEndStricklerCommand(
self._lst, row, strickler
)
)
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._data
)
)
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 replace_data(self, new_data):
self.layoutAboutToBeChanged.emit()
self._undo.push(
ReplaceDataCommand(
self._lst, new_data
)
)
self.layoutAboutToBeChanged.emit()
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_stack.push(
MoveCommand(
self._lst, "up", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()):
if row > len(self._lst):
return
target = row
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
self._undo_stack.push(
MoveCommand(
self._lst, "down", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
def read_from_file(self, file_name):
reach_id = self._study.river.enable_edges().index(self._data) + 1
logger.debug(f"Import frictions from {file_name}")
data = []
with open(file_name, encoding="utf-8") as rug_file:
for line in rug_file:
if line.upper().startswith("K"):
if int(line[1:4]) == reach_id:
data.append(line[4:].split())
new_data = []
for d in data:
new = None
minor = float(d[2])
medium = float(d[3])
for s in self._study.river.stricklers.stricklers:
if s.minor == minor and s.medium == medium:
new = s
break
if new is None:
new = self._study.river.stricklers.new(len(
self._study.river.stricklers))
new.minor = minor
new.medium = medium
new_data.append([self._data, float(d[0]), float(d[1]), new, new])
self.replace_data(new_data)