characteristics pollutant

adists_num
Youcef AOUAD 2024-06-04 16:29:03 +02:00
parent 1e8949067b
commit 37dd4ebaef
21 changed files with 480 additions and 1165 deletions

View File

@ -86,7 +86,7 @@ class OutputKpAdists(SQLSubModel):
CREATE TABLE OutputKpAdists( CREATE TABLE OutputKpAdists(
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
reach INTEGER NOT NULL, reach INTEGER NOT NULL,
kp INTEGER NOT NULL, kp REAL NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
FOREIGN KEY(edge) REFERENCES river_reach(id) FOREIGN KEY(edge) REFERENCES river_reach(id)
) )
@ -130,9 +130,18 @@ class OutputKpAdists(SQLSubModel):
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
print("print output kp unit")
print("id : ", self.id)
print("reach : ", self._reach)
print("kp : ", self._kp)
print("title : ", self._title)
print("title format : ", self._db_format(self._title))
execute(f"DELETE FROM OutputKpAdists WHERE id = {self.id}")
sql = ( sql = (
"INSERT INTO " + "INSERT INTO " +
"OutputKpAdists(id, reach, kp, title " + "OutputKpAdists(id, reach, kp, title) " +
"VALUES (" + "VALUES (" +
f"{self.id}, {self._reach}, " + f"{self.id}, {self._reach}, " +
f"{self._kp}, '{self._db_format(self._title)}'" + f"{self._kp}, '{self._db_format(self._title)}'" +

View File

@ -35,6 +35,7 @@ class OutputKpAdistsList(PamhyrModelList):
return new return new
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
print("save outputkpadists")
ok = True ok = True
# Delete previous data # Delete previous data

View File

@ -47,7 +47,7 @@ class Pollutants(SQLSubModel):
self._name = str(name) self._name = str(name)
self._enabled = True self._enabled = True
self._characteristics = [] self._data = []
Pollutants._id_cnt = max( Pollutants._id_cnt = max(
Pollutants._id_cnt + 1, self.id) Pollutants._id_cnt + 1, self.id)
@ -61,6 +61,9 @@ class Pollutants(SQLSubModel):
self._name = name self._name = name
self._status.modified() self._status.modified()
@property
def data(self):
return self._data.copy()
@classmethod @classmethod
def _db_create(cls, execute): def _db_create(cls, execute):
@ -125,7 +128,7 @@ class Pollutants(SQLSubModel):
for t in table: for t in table:
new_data.append(t) new_data.append(t)
new_pollutant._characteristics = new_data new_pollutant._data.append(new_data)
new.append(new_pollutant) new.append(new_pollutant)
@ -133,6 +136,8 @@ class Pollutants(SQLSubModel):
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
print("save in data base for pollutants")
execute(f"DELETE FROM Pollutants WHERE id = {self.id}") execute(f"DELETE FROM Pollutants WHERE id = {self.id}")
execute(f"DELETE FROM Pollutants_characteristics WHERE pollutant = {self.id}") execute(f"DELETE FROM Pollutants_characteristics WHERE pollutant = {self.id}")
@ -147,13 +152,13 @@ class Pollutants(SQLSubModel):
execute(sql) execute(sql)
for d in self._characteristics: for d in self._data:
sql = ( sql = (
"INSERT INTO " + "INSERT INTO " +
"Pollutants_characteristics(type, diametre, rho, porosity, " + "Pollutants_characteristics(type, diametre, rho, porosity, " +
"cdc_riv, cdc_cas, apd, ac, bc) " + "cdc_riv, cdc_cas, apd, ac, bc, pollutant) " +
f"VALUES ({d[1]}, {d[2]}, {d[3]},{d[4]}, {d[5]}, " f"VALUES ({d[0]}, {d[1]}, {d[2]},{d[3]}, {d[4]}, "
f"{d[6]}, {d[7]}, {d[8]}, {d[9]}, {self.id})" f"{d[5]}, {d[6]}, {d[7]}, {d[8]}, {self.id})"
) )
execute(sql) execute(sql)
@ -168,6 +173,13 @@ class Pollutants(SQLSubModel):
self._enabled = enabled self._enabled = enabled
self._status.modified() self._status.modified()
def is_define(self):
return len(self._data) != 0
def __len__(self):
return len(self._data)

View File

@ -35,6 +35,7 @@ class PollutantsList(PamhyrModelList):
return new return new
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
print("save pollutantsList")
ok = True ok = True
# Delete previous data # Delete previous data

View File

@ -232,6 +232,7 @@ class River(Graph, SQLSubModel):
AddFileList, AddFileList,
REPLineList, REPLineList,
OutputKpAdistsList, OutputKpAdistsList,
PollutantsList,
] ]
def __init__(self, status=None): def __init__(self, status=None):

