From 113fafac501df74d9ee46dbf1f5b8e95c7122629 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr> Date: Tue, 16 May 2023 16:24:49 +0200 Subject: [PATCH] Stricklers: Add base of code. --- src/Model/Geometry/Reach.py | 6 +- src/Model/River.py | 7 +- src/Model/Stricklers/Stricklers.py | 47 ++++++++ src/Model/Stricklers/StricklersList.py | 62 ++++++++++ src/View/LateralContribution/Table.py | 10 +- src/View/MainWindow.py | 8 +- src/View/Stricklers/Table.py | 149 +++++++++++++++++++++++ src/View/Stricklers/UndoCommand.py | 156 +++++++++++++++++++++++++ src/View/Stricklers/Window.py | 120 +++++++++++++++++++ src/View/Stricklers/translate.py | 16 +++ src/View/ui/Stricklers.ui | 100 ++++++++++++++++ 11 files changed, 669 insertions(+), 12 deletions(-) create mode 100644 src/Model/Stricklers/Stricklers.py create mode 100644 src/Model/Stricklers/StricklersList.py create mode 100644 src/View/Stricklers/Table.py create mode 100644 src/View/Stricklers/UndoCommand.py create mode 100644 src/View/Stricklers/Window.py create mode 100644 src/View/Stricklers/translate.py create mode 100644 src/View/ui/Stricklers.ui diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index f96cfe59..8a174517 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -202,10 +202,10 @@ class Reach: return [profile.kp for profile in self.profiles] def get_kp_min(self): - return min([profile.kp for profile in self.profiles]) + return min(self.get_kp()) def get_kp_max(self): - return max([profile.kp for profile in self.profiles]) + return max(self.get_kp()) # Guidelines @@ -237,7 +237,7 @@ class Reach: @timer def compute_guidelines(self): - """Compute reach guideline and check if is valid for all profiles + """Compute reach guidelines Returns: Tuple of complete and incomplete guidelines name. diff --git a/src/Model/River.py b/src/Model/River.py index d00de347..a2afe35a 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -9,7 +9,7 @@ from Model.Geometry.Reach import Reach from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList from Model.LateralContribution.LateralContributionList import LateralContributionList - +from Model.Stricklers.StricklersList import StricklersList class RiverNode(Node): def __init__(self, id:str, name:str, @@ -61,6 +61,7 @@ class River(Graph): self._current_reach = None self._boundary_condition = BoundaryConditionList(status=self._status) self._lateral_contribution = LateralContributionList(status=self._status) + self._stricklers = StricklersList(status=self._status) @property def boundary_condition(self): @@ -70,6 +71,10 @@ class River(Graph): def lateral_contribution(self): return self._lateral_contribution + @property + def striklers(self): + return self._stricklers + def has_current_reach(self): return self._current_reach is not None diff --git a/src/Model/Stricklers/Stricklers.py b/src/Model/Stricklers/Stricklers.py new file mode 100644 index 00000000..9799984f --- /dev/null +++ b/src/Model/Stricklers/Stricklers.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +class Stricklers(object): + def __init__(self, status = None): + super(Stricklers, self).__init__() + + self._status = status + + self._name = "" + self._comment = "" + + self._minor = 35 + self._medium = 15 + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + @property + def comment(self): + return self._comment + + @comment.setter + def comment(self, comment): + self._comment = comment + + @property + def minor(self): + return self._minor + + @minor.setter + def minor(self, minor): + self._minor = int(minor) + + @property + def medium(self): + return self._medium + + @medium.setter + def medium(self, medium): + self._medium = int(medium) diff --git a/src/Model/Stricklers/StricklersList.py b/src/Model/Stricklers/StricklersList.py new file mode 100644 index 00000000..631ed93a --- /dev/null +++ b/src/Model/Stricklers/StricklersList.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from Model.Stricklers.Stricklers import Stricklers + +class StricklersList(object): + def __init__(self, status = None): + self._status = status + + self._stricks = [] + + def __len__(self): + return len(self._stricks) + + @property + def stricks(self): + return self._stricks.copy() + + def get(self, index): + if 0 <= index < len(self._stricks): + return self._stricks[index] + + return None + + def insert(self, index, strick): + self._stricks.insert(index, strick) + self._status.modified() + + def add(self, index): + s = Stricklers(status = self._status) + self.insert(index, s) + return s + + def delete(self, stricks): + self._stricks = list( + filter( + lambda s: s not in stricks, + self._stricks + ) + ) + self._status.modified() + + def delete_i(self, indexes): + stricks = set( + map( + lambda e: e[1], + filter( + lambda e: e[0] in indexes, + enumerate(self._stricks) + ) + ) + ) + self.delete(stricks) + + def sort(self, reverse:bool = False): + self._strick = sorted( + self._strick, + key = lambda st: st.name, + reverse = reverse, + ) + self._status.modified() diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index 818605cf..307fc56e 100644 --- a/src/View/LateralContribution/Table.py +++ b/src/View/LateralContribution/Table.py @@ -50,9 +50,7 @@ class ComboBoxDelegate(QItemDelegate): ) ) ) - self.editor.addItems( - lst - ) + self.editor.addItems(lst) else: self.editor.addItems( [_translate("LateralContribution", "Not associate")] + @@ -182,7 +180,7 @@ class TableModel(QAbstractTableModel): self._undo.push( AddCommand( - self._lcs, self._tab,row + self._lcs, self._tab, row ) ) @@ -194,7 +192,7 @@ class TableModel(QAbstractTableModel): self._undo.push( DelCommand( - self._lcs, self._tab,rows + self._lcs, self._tab, rows ) ) @@ -206,7 +204,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SortCommand( - self._lcs, self._tab,False + self._lcs, self._tab, False ) ) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 68e8ef37..30c7de24 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -24,6 +24,7 @@ from View.Network.Window import NetworkWindow from View.Geometry.Window import GeometryWindow from View.BoundaryCondition.Window import BoundaryConditionWindow from View.LateralContribution.Window import LateralContributionWindow +from View.Stricklers.Window import StricklersWindow from Model.Study import Study @@ -125,8 +126,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_toolBar_boundary_cond": self.open_boundary_cond, "action_toolBar_lateral_contrib": self.open_lateral_contrib, "action_toolBar_spills": lambda: self.open_dummy("Deversement"), - "action_toolBar_sections": lambda: self.open_dummy("Tronçons"), - "action_toolBar_frictions": lambda: self.open_dummy("Frottements"), + "action_toolBar_frictions": self.open_stricklers, "action_toolBar_building": lambda: self.open_dummy("Ouvrages"), } @@ -333,6 +333,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.lateral = LateralContributionWindow(study = self.model, parent=self) self.lateral.show() + def open_stricklers(self): + self.strick = StricklersWindow(study = self.model, parent=self) + self.strick.show() + # TODO: Delete me ! ############### # DUMMY STUFF # diff --git a/src/View/Stricklers/Table.py b/src/View/Stricklers/Table.py new file mode 100644 index 00000000..29f8fd53 --- /dev/null +++ b/src/View/Stricklers/Table.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +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, +) + +from View.Stricklers.UndoCommand import ( + SetNameCommand, SetCommentCommand, + SetMinorCommand, SetMediumCommand, + AddCommand, DelCommand, SortCommand, +) + +from View.Stricklers.translate import * + +_translate = QCoreApplication.translate + + +class TableModel(QAbstractTableModel): + def __init__(self, data=None, undo=None, tab=""): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._data = data + self._undo = undo + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._data) + + def columnCount(self, parent): + return len(self._headers) + + def data(self, index, role): + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + row = index.row() + column = index.column() + + if self._headers[column] == "name": + return self._data.get(row).name + elif self._headers[column] == "comment": + return self._data.get(row).comment + elif self._headers[column] == "minor": + return self._data.get(row).minor + elif self._headers[column] == "medium": + return self._data.get(row).medium + + return QVariant() + + def headerData(self, section, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return table_headers[self._headers[section]] + + return QVariant() + + def setData(self, index, value, role=Qt.EditRole): + if not index.isValid() or role != Qt.EditRole: + return False + + row = index.row() + column = index.column() + + if self._headers[column] == "name": + self._undo.push( + SetNameCommand( + self._data, row, value + ) + ) + elif self._headers[column] == "comment": + self._undo.push( + SetCommentCommand( + self._data, row, value + ) + ) + elif self._headers[column] == "minor": + self._undo.push( + SetMinorCommand( + self._data, row, value + ) + ) + elif self._headers[column] == "medium": + self._undo.push( + SetMediumCommand( + self._data, row, value + ) + ) + + self.dataChanged.emit(index, index) + return True + + def add(self, row, parent=QModelIndex()): + self.beginInsertRows(parent, row, row - 1) + + self._undo.push( + AddCommand( + self._data, row + ) + ) + + self.endInsertRows() + self.layoutChanged.emit() + + def delete(self, rows, parent=QModelIndex()): + self.beginRemoveRows(parent, rows[0], rows[-1]) + + self._undo.push( + DelCommand( + self._data, rows + ) + ) + + self.endRemoveRows() + self.layoutChanged.emit() + + def sort(self, _reverse, parent=QModelIndex()): + self.layoutAboutToBeChanged.emit() + + self._undo.push( + SortCommand( + self._data, False + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/Stricklers/UndoCommand.py b/src/View/Stricklers/UndoCommand.py new file mode 100644 index 00000000..df3f98e4 --- /dev/null +++ b/src/View/Stricklers/UndoCommand.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.LateralContribution.LateralContribution import LateralContribution +from Model.LateralContribution.LateralContributionList import LateralContributionList + +class SetNameCommand(QUndoCommand): + def __init__(self, data, index, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._old = self._data.get(self._index).name + self._new = new_value + + def undo(self): + self._data.get(self._index).name = self._old + + def redo(self): + self._data.get(self._index).name = self._new + +class SetCommentCommand(QUndoCommand): + def __init__(self, data, index, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._old = self._data.get(self._index).comment + self._new = new_value + + def undo(self): + self._data.get(self._index).comment = self._old + + def redo(self): + self._data.get(self._index).comment = self._new + +class SetMinorCommand(QUndoCommand): + def __init__(self, data, index, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._old = self._data.get(self._index).minor + self._new = new_value + + def undo(self): + self._data.get(self._index).minor = self._old + + def redo(self): + self._data.get(self._index).minor = self._new + +class SetMediumCommand(QUndoCommand): + def __init__(self, data, index, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._old = self._data.get(self._index).medium + self._new = new_value + + def undo(self): + self._data.get(self._index).medium = self._old + + def redo(self): + self._data.get(self._index).medium = self._new + + +class AddCommand(QUndoCommand): + def __init__(self, data, index): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._new = None + + def undo(self): + self._data.delete_i([self._index]) + + def redo(self): + if self._new is None: + self._new = self._data.add(self._index) + else: + self._data.insert(self._index, self._new) + +class DelCommand(QUndoCommand): + def __init__(self, data, rows): + QUndoCommand.__init__(self) + + self._data = data + self._rows = rows + + self._el = [] + for row in rows: + self._el.append((row, self._data.get(row))) + self._el.sort() + + def undo(self): + for row, el in self._el: + self._data.insert(row, el) + + def redo(self): + self._data.delete_i(self._rows) + +class SortCommand(QUndoCommand): + def __init__(self, data, _reverse): + QUndoCommand.__init__(self) + + self._data = data + self._reverse = _reverse + + self._old = self._data.get_tab(self._tab) + self._indexes = None + + def undo(self): + ll = self._data.get_tab(self._tab) + self._data.sort( + self._tab, + key=lambda x: self._indexes[ll.index(x)] + ) + + def redo(self): + self._data.sort( + self._tab, + reverse=self._reverse, + key=lambda x: x.name + ) + if self._indexes is None: + self._indexes = list( + map( + lambda p: self._old.index(p), + self._data.get_tab(self._tab) + ) + ) + self._old = None + +class PasteCommand(QUndoCommand): + def __init__(self, data, row, el): + QUndoCommand.__init__(self) + + self._data = data + self._row = row + self._el = deepcopy(el) + self._el.reverse() + + def undo(self): + self._data.delete(self._el) + + def redo(self): + for el in self._el: + self._data.insert(self._row, el) diff --git a/src/View/Stricklers/Window.py b/src/View/Stricklers/Window.py new file mode 100644 index 00000000..3f072dfb --- /dev/null +++ b/src/View/Stricklers/Window.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +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, +) + +from Model.Stricklers.Stricklers import Stricklers + +from View.Stricklers.UndoCommand import PasteCommand +from View.Stricklers.Table import TableModel + +_translate = QCoreApplication.translate + + +class StricklersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Stricklers", study=None, parent=None): + title = title + " - " + study.name + + super(StricklersWindow, self).__init__( + name=title, ui="Stricklers", parent=parent + ) + + self._study = study + + self.setup_sc() + self.setup_table() + self.setup_connections() + + self.ui.setWindowTitle(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_table(self): + self._table = {} + + for t in ["app", "study"]: + table = self.find(QTableView, f"tableView_{t}") + self._table[t] = TableModel( + data = self._study.river.striklers, + undo = self._undo_stack, + ) + table.setModel(self._table[t]) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_connections(self): + self.find(QAction, "action_add").triggered.connect(self.add) + self.find(QAction, "action_del").triggered.connect(self.delete) + self.find(QAction, "action_sort").triggered.connect(self.sort) + + 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) + + def index_selected_rows(self): + table = self.find(QTableView, f"tableView_study") + return list( + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + + def add(self): + rows = self.index_selected_rows() + if len(rows) == 0: + self._table['study'].add(0) + else: + self._table['study'].add(rows[0]) + + def delete(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table['study'].delete(rows) + + def sort(self): + self._table['study'].sort() + + def copy(self): + print("TODO") + + def paste(self): + print("TODO") + + def undo(self): + self._table['study'].undo() + + def redo(self): + self._table['study'].redo() diff --git a/src/View/Stricklers/translate.py b/src/View/Stricklers/translate.py new file mode 100644 index 00000000..4664e163 --- /dev/null +++ b/src/View/Stricklers/translate.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, LateralContrib, Rain, Evaporation, +) + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("LateralContribution", "Name"), + "minor": _translate("LateralContribution", "Minor bed"), + "medium": _translate("LateralContribution", "Medium bed"), + "comment": _translate("LateralContribution", "Comment"), +} diff --git a/src/View/ui/Stricklers.ui b/src/View/ui/Stricklers.ui new file mode 100644 index 00000000..b6ec590b --- /dev/null +++ b/src/View/ui/Stricklers.ui @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>820</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Study stricklers</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QTableView" name="tableView_study"/> + </item> + </layout> + </widget> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Application stricklers</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QTableView" name="tableView_app"/> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <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_add"/> + <addaction name="action_del"/> + <addaction name="action_sort"/> + </widget> + <action name="action_add"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> + </property> + <property name="text"> + <string>Add</string> + </property> + <property name="toolTip"> + <string>Add new stricklers</string> + </property> + </action> + <action name="action_del"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="toolTip"> + <string>Delete selected stricklers</string> + </property> + </action> + <action name="action_sort"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset> + </property> + <property name="text"> + <string>Sort</string> + </property> + <property name="toolTip"> + <string>Sort stricklers</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> -- GitLab