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>