diff --git a/src/Model/Geometry/Profile.py b/src/Model/Geometry/Profile.py
index 83833f283faf53f2251a78a6ca882f3ad82b904c..59d677ea546badc88abd038ab768c300bb0db668 100644
--- a/src/Model/Geometry/Profile.py
+++ b/src/Model/Geometry/Profile.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 
+from Model.Geometry.Point import Point
 from Model.Except import NotImplementedMethodeError
 
 class Profile(object):
@@ -20,6 +21,14 @@ class Profile(object):
 
         self._profile_type = _type
 
+    @property
+    def number_points(self):
+        return len(self._points)
+
+    @property
+    def points(self):
+        return self._points.copy()
+
     @property
     def reach(self):
         return self._reach
@@ -100,6 +109,11 @@ class Profile(object):
     def profile_type(self, value: str):
         self._profile_type = value
 
+    def point(self, i:int):
+        if i < len(self._points):
+            return self._points[i]
+
+        return None
 
     def named_points(self):
         """List of named point
@@ -110,6 +124,81 @@ class Profile(object):
         return [point for point in self._points
                 if point.point_is_named()]
 
+
+    def insert_point(self, index: int, point:Point):
+        """Insert point at index.
+
+        Args:
+            index: The index of new profile.
+            point: The point.
+
+        Returns:
+            Nothing.
+        """
+        self._points.insert(index, point)
+
+
+    def delete(self, index: int):
+        """Delete the point at index
+
+        Args:
+            index: Index of point.
+
+        Returns:
+            Nothing.
+        """
+        try:
+            self._points.pop(index)
+        except IndexError:
+            raise IndexError(f"Invalid point index: {index}")
+
+    # Move
+
+    def move_up_point(self, index: int):
+        if index < len(self._points):
+            next = index - 1
+
+            p = self._points
+            p[index], p[next] = p[next], p[index]
+
+    def move_down_point(self, index: int):
+        if index >= 0:
+            prev = index + 1
+
+            p = self._points
+            p[index], p[prev] = p[prev], p[index]
+
+    # Sort
+
+    @timer
+    def sort(self, column, is_reversed: bool = False):
+        predicate = lambda p: p.x
+        if column == 'y':
+            predicate = lambda p: p.y
+        elif column == 'z':
+            predicate = lambda p: p.z
+
+        self._profiles = sorted(
+            self._points,
+            key=predicate,
+            reverse=is_reversed
+        )
+
+    @timer
+    def sort_with_indexes(self, indexes: list):
+        if len(self._points) != len(indexes):
+            print("TODO: CRITICAL ERROR!")
+
+        self._points = list(
+            map(
+                lambda x: x[1],
+                sorted(
+                    enumerate(self.points),
+                    key=lambda x: indexes[x[0]]
+                )
+            )
+        )
+
     # Abstract method, must be implemented for in non abstract class
     def get_station(self):
         raise NotImplementedMethodeError(self, self.get_station)
diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py
index c9818f3ee458a1b5cbf68c9bd8cf95a7d8d84396..fce2bc5c0d0e63a5dae7cdddaf8bb9bd79a7a4df 100644
--- a/src/Model/Geometry/ProfileXYZ.py
+++ b/src/Model/Geometry/ProfileXYZ.py
@@ -117,22 +117,8 @@ class ProfileXYZ(Profile):
         point_xyz = PointXYZ(0., 0., 0.)
         self._points.append(point_xyz)
 
-    def delete(self, index: int):
-        """Delete the point at index
-
-        Args:
-            index: Index of point.
-
-        Returns:
-            Nothing.
-        """
-        try:
-            self._points.pop(index)
-        except IndexError:
-            raise IndexError(f"Invalid point index: {index}")
-
     def insert(self, index: int):
-        """Insert a new profile at index.
+        """Insert a new point at index.
 
         Args:
             index: The index of new profile.
@@ -140,35 +126,8 @@ class ProfileXYZ(Profile):
         Returns:
             Nothing.
         """