View File

@ -1,277 +0,0 @@
# 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 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, QMessageBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.HydraulicStructures.BasicHydraulicStructures.UndoCommand import (
SetNameCommand, SetTypeCommand,
SetEnabledCommand, AddCommand, DelCommand,
SetValueCommand,
)
from Model.HydraulicStructures.Basic.Types import BHS_types
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
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
lst = list(
map(
lambda k: self._long_types[k],
BHS_types.keys()
)
)
self.editor.addItems(lst)
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 __init__(self, trad=None, **kwargs):
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
super(TableModel, self).__init__(trad=trad, **kwargs)
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._data.basic_structure(row).name
elif self._headers[column] == "type":
return self._long_types[self._data.basic_structure(row).type]
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._data, row, value
)
)
elif self._headers[column] == "type":
old_type = self._data.basic_structure(row).type
if old_type == "ND" or self._question_set_type():
key = next(
k for k, v in self._long_types.items()
if v == value
)
self._undo.push(
SetTypeCommand(
self._data, row, BHS_types[key]
)
)
except Exception as e:
logger.error(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def _question_set_type(self):
question = QMessageBox(self._parent)
question.setWindowTitle(self._trad['msg_type_change_title'])
question.setText(self._trad['msg_type_change_text'])
question.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
question.setIcon(QMessageBox.Question)
res = question.exec()
return res == QMessageBox.Ok
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()
self.layoutChanged.emit()
def enabled(self, row, enabled, parent=QModelIndex()):
self._undo.push(
SetEnabledCommand(
self._lst, row, enabled
)
)
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
class ParametersTableModel(PamhyrTableModel):
def __init__(self, trad=None, **kwargs):
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
self._hs_index = None
super(ParametersTableModel, self).__init__(trad=trad, **kwargs)
def rowCount(self, parent):
if self._hs_index is None:
return 0
return len(
self._data.basic_structure(self._hs_index)
)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
if self._hs_index is None:
return QVariant()
row = index.row()
column = index.column()
hs = self._data.basic_structure(self._hs_index)
if self._headers[column] == "name":
return self._trad[hs.parameters[row].name]
elif self._headers[column] == "value":
return str(hs.parameters[row].value)
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
if self._hs_index is None:
return QVariant()
row = index.row()
column = index.column()
try:
if self._headers[column] == "value":
self._undo.push(
SetValueCommand(
self._data.basic_structure(self._hs_index),
row, value
)
)
except Exception as e:
logger.error(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def update_hs_index(self, index):
self._hs_index = index
self.layoutChanged.emit()

View File

@ -1,155 +0,0 @@
# translate.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 -*-
from PyQt5.QtCore import QCoreApplication
from View.Translate import MainTranslate
_translate = QCoreApplication.translate
class BasicHydraulicStructuresTranslate(MainTranslate):
def __init__(self):
super(BasicHydraulicStructuresTranslate, self).__init__()
self._dict["Basic Hydraulic Structures"] = _translate(
"BasicHydraulicStructures", "Basic Hydraulic Structures"
)
self._dict['msg_type_change_title'] = _translate(
"BasicHydraulicStructures",
"Change hydraulic structure type"
)
self._dict['msg_type_change_text'] = _translate(
"BasicHydraulicStructures",
"Do you want to change the hydraulic structure type and reset \
hydraulic structure values?"
)
# BHSValues translation
self._dict['width'] = self._dict["unit_width"]
self._dict['height'] = self._dict["unit_thickness"]
self._dict['elevation'] = self._dict["unit_elevation"]
self._dict['diameter'] = self._dict["unit_diameter"]
self._dict['discharge_coefficient'] = _translate(
"BasicHydraulicStructures", "Discharge coefficient"
)
self._dict['loading_elevation'] = _translate(
"BasicHydraulicStructures", "Upper elevation (m)"
)
self._dict['half-angle_tangent'] = _translate(
"BasicHydraulicStructures", "Half-angle tangent"
)
self._dict['maximal_loading_elevation'] = _translate(
"BasicHydraulicStructures", "Maximal loading elevation"
)
self._dict['siltation_height'] = _translate(
"BasicHydraulicStructures", "Siltation height (m)"
)
self._dict['top_of_the_vault'] = _translate(
"BasicHydraulicStructures", "Top of the vault (m)"
)
self._dict['bottom_of_the_vault'] = _translate(
"BasicHydraulicStructures", "Bottom of the vault (m)"
)
self._dict['opening'] = _translate(
"BasicHydraulicStructures", "Opening"
)
self._dict['maximal_opening'] = _translate(
"BasicHydraulicStructures", "Maximal opening"
)
self._dict['step_space'] = _translate(
"BasicHydraulicStructures", "Step space"
)
self._dict['weir'] = _translate(
"BasicHydraulicStructures", "Weir"
)
self._dict['coefficient'] = _translate(
"BasicHydraulicStructures", "Coefficient"
)
# Dummy parameters
self._dict['parameter_1'] = _translate(
"BasicHydraulicStructures", "Parameter 1"
)
self._dict['parameter_2'] = _translate(
"BasicHydraulicStructures", "Parameter 2"
)
self._dict['parameter_3'] = _translate(
"BasicHydraulicStructures", "Parameter 3"
)
self._dict['parameter_4'] = _translate(
"BasicHydraulicStructures", "Parameter 4"
)
self._dict['parameter_5'] = _translate(
"BasicHydraulicStructures", "Parameter 5"
)
# BHS types long names
self._sub_dict["long_types"] = {
"ND": self._dict["not_defined"],
"S1": _translate(
"BasicHydraulicStructures", "Discharge weir"
),
"S2": _translate(
"BasicHydraulicStructures", "Trapezoidal weir"
),
"S3": _translate(
"BasicHydraulicStructures", "Triangular weir"
),
"OR": _translate(
"BasicHydraulicStructures", "Rectangular orifice"
),
"OC": _translate(
"BasicHydraulicStructures", "Circular orifice"
),
"OV": _translate(
"BasicHydraulicStructures", "Vaulted orifice"
),
"V1": _translate(
"BasicHydraulicStructures", "Rectangular gate"
),
"V2": _translate(
"BasicHydraulicStructures", "Simplified rectangular gate"
),
"BO": _translate(
"BasicHydraulicStructures", "Borda-type head loss"
),
"CV": _translate(
"BasicHydraulicStructures", "Check valve"
),
"UD": _translate(
"BasicHydraulicStructures", "User defined"
),
}
# Tables
self._sub_dict["table_headers"] = {
"name": self._dict["name"],
"type": self._dict["type"],
}
self._sub_dict["table_headers_parameters"] = {
"name": self._dict["name"],
"value": self._dict["value"],
}

View File

@ -1,153 +0,0 @@
# UndoCommand.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 -*-
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
class SetNameCommand(QUndoCommand):
def __init__(self, hs, index, new_value):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._old = self._hs.basic_structure(self._index).name
self._new = str(new_value)
def undo(self):
self._hs.basic_structure(self._index).name = self._old
def redo(self):
self._hs.basic_structure(self._index).name = self._new
class SetTypeCommand(QUndoCommand):
def __init__(self, hs, index, new_type):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._type = new_type
self._old = self._hs.basic_structure(self._index)
self._new = self._hs.basic_structure(self._index)\
.convert(self._type)
def undo(self):
self._hs.delete_i([self._index])
self._hs.insert(self._index, self._old)
def redo(self):
self._hs.delete_i([self._index])
self._hs.insert(self._index, self._new)
class SetEnabledCommand(QUndoCommand):
def __init__(self, hs, index, enabled):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._old = not enabled
self._new = enabled
def undo(self):
self._hs.basic_structure(self._index).enabled = self._old
def redo(self):
self._hs.basic_structure(self._index).enabled = self._new
class AddCommand(QUndoCommand):
def __init__(self, hs, index):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._new = None
def undo(self):
self._hs.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._hs.add(self._index)
else:
self._hs.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, hs, rows):
QUndoCommand.__init__(self)
self._hs = hs
self._rows = rows
self._bhs = []
for row in rows:
self._bhs.append((row, self._hs.basic_structure(row)))
def undo(self):
for row, el in self._bhs:
self._hs.insert(row, el)
def redo(self):
self._hs.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, hs, row, h_s):
QUndoCommand.__init__(self)
self._hs = hs
self._row = row
self._bhs = deepcopy(h_s)
self._bhs.reverse()
def undo(self):
self._hs.delete_i(range(self._row, self._row + len(self._bhs)))
def redo(self):
for r in self._bhs:
self._hs.insert(self._row, r)
####################################
# Basic hydraulic structure values #
####################################
class SetValueCommand(QUndoCommand):
def __init__(self, bhs, index, value):
QUndoCommand.__init__(self)
self._bhs = bhs
self._index = index
self._old = self._bhs.parameters[self._index].value
self._new = self._bhs.parameters[self._index].type(value)
def undo(self):
self._bhs.parameters[self._index].value = self._old
def redo(self):
self._bhs.parameters[self._index].value = self._new

