diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py
index b3ebd29a52df5cbb0ed65ebb889c859d4fb4e53b..cf844ff696ad36414d783e424d8c7ccfacb29036 100644
--- a/src/Model/BoundaryCondition/BoundaryCondition.py
+++ b/src/Model/BoundaryCondition/BoundaryCondition.py
@@ -1,30 +1,7 @@
 # -*- coding: utf-8 -*-
 
-from copy import copy
-
 from Model.Except import NotImplementedMethodeError
 
-class BoundaryConditionList(object):
-    def __init__(self):
-        self._data = []
-
-    def __len__(self):
-        return len(self._data)
-
-    def __getitem__(self, index):
-        return self._data[index]
-
-    def __setitem__(self, index, value):
-        self._data[index] = value
-
-    def __copy__(self):
-        new = BoundaryConditionList()
-        new._data = self._data.copy()
-        return new
-
-    def copy(self):
-        return copy(self)
-
 class BoundaryCondition(object):
     def __init__(self, name:str = ""):
         super(BoundaryCondition, self).__init__()
@@ -67,6 +44,12 @@ class BoundaryCondition(object):
     def data(self):
         return self._data.copy()
 
+    def _default_0(self):
+        return self._types[0](0)
+
+    def _default_1(self):
+        return self._types[1](0.0)
+
     def is_define(self):
         return self._data is not None
 
@@ -117,9 +100,3 @@ class BoundaryCondition(object):
                         new._set_i_c_v(ind, j, v[i])
 
         return new
-
-    def _default_0(self):
-        return self._types[0](0)
-
-    def _default_1(self):
-        return self._types[1](0.0)
diff --git a/src/Model/BoundaryCondition/BoundaryConditionList.py b/src/Model/BoundaryCondition/BoundaryConditionList.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5c2d9add71b5067addaacff8e6cec358661bc8c
--- /dev/null
+++ b/src/Model/BoundaryCondition/BoundaryConditionList.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Except import NotImplementedMethodeError
+
+from Model.BoundaryCondition.BoundaryConditionTypes import (
+    NotDefined,
+    PonctualContribution,
+    TimeOverZ, TimeOverDebit, ZOverDebit
+)
+
+class BoundaryConditionList(list):
+    def __init__(self):
+        super(BoundaryConditionList, self).__init__()
+
+    def new(self, index):
+        n = NotDefined()
+        self.insert(index, n)
+        return n
+
+    def delete(self, bcs):
+        for bc in bcs:
+            self.remove(bc)
+
+    def delete_i(self, indexes):
+        bcs = map(
+            lambda x: x[1],
+            filter(
+                lambda x: x[0] in indexes,
+                enumerate(self)
+            )
+        )
+
+        for bc in bcs:
+            self.remove(bc)
+
+    def move_up(self, index):
+        if index < len(self):
+            next = index - 1
+
+            self[index], self[next] = self[next], self[index]
+
+    def move_down(self, index):
+        if index >= 0:
+            prev = index + 1
+
+            self[index], self[prev] = self[prev], self[index]
+
+    def __copy__(self):
+        new = BoundaryConditionList()
+
+        for bc in self:
+            new.append(bc)
+
+        return new
+
+    def __deepcopy__(self):
+        new = BoundaryConditionList()
+
+        for bc in self:
+            new.append(deepcopy(bc))
+
+        return new
+
+    def copy(self):
+        return copy(self)
diff --git a/src/Model/BoundaryCondition/Types.py b/src/Model/BoundaryCondition/BoundaryConditionTypes.py
similarity index 82%
rename from src/Model/BoundaryCondition/Types.py
rename to src/Model/BoundaryCondition/BoundaryConditionTypes.py
index 78df831eeec4772e146a706e4d002c7e575ecf3b..b7515d432f7067f1a2e4de92f549233492ac7409 100644
--- a/src/Model/BoundaryCondition/Types.py
+++ b/src/Model/BoundaryCondition/BoundaryConditionTypes.py
@@ -4,12 +4,14 @@ from Model.Except import NotImplementedMethodeError
 
 from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
 
-BC_types = [
-    "PC",
-    "TZ",
-    "TD",
-    "ZD"
-]
+
+class NotDefined(BoundaryCondition):
+    def __init__(self, name:str = ""):
+        super(NotDefined, self).__init__(name=name)
+
+        self._type = "ND"
+        self._header = ["", ""]
+        self._types = [str, str]
 
 class PonctualContribution(BoundaryCondition):
     def __init__(self, name:str = ""):
diff --git a/src/Model/River.py b/src/Model/River.py
index 51f9b2ef8c88ce0570f4a2ce3d549aa4dcbf0b29..dad5e824573c413c4fd33eb2ae8a8310a71ffab9 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -8,8 +8,10 @@ from Model.Geometry.Profile import Profile
 from Model.Geometry.Reach import Reach
 
 from Model.BoundaryCondition.BoundaryCondition import (
-    BoundaryCondition,
-    BoundaryConditionList,
+    BoundaryCondition
+)
+from Model.BoundaryCondition.BoundaryConditionList import (
+    BoundaryConditionList
 )
 
 
