diff --git a/src/Model/Geometry/Profile.py b/src/Model/Geometry/Profile.py index b0328f80b6e8ad41ad5f83d67b9add7d0af11dee..7836c47f0da581d6148cd4c1f11652e607734fdc 100644 --- a/src/Model/Geometry/Profile.py +++ b/src/Model/Geometry/Profile.py @@ -34,6 +34,7 @@ class Profile(object): self._kp = float(kp) self._name = str(name) self._reach = reach + self._sl = None self._points: List[Point] = [] @@ -120,6 +121,19 @@ class Profile(object): self._name = value.strip() self._status.modified() + @property + def sl(self): + """ + Returns: + Profile sediment layers. + """ + return self._sl + + @sl.setter + def sl(self, value: str): + self._sl = value + self._status.modified() + @property def profile_type(self): """ diff --git a/src/Model/SedimentLayer/SedimentLayer.py b/src/Model/SedimentLayer/SedimentLayer.py index 7f8f1fa45545a2eb45b538f2ac4dcd7fb02ebc74..8099aab048f7d27531d6a46384f35d01998dc811 100644 --- a/src/Model/SedimentLayer/SedimentLayer.py +++ b/src/Model/SedimentLayer/SedimentLayer.py @@ -108,7 +108,7 @@ class Layer(SQLSubModel): "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}', {sl}" + + f"'{self._sql_format(self._type)}', '{self._height}', {sl.id}" + ")" ) execute(sql) @@ -135,6 +135,9 @@ class SedimentLayer(SQLSubModel): else: self.id = id + def __str__(self): + return f"{self.name} ({len(self)}) - {self.comment}" + def __len__(self): return len(self._layers) diff --git a/src/Model/SedimentLayer/SedimentLayerList.py b/src/Model/SedimentLayer/SedimentLayerList.py index 32317c0baf4d2235027054773b4e033255969fb9..d2b662269cef4d574b6c75b493f3a8bc0c6bbe5f 100644 --- a/src/Model/SedimentLayer/SedimentLayerList.py +++ b/src/Model/SedimentLayer/SedimentLayerList.py @@ -46,6 +46,10 @@ class SedimentLayerList(SQLSubModel): return ok + @property + def sediment_layers(self): + return self._sl.copy() + def get(self, index): return self._sl[index] diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 79c7ef6701e39be916acd28369ef4b1e282ccc10..2c9b1b6585fce916820f233f373223d557acadab 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -34,6 +34,7 @@ from View.InitialConditions.Window import InitialConditionsWindow from View.Stricklers.Window import StricklersWindow from View.Frictions.Window import FrictionsWindow from View.SedimentLayers.Window import SedimentLayersWindow +from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow from View.SolverParameters.Window import SolverParametersWindow from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow from View.CheckList.Window import CheckListWindow @@ -149,7 +150,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_edit_friction": self.open_frictions, "action_menu_edit_lateral_contribution": self.open_lateral_contrib, "action_menu_run_solver": self.run_solver, - "action_menu_sediment_layers": self.open_sediment_layer, + "action_menu_sediment_layers": self.open_sediment_layers, + "action_menu_edit_reach_sediment_layers": self.open_reach_sediment_layers, ## Help "action_menu_about": self.open_about, # ToolBar action @@ -493,13 +495,20 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) params.show() - def open_sediment_layer(self): + def open_sediment_layers(self): sl = SedimentLayersWindow( study = self.model, parent = self ) sl.show() + def open_reach_sediment_layers(self): + sl = ReachSedimentLayersWindow( + study = self.model, + parent = self + ) + sl.show() + def run_solver(self): if self.model is None: return diff --git a/src/View/SedimentLayers/Reach/Table.py b/src/View/SedimentLayers/Reach/Table.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d9cac0956fbd980aae6094d01d3367c402fadeda 100644 --- a/src/View/SedimentLayers/Reach/Table.py +++ b/src/View/SedimentLayers/Reach/Table.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +import logging + +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.Reach.UndoCommand import * +from View.SedimentLayers.Reach.translate import * + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class ComboBoxDelegate(QItemDelegate): + def __init__(self, study=None, parent=None): + super(ComboBoxDelegate, self).__init__(parent) + + self._study = study + + def createEditor(self, parent, option, index): + self.editor = QComboBox(parent) + + self.editor.addItems( + [_translate("SedimentLayers", "Not defined")] + + list( + map( + lambda sl: str(sl), + self._study.river.sediment_layers.sediment_layers + ) + ) + ) + + self.editor.setCurrentText(index.data(Qt.DisplayRole)) + return self.editor + + def setEditorData(self, editor, index): + value = index.data(Qt.DisplayRole) + self.editor.currentTextChanged.connect(self.currentItemChanged) + + def setModelData(self, editor, model, index): + text = str(editor.currentText()) + model.setData(index, text) + editor.close() + editor.deleteLater() + + def updateEditorGeometry(self, editor, option, index): + r = QRect(option.rect) + if self.editor.windowFlags() & Qt.Popup and editor.parent() is not None: + r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft())) + editor.setGeometry(r) + + @pyqtSlot() + def currentItemChanged(self): + self.commitData.emit(self.sender()) + + +class TableModel(QAbstractTableModel): + def __init__(self, study=None, reach=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._study = study + self._undo = undo + self._reach = reach + + def flags(self, index): + column = index.column() + + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + if self._headers[column] == "sl": + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return self._reach.number_profiles + + 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._reach.profile(row).name + if self._headers[column] == "kp": + return self._reach.profile(row).kp + if self._headers[column] == "sl": + value = self._reach.profile(row).sl + if value == None: + text = _translate("SedimentLayers", "Not defined") + return text + return str(value) + + 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] == "sl": + new = None + if value != _translate("SedimentLayers", "Not defined"): + new = next( + filter( + lambda sl: str(sl) == value, + self._study.river.sediment_layers.sediment_layers + ) + ) + + self._undo.push( + SetSLCommand( + self._reach, row, new + ) + ) + + self.dataChanged.emit(index, index) + return True + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/SedimentLayers/Reach/UndoCommand.py b/src/View/SedimentLayers/Reach/UndoCommand.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..82ba3764e15606d1fa2cdeb720883600269c06bc 100644 --- a/src/View/SedimentLayers/Reach/UndoCommand.py +++ b/src/View/SedimentLayers/Reach/UndoCommand.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +import logging + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.Geometry.Reach import Reach +from Model.Geometry.Profile import Profile +from Model.SedimentLayer.SedimentLayer import SedimentLayer +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList + +logger = logging.getLogger() + +class SetSLCommand(QUndoCommand): + def __init__(self, reach, index, new_value): + QUndoCommand.__init__(self) + + self._reach = reach + self._index = index + self._old = self._reach.profile(self._index).sl + self._new = new_value + + def undo(self): + self._reach.profile(self._index).sl = self._old + + def redo(self): + self._reach.profile(self._index).sl = self._new diff --git a/src/View/SedimentLayers/Reach/Window.py b/src/View/SedimentLayers/Reach/Window.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b292db5d490745cc07f48ac86761eb3be4566ec3 100644 --- a/src/View/SedimentLayers/Reach/Window.py +++ b/src/View/SedimentLayers/Reach/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.SedimentLayers.Reach.UndoCommand import * +from View.SedimentLayers.Reach.Table import * + +from View.Plot.MplCanvas import MplCanvas +from View.SedimentLayers.Reach.translate import * + +from View.SedimentLayers.Window import SedimentLayersWindow + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Reach sediment layers", study=None, parent=None): + self._study = study + self._sediment_layers = self._study.river.sediment_layers + self._reach = self._study.river.current_reach().reach + + self.setup_title(title) + + super(ReachSedimentLayersWindow, self).__init__( + name=self._title, ui="ReachSedimentLayers", parent=parent + ) + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(self._title) + + def setup_title(self, title): + self._title = ( + title + " - " + self._study.name + " - " + self._reach.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): + table = self.find(QTableView, f"tableView") + self._table = TableModel( + study = self._study, + reach = self._reach, + undo = self._undo_stack, + ) + table.setModel(self._table) + + self._delegate_stricklers = ComboBoxDelegate( + study = self._study, + parent=self + ) + + table.setItemDelegateForColumn( + list(table_headers).index("sl"), + self._delegate_stricklers + ) + + 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_2") + 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_edit").triggered.connect(self.edit_profile) + + 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 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() + + def edit_profile(self): + rows = self.index_selected_rows() + + for row in rows: + slw = ProfileSedimentLayersWindow( + study = self._study, + sl = self._sediment_layers.get(row), + parent = self + ) + slw.show() diff --git a/src/View/SedimentLayers/Reach/translate.py b/src/View/SedimentLayers/Reach/translate.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f6b17cf8a5395417b5c655ab8c3e141397985f4e 100644 --- a/src/View/SedimentLayers/Reach/translate.py +++ b/src/View/SedimentLayers/Reach/translate.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("SedimentLayers", "Name"), + "kp": _translate("SedimentLayers", "KP (m)"), + "sl": _translate("SedimentLayers", "Sediment layers"), +} diff --git a/src/View/SedimentLayers/Window.py b/src/View/SedimentLayers/Window.py index b2e8f1fa04736758c72d7c3feb4e3dde791b0015..b04c073d97591cf4df341865bfaa9ad995fb7485 100644 --- a/src/View/SedimentLayers/Window.py +++ b/src/View/SedimentLayers/Window.py @@ -68,8 +68,6 @@ class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): 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, diff --git a/src/View/ui/ReachSedimentLayers.ui b/src/View/ui/ReachSedimentLayers.ui index b37db0c458b84fb9d71e792e5083d4c264062555..a1beb8caa877c20aef815a81b511a79a34cefcb7 100644 --- a/src/View/ui/ReachSedimentLayers.ui +++ b/src/View/ui/ReachSedimentLayers.ui @@ -69,9 +69,9 @@ <attribute name="toolBarBreak"> <bool>false</bool> </attribute> - <addaction name="action_edit_profile"/> + <addaction name="action_edit"/> </widget> - <action name="action_edit_profile"> + <action name="action_edit"> <property name="icon"> <iconset> <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset> @@ -83,15 +83,6 @@ <string>Edit profile sediment layer</string> </property> </action> - <action name="action_add_sediment_layer"> - <property name="icon"> - <iconset> - <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> - </property> - <property name="text"> - <string>Add sediment layer</string> - </property> - </action> </widget> <resources/> <connections/>