From d499636cb674602fb4c0964ef2bf8c3a2ca09333 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr> Date: Wed, 9 Aug 2023 08:45:44 +0200 Subject: [PATCH] Results: Add tables and open window. --- src/Model/Results/Results.py | 5 ++ src/Model/Results/River/River.py | 20 ++++++- src/View/MainWindow.py | 33 ++++++++++++ src/View/Results/Table.py | 90 ++++++++++++++++++++++++++++++++ src/View/Results/Window.py | 5 +- src/View/Results/translate.py | 13 +++++ src/View/RunSolver/Window.py | 5 +- 7 files changed, 167 insertions(+), 4 deletions(-) diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py index b3a83e43..67698f4e 100644 --- a/src/Model/Results/Results.py +++ b/src/Model/Results/Results.py @@ -34,6 +34,11 @@ class Results(object): "creation_date": datetime.now(), } + @property + def date(self): + date = self._meta_data["creation_date"] + return f"{date.isoformat(sep=' ')}" + @property def river(self): return self._river diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py index 7425f532..22312a9a 100644 --- a/src/Model/Results/River/River.py +++ b/src/Model/Results/River/River.py @@ -26,6 +26,14 @@ class Profile(object): self._profile = profile # Source profile in the study self._data = {} # Dict of dict {<ts>: {<key>: <value>, ...}, ...} + @property + def name(self): + return self._profile.name + + @property + def kp(self): + return self._profile.kp + def set(self, timestamp, key, data): if timestamp not in self._data: self._data[timestamp] = {} @@ -54,10 +62,17 @@ class Reach(object): ) ) + @property + def name(self): + return self._reach.name + @property def profiles(self): return self._profiles.copy() + def profile(self, id): + return self._profiles[id] + def set(self, profile_id, timestamp, key, data): self._profiles[profile_id].set(timestamp, key, data) @@ -70,7 +85,10 @@ class River(object): @property def reachs(self): - return self.reachs.copy() + return self._reachs.copy() + + def reach(self, id): + return self._reachs[id] def add(self, reach_id): reachs = self._study.river.enable_edges() diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index d5508a10..559601c2 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -52,6 +52,7 @@ from View.Frictions.Window import FrictionsWindow from View.SolverParameters.Window import SolverParametersWindow from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow from View.CheckList.Window import CheckListWindow +from View.Results.Window import ResultsWindow from View.Debug.Window import ReplWindow from Model.Study import Study @@ -105,6 +106,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): # Model self.model = None + # Results + self._last_results = None + # UI self.ui = loadUi( os.path.join(os.path.dirname(__file__), "ui", "MainWindow.ui"), @@ -304,6 +308,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): for action in model_action: self.enable_actions(action, not no_model) + def set_results(self, results): + self._last_results = results + ############ # FEATURES # ############ @@ -638,6 +645,32 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) sol.show() + def open_solver_results(self, solver, results = None): + # If no specific results, get last results + if results is None: + results = self._last_results + + # No results available + if results is None: + return + + # Windows already opened + res = self.sub_win_filter_first( + "Results", + contain = [solver.name, results.date] + ) + + if res is None: + res = ResultsWindow( + study = self.model, + solver = solver, + results = results, + parent = self + ) + res.show() + else: + res.activateWindow() + ######### # DEBUG # ######### diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py index bfe2dbaa..8e167459 100644 --- a/src/View/Results/Table.py +++ b/src/View/Results/Table.py @@ -15,3 +15,93 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. # -*- coding: utf-8 -*- + +import logging +import traceback + +from tools import timer, trace + +from PyQt5.QtGui import ( + QKeySequence, QColor +) +from PyQt5.QtCore import ( + Qt, QAbstractTableModel, QModelIndex, + QVariant, pyqtSlot, QCoreApplication, +) +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, + QStyledItemDelegate, QLineEdit, QAbstractItemView, + QComboBox, +) + +from View.Results.translate import * + +logger = logging.getLogger() + +_translate = QCoreApplication.translate + + +class TableModel(QAbstractTableModel): + def __init__(self, results = None, study = None, mode = None, undo=None): + super(QAbstractTableModel, self).__init__() + + self._results = results + self._study = study + self._mode = mode + self._undo_stack = undo + + self._table_headers = table_headers_reach + if mode != "reach": + self._table_headers = table_headers_profile + + self._headers = list(self._table_headers.keys()) + + self._selected = 0 + self._timestamp = 0 + + def rowCount(self, parent=QModelIndex()): + if self._mode == "reach": + return len(self._results.river.reachs) + + current_reach = self._results.river.reach(self._selected) + return len(current_reach.profiles) + + def columnCount(self, parent=QModelIndex()): + return len(self._headers) + + def data(self, index, role=Qt.DisplayRole): + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + row = index.row() + column = index.column() + + if self._mode == "reach": + if self._headers[column] == "name": + v = self._results.river.reach(row).name + return str(v) + else: + current_reach = self._results.river.reach(self._selected) + if self._headers[column] == "name": + v = current_reach.profile(row).name + return str(v) + elif self._headers[column] == "kp": + v = current_reach.profile(row).kp + return f"{v:.4f}" + + return QVariant() + + def headerData(self, section, orientation, role=Qt.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return self._table_headers[self._headers[section]] + + return QVariant() + + def index(self, row, column, parent=QModelIndex()): + if not self.hasIndex(row, column, parent): + return QModelIndex() + + return self.createIndex(row, column, QModelIndex()) + + def flags(self, index): + return Qt.ItemIsEnabled | Qt.ItemIsSelectable diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 90955df5..e4c1b65c 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -75,7 +75,8 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): self._title = ( title + " - " + self._study.name + " - " - + self._solver.name + + self._solver.name + " - " + + self._results.date ) def setup_sc(self): @@ -153,7 +154,7 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): .selectionChanged\ .connect(fun[t]) - self._table.dataChanged.connect(fun[t]) + self._table[t].dataChanged.connect(fun[t]) def _set_current_reach(self): return diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index dad67d85..4b5ab3c5 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -15,3 +15,16 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. # -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers_reach = { + "name": _translate("Results", "Reach name"), +} + +table_headers_profile = { + "name": _translate("Results", "Name"), + "kp": _translate("Results", "KP (m)"), +} diff --git a/src/View/RunSolver/Window.py b/src/View/RunSolver/Window.py index b03a4603..872d72ef 100644 --- a/src/View/RunSolver/Window.py +++ b/src/View/RunSolver/Window.py @@ -219,6 +219,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow): self._process = self.new_process(self._parent) self._log(" *** Start", color="blue") + self._results = None self._solver.start(self._process) self.find(QAction, "action_start").setEnabled(False) @@ -251,7 +252,9 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow): self.find(QAction, "action_log_file").setEnabled(True) def results(self): - self._results = self._solver.results(self._study, self._workdir, qlog = self._output) + if self._results is None: + self._results = self._solver.results(self._study, self._workdir, qlog = self._output) + self._parent.open_solver_results(self._solver, self._results) def log_file(self): file_name = os.path.join(self._workdir, self._solver.log_file()) -- GitLab