diff --git a/src/Model/River.py b/src/Model/River.py index 0bb8818a785d6eb997407fcfcbf25c8daac29a89..35bf7ab711b529c300199bbcee665014b1790693 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -12,6 +12,7 @@ from Model.LateralContribution.LateralContributionList import LateralContributio from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict from Model.Stricklers.StricklersList import StricklersList from Model.Section.SectionList import SectionList +from Model.SolverParameters.SolverParametersList import SolverParametersList class RiverNode(Node): def __init__(self, id:str, name:str, @@ -69,6 +70,7 @@ class River(Graph): self._lateral_contribution = LateralContributionList(status=self._status) self._initial_conditions = InitialConditionsDict(status=self._status) self._stricklers = StricklersList(status=self._status) + self._parameters = {} @property def boundary_condition(self): @@ -99,6 +101,20 @@ class River(Graph): return ret[0] + + @property + def parameters(self): + return self._parameters + + def get_params(self, solver): + if solver in self._parameters: + return self._parameters[solver] + + new = SolverParametersList(status = self._status) + self._parameters[solver] = new + self._status.modified() + return self._parameters[solver] + def has_current_reach(self): return self._current_reach is not None diff --git a/src/Model/SolverParameters/SolverParametersList.py b/src/Model/SolverParameters/SolverParametersList.py new file mode 100644 index 0000000000000000000000000000000000000000..0d656d0dc71a03176a0dad2c1c0bc509c54b9d68 --- /dev/null +++ b/src/Model/SolverParameters/SolverParametersList.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +from copy import copy +from tools import trace, timer + +class Parameter(object): + def __init__(self, status = None): + self._status = status + + self._name = "" + self._value = "" + + @property + def name(self): + return self._name + + @property + def value(self): + return self._value + + def __getitem__(self, key): + if key == "name": + return self._name + elif key == "value": + return self._value + + return None + + def __setitem__(self, key, value): + if key == "name": + self._name = str(value) + elif key == "value": + self._value = str(value) + + self._status.modified() + + +class SolverParametersList(object): + def __init__(self, status = None): + super(SolverParametersList, self).__init__() + + self._status = status + self._parameters = [] + + def __len__(self): + return len(self._parameters) + + @property + def parameters(self): + return self._parameters.copy() + + def get(self, index): + return self._parameters[index] + + def set(self, index, new): + self._parameters[index] = new + self._status.modified() + + def new(self, index): + n = Parameter(status = self._status) + self._parameters.insert(index, n) + self._status.modified() + return n + + def insert(self, index, new): + self._parameters.insert(index, new) + self._status.modified() + + def delete(self, parameters): + for parameter in parameters: + self._parameters.remove(parameter) + self._status.modified() + + def delete_i(self, indexes): + parameters = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] in indexes, + enumerate(self._parameters) + ) + ) + ) + self.delete(parameters) + + def sort(self, reverse=False, key=None): + self._parameters.sort(reverse=reverse, key=key) + self._status.modified() + + def move_up(self, index): + if index < len(self._parameters): + next = index - 1 + + l = self._parameters + l[index], l[next] = l[next], l[index] + self._status.modified() + + def move_down(self, index): + if index >= 0: + prev = index + 1 + + l = self._parameters + l[index], l[prev] = l[prev], l[index] + self._status.modified() diff --git a/src/Model/Study.py b/src/Model/Study.py index 7d04b9a1e82f5d720495a60cf8fdb86023cf407a..3ce31c43bf4a07151ec509f4777a4f17648cf2e3 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -29,7 +29,11 @@ class Study(Serializable): self.last_save_date = datetime.now() # Study data - self.river = River(status=self.status) + self._river = River(status = self.status) + + @property + def river(self): + return self._river @property def is_saved(self): diff --git a/src/Solver/ASolver.py b/src/Solver/ASolver.py index ecdecd2564c94310c16ce74d332370023d3da8d1..478e49a6bb04a5cb5ac6c8d72cd2ebd9d81842b7 100644 --- a/src/Solver/ASolver.py +++ b/src/Solver/ASolver.py @@ -14,81 +14,84 @@ class AbstractSolver(object): def __init__(self, name): super(AbstractSolver, self).__init__() - self.current_process = None - self.status = STATUS.STOPED + self._current_process = None + self._status = STATUS.STOPED # Informations self._type = "" - self.name = name - self.description = "" + self._name = name + self._description = "" - self.path_input = "" - self.path_solver = "" - self.path_output = "" + self._path_input = "" + self._path_solver = "" + self._path_output = "" - self.cmd_input = "" - self.cmd_solver = "" - self.cmd_output = "" + self._cmd_input = "" + self._cmd_solver = "" + self._cmd_output = "" def __str__(self): - return f"{self.name} : {self._type} : {self.description}" + return f"{self._name} : {self._type} : {self._description}" - # Getter - def get_status(self): - return self.status + @classmethod + def default_parameters(cls): + return [] - def get_type(self): - return self._type + @property + def name(self): + return self.name - def __getitem__(self, name): - ret = None + @property + def description(self): + return self.description - if name == "name": - ret = self.name - elif name == "description": - ret = self.description - elif name == "type": - ret = self._type + @property + def status(self): + return self._status - return ret + @property + def type(self): + return self._type - # Setter - def set_status(self, status): - self.status = status + @status.setter + def status(self, status): + self._status = status - def set_name(self, name): - self.name = name + @name.setter + def name(self, name): + self._name = name - def set_description(self, description): - self.description = description + @description.setter + def description(self, description): + self._description = description def set_input(self, path, cmd): - self.path_input = path - self.cmd_input = cmd + self._path_input = path + self._cmd_input = cmd def set_solver(self, path, cmd): - self.path_solver = path - self.cmd_solver = cmd + self._path_solver = path + self._cmd_solver = cmd def set_output(self, path, cmd): - self.path_output = path - self.cmd_output = cmd + self._path_output = path + self._cmd_output = cmd # Run def run_input_data_fomater(self): - if self.cmd_input == "": + if self._cmd_input == "": return True return False def run_solver(self): - if self.cmd_solver == "": + if self._cmd_solver == "": return True return False def run_output_data_fomater(self, ): - if self.cmd_output == "": + if self._cmd_output == "": return True return False diff --git a/src/Solver/GenericSolver.py b/src/Solver/GenericSolver.py index 03e53daaedb1e39a99035b89ef38b81dfe4c4c79..53bc6f0d3c446878a05950a3459c0ffed4c18048 100644 --- a/src/Solver/GenericSolver.py +++ b/src/Solver/GenericSolver.py @@ -9,3 +9,9 @@ class GenericSolver(AbstractSolver): super(GenericSolver, self).__init__(name) self._type = "generic" + + @classmethod + def default_parameters(cls): + lst = super(Mage, cls).default_parameters() + + return lst diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py new file mode 100644 index 0000000000000000000000000000000000000000..61d8a316eba5eebc1df8b937cba019a5021e0701 --- /dev/null +++ b/src/Solver/Mage.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from Solver.GenericSolver import GenericSolver + +class Mage(GenericSolver): + def __init__(self, name): + super(Mage, self).__init__(name) + + self._type = "mage" + + self._cmd_input = "" + self._cmd_solver = "@path @input -o @output" + self._cmd_output = "" + + @classmethod + def default_parameters(cls): + lst = super(Mage, cls).default_parameters() + + lst += [ + ("time_step", "300"), + ] + + return lst + + +class Mage7(Mage): + def __init__(self, name): + super(Mage7, self).__init__(name) + + self._type = "mage7" + + @classmethod + def default_parameters(cls): + lst = super(Mage7, cls).default_parameters() + return lst + + + +class Mage8(Mage): + def __init__(self, name): + super(Mage8, self).__init__(name) + + self._type = "mage8" + + @classmethod + def default_parameters(cls): + lst = super(Mage8, cls).default_parameters() + return lst diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 3f698978805eb22f306a579074d34632297f9aa3..2c1cb1c2e80c2e5c9130217332bc3c6a084825f7 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -27,6 +27,7 @@ from View.LateralContribution.Window import LateralContributionWindow from View.InitialConditions.Window import InitialConditionsWindow from View.Stricklers.Window import StricklersWindow from View.Sections.Window import SectionsWindow +from View.SolverParameters.Window import SolverParametersWindow from Model.Study import Study @@ -125,7 +126,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_toolBar_network": self.open_network, "action_toolBar_geometry": self.open_geometry, "action_toolBar_mesh": lambda: self.open_dummy("Mesh"), - "action_toolBar_run_meshing_tool": lambda: self.open_dummy("Lancement mailleur externe"), + "action_toolBar_run_meshing_tool": self.open_solver_parameters, "action_toolBar_boundary_cond": self.open_boundary_cond, "action_toolBar_lateral_contrib": self.open_lateral_contrib, "action_toolBar_spills": lambda: self.open_dummy("Deversement"), @@ -361,6 +362,14 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) self.initial.show() + def open_solver_parameters(self): + self.params = SolverParametersWindow( + study = self.model, + parent = self + ) + self.params.show() + + # TODO: Delete me ! ############### # DUMMY STUFF # diff --git a/src/View/SolverParameters/Table.py b/src/View/SolverParameters/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..b46281318d558ebfc7a7e149ba7fd7d5e968cc84 --- /dev/null +++ b/src/View/SolverParameters/Table.py @@ -0,0 +1,157 @@ +# -*- 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.SolverParameters.UndoCommand import * +from View.SolverParameters.translate import * + +_translate = QCoreApplication.translate + +class TableModel(QAbstractTableModel): + def __init__(self, data=None, undo=None, tab=""): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._data = data + self._undo = undo + self._tab = tab + self._params = self._data.get_params(self._tab) + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + + if self._headers[self._column] == "value": + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._params) + + 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 0 <= column < len(self._headers): + return self._params.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] == "value": + self._undo.push( + SetCommand( + self._params, row, "value", 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._params, row + # ) + # ) + + # self.endInsertRows() + # self.layoutChanged.emit() + + # def delete(self, rows, parent=QModelIndex()): + # self.beginRemoveRows(parent, rows[0], rows[-1]) + + # self._undo.push( + # DelCommand( + # self._params, rows + # ) + # ) + + # self.endRemoveRows() + # self.layoutChanged.emit() + + # def sort(self, _reverse, parent=QModelIndex()): + # self.layoutAboutToBeChanged.emit() + + # self._undo.push( + # SortCommand( + # self._params, 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._params, "up", row + # ) + # ) + + # self.endMoveRows() + # self.layoutChanged.emit() + + # def move_down(self, index, parent=QModelIndex()): + # if row > len(self._params): + # return + + # target = row + + # self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + # self._undo_stack.push( + # MoveCommand( + # self._params, "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/SolverParameters/UndoCommand.py b/src/View/SolverParameters/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..d5d2f3a3ee5b11120bdcdf5e3bc2401fa58a2678 --- /dev/null +++ b/src/View/SolverParameters/UndoCommand.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.SolverParameters.SolverParametersList import SolverParametersList + +class SetCommand(QUndoCommand): + def __init__(self, data, index, column, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._column = column + self._old = self._data.get(self._index)[column] + self._new = new_value + + def undo(self): + self._data.get(self._index)[column] = self._old + + def redo(self): + self._data.get(self._index)[column] = self._new diff --git a/src/View/SolverParameters/Window.py b/src/View/SolverParameters/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..c98e3e1a3cb6700f1be3aa5f47f18013c0299550 --- /dev/null +++ b/src/View/SolverParameters/Window.py @@ -0,0 +1,99 @@ +# -*- 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.SolverParameters.UndoCommand import * +from View.SolverParameters.Table import TableModel +from View.SolverParameters.translate import * + +_translate = QCoreApplication.translate + + +class SolverParametersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Solver parameters", study=None, parent=None): + title = title + " - " + study.name + + super(SolverParametersWindow, self).__init__( + name=title, ui="SolverParameters", parent=parent + ) + + self._study = study + self._params = self._study.river.parameters + + self.setup_sc() + self.setup_table() + 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): + self._table = {} + + for t in ["mage"]: + table = self.find(QTableView, f"tableView_{t}") + self._table[t] = TableModel( + data = self._study.river, + undo = self._undo_stack, + tab = t, + ) + table.setModel(self._table[t]) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_connections(self): + 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 current_tab(self): + return self.find(QTabWidget, "tabWidget")\ + .currentWidget()\ + .objectName()\ + .replace("tab_", "") + + def undo(self): + tab = self.current_tab() + self._table[tab].undo() + self._set_current_reach() + + def redo(self): + tab = self.current_tab() + self._table[tab].redo() + self._set_current_reach() + + def copy(self): + print("TODO") + + def paste(self): + print("TODO") diff --git a/src/View/SolverParameters/translate.py b/src/View/SolverParameters/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..3103dcb99a612723efa99f5ac6b98a3b9cba0a37 --- /dev/null +++ b/src/View/SolverParameters/translate.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("LateralContribution", "Name"), + "value": _translate("LateralContribution", "Value") +} + +# Used to translate user parameter with value yes or no +yes_no = { + "yes": _translate("SolverParameters", "Yes"), + "no": _translate("SolverParameters", "No"), + "y": _translate("SolverParameters", "Y"), + "n": _translate("SolverParameters", "N"), + # Reverse + _translate("SolverParameters", "Yes"): "Yes", + _translate("SolverParameters", "No"): "No", + _translate("SolverParameters", "Y"): "y", + _translate("SolverParameters", "N"): "n", + +} + +names = { + "mage_time_step": _translate("SolverParameters", "Time step in second") +} diff --git a/src/View/ui/ConfigureAddSolverDialog.ui b/src/View/ui/ConfigureAddSolverDialog.ui index 7851401352cc43630adf2bf1b1a21efaaaec2466..c621957716514de581bc6ff9ef65b519c4377224 100644 --- a/src/View/ui/ConfigureAddSolverDialog.ui +++ b/src/View/ui/ConfigureAddSolverDialog.ui @@ -113,6 +113,9 @@ </item> <item row="2" column="0"> <widget class="QLabel" name="label_4"> + <property name="locale"> + <locale language="English" country="Europe"/> + </property> <property name="text"> <string>Input formater</string> </property> diff --git a/src/View/ui/SolverParameters.ui b/src/View/ui/SolverParameters.ui new file mode 100644 index 0000000000000000000000000000000000000000..abfb658d6b59deb486f831d2a2048c9b2fd1572d --- /dev/null +++ b/src/View/ui/SolverParameters.ui @@ -0,0 +1,68 @@ +<?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>900</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <property name="locale"> + <locale language="English" country="Europe"/> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab_mage"> + <attribute name="title"> + <string>Mage</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QTableView" name="tableView_mage"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_rubarbe"> + <attribute name="title"> + <string>Rubarbe</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Not implemented yet !</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>900</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <resources/> + <connections/> +</ui>