-        profile = ProfileXYZ()
-        self._points.insert(index, profile)
-
-    def delete1(self, list_index: list):
-        """Delete a list of points
-
-        Args:
-            list_index: Indexes list.
-
-        Returns:
-            Nothing.
-        """
-        try:
-            if list_index:
-                indices = sorted(list(set(list_index)), reverse=True)
-                for idx in indices:
-                    # if idx < len(self._list_profiles) :
-                    try:
-                        self._points.pop(idx)
-                    except IndexError:
-                        print("Empty list, nothing to delete")
-        except TypeError:
-            if isinstance(list_index, int):
-                self._points.pop(list_index)
-                print(f"\n{list_index} is not a list\n")
-            else:
-                raise TypeError(
-                    f"{list_index} is instance of unexpected type '{type(list_index)}'"
-                )
+        point = PointXYZ(0., 0., 0.)
+        self._points.insert(index, point)
 
     def filter_isnan(self, lst):
         """Returns the input list without 'nan' element
diff --git a/src/View/Geometry/Profile/ProfileUndoCommand.py b/src/View/Geometry/Profile/ProfileUndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..964a870bc7a4d1d0a711ea5b1ac11b0d0e64aafe
--- /dev/null
+++ b/src/View/Geometry/Profile/ProfileUndoCommand.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.Geometry.Profile import Profile
+
+
+class SetDataCommand(QUndoCommand):
+    def __init__(self, profile, index, old_value, new_value):
+        QUndoCommand.__init__(self)
+
+        self._profile = profile
+        self._index = index
+        self._old = old_value
+        self._new = new_value
+
+class SetXCommand(SetDataCommand):
+    def undo(self):
+        self._profile.point(self._index).x = self._old
+
+    def redo(self):
+        self._profile.point(self._index).x = self._new
+
+class SetYCommand(SetDataCommand):
+    def undo(self):
+        self._profile.point(self._index).y = self._old
+
+    def redo(self):
+        self._profile.point(self._index).y = self._new
+
+class SetZCommand(SetDataCommand):
+    def undo(self):
+        self._profile.point(self._index).z = self._old
+
+    def redo(self):
+        self._profile.point(self._index).z = self._new
+
+class SetNameCommand(SetDataCommand):
+    def undo(self):
+        self._profile.point(self._index).name = self._old
+
+    def redo(self):
+        self._profile.point(self._index).name = self._new
+
+
+class AddCommand(QUndoCommand):
+    def __init__(self, profile, index):
+        QUndoCommand.__init__(self)
+
+        self._profile = profile
+        self._index = index
+
+    def undo(self):
+        self._profile.delete(self._index)
+
+    def redo(self):
+        self._profile.insert(self._index)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, profile, rows):
+        QUndoCommand.__init__(self)
+
+        self._profile = profile
+        self._rows = rows
+
+        self._points = []
+        for row in rows:
+            self._points.append(self._profile.point(row))
+        self._points.reverse()
+
+    def undo(self):
+        row = self._rows[0]
+        for point in self._points:
+            self._profile.insert_point(row, point)
+
+    def redo(self):
+        row = self._rows[0]
+        for _ in self._rows:
+            self._profile.delete(row)
+
+class SortCommand(QUndoCommand):
+    def __init__(self, profile, column _reverse):
+        QUndoCommand.__init__(self)
+
+        self._profile = profile
+        self._column = column
+        self._reverse = _reverse
+
+        old = self._profile.points
+        self._profile.sort(self.column, self._reverse)
+        new = self._profile.points
+
+        self._indexes = list(
+            map(
+                lambda p: old.index(p),
+                new
+            )
+        )
+
+    def undo(self):
+        self._profile.sort_with_indexes(self._indexes)
+
+    def redo(self):
+        self._profile.sort(self.column, self._reverse)
+
+
+class MoveCommand(QUndoCommand):
+    def __init__(self, profile, up, i):
+        QUndoCommand.__init__(self)
+
+        self._profile = profile
+        self._up = up == "up"
+        self._i = i
+
+    def undo(self):
+        if self._up:
+            self._profile.move_up_point(self._i)
+        else:
+            self._profile.move_down_point(self._i)
+
+    def redo(self):
+        if self._up:
+            self._profile.move_up_point(self._i)
+        else:
+            self._profile.move_down_point(self._i)
+
+
+class PasteCommand(QUndoCommand):
+    def __init__(self, profile, row, points):
+        QUndoCommand.__init__(self)
+
+        self._profile = profile
+        self._row = row
+        self._points = points
+
+        self._points.reverse()
+
+    def undo(self):
+        for ind in range(len(self._profiles)):
+            self._profile.delete(self._row)
+
+    def redo(self):
+        for point in self._points:
+            self._profile.insert_point(self._row, point)
diff --git a/src/View/Geometry/Profile/ProfileWindow.py b/src/View/Geometry/Profile/ProfileWindow.py
index 9266f7114007197062a8e6f0a5986193b0fadb58..4bb712333b766bb15612cbbadb1e02b2e52b8fdf 100644
--- a/src/View/Geometry/Profile/ProfileWindow.py
+++ b/src/View/Geometry/Profile/ProfileWindow.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 import copy
 import sys
 import csv
@@ -93,7 +95,7 @@ class ProfileWindow(QMainWindow):
 
     def graph(self):
         """
