diff --git a/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py b/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py index e0a0f9cfe2c637d9cbc08eca2827f24c29f82a4f..e223346b9a812ad4b0ccba089a47c7378580d228 100644 --- a/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py +++ b/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py @@ -22,11 +22,11 @@ from tools import trace, timer from Model.Tools.PamhyrList import PamhyrModelList from Model.Except import NotImplementedMethodeError -from Model.LateralConditionsAdisTS.LateralConditionAdisTS import LateralConditionAdisTS +from Model.LateralContributionsAdisTS.LateralContributionAdisTS import LateralContributionAdisTS -class LateralConditionsAdisTSList(PamhyrModelList): +class LateralContributionsAdisTSList(PamhyrModelList): _sub_classes = [ - LateralConditionAdisTS, + LateralContributionAdisTS, ] @classmethod @@ -36,7 +36,7 @@ class LateralConditionsAdisTSList(PamhyrModelList): if data is None: data = {} - new._lst = LateralConditionAdisTS._db_load( + new._lst = LateralContributionAdisTS._db_load( execute, data ) @@ -55,7 +55,7 @@ class LateralConditionsAdisTSList(PamhyrModelList): return True def new(self, index, pollutant): - n = LateralConditionAdisTS(pollutant=pollutant, status=self._status) + n = LateralContributionAdisTS(pollutant=pollutant, status=self._status) self._lst.insert(index, n) self._status.modified() return n diff --git a/src/Model/River.py b/src/Model/River.py index 643de2c40f1546f74636dc40e7181443a774a042..5eabb932144a3775e72354e21944a4d07cae3fbb 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -49,7 +49,7 @@ from Model.OutputKpAdists.OutputKpListAdists import OutputKpAdistsList from Model.Pollutants.PollutantsList import PollutantsList from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList import InitialConditionsAdisTSList from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList import BoundaryConditionsAdisTSList -from Model.LateralConditionsAdisTS.LateralConditionsAdisTSList import LateralConditionsAdisTSList +from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList import LateralContributionsAdisTSList class RiverNode(Node, SQLSubModel): @@ -238,7 +238,7 @@ class River(Graph, SQLSubModel): PollutantsList, InitialConditionsAdisTSList, BoundaryConditionsAdisTSList, - LateralConditionsAdisTSList, + LateralContributionsAdisTSList, ] def __init__(self, status=None): @@ -266,7 +266,7 @@ class River(Graph, SQLSubModel): self._Pollutants = PollutantsList(status=self._status) self._InitialConditionsAdisTS = InitialConditionsAdisTSList(status=self._status) self._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList(status=self._status) - self._LateralConditionsAdisTS = LateralConditionsAdisTSList(status=self._status) + self._LateralContributionsAdisTS = LateralContributionsAdisTSList(status=self._status) @classmethod def _db_create(cls, execute): @@ -351,7 +351,7 @@ class River(Graph, SQLSubModel): new._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList._db_load(execute, data) - new._LateralConditionsAdisTS = LateralConditionsAdisTSList._db_load(execute, data) + new._LateralContributionsAdisTS = LateralContributionsAdisTSList._db_load(execute, data) return new @@ -376,7 +376,7 @@ class River(Graph, SQLSubModel): objs.append(self._Pollutants) objs.append(self._InitialConditionsAdisTS) objs.append(self._BoundaryConditionsAdisTS) - objs.append(self._LateralConditionsAdisTS) + objs.append(self._LateralContributionsAdisTS) self._save_submodel(execute, objs, data) return True @@ -517,8 +517,8 @@ Last export at: @date.""" return self._BoundaryConditionsAdisTS @property - def lateral_conditions_adists(self): - return self._LateralConditionsAdisTS + def lateral_contributions_adists(self): + return self._LateralContributionsAdisTS def get_params(self, solver): if solver in self._parameters: diff --git a/src/View/InitialConditionsAdisTS/Table.py b/src/View/InitialConditionsAdisTS/Table.py index e04c5d2fad24200bab053e96e202186eb782ce45..78938d9c1ddd501f709f98c377e7ebf0d1819068 100644 --- a/src/View/InitialConditionsAdisTS/Table.py +++ b/src/View/InitialConditionsAdisTS/Table.py @@ -61,8 +61,6 @@ class ComboBoxDelegate(QItemDelegate): if self._mode == "kp": reach_id = self._ic_spec_lst[index.row()].reach - print(self._data.edges()[0].id, reach_id) - reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges())) if reach_id is not None: diff --git a/src/View/LateralContributionsAdisTS/Edit/Plot.py b/src/View/LateralContributionsAdisTS/Edit/Plot.py new file mode 100644 index 0000000000000000000000000000000000000000..5405a501315a74c96839b2c6c27b8ce81355a792 --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Edit/Plot.py @@ -0,0 +1,104 @@ +# Plot.py -- Pamhyr +# Copyright (C) 2023-2024 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 logging + +from datetime import datetime + +from tools import timer, trace +from View.Tools.PamhyrPlot import PamhyrPlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + + +class Plot(PamhyrPlot): + def __init__(self, mode="time", data=None, + trad=None, canvas=None, toolbar=None, + parent=None): + super(Plot, self).__init__( + canvas=canvas, + trad=trad, + data=data, + toolbar=toolbar, + parent=parent + ) + + self._table_headers = self._trad.get_dict("table_headers") + + header = self.data.header + self.label_x = self._table_headers[header[0]] + self.label_y = self._table_headers[header[1]] + + self._mode = mode + self._isometric_axis = False + + self._auto_relim_update = True + self._autoscale_update = True + + def custom_ticks(self): + if self.data.header[0] != "time": + return + + self.set_ticks_time_formater() + + @timer + def draw(self): + self.init_axes() + + if len(self.data) == 0: + self._init = False + return + + self.draw_data() + self.custom_ticks() + + self.idle() + self._init = True + + def draw_data(self): + # Plot data + x = list(map(lambda v: v[0], self.data.data)) + y = list(map(lambda v: v[1], self.data.data)) + + self._line, = self.canvas.axes.plot( + x, y, + color=self.color_plot, + **self.plot_default_kargs + ) + + @timer + def update(self, ind=None): + if not self._init: + self.draw() + return + + self.update_data() + + self.update_idle() + + def update_data(self): + x = list(map(lambda v: v[0], self.data.data)) + y = list(map(lambda v: v[1], self.data.data)) + + self._line.set_data(x, y) diff --git a/src/View/LateralContributionsAdisTS/Edit/Table.py b/src/View/LateralContributionsAdisTS/Edit/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..c0ab3b4450d50c010aa67348e529673a7dd4896a --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Edit/Table.py @@ -0,0 +1,193 @@ +# Table.py -- Pamhyr +# Copyright (C) 2023-2024 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 logging +import traceback +from datetime import date, time, datetime, timedelta + +from tools import ( + trace, timer, + timestamp_to_old_pamhyr_date, + old_pamhyr_date_to_timestamp +) + +from View.Tools.PamhyrTable import PamhyrTableModel + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, + QCoreApplication, QModelIndex, pyqtSlot, + QRect, QTime, QDateTime, +) + +from PyQt5.QtWidgets import ( + QTableView, QAbstractItemView, QSpinBox, + QTimeEdit, QDateTimeEdit, QItemDelegate, +) + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, LateralContrib, Rain, Evaporation, +) + +from View.LateralContribution.Edit.UndoCommand import ( + SetDataCommand, AddCommand, DelCommand, + SortCommand, MoveCommand, PasteCommand, + DuplicateCommand, +) +from View.LateralContribution.Edit.translate import * + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + + +class TableModel(PamhyrTableModel): + def data(self, index, role): + if role == Qt.TextAlignmentRole: + return Qt.AlignHCenter | Qt.AlignVCenter + + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + row = index.row() + column = index.column() + + value = QVariant() + + if 0 <= column < 2: + v = self._data.get_i(row)[column] + if self._data.get_type_column(column) == float: + value = f"{v:.4f}" + elif self._data.header[column] == "time": + if self._opt_data == "time": + value = timestamp_to_old_pamhyr_date(int(v)) + else: + value = str(datetime.fromtimestamp(v)) + else: + value = f"{v}" + + return value + + def setData(self, index, value, role=Qt.EditRole): + if not index.isValid() or role != Qt.EditRole: + return False + + row = index.row() + column = index.column() + + try: + self._undo.push( + SetDataCommand( + self._data, row, column, value + ) + ) + except Exception as e: + logger.info(e) + logger.debug(traceback.format_exc()) + + 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() + + def sort(self, _reverse, parent=QModelIndex()): + self.layoutAboutToBeChanged.emit() + + self._undo.push( + SortCommand( + self._data, _reverse + ) + ) + + self.layoutAboutToBeChanged.emit() + 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._data, "up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, index, parent=QModelIndex()): + if row > len(self._data): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._data, "down", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def paste(self, row, header, data): + if len(data) == 0: + return + + self.layoutAboutToBeChanged.emit() + + self._undo.push( + PasteCommand( + self._data, row, + list( + map( + lambda d: self._data.new_from_data(header, d), + data + ) + ) + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() diff --git a/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py b/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..d56c63821bee182db245a8b73d0559549e049cbb --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py @@ -0,0 +1,168 @@ +# UndoCommand.py -- Pamhyr +# Copyright (C) 2023-2024 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 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.LateralContribution.LateralContribution import LateralContribution + + +class SetDataCommand(QUndoCommand): + def __init__(self, data, index, column, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._column = column + self._old = self._data.get_i(self._index)[self._column] + _type = self._data.get_type_column(self._column) + self._new = _type(new_value) + + def undo(self): + self._data._set_i_c_v(self._index, self._column, self._old) + + def redo(self): + self._data._set_i_c_v(self._index, self._column, 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._lc = [] + for row in rows: + self._lc.append((row, self._data.get_i(row))) + self._lc.sort() + + def undo(self): + for row, el in self._lc: + 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.data + self._indexes = None + + def undo(self): + ll = self._data.data + self._data.sort( + key=lambda x: self._indexes[ll.index(x)] + ) + + def redo(self): + self._data.sort( + _reverse=self._reverse, + key=lambda x: x[0] + ) + if self._indexes is None: + self._indexes = list( + map( + lambda p: self._old.index(p), + self._data.data + ) + ) + self._old = None + + +class MoveCommand(QUndoCommand): + def __init__(self, data, up, i): + QUndoCommand.__init__(self) + + self._data = data + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._data.move_up(self._i) + else: + self._data.move_down(self._i) + + def redo(self): + if self._up: + self._data.move_up(self._i) + else: + self._data.move_down(self._i) + + +class PasteCommand(QUndoCommand): + def __init__(self, data, row, lcs): + QUndoCommand.__init__(self) + + self._data = data + self._row = row + self._lcs = lcs + self._lcs.reverse() + + def undo(self): + self._data.delete(self._lcs) + + def redo(self): + for bc in self._lcs: + self._data.insert(self._row, bc) + + +class DuplicateCommand(QUndoCommand): + def __init__(self, data, rows, bc): + QUndoCommand.__init__(self) + + self._data = data + self._rows = rows + self._lc = deepcopy(bc) + self._lc.reverse() + + def undo(self): + self._data.delete(self._lc) + + def redo(self): + for bc in self._lcs: + self._data.insert(self._rows[0], bc) diff --git a/src/View/LateralContributionsAdisTS/Edit/Window.py b/src/View/LateralContributionsAdisTS/Edit/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..53c774cb51f6bfcb3a3c38c370d79a964010ac61 --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Edit/Window.py @@ -0,0 +1,225 @@ +# Window.py -- Pamhyr +# Copyright (C) 2023-2024 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 -*- + +from tools import timer, trace + +from View.Tools.PamhyrWindow import PamhyrWindow +from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, QCoreApplication, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QPushButton, QLineEdit, + QFileDialog, QTableView, QAbstractItemView, + QUndoStack, QShortcut, QAction, QItemDelegate, + QHeaderView, +) + +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar + +from View.LateralContribution.Edit.translate import LCETranslate +from View.LateralContribution.Edit.Table import TableModel +from View.LateralContribution.Edit.Plot import Plot + +_translate = QCoreApplication.translate + + +class EditLateralContributionWindow(PamhyrWindow): + _pamhyr_ui = "EditLateralContribution" + _pamhyr_name = "Edit lateral contribution" + + def __init__(self, data=None, + study=None, config=None, + parent=None): + self._data = data + + trad = LCETranslate() + name = trad[self._pamhyr_name] + if self._data is not None: + edge_name = (self._data.edge.name if self._data.edge is not None + else trad['not_associated']) + name += ( + f" - {study.name} " + + f" - {self._data.name} ({self._data.id}) " + + f"({trad.get_dict('long_types')[self._data.lctype]} - " + + f"{edge_name})" + ) + + super(EditLateralContributionWindow, self).__init__( + title=name, + study=study, + config=config, + trad=trad, + parent=parent + ) + + self._hash_data.append(data) + + self.setup_table() + self.setup_plot() + self.setup_connections() + + def setup_table(self): + self._delegate_time = PamhyrExTimeDelegate( + data=self._data, + mode=self._study.time_system, + parent=self + ) + + headers = {} + table_headers = self._trad.get_dict("table_headers") + for h in self._data.header: + headers[h] = table_headers[h] + + table = self.find(QTableView, "tableView") + self._table = TableModel( + table_view=table, + table_headers=headers, + editable_headers=self._data.header, + delegates={ + # "time": self._delegate_time, + }, + data=self._data, + undo=self._undo_stack, + trad=self._trad, + opt_data=self._study.time_system + ) + + table.setModel(self._table) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_plot(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.toolbar = PamhyrPlotToolbar( + self.canvas, self + ) + self.verticalLayout.addWidget(self.toolbar) + self.verticalLayout.addWidget(self.canvas) + + self.plot = Plot( + canvas=self.canvas, + data=self._data, + mode=self._study.time_system, + toolbar=self.toolbar, + trad=self._trad, + parent=self + ) + 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_sort").triggered.connect(self.sort) + + self._table.dataChanged.connect(self.update) + + def update(self): + self.plot.update() + + def index_selected_row(self): + table = self.find(QTableView, "tableView") + return table.selectionModel()\ + .selectedRows()[0]\ + .row() + + def index_selected_rows(self): + table = self.find(QTableView, "tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def add(self): + rows = self.index_selected_rows() + if len(self._data) == 0 or len(rows) == 0: + self._table.add(0) + else: + self._table.add(rows[0]) + + self.plot.update() + + def delete(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table.delete(rows) + self.plot.update() + + def sort(self): + self._table.sort(False) + self.plot.update() + + def move_up(self): + row = self.index_selected_row() + self._table.move_up(row) + self.plot.update() + + def move_down(self): + row = self.index_selected_row() + self._table.move_down(row) + self.plot.update() + + def _copy(self): + rows = self.index_selected_rows() + + table = [] + table.append(self._data.header) + + data = self._data.data + for row in rows: + table.append(list(data[row])) + + self.copyTableIntoClipboard(table) + + def _paste(self): + header, data = self.parseClipboardTable() + + if len(data) == 0: + return + + row = 0 + rows = self.index_selected_rows() + if len(rows) != 0: + row = rows[0] + + self._table.paste(row, header, data) + self.plot.update() + + def _undo(self): + self._table.undo() + self.plot.update() + + def _redo(self): + self._table.redo() + self.plot.update() diff --git a/src/View/LateralContributionsAdisTS/Edit/translate.py b/src/View/LateralContributionsAdisTS/Edit/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..eeb889304838a4b75339bfdcb481d6de96e55b4c --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Edit/translate.py @@ -0,0 +1,41 @@ +# translate.py -- Pamhyr +# Copyright (C) 2023-2024 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 -*- + +from PyQt5.QtCore import QCoreApplication + +from View.Translate import MainTranslate +from View.LateralContribution.translate import LCTranslate + +_translate = QCoreApplication.translate + + +class LCETranslate(LCTranslate): + def __init__(self): + super(LCETranslate, self).__init__() + self._dict["Edit lateral contribution"] = _translate( + "LateralContribution", "Edit lateral contribution" + ) + + self._sub_dict["table_headers"] = { + "x": _translate("LateralContribution", "X"), + "y": _translate("LateralContribution", "Y"), + "time": self._dict["time"], + "date": self._dict["date"], + "discharge": self._dict["unit_discharge"], + "z": self._dict["unit_elevation"], + } diff --git a/src/View/LateralContributionsAdisTS/Table.py b/src/View/LateralContributionsAdisTS/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..5b7fbd31c44c0afad4787c6def5dd6cf5006777c --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Table.py @@ -0,0 +1,249 @@ +# Table.py -- Pamhyr +# Copyright (C) 2023-2024 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 logging +import traceback + +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.LateralContributionsAdisTS.UndoCommand import ( + SetNameCommand, SetEdgeCommand, SetTypeCommand, + SetBeginCommand, SetEndCommand, + AddCommand, DelCommand, SortCommand, + MoveCommand, PasteCommand, DuplicateCommand, +) + +from View.Tools.PamhyrTable import PamhyrTableModel + +logger = logging.getLogger() + +_translate = QCoreApplication.translate + + +class ComboBoxDelegate(QItemDelegate): + def __init__(self, data=None, mode="edge", + trad=None, parent=None): + super(ComboBoxDelegate, self).__init__(parent) + + self._data = data + self._mode = mode + self._trad = trad + + @property + def data(self): + return self._data + + @data.setter + def data(self, data): + self._data = data + + def createEditor(self, parent, option, index): + self.editor = QComboBox(parent) + + if self._mode == "kp": + if self._data is None: + self.editor.addItems( + ["0"] + ) + else: + self.editor.addItems( + list( + map(str, self._data.reach.get_kp()) + ) + ) + else: + self.editor.addItems( + [self._trad['not_associated']] + + self._data.edges_names() + ) + + 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: + if 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(PamhyrTableModel): + def __init__(self, pollutant=None, lcs_list=None, trad=None, **kwargs): + self._trad = trad + self._lcs_list = lcs_list + self._pollutant = pollutant + + super(TableModel, self).__init__(trad=trad, **kwargs) + + def _setup_lst(self): + self._lst = self._data.lateral_contributions_adists.lst + self._tab = self._opt_data + self._long_types = self._trad.get_dict("long_types") + + def rowCount(self, parent): + return len(self._lst) + + def data(self, index, role): + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + row = index.row() + column = index.column() + + if self._headers[column] == "edge": + n = self._lst[row].edge + if n is None: + return self._trad['not_associated'] + return next(filter(lambda edge: edge.id == n, self._data.edges())).name + elif self._headers[column] == "begin_kp": + return str(self._lst[row].begin_kp) + elif self._headers[column] == "end_kp": + return str(self._lst[row].end_kp) + + 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() + + try: + if self._headers[column] == "edge": + self._undo.push( + SetEdgeCommand( + self._lst, self._tab, row, self._data.edge(value) + ) + ) + elif self._headers[column] == "begin_kp": + self._undo.push( + SetBeginCommand( + self._lst, self._tab, row, value + ) + ) + elif self._headers[column] == "end_kp": + self._undo.push( + SetEndCommand( + self._lst, self._tab, row, value + ) + ) + except Exception as e: + logger.info(e) + logger.debug(traceback.format_exc()) + + self.dataChanged.emit(index, index) + return True + + def add(self, row, parent=QModelIndex()): + self.beginInsertRows(parent, row, row - 1) + + self._undo.push( + AddCommand( + self._pollutant, self._lcs_list, self._lst, row + ) + ) + + self.endInsertRows() + self.layoutChanged.emit() + + def delete(self, rows, parent=QModelIndex()): + self.beginRemoveRows(parent, rows[0], rows[-1]) + + self._undo.push( + DelCommand( + self._lst, self._tab, rows + ) + ) + + self.endRemoveRows() + self.layoutChanged.emit() + + def sort(self, _reverse, parent=QModelIndex()): + self.layoutAboutToBeChanged.emit() + + self._undo.push( + SortCommand( + self._lst, self._tab, False + ) + ) + + self.layoutAboutToBeChanged.emit() + 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._lst, self._tab, "up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, index, parent=QModelIndex()): + if row > len(self._lst): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._lst, self._tab, "down", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() diff --git a/src/View/LateralContributionsAdisTS/UndoCommand.py b/src/View/LateralContributionsAdisTS/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..707d61a1af041c56e1777ee508184b0f34d4981e --- /dev/null +++ b/src/View/LateralContributionsAdisTS/UndoCommand.py @@ -0,0 +1,249 @@ +# UndoCommand.py -- Pamhyr +# Copyright (C) 2023-2024 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 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.LateralContributionsAdisTS.LateralContributionAdisTS import LateralContributionAdisTS +from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList import ( + LateralContributionsAdisTSList +) + + +class SetNameCommand(QUndoCommand): + def __init__(self, lcs, tab, index, new_value): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._old = self._lcs.get(self._tab, self._index).name + self._new = str(new_value) + + def undo(self): + self._lcs.get(self._tab, self._index).name = self._old + + def redo(self): + self._lcs.get(self._tab, self._index).name = self._new + + +class SetBeginCommand(QUndoCommand): + def __init__(self, lcs, tab, index, new_value): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._old = self._lcs.get(self._tab, self._index).begin_kp + self._new = float(new_value) + + def undo(self): + self._lcs.get(self._tab, self._index).begin_kp = float(self._old) + + def redo(self): + self._lcs.get(self._tab, self._index).begin_kp = float(self._new) + + +class SetEndCommand(QUndoCommand): + def __init__(self, lcs, tab, index, new_value): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._old = self._lcs.get(self._tab, self._index).end_kp + self._new = float(new_value) + + def undo(self): + self._lcs.get(self._tab, self._index).end_kp = float(self._old) + + def redo(self): + self._lcs.get(self._tab, self._index).end_kp = float(self._new) + + +class SetEdgeCommand(QUndoCommand): + def __init__(self, lcs, tab, index, edge): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._old = self._lcs.get(self._tab, self._index).edge + self._new = edge + + def undo(self): + self._lcs.get(self._tab, self._index).edge = self._old + + def redo(self): + self._lcs.get(self._tab, self._index).edge = self._new + + +class SetTypeCommand(QUndoCommand): + def __init__(self, lcs, tab, index, _type): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._type = _type + self._old = self._lcs.get(self._tab, self._index) + self._new = self._lcs.get(self._tab, self._index)\ + .convert(self._type) + + def undo(self): + self._lcs.set(self._tab, self._index, self._old) + + def redo(self): + self._lcs.set(self._tab, self._index, self._new) + + +class AddCommand(QUndoCommand): + def __init__(self, pollutant, lcs, lcs_lst, index): + QUndoCommand.__init__(self) + + self._pollutant = pollutant + self._lcs = lcs + self._lcs_lst = lcs_lst + self._index = index + self._new = None + + def undo(self): + del self._lcs[self._index] + + def redo(self): + if self._new is None: + self._new = self._lcs.new(self._index, self._pollutant) + else: + self._lcs_lst.insert(self._index, self._new) + + +class DelCommand(QUndoCommand): + def __init__(self, lcs, tab, rows): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._rows = rows + + self._bc = [] + for row in rows: + self._bc.append((row, self._lcs.get(self._tab, row))) + self._bc.sort() + + def undo(self): + for row, el in self._bc: + self._lcs.insert(self._tab, row, el) + + def redo(self): + self._lcs.delete_i(self._tab, self._rows) + + +class SortCommand(QUndoCommand): + def __init__(self, lcs, tab, _reverse): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._reverse = _reverse + + self._old = self._lcs.get_tab(self._tab) + self._indexes = None + + def undo(self): + ll = self._lcs.get_tab(self._tab) + self._lcs.sort( + self._tab, + key=lambda x: self._indexes[ll.index(x)] + ) + + def redo(self): + self._lcs.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._lcs.get_tab(self._tab) + ) + ) + self._old = None + + +class MoveCommand(QUndoCommand): + def __init__(self, lcs, tab, up, i): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._lcs.move_up(self._tab, self._i) + else: + self._lcs.move_down(self._tab, self._i) + + def redo(self): + if self._up: + self._lcs.move_up(self._tab, self._i) + else: + self._lcs.move_down(self._tab, self._i) + + +class PasteCommand(QUndoCommand): + def __init__(self, lcs, tab, row, bc): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._row = row + self._bc = deepcopy(bc) + self._bc.reverse() + + def undo(self): + self._lcs.delete(self._tab, self._bc) + + def redo(self): + for bc in self._bc: + self._lcs.insert(self._tab, self._row, bc) + + +class DuplicateCommand(QUndoCommand): + def __init__(self, lcs, tab, rows, bc): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._rows = rows + self._bc = deepcopy(bc) + self._bc.reverse() + + def undo(self): + self._lcs.delete(self._tab, self._bc) + + def redo(self): + for bc in self._lcs: + self._lcs.insert(self._tab, self._rows[0], bc) diff --git a/src/View/LateralContributionsAdisTS/Window.py b/src/View/LateralContributionsAdisTS/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..69c3e0caba21754ad0420b8ced500fbaac2c26e1 --- /dev/null +++ b/src/View/LateralContributionsAdisTS/Window.py @@ -0,0 +1,256 @@ +# Window.py -- Pamhyr +# Copyright (C) 2023-2024 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 logging + +from tools import trace, timer + +from View.Tools.PamhyrWindow import PamhyrWindow + +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.LateralContributionsAdisTS.UndoCommand import ( + SetNameCommand, SetEdgeCommand, SetTypeCommand, + AddCommand, DelCommand, SortCommand, + MoveCommand, PasteCommand, DuplicateCommand, +) + +from View.LateralContributionsAdisTS.Table import ( + TableModel, ComboBoxDelegate +) + +from View.Tools.Plot.PamhyrCanvas import MplCanvas +from View.Geometry.PlotXY import PlotXY +from View.LateralContributionsAdisTS.translate import ( + LCTranslate, +) +from View.LateralContributionsAdisTS.Edit.Window import EditLateralContributionWindow + +logger = logging.getLogger() + + +class LateralContributionAdisTSWindow(PamhyrWindow): + _pamhyr_ui = "LateralContributionsAdisTS" + _pamhyr_name = "Lateral contribution AdisTS" + + def __init__(self, study=None, pollutant=None, config=None, parent=None): + trad = LCTranslate() + name = trad[self._pamhyr_name] + " - " + study.name + + self._pollutant = pollutant + + super(LateralContributionAdisTSWindow, self).__init__( + title=name, + study=study, + trad=trad, + config=config, + parent=parent + ) + + self._lcs = self._study.river.lateral_contributions_adists + + self.setup_table() + self.setup_graph() + self.setup_connections() + + def setup_table(self): + self._table = {} + + self._delegate_kp = [] + + delegate_kp = ComboBoxDelegate( + data=None, + mode="kp", + trad=self._trad, + parent=self + ) + self._delegate_kp.append(delegate_kp) + + self._delegate_edge = ComboBoxDelegate( + data=self._study.river, + mode="edge", + trad=self._trad, + parent=self + ) + + table = self.find(QTableView, f"tableView") + self._table = TableModel( + table_view=table, + table_headers=self._trad.get_dict("table_headers"), + editable_headers=self._trad.get_dict("table_headers"), + delegates={ + "edge": self._delegate_edge, + "begin_kp": delegate_kp, + "end_kp": delegate_kp, + }, + data=self._study.river, + undo=self._undo_stack, + trad=self._trad, + opt_data="liquid", + pollutant=self._pollutant, + lcs_list=self._lcs, + ) + 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_2") + self.plot_layout.addWidget(self.canvas) + + self.plot = PlotXY( + canvas=self.canvas, + data=None, + trad=self._trad, + toolbar=None, + ) + + 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_edit").triggered.connect(self.edit) + self.find(QAction, "action_sort").triggered.connect(self.sort) + + table = self.find(QTableView, f"tableView") + table.selectionModel()\ + .selectionChanged\ + .connect(self._set_current_reach) + + self._table.dataChanged\ + .connect(self._set_current_reach) + + def index_selected_row(self): + table = self.find(QTableView, f"tableView") + return table.selectionModel()\ + .selectedRows()[0]\ + .row() + + def index_selected_rows(self): + table = self.find(QTableView, f"tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def _set_current_reach(self): + rows = self.index_selected_rows() + + data = None + highlight = None + + tab = "liquid" + + if len(rows) > 0: + edge = self._study.river\ + .lateral_contributions_adists.lst[rows[0]]\ + .edge + if edge: + data = edge.reach + lc = self._lcs.get(tab, rows[0]) + highlight = (lc.begin_kp, lc.end_kp) + + for delegate in self._delegate_kp: + delegate.data = edge + + self.plot = PlotXY( + canvas=self.canvas, + data=data, + trad=self._trad, + toolbar=None, + ) + self.plot.highlight = highlight + self.plot.update() + + def add(self): + rows = self.index_selected_rows() + if len(self._lcs) == 0 or len(rows) == 0: + self._table.add(0) + else: + self._table.add(rows[0]) + + self._set_current_reach() + + def delete(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table.delete(rows) + self._set_current_reach() + + def sort(self): + self._table.sort(False) + self._set_current_reach() + + def _copy(self): + logger.info("TODO: copy") + self._set_current_reach() + + def _paste(self): + logger.info("TODO: paste") + self._set_current_reach() + + def _undo(self): + self._table.undo() + self._set_current_reach() + + def _redo(self): + self._table.redo() + self._set_current_reach() + + def edit(self): + tab = self.current_tab() + rows = self.index_selected_rows() + for row in rows: + data = self._lcs.get(tab, row) + + if self.sub_window_exists( + EditLateralContributionWindow, + data=[self._study, None, data] + ): + continue + + win = EditLateralContributionWindow( + data=data, + study=self._study, + parent=self + ) + win.show() diff --git a/src/View/LateralContributionsAdisTS/translate.py b/src/View/LateralContributionsAdisTS/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..19118c0bfe51f8629983906efacece85d2e865d9 --- /dev/null +++ b/src/View/LateralContributionsAdisTS/translate.py @@ -0,0 +1,48 @@ +# translate.py -- Pamhyr +# Copyright (C) 2023-2024 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 -*- + +from PyQt5.QtCore import QCoreApplication + +from View.Translate import MainTranslate + +_translate = QCoreApplication.translate + +class LCTranslate(MainTranslate): + def __init__(self): + super(LCTranslate, self).__init__() + + self._dict["Lateral contribution AdisTS"] = _translate( + "LateralContribution1adisTS", "Lateral contribution AdisTS" + ) + + self._sub_dict["long_types"] = { + "ND": self._dict["not_defined"], + "LC": _translate("LateralContribution", "Lateral contribution"), + "RA": _translate("LateralContribution", "Rain"), + "EV": _translate("LateralContribution", "Evaporation"), + } + + self._dict["x"] = _translate("Geometry", "X (m)") + self._dict["y"] = _translate("Geometry", "Y (m)") + self._dict["z"] = _translate("Geometry", "Z (m)") + + self._sub_dict["table_headers"] = { + "edge": self._dict["reach"], + "begin_kp": _translate("LateralContribution", "Begin kp (m)"), + "end_kp": _translate("LateralContribution", "End kp (m)") + } diff --git a/src/View/Pollutants/Window.py b/src/View/Pollutants/Window.py index d9ee260ec7177aa845903d55ab486b437be0fcc8..a87c791fb1ac5f8a56c1173488ffe99f7a3c9558 100644 --- a/src/View/Pollutants/Window.py +++ b/src/View/Pollutants/Window.py @@ -45,6 +45,7 @@ from View.Pollutants.Edit.Window import EditPolluantWindow from View.InitialConditionsAdisTS.Window import InitialConditionsAdisTSWindow from View.BoundaryConditionsAdisTS.Window import BoundaryConditionAdisTSWindow +from View.LateralContributionsAdisTS.Window import LateralContributionAdisTSWindow logger = logging.getLogger() @@ -107,6 +108,7 @@ class PollutantsWindow(PamhyrWindow): self.find(QAction, "action_edit").triggered.connect(self.edit) self.find(QAction, "action_initial_conditions").triggered.connect(self.initial_conditions) self.find(QAction, "action_boundary_conditions").triggered.connect(self.boundary_conditions) + self.find(QAction, "action_lateral_contributions").triggered.connect(self.lateral_contrib) self._checkbox.clicked.connect(self._set_structure_state) table = self.find(QTableView, "tableView") @@ -225,13 +227,32 @@ class PollutantsWindow(PamhyrWindow): ): bound = self.get_sub_window( BoundaryConditionAdisTSWindow, - data=[self._study, None, ollutant_id] + data=[self._study, None, pollutant_id] ) return bound = BoundaryConditionAdisTSWindow(study=self._study, pollutant=pollutant_id, parent=self) bound.show() + def lateral_contrib(self): + rows = self.index_selected_rows() + + for row in rows: + pollutant_id = self._pollutants_lst.get(row).id + + if self.sub_window_exists( + LateralContributionAdisTSWindow, + data=[self._study, None, pollutant_id] + ): + return + + lateral = LateralContributionAdisTSWindow( + study=self._study, + parent=self, + pollutant=pollutant_id + ) + lateral.show() + def _set_checkbox_state(self): row = self.index_selected_row() if row is None: diff --git a/src/View/ui/LateralContributions.ui b/src/View/ui/LateralContributions.ui index d8d81b79cadb6bd83a69b1a1c559bdba4aaf66ff..bf9e5b4531ac85bc39c0a0d8618594428fff28e6 100644 --- a/src/View/ui/LateralContributions.ui +++ b/src/View/ui/LateralContributions.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>800</width> - <height>450</height> + <width>936</width> + <height>594</height> </rect> </property> <property name="sizePolicy"> @@ -82,7 +82,7 @@ <rect> <x>0</x> <y>0</y> - <width>800</width> + <width>936</width> <height>22</height> </rect> </property> diff --git a/src/View/ui/LateralContributionsAdisTS.ui b/src/View/ui/LateralContributionsAdisTS.ui new file mode 100644 index 0000000000000000000000000000000000000000..cc1b9f16175fe58a1dd3db04a8322f7cdaed9e6b --- /dev/null +++ b/src/View/ui/LateralContributionsAdisTS.ui @@ -0,0 +1,137 @@ +<?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>990</width> + <height>582</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <property name="locale"> + <locale language="English" country="Europe"/> + </property> + <widget class="QWidget" name="centralwidget"> + <property name="enabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTableView" name="tableView"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="verticalLayoutWidget_2"> + <layout class="QVBoxLayout" name="verticalLayout_2"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>990</width> + <height>22</height> + </rect> + </property> + </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_edit"/> + <addaction name="action_sort"/> + </widget> + <action name="action_add"> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="icon"> + <iconset> + <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset> + </property> + <property name="text"> + <string>Add</string> + </property> + <property name="toolTip"> + <string>Add a new boundary condition or lateral contribution</string> + </property> + <property name="shortcut"> + <string>Ctrl+N</string> + </property> + </action> + <action name="action_del"> + <property name="icon"> + <iconset> + <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="toolTip"> + <string>Delete current selected rows</string> + </property> + <property name="shortcut"> + <string>Ctrl+D</string> + </property> + </action> + <action name="action_edit"> + <property name="icon"> + <iconset> + <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset> + </property> + <property name="text"> + <string>Edit</string> + </property> + <property name="toolTip"> + <string>Edit boundary condition or lateral contribution</string> + </property> + <property name="shortcut"> + <string>Ctrl+E</string> + </property> + </action> + <action name="action_sort"> + <property name="icon"> + <iconset> + <normaloff>ressources/sort_A-Z.png</normaloff>ressources/sort_A-Z.png</iconset> + </property> + <property name="text"> + <string>Sort</string> + </property> + <property name="toolTip"> + <string>Sort boundary condition by name</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/View/ui/Pollutants.ui b/src/View/ui/Pollutants.ui index 3f7a288f9839b0fd24a9d661507f1ce1b8372d0a..195cf1bbef6c0734f6a63adf2edacb48e21696d1 100644 --- a/src/View/ui/Pollutants.ui +++ b/src/View/ui/Pollutants.ui @@ -98,6 +98,7 @@ <addaction name="action_edit"/> <addaction name="action_initial_conditions"/> <addaction name="action_boundary_conditions"/> + <addaction name="action_lateral_contributions"/> </widget> <action name="action_add"> <property name="icon"> @@ -141,6 +142,11 @@ <string>BoundaryConditions</string> </property> </action> + <action name="action_lateral_contributions"> + <property name="text"> + <string>LateralContributions</string> + </property> + </action> </widget> <resources/> <connections/>