# Window.py -- Pamhyr # Copyright (C) 2023 INRAE # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # -*- 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, QTableView, QAbstractItemView, QHeaderView, QVBoxLayout, QAction, ) from Model.Geometry.Reach import Reach from Model.Geometry.ProfileXYZ import ProfileXYZ from View.Tools.PamhyrWindow import PamhyrWindow from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.Geometry.Profile.Plot import Plot from View.Geometry.Profile.Table import GeometryProfileTableModel from View.Geometry.Profile.Translate import GeometryProfileTranslate _translate = QCoreApplication.translate class ProfileWindow(PamhyrWindow): _pamhyr_ui = "GeometryCrossSection" _pamhyr_name = "Geometry cross-section" def __init__(self, profile=None, study=None, config=None, parent=None): self._profile = profile name = f"{self._pamhyr_name} - {self._profile.name} {self._profile.kp}" super(ProfileWindow, self).__init__( title=name, study=study, config=config, trad=GeometryProfileTranslate(), parent=parent ) self._hash_data.append(profile) self.setup_table() self.setup_plot() self.setup_connections() def setup_table(self): table_headers = self._trad.get_dict("table_headers") table = self.find(QTableView, "tableView") self._tablemodel = GeometryProfileTableModel( table_view=table, table_headers=table_headers, editable_headers=["name", "x", "y", "z"], data=self._profile, undo=self._undo_stack ) table.setModel(self._tablemodel) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) def setup_plot(self): self._tablemodel.blockSignals(True) self._canvas = MplCanvas(width=3, height=4, dpi=100) self._canvas.setObjectName("canvas") self._toolbar = PamhyrPlotToolbar( self._canvas, self, items=["home", "zoom", "save", "iso", "back/forward", "move"] ) self._plot_layout = self.find(QVBoxLayout, "verticalLayout") self._plot_layout.addWidget(self._toolbar) self._plot_layout.addWidget(self._canvas) self._plot = Plot( canvas=self._canvas, data=self._profile, toolbar=self._toolbar, table=self.find(QTableView, "tableView") ) self._plot.draw() self._tablemodel.blockSignals(False) def setup_connections(self): actions = { "action_sort_asc": self.sort_X_ascending, "action_sort_des": self.sort_X_descending, "action_up": self.move_up, "action_down": self.move_down, "action_add": self.add, "action_delete": self.delete, } for action in actions: self.find(QAction, action)\ .triggered.connect(actions[action]) self._tablemodel.dataChanged.connect(self.update_plot) def update_plot(self): self._tablemodel.blockSignals(True) # TODO: Do not rebuild all graph self._plot.update() self._tablemodel.blockSignals(False) def index_selected_row(self): table = self.find(QTableView, "tableView") rows = table.selectionModel()\ .selectedRows() if len(rows) == 0: return 0 return rows[0].row() def add(self): table = self.find(QTableView, "tableView") if len(table.selectedIndexes()) == 0: self._tablemodel.insert_row(self._tablemodel.rowCount()) else: row = self.index_selected_row() self._tablemodel.insert_row(row + 1) self.update_plot() def delete(self): table = self.find(QTableView, "tableView") rows = sorted( list( set( [index.row() for index in table.selectedIndexes()] ) ) ) if len(rows) > 0: self._tablemodel.remove_rows(rows) self.update_plot() def sort_X_ascending(self): self._tablemodel.sort('x', order=Qt.AscendingOrder) self.update_plot() def sort_X_descending(self): self._tablemodel.sort('x', order=Qt.DescendingOrder) self.update_plot() def sort_Y_ascending(self): self._tablemodel.sort('y', order=Qt.AscendingOrder) self.update_plot() def sort_Y_descending(self): self._tablemodel.sort('y', order=Qt.DescendingOrder) self.update_plot() def move_down(self): rows = list( set( [index.row() for index in self.find(QTableView, "tableView").selectedIndexes()] ) ) for row in rows: if row < self._tablemodel.rowCount() - 1: self._tablemodel.move_down(row) self.update_plot() def move_up(self): rows = list( set( [index.row() for index in self.find(QTableView, "tableView").selectedIndexes()] ) ) for row in rows: if 0 < row: self._tablemodel.move_up(row) self.update_plot() def _copy(self): table = self.find(QTableView, "tableView") rows = table.selectionModel().selectedRows() data = [] data.append(["x", "y", "z", "name"]) for row in rows: point = self._profile.point(row.row()) data.append( [ point.x, point.y, point.z, point.name ] ) self.copyTableIntoClipboard(data) 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._tablemodel.paste(row, header, data) self.update_plot() def _undo(self): self._tablemodel.undo() self.update_plot() def _redo(self): self._tablemodel.redo() self.update_plot()