mirror of https://gitlab.com/pamhyr/pamhyr2
561 lines
21 KiB
Python
561 lines
21 KiB
Python
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
|
|
|
|
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.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):
|
|
if self.profile_selected is None:
|
|
bief = reach.Bief("../../resources/Fichier_ST/Saar.ST")
|
|
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)
|
|
else:
|
|
self.model = qtableview_profile.PandasModelEditable(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())
|
|
|
|
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
|
|
)
|
|
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
|
|
)
|
|
|
|
# Add label on graph
|
|
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_color('none')
|
|
self.my_canvas.axes.yaxis.tick_left()
|
|
self.my_canvas.axes.xaxis.tick_bottom()
|
|
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("TODO")
|
|
|
|
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 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()]))
|
|
|
|
if len(rows) > 0:
|
|
self.model.remove_rows(rows)
|
|
|
|
try:
|
|
self.graph()
|
|
except:
|
|
print("TODO")
|
|
|
|
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()
|
|
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()
|
|
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:
|
|
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)
|
|
|
|
def move_row_up(self):
|
|
rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
|
|
|
|
for row in rows:
|
|
if 0 < row:
|
|
self.model.moveRowUp(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)
|
|
|
|
def eventFilter(self, source, event):
|
|
if event.type() == QEvent.KeyPress:
|
|
if event == QtGui.QKeySequence.Copy:
|
|
self.copyTable()
|
|
return True
|
|
|
|
elif event == QtGui.QKeySequence.Paste:
|
|
self.pasteTable()
|
|
return True
|
|
|
|
elif event.type() == QEvent.ContextMenu:
|
|
menu = QtWidgets.QMenu()
|
|
copyAction = menu.addAction('Copy')
|
|
pasteAction = menu.addAction('Paste')
|
|
|
|
if not self.tableView.selectedIndexes():
|
|
pass
|
|
|
|
return True
|
|
|
|
return super(View, self).eventFilter(source, event)
|
|
|
|
def copySelection(self):
|
|
self.clipboard.clear()
|
|
selected = self.tableView.selectedIndexes()
|
|
rows = []
|
|
columns = []
|
|
|
|
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:
|
|
self.clipboard.append((index.row() - minRow,
|
|
index.column() - minCol,
|
|
index.data_frame()))
|
|
|
|
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()
|
|
|
|
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)
|
|
# 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()
|
|
df = self.model.model_data.loc[rows, :]
|
|
df.to_clipboard(header=None, index=False, excel=True, sep='\t')
|
|
|
|
def pasteTable(self):
|
|
if len(self.tableView.selectedIndexes()) == 0:
|
|
self.model.pasteTable(self.model.rowCount())
|
|
else:
|
|
rows = list(set([index.row() for index in self.tableView.selectedIndexes()]))
|
|
for row in rows:
|
|
try:
|
|
self.model.pasteTable(row + 1)
|
|
except:
|
|
print("TODO")
|
|
|
|
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 handleSave(self):
|
|
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')
|
|
|
|
for row in range(self.model.rowCount(QModelIndex())):
|
|
rowdata = []
|
|
for column in range(self.model.columnCount(QModelIndex())):
|
|
item = self.model.index(
|
|
row, column, QModelIndex()
|
|
).data(Qt.DisplayRole)
|
|
|
|
if item is not None:
|
|
rowdata.append(item)
|
|
|
|
if item == 'nan':
|
|
rowdata.remove(item)
|
|
|
|
csvout.writerow(rowdata)
|
|
|
|
def handleOpen(self):
|
|
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):
|
|
self.model.model_data = copy.deepcopy(self._last_saved_model_data)
|
|
self.graph()
|
|
self.ui.btn_go_back.setEnabled(False)
|
|
|
|
def validate_changes(self):
|
|
self.remove_duplicates_point_names()
|
|
self.delete_empty_rows()
|
|
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])
|
|
|
|
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)
|
|
|
|
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
|
|
)
|
|
|
|
if buttonReply == QtWidgets.QMessageBox.Yes:
|
|
self.model.delete_empty_rows()
|
|
|
|
if buttonReply == QtWidgets.QMessageBox.No:
|
|
pass
|
|
|
|
def remove_duplicates_point_names(self):
|
|
counter_list = []
|
|
list_deleted_names = []
|
|
|
|
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:
|
|
if name_point not in list_deleted_names:
|
|
list_deleted_names.append(name_point)
|
|
|
|
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)
|
|
elif reply == QtWidgets.QMessageBox.No:
|
|
pass
|
|
|
|
def closeEvent(self, event):
|
|
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:
|
|
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
|
|
)
|
|
|
|
if buttonReply == QtWidgets.QMessageBox.Yes:
|
|
self.model.remove_duplicates_names()
|
|
|
|
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):
|
|
return profile_initial_state
|