diff --git a/src/View/InitialConditionsAdisTS/DialogDischarge.py b/src/View/InitialConditionsAdisTS/DialogDischarge.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b63bdabd70c5285cc12ceddef3c5c98861d7c06
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/DialogDischarge.py
@@ -0,0 +1,54 @@
+# DialogDischarge.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from View.Tools.PamhyrWindow import PamhyrDialog
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QComboBox, QUndoStack, QShortcut,
+    QDoubleSpinBox,
+)
+
+
+class DischargeDialog(PamhyrDialog):
+    _pamhyr_ui = "InitialConditions_Dialog_Generator_Discharge"
+    _pamhyr_name = "Discharge"
+
+    def __init__(self, title="Discharge", trad=None, parent=None):
+        super(DischargeDialog, self).__init__(
+            title=trad[self._pamhyr_name],
+            options=[],
+            trad=trad,
+            parent=parent
+        )
+
+        self.value = None
+
+    def accept(self):
+        self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value()
+        super().accept()
+
+    def reject(self):
+        self.close()
diff --git a/src/View/InitialConditionsAdisTS/DialogHeight.py b/src/View/InitialConditionsAdisTS/DialogHeight.py
new file mode 100644
index 0000000000000000000000000000000000000000..408212324c0f47116c97795436f2fe97812d1954
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/DialogHeight.py
@@ -0,0 +1,54 @@
+# DialogHeight.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from View.Tools.PamhyrWindow import PamhyrDialog
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QComboBox, QUndoStack, QShortcut,
+    QDoubleSpinBox,
+)
+
+
+class HeightDialog(PamhyrDialog):
+    _pamhyr_ui = "InitialConditions_Dialog_Generator_Height"
+    _pamhyr_name = "Height"
+
+    def __init__(self, trad=None, parent=None):
+        super(HeightDialog, self).__init__(
+            title=trad[self._pamhyr_name],
+            options=[],
+            trad=trad,
+            parent=parent
+        )
+
+        self.value = None
+
+    def accept(self):
+        self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value()
+        super().accept()
+
+    def reject(self):
+        self.close()
diff --git a/src/View/InitialConditionsAdisTS/PlotDKP.py b/src/View/InitialConditionsAdisTS/PlotDKP.py
new file mode 100644
index 0000000000000000000000000000000000000000..d29356fa01fc1d0b2d9480db5a113189287c078b
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/PlotDKP.py
@@ -0,0 +1,107 @@
+# PlotDKP.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import timer
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class PlotDKP(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 data=None, parent=None):
+        super(PlotDKP, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=data,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self.label_x = self._trad["kp"]
+        self.label_y = self._trad["elevation"]
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = True
+        self._autoscale_update = True
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.data is None:
+            return
+
+        self.draw_river_bottom()
+        self.draw_water()
+
+        self.idle()
+        self._init = True
+
+    def draw_river_bottom(self):
+        kp = self.data.reach.reach.get_kp()
+        z_min = self.data.reach.reach.get_z_min()
+
+        self.line_kp_zmin = self.canvas.axes.plot(
+            kp, z_min,
+            color=self.color_plot_river_bottom,
+            lw=1.
+        )
+
+    def draw_water(self):
+        if len(self.data) != 0:
+            kp = self.data.get_kp()
+            elevation = self.data.get_elevation()
+
+            self.line_kp_elevation = self.canvas.axes.plot(
+                kp, elevation,
+                color=self.color_plot_river_water,
+                **self.plot_default_kargs
+            )
+
+            z_min = self.data.reach.reach.get_z_min()
+            geometry_kp = self.data.reach.reach.get_kp()
+
+            filtred_elevation = list(
+                map(
+                    lambda x: elevation[x[0]],
+                    filter(
+                        lambda x: x[1] in geometry_kp,
+                        enumerate(kp)
+                    )
+                )
+            )
+
+            self.collection = self.canvas.axes.fill_between(
+                geometry_kp, z_min, filtred_elevation,
+                color=self.color_plot_river_water_zone,
+                alpha=0.7, interpolate=True
+            )
+
+    @timer
+    def update(self, ind=None):
+        self.draw()
diff --git a/src/View/InitialConditionsAdisTS/PlotDischarge.py b/src/View/InitialConditionsAdisTS/PlotDischarge.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c43c1e557815071c4908de1de08783b1034cc91
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/PlotDischarge.py
@@ -0,0 +1,69 @@
+# PlotDischarge.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import timer
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+
+class PlotDischarge(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 data=None, parent=None):
+        super(PlotDischarge, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=data,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self.label_x = self._trad["kp"]
+        self.label_y = self._trad["discharge"]
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = True
+        self._autoscale_update = True
+
+    @timer
+    def draw(self):
+        self.init_axes()
+
+        if self.data is None:
+            return
+
+        self.draw_data()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self):
+        kp = self.data.reach.reach.get_kp()
+
+        if len(self.data) != 0:
+            kp = self.data.get_kp()
+            discharge = self.data.get_discharge()
+
+            self.line_kp_zmin = self.canvas.axes.plot(
+                kp, discharge,
+                color=self.color_plot,
+                **self.plot_default_kargs
+            )
+
+    @timer
+    def update(self, ind=None):
+        self.draw()
diff --git a/src/View/InitialConditionsAdisTS/Table.py b/src/View/InitialConditionsAdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..5461e862105ec08b3bb7cf0a9ad0e17210b246b7
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/Table.py
@@ -0,0 +1,298 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.InitialConditions.UndoCommand import (
+    SetCommand, AddCommand, DelCommand,
+    SortCommand, MoveCommand, InsertCommand,
+    DuplicateCommand, GenerateCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, reach=None, parent=None):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._reach = reach.reach
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        self.editor.addItems(
+            list(
+                map(
+                    str,
+                    self._reach.get_kp()
+                )
+            )
+        )
+
+        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 InitialConditionTableModel(PamhyrTableModel):
+    def __init__(self, reach=None, **kwargs):
+        self._reach = reach
+        super(InitialConditionTableModel, self).__init__(**kwargs)
+
+    def _setup_lst(self):
+        self._lst = self._data.river.initial_conditions.get(self._reach)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "speed":
+            z = self._lst.get(row)["elevation"]
+            q = self._lst.get(row)["discharge"]
+            profile = self._reach.reach.get_profiles_from_kp(
+                self._lst.get(row)["kp"]
+            )
+            if len(profile) >= 1:
+                speed = profile[0].speed(q, z)
+                return f"{speed:.4f}"
+
+            return ""
+        elif self._headers[column] not in ["name", "comment"]:
+            v = self._lst.get(row)[self._headers[column]]
+            return f"{v:.4f}"
+        else:
+            return self._lst.get(row)[self._headers[column]]
+
+        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._lst, 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 add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                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._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def sort(self, _reverse, parent=QModelIndex()):
+        self.layoutAboutToBeChanged.emit()
+
+        self._undo.push(
+            SortCommand(
+                self._lst, 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.push(
+            MoveCommand(
+                self._lst, "up", row
+            )
+        )
+
+        self.endMoveRows()
+        self.layoutChanged.emit()
+
+    def move_down(self, row, parent=QModelIndex()):
+        if row > len(self._lst):
+            return
+
+        target = row
+
+        self.beginMoveRows(parent, row + 1, row + 1, parent, target)
+
+        self._undo.push(
+            MoveCommand(
+                self._lst, "down", row
+            )
+        )
+
+        self.endMoveRows()
+        self.layoutChanged.emit()
+
+    def paste(self, index, header, data):
+        if len(header) != 0:
+            logger.error("Unexpected header in IC past data")
+            return
+
+        if len(data) == 0:
+            logger.error("Empty data")
+            return
+
+        if len(data[0]) != 3:
+            logger.error(f"Unexpected data size: [{data[0]}, ...]")
+            return
+
+        self.layoutAboutToBeChanged.emit()
+
+        self._undo.push(
+            InsertCommand(
+                self._lst, index,
+                list(
+                    map(
+                        lambda d: self._lst.new_from_data(*d),
+                        data
+                    )
+                )
+            )
+        )
+
+        self.layoutAboutToBeChanged.emit()
+        self.layoutChanged.emit()
+
+    def import_from_results(self, index, results):
+        if results is None:
+            logger.error("No results data")
+            return
+
+        self.layoutAboutToBeChanged.emit()
+
+        ts = max(results.get("timestamps"))
+        res_reach = results.river.get_reach_by_geometry(
+            self._reach.reach
+        )
+        data = list(
+            map(
+                lambda p: [
+                    p.geometry.kp,
+                    p.get_ts_key(ts, "Q"),
+                    p.get_ts_key(ts, "Z"),
+                ],
+                res_reach.profiles
+            )
+        )
+
+        self._undo.push(
+            InsertCommand(
+                self._lst, index,
+                list(
+                    map(
+                        lambda d: self._lst.new_from_data(*d),
+                        data
+                    )
+                )
+            )
+        )
+
+        self.layoutAboutToBeChanged.emit()
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
+    def generate(self, generator, param):
+        self._undo.push(
+            GenerateCommand(
+                self._lst, generator, param
+            )
+        )
+        self.layoutChanged.emit()
diff --git a/src/View/InitialConditionsAdisTS/UndoCommand.py b/src/View/InitialConditionsAdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..fecb6c6d4a0c2f7341047889ee424e4b61876e39
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/UndoCommand.py
@@ -0,0 +1,192 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+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._column = column
+        self._old = self._ics.get(self._row)[column]
+
+        _type = float
+        if column == "name" or column == "comment":
+            _type = str
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        self._ics.get(self._row)[self._column] = self._old
+
+    def redo(self):
+        self._ics.get(self._row)[self._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, 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, _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["kp"]
+        )
+        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, 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 InsertCommand(QUndoCommand):
+    def __init__(self, ics, 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, 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)
+
+
+class GenerateCommand(QUndoCommand):
+    def __init__(self, ics, generator, param):
+        QUndoCommand.__init__(self)
+
+        self._ics = ics
+        self._param = param
+        self._copy = self._ics.data
+        self._generator = generator
+
+    def undo(self):
+        self._ics.data = self._copy
+
+    def redo(self):
+        if self._generator == "growing":
+            self._ics.generate_growing_constante_height(self._param)
+        elif self._generator == "discharge":
+            self._ics.generate_discharge(self._param)
diff --git a/src/View/InitialConditionsAdisTS/Window.py b/src/View/InitialConditionsAdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..9562f68da0cb7dfcf0299beb13cc3593f87df2c5
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/Window.py
@@ -0,0 +1,355 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+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 Modules import Modules
+
+from View.InitialConditionsAdisTS.UndoCommand import (
+    SetCommand, AddCommand, DelCommand,
+    SortCommand, MoveCommand, InsertCommand,
+    DuplicateCommand,
+)
+
+from View.InitialConditionsAdisTS.Table import (
+    InitialConditionTableModel, ComboBoxDelegate,
+)
+
+from View.Tools.Plot.PamhyrCanvas import MplCanvas
+from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
+
+from View.InitialConditionsAdisTS.PlotDKP import PlotDKP
+from View.InitialConditionsAdisTS.PlotDischarge import PlotDischarge
+from View.InitialConditionsAdisTS.translate import ICTranslate
+from View.InitialConditionsAdisTS.DialogHeight import HeightDialog
+from View.InitialConditionsAdisTS.DialogDischarge import DischargeDialog
+from View.Results.ReadingResultsDialog import ReadingResultsDialog
+
+from Solver.Mage import Mage8
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class InitialConditionsWindow(PamhyrWindow):
+    _pamhyr_ui = "InitialConditions"
+    _pamhyr_name = "Initial condition"
+
+    def __init__(self, study=None, config=None, reach=None, parent=None):
+        trad = ICTranslate()
+
+        if reach is not None:
+            self._reach = reach
+        else:
+            self._reach = study.river.current_reach()
+
+        name = (
+            trad[self._pamhyr_name] +
+            " - " + study.name +
+            " - " + self._reach.name
+        )
+
+        super(InitialConditionsWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        # Add reach to hash computation data
+        self._hash_data.append(self._reach)
+
+        self._ics = study.river.initial_conditions.get(self._reach)
+
+        self.setup_table()
+        self.setup_plot()
+        self.setup_connections()
+
+        self.ui.setWindowTitle(self._title)
+
+    def setup_table(self):
+        table = self.find(QTableView, f"tableView")
+        self._delegate_kp = ComboBoxDelegate(
+            reach=self._reach,
+            parent=self
+        )
+
+        self._table = InitialConditionTableModel(
+            reach=self._reach,
+            table_view=table,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["kp", "discharge", "elevation", "height"],
+            delegates={"kp": self._delegate_kp},
+            data=self._study,
+            undo=self._undo_stack,
+            trad=self._trad
+        )
+
+        table.setModel(self._table)
+        table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table.setAlternatingRowColors(True)
+
+    def setup_plot(self):
+        self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_1.setObjectName("canvas_1")
+        self.toolbar_1 = PamhyrPlotToolbar(
+            self.canvas_1, self
+        )
+        self.plot_layout_1 = self.find(QVBoxLayout, "verticalLayout_1")
+        self.plot_layout_1.addWidget(self.toolbar_1)
+        self.plot_layout_1.addWidget(self.canvas_1)
+
+        self.plot_1 = PlotDKP(
+            canvas=self.canvas_1,
+            data=self._ics,
+            trad=self._trad,
+            toolbar=self.toolbar_1,
+            parent=self
+        )
+        self.plot_1.draw()
+
+        self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_2.setObjectName("canvas_2")
+        self.toolbar_2 = PamhyrPlotToolbar(
+            self.canvas_2, self
+        )
+        self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
+        self.plot_layout_2.addWidget(self.toolbar_2)
+        self.plot_layout_2.addWidget(self.canvas_2)
+
+        self.plot_2 = PlotDischarge(
+            canvas=self.canvas_2,
+            data=self._ics,
+            trad=self._trad,
+            toolbar=self.toolbar_2,
+            parent=self
+        )
+        self.plot_2.draw()
+
+    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.find(QAction, "action_import").triggered\
+                                           .connect(self.import_from_file)
+
+        self.find(QPushButton, "pushButton_generate_1").clicked.connect(
+            self.generate_growing_constante_height
+        )
+
+        self.find(QPushButton, "pushButton_generate_2").clicked.connect(
+            self.generate_discharge
+        )
+
+        self._table.dataChanged.connect(self._update_plot)
+
+    def index_selected_row(self):
+        table = self.find(QTableView, f"tableView")
+        rows = table.selectionModel()\
+                    .selectedRows()
+
+        if len(rows) == 0:
+            return 0
+
+        return rows[0].row()
+
+    def update(self):
+        self._update_plot()
+        self._propagate_update(key=Modules.INITIAL_CONDITION)
+
+    def _update_plot(self):
+        self.plot_1.draw()
+        self.plot_2.draw()
+
+    def _propagated_update(self, key=Modules(0)):
+        if Modules.GEOMETRY not in key:
+            return
+
+        self.update()
+
+    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 len(self._ics) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+        self._update()
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+        self._update()
+
+    def sort(self):
+        self._table.sort(False)
+        self._update()
+
+    def import_from_file(self):
+        workdir = os.path.dirname(self._study.filename)
+
+        return self.file_dialog(
+            callback=lambda d: self._import_from_file(d[0]),
+            directory=workdir,
+            default_suffix=".BIN",
+            file_filter=["Mage (*.BIN)"],
+        )
+
+    def _import_from_file(self, file_name):
+        solver = Mage8("dummy")
+        name = os.path.basename(file_name)\
+                      .replace(".BIN", "")
+
+        def reading():
+            self._tmp_results = solver.results(
+                self._study,
+                os.path.dirname(file_name),
+                name=name
+            )
+
+        dlg = ReadingResultsDialog(
+            reading_fn=reading,
+            parent=self
+        )
+        dlg.exec_()
+        results = self._tmp_results
+        self._import_from_results(results)
+
+    def _import_from_results(self, results):
+        logger.debug(f"import from results: {results}")
+        row = self.index_selected_row()
+
+        self._table.import_from_results(row, results)
+
+    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 generate_growing_constante_height(self):
+        dlg = HeightDialog(trad=self._trad, parent=self)
+        if dlg.exec():
+            value = dlg.value
+            self._table.generate("growing", value)
+            self._update()
+
+    def generate_discharge(self):
+        dlg = DischargeDialog(trad=self._trad, parent=self)
+        if dlg.exec():
+            value = dlg.value
+            self._table.generate("discharge", value)
+            self._update()
diff --git a/src/View/InitialConditionsAdisTS/translate.py b/src/View/InitialConditionsAdisTS/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..46d5f4040f3b3f44efad35d6aecd8cdf1a809572
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/translate.py
@@ -0,0 +1,49 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class ICTranslate(MainTranslate):
+    def __init__(self):
+        super(ICTranslate, self).__init__()
+
+        self._dict["Initial condition"] = _translate(
+            "InitialCondition", "Initial condition")
+        self._dict["Discharge"] = _translate(
+            "InitialCondition", "Discharge")
+        self._dict["Height"] = _translate(
+            "InitialCondition", "Height")
+
+        self._dict["elevation"] = self._dict["unit_elevation"]
+        self._dict["discharge"] = self._dict["unit_discharge"]
+        self._dict["kp"] = self._dict["unit_kp"]
+
+        self._sub_dict["table_headers"] = {
+            # "name": _translate("InitialCondition", "Name"),
+            "kp": self._dict["unit_kp"],
+            "discharge": self._dict["unit_discharge"],
+            "elevation": self._dict["unit_elevation"],
+            "height": self._dict["unit_height"],
+            "speed": self._dict["unit_speed"],
+            # "comment": _translate("InitialCondition", "Comment"),
+        }
diff --git a/src/View/Pollutants/Window.py b/src/View/Pollutants/Window.py
index a9dc2059c224fd10c85df7c3adad41ad9b503707..cabf5eb773e3c62d450cb43af204f526918b2afe 100644
--- a/src/View/Pollutants/Window.py
+++ b/src/View/Pollutants/Window.py
@@ -43,6 +43,8 @@ from View.Pollutants.Translate import PollutantsTranslate
 
 from View.Pollutants.Edit.Window import EditPolluantWindow
 
+from View.InitialConditionsAdisTS.Window import InitialConditionsWindow
+
 logger = logging.getLogger()
 
 
@@ -102,6 +104,7 @@ class PollutantsWindow(PamhyrWindow):
         self.find(QAction, "action_add").triggered.connect(self.add)
         self.find(QAction, "action_delete").triggered.connect(self.delete)
         self.find(QAction, "action_edit").triggered.connect(self.edit)
+        self.find(QAction, "action_initial_conditions").triggered.connect(self.initial_conditions)
         self._checkbox.clicked.connect(self._set_structure_state)
 
         table = self.find(QTableView, "tableView")
@@ -186,6 +189,27 @@ class PollutantsWindow(PamhyrWindow):
             )
             win.show()
 
+    def initial_conditions(self):
+        if self._study.river.has_current_reach():
+            reach = self._study.river.current_reach()
+
+            if self.sub_window_exists(
+                InitialConditionsWindow,
+                #data=[self._study, self.conf, reach]
+                data=[self._study, None, reach]
+            ):
+                return
+
+            initial = InitialConditionsWindow(
+                study=self._study,
+                #config=self.conf,
+                reach=reach,
+                parent=self
+            )
+            initial.show()
+        else:
+            self.msg_select_reach()
+
     def _set_checkbox_state(self):
         row = self.index_selected_row()
         if row is None:
diff --git a/src/View/ui/Pollutant.ui b/src/View/ui/Pollutant.ui
index e842bacae69422f03322464664570f854efa5503..ebe58a6f1b6d7ec5b599aa238d01fb67c4342677 100644
--- a/src/View/ui/Pollutant.ui
+++ b/src/View/ui/Pollutant.ui
@@ -34,9 +34,6 @@
         </size>
        </property>
       </widget>
-      <widget class="QWidget" name="verticalLayoutWidget">
-       <layout class="QVBoxLayout" name="verticalLayout"/>
-      </widget>
      </widget>
     </item>
    </layout>
diff --git a/src/View/ui/Pollutants.ui b/src/View/ui/Pollutants.ui
index 16960d920a52b5b61ee239aab37aed4f6198d81c..9ed80616a81d154a69e8e93c6b283bf719f8c505 100644
--- a/src/View/ui/Pollutants.ui
+++ b/src/View/ui/Pollutants.ui
@@ -96,6 +96,7 @@
    <addaction name="action_add"/>
    <addaction name="action_delete"/>
    <addaction name="action_edit"/>
+   <addaction name="action_initial_conditions"/>
   </widget>
   <action name="action_add">
    <property name="icon">
@@ -129,6 +130,11 @@
     <string>Edit selected hydraulic structure</string>
    </property>
   </action>
+  <action name="action_initial_conditions">
+   <property name="text">
+    <string>InitialConditions</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections/>