View File

@ -1,270 +0,0 @@
# Window.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
from tools import timer, trace
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.HydraulicStructures.PlotAC import PlotAC
from View.HydraulicStructures.BasicHydraulicStructures.Table import (
ComboBoxDelegate, TableModel, ParametersTableModel,
)
from View.Network.GraphWidget import GraphWidget
from View.HydraulicStructures.BasicHydraulicStructures.Translate import (
BasicHydraulicStructuresTranslate
)
_translate = QCoreApplication.translate
logger = logging.getLogger()
class BasicHydraulicStructuresWindow(PamhyrWindow):
_pamhyr_ui = "BasicHydraulicStructures"
_pamhyr_name = "Basic Hydraulic Structures"
def __init__(self, data=None, study=None, config=None, parent=None):
trad = BasicHydraulicStructuresTranslate()
name = " - ".join([
trad[self._pamhyr_name], data.name, study.name
])
super(BasicHydraulicStructuresWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(data)
self._hs = data
self.setup_table()
self.setup_checkbox()
self.setup_plot()
self.setup_connections()
self.update()
def setup_table(self):
self.setup_table_bhs()
self.setup_table_bhs_parameters()
def setup_table_bhs(self):
self._table = None
self._delegate_type = ComboBoxDelegate(
trad=self._trad,
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", "type"],
delegates={
"type": self._delegate_type,
},
trad=self._trad,
data=self._hs,
undo=self._undo_stack,
parent=self,
)
selectionModel = table.selectionModel()
index = table.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def setup_table_bhs_parameters(self):
self._table_parameters = None
table = self.find(QTableView, f"tableView_2")
self._table_parameters = ParametersTableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers_parameters"),
editable_headers=["value"],
delegates={},
trad=self._trad,
data=self._hs,
undo=self._undo_stack,
parent=self,
)
def setup_checkbox(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._set_checkbox_state()
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.toolbar)
self.plot_layout.addWidget(self.canvas)
reach = self._hs.input_reach
profile_kp = self._hs.input_kp
if profile_kp is not None:
profiles = reach.reach.get_profiles_from_kp(float(profile_kp))
else:
profiles = None
if profiles is not None:
profile = profiles[0]
else:
profile = None
self.plot_ac = PlotAC(
canvas=self.canvas,
river=self._study.river,
reach=self._hs.input_reach,
profile=profile,
trad=self._trad,
toolbar=self.toolbar
)
self.plot_ac.draw()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self._checkbox.clicked.connect(self._set_basic_structure_state)
table = self.find(QTableView, "tableView")
table.selectionModel()\
.selectionChanged\
.connect(self.update)
self._table.dataChanged.connect(self.update)
self._table.layoutChanged.connect(self.update)
def index_selected(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0]
else:
return None
def index_selected_row(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0].row()
else:
return None
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._hs) == 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 _set_checkbox_state(self):
row = self.index_selected_row()
if row is None:
self._checkbox.setEnabled(False)
self._checkbox.setChecked(True)
else:
self._checkbox.setEnabled(True)
self._checkbox.setChecked(self._hs.basic_structure(row).enabled)
def _set_basic_structure_state(self):
rows = self.index_selected_rows()
if len(rows) != 0:
for row in rows:
if row is not None:
self._table.enabled(
row,
self._checkbox.isChecked()
)
def update(self):
self._set_checkbox_state()
self._update_parameters_table()
def _update_parameters_table(self):
row = self.index_selected_row()
self._table_parameters.update_hs_index(row)

