diff --git a/src/View/Configure/Window.py b/src/View/Configure/Window.py index 394ddf8b..c5ba5821 100644 --- a/src/View/Configure/Window.py +++ b/src/View/Configure/Window.py @@ -43,6 +43,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QComboBox, QUndoStack, QShortcut, QHeaderView, + QCheckBox, ) from Modules import Modules @@ -119,11 +120,15 @@ class ConfigureWindow(PamhyrDialog): editable_headers=["name", "comment", "minor", "medium"], data=self._stricklers, undo=self._undo_stack, + display_manning=self._config.display_manning, ) def setup_data(self): # Backup self.set_check_box("checkBox_backup", self._config.backup_enable) + self.set_check_box( + "checkBox_display_manning", self._config.display_manning + ) # self.set_line_edit_text("lineEdit_backup_path", # self._config.backup_path) self.set_time_edit("timeEdit_backup_frequence", @@ -167,6 +172,10 @@ class ConfigureWindow(PamhyrDialog): for button in buttons: self.find(QPushButton, button).clicked.connect(buttons[button]) + self.find(QCheckBox, "checkBox_display_manning").stateChanged.connect( + self.update_stricklers_display + ) + def accept(self): # Solvers self._config.solvers = self.solver_table_model._data.copy() @@ -178,6 +187,9 @@ class ConfigureWindow(PamhyrDialog): self._config.backup_frequence = self.get_time_edit( "timeEdit_backup_frequence" ) + self._config.display_manning = self.get_check_box( + "checkBox_display_manning" + ) # self._config.backup_max = self.get_spin_box("spinBox_backup_max") # Stricklers @@ -237,6 +249,12 @@ class ConfigureWindow(PamhyrDialog): # Stricklers + def update_stricklers_display(self): + self._stricklers_table.display_manning = self.get_check_box( + "checkBox_display_manning" + ) + self._stricklers_table.layoutChanged.emit() + def index_selected_rows_strikclers(self): table = self.find(QTableView, f"tableView_stricklers") return list( diff --git a/src/View/Frictions/PlotStricklers.py b/src/View/Frictions/PlotStricklers.py index 67341530..bc40c9af 100644 --- a/src/View/Frictions/PlotStricklers.py +++ b/src/View/Frictions/PlotStricklers.py @@ -20,6 +20,7 @@ import logging from tools import timer, flatten from View.Tools.PamhyrPlot import PamhyrPlot +from View.Tools.CoefficientDisplay import display_coefficient from PyQt5.QtCore import ( QCoreApplication @@ -32,7 +33,7 @@ _translate = QCoreApplication.translate class PlotStricklers(PamhyrPlot): def __init__(self, data=None, canvas=None, trad=None, - toolbar=None, parent=None): + toolbar=None, display_manning=True, parent=None): super(PlotStricklers, self).__init__( canvas=canvas, trad=trad, @@ -42,7 +43,11 @@ class PlotStricklers(PamhyrPlot): ) self.label_x = self._trad["rk"] - self.label_y = self._trad["strickler_plot"] + self._display_manning = display_manning + self.label_y = ( + self._trad["manning_plot"] if display_manning + else self._trad["strickler_plot"] + ) self.line_rk_elevation = [None, None] @@ -113,8 +118,12 @@ class PlotStricklers(PamhyrPlot): ) ) - coef_minor = list(map(lambda s: s.minor, coef)) - coef_medium = list(map(lambda s: s.medium, coef)) + coef_minor = [ + display_coefficient(s.minor, self._display_manning) for s in coef + ] + coef_medium = [ + display_coefficient(s.medium, self._display_manning) for s in coef + ] self.line_rk_elevation[0] = self.canvas.axes.plot( rk, coef_minor, diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index 9bccfe2f..6d2081d8 100644 --- a/src/View/Frictions/Table.py +++ b/src/View/Frictions/Table.py @@ -42,6 +42,7 @@ from View.Frictions.UndoCommand import ( ) from View.Tools.PamhyrTable import PamhyrTableModel +from View.Tools.CoefficientDisplay import display_strickler from View.Frictions.translate import * @@ -52,27 +53,26 @@ _translate = QCoreApplication.translate class ComboBoxDelegate(QItemDelegate): def __init__(self, data=None, study=None, - mode="stricklers", trad=None, parent=None): + mode="stricklers", trad=None, display_manning=True, + parent=None): super(ComboBoxDelegate, self).__init__(parent) self._data = data self._study = study self._trad = trad self._mode = mode + self._display_manning = display_manning def createEditor(self, parent, option, index): self.editor = QComboBox(parent) if self._mode == "stricklers": - self.editor.addItems( - [self._trad["not_defined"]] + - list( - map( - lambda s: str(s), - self._study.river.stricklers.stricklers - ) + self.editor.addItem(self._trad["not_defined"], None) + for strickler in self._study.river.stricklers.stricklers: + self.editor.addItem( + display_strickler(strickler, self._display_manning), + strickler ) - ) elif self._mode == "rk": self.editor.addItems( list(map(str, self._data.reach.get_rk())) @@ -86,8 +86,9 @@ class ComboBoxDelegate(QItemDelegate): self.editor.currentTextChanged.connect(self.currentItemChanged) def setModelData(self, editor, model, index): - text = str(editor.currentText()) - model.setData(index, text) + value = editor.currentData() if self._mode == "stricklers" else \ + str(editor.currentText()) + model.setData(index, value) editor.close() editor.deleteLater() @@ -104,6 +105,10 @@ class ComboBoxDelegate(QItemDelegate): class FrictionTableModel(PamhyrTableModel): + def __init__(self, *args, display_manning=False, **kwargs): + self.display_manning = display_manning + super(FrictionTableModel, self).__init__(*args, **kwargs) + def _setup_lst(self): self._lst = self._data.frictions self._study = self._opt_data @@ -125,12 +130,12 @@ class FrictionTableModel(PamhyrTableModel): value = self._lst.get(row).begin_strickler if value is None: return self._trad["not_defined"] - return str(value) + return display_strickler(value, self.display_manning) elif self._headers[column] == "end_strickler": value = self._lst.get(row).end_strickler if value is None: return self._trad["not_defined"] - return str(value) + return display_strickler(value, self.display_manning) return QVariant() @@ -164,15 +169,19 @@ class FrictionTableModel(PamhyrTableModel): ) ) elif self._headers[column] == "begin_strickler": + strickler = value if hasattr(value, "minor") else \ + self._study.river.strickler(value) self._undo.push( SetBeginStricklerCommand( - self._lst, row, self._study.river.strickler(value) + self._lst, row, strickler ) ) elif self._headers[column] == "end_strickler": + strickler = value if hasattr(value, "minor") else \ + self._study.river.strickler(value) self._undo.push( SetEndStricklerCommand( - self._lst, row, self._study.river.strickler(value) + self._lst, row, strickler ) ) except Exception as e: diff --git a/src/View/Frictions/Window.py b/src/View/Frictions/Window.py index c3aec368..4d6c2528 100644 --- a/src/View/Frictions/Window.py +++ b/src/View/Frictions/Window.py @@ -67,6 +67,9 @@ class FrictionsWindow(PamhyrWindow): def __init__(self, reach=None, study=None, config=None, parent=None): trad = FrictionsTranslate() + if config is None and parent is not None: + config = getattr(parent, "conf", None) + if reach is not None: self._reach = reach else: @@ -103,6 +106,7 @@ class FrictionsWindow(PamhyrWindow): study=self._study, mode="stricklers", trad=self._trad, + display_manning=getattr(self._config, "display_manning", False), parent=self ) self._delegate_rk = ComboBoxDelegate( @@ -122,9 +126,15 @@ class FrictionsWindow(PamhyrWindow): editable_headers = [] table = self.find(QTableView, f"tableView") + table_headers = self._trad.get_dict("table_headers").copy() + table_headers["begin_strickler"] = ( + self._trad["manning"] + if getattr(self._config, "display_manning", False) + else self._trad["strickler"] + ) self._table = FrictionTableModel( table_view=table, - table_headers=self._trad.get_dict("table_headers"), + table_headers=table_headers, editable_headers=editable_headers, delegates={ "begin_rk": self._delegate_rk, @@ -135,7 +145,8 @@ class FrictionsWindow(PamhyrWindow): data=self._reach, trad=self._trad, undo=self._undo_stack, - opt_data=self._study + opt_data=self._study, + display_manning=getattr(self._config, "display_manning", False) ) table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) @@ -167,7 +178,8 @@ class FrictionsWindow(PamhyrWindow): canvas=self.canvas_2, data=self._reach, trad=self._trad, - toolbar=None + toolbar=None, + display_manning=getattr(self._config, "display_manning", False) ) self.plot_2.draw() @@ -274,13 +286,13 @@ class FrictionsWindow(PamhyrWindow): def edit_stricklers(self): if self.sub_window_exists( StricklersWindow, - data=[self._study, self.parent.conf] + data=[self._study, self._config] ): return strick = StricklersWindow( study=self._study, - config=self.parent.conf, + config=self._config, parent=self ) strick.show() diff --git a/src/View/Frictions/translate.py b/src/View/Frictions/translate.py index 3d094580..ae5674e3 100644 --- a/src/View/Frictions/translate.py +++ b/src/View/Frictions/translate.py @@ -31,9 +31,18 @@ class FrictionsTranslate(MainTranslate): self._dict["strickler_plot"] = _translate( "Frictions", "Strickler ($m^{1/3}/s$)" ) + self._dict["manning_plot"] = _translate( + "Frictions", "Manning ($s/m^{1/3}$)" + ) self._dict["stricklers"] = _translate( "Frictions", "Stricklers" ) + self._dict["strickler"] = _translate( + "Frictions", "Strickler" + ) + self._dict["manning"] = _translate( + "Frictions", "Manning" + ) self._dict["Edit frictions"] = _translate( "Frictions", "Edit frictions" diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 38ca8770..d5a80e46 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -1427,12 +1427,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if self.sub_window_exists( FrictionsWindow, - data=[self._study, None, reach] + data=[self._study, self.conf, reach] ): return frictions = FrictionsWindow( study=self._study, + config=self.conf, parent=self ) frictions.show() diff --git a/src/View/Stricklers/Table.py b/src/View/Stricklers/Table.py index 75fcff84..1e4a4c9a 100644 --- a/src/View/Stricklers/Table.py +++ b/src/View/Stricklers/Table.py @@ -41,6 +41,9 @@ from View.Stricklers.UndoCommand import ( SetMinorCommand, SetMediumCommand, AddCommand, DelCommand, SortCommand, ) +from View.Tools.CoefficientDisplay import ( + display_coefficient, stored_coefficient +) logger = logging.getLogger() @@ -48,6 +51,10 @@ _translate = QCoreApplication.translate class TableModel(PamhyrTableModel): + def __init__(self, *args, display_manning=False, **kwargs): + self.display_manning = display_manning + super(TableModel, self).__init__(*args, **kwargs) + def get_true_data_row(self, row): el = self._data.get(row) @@ -73,9 +80,13 @@ class TableModel(PamhyrTableModel): elif self._headers[column] == "comment": return self._data.get(row).comment elif self._headers[column] == "minor": - return self._data.get(row).minor + return display_coefficient( + self._data.get(row).minor, self.display_manning + ) elif self._headers[column] == "medium": - return self._data.get(row).medium + return display_coefficient( + self._data.get(row).medium, self.display_manning + ) return QVariant() @@ -106,13 +117,15 @@ class TableModel(PamhyrTableModel): elif self._headers[column] == "minor": self._undo.push( SetMinorCommand( - self._data, row, value + self._data, row, + stored_coefficient(value, self.display_manning) ) ) elif self._headers[column] == "medium": self._undo.push( SetMediumCommand( - self._data, row, value + self._data, row, + stored_coefficient(value, self.display_manning) ) ) except Exception as e: diff --git a/src/View/Stricklers/Window.py b/src/View/Stricklers/Window.py index 534f0469..f4282733 100644 --- a/src/View/Stricklers/Window.py +++ b/src/View/Stricklers/Window.py @@ -37,6 +37,7 @@ from PyQt5.QtWidgets import ( QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, QComboBox, QVBoxLayout, QHeaderView, QTabWidget, + QGroupBox, ) from Model.Stricklers.Stricklers import Stricklers @@ -56,7 +57,11 @@ class StricklersWindow(PamhyrWindow): def __init__(self, study=None, config=None, parent=None): trad = StricklersTranslate() - name = trad[self._pamhyr_name] + " - " + study.name + display_manning = getattr(config, "display_manning", False) + coefficient_name = ( + "Mannings" if display_manning else self._pamhyr_name + ) + name = trad[coefficient_name] + " - " + study.name super(StricklersWindow, self).__init__( title=name, @@ -66,9 +71,23 @@ class StricklersWindow(PamhyrWindow): parent=parent ) + self.setup_labels() self.setup_table() self.setup_connections() + def setup_labels(self): + display_manning = getattr(self._config, "display_manning", False) + study_label = ( + self._trad["study_mannings"] if display_manning + else self._trad["study_stricklers"] + ) + application_label = ( + self._trad["application_mannings"] if display_manning + else self._trad["application_stricklers"] + ) + self.find(QGroupBox, "groupBox_2").setTitle(study_label) + self.find(QGroupBox, "groupBox").setTitle(application_label) + def setup_table(self): self._table = {} @@ -90,6 +109,9 @@ class StricklersWindow(PamhyrWindow): editable_headers=editable_headers, data=data, undo=self._undo_stack, + display_manning=getattr( + self._config, "display_manning", False + ), ) table.setModel(self._table[t]) diff --git a/src/View/Stricklers/translate.py b/src/View/Stricklers/translate.py index de77d569..5f50c535 100644 --- a/src/View/Stricklers/translate.py +++ b/src/View/Stricklers/translate.py @@ -30,6 +30,21 @@ class StricklersTranslate(MainTranslate): self._dict["Stricklers"] = _translate( "Stricklers", "Strickler coefficients" ) + self._dict["Mannings"] = _translate( + "Stricklers", "Manning coefficients" + ) + self._dict["study_stricklers"] = _translate( + "Stricklers", "Strickler coefficients of the study" + ) + self._dict["study_mannings"] = _translate( + "Stricklers", "Manning coefficients of the study" + ) + self._dict["application_stricklers"] = _translate( + "Stricklers", "Strickler coefficients of the application" + ) + self._dict["application_mannings"] = _translate( + "Stricklers", "Manning coefficients of the application" + ) self._sub_dict["table_headers"] = { "name": self._dict["name"], diff --git a/src/View/Tools/CoefficientDisplay.py b/src/View/Tools/CoefficientDisplay.py new file mode 100644 index 00000000..e46bd863 --- /dev/null +++ b/src/View/Tools/CoefficientDisplay.py @@ -0,0 +1,30 @@ +# CoefficientDisplay.py -- Pamhyr +# Copyright (C) 2023-2026 INRAE + +"""View-only conversion helpers for friction coefficients. + +Strickler coefficients remain the canonical representation in models, +study files and solver exports. +""" + + +def display_coefficient(strickler, display_manning=False): + value = float(strickler) + if not display_manning: + return value + return float("inf") if value == 0.0 else 1.0 / value + + +def stored_coefficient(displayed, display_manning=False): + value = float(displayed) + if not display_manning: + return value + if value == 0.0: + raise ValueError("A Manning coefficient cannot be zero") + return 1.0 / value + + +def display_strickler(strickler, display_manning=False): + minor = display_coefficient(strickler.minor, display_manning) + medium = display_coefficient(strickler.medium, display_manning) + return f"{strickler.name} ({minor:g}, {medium:g})" diff --git a/src/View/ui/ConfigureDialog.ui b/src/View/ui/ConfigureDialog.ui index adcab8b5..b356dc3a 100644 --- a/src/View/ui/ConfigureDialog.ui +++ b/src/View/ui/ConfigureDialog.ui @@ -242,6 +242,13 @@ + + + Display Manning coefficients + + + + diff --git a/src/config.py b/src/config.py index 4fd97764..c268d254 100644 --- a/src/config.py +++ b/src/config.py @@ -37,7 +37,7 @@ logger = logging.getLogger() class Config(SQL): def __init__(self): - self._version = '0.0.8' + self._version = '0.0.9' self.filename = Config.filename() self.set_default_value() @@ -215,6 +215,12 @@ class Config(SQL): ) """) + if int(release) < 9: + self.execute( + "INSERT OR IGNORE INTO data " + "VALUES ('display_manning', 'False')" + ) + self.execute( f"UPDATE info SET value='{self._version}' " + "WHERE key='version'" @@ -275,6 +281,11 @@ class Config(SQL): # self.backup_path = v[0] v = self.execute("SELECT value FROM data WHERE key='backup_frequence'") self.backup_frequence = v[0] + # Friction coefficient display + v = self.execute( + "SELECT value FROM data WHERE key='display_manning'" + ) + self.display_manning = v[0] == "True" # v = self.execute("SELECT value FROM data WHERE key='backup_max'") # self.backup_max = int(v[0]) @@ -342,6 +353,7 @@ class Config(SQL): "backup_enable": self.backup_enable, # "backup_path": self.backup_path, "backup_frequence": self.backup_frequence, + "display_manning": self.display_manning, # "backup_max": self.backup_max, "editor": self.editor, "lang": self.lang, @@ -424,6 +436,7 @@ class Config(SQL): self.backup_enable = True # self.backup_path = "" self.backup_frequence = "00:15:00" + self.display_manning = False # self.backup_max = 10 # Editor