From e228a59537b02c33e0d12a1e3b2223eb32a91f51 Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Wed, 5 Apr 2023 10:28:40 +0200
Subject: [PATCH] subwindow, geomerty: Split ASubWindow into two classes and
 add missing file.

---
 src/Model/Geometry/ProfileXYZ.py        |   3 +-
 src/View/ASubWindow.py                  | 101 ++--
 src/View/ConfigureWindow.py             |   4 +-
 src/View/Geometry/qtableview_profile.py |   5 +-
 src/View/Geometry/window_profileXYZ.py  | 686 ++++++++++++++++++++++++
 src/View/GeometryWindow.py              |   7 +-
 src/View/MainWindow.py                  |   8 +-
 7 files changed, 757 insertions(+), 57 deletions(-)
 create mode 100644 src/View/Geometry/window_profileXYZ.py

diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py
index fcf3b62c..237ef6e3 100644
--- a/src/Model/Geometry/ProfileXYZ.py
+++ b/src/Model/Geometry/ProfileXYZ.py
@@ -5,6 +5,7 @@ import pandas as pd
 from typing import List
 
 from Model.Geometry.Profile import Profile
+from Model.Geometry.PointXYZ import PointXYZ
 
 class ProfileXYZ(Profile):
     def __init__(self, num: int = 0, code1: int = 0, code2: int = 0,
@@ -24,7 +25,7 @@ class ProfileXYZ(Profile):
         """
         super(ProfileXYZ, self).__init__(
             num = num,
-            name = name
+            name = name,
             kp = kp,
         )
 
diff --git a/src/View/ASubWindow.py b/src/View/ASubWindow.py
index 5133c997..f4fb1dd3 100644
--- a/src/View/ASubWindow.py
+++ b/src/View/ASubWindow.py
@@ -17,7 +17,58 @@ from PyQt5.QtCore import (
 )
 from PyQt5.uic import loadUi
 
-class ASubWindow(QDialog):
+class WindowToolKit(object):
+    def __init__(self, parent=None):
+        super(WindowToolKit, self).__init__()
+
+    def file_dialog(self, select_file=True, callback=lambda x: None):
+        """Open a new file dialog and send result to callback function
+
+        Args:
+            select_file: Select a file if True, else select a dir
+            callback: The callback function with one arguments, files
+                      selection list
+
+        Returns:
+            The returns of callback
+        """
+        dialog = QFileDialog(self)
+
+        if select_file:
+            mode = QFileDialog.FileMode.ExistingFile
+        else:
+            mode = QFileDialog.FileMode.Directory
+
+        dialog.setFileMode(mode)
+
+        if dialog.exec_():
+            file_names = dialog.selectedFiles()
+            return callback(file_names)
+
+    def message_box(self, text: str,
+                    informative_text: str,
+                    window_title: str = "Warning"):
+        """Open a new message box
+
+        Args:
+            text: Short text string
+            informative_text: Verbose text string
+            window_title: Title of message box window
+
+        Returns:
+            Nothing
+        """
+        msg = QMessageBox()
+
+        msg.setIcon(QMessageBox.Warning)
+        msg.setText(text)
+        msg.setInformativeText(informative_text)
+        msg.setWindowTitle(window_title)
+
+        msg.exec_()
+
+
+class ASubWindow(QDialog, WindowToolKit):
     def __init__(self, name="", ui="dummy", parent=None):
         super(ASubWindow, self).__init__(parent=parent)
         self.ui = loadUi(
@@ -66,7 +117,6 @@ class ASubWindow(QDialog):
             self.find(QLineEdit, name).setText(text)
         except AttributeError as e:
             print(e)
-            print(f"{name}")
 
     def get_line_edit_text(self, name:str):
         """Get text of line edit component
@@ -253,50 +303,3 @@ class ASubWindow(QDialog):
             Current text
         """
         return self.find(QComboBox, name).currentText()
-
-
-    # Custom dialog
-    def file_dialog(self, select_file=True, callback=lambda x: None):
-        """Open a new file dialog and send result to callback function
-
-        Args:
-            select_file: Select a file if True, else select a dir
-            callback: The callback function
-
-        Returns:
-            Nothing
-        """
-        dialog = QFileDialog(self)
-
-        if select_file:
-            mode = QFileDialog.FileMode.ExistingFile
-        else:
-            mode = QFileDialog.FileMode.Directory
-
-        dialog.setFileMode(mode)
-
-        if dialog.exec_():
-            file_names = dialog.selectedFiles()
-            callback(file_names)
-
-    def message_box(self, text: str,
-                    informative_text: str,
-                    window_title: str = "Warning"):
-        """Open a new message box
-
-        Args:
-            text: Short text string
-            informative_text: Verbose text string
-            window_title: Title of message box window
-
-        Returns:
-            Nothing
-        """
-        msg = QMessageBox()
-
-        msg.setIcon(QMessageBox.Warning)
-        msg.setText(text)
-        msg.setInformativeText(informative_text)
-        msg.setWindowTitle(window_title)
-
-        msg.exec_()
diff --git a/src/View/ConfigureWindow.py b/src/View/ConfigureWindow.py
index ff3f1b94..dbc37604 100644
--- a/src/View/ConfigureWindow.py
+++ b/src/View/ConfigureWindow.py
@@ -58,7 +58,9 @@ class SolverTableModel(QAbstractTableModel):
 
 class ConfigureWindow(ASubWindow, ListedSubWindow):
     def __init__(self, conf=None, title="Configure", parent=None):
-        super(ConfigureWindow, self).__init__(name=title, ui="ConfigureDialog", parent=parent)
+        super(ConfigureWindow, self).__init__(
+            name=title, ui="ConfigureDialog", parent=parent
+        )
         self.ui.setWindowTitle(title)
 
         if conf is None:
diff --git a/src/View/Geometry/qtableview_profile.py b/src/View/Geometry/qtableview_profile.py
index 7f59e1bf..267d8862 100644
--- a/src/View/Geometry/qtableview_profile.py
+++ b/src/View/Geometry/qtableview_profile.py
@@ -5,8 +5,9 @@ from PyQt5.QtGui import QFont
 from PyQt5.QtWidgets import QMessageBox
 from PyQt5 import QtWidgets, QtGui
 from PyQt5.QtCore import QModelIndex, Qt, QAbstractTableModel, QVariant, QCoreApplication
-from ProfileXYZ import ProfileXYZ
-import projection_pointXYZ
+
+from Model.Geometry.ProfileXYZ import ProfileXYZ
+from Model.Geometry.projection_pointXYZ import *
 
 _translate = QCoreApplication.translate
 
diff --git a/src/View/Geometry/window_profileXYZ.py b/src/View/Geometry/window_profileXYZ.py
new file mode 100644
index 00000000..9bf46c41
--- /dev/null
+++ b/src/View/Geometry/window_profileXYZ.py
@@ -0,0 +1,686 @@
+import copy
+import sys
+import csv
+from time import time
+
+from PyQt5 import QtWidgets, QtGui
+from PyQt5.QtCore import QModelIndex, Qt, QEvent, QCoreApplication
+from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QCheckBox
+
+from View.Geometry.mainwindow_ui_profile import Ui_MainWindow
+
+from Model.Geometry.Reach import Reach
+from View.Geometry.qtableview_profile import *
+
+from View.Plot.mpl_canvas_onpick_event import OnpickEvent
+
+_translate = QCoreApplication.translate
+
+
+class View(QMainWindow):
+
+    def __init__(self, profile_selected_num: int = 1, profile=None, pk=None, profile_name=None, parent=None):
+        self.parent = parent
+
+        super(View, self).__init__(self.parent)
+        self.ui = Ui_MainWindow()
+        self.ui.setupUi(self)
+        self.tableView = self.ui.tableView
+        self.my_canvas = self.ui.my_canvas
+        self.profile_selected_num = profile_selected_num
+
+        self.pk = pk
+        self.profile_name = profile_name
+
+        self.profile_selected = profile
+        self.window_title(self.pk, self.profile_name, self.profile_selected_num)
+
+        self.setup_model()
+        self.setup_connections()
+        self.graph()
+
+        self.model.dataChanged.connect(self.graph)
+        self.filters = "CSV files (*.csv)"
+        self.fileName = None
+
+        self.tableView.installEventFilter(self)
+        self.status_change_tableview = False
+
+        self.model.dataChanged.connect(self.tableview_is_modified)
+
+        self.reference_data = None
+        self.ui.btn_go_back.setEnabled(False)
+        self.ui.btn_check.setEnabled(False)
+        self.model.dataChanged.connect(self.set_enable_cancel_btn)
+        self.model.dataChanged.connect(self.set_enable_validate_changes_btn)
+        self.ui.btn_reset.setEnabled(False)
+        self.model.dataChanged.connect(self.set_enable_go_back_initial_state_btn)
+
+    def window_title(self, pk, profile_name=None, profile_selected_num=None):
+        if profile_name is None or profile_name == "":
+            profile_name = _translate("MainWindowProfile", "(sans nom)")
+        self.setWindowTitle(_translate("MainWindowProfile", f"Profil Pk = {pk} \t Section {profile_name}  "
+                                                            f"N°: {profile_selected_num + 1} (selon "
+                            f"l'ordre du tableau)"))
+
+    def tableview_is_modified(self):
+        self.status_change_tableview = True
+        print("tableview_is_modified... :", self.status_change_tableview)
+
+    def setup_connections(self):
+        self.ui.btn_sort_asc_x.clicked.connect(self.sort_X_ascending)
+        self.ui.btn_sort_desc_x.clicked.connect(self.sort_X_descending)
+        self.ui.btn_sort_asc_y.clicked.connect(self.sort_Y_ascending)
+        self.ui.btn_sort_desc_y.clicked.connect(self.sort_Y_descending)
+        self.ui.btn_move_up.clicked.connect(self.move_row_up)
+        self.ui.btn_move_down.clicked.connect(self.move_row_down)
+        self.ui.btn_export.clicked.connect(self.handleSave)
+        self.ui.btn_add.clicked.connect(self.insert_row)
+        self.ui.btn_delete.clicked.connect(self.delete_row)
+        self.ui.btn_copy.clicked.connect(self.copyTable)
+        self.ui.btn_paste.clicked.connect(self.pasteTable)
+        self.ui.btn_check.clicked.connect(self.validate_changes)
+
+        # self.button_moveUp.clicked.connect(self.graph)
+        # self.button_moveDown.clicked.connect(self.graph)
+        #        self.button_moveUp.clicked.connect(self.myslot)
+        self.ui.btn_go_back.clicked.connect(self.cancel_validate_changes)
+        self.ui.btn_reset.clicked.connect(self.go_back_to_initial_state)
+
+    def setup_model(self):
+        t0 = time()
+        if self.profile_selected is None:
+            # chemin = os.path.abspath(os.pardir)
+            # print(chemin)
+            # chemin = os.path.dirname(chemin)
+            # print(chemin)
+            # bief = reach.Bief(f"{chemin}/resources/Fichier_ST/Saar.ST")
+            bief = reach.Bief("../../resources/Fichier_ST/Saar.ST")
+            #  bief = reach.Bief("PamHyrPyQt5/resources/Fichier_ST/test_Pamhyr.st")
+            # bief = reach.Bief("/home/sylvain.coulibaly/Bureau/testpamhyr/test_Pamhyr.st")
+            # bief = reach.Bief("C:/Users/csylv/Fichiers-ST/Saar.ST")
+            # bief = reach.Bief("C:/Users/csylv/Fichiers-ST/testpamhyr/test_Pamhyr.st")
+            print("** Debut création du modèle **")
+            profile_num = 0
+            self.profile_default = bief.profile[profile_num]
+            self.window_title(bief.profile[0].pk, bief.profile[0].name, profile_num + 1)
+            self.model = qtableview_profile.PandasModelEditable(self.profile_default)
+            # self._last_saved_model_data = copy.deepcopy(self.profile_default)
+            # self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+            #
+            # self.reference_data = copy.deepcopy(self.profile_default)
+
+        else:
+            self.model = qtableview_profile.PandasModelEditable(self.profile_selected)
+            # self._last_saved_model_data = copy.deepcopy(self.profile_selected)
+            # self.reference_data = copy.deepcopy(self.profile_selected)
+        self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+        self.__initial_model_data = copy.deepcopy(self.model.model_data)
+
+        self.tableView.setModel(self.model)
+        self.tableView.setItemDelegate(qtableview_profile.Delegate())
+        print(" ==> Modèle créé au bout de :", time() - t0, "s")
+
+    def graph(self):
+        """
+        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
+        ld = self.model.name  # nom des points
+        x_carto = self.model.x  # x 'cartographique'
+        y_carto = self.model.y  # y 'cartographique'
+
+        if len(self.model.x) >= 3 and len(self.model.y) >= 3 and len(self.model.station) >= 3:
+
+            self.my_canvas.axes.cla()
+            self.my_canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
+            self.profile_line2D, = self.my_canvas.axes.plot(x, y, color='r', lw=1.5, markersize=7, marker='+',
+                                                            picker=30)
+            #          pickradius=5)
+            # print("type", type(self.profile_line2D))
+            # self.my_canvas.axes.plot(self.model.profile.iloc[:, 4], self.model.profile.iloc[:, 2], vertical_slider_label='y(x)', color='r',
+            # lw=1.5, markersize=7, marker='+')
+            self.my_canvas.axes.set_xlabel(_translate("MainWindowProfile", "Abscisse en travers (m)"), color='black',
+                                           fontsize=10)
+            self.my_canvas.axes.set_ylabel(_translate("MainWindowProfile", "Cote (m)"), color='black', fontsize=10)
+
+            # Ajout des étiquettes sur le graphe
+            self.annotation = []
+            for i, txt in enumerate(list(ld)):
+                annotation = self.my_canvas.axes.annotate(txt, (x[i], y[i]), horizontalalignment='left',
+                                                          verticalalignment='top', annotation_clip=True, fontsize=10,
+                                                          color='black')
+                annotation.set_position((x[i], y[i]))
+                annotation.set_color("black")
+                self.annotation.append(annotation)
+
+            al = 8.  # arrow length in points
+            arrowprops = dict(clip_on=True,  # plotting outside axes on purpose
+                              #    frac=1.,  # make end arrowhead the whole size of arrow
+                              headwidth=5.,  # in points
+                              facecolor='k')
+            kwargs = dict(
+                xycoords='axes fraction',
+                textcoords='offset points',
+                arrowprops=arrowprops,
+            )
+            self.my_canvas.axes.annotate("", (1, 0), xytext=(-al, 0), **kwargs)
+            self.my_canvas.axes.annotate("", (0, 1), xytext=(0, -al), **kwargs)  # left spin arrow
+            # self.my_canvas.axes.spines[['top', 'right']].set_visible(
+            #     False)  # pour enlever le 'cadre' en haut et à droite de la figure
+            self.my_canvas.axes.spines[['top', 'right']].set_color('none')
+            self.my_canvas.axes.yaxis.tick_left()  # enlève les traits de graduation sur le côté gauche du graphique
+            self.my_canvas.axes.xaxis.tick_bottom()  # enlève les traits de graduation sur la partie supérieure du graphique
+            self.my_canvas.axes.set_facecolor('#F9F9F9')  # '#E0FFFF')
+            self.my_canvas.figure.patch.set_facecolor('white')
+            try:
+                self.onpick_event = OnpickEvent(self.my_canvas.axes, x, y, x_carto, y_carto, self.tableView)
+                self.my_canvas.figure.canvas.mpl_connect('pick_event', self.onpick_event.onpick)
+
+                self.onclick_event = OnpickEvent(self.my_canvas.axes, x, y, x_carto, y_carto, self.tableView)
+                self.my_canvas.figure.canvas.mpl_connect('button_press_event', self.onclick_event.onclick)
+            except:
+                print("pas assez de points pour calculer la ligne d'eau")
+            self.my_canvas.figure.tight_layout()
+
+            self.my_canvas.figure.canvas.draw_idle()
+        else:
+            self.my_canvas.axes.cla()
+            self.my_canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
+
+        # def update_graphic(self):
+        #     self.profile_line2D.set_data(self.model.station, self.model.z)
+        #     print(self.annotation)
+        #     for i, txt in enumerate(list(self.model.name)):
+        #         self.annotation[i].set_text(txt)
+        #         self.annotation[i].set_position((self.model.station[i] + 0.5, self.model.z[i]))
+        #     # self.onpick_event = OnpickEvent(self.my_canvas.axes, self.model.station, self.model.z, self.model.x, self.model.y,
+        #     # self.tableView)
+        #     self.my_canvas.figure.canvas.mpl_connect('pick_event', self.onpick_event.onpick)
+        #
+        #     # self.onclick_event = OnpickEvent(self.my_canvas.axes, self.model.station, self.model.z, self.model.x,
+        #     # self.model.y, self.tableView)
+        #     self.my_canvas.figure.canvas.mpl_connect('button_press_event', self.onclick_event.onclick)
+        #
+        #     self.my_canvas.figure.canvas.draw_idle()
+
+    def insert_row(self):
+        if len(self.tableView.selectedIndexes()) == 0:
+            self.model.insertRows(self.model.rowCount(), 1)
+        else:
+            rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
+            for row in rows:  # [::-1]:
+                self.model.insertRows(row + 1, 1)
+        try:
+            self.graph()  # mise à jour du graphique
+        except:
+            pass
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def delete_row(self):
+        rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
+        # for row in rows:#[::-1]:
+        #     self.model.removeRows(row, 1)
+        #
+        # self.graph()  # mise à jour du graphique
+
+        if len(rows) > 0:
+            self.model.remove_rows(rows)
+
+        try:
+            self.graph()  # mise à jour du graphique
+        except:
+            print("Plus rien à afficher")
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def sort_X_ascending(self):
+        self.model.sort(0, order=Qt.AscendingOrder)
+        self.graph()
+        print("self.status_change_tableview ", self.status_change_tableview)
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def sort_X_descending(self):
+        self.model.sort(0, order=Qt.DescendingOrder)
+        self.graph()
+        print("initial_state_model", self._last_saved_model_data)
+        print("self.status_change_tableview ", self.status_change_tableview)
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def sort_Y_ascending(self):
+        self.model.sort(1, order=Qt.AscendingOrder)
+        self.graph()
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def sort_Y_descending(self):
+        self.model.sort(1, order=Qt.DescendingOrder)
+        self.graph()
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def move_row_down(self):
+        rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
+        for row in rows:
+            if row < self.model.rowCount() - 1:  # on s'arrête à la dernière ligne du tableau
+                self.model.moveRowDown(row)
+        self.graph()
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+        # print("profile.x ", self.model.profile.x)
+
+    def move_row_up(self):
+        rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
+        for row in rows:
+            if 0 < row:  # on s'arrête lorsqu'on est à l'index 0 ie la première ligne
+                self.model.moveRowUp(row)
+        self.graph()
+        print("status_change_tableview", self.status_change_tableview)
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+    def eventFilter(self, source, event):
+        if event.type() == QEvent.KeyPress:
+            if event == QtGui.QKeySequence.Copy:
+                # self.copySelection()
+                self.copyTable()
+                print("copie")
+                return True
+            elif event == QtGui.QKeySequence.Paste:
+                # self.pasteSelection()
+                self.pasteTable()
+                return True
+        elif event.type() == QEvent.ContextMenu:
+
+            # a context icons for the copy/paste operations
+            menu = QtWidgets.QMenu()
+            copyAction = menu.addAction('Copy')
+            # copyAction.triggered.connect(self.copySelection)
+            # self.button_copy.triggered.connect(self.copyTable)
+            pasteAction = menu.addAction('Paste')
+            # pasteAction.triggered.connect(self.pasteSelection)
+            #  self.button_paste.triggered.connect(self.pasteTable)
+            if not self.tableView.selectedIndexes():
+                pass
+                # no selection available, both copy and paste are disabled
+                # copyAction.setEnabled(False)
+
+            # if not self.clipboard:
+            #     pass
+            #     # no clipboard contents, paste is disabled
+            #  pasteAction.setEnabled(False)
+            #     self.button_paste.setEnabled(False)
+            #     icons.exec(event.globalPos())
+            # icons.exec(event.globalPos())
+            return True
+        # self.button_copy.setEnabled(False)
+        # self.button_paste.setEnabled(False)
+        return super(View, self).eventFilter(source, event)
+
+    def copySelection(self):
+        # clear the current contents of the clipboard
+        self.clipboard.clear()
+        selected = self.tableView.selectedIndexes()
+        rows = []
+        columns = []
+        # cycle all selected items to get the minimum row and column, so that the
+        # reference will always be [0, 0]
+        for index in selected:
+            rows.append(index.row())
+            columns.append(index.column())
+        minRow = min(rows)
+        minCol = min(columns)
+        print(minCol)
+        print(minRow, columns)
+        for index in selected:
+            # append the profile of each selected index
+            self.clipboard.append((index.row() - minRow, index.column() - minCol, index.data_frame()))
+            # self.clipboard.append((index.row(), index.column() , index.profile()))
+
+    def pasteSelection(self):
+        if not self.clipboard:
+            return
+        current = self.tableView.currentIndex()
+        if not current.isValid():
+            # in the rare case that there is no current index, use the first row
+            # and column as target
+            current = self.model.index(0, 0)
+
+        firstRow = current.row()
+        firstColumn = current.column()
+
+        # optional: get the selection model so that pasted indexes will be
+        # automatically selected at the end
+        selection = self.tableView.selectionModel()
+        print(self.clipboard)
+        for row, column, data in self.clipboard:
+            # get the index, with rows and columns relative to the current
+            # index = self.model.index(firstRow + row, firstColumn + column)
+            index = self.model.index(firstRow, column)
+
+            # set the profile for the index
+            self.model.setData(index, data, Qt.EditRole)  # Qt.DisplayRole)
+            # add the index to the selection
+            selection.select(index, selection.Select)
+
+        # apply the selection model
+        self.tableView.setSelectionModel(selection)
+
+    def copyTable(self):
+        if len(self.tableView.selectedIndexes()) == 0:
+            self.model.copyTable(0, self.model.rowCount())
+        else:
+            rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
+            rows.sort()  # pour trier la liste afin de respecter l'ordre des lignes sélectionnées en collant
+            print(rows)
+            df = self.model.model_data.loc[rows, :]
+            df.to_clipboard(header=None, index=False, excel=True, sep='\t')
+
+    def pasteTable(self):
+        # self.tableView.clearSelection()
+        if len(self.tableView.selectedIndexes()) == 0:
+
+            self.model.pasteTable(self.model.rowCount())
+        else:
+            rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
+            # rows.sort()  # pour trier la liste afin de respecter l'ordre des lignes sélectionnées en collant
+            for row in rows:
+                try:
+                    self.model.pasteTable(row + 1)
+                except:
+                    print("Le collage n'est pas réussi")
+            print(rows)
+        self.graph()
+        self.status_change_tableview = True
+        self.ui.btn_check.setEnabled(True)
+        self.ui.btn_go_back.setEnabled(True)
+        self.ui.btn_reset.setEnabled(True)
+
+        # try:
+        #     self.model.pasteTable()
+        #     self.graph()
+        #     print(" Collage réussi !")
+        # except:
+        #     pass
+        #     print("Le collage n'a pas été effectué. Vérifier que vous avez copié depuis un presse-papier au format "
+        #           "tabulé")
+
+    def handleSave(self):
+        print("handleSave")
+        if self.fileName is None or self.fileName == '':
+            self.fileName, self.filters = QFileDialog.getSaveFileName(self, filter=self.filters)
+        if self.fileName != '':
+            with open(self.fileName, 'w') as stream:
+                csvout = csv.writer(stream, delimiter='\t', quotechar=' ', escapechar=None,
+                                    quoting=csv.QUOTE_NONNUMERIC, lineterminator='\n')
+                # csvout.writerow(self.headers)
+                for row in range(self.model.rowCount(QModelIndex())):
+                    print(self.model.rowCount(QModelIndex()))
+                    rowdata = []
+                    for column in range(self.model.columnCount(QModelIndex())):
+                        item = self.model.index(row, column, QModelIndex()).data(Qt.DisplayRole)
+                        # if column == 0:
+                        #     rowdata.append('')
+                        #     continue
+
+                        if item is not None:
+                            rowdata.append(item)
+                        if item == 'nan':
+                            rowdata.remove(item)
+                        else:
+                            pass  # rowdata.append('')
+                    csvout.writerow(rowdata)
+                    print(rowdata)
+
+    def handleOpen(self):
+        print("handleOpen")
+        self.fileName, self.filterName = QFileDialog.getOpenFileName(self)
+
+        if self.fileName != '':
+            with open(self.fileName, 'r') as f:
+                reader = csv.reader(f, delimiter='\t')
+                header = next(reader)
+                buf = []
+                for row in reader:
+                    row[0] = QCheckBox("-")
+                    buf.append(row)
+
+                self.model = None
+                self.model = qtableview_profile.PandasModelEditable(buf)
+                self.tableView.setModel(self.model)
+                self.fileName = ''
+
+    def cancel_validate_changes(self):
+        # if self.profile_selected is None:
+        #     self._last_saved_model_data = copy.deepcopy(self.profile_default)
+        # else:
+        #     self._last_saved_model_data = copy.deepcopy(self.profile_selected)
+        # data = pd.DataFrame({"x": self._last_saved_model_data.x, "y": self._last_saved_model_data.y,
+        #                      "z": self._last_saved_model_data.z, "ld": self._last_saved_model_data.ld,
+        #                      "abs": projection_pointXYZ.get_station(self._last_saved_model_data)
+        #
+        #                      })
+        # self._data = data
+        # self.model.model_data = data
+
+        self.model.model_data = copy.deepcopy(self._last_saved_model_data)
+        print(self.model.model_data)
+
+        # print(data)
+        self.graph()
+        self.ui.btn_go_back.setEnabled(False)
+
+        # self.button_cancel_validate.setEnabled(False)
+        # self.status_change_tableview = True
+
+    def validate_changes(self):
+        # self.model.validate_changes()
+        self.remove_duplicates_point_names()
+        # self.model.delete_empty_rows()
+        self.delete_empty_rows()
+
+        # if self.profile_selected is None:
+        #     #self._last_saved_model_data = copy.deepcopy(self.profile_default)
+        #     self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+        #
+        #     # self.profile_selected == copy.deepcopy(
+        # else:
+        #     #self._last_saved_model_data = copy.deepcopy(self.profile_selected)
+        #     self._last_saved_model_data = copy.deepcopy(self.model.profile)
+        self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+        # self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+        self.ui.btn_check.setEnabled(False)
+        self.ui.btn_go_back.setEnabled(False)
+        self.status_change_tableview = False
+        self.graph()
+        self.model.valide_all_changes()
+        self.parent.update_graphic_2()
+        self.parent.update_graphic_1()
+        self.selected_row = list(set([index.row() for index in self.parent.tableView.selectedIndexes()]))
+        self.parent.update_graphic_3(self.selected_row[0])
+
+        # self._last_saved_model_data = copy.deepcopy(self.model.profile)
+
+    def set_enable_validate_changes_btn(self):
+        self.ui.btn_check.setEnabled(True)
+
+    def set_enable_cancel_btn(self):
+        self.ui.btn_go_back.setEnabled(True)
+
+    def set_enable_go_back_initial_state_btn(self):
+        self.ui.btn_reset.setEnabled(True)
+
+        # self._last_saved_model_data = copy.deepcopy(self.profile_default)
+
+    def delete_empty_rows(self):
+        if self.model.data_contains_nan():
+            buttonReply = QtWidgets.QMessageBox.question(self, _translate("MainWindowProfile", "Suppression les lignes "
+                                                                                               "incomplètes"),
+                                                         _translate("MainWindowProfile", "Supprimer les"
+                                                                                         " lignes des cellules non "
+                                                                                         "renseignées ?"),
+                                                         QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+            print(int(buttonReply))
+
+            if buttonReply == QtWidgets.QMessageBox.Yes:
+                print('Yes clicked.')
+                self.model.delete_empty_rows()
+                # self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+            if buttonReply == QtWidgets.QMessageBox.No:
+                # self._last_saved_model_data = copy.deepcopy(self.model.model_data)
+                print(self.model.model_data)
+                pass
+
+    def remove_duplicates_point_names(self):
+        counter_list = []
+        list_deleted_names = []
+        # is_removed = False
+        for ind, name_point in enumerate(self.model.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:
+                # is_removed = True
+                # self.msg_question(self.model.get_data(), ind)
+                # self._data.iat[ind, 3] = ""
+                # self._remove_duplicates_names()
+                if name_point not in list_deleted_names:
+                    list_deleted_names.append(name_point)
+                print("mon indddddd", ind)
+        if list_deleted_names:
+            self.msg_box_check_duplication_names(list_deleted_names)
+
+    def go_back_to_initial_state(self):
+
+        reply = QtWidgets.QMessageBox.question(self, _translate("MainWindowProfile", "Retour à l'état initial "),
+                                               _translate("MainWindowProfile", "Voulez-vous vraiment annuler toutes "
+                                                                               "les modifications?\n\n "
+                                                                               "\tOui : Revenir à l'état initial\n"
+                                                                               "\tNon : Garder l'état actuel des "
+                                                                               "données du profil"),
+                                               QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+
+        if reply == QtWidgets.QMessageBox.Yes:
+            self.model.model_data = copy.deepcopy(self.__initial_model_data)
+            self.model.valide_all_changes()
+            self.graph()
+            self.ui.btn_reset.setEnabled(False)
+            self.ui.btn_check.setEnabled(False)
+            self.ui.btn_go_back.setEnabled(False)
+        if reply == QtWidgets.QMessageBox.No:
+            pass
+
+    def closeEvent(self, event):
+        # self.vrai =True
+        if self.status_change_tableview:
+            reply = QtWidgets.QMessageBox.question(self,
+                                                   _translate("MainWindowProfile", "Terminer l'édition du profil "),
+                                                   _translate("MainWindowProfile", "Voulez-vous vraiment quitter "
+                                                                                   "?\n Oui : Valider et quitter\n"
+                                                                                   "Non : Annuler"),
+                                                   QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+
+            if reply == QtWidgets.QMessageBox.Yes:
+                self.validate_changes()
+                self.graph()
+                event.accept()
+            else:
+                # data = pd.DataFrame({"a": self._last_saved_model_data.x, "b": self._last_saved_model_data.y,
+                #                      "c": self._last_saved_model_data.z, "d": self._last_saved_model_data.ld,
+                #                      "e": projection_pointXYZ.get_station(self._last_saved_model_data)
+                #
+                #                      })
+                # # self._data = data
+                # self.model.model_data = data
+                #
+                event.ignore()
+                self.ui.btn_check.setEnabled(True)
+                self.ui.btn_go_back.setEnabled(True)
+
+    def msg_box_check_duplication_names(self, list_deleted_names):  # name_point,list_deleted_names,counter_list):
+        if len(list_deleted_names) == 1:
+            text = _translate("MainWindowProfile", "Le nom ''{}'' est dupliqué."
+                                                   " \n\nYes : Ne garder que la première occurrence. \nNo :"
+                                                   " Annuler la suppression.".format(list_deleted_names[0]))
+        else:
+            text = _translate("MainWindowProfile", "Les noms suivants :  \n{} sont dupliqués"
+                                                   "  \n\nYes : Ne garder que la première occurrence de "
+                                                   "chaque nom. \nNo : Annuler la suppression.".format(list_deleted_names))
+        buttonReply = QtWidgets.QMessageBox.question(self, _translate("MainWindowProfile",
+                                                                      "Suppression des noms répétés"),
+                                                     text,
+                                                     QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+        print(int(buttonReply))
+
+        if buttonReply == QtWidgets.QMessageBox.Yes:
+            print('Yes clicked.')
+            self.model.remove_duplicates_names()
+        if buttonReply == QtWidgets.QMessageBox.No:
+            print('No clicked.')
+        # if buttonReply == QtWidgets.QMessageBox.Cancel:
+        #     print('Cancel')
+
+        # def showDialog(self):
+        #     msgBox = QtWidgets.QMessageBox()
+        #     msgBox.setIcon(QtWidgets.QMessageBox.Information)
+        #     msgBox.setText("Message box pop up window")
+        #     msgBox.setWindowTitle("QMessageBox Example")
+        #     msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
+        #     msgBox.buttonClicked.connect(self.msgButtonClick)
+        #
+        #     returnValue = msgBox.exec()
+        #     if returnValue == QtWidgets.QMessageBox.Ok:
+        #         print('OK clicked')
+        #         self.model._remove_duplicates_names()
+
+    def msg_button_click(self, i):
+        print("Button clicked is:", i.text())
+
+    def ask_quit(self):
+        choice = QtWidgets.QMessageBox.question(self, _translate("MainWindowProfile", "Quittez ?"),
+                                                _translate("MainWindowProfile", "Etes-vous sûr de vouloir quitter ?"),
+                                                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+        return choice == QtWidgets.QMessageBox.Yes
+
+    def initial_state_model(self, profile_initial_state):
+        #  profile_initial_state = self.profile_selected
+        return profile_initial_state
+
+
+if __name__ == '__main__':
+    # http://www.xavierdupre.fr/blog/2014-04-12_nojs.html
+    # import cProfile
+    # import re
+
+    app = QApplication(sys.argv)
+    app.setStyle("Fusion")  # 'Breeze', 'Oxygen', 'QtCurve', 'Windows', 'Fusion'
+    table = View()
+    table.show()
+    # cProfile.run('View()')
+    # translator = QtCore.QTranslator(app)
+    # locale = QtCore.QLocale.system().name()
+    # path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)
+    # translator.load('qt_%s' % locale, path)
+    # app.installTranslator(translator)
+    sys.exit(app.exec_())
diff --git a/src/View/GeometryWindow.py b/src/View/GeometryWindow.py
index f6dfba24..c39d24da 100644
--- a/src/View/GeometryWindow.py
+++ b/src/View/GeometryWindow.py
@@ -16,13 +16,14 @@ from PyQt5.QtWidgets import (
 from View.Geometry.mainwindow_ui_reach import Ui_MainWindow
 from View.Geometry import qtableview_reach
 from View.Geometry import window_profileXYZ
+from View.ASubWindow import WindowToolKit
 
 _translate = QCoreApplication.translate
 
-class GeomatryWindow(ASubWindow):
+class GeometryWindow(QMainWindow, WindowToolKit):
     def __init__(self, parent=None):
         self.parent = parent
-        super(MainReach, self).__init__()
+        super(GeometryWindow, self).__init__(parent=parent)
 
         self.ui = Ui_MainWindow()
         self.ui.setupUi(self)
@@ -57,7 +58,7 @@ class GeomatryWindow(ASubWindow):
     def open_file_dialog(self):
         options = QFileDialog.Options()
         DEFAULT_DIRECTORY = '/home/'
-        settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', )  # application='MyApp', )
+        settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', )
         current_dir = settings.value('current_directory', DEFAULT_DIRECTORY, type=str)
         options |= QFileDialog.DontUseNativeDialog
         self.filename, _ = QtWidgets.QFileDialog.getOpenFileName(
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index 1a4af585..01e09bee 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -18,6 +18,7 @@ from View.DummyWindow import DummyWindow
 from View.ConfigureWindow import ConfigureWindow
 from View.NewStudyWindow import NewStudyWindow
 from View.NetworkWindow import NetworkWindow
+from View.GeometryWindow import GeometryWindow
 from View.AboutWindow import AboutWindow
 
 from Model.Study import Study
@@ -107,7 +108,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow):
             "action_toolBar_listing": self.open_dummy,
             ## Current actions
             "action_toolBar_network": self.open_network,
-            "action_toolBar_geometry": lambda: self.open_dummy("Geomerty"),
+            "action_toolBar_geometry": self.open_geometry,
             "action_toolBar_mesh": lambda: self.open_dummy("Mesh"),
             "action_toolBar_run_meshing_tool": lambda: self.open_dummy("Lancement mailleur externe"),
             "action_toolBar_boundary_cond": lambda: self.open_dummy("Condition Limites"),
@@ -291,6 +292,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow):
             self.network = NetworkWindow(model=self.model, parent=self)
             self.network.show()
 
+    def open_geometry(self):
+        if not self.model is None:
+            geometry = GeometryWindow(parent=self)
+            geometry.show()
+
     # TODO: Delete me !
     ###############
     # DUMMY STUFF #
-- 
GitLab