# 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 . # -*- coding: utf-8 -*- import logging from datetime import datetime from tools import trace, timer from View.Tools.PamhyrWindow import PamhyrWindow from PyQt5.QtGui import ( QKeySequence, ) from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, QModelIndex, pyqtSlot, QItemSelectionModel, ) from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, QComboBox, QVBoxLayout, QHeaderView, QTabWidget, QSlider, QLabel, QWidget, QGridLayout, ) from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.Results.PlotXY import PlotXY from View.Results.PlotAC import PlotAC from View.Results.PlotKPC import PlotKPC from View.Results.PlotH import PlotH from View.Results.PlotSedReach import PlotSedReach from View.Results.PlotSedProfile import PlotSedProfile from View.Results.CustomPlot.Plot import CustomPlot from View.Results.CustomPlot.CustomPlotValuesSelectionDialog import ( CustomPlotValuesSelectionDialog, ) from View.Results.Table import TableModel from View.Results.translate import ResultsTranslate from View.Stricklers.Window import StricklersWindow _translate = QCoreApplication.translate logger = logging.getLogger() class ResultsWindow(PamhyrWindow): _pamhyr_ui = "Results" _pamhyr_name = "Results" def __init__(self, study=None, config=None, solver=None, results=None, parent=None): self._solver = solver self._results = results self._timestamps = sorted(list(self._results.get("timestamps"))) name = ( self._pamhyr_name + " - " + study.name + " - " + self._solver.name + " - " + self._results.date ) super(ResultsWindow, self).__init__( title=name, study=study, config=config, trad=ResultsTranslate(), parent=parent ) self._additional_plot = {} self.setup_table() self.setup_plot() self.setup_slider() self.setup_statusbar() self.setup_connections() def setup_table(self): self._table = {} for t in ["reach", "profile"]: table = self.find(QTableView, f"tableView_{t}") self._table[t] = TableModel( table_view=table, table_headers=self._trad.get_dict(f"table_headers_{t}"), data=self._results, undo=self._undo_stack, opt_data=t ) def setup_slider(self): self._slider_profile = self.find(QSlider, f"verticalSlider_profile") default_reach = self._results.river.reach(0) self._slider_profile.setMaximum(len(default_reach.profiles) - 1) self._slider_profile.setValue(0) self._slider_time = self.find(QSlider, f"horizontalSlider_time") self._slider_time.setMaximum(len(self._timestamps) - 1) self._slider_time.setValue(len(self._timestamps) - 1) def setup_plot(self): self.canvas = MplCanvas(width=5, height=4, dpi=100) self.canvas.setObjectName("canvas") self.toolbar = PamhyrPlotToolbar( self.canvas, self ) self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout.addWidget(self.toolbar) self.plot_layout.addWidget(self.canvas) self.plot_xy = PlotXY( canvas=self.canvas, results=self._results, reach_id=0, profile_id=0, toolbar=self.toolbar, display_current=False ) self.plot_xy.draw() self.canvas_2 = MplCanvas(width=5, height=4, dpi=100) self.canvas_2.setObjectName("canvas_2") self.toolbar_2 = PamhyrPlotToolbar( self.canvas_2, self ) self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2") self.plot_layout_2.addWidget(self.toolbar_2) self.plot_layout_2.addWidget(self.canvas_2) self.plot_kpc = PlotKPC( canvas=self.canvas_2, results=self._results, reach_id=0, profile_id=0, toolbar=self.toolbar_2 ) self.plot_kpc.draw() self.canvas_3 = MplCanvas(width=5, height=4, dpi=100) self.canvas_3.setObjectName("canvas_3") self.toolbar_3 = PamhyrPlotToolbar( self.canvas_3, self ) self.plot_layout_3 = self.find(QVBoxLayout, "verticalLayout_3") self.plot_layout_3.addWidget(self.toolbar_3) self.plot_layout_3.addWidget(self.canvas_3) self.plot_ac = PlotAC( canvas=self.canvas_3, results=self._results, reach_id=0, profile_id=0, toolbar=self.toolbar_3 ) self.plot_ac.draw() self.canvas_4 = MplCanvas(width=5, height=4, dpi=100) self.canvas_4.setObjectName("canvas_4") self.toolbar_4 = PamhyrPlotToolbar( self.canvas_4, self ) self.plot_layout_4 = self.find( QVBoxLayout, "verticalLayout_hydrograph") self.plot_layout_4.addWidget(self.toolbar_4) self.plot_layout_4.addWidget(self.canvas_4) self.plot_h = PlotH( canvas=self.canvas_4, results=self._results, reach_id=0, profile_id=0, toolbar=self.toolbar_4 ) self.plot_h.draw() self.canvas_5 = MplCanvas(width=5, height=4, dpi=100) self.canvas_5.setObjectName("canvas_5") self.toolbar_5 = PamhyrPlotToolbar( self.canvas_5, self ) self.plot_layout_5 = self.find(QVBoxLayout, "verticalLayout_sed_reach") self.plot_layout_5.addWidget(self.toolbar_5) self.plot_layout_5.addWidget(self.canvas_5) if self._study.river.has_sediment(): self.plot_sed_reach = PlotSedReach( canvas=self.canvas_5, results=self._results, reach_id=0, profile_id=0, toolbar=self.toolbar_5 ) self.plot_sed_reach.draw() self.canvas_6 = MplCanvas(width=5, height=4, dpi=100) self.canvas_6.setObjectName("canvas_6") self.toolbar_6 = PamhyrPlotToolbar( self.canvas_6, self ) self.plot_layout_6 = self.find( QVBoxLayout, "verticalLayout_sed_profile") self.plot_layout_6.addWidget(self.toolbar_6) self.plot_layout_6.addWidget(self.canvas_6) if self._study.river.has_sediment(): self.plot_sed_profile = PlotSedProfile( canvas=self.canvas_6, results=self._results, reach_id=0, profile_id=0, toolbar=self.toolbar_6 ) self.plot_sed_profile.draw() def _compute_status_label(self): # Timestamp ts = self._timestamps[self._slider_time.value()] t0 = datetime.fromtimestamp(0) fts = str( datetime.fromtimestamp(ts) - t0 ) fts.replace("days", _translate("Results", "days"))\ .replace("day", _translate("Results", "day")) # Reach table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() if len(indexes) == 0: reach = self._study.river.edges()[0] else: reach = self._study.river.edges()[indexes[0].row()] # Profile table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() if len(indexes) == 0: profile = reach.reach.profile(0) else: profile = reach.reach.profile(indexes[0].row()) pname = profile.name if profile.name != "" else profile.kp return (f"Reach: {reach.name} | " + f"Profile: {pname} | " + f"Timestamp : {fts} ({ts} sec)") def setup_statusbar(self): txt = self._compute_status_label() self._status_label = QLabel(txt) self.statusbar.addPermanentWidget(self._status_label) def update_statusbar(self): txt = self._compute_status_label() self._status_label.setText(txt) def setup_connections(self): # Action actions = { "action_add": self._add_custom_plot } for action in actions: self.find(QAction, action).triggered.connect( actions[action] ) # Table and Plot fun = { "reach": self._set_current_reach, "profile": self._set_current_profile, } for t in ["reach", "profile"]: table = self.find(QTableView, f"tableView_{t}") table.selectionModel()\ .selectionChanged\ .connect(fun[t]) self._table[t].dataChanged.connect(fun[t]) self._slider_profile.valueChanged.connect( self._set_current_profile_slider) self._slider_time.valueChanged.connect(self._set_current_timestamp) def update_table_selection_reach(self, ind): table = self.find(QTableView, f"tableView_reach") selectionModel = table.selectionModel() index = table.model().index(ind, 0) selectionModel.select( index, QItemSelectionModel.Rows | QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Select ) table.scrollTo(index) def update_table_selection_profile(self, ind): table = self.find(QTableView, f"tableView_profile") selectionModel = table.selectionModel() index = table.model().index(ind, 0) selectionModel.select( index, QItemSelectionModel.Rows | QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Select ) table.scrollTo(index) def update(self, reach_id=None, profile_id=None, timestamp=None): if reach_id is not None: self.plot_xy.set_reach(reach_id) self.plot_ac.set_reach(reach_id) self.plot_kpc.set_reach(reach_id) self.plot_h.set_reach(reach_id) if self._study.river.has_sediment(): self.plot_sed_reach.set_reach(reach_id) self.plot_sed_profile.set_reach(reach_id) self.update_table_selection_reach(reach_id) self.update_table_selection_profile(0) if profile_id is not None: self.plot_xy.set_profile(profile_id) self.plot_ac.set_profile(profile_id) self.plot_kpc.set_profile(profile_id) self.plot_h.set_profile(profile_id) if self._study.river.has_sediment(): self.plot_sed_reach.set_profile(profile_id) self.plot_sed_profile.set_profile(profile_id) self.update_table_selection_profile(profile_id) if timestamp is not None: self.plot_xy.set_timestamp(timestamp) self.plot_ac.set_timestamp(timestamp) self.plot_kpc.set_timestamp(timestamp) # self.plot_h.set_timestamp(timestamp) if self._study.river.has_sediment(): self.plot_sed_reach.set_timestamp(timestamp) self.plot_sed_profile.set_timestamp(timestamp) self.plot_xy.draw() self.plot_ac.draw() self.plot_kpc.draw() self.plot_h.draw() if self._study.river.has_sediment(): self.plot_sed_reach.draw() self.plot_sed_profile.draw() self.update_statusbar() def _get_current_reach(self): table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() if len(indexes) == 0: return 0 return indexes[0].row() def _get_current_profile(self): table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() if len(indexes) == 0: return 0 return indexes[0].row() def _get_current_timestamp(self): return self._timestamps[ self._slider_time.value() ] def _set_current_reach(self): table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() if len(indexes) == 0: return self.update(reach_id=indexes[0].row()) def _set_current_profile(self): table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() if len(indexes) == 0: return ind = indexes[0].row() self.update(profile_id=ind) self._slider_profile.setValue(ind) def _set_current_profile_slider(self): pid = self._slider_profile.value() self.update(profile_id=pid) return def _set_current_timestamp(self): timestamp = self._timestamps[self._slider_time.value()] self.update(timestamp=timestamp) def _add_custom_plot(self): dlg = CustomPlotValuesSelectionDialog(parent=self) if dlg.exec(): x, y = dlg.value self.create_new_tab_custom_plot(x, y) def create_new_tab_custom_plot(self, x:str, y:list): name = f"{x}: {','.join(y)}" wname = f"tab_custom_{x}_{y}" tab_widget = self.find(QTabWidget, f"tabWidget") # This plot already exists if name in self._additional_plot: tab_widget.setCurrentWidget( tab_widget.findChild(QWidget, wname) ) return widget = QWidget() grid = QGridLayout() widget.setObjectName(wname) canvas = MplCanvas(width=5, height=4, dpi=100) canvas.setObjectName(f"canvas_{x}_{y}") toolbar = PamhyrPlotToolbar( canvas, self ) plot = CustomPlot( x, y, self._get_current_reach(), self._get_current_profile(), self._get_current_timestamp(), data=self._results, canvas=canvas, toolbar=toolbar, trad=self._trad, parent=self, ) plot.draw() # Add plot to additional plot self._additional_plot[name] = plot grid.addWidget(toolbar, 0, 0) grid.addWidget(canvas, 1, 0) widget.setLayout(grid) tab_widget.addTab(widget, name) def _copy(self): logger.info("TODO: copy") def _paste(self): logger.info("TODO: paste") def _undo(self): self._table.undo() def _redo(self): self._table.redo()