View File

@ -0,0 +1,106 @@
# 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 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.Pollutants.Edit.UndoCommand import (
SetDataCommand, 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()
return self._data.data[row][column]
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] == "type":
self._undo.push(
SetDataCommand(
self._data, row, column, int(value)
)
)
else:
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 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()

View File

@ -0,0 +1,46 @@
# translate.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 -*-
from PyQt5.QtCore import QCoreApplication
from View.Translate import MainTranslate
from View.Pollutants.Translate import PollutantsTranslate
_translate = QCoreApplication.translate
class EditPollutantTranslate(PollutantsTranslate):
def __init__(self):
super(EditPollutantTranslate, self).__init__()
self._dict["Edit Pollutant"] = _translate(
"Pollutants", "Edit Pollutant"
)
self._sub_dict["table_headers"] = {
"type": self._dict["type"],
"diametre": self._dict["unit_area"],
"rho": self._dict["unit_rho"],
"porosity": self._dict["unit_porosity"],
"cdc_riv": self._dict["unit_cdc_riv"],
"cdc_cas": self._dict["unit_cdc_cas"],
"apd": self._dict["unit_apd"],
"ac": self._dict["unit_ac"],
"bc": self._dict["unit_bc"],
}

View File

@ -0,0 +1,65 @@
# UndoCommand.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
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
from Model.Pollutants.Pollutants import Pollutants
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.data[self._index][self._column]
self._new = new_value
def undo(self):
self._data.data[self._index][self._column] = self._old
def redo(self):
self._data.data[self._index][self._column] = self._new
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)

