From e9b00aa148e5caf1dd2b057e8cc9fd9d1fed4dbc Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr> Date: Fri, 21 Jul 2023 16:11:13 +0200 Subject: [PATCH] SL: Add Edit window and fix DB save and load. --- src/Model/River.py | 1 + src/Model/SedimentLayer/SedimentLayer.py | 45 +++++- src/View/SedimentLayers/Edit/Table.py | 153 ++++++++++++++++++++ src/View/SedimentLayers/Edit/UndoCommand.py | 97 +++++++++++++ src/View/SedimentLayers/Edit/Window.py | 150 +++++++++++++++++++ src/View/SedimentLayers/Edit/translate.py | 11 ++ src/View/SedimentLayers/Window.py | 4 +- src/View/ui/EditSedimentLayers.ui | 8 +- 8 files changed, 456 insertions(+), 13 deletions(-) diff --git a/src/Model/River.py b/src/Model/River.py index 2ff98bd8..59b47957 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -282,6 +282,7 @@ class River(Graph, SQLSubModel): objs.append(self._boundary_condition) objs.append(self._initial_conditions) objs.append(self._lateral_contribution) + objs.append(self._sediment_layers) objs.append(self._stricklers) for solver in self._parameters: diff --git a/src/Model/SedimentLayer/SedimentLayer.py b/src/Model/SedimentLayer/SedimentLayer.py index 65684bd8..7f8f1fa4 100644 --- a/src/Model/SedimentLayer/SedimentLayer.py +++ b/src/Model/SedimentLayer/SedimentLayer.py @@ -16,7 +16,6 @@ class Layer(SQLSubModel): super(Layer, self).__init__() self._status = status - self._sl = sl self._name = name self._type = type @@ -27,6 +26,31 @@ class Layer(SQLSubModel): else: self.id = id + Layer._id_cnt = max(id, Layer._id_cnt+1) + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + @property + def type(self): + return self._type + + @type.setter + def type(self, type): + self._type = type + + @property + def height(self): + return self._height + + @height.setter + def height(self, height): + self._height = height @classmethod def _sql_create(cls, execute): @@ -56,7 +80,7 @@ class Layer(SQLSubModel): table = execute( "SELECT id, ind, name, type, height " + "FROM sedimentary_layer_layer " + - f"WHERE sl = {sl.id}" + f"WHERE sl = {sl}" ) for _ in table: @@ -77,13 +101,14 @@ class Layer(SQLSubModel): def _sql_save(self, execute, data = None): ind = data["ind"] + sl = data["sl"] sql = ( "INSERT INTO " + "sedimentary_layer_layer(id, ind, name, type, height, sl) "+ "VALUES (" + f"{self.id}, {ind}, '{self._sql_format(self._name)}', " + - f"'{self._sql_format(self._type)}', '{self._height}', {self._sl.id}" + + f"'{self._sql_format(self._type)}', '{self._height}', {sl}" + ")" ) execute(sql) @@ -110,6 +135,9 @@ class SedimentLayer(SQLSubModel): else: self.id = id + def __len__(self): + return len(self._layers) + @property def name(self): return self._name @@ -147,7 +175,7 @@ class SedimentLayer(SQLSubModel): new = [] table = execute( - "SELECT id, name, comment" + + "SELECT id, name, comment " + "FROM sedimentary_layer " ) @@ -171,11 +199,14 @@ class SedimentLayer(SQLSubModel): data = {} sql = ( - "INSERT INTO sedimentary_layer (id, name) "+ - f"VALUES ({self.id}, '{self._sql_format(self._name)}')" + "INSERT INTO sedimentary_layer (id, name, comment) " + + f"VALUES ({self.id}, '{self._sql_format(self._name)}', " + + f"'{self._sql_format(self._comment)}')" ) execute(sql) + data["sl"] = self + ind = 0 for l in self._layers: data["ind"] = ind @@ -196,7 +227,7 @@ class SedimentLayer(SQLSubModel): self._status.modified() def new(self, index): - n = Layer(status = self._status) + n = Layer(sl=self, status = self._status) self.insert(index, n) self._status.modified() return n diff --git a/src/View/SedimentLayers/Edit/Table.py b/src/View/SedimentLayers/Edit/Table.py index e69de29b..6bd59387 100644 --- a/src/View/SedimentLayers/Edit/Table.py +++ b/src/View/SedimentLayers/Edit/Table.py @@ -0,0 +1,153 @@ +# -*- 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.SedimentLayers.Edit.UndoCommand import * +from View.SedimentLayers.Edit.translate import * + +_translate = QCoreApplication.translate + + +class TableModel(QAbstractTableModel): + def __init__(self, study=None, sl=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._study = study + self._undo = undo + self._sl = sl + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._sl) + + 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._sl.get(row).name + elif self._headers[column] == "type": + return self._sl.get(row).type + elif self._headers[column] == "height": + return self._sl.get(row).height + + return QVariant() + + def headerData(self, friction, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return table_headers[self._headers[friction]] + + 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._sl, row, value + ) + ) + if self._headers[column] == "comment": + self._undo.push( + SetCommentCommand( + self._sl, 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._sl, row + ) + ) + + self.endInsertRows() + self.layoutChanged.emit() + + def delete(self, rows, parent=QModelIndex()): + self.beginRemoveRows(parent, rows[0], rows[-1]) + + self._undo.push( + DelCommand( + self._sl, rows + ) + ) + + self.endRemoveRows() + self.layoutChanged.emit() + + def move_up(self, row, parent=QModelIndex()): + if row <= 0: + return + + target = row + 2 + + self.beginMoveRows(parent, row - 1, row - 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._sl, "up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, index, parent=QModelIndex()): + if row > len(self._sl): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._sl, "down", row + ) + ) + + self.endMoveRows() + 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/SedimentLayers/Edit/UndoCommand.py b/src/View/SedimentLayers/Edit/UndoCommand.py index e69de29b..9e630a6e 100644 --- a/src/View/SedimentLayers/Edit/UndoCommand.py +++ b/src/View/SedimentLayers/Edit/UndoCommand.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.SedimentLayer.SedimentLayer import SedimentLayer +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList + +class SetNameCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).name + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).name = self._old + + def redo(self): + self._sediment_layers.get(self._index).name = self._new + +class SetHeightCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).height + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).height = self._old + + def redo(self): + self._sediment_layers.get(self._index).height = self._new + +class AddCommand(QUndoCommand): + def __init__(self, sediment_layers, index): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._new = None + + def undo(self): + self._sediment_layers.delete_i([self._index]) + + def redo(self): + if self._new is None: + self._new = self._sediment_layers.new(self._index) + else: + self._sediment_layers.insert(self._index, self._new) + +class DelCommand(QUndoCommand): + def __init__(self, sediment_layers, rows): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._rows = rows + + self._sl_pos = [] + for row in rows: + self._sl_pos.append((row, self._sediment_layers.get(row))) + self._sl_pos.sort() + + def undo(self): + for row, el in self._sl_pos: + self._sediment_layers.insert(row, el) + + def redo(self): + self._sediment_layers.delete_i(self._rows) + +class MoveCommand(QUndoCommand): + def __init__(self, sediment_layers, up, i): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._sediment_layers.move_up(self._i) + else: + self._sediment_layers.move_down(self._i) + + def redo(self): + if self._up: + self._sediment_layers.move_up(self._i) + else: + self._sediment_layers.move_down(self._i) diff --git a/src/View/SedimentLayers/Edit/Window.py b/src/View/SedimentLayers/Edit/Window.py index e69de29b..44100d6f 100644 --- a/src/View/SedimentLayers/Edit/Window.py +++ b/src/View/SedimentLayers/Edit/Window.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +import logging + +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 View.Plot.MplCanvas import MplCanvas + +from View.SedimentLayers.Edit.UndoCommand import * +from View.SedimentLayers.Edit.Table import * +from View.SedimentLayers.Edit.translate import * + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Edit Sediment Layers", + study=None, sl=None, parent=None): + self._study = study + self._sl = sl + + self.setup_title(title) + + super(EditSedimentLayersWindow, self).__init__( + name=self._title, ui="EditSedimentLayers", parent=parent + ) + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(self._title) + + def setup_title(self, title): + name = self._sl.name + if name == "": + name = _translate("SedimentLayers", "(no name)") + + self._title = ( + title + " - " + self._study.name + " - " + name + ) + + 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 = {} + + table = self.find(QTableView, f"tableView") + self._table = TableModel( + study = self._study, + sl = self._sl, + undo = self._undo_stack, + ) + table.setModel(self._table) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_graph(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.plot_layout = self.find(QVBoxLayout, "verticalLayout") + self.plot_layout.addWidget(self.canvas) + + # self.plot = PlotKPC( + # canvas = self.canvas, + # data = self._reach.reach, + # toolbar = None, + # display_current = False + # ) + # self.plot.draw() + + + 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_move_up").triggered.connect(self.delete) + self.find(QAction, "action_move_down").triggered.connect(self.delete) + + 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") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def add(self): + rows = self.index_selected_rows() + if len(self._sl) == 0 or len(rows) == 0: + self._table.add(0) + else: + self._table.add(rows[0]) + + def delete(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table.delete(rows) + + 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() diff --git a/src/View/SedimentLayers/Edit/translate.py b/src/View/SedimentLayers/Edit/translate.py index e69de29b..3b61d18c 100644 --- a/src/View/SedimentLayers/Edit/translate.py +++ b/src/View/SedimentLayers/Edit/translate.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("SedimentLayers", "Name"), + "type": _translate("SedimentLayers", "Type"), + "height": _translate("Sedimentlayers", "Height"), +} diff --git a/src/View/SedimentLayers/Window.py b/src/View/SedimentLayers/Window.py index 22b61318..b2e8f1fa 100644 --- a/src/View/SedimentLayers/Window.py +++ b/src/View/SedimentLayers/Window.py @@ -30,7 +30,7 @@ from View.SedimentLayers.Table import * from View.Plot.MplCanvas import MplCanvas from View.SedimentLayers.translate import * -# from View.SedimentLayers.Edit.Window import EditSedimentLayersWindow +from View.SedimentLayers.Edit.Window import EditSedimentLayersWindow _translate = QCoreApplication.translate @@ -150,7 +150,7 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): for row in rows: slw = EditSedimentLayersWindow( study = self._study, - sl = self.__sediment_layers[row], + sl = self._sediment_layers.get(row), parent = self ) slw.show() diff --git a/src/View/ui/EditSedimentLayers.ui b/src/View/ui/EditSedimentLayers.ui index 7e236641..6fe4c11b 100644 --- a/src/View/ui/EditSedimentLayers.ui +++ b/src/View/ui/EditSedimentLayers.ui @@ -49,12 +49,12 @@ <attribute name="toolBarBreak"> <bool>false</bool> </attribute> - <addaction name="action_add_sediment_layer"/> - <addaction name="action_delete_sediment_layer"/> + <addaction name="action_add"/> + <addaction name="action_del"/> <addaction name="action_move_up"/> <addaction name="action_move_down"/> </widget> - <action name="action_add_sediment_layer"> + <action name="action_add"> <property name="icon"> <iconset> <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> @@ -69,7 +69,7 @@ <string>Ctrl+N</string> </property> </action> - <action name="action_delete_sediment_layer"> + <action name="action_del"> <property name="icon"> <iconset> <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset> -- GitLab