diff --git a/src/View/BoundaryCondition/BCUndoCommand.py b/src/View/BoundaryCondition/BCUndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bcefc9c493e49a83a18e260a4c77e4506ecd076
--- /dev/null
+++ b/src/View/BoundaryCondition/BCUndoCommand.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
+from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList
+
+class SetNameCommand(QUndoCommand):
+    def __init__(self, lst, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._index = index
+        self._old = self._lst[self._index].name
+        self._new = new_value
+
+    def undo(self):
+        self._lst[self._index].name = self._old
+
+    def redo(self):
+        self._lst[self._index].name = self._new
+
+class SetNodeCommand(QUndoCommand):
+    def __init__(self, lst, index, node):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._index = index
+        self._old = self.lst[index].node
+        self._new = node
+
+    def undo(self):
+        self._lst[self._index].node = self._old
+
+    def redo(self):
+        self._lst[self._index].node = self._new
+
+class SetTypeCommand(QUndoCommand):
+    def __init__(self, lst, index, _type):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._index = index
+        self._type = _type
+        self._old = self.lst[index]
+        self._new = self.lst[index].convert(self._type)
+
+    def undo(self):
+        self._lst[self._index] = self._old
+
+    def redo(self):
+        self._lst[self._index] = self._new
+
+class AddCommand(QUndoCommand):
+    def __init__(self, lst, index):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        self._lst.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._lst.new(self._index)
+        else:
+            self._lst.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, lst, rows):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._rows = rows
+
+        self._bc = []
+        for row in rows:
+            self._bc.append((row, self._lst[row]))
+        self._bc.sort()
+
+    def undo(self):
+        for row, el in self._bc:
+            self._lst.insert(row, el)
+
+    def redo(self):
+        self._lst.delete_i(self._rows)
+
+class SortCommand(QUndoCommand):
+    def __init__(self, lst, _reverse):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._reverse = _reverse
+
+        self.old = self._lst.copy()
+        self._indexes = None
+
+    def undo(self):
+        ll = self._lst.copy()
+        self._lst.sort(
+            key=lambda x: self._indexes[ll.index(x)]
+        )
+
+    def redo(self):
+        self._lst.sort(self._reverse)
+        if self._indexes is None:
+            self._indexes = list(
+                map(
+                    lambda p: self._old.index(p),
+                    self._lst
+                )
+            )
+            self._old = None
+
+
+class MoveCommand(QUndoCommand):
+    def __init__(self, lst, up, i):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._up = up == "up"
+        self._i = i
+
+    def undo(self):
+        if self._up:
+            self._lst.move_up(self._i)
+        else:
+            self._lst.move_down(self._i)
+
+    def redo(self):
+        if self._up:
+            self._lst.move_up(self._i)
+        else:
+            self._lst.move_down(self._i)
+
+
+class PasteCommand(QUndoCommand):
+    def __init__(self, lst, row, bc):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._row = row
+        self._bc = deepcopy(bc)
+        self._bc.reverse()
+
+    def undo(self):
+        self._lst.delete(self._bc)
+
+    def redo(self):
+        for bc in self._bc:
+            self._lst.insert(self._row, bc)
+
+
+class DuplicateCommand(QUndoCommand):
+    def __init__(self, lst, rows, bc):
+        QUndoCommand.__init__(self)
+
+        self._lst = lst
+        self._rows = rows
+        self._bc = deepcopy(bc)
+        self._bc.reverse()
+
+    def undo(self):
+        self._lst.delete(self._bc)
+
+    def redo(self):
+        for profile in self._profiles:
+            self._lst.insert(self._rows[0], profile)
diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py
index 70f8b346660c0cc21446d019b76eb76a80ce6c85..ab23890125986b92fe326116bdcde0c4aa5fd474 100644
--- a/src/View/BoundaryCondition/BoundaryConditionWindow.py
+++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py
@@ -1,23 +1,41 @@
 # -*- 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,
+    QCoreApplication, QModelIndex, pyqtSlot,
 )
 
 from PyQt5.QtWidgets import (
     QDialogButtonBox, QPushButton, QLineEdit,
     QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
 )
 
