d90 Complete

adists_num
Youcef AOUAD 2024-06-21 15:46:41 +02:00
parent 9f53ad685e
commit 95a0dabe68
10 changed files with 878 additions and 7 deletions

View File

@ -62,7 +62,7 @@ class D90AdisTS(SQLSubModel):
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
d90 REAL NOT NULL,
enabled BOOLEAN NOT NULL,
enabled BOOLEAN NOT NULL
)
""")

View File

@ -177,7 +177,7 @@ class D90AdisTSSpec(SQLSubModel):
return self._d90
@d90.setter
def d90(self, concentration):
def d90(self, d90):
self._d90 = d90
self._status.modified()

217
src/View/D90AdisTS/Table.py Normal file
View File

@ -0,0 +1,217 @@
# 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.Tools.PamhyrTable import PamhyrTableModel
from View.D90AdisTS.UndoCommand import (
SetCommand, AddCommand, SetCommandSpec,
DelCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, ic_spec_lst=None, trad=None, parent=None, mode="reaches"):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._mode = mode
self._trad = trad
self._ic_spec_lst = ic_spec_lst
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
val = []
if self._mode == "kp":
reach_id = self._ic_spec_lst[index.row()].reach
reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges()))
if reach_id is not None:
val = list(
map(
lambda kp: str(kp), reach.reach.get_kp()
)
)
else:
val = list(
map(
lambda n: n.name, self._data.edges()
)
)
self.editor.addItems(
[self._trad['not_associated']] +
val
)
self.editor.setCurrentText(str(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 D90TableModel(PamhyrTableModel):
def __init__(self, river=None, data=None, **kwargs):
self._river = river
super(D90TableModel, self).__init__(data=data, **kwargs)
self._data = data
def _setup_lst(self):
self._lst = self._data._data
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] is "name":
n = self._lst[row].name
if n is None or n == "":
return self._trad['not_associated']
return n
elif self._headers[column] is "reach":
n = self._lst[row].reach
if n is None:
return self._trad['not_associated']
return next(filter(lambda edge: edge.id == n, self._river.edges())).name
elif self._headers[column] is "start_kp":
n = self._lst[row].start_kp
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "end_kp":
n = self._lst[row].end_kp
if n is None:
return self._trad['not_associated']
return n
elif self._headers[column] is "d90":
n = self._lst[row].d90
if n is None:
return self._trad['not_associated']
return n
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] != "reach":
self._undo.push(
SetCommandSpec(
self._lst, row, self._headers[column], value
)
)
elif self._headers[column] == "reach":
print(self._river.edge(value).id)
self._undo.push(
SetCommandSpec(
self._lst, row, self._headers[column], self._river.edge(value).id
)
)
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, 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._data, self._lst, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()

View File

@ -0,0 +1,95 @@
# 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.Tools.PamhyrTable import PamhyrTableModel
from View.D90AdisTS.UndoCommand import (
SetCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class D90TableDefaultModel(PamhyrTableModel):
def __init__(self, **kwargs):
super(D90TableDefaultModel, self).__init__(**kwargs)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] is "name":
return self._data[row].name
elif self._headers[column] is "d90":
n = self._data[row].d90
if n is None:
return self._trad['not_associated']
return n
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] is not None:
self._undo.push(
SetCommand(
self._data, row, self._headers[column], value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
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()

View File

@ -0,0 +1,150 @@
# 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.D90AdisTS.D90AdisTS import D90AdisTS
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
class SetCommand(QUndoCommand):
def __init__(self, data, row, column, new_value):
QUndoCommand.__init__(self)
self._data = data
self._row = row
self._column = column
if self._column == "name":
self._old = self._data[self._row].name
elif self._column == "d90":
self._old = self._data[self._row].d90
_type = float
if column == "name":
_type = str
self._new = _type(new_value)
def undo(self):
if self._column == "name":
self._data[self._row].name = self._old
elif self._column == "d90":
self._data[self._row].d90 = self._old
def redo(self):
if self._column == "name":
self._data[self._row].name = self._new
elif self._column == "d90":
self._data[self._row].d90 = self._new
class SetCommandSpec(QUndoCommand):
def __init__(self, data, row, column, new_value):
QUndoCommand.__init__(self)
self._data = data
self._row = row
self._column = column
if self._column == "name":
self._old = self._data[self._row].name
elif self._column == "reach":
self._old = self._data[self._row].reach
elif self._column == "start_kp":
self._old = self._data[self._row].start_kp
elif self._column == "end_kp":
self._old = self._data[self._row].end_kp
elif self._column == "d90":
self._old = self._data[self._row].d90
_type = float
if column == "name":
_type = str
elif column == "reach":
_type = int
self._new = _type(new_value)
def undo(self):
if self._column == "name":
self._data[self._row].name = self._old
elif self._column == "reach":
self._data[self._row].reach = self._old
elif self._column == "start_kp":
self._data[self._row].start_kp = self._old
elif self._column == "end_kp":
self._data[self._row].end_kp = self._old
elif self._column == "d90":
self._data[self._row].d90 = self._old
def redo(self):
if self._column == "name":
self._data[self._row].name = self._new
elif self._column == "reach":
self._data[self._row].reach = self._new
elif self._column == "start_kp":
self._data[self._row].start_kp = self._new
elif self._column == "end_kp":
self._data[self._row].end_kp = self._new
elif self._column == "d90":
self._data[self._row].d90 = self._new
class AddCommand(QUndoCommand):
def __init__(self, data, ics_spec, index):
QUndoCommand.__init__(self)
self._data = data
self._ics_spec = ics_spec
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.new(self._index)
else:
self._data.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, data, ics_spec, rows):
QUndoCommand.__init__(self)
self._data = data
self._ics_spec = ics_spec
self._rows = rows
#self._data = data
self._ic = []
for row in rows:
self._ic.append((row, self._ics_spec[row]))
self._ic.sort()
def undo(self):
for row, el in self._ic:
self._data.insert(row, el)
def redo(self):
self._data.delete_i(self._rows)

View File

@ -0,0 +1,283 @@
# 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 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,
)
from Modules import Modules
from View.InitialConditionsAdisTS.UndoCommand import (
SetCommand,
)
from View.D90AdisTS.TableDefault import (
D90TableDefaultModel,
)
from View.D90AdisTS.Table import (
D90TableModel, ComboBoxDelegate,
)
from View.D90AdisTS.translate import D90AdisTSTranslate
from Solver.Mage import Mage8
_translate = QCoreApplication.translate
logger = logging.getLogger()
class D90AdisTSWindow(PamhyrWindow):
_pamhyr_ui = "D90AdisTS"
_pamhyr_name = "D90 AdisTS"
def __init__(self, data=None, study=None, config=None, parent=None):
self._data = []
self._data.append(data)
trad = D90AdisTSTranslate()
name = (
trad[self._pamhyr_name] +
" - " + study.name +
" - " + self._data[0].name
)
super(D90AdisTSWindow, self).__init__(
title=name,
study=study,
config=config,
trad=trad,
parent=parent
)
self._hash_data.append(data)
self._d90_adists_lst = study.river.d90_adists
self.setup_table()
self.ui.setWindowTitle(self._title)
def setup_table(self):
path_icons = os.path.join(self._get_ui_directory(), f"ressources")
table_default = self.find(QTableView, f"tableView")
self._table = D90TableDefaultModel(
table_view=table_default,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["name", "d90"],
delegates={},
data=self._data,
undo=self._undo_stack,
trad=self._trad
)
table_default.setModel(self._table)
table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
table_default.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table_default.setAlternatingRowColors(True)
layout = self.find(QVBoxLayout, f"verticalLayout_1")
toolBar = QToolBar()
layout.addWidget(toolBar)
action_add = QAction(self)
action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
action_add.triggered.connect(self.add)
action_delete = QAction(self)
action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
action_delete.triggered.connect(self.delete)
toolBar.addAction(action_add)
toolBar.addAction(action_delete)
self.table_spec = QTableView()
layout.addWidget(self.table_spec)
self._delegate_reach = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
ic_spec_lst=self._data[0]._data,
parent=self,
mode="reaches"
)
self._delegate_kp = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
ic_spec_lst=self._data[0]._data,
parent=self,
mode="kp"
)
self._table_spec = D90TableModel(
table_view=self.table_spec,
table_headers=self._trad.get_dict("table_headers_spec"),
editable_headers=["name", "reach", "start_kp", "end_kp", "d90"],
delegates={
"reach": self._delegate_reach,
"start_kp": self._delegate_kp,
"end_kp": self._delegate_kp
},
data=self._data[0],
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)
def index_selected_row(self):
#table = self.find(QTableView, f"tableView")
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.find(QTableView, f"tableView")
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],
["kp", "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, header, data)
self._table.paste(row, [], data)
except Exception as e:
logger_exception(e)
self._update()
def _undo(self):
self._table.undo()
self._update()
def _redo(self):
self._table.redo()
self._update()
def add(self):
rows = self.index_selected_rows()
if len(self._data[0]._data) == 0 or len(rows) == 0:
self._table_spec.add(0)
else:
self._table_spec.add(rows[0])
def delete(self):
print("del")
rows = self.index_selected_rows()
if len(rows) == 0:
print("len 0")
return
self._table_spec.delete(rows)

View File

@ -0,0 +1,46 @@
# 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 D90AdisTSTranslate(MainTranslate):
def __init__(self):
super(D90AdisTSTranslate, self).__init__()
self._dict["D90 AdisTS"] = _translate(
"D90AdisTS", "D90 AdisTS")
self._dict["kp"] = self._dict["unit_kp"]
self._sub_dict["table_headers"] = {
"name": self._dict["name"],
"d90": _translate("Unit", "D90"),
}
self._sub_dict["table_headers_spec"] = {
"name": self._dict["name"],
"reach": self._dict["reach"],
"start_kp": _translate("Unit", "Start_KP (m)"),
"end_kp": _translate("Unit", "End_KP (m)"),
"d90": _translate("Unit", "D90"),
}

View File

@ -77,6 +77,7 @@ from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.Debug.Window import ReplWindow
from View.OutputKpAdisTS.Window import OutputKpAdisTSWindow
from View.Pollutants.Window import PollutantsWindow
from View.D90AdisTS.Window import D90AdisTSWindow
# Optional internal display of documentation for make the application
# package lighter...
@ -869,17 +870,23 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
###############
def open_d90(self):
if len(self._study.river.d90_adists.lst) != 0:
d90_default = self._study.river.d90_adists.lst[0]
else:
d90_default = self._study.river.d90_adists.new(0)
if self.sub_window_exists(
PollutantsWindow,
data=[self._study, None]
D90AdisTSWindow,
data=[self._study, None, d90_default]
):
return
Pollutants = PollutantsWindow(
D90AdisTS = D90AdisTSWindow(
study=self._study,
parent=self
parent=self,
data=d90_default
)
Pollutants.show()
D90AdisTS.show()
def open_pollutants(self):
if self.sub_window_exists(

73
src/View/ui/D90AdisTS.ui Normal file
View File

@ -0,0 +1,73 @@
<?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>1024</width>
<height>576</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout"/>
</item>
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_1"/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1024</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<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>
</widget>
</widget>
<resources/>
<connections/>
</ui>