View File

@ -0,0 +1,149 @@
# Window.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
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.Pollutants.Edit.Translate import EditPollutantTranslate
from View.Pollutants.Edit.Table import TableModel
_translate = QCoreApplication.translate
logger = logging.getLogger()
class EditPolluantWindow(PamhyrWindow):
_pamhyr_ui = "Pollutant"
_pamhyr_name = "Edit Pollutant"
def __init__(self, data=None, study=None, config=None, parent=None):
self._data = data
trad = EditPollutantTranslate()
name = trad[self._pamhyr_name]
print(name)
if self._data is not None:
name += (
f" - {study.name} " +
f" - {self._data.name}"
)
super(EditPolluantWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(data)
self.setup_table()
def setup_table(self):
headers = {}
table_headers = self._trad.get_dict("table_headers")
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view=table,
table_headers=table_headers,
editable_headers=table_headers,
delegates={},
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 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 _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)
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()

View File

@ -1,120 +0,0 @@
# PlotAC.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 -*-
from tools import timer
from View.Tools.PamhyrPlot import PamhyrPlot
from matplotlib import pyplot as plt
class PlotAC(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None,
river=None, reach=None, profile=None,
parent=None):
super(PlotAC, self).__init__(
canvas=canvas,
trad=trad,
data=river,
toolbar=toolbar,
parent=parent
)
self._current_reach = reach
self._current_profile = profile
self.label_x = self._trad["x"]
self.label_y = self._trad["unit_elevation"]
self._isometric_axis = False
self._auto_relim_update = True
self._autoscale_update = True
@property
def river(self):
return self.data
@river.setter
def river(self, river):
self.data = river
@timer
def draw(self):
self.init_axes()
if self.data is None:
self.line_kp = None
return
if self._current_reach is None:
self.line_kp = None
return
self.draw_data()
self.idle()
self._init = True
def draw_data(self):
reach = self._current_reach
if self._current_profile is None:
self.line_kp = None
else:
profile = self._current_profile
x = profile.get_station()
z = profile.z()
self.line_kp, = self.canvas.axes.plot(
x, z,
color=self.color_plot_river_bottom,
**self.plot_default_kargs
)
def set_reach(self, reach):
self._current_reach = reach
self.update()
def set_profile(self, profile):
self._current_profile = profile
self.update()
def update(self):
if self.line_kp is None:
self.draw()
return
if self._current_reach is None or self._current_profile is None:
self.update_clear()
else:
self.update_data()
self.update_idle()
def update_data(self):
profile = self._current_profile
x = profile.get_station()
z = profile.z()
self.line_kp.set_data(x, z)
def clear(self):
self.update_clear()
def update_clear(self):
if self.line_kp is not None:
self.line_kp.set_data([], [])

