diff --git a/src/Checker/Checker.py b/src/Checker/Checker.py index 92199b8d7b51a8a8d3f7885401294790ead18764..6e87a601a34a497a1b7974602ffa6cdc198e072a 100644 --- a/src/Checker/Checker.py +++ b/src/Checker/Checker.py @@ -26,6 +26,10 @@ class AbstractModelChecker(object): def description(self): return self._description + @property + def summary(self): + return self._summary + # Checker status def is_unknown(self): @@ -53,11 +57,3 @@ class AbstractModelChecker(object): otherelse """ raise NotImplementedMethodeError(self, self.run) - - def summary(self): - """Return summary message - - Returns: - Return summary string - """ - raise NotImplementedMethodeError(self, self.summary) diff --git a/src/Checker/Study.py b/src/Checker/Study.py new file mode 100644 index 0000000000000000000000000000000000000000..d0b84f21775ae33ec42eaed0d0b08da32cae9106 --- /dev/null +++ b/src/Checker/Study.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +from Checker.Checker import AbstractModelChecker, STATUS + +_translate = QCoreApplication.translate + + +class StudyNetworkReachChecker(AbstractModelChecker): + def __init__(self): + super(StudyNetworkReachChecker, self).__init__() + + self._name = _translate("Checker", "Study network reach checker") + self._description = _translate("Checker", "Check if exists at least one reach for study") + + def run(self, study): + if study is None: + self._status = STATUS.ERROR + self._summary = "invalid_study" + return False + + river = study.river + if river is None: + self._status = STATUS.ERROR + self._summary = "no_river_found" + return False + + if len(river.edges()) == 0: + self._status = STATUS.ERROR + self._summary = "no_reach_defined" + return False + + self._summary = "ok" + self._status = STATUS.OK + return True diff --git a/src/Model/Study.py b/src/Model/Study.py index 3ce31c43bf4a07151ec509f4777a4f17648cf2e3..5cc485c2245cd7a8c53ac8b679edf4beecd9b9aa 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -8,6 +8,7 @@ from Model.Serializable import Serializable from Model.River import River +from Checker.Study import * class Study(Serializable): def __init__(self): @@ -31,6 +32,14 @@ class Study(Serializable): # Study data self._river = River(status = self.status) + @classmethod + def checkers(cls): + lst = [ + StudyNetworkReachChecker(), + ] + + return lst + @property def river(self): return self._river diff --git a/src/Solver/ASolver.py b/src/Solver/ASolver.py index 830d296e32be39625ba45fbff9e6065090392112..ba1e511e160d27fdf5b53d9146bb381db3e951c7 100644 --- a/src/Solver/ASolver.py +++ b/src/Solver/ASolver.py @@ -56,7 +56,7 @@ class AbstractSolver(object): return lst @classmethod - def checker(cls): + def checkers(cls): lst = [ ] diff --git a/src/View/CheckList/Table.py b/src/View/CheckList/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..071b49076d6c8687680415a8c224029344303774 --- /dev/null +++ b/src/View/CheckList/Table.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, + QCoreApplication, QModelIndex, pyqtSlot, + QRect, +) + +from PyQt5.QtGui import ( + QColor, QBrush, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QPushButton, QLineEdit, + QFileDialog, QTableView, QAbstractItemView, + QUndoStack, QShortcut, QAction, QItemDelegate, + QComboBox, +) + +_translate = QCoreApplication.translate + + +class TableModel(QAbstractTableModel): + def __init__(self, data=None): + super(QAbstractTableModel, self).__init__() + self._headers = ["name", "status"] + self._data = data + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + return options + + def rowCount(self, parent): + return len(self._data) + + def columnCount(self, parent): + return len(self._headers) + + def data(self, index, role): + row = index.row() + column = index.column() + + if role == Qt.ForegroundRole: + if self._headers[column] == "status": + color = Qt.gray + + if self._data[row].is_ok(): + color = Qt.green + elif self._data[row].is_warning(): + color = Qt.yellow + elif self._data[row].is_error(): + color = Qt.red + + return QBrush(color) + return QVariant() + + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + if self._headers[column] == "name": + return self._data[row].name + if self._headers[column] == "status": + return self._data[row].summary + + return QVariant() + + def headerData(self, section, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return self._headers[section] + + return QVariant() + + def update(self): + self.layoutChanged.emit() diff --git a/src/View/CheckList/Window.py b/src/View/CheckList/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..c83bda9e2317bf750f3e96ad9eca04ea76a42dc0 --- /dev/null +++ b/src/View/CheckList/Window.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +import time +import threading + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, + QCoreApplication, QModelIndex, pyqtSlot, + QRect, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QPushButton, QLineEdit, + QFileDialog, QTableView, QAbstractItemView, + QUndoStack, QShortcut, QAction, QItemDelegate, + QComboBox, QVBoxLayout, QHeaderView, QTabWidget, + QProgressBar, +) + +from View.CheckList.Table import TableModel + +_translate = QCoreApplication.translate + +def _run_checker_list(study, checker_list, parent): + parent.start_compute() + for checker in checker_list: + time.sleep(1) + checker.run(study) + parent.progress() + parent.end_compute() + +class CheckListWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Check list", + study=None, config=None, + solver=None, parent=None): + self._title = title + " - " + study.name + + self._study = study + self._config = config + self._solver = solver + + super(CheckListWindow, self).__init__( + name=self._title, ui="CheckList", parent=parent + ) + self.ui.setWindowTitle(self._title) + + self._checker_list = ( + self._study.checkers() + self._solver.checkers() + ) + + self.setup_table() + self.setup_progress_bar() + self.setup_connections() + self.setup_thread() + + def setup_table(self): + table = self.find(QTableView, f"tableView") + + self._table = TableModel( + data = self._checker_list, + ) + + table.setModel(self._table) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_progress_bar(self): + self._progress = self.find(QProgressBar, f"progressBar") + self._p = 0 # Progress current step + + self._progress.setRange(0, len(self._checker_list)) + self._progress.setValue(self._p) + + def setup_connections(self): + self.find(QPushButton, "pushButton_ok").clicked.connect(self.accept) + self.find(QPushButton, "pushButton_retry").clicked.connect(self.retry) + self.find(QPushButton, "pushButton_cancel").clicked.connect(self.reject) + + def setup_thread(self): + self._t1 = threading.Thread( + target=_run_checker_list, + args=(self._study, self._checker_list, self,) + ) + self._t1.start() + + def progress(self): + self._p += 1 + self._progress.setValue(self._p) + # self._table.update() + + def start_compute(self): + self._p = 0 + self._progress.setValue(self._p) + + def end_compute(self): + self._table.layoutChanged.emit() + self.find(QPushButton, "pushButton_retry").setEnabled(True) + + errors = list(filter(lambda c: c.is_error(), self._checker_list)) + if len(errors) == 0: + self.find(QPushButton, "pushButton_ok").setEnabled(True) + + def end(self): + # self._t1.join() + self.close() + + def retry(self): + self._t1.join() + self._t1 = threading.Thread( + target=_run_checker_list, + args=(self._study, self._checker_list, self,) + ) + + self.find(QPushButton, "pushButton_retry").setEnabled(False) + self.find(QPushButton, "pushButton_ok").setEnabled(False) + + self._t1.start() + + def reject(self): + print("cancel") + self.end() + + def accept(self): + print("ok") + self.end() diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index bc79d2fb97e7321deb0361adc23406aeddfd8ac8..b7cfa96ec2fab79856d881c39ba4f26a13e28f04 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -29,6 +29,7 @@ from View.Stricklers.Window import StricklersWindow from View.Sections.Window import SectionsWindow from View.SolverParameters.Window import SolverParametersWindow from View.RunSolver.Window import SelectSolverWindow +from View.CheckList.Window import CheckListWindow from Model.Study import Study @@ -382,6 +383,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) if run.exec(): solver = run.solver + check = CheckListWindow( + study = self.model, + config = self.conf, + solver = solver, + parent = self + ) + check.show() # TODO: Delete me ! ############### diff --git a/src/View/ui/CheckList.ui b/src/View/ui/CheckList.ui index 79589e5e3e4c6ec18e7378e321b7bf76ca376a32..06e6403c1569e3acd353cd980ccc5614cb319f2f 100644 --- a/src/View/ui/CheckList.ui +++ b/src/View/ui/CheckList.ui @@ -16,7 +16,7 @@ <widget class="QWidget" name="centralwidget"> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <widget class="QListView" name="listView"/> + <widget class="QTableView" name="tableView"/> </item> <item row="1" column="0"> <widget class="QProgressBar" name="progressBar"> @@ -26,31 +26,52 @@ </widget> </item> <item row="2" column="0"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry</set> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pushButton_cancel"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_retry"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Retry</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_ok"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Ok</string> + </property> + </widget> + </item> + </layout> </item> </layout> </widget> <widget class="QStatusBar" name="statusbar"/> - <widget class="QToolBar" name="toolBar"> - <property name="windowTitle"> - <string>toolBar</string> - </property> - <attribute name="toolBarArea"> - <enum>TopToolBarArea</enum> - </attribute> - <attribute name="toolBarBreak"> - <bool>false</bool> - </attribute> - <addaction name="action_retry"/> - <addaction name="action_cancel"/> - </widget> <action name="action_retry"> <property name="icon"> <iconset>