# 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 os import pathlib import sys import time from copy import deepcopy from tools import timer, trace from PyQt5 import QtWidgets from PyQt5.QtGui import ( QKeySequence, ) from PyQt5.QtCore import ( QModelIndex, Qt, QSettings, pyqtSlot, QItemSelectionModel, QCoreApplication, QSize ) from PyQt5.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QCheckBox, QUndoStack, QShortcut, ) from View.Geometry.PlotXY import PlotXY from View.Geometry.PlotKPC import PlotKPC from View.Geometry.PlotAC import PlotAC from View.ASubWindow import ASubMainWindow, WindowToolKit from View.ListedSubWindow import ListedSubWindow from View.Geometry.mainwindow_ui_reach import Ui_MainWindow from View.Geometry.Table import * from View.Geometry.Profile.Window import ProfileWindow _translate = QCoreApplication.translate class GeometryWindow(ASubMainWindow, ListedSubWindow): def __init__(self, model=None, title="Geometry", parent=None): self._title = title self.parent = parent super(GeometryWindow, self).__init__( name=self._title, parent=parent ) self._model = model self._reach = model.river.current_reach().reach self.ui = Ui_MainWindow() self.ui.setupUi(self) self.tableView = self.ui.tableView self.tableView_header = self.ui.tableView_header self._tablemodel = None self._profile_window = [] self._clipboard = None self.setup_window() self.setup_sc() self.setup_model() self.setup_plots() self.setup_connections() self.changed_slider_value() def setup_window(self): self._title = f"{self.ui.mainwindow_title} - {self._reach.name}" self.setWindowTitle(self._title) 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._tablemodel = TableEditableModel( headers = self.ui.tableView_header, reach = self._reach, undo = self._undo_stack ) self.tableView.setModel(self._tablemodel) self.tableView.setItemDelegate(Delegate()) def setup_plots(self): self.plot_xy() self.plot_kpc() self.plot_ac() def setup_connections(self): self.ui.btn_open.triggered.connect(self.open_file_dialog) self.ui.btn_sort_asc.triggered.connect(self.sort_ascending) self.ui.btn_sort_desc.triggered.connect(self.sort_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_end_editing.triggered.connect(self.handleSave) self.ui.btn_add.triggered.connect(self.insert_row) self.ui.btn_delete.triggered.connect(self.delete_rows) self.ui.btn_edit.triggered.connect(self.edit_profile) self.ui.verticalSlider.valueChanged.connect(self.changed_slider_value) self.ui.btn_slider_up.clicked.connect(self.decrement_value_slider) self.ui.btn_slider_down.clicked.connect(self.increment_value_slider) self.ui.btn_move_up.triggered.connect(self.changed_profile_slot) 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) # Profile selection when line change in table self.tableView.selectionModel()\ .selectionChanged\ .connect(self.select_current_profile) def open_file_dialog(self): options = QFileDialog.Options() settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', ) options |= QFileDialog.DontUseNativeDialog filename, _ = QtWidgets.QFileDialog.getOpenFileName( self, _translate("MainWindow_reach", "Ouvrir un fichier"), "", _translate("MainWindow_reach", "Fichiers .ST (*.ST)") + ";; " + _translate("MainWindow_reach", "Fichiers .M (*.M)") + ";; " + _translate("MainWindow_reach", "Tous les fichiers (*)"), options=options ) if filename != "": size = os.stat(filename).st_size self._reach.import_geometry(filename) self._tablemodel.layoutChanged.emit() self.update_profile_windows() self.plot_xy() self.plot_kpc() self.plot_ac() def messagebox_profile_editing(self): msg_box = QtWidgets.QMessageBox() msg_box.setIcon(QtWidgets.QMessageBox.Information) msg_box.setWindowTitle(_translate("MainWindow_reach", "Édition des profils sélectionnés")) msg_box.setText(_translate("MainWindow_reach", "Vous avez sélectionné plus de 5 profils." " \nSeuls les 5 premiers seront édités.")) msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) return_value = msg_box.exec() # if return_value == QtWidgets.QMessageBox.Ok: # print('OK clicked') def edit_profile(self): self.tableView.model().blockSignals(True) rows = list( set( (i.row() for i in self.tableView.selectedIndexes()) ) ) for row in rows: profile = self._reach.profile(row) win = ProfileWindow( profile = profile, parent = self, ) self._profile_window.append(win) win.show() self.tableView.model().blockSignals(False) pyqtSlot(bool) def changed_profile_slot(self, status): self.update_view1 = status def update_profile_windows(self): self.list_second_window = [] self.list_row = [] def plot_xy(self): self.tableView.model().blockSignals(True) self._plot_xy = PlotXY( canvas = self.ui.canvas_1, data = self._reach, toolbar = self.ui.toolbar_1 ) self._plot_xy.draw() self.tableView.model().blockSignals(False) def update_plot_xy(self): self.tableView.model().blockSignals(True) self._plot_xy.update() self.tableView.model().blockSignals(False) def plot_kpc(self): self.tableView.model().blockSignals(True) self._plot_kpc = PlotKPC( canvas = self.ui.canvas_2, data = self._reach, toolbar = self.ui.toolbar_2 ) self._plot_kpc.draw() self.tableView.model().blockSignals(False) def update_plot_kpc(self): self.tableView.model().blockSignals(True) self._plot_kpc.update() self.tableView.model().blockSignals(False) def plot_ac(self): self.tableView.model().blockSignals(True) self._plot_ac = PlotAC( canvas = self.ui.canvas_3, data = self._reach, toolbar = self.ui.toolbar_3, plot_xy = self._plot_xy ) self._plot_ac.draw() self.tableView.model().blockSignals(False) def update_plot_ac(self, ind: int): self.tableView.model().blockSignals(True) self._plot_ac.update(ind=ind) self.tableView.model().blockSignals(False) def get_station(self, ind: int): return self._reach.profile(ind).get_station() def get_elevation(self, ind: int): return self._reach.profile(ind).z() def select_plot_xy(self, ind: int): self.tableView.model().blockSignals(True) self._plot_xy.update(ind=ind) self.tableView.model().blockSignals(False) def select_plot_kpc(self, ind: int): self.tableView.model().blockSignals(True) self._plot_kpc.update(ind=ind) self.tableView.model().blockSignals(False) def select_plot_ac(self, ind: int): self.tableView.model().blockSignals(True) self._plot_ac.update(ind=ind) self.tableView.model().blockSignals(False) def select_row_profile_slider(self, ind: int = 0): if self.tableView is not None: selectionModel = self.tableView.selectionModel() index = self.tableView.model().index(ind, 0) selectionModel.select( index, QItemSelectionModel.Rows | QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Select ) self.tableView.scrollTo(index) def select_current_profile(self): self.tableView.model().blockSignals(True) if len(self.tableView.selectedIndexes()) > 0: row = self.index_selected_row() self.ui.verticalSlider.setValue(row) self.select_plot_xy(row) self.select_plot_kpc(row) self.select_plot_ac(row) self.tableView.model().blockSignals(False) def changed_slider_value(self): self.tableView.model().blockSignals(True) if self._tablemodel.rowCount() != 0: self.ui.verticalSlider.setMaximum(self._tablemodel.rowCount() - 1) slider_value = self.ui.verticalSlider.value() kp = self._reach.profile(slider_value).kp self.ui.vertical_slider_label.setText( _translate("MainWindow_reach", "Kp : ") + f"{kp}" + "\n" + _translate("MainWindow_reach", "Profil N° : ") + f"{slider_value + 1}" ) self.select_plot_xy(slider_value) self.select_plot_kpc(slider_value) self.select_row_profile_slider(slider_value) self.tableView.model().blockSignals(False) def increment_value_slider(self): if 0 <= self.ui.verticalSlider.value() < self._tablemodel.rowCount() - 1: self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1) def decrement_value_slider(self): if 0 < self.ui.verticalSlider.value() < self._tablemodel.rowCount(): self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1) def insert_row(self): if len(self.tableView.selectedIndexes()) == 0: self._tablemodel.insert_row(self._tablemodel.rowCount()) else: row = self.index_selected_row() self._tablemodel.insert_row(row + 1) def delete_rows(self): rows = sorted( list( set( [index.row() for index in self.tableView.selectedIndexes()] ) ) ) if len(rows) > 0: self._tablemodel.remove_rows(rows) self.update_plot_xy() self.select_current_profile() self.plot_kpc() self.changed_slider_value() def index_selected_row(self): return self.tableView\ .selectionModel()\ .selectedRows()[0]\ .row() def sort_ascending(self): self._tablemodel.sort_profiles(True) self.select_current_profile() self.changed_slider_value() def sort_descending(self): self._tablemodel.sort_profiles(False) self.select_current_profile() self.changed_slider_value() def move_row_up(self): row = self.index_selected_row() self._tablemodel.move_row_up(row) self.select_current_profile() def move_row_down(self): row = self.index_selected_row() self._tablemodel.move_row_down(row) self.select_current_profile() def duplicate(self): rows = [ row.row() for row in self.tableView.selectionModel().selectedRows() ] profiles = [] for row in rows: profiles.append( self._reach.profile(row) ) if len(profiles) == 0: return self._tablemodel.duplicate(rows, profiles) self.select_current_profile() def copy(self): rows = self.tableView\ .selectionModel()\ .selectedRows() table = [] table.append(["name", "kp"]) for row in rows: profile = self._reach.profile(row.row()) table.append( [profile.name, profile.kp] ) self.copyTableIntoClipboard(table) def paste(self): header, data = self.parseClipboardTable() if len(data) == 0: return if len(header) != 0: header.append("reach") for row in data: row.append(self._reach) row = self.index_selected_row() self._tablemodel.paste(row, header, data) self.select_current_profile() def undo(self): self._tablemodel.undo() self.select_current_profile() self.update_plot_xy() self.update_plot_kpc() def redo(self): self._tablemodel.redo() self.select_current_profile() self.update_plot_xy() self.update_plot_kpc() def handleSave(self): options = QFileDialog.Options() DEFAULT_DIRECTORY = '/home/' settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', ) current_dir = settings.value('current_directory', DEFAULT_DIRECTORY, type=str) options |= QFileDialog.DontUseNativeDialog filename, filters = QFileDialog.getSaveFileName( self, filter=_translate("MainWindow_reach", "Files .ST(*.ST or *.st)") + ";; " + _translate("MainWindow_reach", "All files " "(*)"), options=options ) current_dir = os.path.split(filename)[0] or DEFAULT_DIRECTORY if filename != '': self._tablemodel.export_reach(filename) def handleOpen(self): filename, filterName = QFileDialog.getOpenFileName(self) if filename != '': with open(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._tablemodel = None self._tablemodel = TableEditableModel(buf) self.tableView.setModel(self._tablemodel) filename = ''