Pamhyr2/src/View/WeatherParameters/Window.py

409 lines
12 KiB
Python

# Window.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os
import logging
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence, QIcon,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QVBoxLayout, QToolBar, QAction, QToolButton, QLabel,
)
from Modules import Modules
from View.WeatherParameters.UndoCommand import (
SetCommand,
)
from View.WeatherParameters.TableDefault import (
WeatherParametersTableDefaultModel,
)
from View.WeatherParameters.Table import (
WeatherParametersTableModel, ComboBoxDelegate,
)
from View.WeatherParameters.translate import WeatherParametersTranslate
from View.WeatherParameters.Edit.Window import EditWeatherParametersWindow
from Solver.Mage import Mage8
_translate = QCoreApplication.translate
logger = logging.getLogger()
class WeatherParametersWindow(PamhyrWindow):
_pamhyr_ui = "WeatherParameters"
_pamhyr_name = "Weather parameters"
def __init__(self, data=None, study=None,
config=None, parent=None):
self._data = data
trad = WeatherParametersTranslate()
self.weather_params = [trad.get_dict("weather_parameters")[k] for
k in trad.get_dict("weather_parameters").keys()]
name = (
trad[self._pamhyr_name] +
" - " + study.name
)
super(WeatherParametersWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(data)
self._wp_acronyms = [
"AT", "SH", "GR", "RWS", "GT", "GFR", "ALB", "SC", "CCF"
]
self.setup_table()
self.setup_connections()
def setup_connections(self):
path_icons = os.path.join(self._get_ui_directory(), f"ressources")
layout = self.find(QVBoxLayout, f"verticalLayout_1")
toolBar = QToolBar()
layout.addWidget(toolBar)
# Add buttons to the toolbar
action_add = QAction(self)
action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
if self._study.is_editable():
action_add.triggered.connect(self.add)
action_delete = QAction(self)
action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
if self._study.is_editable():
action_delete.triggered.connect(self.delete)
action_edit = QAction(self)
action_edit.setIcon(QIcon(os.path.join(path_icons, f"edit.png")))
action_edit.triggered.connect(self.edit)
toolBar.addAction(action_add)
toolBar.addAction(action_delete)
toolBar.addAction(action_edit)
layout.addWidget(self.table_spec_title)
layout.addWidget(self.table_spec)
def setup_table(self):
self.table_default = self.find(QTableView, f"tableView")
if self._study.is_editable():
editable_headers = ["value"]
else:
editable_headers = []
self._table = WeatherParametersTableDefaultModel(
table_view=self.table_default,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=editable_headers,
delegates={},
data=self._study.river.weather_parameters.default,
opt_data=self.weather_params,
undo=self._undo_stack,
trad=self._trad
)
self.table_default.setModel(self._table)
self.table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_default.setSelectionMode(QAbstractItemView.SingleSelection)
self.table_default.horizontalHeader().setSectionResizeMode(
QHeaderView.Stretch
)
self.table_default.setAlternatingRowColors(True)
self.table_default.selectionModel().selectionChanged.connect(
self.on_table_default_selection_changed
)
self.table_spec_title = QLabel()
self.table_spec = QTableView()
self._delegate_reach = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
weather_param_lst=None,
parent=self,
mode="reaches"
)
self._delegate_rk = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
weather_param_lst=self._study.river.weather_parameters,
parent=self,
mode="rk"
)
if self._study.is_editable():
editable_headers_spec = self._trad.get_dict("table_headers_spec")
else:
editable_headers_spec = []
self._table_spec = WeatherParametersTableModel(
table_view=self.table_spec,
table_headers=self._trad.get_dict("table_headers_spec"),
editable_headers=editable_headers_spec,
delegates={
"reach": self._delegate_reach,
"start_rk": self._delegate_rk,
"end_rk": self._delegate_rk
},
data=self._study.river.weather_parameters,
undo=self._undo_stack,
trad=self._trad,
river=self._study.river
)
self.table_spec.setModel(self._table_spec)
self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_spec.horizontalHeader().setSectionResizeMode(
QHeaderView.Stretch
)
self.table_spec.setAlternatingRowColors(True)
selectionModel = self.table_spec.selectionModel()
index = self.table_spec.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
self.table_spec.scrollTo(index)
self._selected_default_row_index = 0
self.select_table_default_row(0)
def select_table_default_row(self, row):
model = self.table_default.model()
if model is None or model.rowCount() == 0:
return
row = max(0, min(row, model.rowCount() - 1))
index = model.index(row, 0)
self.table_default.setCurrentIndex(index)
self.table_default.selectionModel().select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
self.table_default.scrollTo(index)
def on_table_default_selection_changed(self, selected, deselected):
rows = self.table_default.selectionModel().selectedRows()
if len(rows) == 0:
self.select_table_default_row(self._selected_default_row_index)
return self._selected_default_row_index
self._selected_default_row_index = rows[0].row()
self.update_table_spec_title()
self._table_spec.update_tab_spec(
type=self._wp_acronyms[
self._selected_default_row_index
]
)
return self._selected_default_row_index
def update_table_spec_title(self):
row = self._selected_default_row_index
if 0 <= row < len(self.weather_params):
self.table_spec_title.setText(self.weather_params[row])
def index_selected_row(self):
table = self.table_spec
rows = table.selectionModel()\
.selectedRows()
if len(rows) == 0:
return 0
return rows[0].row()
def index_selected_rows(self):
table = self.table_default
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def index_selected_rows_spec(self):
table = self.table_spec
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def move_up(self):
row = self.index_selected_row()
self._table.move_up(row)
self._update()
def move_down(self):
row = self.index_selected_row()
self._table.move_down(row)
self._update()
def _copy(self):
rows = list(
map(
lambda row: row.row(),
self.tableView.selectionModel().selectedRows()
)
)
table = list(
map(
lambda eic: list(
map(
lambda k: eic[1][k],
["rk", "discharge", "elevation"]
)
),
filter(
lambda eic: eic[0] in rows,
enumerate(self._ics.lst())
)
)
)
self.copyTableIntoClipboard(table)
def _paste(self):
header, data = self.parseClipboardTable()
if len(data) + len(header) == 0:
return
logger.debug(
"IC: Paste: " +
f"header = {header}, " +
f"data = {data}"
)
try:
row = self.index_selected_row()
self._table.paste(row, [], data)
except Exception as e:
logger_exception(e)
self._update()
def _undo(self):
undo_stack = self._undo_stack
if undo_stack is None or not undo_stack.canUndo():
return
if isinstance(undo_stack.command(undo_stack.index() - 1), SetCommand):
self._table.undo()
else:
self._table_spec.undo()
self._update()
def _redo(self):
undo_stack = self._undo_stack
if undo_stack is None or not undo_stack.canRedo():
return
if isinstance(undo_stack.command(undo_stack.index()), SetCommand):
self._table.redo()
else:
self._table_spec.redo()
self._update()
def add(self):
rows = self.index_selected_rows()
if rows is not None and len(rows) > 0:
if len(self._data.lst) == 0 or len(rows) == 0:
self._table_spec.add(0)
else:
self._table_spec.add(rows[0])
def delete(self):
rows = self.index_selected_rows_spec()
if len(rows) == 0:
return
self._table_spec.delete(rows)
def edit(self):
rows = self.index_selected_rows_spec()
data_rows = {
id(wp): i for i, wp in enumerate(self._data.lst)
}
rows = [data_rows[id(self._table_spec._lst[r])]
for r in rows
if 0 <= r < len(self._table_spec._lst)]
for row in rows:
data = self._data.get(row)
if self.sub_window_exists(
EditWeatherParametersWindow,
data=[self._study, None, data]
):
continue
win = EditWeatherParametersWindow(
data=data,
study=self._study,
parent=self
)
win.show()