View File

@ -1,165 +0,0 @@
# PlotKPC.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 -*-
from tools import timer
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
)
from matplotlib.collections import LineCollection
_translate = QCoreApplication.translate
class PlotKPC(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None,
river=None, reach=None, profile=None,
parent=None):
super(PlotKPC, self).__init__(
canvas=canvas,
trad=trad,
data=river,
toolbar=toolbar,
parent=parent
)
self._current_reach = reach
self._current_profile = profile
self.label_x = self._trad["unit_kp"]
self.label_y = self._trad["unit_elevation"]
self._isometric_axis = False
self._auto_relim_update = True
self._autoscale_update = True
@property
def river(self):
return self.data
@river.setter
def river(self, river):
self.data = river
@timer
def draw(self, highlight=None):
self.init_axes()
if self.data is None:
self.profile = None
self.line_kp_zmin_zmax = None
self.line_kp_zmin = None
return
if self._current_reach is None:
self.profile = None
self.line_kp_zmin_zmax = None
self.line_kp_zmin = None
return
self.draw_data()
self.draw_current()
self.idle()
self._init = True
def draw_data(self):
reach = self._current_reach
kp = reach.reach.get_kp()
z_min = reach.reach.get_z_min()
z_max = reach.reach.get_z_max()
self.line_kp_zmin, = self.canvas.axes.plot(
kp, z_min,
color=self.color_plot_river_bottom,
lw=1.
)
if len(kp) != 0:
self.line_kp_zmin_zmax = self.canvas.axes.vlines(
x=kp,
ymin=z_min, ymax=z_max,
color=self.color_plot,
lw=1.
)
def draw_current(self):
if self._current_profile is None:
self.profile = None
else:
kp = [self._current_profile.kp,
self._current_profile.kp]
min_max = [self._current_profile.z_min(),
self._current_profile.z_max()]
self.profile = self.canvas.axes.plot(
kp, min_max,
color=self.color_plot_current,
lw=1.
)
def set_reach(self, reach):
self._current_reach = reach
self._current_profile = None
self.update()
def set_profile(self, profile):
self._current_profile = profile
self.update_current_profile()
def update(self):
self.draw()
def update_current_profile(self):
reach = self._current_reach
kp = reach.reach.get_kp()
z_min = reach.reach.get_z_min()
z_max = reach.reach.get_z_max()
if self.profile is None:
self.draw()
else:
self.profile.set_data(
[self._current_profile.kp, self._current_profile.kp],
[self._current_profile.z_min(), self._current_profile.z_max()],
)
self.update_idle()
def clear(self):
if self.profile is not None:
self.profile[0].set_data([], [])
if self.line_kp_zmin_zmax is not None:
self.line_kp_zmin_zmax.remove()
self.line_kp_zmin_zmax = None
if self.line_kp_zmin is not None:
self.line_kp_zmin.set_data([], [])
self.canvas.figure.canvas.draw_idle()
def clear_profile(self):
if self.profile is not None:
self.profile.set_data([], [])
self.canvas.figure.canvas.draw_idle()

