diff --git a/src/Model/DIFAdisTS/DIFAdisTS.py b/src/Model/DIFAdisTS/DIFAdisTS.py
new file mode 100644
index 00000000..c0447941
--- /dev/null
+++ b/src/Model/DIFAdisTS/DIFAdisTS.py
@@ -0,0 +1,258 @@
+# DIFAdisTS.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 .
+
+# -*- coding: utf-8 -*-
+
+import logging
+from functools import reduce
+
+from tools import trace, timer, old_pamhyr_date_to_timestamp
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+from Model.DIFAdisTS.DIFAdisTSSpec import DIFAdisTSSpec
+
+logger = logging.getLogger()
+
+class DIFAdisTS(SQLSubModel):
+ _sub_classes = [
+ DIFAdisTSSpec,
+ ]
+ _id_cnt = 0
+
+ def __init__(self, id: int = -1, name: str = "default",
+ status=None):
+ super(DIFAdisTS, self).__init__()
+
+ self._status = status
+
+ if id == -1:
+ self.id = DIFAdisTS._id_cnt
+ else:
+ self.id = id
+
+ self._name = name
+ self._method = None
+ self._dif = None
+ self._b = None
+ self._c = None
+ self._enabled = True
+ self._types = ["iwasa", "fisher", "elder", "constante", "generique"]
+ self._data = []
+
+ DIFAdisTS._id_cnt = max(
+ DIFAdisTS._id_cnt + 1,
+ self.id
+ )
+
+ @classmethod
+ def _db_create(cls, execute):
+ execute("""
+ CREATE TABLE dif_adists(
+ id INTEGER NOT NULL PRIMARY KEY,
+ name TEXT NOT NULL,
+ method TEXT NOT NULL,
+ dif REAL NOT NULL,
+ b REAL,
+ c REAL,
+ enabled BOOLEAN NOT NULL
+ )
+ """)
+
+ return cls._create_submodel(execute)
+
+ @classmethod
+ def _db_update(cls, execute, version):
+ major, minor, release = version.strip().split(".")
+ if major == minor == "0":
+ if int(release) < 6:
+ cls._db_create(execute)
+
+ return True
+
+ @classmethod
+ def _db_load(cls, execute, data=None):
+ new = []
+
+ table = execute(
+ "SELECT id, name, method, dif, b, c, enabled " +
+ "FROM dif_adists"
+ )
+
+ if table is not None:
+ for row in table:
+ dif_id = row[0]
+ name = row[1]
+ method = row[2]
+ dif = row[3]
+ b = row[4]
+ c = row[5]
+ enabled = (row[6] == 1)
+
+ DIF = cls(
+ id=dif_id,
+ name=name,
+ status=data['status']
+ )
+
+ DIF.method = method
+ DIF.dif = dif
+ DIF.b = b
+ DIF.c = c
+ DIF.enabled = enabled
+
+ data['dif_default_id'] = dif_id
+ DIF._data = DIFAdisTSSpec._db_load(execute, data)
+
+ new.append(DIF)
+
+ return new
+
+ def _db_save(self, execute, data=None):
+ execute(f"DELETE FROM dif_adists WHERE id = {self.id}")
+
+ method = ""
+ if self.method is not None:
+ method = self.method
+
+ dif = -1.
+ if self.dif is not None:
+ dif = self.dif
+
+ b = -1.
+ if self.b is not None:
+ b = self.b
+
+ c = -1.
+ if self.dif is not None:
+ c = self.c
+
+ sql = (
+ "INSERT INTO " +
+ "dif_adists(" +
+ "id, name, method, dif, b, c, enabled" +
+ ") " +
+ "VALUES (" +
+ f"{self.id}, '{self._db_format(self._name)}', " +
+ f"'{self._db_format(self._method)}', " +
+ f"{dif}, {b}, {c}, {self._enabled}" +
+ ")"
+ )
+
+ execute(sql)
+
+ data['dif_default_id'] = self.id
+ execute(
+ "DELETE FROM dif_spec " +
+ f"WHERE dif_default = {self.id}"
+ )
+
+ for dif_spec in self._data:
+ dif_spec._db_save(execute, data)
+
+ return True
+
+ def __len__(self):
+ return len(self._data)
+
+ @property
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ self._name = name
+ self._status.modified()
+
+ @property
+ def method(self):
+ return self._method
+
+ @method.setter
+ def method(self, method):
+ self._method = method
+ self._status.modified()
+
+ @property
+ def types(self):
+ return self._types
+
+ @property
+ def dif(self):
+ return self._dif
+
+ @dif.setter
+ def dif(self, dif):
+ self._dif = dif
+ self._status.modified()
+
+ @property
+ def b(self):
+ return self._b
+
+ @b.setter
+ def b(self, b):
+ self._b = b
+ self._status.modified()
+
+ @property
+ def c(self):
+ return self._c
+
+ @c.setter
+ def c(self, c):
+ self._c = c
+ self._status.modified()
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, enabled):
+ self._enabled = enabled
+ self._status.modified()
+
+ def new(self, index):
+ n = DIFAdisTSSpec(status=self._status)
+ self._data.insert(index, n)
+ self._status.modified()
+ return n
+
+ def delete(self, data):
+ self._data = list(
+ filter(
+ lambda x: x not in data,
+ self._data
+ )
+ )
+ self._status.modified()
+
+ def delete_i(self, indexes):
+ for ind in indexes:
+ del self._data[ind]
+ self._status.modified()
+
+ def insert(self, index, data):
+ self._data.insert(index, data)
+ self._status.modified()
+
+
+
+
+
+
diff --git a/src/Model/DIFAdisTS/DIFAdisTSList.py b/src/Model/DIFAdisTS/DIFAdisTSList.py
new file mode 100644
index 00000000..89eff085
--- /dev/null
+++ b/src/Model/DIFAdisTS/DIFAdisTSList.py
@@ -0,0 +1,62 @@
+# DIFAdisTSList.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 .
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
+
+class DIFAdisTSList(PamhyrModelList):
+ _sub_classes = [
+ DIFAdisTS,
+ ]
+
+ @classmethod
+ def _db_load(cls, execute, data=None):
+ new = cls(status=data['status'])
+
+ if data is None:
+ data = {}
+
+ new._lst = DIFAdisTS._db_load(
+ execute, data
+ )
+
+ return new
+
+ def _db_save(self, execute, data=None):
+ execute("DELETE FROM dif_adists")
+
+ if data is None:
+ data = {}
+
+ for dif in self._lst:
+ dif._db_save(execute, data=data)
+
+ return True
+
+ def new(self, index):
+ n = DIFAdisTS(status=self._status)
+ self._lst.insert(index, n)
+ self._status.modified()
+ return n
+
+
+
+
diff --git a/src/Model/DIFAdisTS/DIFAdisTSSpec.py b/src/Model/DIFAdisTS/DIFAdisTSSpec.py
new file mode 100644
index 00000000..6850fb65
--- /dev/null
+++ b/src/Model/DIFAdisTS/DIFAdisTSSpec.py
@@ -0,0 +1,228 @@
+# DIFAdisTSSpec.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 .
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class DIFAdisTSSpec(SQLSubModel):
+ _sub_classes = [
+ ]
+ _id_cnt = 0
+
+ def __init__(self, id: int = -1, method: str = "",
+ status=None):
+ super(DIFAdisTSSpec, self).__init__()
+
+ self._status = status
+
+ if id == -1:
+ self.id = DIFAdisTSSpec._id_cnt
+ else:
+ self.id = id
+
+ self._method = method
+ self._reach = None
+ self._start_kp = None
+ self._end_kp = None
+ self._dif = None
+ self._b = None
+ self._c = None
+ self._enabled = True
+
+ DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self.id)
+
+ @classmethod
+ def _db_create(cls, execute):
+ execute("""
+ CREATE TABLE dif_spec(
+ id INTEGER NOT NULL PRIMARY KEY,
+ dif_default INTEGER NOT NULL,
+ method TEXT NOT NULL,
+ reach INTEGER NOT NULL,
+ start_kp REAL NOT NULL,
+ end_kp REAL NOT NULL,
+ dif REAL NOT NULL,
+ b REAL,
+ c REAL,
+ enabled BOOLEAN NOT NULL,
+ FOREIGN KEY(dif_default) REFERENCES dif_adists(id),
+ FOREIGN KEY(reach) REFERENCES river_reach(id)
+ )
+ """)
+
+ return cls._create_submodel(execute)
+
+ @classmethod
+ def _db_update(cls, execute, version):
+ major, minor, release = version.strip().split(".")
+ if major == minor == "0":
+ if int(release) < 6:
+ cls._db_create(execute)
+
+ return True
+
+ @classmethod
+ def _db_load(cls, execute, data=None):
+ new = []
+
+ table = execute(
+ "SELECT id, dif_default, method, reach, start_kp, end_kp, " +
+ "dif, b, c, enabled " +
+ "FROM dif_spec " +
+ f"WHERE dif_default = {data['dif_default_id']} "
+ )
+
+ for row in table:
+ id = row[0]
+ method = row[2]
+ reach = row[3]
+ start_kp = row[4]
+ end_kp = row[5]
+ dif = row[6]
+ b = row[7]
+ c = row[8]
+ enabled = (row[9] == 1)
+
+ new_spec = cls(
+ id=id,
+ method=method,
+ status=data['status']
+ )
+
+ new_spec.reach = reach
+ new_spec.start_kp = start_kp
+ new_spec.end_kp = end_kp
+ new_spec.dif = dif
+ new_spec.b = b
+ new_spec.c = c
+ new_spec.enabled = enabled
+
+ new.append(new_spec)
+
+ return new
+
+ def _db_save(self, execute, data=None):
+ dif_default = data['dif_default_id']
+
+ sql = (
+ "INSERT INTO " +
+ "dif_spec(id, dif_default, method, reach, " +
+ "start_kp, end_kp, dif, b, c, enabled) " +
+ "VALUES (" +
+ f"{self.id}, " +
+ f"{dif_default}, " +
+ f"'{self._db_format(self._method)}', " +
+ f"{self._reach}, " +
+ f"{self._start_kp}, " +
+ f"{self._end_kp}, " +
+ f"{self._dif}, " +
+ f"{self._b}, " +
+ f"{self._c}, " +
+ f"{self._enabled}" +
+ ")"
+ )
+ execute(sql)
+
+ return True
+
+ @property
+ def method(self):
+ return self._method
+
+ @method.setter
+ def method(self, method):
+ self._method = method
+ self._status.modified()
+
+ @property
+ def reach(self):
+ return self._reach
+
+ @reach.setter
+ def reach(self, reach):
+ self._reach = reach
+ self._status.modified()
+
+ @property
+ def start_kp(self):
+ return self._start_kp
+
+ @start_kp.setter
+ def start_kp(self, start_kp):
+ self._start_kp = start_kp
+ self._status.modified()
+
+ @property
+ def end_kp(self):
+ return self._end_kp
+
+ @end_kp.setter
+ def end_kp(self, end_kp):
+ self._end_kp = end_kp
+ self._status.modified()
+
+ @property
+ def dif(self):
+ return self._dif
+
+ @dif.setter
+ def dif(self, dif):
+ self._dif = dif
+ self._status.modified()
+
+ @property
+ def b(self):
+ return self._b
+
+ @b.setter
+ def b(self, b):
+ self._b = b
+ self._status.modified()
+
+ @property
+ def c(self):
+ return self._c
+
+ @c.setter
+ def c(self, c):
+ self._c = c
+ self._status.modified()
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, enabled):
+ self._enabled = enabled
+ self._status.modified()
+
+
+
+
+
+
+
+
+
diff --git a/src/Model/River.py b/src/Model/River.py
index 4cc2c5e4..e5797961 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -51,6 +51,7 @@ from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList import InitialCon
from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList import BoundaryConditionsAdisTSList
from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList import LateralContributionsAdisTSList
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
+from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
class RiverNode(Node, SQLSubModel):
@@ -241,6 +242,7 @@ class River(Graph, SQLSubModel):
BoundaryConditionsAdisTSList,
LateralContributionsAdisTSList,
D90AdisTSList,
+ DIFAdisTSList,
]
def __init__(self, status=None):
@@ -270,6 +272,7 @@ class River(Graph, SQLSubModel):
self._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList(status=self._status)
self._LateralContributionsAdisTS = LateralContributionsAdisTSList(status=self._status)
self._D90AdisTS = D90AdisTSList(status=self._status)
+ self._DIFAdisTS = DIFAdisTSList(status=self._status)
@classmethod
def _db_create(cls, execute):
@@ -358,6 +361,8 @@ class River(Graph, SQLSubModel):
new._D90AdisTS = D90AdisTSList._db_load(execute, data)
+ new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
+
return new
def _db_save(self, execute, data=None):
@@ -383,6 +388,7 @@ class River(Graph, SQLSubModel):
objs.append(self._BoundaryConditionsAdisTS)
objs.append(self._LateralContributionsAdisTS)
objs.append(self._D90AdisTS)
+ objs.append(self._DIFAdisTS)
self._save_submodel(execute, objs, data)
return True
@@ -530,6 +536,10 @@ Last export at: @date."""
def d90_adists(self):
return self._D90AdisTS
+ @property
+ def dif_adists(self):
+ return self._DIFAdisTS
+
def get_params(self, solver):
if solver in self._parameters:
return self._parameters[solver]
diff --git a/src/View/DIFAdisTS/Table.py b/src/View/DIFAdisTS/Table.py
new file mode 100644
index 00000000..13d5555c
--- /dev/null
+++ b/src/View/DIFAdisTS/Table.py
@@ -0,0 +1,217 @@
+# 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 .
+
+# -*- 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.D90AdisTS.UndoCommand import (
+ SetCommand, AddCommand, SetCommandSpec,
+ DelCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+ def __init__(self, data=None, ic_spec_lst=None, trad=None, parent=None, mode="reaches"):
+ super(ComboBoxDelegate, self).__init__(parent)
+
+ self._data = data
+ self._mode = mode
+ self._trad = trad
+ self._ic_spec_lst = ic_spec_lst
+
+ def createEditor(self, parent, option, index):
+ self.editor = QComboBox(parent)
+
+ val = []
+ if self._mode == "kp":
+ reach_id = self._ic_spec_lst[index.row()].reach
+
+ reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges()))
+
+ if reach_id is not None:
+ val = list(
+ map(
+ lambda kp: str(kp), reach.reach.get_kp()
+ )
+ )
+ else:
+ val = list(
+ map(
+ lambda n: n.name, self._data.edges()
+ )
+ )
+
+ self.editor.addItems(
+ [self._trad['not_associated']] +
+ val
+ )
+
+ 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())
+ 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 D90TableModel(PamhyrTableModel):
+ def __init__(self, river=None, data=None, **kwargs):
+ self._river = river
+
+ super(D90TableModel, self).__init__(data=data, **kwargs)
+
+ self._data = data
+
+ def _setup_lst(self):
+ self._lst = self._data._data
+
+ 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] is "name":
+ n = self._lst[row].name
+ if n is None or n == "":
+ return self._trad['not_associated']
+ return n
+ elif self._headers[column] is "reach":
+ n = self._lst[row].reach
+ if n is None:
+ return self._trad['not_associated']
+ return next(filter(lambda edge: edge.id == n, self._river.edges())).name
+ elif self._headers[column] is "start_kp":
+ n = self._lst[row].start_kp
+ if n is None:
+ return self._trad['not_associated']
+ return n
+ elif self._headers[column] is "end_kp":
+ n = self._lst[row].end_kp
+ if n is None:
+ return self._trad['not_associated']
+ return n
+ elif self._headers[column] is "d90":
+ n = self._lst[row].d90
+ if n is None:
+ return self._trad['not_associated']
+ return n
+
+ 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] != "reach":
+ self._undo.push(
+ SetCommandSpec(
+ self._lst, row, self._headers[column], value
+ )
+ )
+ elif self._headers[column] == "reach":
+ print(self._river.edge(value).id)
+ self._undo.push(
+ SetCommandSpec(
+ self._lst, row, self._headers[column], self._river.edge(value).id
+ )
+ )
+ 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, 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._data, 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()
+
diff --git a/src/View/DIFAdisTS/TableDefault.py b/src/View/DIFAdisTS/TableDefault.py
new file mode 100644
index 00000000..c3231d42
--- /dev/null
+++ b/src/View/DIFAdisTS/TableDefault.py
@@ -0,0 +1,95 @@
+# 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 .
+
+# -*- 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.D90AdisTS.UndoCommand import (
+ SetCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+class D90TableDefaultModel(PamhyrTableModel):
+ def __init__(self, **kwargs):
+ super(D90TableDefaultModel, self).__init__(**kwargs)
+
+ def data(self, index, role):
+ if role != Qt.ItemDataRole.DisplayRole:
+ return QVariant()
+
+ row = index.row()
+ column = index.column()
+
+ if self._headers[column] is "name":
+ return self._data[row].name
+ elif self._headers[column] is "d90":
+ n = self._data[row].d90
+ if n is None:
+ return self._trad['not_associated']
+ return n
+
+ 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._data, 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 undo(self):
+ self._undo.undo()
+ self.layoutChanged.emit()
+
+ def redo(self):
+ self._undo.redo()
+ self.layoutChanged.emit()
+
diff --git a/src/View/DIFAdisTS/UndoCommand.py b/src/View/DIFAdisTS/UndoCommand.py
new file mode 100644
index 00000000..f0644955
--- /dev/null
+++ b/src/View/DIFAdisTS/UndoCommand.py
@@ -0,0 +1,150 @@
+# 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 .
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+ QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.D90AdisTS.D90AdisTS import D90AdisTS
+from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
+
+
+class SetCommand(QUndoCommand):
+ def __init__(self, data, row, column, new_value):
+ QUndoCommand.__init__(self)
+
+ self._data = data
+ self._row = row
+ self._column = column
+
+ if self._column == "name":
+ self._old = self._data[self._row].name
+ elif self._column == "d90":
+ self._old = self._data[self._row].d90
+
+ _type = float
+ if column == "name":
+ _type = str
+
+ self._new = _type(new_value)
+
+ def undo(self):
+ if self._column == "name":
+ self._data[self._row].name = self._old
+ elif self._column == "d90":
+ self._data[self._row].d90 = self._old
+
+ def redo(self):
+ if self._column == "name":
+ self._data[self._row].name = self._new
+ elif self._column == "d90":
+ self._data[self._row].d90 = self._new
+
+class SetCommandSpec(QUndoCommand):
+ def __init__(self, data, row, column, new_value):
+ QUndoCommand.__init__(self)
+
+ self._data = data
+ self._row = row
+ self._column = column
+
+ if self._column == "name":
+ self._old = self._data[self._row].name
+ elif self._column == "reach":
+ self._old = self._data[self._row].reach
+ elif self._column == "start_kp":
+ self._old = self._data[self._row].start_kp
+ elif self._column == "end_kp":
+ self._old = self._data[self._row].end_kp
+ elif self._column == "d90":
+ self._old = self._data[self._row].d90
+
+ _type = float
+ if column == "name":
+ _type = str
+ elif column == "reach":
+ _type = int
+
+ self._new = _type(new_value)
+
+ def undo(self):
+ if self._column == "name":
+ self._data[self._row].name = self._old
+ elif self._column == "reach":
+ self._data[self._row].reach = self._old
+ elif self._column == "start_kp":
+ self._data[self._row].start_kp = self._old
+ elif self._column == "end_kp":
+ self._data[self._row].end_kp = self._old
+ elif self._column == "d90":
+ self._data[self._row].d90 = self._old
+
+ def redo(self):
+ if self._column == "name":
+ self._data[self._row].name = self._new
+ elif self._column == "reach":
+ self._data[self._row].reach = self._new
+ elif self._column == "start_kp":
+ self._data[self._row].start_kp = self._new
+ elif self._column == "end_kp":
+ self._data[self._row].end_kp = self._new
+ elif self._column == "d90":
+ self._data[self._row].d90 = self._new
+
+class AddCommand(QUndoCommand):
+ def __init__(self, data, ics_spec, index):
+ QUndoCommand.__init__(self)
+
+ self._data = data
+ self._ics_spec = ics_spec
+ 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.new(self._index)
+ else:
+ self._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+ def __init__(self, data, ics_spec, rows):
+ QUndoCommand.__init__(self)
+
+ self._data = data
+ self._ics_spec = ics_spec
+ self._rows = rows
+ #self._data = data
+
+ self._ic = []
+ for row in rows:
+ self._ic.append((row, self._ics_spec[row]))
+ self._ic.sort()
+
+ def undo(self):
+ for row, el in self._ic:
+ self._data.insert(row, el)
+
+ def redo(self):
+ self._data.delete_i(self._rows)
+
diff --git a/src/View/DIFAdisTS/Window.py b/src/View/DIFAdisTS/Window.py
new file mode 100644
index 00000000..5fffc17d
--- /dev/null
+++ b/src/View/DIFAdisTS/Window.py
@@ -0,0 +1,283 @@
+# 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 .
+
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+ QKeySequence, QIcon,
+)
+
+from PyQt5.QtCore import (
+ Qt, QVariant, QAbstractTableModel,
+ QCoreApplication, QModelIndex, pyqtSlot,
+ QRect, QItemSelectionModel,
+)
+
+from PyQt5.QtWidgets import (
+ QDialogButtonBox, QPushButton, QLineEdit,
+ QFileDialog, QTableView, QAbstractItemView,
+ QUndoStack, QShortcut, QAction, QItemDelegate,
+ QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+ QVBoxLayout, QToolBar, QAction, QToolButton,
+)
+
+from Modules import Modules
+
+from View.InitialConditionsAdisTS.UndoCommand import (
+ SetCommand,
+)
+
+from View.D90AdisTS.TableDefault import (
+ D90TableDefaultModel,
+)
+
+from View.D90AdisTS.Table import (
+ D90TableModel, ComboBoxDelegate,
+)
+
+from View.D90AdisTS.translate import D90AdisTSTranslate
+
+from Solver.Mage import Mage8
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class D90AdisTSWindow(PamhyrWindow):
+ _pamhyr_ui = "D90AdisTS"
+ _pamhyr_name = "D90 AdisTS"
+
+ def __init__(self, data=None, study=None, config=None, parent=None):
+ self._data = []
+ self._data.append(data)
+ trad = D90AdisTSTranslate()
+
+ name = (
+ trad[self._pamhyr_name] +
+ " - " + study.name +
+ " - " + self._data[0].name
+ )
+
+ super(D90AdisTSWindow, self).__init__(
+ title=name,
+ study=study,
+ config=config,
+ trad=trad,
+ parent=parent
+ )
+
+ self._hash_data.append(data)
+
+ self._d90_adists_lst = study.river.d90_adists
+
+ self.setup_table()
+
+ self.ui.setWindowTitle(self._title)
+
+ def setup_table(self):
+
+ path_icons = os.path.join(self._get_ui_directory(), f"ressources")
+
+ table_default = self.find(QTableView, f"tableView")
+
+ self._table = D90TableDefaultModel(
+ table_view=table_default,
+ table_headers=self._trad.get_dict("table_headers"),
+ editable_headers=["name", "d90"],
+ delegates={},
+ data=self._data,
+ undo=self._undo_stack,
+ trad=self._trad
+ )
+
+ table_default.setModel(self._table)
+ table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
+ table_default.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ table_default.setAlternatingRowColors(True)
+
+ layout = self.find(QVBoxLayout, f"verticalLayout_1")
+ toolBar = QToolBar()
+ layout.addWidget(toolBar)
+
+ action_add = QAction(self)
+ action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
+ action_add.triggered.connect(self.add)
+ action_delete = QAction(self)
+ action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
+ action_delete.triggered.connect(self.delete)
+
+ toolBar.addAction(action_add)
+ toolBar.addAction(action_delete)
+
+ self.table_spec = QTableView()
+ layout.addWidget(self.table_spec)
+
+ self._delegate_reach = ComboBoxDelegate(
+ trad=self._trad,
+ data=self._study.river,
+ ic_spec_lst=self._data[0]._data,
+ parent=self,
+ mode="reaches"
+ )
+ self._delegate_kp = ComboBoxDelegate(
+ trad=self._trad,
+ data=self._study.river,
+ ic_spec_lst=self._data[0]._data,
+ parent=self,
+ mode="kp"
+ )
+
+ self._table_spec = D90TableModel(
+ table_view=self.table_spec,
+ table_headers=self._trad.get_dict("table_headers_spec"),
+ editable_headers=["name", "reach", "start_kp", "end_kp", "d90"],
+ delegates={
+ "reach": self._delegate_reach,
+ "start_kp": self._delegate_kp,
+ "end_kp": self._delegate_kp
+ },
+ data=self._data[0],
+ undo=self._undo_stack,
+ trad=self._trad,
+ river=self._study.river
+ )
+
+ self.table_spec.setModel(self._table_spec)
+ self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
+ self.table_spec.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ self.table_spec.setAlternatingRowColors(True)
+
+ selectionModel = self.table_spec.selectionModel()
+ index = self.table_spec.model().index(0, 0)
+
+ selectionModel.select(
+ index,
+ QItemSelectionModel.Rows |
+ QItemSelectionModel.ClearAndSelect |
+ QItemSelectionModel.Select
+ )
+ self.table_spec.scrollTo(index)
+
+ def index_selected_row(self):
+ #table = self.find(QTableView, f"tableView")
+ table = self.table_spec
+ rows = table.selectionModel()\
+ .selectedRows()
+
+ if len(rows) == 0:
+ return 0
+
+ return rows[0].row()
+
+ def index_selected_rows(self):
+ #table = self.find(QTableView, f"tableView")
+ table = self.table_spec
+ return list(
+ # Delete duplicate
+ set(
+ map(
+ lambda i: i.row(),
+ table.selectedIndexes()
+ )
+ )
+ )
+
+ def move_up(self):
+ row = self.index_selected_row()
+ self._table.move_up(row)
+ self._update()
+
+ def move_down(self):
+ row = self.index_selected_row()
+ self._table.move_down(row)
+ self._update()
+
+ def _copy(self):
+ rows = list(
+ map(
+ lambda row: row.row(),
+ self.tableView.selectionModel().selectedRows()
+ )
+ )
+
+ table = list(
+ map(
+ lambda eic: list(
+ map(
+ lambda k: eic[1][k],
+ ["kp", "discharge", "elevation"]
+ )
+ ),
+ filter(
+ lambda eic: eic[0] in rows,
+ enumerate(self._ics.lst())
+ )
+ )
+ )
+
+ self.copyTableIntoClipboard(table)
+
+ def _paste(self):
+ header, data = self.parseClipboardTable()
+
+ if len(data) + len(header) == 0:
+ return
+
+ logger.debug(
+ "IC: Paste: " +
+ f"header = {header}, " +
+ f"data = {data}"
+ )
+
+ try:
+ row = self.index_selected_row()
+ # self._table.paste(row, header, data)
+ self._table.paste(row, [], data)
+ except Exception as e:
+ logger_exception(e)
+
+ self._update()
+
+ def _undo(self):
+ self._table.undo()
+ self._update()
+
+ def _redo(self):
+ self._table.redo()
+ self._update()
+
+ def add(self):
+ rows = self.index_selected_rows()
+ if len(self._data[0]._data) == 0 or len(rows) == 0:
+ self._table_spec.add(0)
+ else:
+ self._table_spec.add(rows[0])
+
+ def delete(self):
+ print("del")
+ rows = self.index_selected_rows()
+ if len(rows) == 0:
+ print("len 0")
+ return
+ self._table_spec.delete(rows)
diff --git a/src/View/DIFAdisTS/translate.py b/src/View/DIFAdisTS/translate.py
new file mode 100644
index 00000000..5a7bbd75
--- /dev/null
+++ b/src/View/DIFAdisTS/translate.py
@@ -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 .
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class D90AdisTSTranslate(MainTranslate):
+ def __init__(self):
+ super(D90AdisTSTranslate, self).__init__()
+
+ self._dict["D90 AdisTS"] = _translate(
+ "D90AdisTS", "D90 AdisTS")
+
+ self._dict["kp"] = self._dict["unit_kp"]
+
+ self._sub_dict["table_headers"] = {
+ "name": self._dict["name"],
+ "d90": _translate("Unit", "D90"),
+ }
+
+ self._sub_dict["table_headers_spec"] = {
+ "name": self._dict["name"],
+ "reach": self._dict["reach"],
+ "start_kp": _translate("Unit", "Start_KP (m)"),
+ "end_kp": _translate("Unit", "End_KP (m)"),
+ "d90": _translate("Unit", "D90"),
+ }
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index c61fae90..ddca58d6 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -78,6 +78,7 @@ from View.Debug.Window import ReplWindow
from View.OutputKpAdisTS.Window import OutputKpAdisTSWindow
from View.Pollutants.Window import PollutantsWindow
from View.D90AdisTS.Window import D90AdisTSWindow
+from View.DIFAdisTS.Window import D90AdisTSWindow
# Optional internal display of documentation for make the application
# package lighter...
@@ -123,7 +124,7 @@ define_model_action = [
"action_menu_boundary_conditions_sediment",
"action_menu_rep_additional_lines", "action_menu_output_kp",
"action_menu_run_adists", "action_menu_pollutants",
- "action_menu_d90",
+ "action_menu_d90", "action_menu_dif",
]
action = (
@@ -237,6 +238,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"""
actions = {
# Menu action
+ "action_menu_dif": self.open_dif,
"action_menu_d90": self.open_d90,
"action_menu_pollutants": self.open_pollutants,
"action_menu_run_adists":self.run_solver_adists,
@@ -888,6 +890,25 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
D90AdisTS.show()
+ def open_dif(self):
+ if len(self._study.river.d90_adists.lst) != 0:
+ d90_default = self._study.river.d90_adists.lst[0]
+ else:
+ d90_default = self._study.river.d90_adists.new(0)
+
+ if self.sub_window_exists(
+ D90AdisTSWindow,
+ data=[self._study, None, d90_default]
+ ):
+ return
+
+ D90AdisTS = D90AdisTSWindow(
+ study=self._study,
+ parent=self,
+ data=d90_default
+ )
+ D90AdisTS.show()
+
def open_pollutants(self):
if self.sub_window_exists(
PollutantsWindow,
diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui
index 842e220a..c2bda3eb 100644
--- a/src/View/ui/MainWindow.ui
+++ b/src/View/ui/MainWindow.ui
@@ -220,6 +220,7 @@
+
@@ -767,6 +768,11 @@
D90
+
+
+ DIF
+
+