# -*- coding: utf-8 -*- import copy import sys import csv from time import time from tools import trace, timer from PyQt5.QtGui import ( QKeySequence, ) from PyQt5.QtCore import ( QModelIndex, Qt, QEvent, QCoreApplication ) from PyQt5.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QCheckBox, QUndoStack, QShortcut, ) from Model.Geometry.Reach import Reach from Model.Geometry.ProfileXYZ import ProfileXYZ from View.ASubWindow import WindowToolKit from View.Geometry.Profile.mainwindow_ui_profile import Ui_MainWindow from View.Geometry.Profile.Plot import Plot from View.Geometry.Profile.Table import * _translate = QCoreApplication.translate class ProfileWindow(QMainWindow, WindowToolKit): def __init__(self, profile=None, parent=None): self.parent = parent super(ProfileWindow, self).__init__(self.parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self._profile = profile self._model = None self.setup_window() self.setup_sc() self.setup_model() self.setup_connections() self.plot() self._model.dataChanged.connect(self.update_plot) self.fileName = None # self.ui.tableView.installEventFilter(self) # self._model.dataChanged.connect(self.tableview_is_modified) # 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 setup_window(self): header = _translate("MainWindowProfile", "Profile") name = self._profile.name if (name is None) or (name == ""): name = _translate("MainWindowProfile", "(no name)") self.setWindowTitle( header + " - " + f"{self._profile.reach.name}" + " - " + f"{name} ({self._profile.kp})" ) def setup_sc(self): self._undo_stack = QUndoStack() self.undo_sc = QShortcut(QKeySequence.Undo, self) self.redo_sc = QShortcut(QKeySequence.Redo, self) self.copy_sc = QShortcut(QKeySequence.Copy, self) self.paste_sc = QShortcut(QKeySequence.Paste, self) def setup_model(self): self._model = TableEditableModel( profile = self._profile, undo = self._undo_stack ) self.ui.tableView.setModel(self._model) self.ui.tableView.setItemDelegate(Delegate()) def setup_connections(self): self.ui.btn_sort_asc_x.triggered.connect(self.sort_X_ascending) self.ui.btn_sort_desc_x.triggered.connect(self.sort_X_descending) self.ui.btn_sort_asc_y.triggered.connect(self.sort_Y_ascending) self.ui.btn_sort_desc_y.triggered.connect(self.sort_Y_descending) self.ui.btn_move_up.triggered.connect(self.move_row_up) self.ui.btn_move_down.triggered.connect(self.move_row_down) self.ui.btn_export.triggered.connect(self.handleSave) self.ui.btn_add.triggered.connect(self.insert_row) self.ui.btn_delete.triggered.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) self.undo_sc.activated.connect(self.undo) self.redo_sc.activated.connect(self.redo) self.copy_sc.activated.connect(self.copy) self.paste_sc.activated.connect(self.paste) def plot(self): self.ui.tableView.model().blockSignals(True) self._plot = Plot( canvas = self.ui.canvas, data = self._profile, toolbar = None, table = self.ui.tableView, ) self._plot.draw() self.ui.tableView.model().blockSignals(False) def update_plot(self): self.ui.tableView.model().blockSignals(True) # TODO: Do not rebuild all graph self._plot.update() self.ui.tableView.model().blockSignals(False) def index_selected_row(self): rows = self.ui.tableView\ .selectionModel()\ .selectedRows() if len(rows) == 0: return 0 return self.ui.tableView\ .selectionModel()\ .selectedRows()[0]\ .row() def insert_row(self): if len(self.ui.tableView.selectedIndexes()) == 0: self._model.insert_row(self._model.rowCount()) else: row = self.index_selected_row() self._model.insert_row(row + 1) self.update_plot() def delete_row(self): rows = sorted( list( set( [index.row() for index in self.ui.tableView.selectedIndexes()] ) ) ) if len(rows) > 0: self._model.remove_rows(rows) self.update_plot() def sort_X_ascending(self): self._model.sort('x', order=Qt.AscendingOrder) self.update_plot() def sort_X_descending(self): self._model.sort('x', order=Qt.DescendingOrder) self.update_plot() def sort_Y_ascending(self): self._model.sort('y', order=Qt.AscendingOrder) self.update_plot() def sort_Y_descending(self): self._model.sort('y', order=Qt.DescendingOrder) self.update_plot() def move_row_down(self): rows = list( set( [index.row() for index in self.ui.tableView.selectedIndexes()] ) ) for row in rows: if row < self._model.rowCount() - 1: self._model.move_row_down(row) self.update_plot() def move_row_up(self): rows = list( set( [index.row() for index in self.ui.tableView.selectedIndexes()] ) ) for row in rows: if 0 < row: self._model.move_row_up(row) self.update_plot() def copy(self): rows = self.ui.tableView\ .selectionModel()\ .selectedRows() table = [] table.append(["x", "y", "z", "name"]) for row in rows: point = self._profile.point(row.row()) table.append( [ point.x, point.y, point.z, point.name ] ) self.copyTableIntoClipboard(table) def paste(self): header, data = self.parseClipboardTable() if len(data) == 0: return if len(header) != 0: header.append("profile") for row in data: row.append(self._profile) row = self.index_selected_row() self._model.paste(row, header, data) self.update_plot() def undo(self): self._model.undo() self.update_plot() def redo(self): self._model.redo() self.update_plot() def handleSave(self): if self.fileName is None or self.fileName == '': self.fileName, self.filters = QFileDialog.getSaveFileName( self, filter="CSV files (*.csv)" ) 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 = TableEditableModel(buf) self.ui.tableView.setModel(self._model) self.fileName = '' 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 closeEvent(self, event): print("TODO: Close") # 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.plot() # 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