View File

@ -74,6 +74,7 @@ class AddCommand(QUndoCommand):
def redo(self): def redo(self):
if self._new is None: if self._new is None:
self._new = self._pollutants_lst.new(self._pollutants_lst, self._index) self._new = self._pollutants_lst.new(self._pollutants_lst, self._index)
self._new._data = [[0, 0., 0., 0., 0., 0., 0., 0., 0.]]
else: else:
self._pollutants_lst.insert(self._index, self._new) self._pollutants_lst.insert(self._index, self._new)

View File

@ -35,22 +35,13 @@ from PyQt5.QtWidgets import (
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
) )
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Pollutants.PlotAC import PlotAC
from View.Pollutants.PlotKPC import PlotKPC
from View.Pollutants.Table import ( from View.Pollutants.Table import (
TableModel TableModel
) )
from View.Network.GraphWidget import GraphWidget
from View.Pollutants.Translate import PollutantsTranslate from View.Pollutants.Translate import PollutantsTranslate
from View.Pollutants.BasicHydraulicStructures.Window import ( from View.Pollutants.Edit.Window import EditPolluantWindow
BasicHydraulicStructuresWindow
)
logger = logging.getLogger() logger = logging.getLogger()
@ -183,12 +174,12 @@ class PollutantsWindow(PamhyrWindow):
data = self._pollutants_lst.get(row) data = self._pollutants_lst.get(row)
if self.sub_window_exists( if self.sub_window_exists(
BasicHydraulicStructuresWindow, EditPolluantWindow,
data=[self._study, None, data] data=[self._study, None, data]
): ):
continue continue
win = BasicHydraulicStructuresWindow( win = EditPolluantWindow(
data=data, data=data,
study=self._study, study=self._study,
parent=self parent=self

View File

@ -72,6 +72,14 @@ class UnitTranslate(CommonWordTranslate):
self._dict["unit_date_s"] = _translate("Unit", "Date (sec)") self._dict["unit_date_s"] = _translate("Unit", "Date (sec)")
self._dict["unit_date_iso"] = _translate("Unit", "Date (ISO format)") self._dict["unit_date_iso"] = _translate("Unit", "Date (ISO format)")
self._dict["unit_area"] = _translate("Unit", "Area")
self._dict["unit_rho"] = _translate("Unit", "Rho")
self._dict["unit_porosity"] = _translate("Unit", "Porosity")
self._dict["unit_cdc_riv"] = _translate("Unit", "CDC_RIV")
self._dict["unit_cdc_cas"] = _translate("Unit", "CDC_CAS")
self._dict["unit_apd"] = _translate("Unit", "APD")
self._dict["unit_ac"] = _translate("Unit", "AC")
self._dict["unit_bc"] = _translate("Unit", "BC")
class MainTranslate(UnitTranslate): class MainTranslate(UnitTranslate):
def __init__(self): def __init__(self):

69
src/View/ui/Pollutant.ui Normal file
View File

@ -0,0 +1,69 @@
<?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>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -122,12 +122,8 @@
</property> </property>
</action> </action>
<action name="action_edit"> <action name="action_edit">
<property name="icon">
<iconset>
<normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
</property>
<property name="text"> <property name="text">
<string>Edit</string> <string>Characteristics</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Edit selected hydraulic structure</string> <string>Edit selected hydraulic structure</string>

Binary file not shown.