+from View.BoundaryCondition.BCUndoCommand import (
+    SetNameCommand, SetNodeCommand, SetTypeCommand,
+    AddCommand, DelCommand, SortCommand,
+    MoveCommand, PasteCommand, DuplicateCommand,
+)
+from Model.BoundaryCondition.BoundaryConditionTypes import (
+    NotDefined, PonctualContribution,
+    TimeOverZ, TimeOverDebit, ZOverDebit
+)
 from View.BoundaryCondition.EditBoundaryConditionWindow import EditBoundaryConditionWindow
 
 _translate = QCoreApplication.translate
 
 long_types = {
+    "ND": _translate("BoundaryCondition", "Not defined"),
     "PC": _translate("BoundaryCondition", "Ponctual contribution"),
     "TZ": _translate("BoundaryCondition", "Time over Z"),
     "TD": _translate("BoundaryCondition", "Time over Debit"),
@@ -30,11 +48,63 @@ table_headers = {
     "node": _translate("BoundaryCondition", "Node")
 }
 
+BC_types = {
+    "ND": NotDefined,
+    "PC": PonctualContribution,
+    "TZ": TimeOverZ,
+    "TD": TimeOverDebit,
+    "ZD": ZOverDebit
+}
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, mode="type", parent=None):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._mode = mode
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        if self._mode == "type":
+            self.editor.addItems(BC_types.keys())
+        else:
+            self.editor.addItems(
+                ["Not associate"] +
+                self._data.nodes_names()
+            )
+
+        self.editor.setCurrentText(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 and 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 TableModel(QAbstractTableModel):
-    def __init__(self, data=None):
+    def __init__(self, data=None, undo=None):
         super(QAbstractTableModel, self).__init__()
         self._headers = list(table_headers.keys())
         self._data = data
+        self._undo = undo
         self._lst = self._data.boundary_condition
 
     def flags(self, index):
@@ -57,12 +127,12 @@ class TableModel(QAbstractTableModel):
         column = index.column()
 
         if self._headers[column] == "name":
-            return self.lst[row].name
+            return self._lst[row].name
         elif self._headers[column] == "type":
-            t = self.lst[row].bctype
+            t = self._lst[row].bctype
             return long_types[t]
         elif self._headers[column] == "node":
-            n = self.lst[row].node
+            n = self._lst[row].node
             if n is None:
                 return "-"
             return n.name
@@ -83,15 +153,104 @@ class TableModel(QAbstractTableModel):
         column = index.column()
 
         if self._headers[column] == "name":
-            self.lst[row].name = value
+            self._undo.push(
+                SetNameCommand(
+                    self._lst, row, value
+                )
+            )
         elif self._headers[column] == "type":
-            self.lst[row].bctype = value
+            self._undo.push(
+                SetTypeCommand(
+                    self._lst, row, BC_types[value]
+                )
+            )
         elif self._headers[column] == "node":
-            self.lst[row].node = value
+            self._undo.push(
+                SetNodeCommand(
+                    self._lst, row, self._data.node(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._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()
+
+    def sort(self, _reverse, parent=QModelIndex()):
+        self.layoutAboutToBeChanged.emit()
+
+        self._undo_stack.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_stack.push(
+            MoveCommand(
+                self._lst, "up", row
+            )
+        )
+
+        self.endMoveRows()
+        self.layoutChanged.emit()
+
+    def move_down(self, index, parent=QModelIndex()):
+        if row > len(self._lst):
+            return
+
+        target = row
+
+        self.beginMoveRows(parent, row + 1, row + 1, parent, target)
+
+        self._undo_stack.push(
+            MoveCommand(
+                self._lst, "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()
+
 
 class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
     def __init__(self, title="BoundaryConditions", study=None, parent=None):
@@ -100,14 +259,109 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow):
         )
 
         self._study = study
+        self._lst = self._study.river.boundary_condition
 
+        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):
         table = self.find(QTableView, "tableView")
         self._table = TableModel(
-            data = self._study.river
+            data = self._study.river,
+            undo = self._undo_stack
         )
         table.setModel(self._table)
 
-        self.ui.setWindowTitle(title)
+        self._delegate_type = ComboBoxDelegate(
+            data = self._study.river,
+            mode = "type"
+        )
+        self._delegate_node = ComboBoxDelegate(
+            data = self._study.river,
+            mode = "node"
+        )
+
+        table.setItemDelegateForColumn(
+            1, self._delegate_type
+        )
+        table.setItemDelegateForColumn(
+            2, self._delegate_node
+        )
+
+    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_edit").triggered.connect(self.edit)
+        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, "tableView")
+        return table.selectionModel()\
+                    .selectedRows()[0]\
+                    .row()
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, "tableView")
+        return list(
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def add(self):
+        if len(self._lst) == 0:
+            self._table.add(0)
+        else:
+            row = self.index_selected_row()
+            self._table.add(row)
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        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()
 
     def edit(self):
         win = EditBoundaryConditionWindow(data=None, parent=self)
diff --git a/src/tools.py b/src/tools.py
index 6ae8996b133570388631ba32ff365fea0674dec6..902f2ea9988610965e4c08df42e7db6bddfd7dc0 100644
--- a/src/tools.py
+++ b/src/tools.py
@@ -90,7 +90,7 @@ def trace(func):
         value = func(*args, **kwargs)
 
         t = time.ctime()
-        r = f"{head}[{t}] Return {func.__module__}.{Fore.GREEN}{func.__qualname__}{Style.RESET_ALL}"
+        r = f"{head}[{t}] Return {func.__module__}.{Fore.GREEN}{func.__qualname__}{Style.RESET_ALL}: {value}"
         print(r)
 
         return value