add Reservoirs

This commit is contained in:
Theophile Terraz committed 2023-11-29 14:34:06 +01:00
1 parent 12e3d779c6
commit 92eb729a39
18 files changed
+1791 -11

No files matched your search

+13 -1
View File
@@ -47,6 +47,7 @@ from View.About.Window import AboutWindow
from View.Network.Window import NetworkWindow
from View.Geometry.Window import GeometryWindow
from View.BoundaryCondition.Window import BoundaryConditionWindow
from View.Reservoir.Window import ReservoirWindow
from View.LateralContribution.Window import LateralContributionWindow
from View.InitialConditions.Window import InitialConditionsWindow
from View.Stricklers.Window import StricklersWindow
@@ -101,7 +102,7 @@ define_model_action = [
"action_menu_boundary_conditions", "action_menu_initial_conditions",
"action_menu_edit_friction", "action_menu_edit_lateral_contribution",
"action_menu_run_solver", "action_menu_sediment_layers",
"action_menu_edit_reach_sediment_layers"
"action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs"
]
action = (
@@ -191,6 +192,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_edit_network": self.open_network,
"action_menu_edit_geometry": self.open_geometry,
"action_menu_boundary_conditions": self.open_boundary_cond,
"action_menu_edit_reservoirs": self.open_reservoir,
"action_menu_initial_conditions": self.open_initial_conditions,
"action_menu_edit_friction": self.open_frictions,
"action_menu_edit_lateral_contribution": self.open_lateral_contrib,
@@ -616,6 +618,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
bound = BoundaryConditionWindow(study=self._study, parent=self)
bound.show()
def open_reservoir(self):
if self.sub_window_exists(
ReservoirWindow,
data=[self._study, None]
):
return
reservoir = ReservoirWindow(study=self._study, parent=self)
reservoir.show()
def open_lateral_contrib(self):
if self.sub_window_exists(
LateralContributionWindow,
+101
View File
@@ -0,0 +1,101 @@
# Plot.py -- Pamhyr
# Copyright (C) 2023 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
from datetime import datetime
from tools import timer, trace
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
)
from View.BoundaryCondition.Edit.translate import BCETranslate
_translate = QCoreApplication.translate
logger = logging.getLogger()
class Plot(PamhyrPlot):
def __init__(self, mode="time", data=None,
trad=None, canvas=None, toolbar=None,
parent=None):
super(Plot, self).__init__(
canvas=canvas,
trad=trad,
data=data,
toolbar=toolbar,
parent=parent
)
self._table_headers = self._trad.get_dict("table_headers")
self._mode = mode
@timer
def draw(self):
self.canvas.axes.cla()
self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
if len(self.data) == 0:
self._init = False
return
# Plot data
x = list(map(lambda v: v[0], self.data.data))
y = list(map(lambda v: v[1], self.data.data))
self._line, = self.canvas.axes.plot(
x, y,
color='r', lw=1.,
markersize=5, marker='+',
picker=30,
)
# Plot label
#header = self.data.header
self.canvas.axes.set_xlabel(
self._table_headers["z"], color='black', fontsize=10
)
self.canvas.axes.set_ylabel(
self._table_headers["Area"], color='black', fontsize=10
)
self.canvas.axes.autoscale_view(True, True, True)
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
# self.toolbar.update()
self._init = True
@timer
def update(self, ind=None):
if not self._init:
self.draw()
return
x = list(map(lambda v: v[0], self.data.data))
y = list(map(lambda v: v[1], self.data.data))
self._line.set_data(x, y)
self.canvas.axes.relim()
self.canvas.axes.autoscale()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
+141
View File
@@ -0,0 +1,141 @@
# Table.py -- Pamhyr
# Copyright (C) 2023 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 datetime import date, time, datetime, timedelta
from tools import trace, timer
from View.Tools.PamhyrTable import PamhyrTableModel
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect, QTime, QDateTime,
)
from PyQt5.QtWidgets import (
QTableView, QAbstractItemView, QSpinBox, QItemDelegate,
)
from View.Reservoir.Edit.UndoCommand import (
SetDataCommand, AddCommand, DelCommand,
SortCommand, PasteCommand,
)
_translate = QCoreApplication.translate
logger = logging.getLogger()
class TableModel(PamhyrTableModel):
def data(self, index, role):
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
value = QVariant()
if 0 <= column < 2:
v = self._data.get_i(row)[column]
value = f"{v:.4f}"
return value
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:
self._undo.push(
SetDataCommand(
self._data, row, column, float(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._data, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._data, rows
)
)
self.endRemoveRows()
def sort(self, _reverse, parent=QModelIndex()):
self.layoutAboutToBeChanged.emit()
self._undo.push(
SortCommand(
self._data, _reverse
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def paste(self, row, data):
if len(data) == 0:
return
self.layoutAboutToBeChanged.emit()
self._undo.push(
PasteCommand(
self._data, row,
list(
map(
lambda d: self._data.new_from_data(d),
data
)
)
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
+35
View File
@@ -0,0 +1,35 @@
# translate.py -- Pamhyr
# Copyright (C) 2023 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 -*-
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
from View.Reservoir.Translate import ReservoirTranslate
_translate = QCoreApplication.translate
class EditReservoirTranslate(ReservoirTranslate):
def __init__(self):
super(EditReservoirTranslate, self).__init__()
self._sub_dict["table_headers"] = {
"z": _translate("Reservoir", "Elevation (m)"),
"Area": _translate("Reservoir", "Area (hectare)"),
}
+135
View File
@@ -0,0 +1,135 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2023 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
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
from Model.Reservoir.Reservoir import Reservoir
logger = logging.getLogger()
class SetDataCommand(QUndoCommand):
def __init__(self, data, index, column, new_value):
QUndoCommand.__init__(self)
self._data = data
self._index = index
self._column = column
self._old = self._data.get_i(self._index)[self._column]
self._new = new_value
def undo(self):
self._data._set_i_c_v(self._index, self._column, self._old)
def redo(self):
self._data._set_i_c_v(self._index, self._column, self._new)
class AddCommand(QUndoCommand):
def __init__(self, data, index):
QUndoCommand.__init__(self)
self._data = data
self._index = index
self._new = None
def undo(self):
self._data.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._data.add(self._index)
else:
self._data.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, data, rows):
QUndoCommand.__init__(self)
self._data = data
self._rows = rows
self._point = []
for row in rows:
self._point.append((row, self._data.get_i(row)))
self._point.sort()
def undo(self):
for row, el in self._point:
self._data.insert(row, el)
def redo(self):
self._data.delete_i(self._rows)
class SortCommand(QUndoCommand):
def __init__(self, data, _reverse):
QUndoCommand.__init__(self)
self._data = data
self._reverse = _reverse
self._old = self._data.data
self._indexes = None
def undo(self):
ll = self._data.data
self._data.sort(
key=lambda x: self._indexes[ll.index(x)]
)
def redo(self):
self._data.sort(
_reverse=self._reverse,
key=lambda x: x[0]
)
if self._indexes is None:
self._indexes = list(
map(
lambda p: self._old.index(p),
self._data.data
)
)
self._old = None
class PasteCommand(QUndoCommand):
def __init__(self, data, row, hs):
QUndoCommand.__init__(self)
self._data = data
self._row = row
self._h = hs
self._h.reverse()
def undo(self):
self._data.delete_i(
range(self._row, self._row + len(self._h))
)
def redo(self):
for h in self._h:
self._data.insert(self._row, h)
+217
View File
@@ -0,0 +1,217 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 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
from tools import timer, trace
from View.Tools.PamhyrWindow import PamhyrWindow
from View.Tools.PamhyrWidget import PamhyrWidget
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout,
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Reservoir.Edit.Translate import EditReservoirTranslate
from View.Reservoir.Edit.Table import TableModel
from View.Reservoir.Edit.Plot import Plot
_translate = QCoreApplication.translate
logger = logging.getLogger()
class EditReservoirWindow(PamhyrWindow):
_pamhyr_ui = "Reservoir"
_pamhyr_name = "Edit Reservoir"
def __init__(self, data=None, study=None, config=None, parent=None):
self._data = data
trad = EditReservoirTranslate()
name = self._pamhyr_name
if self._data is not None:
node_name = (self._data.node.name if self._data.node is not None
else _translate("Reservoir", "Not associated"))
name = (
_translate("Edit Reservoir", self._pamhyr_name) +
f" - {study.name} " +
f" - {self._data.name} ({self._data.id}) " +
f"({node_name})"
)
super(EditReservoirWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(data)
self.setup_table()
self.setup_plot()
self.setup_connections()
def setup_table(self):
headers = {}
table_headers = self._trad.get_dict("table_headers")
#for h in self._data.header:
#headers[h] = table_headers[h]
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view=table,
table_headers=table_headers,
editable_headers=table_headers,
#editable_headers=self._data.header,
delegates={
#"time": self._delegate_time,
},
data=self._data,
undo=self._undo_stack,
opt_data=self._study.time_system
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.verticalLayout.addWidget(self.toolbar)
self.verticalLayout.addWidget(self.canvas)
self.plot = Plot(
canvas=self.canvas,
data=self._data,
mode=self._study.time_system,
trad=self._trad,
toolbar=self.toolbar,
)
self.plot.draw()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self.find(QAction, "action_sort").triggered.connect(self.sort)
self._table.dataChanged.connect(self.update)
def update(self):
self.plot.update()
def index_selected_row(self):
table = self.find(QTableView, "tableView")
return table.selectionModel()\
.selectedRows()[0]\
.row()
def index_selected_rows(self):
table = self.find(QTableView, "tableView")
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def add(self):
rows = self.index_selected_rows()
if len(self._data) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
self.plot.update()
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
self.plot.update()
def sort(self):
self._table.sort(False)
self.plot.update()
def _copy(self):
rows = self.index_selected_rows()
table = []
#table.append(self._data.header)
table.append(self._trad.get_dict("table_headers"))
data = self._data.data
for row in rows:
table.append(list(data[row]))
self.copyTableIntoClipboard(table)
def _paste(self):
header, data = self.parseClipboardTable()
logger.debug(f"paste: h:{header}, d:{data}")
if len(data) == 0:
return
row = 0
rows = self.index_selected_rows()
if len(rows) != 0:
row = rows[0]
self._table.paste(row, data)
self.plot.update()
def _undo(self):
self._table.undo()
self.plot.update()
self.widget_update()
def _redo(self):
self._table.redo()
self.plot.update()
self.widget_update()
+170
View File
@@ -0,0 +1,170 @@
# Table.py -- Pamhyr
# Copyright (C) 2023 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.Tools.PamhyrTable import PamhyrTableModel
from View.Reservoir.UndoCommand import (
SetNameCommand, SetNodeCommand,
AddCommand, DelCommand, PasteCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, trad=None, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._trad = trad
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
self.editor.addItems(
[_translate("Reservoir", "Not associated")] +
self._data.nodes_names()
)
self.editor.setCurrentText(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())
model.setData(index, text)
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 TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data.reservoir
def rowCount(self, parent):
return len(self._lst)
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] == "node":
n = self._lst.get(row).node
if n is None:
return _translate("Reservoir", "Not associated")
return n.name
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] == "name":
self._undo.push(
SetNameCommand(
self._lst, row, value
)
)
elif self._headers[column] == "node":
self._undo.push(
SetNodeCommand(
self._lst, row, self._data.node(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 undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+33
View File
@@ -0,0 +1,33 @@
# translate.py -- Pamhyr
# Copyright (C) 2023 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 -*-
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate
class ReservoirTranslate(PamhyrTranslate):
def __init__(self):
super(ReservoirTranslate, self).__init__()
self._sub_dict["table_headers"] = {
"name": _translate("Reservoir", "Name"),
"node": _translate("Reservoir", "Node")
}
+127
View File
@@ -0,0 +1,127 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2023 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 -*-
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
from Model.Reservoir.Reservoir import Reservoir
from Model.Reservoir.ReservoirList import ReservoirList
class SetNameCommand(QUndoCommand):
def __init__(self, reservoir_lst, index, new_value):
QUndoCommand.__init__(self)
self._reservoir_lst = reservoir_lst
self._index = index
self._old = self._reservoir_lst.get(self._index).name
self._new = str(new_value)
def undo(self):
self._reservoir_lst.get(self._index).name = self._old
def redo(self):
self._reservoir_lst.get(self._index).name = self._new
class SetNodeCommand(QUndoCommand):
def __init__(self, reservoir_lst, index, node):
QUndoCommand.__init__(self)
self._reservoir_lst = reservoir_lst
self._index = index
self._old = self._reservoir_lst.get(self._index).node
self._new = node
self._prev_assoc_to_node = self._reservoir_lst.get_assoc_to_node(node)
def _previous_assoc_node(self, node):
if self._prev_assoc_to_node is not None:
self._prev_assoc_to_node.node = node
def undo(self):
self._reservoir_lst.get(self._index).node = self._old
self._previous_assoc_node(self._new)
def redo(self):
self._reservoir_lst.get(self._index).node = self._new
self._previous_assoc_node(None)
class AddCommand(QUndoCommand):
def __init__(self, reservoir_lst, index):
QUndoCommand.__init__(self)
self._reservoir_lst = reservoir_lst
self._index = index
self._new = None
def undo(self):
self._reservoir_lst.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._reservoir_lst.new(self._index)
else:
self._reservoir_lst.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, reservoir_lst, rows):
QUndoCommand.__init__(self)
self._reservoir_lst = reservoir_lst
self._rows = rows
self._reservoir = []
for row in rows:
self._reservoir.append((row, self._reservoir_lst.get(row)))
self._reservoir.sort()
def undo(self):
for row, el in self._reservoir:
self._reservoir_lst.insert(row, el)
def redo(self):
self._reservoir_lst.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, reservoir_lst, row, reservoir):
QUndoCommand.__init__(self)
self._reservoir_lst = reservoir_lst
self._row = row
self._reservoir = deepcopy(reservoir)
self._reservoir.reverse()
def undo(self):
self._reservoir_lst.delete_i(
self._tab,
range(self._row, self._row + len(self._reservoir))
)
def redo(self):
for r in self._reservoir:
self._reservoir_lst.insert(self._row, r)
+173
View File
@@ -0,0 +1,173 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 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
from tools import trace, timer
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
)
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, QVBoxLayout, QHeaderView, QTabWidget,
)
from View.Reservoir.Table import (
TableModel, ComboBoxDelegate
)
from View.Network.GraphWidget import GraphWidget
from View.Reservoir.Translate import ReservoirTranslate
from View.Reservoir.Edit.Window import EditReservoirWindow
_translate = QCoreApplication.translate
logger = logging.getLogger()
class ReservoirWindow(PamhyrWindow):
_pamhyr_ui = "ReservoirList"
_pamhyr_name = "Reservoir"
def __init__(self, study=None, config=None, parent=None):
name = self._pamhyr_name + " - " + study.name
super(ReservoirWindow, self).__init__(
title=name,
study=study,
config=config,
trad=ReservoirTranslate(),
parent=parent
)
self._reservoir_lst = self._study.river.reservoir
self.setup_table()
self.setup_graph()
self.setup_connections()
def setup_table(self):
self._table = None
self._delegate_node = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self
)
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["name", "node"],
delegates={
"node": self._delegate_node,
},
trad=self._trad,
data=self._study.river,
undo=self._undo_stack,
)
def setup_graph(self):
self.graph_widget = GraphWidget(
self._study.river,
min_size=None, size=(200, 200),
only_display=True,
parent=self
)
self.graph_layout = self.find(QVBoxLayout, "verticalLayout")
self.graph_layout.addWidget(self.graph_widget)
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self.find(QAction, "action_edit").triggered.connect(self.edit)
def index_selected_row(self):
table = self.find(QTableView, "tableView")
return table.selectionModel()\
.selectedRows()[0]\
.row()
def index_selected_rows(self):
table = self.find(QTableView, "tableView")
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def add(self):
rows = self.index_selected_rows()
if len(self._reservoir_lst) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
def _copy(self):
logger.info("TODO: copy")
def _paste(self):
logger.info("TODO: paste")
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()
def edit(self):
rows = self.index_selected_rows()
for row in rows:
data = self._reservoir_lst.get(row)
if self.sub_window_exists(
EditReservoirWindow,
data=[self._study, None, data]
):
continue
win = EditReservoirWindow(
data=data,
study=self._study,
parent=self
)
win.show()
+11 -9
View File
@@ -131,6 +131,7 @@
<addaction name="action_menu_initial_conditions"/>
<addaction name="action_menu_edit_friction"/>
<addaction name="action_menu_edit_lateral_contribution"/>
<addaction name="action_menu_edit_reservoirs"/>
</widget>
<widget class="QMenu" name="menu_results">
<property name="title">
@@ -492,10 +493,7 @@
<string>Boundary conditions and one-time contributions</string>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
<font/>
</property>
</action>
<action name="action_menu_initial_conditions">
@@ -600,11 +598,7 @@
<string>Visualize the last results</string>
</property>
<property name="font">
<font>
<family>Ubuntu</family>
<weight>50</weight>
<bold>false</bold>
</font>
<font/>
</property>
</action>
<action name="action_plot_limnigram">
@@ -932,6 +926,14 @@
<string>Developers (html)</string>
</property>
</action>
<action name="action_menu_edit_reservoirs">
<property name="text">
<string>Reservoirs</string>
</property>
<property name="toolTip">
<string>Edit reservoirs</string>
</property>
</action>
</widget>
<resources/>
<connections>
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTableView" name="tableView">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_delete"/>
<addaction name="action_sort"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new point</string>
</property>
</action>
<action name="action_delete">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete points</string>
</property>
</action>
<action name="action_edit">
<property name="icon">
<iconset>
<normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
</property>
<property name="text">
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit elevation/surface law</string>
</property>
</action>
<action name="action_sort">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset>
</property>
<property name="text">
<string>Sort</string>
</property>
<property name="toolTip">
<string>Sort points by elevation</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
+114
View File
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>849</width>
<height>600</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="_1">
<item>
<widget class="QTableView" name="tableView">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>849</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_delete"/>
<addaction name="action_edit"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new reservoir</string>
</property>
</action>
<action name="action_delete">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete reservoirs</string>
</property>
</action>
<action name="action_edit">
<property name="icon">
<iconset>
<normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
</property>
<property name="text">
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit reservoir law</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>