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