-        Returns: Le tracé de la cote z en fonction  de l'abscisse (calculée).
+        Returns: Le tracé de la cote z en fonction de l'abscisse (calculée).
         """
         x = self._model.station  # abscisse en travers
         y = self._model.z  # cote z
diff --git a/src/View/Geometry/Profile/qtableview_profile.py b/src/View/Geometry/Profile/qtableview_profile.py
index b94f59ac89c0b3d1e3598528c98aabe27a994b5d..6e237ac6ccc1f06a5369cff92d8830fd325f288b 100644
--- a/src/View/Geometry/Profile/qtableview_profile.py
+++ b/src/View/Geometry/Profile/qtableview_profile.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 import numpy as np
 import pandas as pd
 from PyQt5.QtGui import QFont
@@ -8,66 +10,80 @@ from PyQt5.QtCore import QModelIndex, Qt, QAbstractTableModel, QVariant, QCoreAp
 
 from Model.Geometry.ProfileXYZ import ProfileXYZ
 
+from View.Geometry.Profile.ProfileUndoCommand import *
+
 _translate = QCoreApplication.translate
 
 
 class PandasModelEditable(QAbstractTableModel):
-    def __init__(self, profile: ProfileXYZ, table_header=None):
+    def __init__(self, profile: ProfileXYZ, table_header=None, undo=None):
         QAbstractTableModel.__init__(self)
 
+        self._undo_stack = undo
+        self._profile = profile
+
         if table_header is None:
-            self.header = ["X (m)", "Y (m)", "Z (m)", _translate("MainWindowProfile", "Nom"),
-                           _translate("MainWindowProfile", "Abs en travers (m)")]
+            self._header = [
+                "X (m)", "Y (m)", "Z (m)",
+                _translate("MainWindowProfile", "Nom"),
+                _translate("MainWindowProfile", "Abs en travers (m)")
+            ]
         else:
-            self.header = table_header
-
-        self.profile = profile
-
-        data = pd.DataFrame({
-            self.header[0]: profile.x(),
-            self.header[1]: profile.y(),
-            self.header[2]: profile.z(),
-            self.header[3]: profile.name(),
-            self.header[4]: profile.get_station()
-        })
-        self._data = data
+            self._header = table_header
 
     def rowCount(self, parent=QModelIndex()):
-        return self._data.shape[0]
+        return self._profile.number_points
 
     def columnCount(self, parent=QModelIndex()):
-        return self._data.shape[1]
+        return len(self._header)
 
     def data(self, index, role=Qt.DisplayRole):
-        value = self._data.iloc[index.row()][index.column()]
         if index.isValid():
             if role == Qt.DisplayRole:
-                if index.column() != 4:
-                    if isinstance(value, float):
-                        return "%.4f" % value
-                else:
-                    if isinstance(value, float):
-                        return "%.3f" % value
+                value = ""
+
+                if index.column() == 0:
+                    value = self._profile.point(index.row()).x
+                elif index.column() == 1:
+                    value = self._profile.point(index.row()).y
+                elif index.column() == 2:
+                    value = self._profile.point(index.row()).z
+                elif index.column() == 3:
+                    value = self._profile.point(index.row()).name
+                elif index.column() == 4:
+                    value = self._profile.get_station()
 
-                return str(self._data.iloc[index.row(), index.column()])
+                if 0 <= index.column() < 3:
+                    return f"{value:.4f}"
+                elif index.column() == 4:
+                    return f"{value:.3f}"
+
+                return f"{value}"
 
             if role == Qt.TextAlignmentRole:
                 return Qt.AlignHCenter | Qt.AlignVCenter
 
-            # if index.column() == 2:
-            #     if role == Qt.ForegroundRole:
-            #         if value == min(self._data.iloc[:, index.column()]):
-            #             return QtGui.QColor("red")
-            #         elif value == max(self._data.iloc[:, index.column()]):
-            #             return QtGui.QColor("Blue")
+            if index.column() == 2:
+                value = self._profile.point(index.row()).z
+                if role == Qt.ForegroundRole:
+                    if value == self._profile.z_min():
+                        return QtGui.QColor("red")
+                    elif value == self._profile.z_max():
+                        return QtGui.QColor("blue")
 
                 if role == Qt.ToolTipRole:
-                    if value == min(self._data.iloc[:, index.column()]):
-                        return _translate("MainWindowProfile", "La cote du fond", "Z minimale")
-                    elif value == max(self._data.iloc[:, index.column()]):
-                        return _translate("MainWindowProfile", "La cote maximale", "Z maximale")
+                    if value == self._profile.z_min():
+                        return _translate("MainWindowProfile",
+                                          "La cote du fond",
+                                          "Z minimale")
+                    elif value == self._profile.z_max():
+                        return _translate("MainWindowProfile",
+                                          "La cote maximale",
+                                          "Z maximale")
 
             if index.column() == 3:
+                value = self._profile.point(index.row()).name
+
                 if value.strip().upper() in ["RG", "RD"]:
                     if role == Qt.FontRole:
                         font = QFont()
@@ -89,14 +105,13 @@ class PandasModelEditable(QAbstractTableModel):
                     font.setBold(True)
                     return font
 
-                # if role == Qt.BackgroundRole:
-                #     return QtGui.QColor("#ededee")
-
         return QVariant()
 
     def headerData(self, section, orientation, role=Qt.DisplayRole):
         if orientation == Qt.Horizontal and role == Qt.DisplayRole:
-            return self.header[section]
+            return self._header[section]
+        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
+            return return str(section + 1)
 
         if role == Qt.ToolTipRole and section == 4:
             return _translate(
@@ -105,268 +120,163 @@ class PandasModelEditable(QAbstractTableModel):
                 " \nsur le plan défini par les deux points nommés extrêmes "
             )
 
-        if orientation == Qt.Vertical and role == Qt.DisplayRole:
-            return self._data.index[section] + 1
-
-        return None
+        return QVariant()
 
     def setData(self, index, value, role=Qt.EditRole):
-        if role == Qt.EditRole:
-            try:
-                if index.column() == 3:
-                    self._data.iat[index.row(), index.column()] = str(value)
-                elif index.column() == 0:
-                    self._data.iat[index.row(), index.column()] = float(value)
-                elif index.column() == 1:
-                    self._data.iat[index.row(), index.column()] = float(value)
-                elif index.column() == 2:
-                    self._data.iat[index.row(), index.column()] = float(value)
+        row = index.row()
+        column = index.column()
 
-                self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-                    self.header,
-                    self._data.values.tolist()
+        if role == Qt.EditRole:
+            if column == 0:
+                self._undo_stack.push(
+                    SetXCommand(
+                        self._profile, row,
+                        self._profile.profile(row).x,
+                        value
+                    )
+                )
+            elif column == 1:
+                self._undo_stack.push(
+                    SetYCommand(
+                        self._profile, row,
+                        self._profile.profile(row).y,
+                        value
+                    )
+                )
+            elif column == 2:
+                self._undo_stack.push(
+                    SetZCommand(
+                        self._profile, row,
+                        self._profile.profile(row).z,
+                        value
+                    )
+                )
+            elif column == 3:
+                self._undo_stack.push(
+                    SetNameCommand(
+                        self._profile, row,
+                        self._profile.profile(row).name,
+                        value
+                    )
                 )
-                self.dataChanged.emit(index, index)
-            except:
-                print('TODO')
-                self.QMessageBoxCritical(value)
 
+            self.dataChanged.emit(index, index)
             return True
 
         self.dataChanged.emit(index, index)
         self.layoutChanged.emit()
-
         return False
 
-    @staticmethod
-    def QMessageBoxCritical(value):
-        msg = QMessageBox()
-        msg.setIcon(QMessageBox.Warning)
-        msg.setText("{} : Valeur saisie incorrecte ".format(value))
-        msg.setInformativeText("Seules les valeurs numériques sont autorisées.")
-        msg.setWindowTitle("Warning ")
-        msg.setStyleSheet("QLabel{min-width:150 px; font-size: 13px;} QPushButton{ width:20px; font-size: 12px};"
-                          "background-color: Ligthgray ; color : gray;font-size: 8pt; color: #888a80;")
-        msg.exec_()
-
     def index(self, row, column, parent=QModelIndex()):
         if not self.hasIndex(row, column, parent):
             return QModelIndex()
+
         return self.createIndex(row, column, QModelIndex())
 
     def flags(self, index):
-        return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
-    # @QtCore.pyqtSlot()
-    def insertRows(self, row, count, parent=QModelIndex()):
-        self.beginInsertRows(parent, row, row + count - 1)
-        indexes = [str(self.rowCount() + i) for i in range(count)]
-        left = self._data[0:row]
-        mid = pd.DataFrame(index=indexes, columns=self._data.columns)
-        right = self._data[row + count - 1:self.rowCount()]
+        flg = Qt.ItemIsSelectable | Qt.ItemIsEnabled
 
-        self._data = pd.concat([left, mid, right])
+        if index.column() == 4:
+            return flg
 
-        for i in [3]:
-            self._data.iloc[:, i].replace(np.nan, '', inplace=True)
+        return Qt.ItemIsEditable | flg
 
-        self._data.reset_index(drop=True, inplace=True)
+    def insert_row(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
 
-        try:
-            self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-                self.header,
-                self._data.values.tolist()
+        self._undo_stack.push(
+            AddCommand(
+                self._profile, row
             )
-        except:
-            print("TODO")
+        )
 
         self.endInsertRows()
         self.layoutChanged.emit()
 
-    # @QtCore.pyqtSlot()
-    def removeRows(self, row, count, parent=QModelIndex()):
-        self.beginRemoveRows(parent, row, row + count + 1)
-        self._data.drop(self._data.index[row], inplace=True)
-        self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-            self.header,
-            self._data.values.tolist()
-        )
-        self.endRemoveRows()
-        self.layoutChanged.emit()
-
-    def remove_rows1(self, row, count, parent=QModelIndex()):
-        self.beginRemoveRows(parent, row, row + count - 1)
-        left = self._data.iloc[0:row]
-        right = self._data.iloc[row + count:self.rowCount()]
-
-        self._data = pd.concat([left, right], axis=0, ignore_index=True)
-        self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-            self.header,
-            self._data.values.tolist()
-        )
-        self.endRemoveRows()
-        self.layoutChanged.emit()
+    def remove_rows(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], row[-1])
 
-    def remove_rows(self, list_row_selected, parent=QModelIndex()):
-        self.beginRemoveRows(parent, list_row_selected[0], list_row_selected[-1])
-
-        try:
-            self._data.drop(self._data.index[list_row_selected], inplace=True)
-            self._data.reset_index(drop=True, inplace=True)
-        except:
-            print('TODO')
-        try:
-            self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-                self.header,
-                self._data.values.tolist()
+        self._undo_stack.push(
+            DelCommand(
+                self._profile, rows
             )
-        except:
-            print("TODO")
+        )
 
         self.endRemoveRows()
         self.layoutChanged.emit()
 
-    def sort(self, column, order=Qt.AscendingOrder):
+    def sort(self, column='x', order=Qt.AscendingOrder):
         self.layoutAboutToBeChanged.emit()
-        colname = self._data.columns.tolist()[column]
-        self._data.sort_values(colname, ascending=order == Qt.AscendingOrder, inplace=True)
-        self._data.reset_index(inplace=True, drop=True)
 
-        self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-            self.header,
-            self._data.values.tolist()
+        reverse = (order != Qt.AscendingOrder)
+
+        self._undo_stack.push(
+            SortCommand(
+                self._profile, column, reverse
+            )
         )
-        self.layoutChanged.emit()
 
-    def moveRowDown(self, row_to_move, parent=QModelIndex()):
-        target = row_to_move + 2
-        self.beginMoveRows(parent, row_to_move, row_to_move, parent, target)
-        block_before_row = self._data.iloc[0:row_to_move]
-        selected_row = self._data.iloc[row_to_move:row_to_move + 1]
-        after_selcted_row = self._data.iloc[row_to_move + 1:row_to_move + 2]
-        block_after_row = self._data.iloc[row_to_move + 2:self.rowCount()]
+        self.layoutChanged.emit()
 
-        self._data = pd.concat([block_before_row, after_selcted_row, selected_row, block_after_row], axis=0)
-        self._data.reset_index(inplace=True, drop=True)
+    def move_row_up(self, row, parent=QModelIndex()):
+        if row <= 0:
+            return
 
-        self.endMoveRows()
-        self.layoutChanged.emit()
+        target = row + 2
 
-    def moveRowUp(self, row_to_move, parent=QModelIndex()):
-        target = row_to_move + 1
-        self.beginMoveRows(parent, row_to_move - 1, row_to_move - 1, parent, target)
-        block_before_row = self._data.iloc[0:row_to_move - 1]
-        before_selected_row = self._data.iloc[row_to_move - 1:row_to_move]
-        selected_row = self._data.iloc[row_to_move:row_to_move + 1]
-        block_after_row = self._data.iloc[row_to_move + 1:self.rowCount()]
+        self.beginMoveRows(parent, row - 1, row - 1, parent, target)
 
-        self._data = pd.concat([block_before_row, selected_row, before_selected_row, block_after_row], axis=0)
-        self._data.reset_index(inplace=True, drop=True)
+        self._undo_stack.push(
+            MoveCommand(
+                self._profile, "up", row
+            )
+        )
 
         self.endMoveRows()
         self.layoutChanged.emit()
 
-    def copyTable(self, start_selection, end_selection):
-        end_selection = self.rowCount()
-
-        self._data.loc[start_selection:end_selection]\
-                  .to_clipboard(header=None, index=False, excel=True, sep='\t')
+    def move_row_down(self, row_to_move, parent=QModelIndex()):
+        if row > self._profile.number_points:
+            return
 
-    def insert_df_to_idx(self, idx, df, df_insert):
-        """
-        Args:
-            idx: is the index position in df where you want to insert new dataframe (df_insert)
-            df: dataframe
-            df_insert: dataframe to insert
-        Returns:
-            The dataframe df with df_insert inserted at index idx.
-        """
-        return df.iloc[:idx, ].append(df_insert).append(df.iloc[idx:, ]).reset_index(drop=True)
+        target = row
 
-    def pasteTable(self, insertion_index):
-        self.layoutAboutToBeChanged.emit()
-        df = pd.read_clipboard(header=None, skip_blank_lines=True,
-                               sep="\t", names=self.header)
-        self._data = self.insert_df_to_idx(insertion_index, self._data, df)
-
-        for i in [3]:
-            self._data.iloc[:, i].replace(np.nan, '', inplace=True)
+        self.beginMoveRows(parent, row + 1, row + 1, parent, target)
 
-        self.layoutChanged.emit()
-        self._data.iloc[:, 4] = projection_pointXYZ.update_station(
-            self.header,
-            self._data.values.tolist()
+        self._undo_stack.push(
+            MoveCommand(
+                self._profile, "down", row
+            )
         )
 
-    @property
-    def model_data(self):
-        return self._data
-
-    @model_data.setter
-    def model_data(self, new_data):
-        self._data = new_data
+        self.endMoveRows()
         self.layoutChanged.emit()
 
-    @property
-    def x(self):
-        return self._data.iloc[:, 0].tolist()
-
-    @property
-    def y(self):
-        return self._data.iloc[:, 1].tolist()
-
-    @property
-    def z(self):
-        return self._data.iloc[:, 2].tolist()
-
-    @property
-    def name(self):
-        return self._data.iloc[:, 3].tolist()
-
-    def get_data(self):
-        return self._data
-
-    @property
-    def station(self):
-        return self._data.iloc[:, 4].tolist()
-
-    def remove_duplicates_names(self):
-        counter_list = []
-        list_deleted_names = []
-        ind_ind = []
+    def paste(self, row, points):
+        if row > self._profile.number_points:
+            return
 
-        for ind, name_point in enumerate(self.name):
-            if name_point not in counter_list:
-                counter_list.append(name_point)
-            elif len(name_point.strip()) > 0 and name_point in counter_list:
-                ind_ind.append(ind)
+        if len(points) == 0:
+            return
 
-                if name_point not in list_deleted_names:
-                    list_deleted_names.append(name_point)
-
-        for ind in ind_ind:
-            self._data.iat[ind, 3] = ""
+        self.layoutAboutToBeChanged.emit()
 
-    def data_contains_nan(self) -> bool:
-        """
-        Returns:
-            Returns True if the QTableView() contains np.nan
-        """
-        return self._data.isnull().values.any()
+        self._undo_stack.push(
+            PasteCommand(
+                self._profile, row, points
+            )
+        )
 
-    def delete_empty_rows(self):
         self.layoutAboutToBeChanged.emit()
+        self.layoutChanged.emit()
 
-        self._data.dropna(inplace=True)
-        self._data.reset_index(drop=True, inplace=True)
-
+    def undo(self):
+        self._undo_stack.undo()
         self.layoutChanged.emit()
 
-    def valide_all_changes(self):
-        self.profile.x = self._data.iloc[:, 0]
-        self.profile.y = self._data.iloc[:, 1]
-        self.profile.z = self._data.iloc[:, 2]
-        self.profile.ld = self._data.iloc[:, 3]
+    def redo(self):
+        self._undo_stack.redo()
+        self.layoutChanged.emit()
 
 
 class Delegate(QtWidgets.QStyledItemDelegate):
@@ -375,48 +285,18 @@ class Delegate(QtWidgets.QStyledItemDelegate):
         self.setModelDataEvent = setModelDataEvent
 
     def createEditor(self, parent, option, index):
-        """
-        Args:
-            parent:
-            option:
-            index:
-        Returns:
-            Le widget (éditeur) pour éditer l'item se trouvant à l'index index.
-        """
         index.model().data(index, Qt.DisplayRole)
         return QtWidgets.QLineEdit(parent)
 
     def setEditorData(self, editor, index):
-        """
-        Args:
-            editor: l'éditeur
-            index: l'index
-        Returns: permet de transmettre à l'éditeur editor les données à afficher à partir du modèle se trouvant
-                à l'index index.
-        """
         value = index.model().data(index, Qt.DisplayRole)
         editor.setText(str(value))
 
     def setModelData(self, editor, model, index):
-        """
-        Args:
-            editor: l'éditeur
-            model: le modèle
-            index: l'index
-        Returns: permet de récupérer les données de l'éditeur et de les stocker à l'intérieur du modèle, à l'index
-                identifié par le paramètre index
-        """
         model.setData(index, editor.text())
 
         if not self.setModelDataEvent is None:
             self.setModelDataEvent()
 
     def updateEditorGeometry(self, editor, option, index):
-        """
-        Args:
-            editor: l'éditeur
-            option:
-            index: l'index
-        Returns: Permet de redimensionner l'éditeur à la bonne taille lorsque la taille de la vue change
-        """
         editor.setGeometry(option.rect)
diff --git a/src/View/Geometry/qtableview_reach.py b/src/View/Geometry/qtableview_reach.py
index bb984e83f685f99fbb2731b7011fd8d23fbe1d8c..7f0e498869668c8ff48444a6603330cad34b8307 100644
--- a/src/View/Geometry/qtableview_reach.py
+++ b/src/View/Geometry/qtableview_reach.py
@@ -178,11 +178,11 @@ class PandasModelEditable(QAbstractTableModel):
 
 
     def move_row_up(self, row, parent=QModelIndex()):
-        target = row + 2
-
         if row <= 0:
             return
 
+        target = row + 2
+
         self.beginMoveRows(parent, row - 1, row - 1, parent, target)
 
         self._undo_stack.push(
@@ -195,11 +195,11 @@ class PandasModelEditable(QAbstractTableModel):
         self.layoutChanged.emit()
 
     def move_row_down(self, row, parent=QModelIndex()):
-        target = row
-
         if row > self._reach.number_profiles:
             return
 
+        target = row
+
         self.beginMoveRows(parent, row + 1, row + 1, parent, target)
 
         self._undo_stack.push(
@@ -249,8 +249,8 @@ class Delegate(QtWidgets.QStyledItemDelegate):
         return QtWidgets.QLineEdit(parent)
 
     def setEditorData(self, editor, index):
-        value = index.model().data(index, Qt.DisplayRole)  # DisplayRole
-        editor.setText(str(value))  # récupère la valeur de la cellule applique la méthode définie dans setData
+        value = index.model().data(index, Qt.DisplayRole)
+        editor.setText(str(value))
 
     def setModelData(self, editor, model, index):
         model.setData(index, editor.text())