diff --git a/src/Model/InitialConditions/InitialConditions.py b/src/Model/InitialConditions/InitialConditions.py
new file mode 100644
index 00000000..3b89089f
--- /dev/null
+++ b/src/Model/InitialConditions/InitialConditions.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+class Data(object):
+ def __init__(self, status = None):
+ super(Data, self).__init()
+
+ self._status = status
+
+ self._name = ""
+ self._comment = ""
+
+ self._kp = 0.0
+ self._flow = 0.0
+ self._cote = 0.0
+ self._tiran = 0.0
+
+ def __getitems__(self, key):
+ val = None
+
+ if key == "name":
+ val = self._name
+ elif key == "comment":
+ val = self._comment
+ elif key == "kp":
+ val = self._kp
+ elif key == "flow":
+ val = self._flow
+ elif key == "cote":
+ val = self._cote
+ elif key == "tiran":
+ val = self._tiran
+
+ return val
+
+ def __setitems__(self, key, value):
+ if key == "name":
+ self._name = str(value)
+ elif key == "comment":
+ self._comment = str(value)
+ elif key == "kp":
+ self._kp = float(value)
+ elif key == "flow":
+ self._flow = float(value)
+ elif key == "cote":
+ self._cote = float(value)
+ elif key == "tiran":
+ self._tiran = float(value)
+
+ self._status.modified()
+
+class InitialConditions(object):
+ def __init__(self, reach = None, status = None):
+ super(InitialConditions, self).__init__()
+
+ self._status = status
+
+ self._reach = reach
+ self._data = []
+
+ def __len__(self):
+ return len(self._data)
+
+ @property
+ def reach(self):
+ return self._reach
+
+ @reach.setter
+ def reach(self, new):
+ self._reach = reach
+ self._status.modified()
+
+ @property
+ def data(self):
+ return self._data.copy()
+
+ def get(self, index):
+ return self._data[index]
+
+ def set(self, index, data):
+ self._data.insert(index, data)
+ self._status.modified()
+
+ def new(self, index):
+ n = Data(self._status)
+ self._data.insert(index, n)
+ self._status.modified()
+
+ def insert(self, index, data):
+ self._data.insert(index, data)
+ self._status.modified()
+
+ def delete(self, data):
+ self._data = list(
+ filter(
+ lambda x: x in data,
+ self._data
+ )
+ )
+ self._status.modified()
+
+ def delete_i(self, indexes):
+ data = list(
+ map(
+ lambda x: x[1],
+ filter(
+ lambda x: x[0] in indexes,
+ enumerate(self._data)
+ )
+ )
+ )
+ self.delete(data)
+
+ def sort(self, reverse=False, key=None):
+ self._data.sort(reverse=reverse, key=key)
+ self._status.modified()
diff --git a/src/Model/InitialConditions/InitialConditionsDict.py b/src/Model/InitialConditions/InitialConditionsDict.py
new file mode 100644
index 00000000..dedabac2
--- /dev/null
+++ b/src/Model/InitialConditions/InitialConditionsDict.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.InitialConditions.InitialConditions import InitialConditions
+
+class InitialConditionsDict(object):
+ def __init__(self, status = None):
+ super(InitialConditionsDict, self).__init__()
+
+ self._status = status
+
+ self._reach = {}
+
+ def __len__(self):
+ return len(self._reach)
+
+ def is_defined(self, reach):
+ return reach in self._reach
+
+ def get(self, reach):
+ if reach in self._reach:
+ return self._reach[reach]
+
+ new = self.new(reach)
+ self.set(reach, new)
+ return new
+
+ def set(self, reach, new):
+ self._reach[reach] = new
+ self._status.modified()
+
+ def new(self, reach):
+ new = InitialConditions(reach = reach, status = self._status)
+ self.set(reach, new)
diff --git a/src/Model/River.py b/src/Model/River.py
index 7011d73e..cc87c75f 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -9,6 +9,7 @@ from Model.Geometry.Reach import Reach
from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList
from Model.LateralContribution.LateralContributionList import LateralContributionList
+from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
from Model.Stricklers.StricklersList import StricklersList
from Model.Section.SectionList import SectionList
@@ -62,6 +63,7 @@ class River(Graph):
self._current_reach = None
self._boundary_condition = BoundaryConditionList(status=self._status)
self._lateral_contribution = LateralContributionList(status=self._status)
+ self._initial_conditions = InitialConditionsDict(status=self._status)
self._stricklers = StricklersList(status=self._status)
self._sections = SectionList(status=self._status)
@@ -73,6 +75,10 @@ class River(Graph):
def lateral_contribution(self):
return self._lateral_contribution
+ @property
+ def initial_conditions(self):
+ return self._initial_conditions
+
@property
def stricklers(self):
return self._stricklers
diff --git a/src/View/InitialConditions/Table.py b/src/View/InitialConditions/Table.py
new file mode 100644
index 00000000..525a898b
--- /dev/null
+++ b/src/View/InitialConditions/Table.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+
+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.InitialConditions.UndoCommand import (
+ SetCommand, AddCommand, DelCommand,
+ SortCommand, MoveCommand, PasteCommand,
+ DuplicateCommand,
+)
+
+from View.InitialConditions.translate import *
+
+_translate = QCoreApplication.translate
+
+class TableModel(QAbstractTableModel):
+ def __init__(self, river=None, reach=None, undo=None):
+ super(QAbstractTableModel, self).__init__()
+ self._headers = list(table_headers.keys())
+ self._river = river
+ self._reach = reach
+ self._undo = undo
+ self._ics = self._river.initial_conditions.get(reach)
+
+ def flags(self, index):
+ options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
+ options |= Qt.ItemIsEditable
+
+ return options
+
+ def rowCount(self, parent):
+ return len(self._ics)
+
+ def columnCount(self, parent):
+ return len(self._headers)
+
+ def data(self, index, role):
+ if role != Qt.ItemDataRole.DisplayRole:
+ return QVariant()
+
+ row = index.row()
+ column = index.column()
+
+ if self._headers[column] is not None:
+ return self._ics.get(row)[self._headers[column]]
+
+ return QVariant()
+
+ def headerData(self, section, orientation, role):
+ if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
+ return table_headers[self._headers[section]]
+
+ 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()
+
+ if self._headers[column] is not None:
+ self._undo.push(
+ SetCommand(
+ self._ics, row, self._headers[column], value
+ )
+ )
+
+ self.dataChanged.emit(index, index)
+ return True
+
+ def add(self, row, parent=QModelIndex()):
+ self.beginInsertRows(parent, row, row - 1)
+
+ self._undo.push(
+ AddCommand(
+ self._ics, row
+ )
+ )
+
+ self.endInsertRows()
+ self.layoutChanged.emit()
+
+ def delete(self, rows, parent=QModelIndex()):
+ self.beginRemoveRows(parent, rows[0], rows[-1])
+
+ self._undo.push(
+ DelCommand(
+ self._ics, rows
+ )
+ )
+
+ self.endRemoveRows()
+ self.layoutChanged.emit()
+
+ def sort(self, _reverse, parent=QModelIndex()):
+ self.layoutAboutToBeChanged.emit()
+
+ self._undo.push(
+ SortCommand(
+ self._ics, False
+ )
+ )
+
+ self.layoutAboutToBeChanged.emit()
+ self.layoutChanged.emit()
+
+ def move_up(self, row, parent=QModelIndex()):
+ if row <= 0:
+ return
+
+ target = row + 2
+
+ self.beginMoveRows(parent, row - 1, row - 1, parent, target)
+
+ self._undo_stack.push(
+ MoveCommand(
+ self._ics, "up", row
+ )
+ )
+
+ self.endMoveRows()
+ self.layoutChanged.emit()
+
+ def move_down(self, index, parent=QModelIndex()):
+ if row > len(self._ics):
+ return
+
+ target = row
+
+ self.beginMoveRows(parent, row + 1, row + 1, parent, target)
+
+ self._undo_stack.push(
+ MoveCommand(
+ self._ics, "down", row
+ )
+ )
+
+ self.endMoveRows()
+ self.layoutChanged.emit()
+
+ def undo(self):
+ self._undo.undo()
+ self.layoutChanged.emit()
+
+ def redo(self):
+ self._undo.redo()
+ self.layoutChanged.emit()
diff --git a/src/View/InitialConditions/UndoCommand.py b/src/View/InitialConditions/UndoCommand.py
new file mode 100644
index 00000000..32bff726
--- /dev/null
+++ b/src/View/InitialConditions/UndoCommand.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+ QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.InitialConditions.InitialConditions import InitialConditions
+from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
+
+class SetCommand(QUndoCommand):
+ def __init__(self, ics, row, column, new_value):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._row = row
+ self._old = self._ics.get(self._row)[column]
+ self._new = new_value
+
+ def undo(self):
+ self._ics.get(self._row)[column] = self._old
+
+ def redo(self):
+ self._ics.get(self._row)[column] = self._new
+
+class AddCommand(QUndoCommand):
+ def __init__(self, ics, index):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._index = index
+ self._new = None
+
+ def undo(self):
+ self._ics.delete_i([self._index])
+
+ def redo(self):
+ if self._new is None:
+ self._new = self._ics.new(self._index)
+ else:
+ self._ics.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+ def __init__(self, ics, tab, rows):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._rows = rows
+
+ self._ic = []
+ for row in rows:
+ self._ic.append((row, self._ics.get(row)))
+ self._ic.sort()
+
+ def undo(self):
+ for row, el in self._ic:
+ self._ics.insert(row, el)
+
+ def redo(self):
+ self._ics.delete_i(self._rows)
+
+class SortCommand(QUndoCommand):
+ def __init__(self, ics, tab, _reverse):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._reverse = _reverse
+
+ self._old = self._ics.data
+ self._indexes = None
+
+ def undo(self):
+ ll = self._ics.data
+ self._ics.sort(
+ key=lambda x: self._indexes[ll.index(x)]
+ )
+
+ def redo(self):
+ self._ics.sort(
+ reverse=self._reverse,
+ key=lambda x: x.name
+ )
+ if self._indexes is None:
+ self._indexes = list(
+ map(
+ lambda p: self._old.index(p),
+ self._ics.data
+ )
+ )
+ self._old = None
+
+
+class MoveCommand(QUndoCommand):
+ def __init__(self, ics, tab, up, i):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._up = up == "up"
+ self._i = i
+
+ def undo(self):
+ if self._up:
+ self._ics.move_up(self._i)
+ else:
+ self._ics.move_down(self._i)
+
+ def redo(self):
+ if self._up:
+ self._ics.move_up(self._i)
+ else:
+ self._ics.move_down(self._i)
+
+
+class PasteCommand(QUndoCommand):
+ def __init__(self, ics, tab, row, ic):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._row = row
+ self._ic = deepcopy(ic)
+ self._ic.reverse()
+
+ def undo(self):
+ self._ics.delete(self._ic)
+
+ def redo(self):
+ for ic in self._ic:
+ self._ics.insert(self._row, ic)
+
+
+class DuplicateCommand(QUndoCommand):
+ def __init__(self, ics, tab, rows, ic):
+ QUndoCommand.__init__(self)
+
+ self._ics = ics
+ self._rows = rows
+ self._ic = deepcopy(ic)
+ self._ic.reverse()
+
+ def undo(self):
+ self._ics.delete(self._ic)
+
+ def redo(self):
+ for ic in self._ics:
+ self._ics.insert(self._rows[0], ic)
diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py
new file mode 100644
index 00000000..dbf22ec4
--- /dev/null
+++ b/src/View/InitialConditions/Window.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from View.ASubWindow import ASubMainWindow
+from View.ListedSubWindow import ListedSubWindow
+
+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.InitialConditions.UndoCommand import (
+ SetCommand, AddCommand, DelCommand,
+ SortCommand, MoveCommand, PasteCommand,
+ DuplicateCommand,
+)
+
+from View.InitialConditions.Table import TableModel
+
+from View.Plot.MplCanvas import MplCanvas
+from View.InitialConditions.translate import *
+
+_translate = QCoreApplication.translate
+
+
+class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
+ def __init__(self, title="Initial condition",
+ study=None, parent=None):
+ title = title + " - " + study.name
+
+ super(InitialConditionsWindow, self).__init__(
+ name=title, ui="InitialConditions", parent=parent
+ )
+
+ self._study = study
+ self._reach = study.river.current_reach()
+ self._ics = self._study.river.initial_conditions.get(self._reach)
+
+ self.setup_sc()
+ self.setup_table()
+ self.setup_graph()
+ self.setup_connections()
+
+ self.ui.setWindowTitle(title)
+
+ def setup_sc(self):
+ self._undo_stack = QUndoStack()
+
+ self.undo_sc = QShortcut(QKeySequence.Undo, self)
+ self.redo_sc = QShortcut(QKeySequence.Redo, self)
+ self.copy_sc = QShortcut(QKeySequence.Copy, self)
+ self.paste_sc = QShortcut(QKeySequence.Paste, self)
+
+ def setup_table(self):
+ table = self.find(QTableView, f"tableView")
+ self._table = TableModel(
+ river = self._study.river,
+ reach = self._reach,
+ undo = self._undo_stack,
+ )
+ table.setModel(self._table)
+
+ table.setSelectionBehavior(QAbstractItemView.SelectRows)
+ table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ table.setAlternatingRowColors(True)
+
+ def setup_graph(self):
+ print("TODO")
+ # self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
+ # self.canvas_1.setObjectName("canvas_1")
+ # self.plot_layout_1 = self.find(QVBoxLayout, "verticalLayout_1")
+ # self.plot_layout_1.addWidget(self.canvas_1)
+
+ # self.plot = PlotXY(
+ # canvas = self.canvas_1,
+ # data = None,
+ # toolbar = None,
+ # )
+
+ # self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
+ # self.canvas_2.setObjectName("canvas_2")
+ # self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
+ # self.plot_layout_2.addWidget(self.canvas_2)
+
+ # self.plot = PlotXY(
+ # canvas = self.canvas_2,
+ # data = None,
+ # toolbar = None,
+ # )
+
+ def setup_connections(self):
+ self.find(QAction, "action_add").triggered.connect(self.add)
+ self.find(QAction, "action_del").triggered.connect(self.delete)
+ self.find(QAction, "action_sort").triggered.connect(self.sort)
+
+ self.undo_sc.activated.connect(self.undo)
+ self.redo_sc.activated.connect(self.redo)
+ self.copy_sc.activated.connect(self.copy)
+ self.paste_sc.activated.connect(self.paste)
+
+ def index_selected_row(self):
+ table = self.find(QTableView, f"tableView")
+ return table.selectionModel()\
+ .selectedRows()[0]\
+ .row()
+
+ def index_selected_rows(self):
+ table = self.find(QTableView, f"tableView")
+ return list(
+ # Delete duplicate
+ set(
+ map(
+ lambda i: i.row(),
+ table.selectedIndexes()
+ )
+ )
+ )
+
+ def add(self):
+ rows = self.index_selected_rows()
+ if self._ics.len(tab) == 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 sort(self):
+ self._table.sort(False)
+
+ def move_up(self):
+ row = self.index_selected_row()
+ self._table.move_up(row)
+
+ def move_down(self):
+ row = self.index_selected_row()
+ self._table.move_down(row)
+
+ def copy(self):
+ print("TODO")
+
+ def paste(self):
+ print("TODO")
+
+ def undo(self):
+ self._table.undo()
+
+ def redo(self):
+ self._table.redo()
diff --git a/src/View/InitialConditions/translate.py b/src/View/InitialConditions/translate.py
new file mode 100644
index 00000000..ea88dff3
--- /dev/null
+++ b/src/View/InitialConditions/translate.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+_translate = QCoreApplication.translate
+
+table_headers = {
+ "name": _translate("LateralContribution", "Name"),
+ "comment": _translate("LateralContribution", "Comment"),
+ "kp": _translate("LateralContribution", "KP (m)"),
+ "flow": _translate("LateralContribution", "Flow (m³/s)"),
+ "cote": _translate("LateralContribution", "Cote (m)"),
+ "tiran": _translate("LateralContribution", "Tiran (m)")
+}
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index 5c703530..bb0f0ce7 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -24,6 +24,7 @@ from View.Network.Window import NetworkWindow
from View.Geometry.Window import GeometryWindow
from View.BoundaryCondition.Window import BoundaryConditionWindow
from View.LateralContribution.Window import LateralContributionWindow
+from View.InitialConditions.Window import InitialConditionsWindow
from View.Stricklers.Window import StricklersWindow
from View.Sections.Window import SectionsWindow
@@ -49,6 +50,7 @@ define_model_action = [
"action_toolBar_boundary_cond", "action_toolBar_lateral_contrib",
"action_toolBar_spills", "action_toolBar_sections",
"action_toolBar_stricklers", "action_toolBar_building",
+ "action_toolBar_initial_cond",
]
action = (
@@ -130,6 +132,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_toolBar_stricklers": self.open_stricklers,
"action_toolBar_sections": self.open_sections,
"action_toolBar_building": lambda: self.open_dummy("Ouvrages"),
+ "action_toolBar_initial_cond": self.open_initial_conditions,
}
for action in actions:
@@ -328,28 +331,40 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"into river network window to work on it")
def open_boundary_cond(self):
- self.bound = BoundaryConditionWindow(study = self.model, parent=self)
+ self.bound = BoundaryConditionWindow(study = self.model, parent = self)
self.bound.show()
def open_lateral_contrib(self):
- self.lateral = LateralContributionWindow(study = self.model, parent=self)
+ self.lateral = LateralContributionWindow(study = self.model, parent = self)
self.lateral.show()
def open_stricklers(self):
self.strick = StricklersWindow(
study = self.model,
config = self.conf,
- parent=self
+ parent = self
)
self.strick.show()
def open_sections(self):
self.sections = SectionsWindow(
study = self.model,
- parent=self
+ parent = self
)
self.sections.show()
+ def open_initial_conditions(self):
+ print("xxx")
+ if self.model.river.has_current_reach():
+ print("yyy")
+ self.initial = InitialConditionsWindow(
+ study = self.model,
+ parent = self
+ )
+ self.initial.show()
+ print("zzz")
+
+
# TODO: Delete me !
###############
# DUMMY STUFF #
diff --git a/src/View/ui/InitialConditions.ui b/src/View/ui/InitialConditions.ui
new file mode 100644
index 00000000..087dbe02
--- /dev/null
+++ b/src/View/ui/InitialConditions.ui
@@ -0,0 +1,127 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 889
+ 480
+
+
+
+ MainWindow
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
-
+
+
-
+
+
+ Generate 1
+
+
+
+ -
+
+
+ Generate 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ toolBar
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+ ressources/gtk-add.pngressources/gtk-add.png
+
+
+ Add
+
+
+ Add new initial condition
+
+
+
+
+
+ ressources/gtk-remove.pngressources/gtk-remove.png
+
+
+ delete
+
+
+ Delete inital condition
+
+
+
+
+
+ ressources/gtk-sort-ascending.pngressources/gtk-sort-ascending.png
+
+
+ sort
+
+
+ Sort inital condition
+
+
+
+
+
+
diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui
index 0c77e22f..fa4be386 100644
--- a/src/View/ui/MainWindow.ui
+++ b/src/View/ui/MainWindow.ui
@@ -302,6 +302,7 @@
+
@@ -861,6 +862,11 @@
French
+
+
+ Initial conditions
+
+