IC: Add Model and View base code.

mesh
Pierre-Antoine Rouby 2023-05-30 11:44:32 +02:00
parent 10f1d11ddf
commit 0512947e0e
10 changed files with 800 additions and 4 deletions

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from copy import copy
from tools import trace, timer
class Data(object):
def __init__(self, status = None):
super(Data, self).__init()
self._status = status
self._name = ""
self._comment = ""
self._kp = 0.0
self._flow = 0.0
self._cote = 0.0
self._tiran = 0.0
def __getitems__(self, key):
val = None
if key == "name":
val = self._name
elif key == "comment":
val = self._comment
elif key == "kp":
val = self._kp
elif key == "flow":
val = self._flow
elif key == "cote":
val = self._cote
elif key == "tiran":
val = self._tiran
return val
def __setitems__(self, key, value):
if key == "name":
self._name = str(value)
elif key == "comment":
self._comment = str(value)
elif key == "kp":
self._kp = float(value)
elif key == "flow":
self._flow = float(value)
elif key == "cote":
self._cote = float(value)
elif key == "tiran":
self._tiran = float(value)
self._status.modified()
class InitialConditions(object):
def __init__(self, reach = None, status = None):
super(InitialConditions, self).__init__()
self._status = status
self._reach = reach
self._data = []
def __len__(self):
return len(self._data)
@property
def reach(self):
return self._reach
@reach.setter
def reach(self, new):
self._reach = reach
self._status.modified()
@property
def data(self):
return self._data.copy()
def get(self, index):
return self._data[index]
def set(self, index, data):
self._data.insert(index, data)
self._status.modified()
def new(self, index):
n = Data(self._status)
self._data.insert(index, n)
self._status.modified()
def insert(self, index, data):
self._data.insert(index, data)
self._status.modified()
def delete(self, data):
self._data = list(
filter(
lambda x: x in data,
self._data
)
)
self._status.modified()
def delete_i(self, indexes):
data = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._data)
)
)
)
self.delete(data)
def sort(self, reverse=False, key=None):
self._data.sort(reverse=reverse, key=key)
self._status.modified()

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from copy import copy
from tools import trace, timer
from Model.InitialConditions.InitialConditions import InitialConditions
class InitialConditionsDict(object):
def __init__(self, status = None):
super(InitialConditionsDict, self).__init__()
self._status = status
self._reach = {}
def __len__(self):
return len(self._reach)
def is_defined(self, reach):
return reach in self._reach
def get(self, reach):
if reach in self._reach:
return self._reach[reach]
new = self.new(reach)
self.set(reach, new)
return new
def set(self, reach, new):
self._reach[reach] = new
self._status.modified()
def new(self, reach):
new = InitialConditions(reach = reach, status = self._status)
self.set(reach, new)

View File

@ -9,6 +9,7 @@ from Model.Geometry.Reach import Reach
from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList
from Model.LateralContribution.LateralContributionList import LateralContributionList from Model.LateralContribution.LateralContributionList import LateralContributionList
from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
from Model.Stricklers.StricklersList import StricklersList from Model.Stricklers.StricklersList import StricklersList
from Model.Section.SectionList import SectionList from Model.Section.SectionList import SectionList
@ -62,6 +63,7 @@ class River(Graph):
self._current_reach = None self._current_reach = None
self._boundary_condition = BoundaryConditionList(status=self._status) self._boundary_condition = BoundaryConditionList(status=self._status)
self._lateral_contribution = LateralContributionList(status=self._status) self._lateral_contribution = LateralContributionList(status=self._status)
self._initial_conditions = InitialConditionsDict(status=self._status)
self._stricklers = StricklersList(status=self._status) self._stricklers = StricklersList(status=self._status)
self._sections = SectionList(status=self._status) self._sections = SectionList(status=self._status)
@ -73,6 +75,10 @@ class River(Graph):
def lateral_contribution(self): def lateral_contribution(self):
return self._lateral_contribution return self._lateral_contribution
@property
def initial_conditions(self):
return self._initial_conditions
@property @property
def stricklers(self): def stricklers(self):
return self._stricklers return self._stricklers

View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
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.InitialConditions.UndoCommand import (
SetCommand, AddCommand, DelCommand,
SortCommand, MoveCommand, PasteCommand,
DuplicateCommand,
)
from View.InitialConditions.translate import *
_translate = QCoreApplication.translate
class TableModel(QAbstractTableModel):
def __init__(self, river=None, reach=None, undo=None):
super(QAbstractTableModel, self).__init__()
self._headers = list(table_headers.keys())
self._river = river
self._reach = reach
self._undo = undo
self._ics = self._river.initial_conditions.get(reach)
def flags(self, index):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
options |= Qt.ItemIsEditable
return options
def rowCount(self, parent):
return len(self._ics)
def columnCount(self, parent):
return len(self._headers)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] is not None:
return self._ics.get(row)[self._headers[column]]
return QVariant()
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return table_headers[self._headers[section]]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
row = index.row()
column = index.column()
if self._headers[column] is not None:
self._undo.push(
SetCommand(
self._ics, row, self._headers[column], value
)
)
self.dataChanged.emit(index, index)
return True
def add(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row - 1)
self._undo.push(
AddCommand(
self._ics, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._ics, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def sort(self, _reverse, parent=QModelIndex()):
self.layoutAboutToBeChanged.emit()
self._undo.push(
SortCommand(
self._ics, 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._ics, "up", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def move_down(self, index, parent=QModelIndex()):
if row > len(self._ics):
return
target = row
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
self._undo_stack.push(
MoveCommand(
self._ics, "down", row
)
)
self.endMoveRows()
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,147 @@
# -*- coding: utf-8 -*-
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
from Model.InitialConditions.InitialConditions import InitialConditions
from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict
class SetCommand(QUndoCommand):
def __init__(self, ics, row, column, new_value):
QUndoCommand.__init__(self)
self._ics = ics
self._row = row
self._old = self._ics.get(self._row)[column]
self._new = new_value
def undo(self):
self._ics.get(self._row)[column] = self._old
def redo(self):
self._ics.get(self._row)[column] = self._new
class AddCommand(QUndoCommand):
def __init__(self, ics, index):
QUndoCommand.__init__(self)
self._ics = ics
self._index = index
self._new = None
def undo(self):
self._ics.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._ics.new(self._index)
else:
self._ics.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, ics, tab, rows):
QUndoCommand.__init__(self)
self._ics = ics
self._rows = rows
self._ic = []
for row in rows:
self._ic.append((row, self._ics.get(row)))
self._ic.sort()
def undo(self):
for row, el in self._ic:
self._ics.insert(row, el)
def redo(self):
self._ics.delete_i(self._rows)
class SortCommand(QUndoCommand):
def __init__(self, ics, tab, _reverse):
QUndoCommand.__init__(self)
self._ics = ics
self._reverse = _reverse
self._old = self._ics.data
self._indexes = None
def undo(self):
ll = self._ics.data
self._ics.sort(
key=lambda x: self._indexes[ll.index(x)]
)
def redo(self):
self._ics.sort(
reverse=self._reverse,
key=lambda x: x.name
)
if self._indexes is None:
self._indexes = list(
map(
lambda p: self._old.index(p),
self._ics.data
)
)
self._old = None
class MoveCommand(QUndoCommand):
def __init__(self, ics, tab, up, i):
QUndoCommand.__init__(self)
self._ics = ics
self._up = up == "up"
self._i = i
def undo(self):
if self._up:
self._ics.move_up(self._i)
else:
self._ics.move_down(self._i)
def redo(self):
if self._up:
self._ics.move_up(self._i)
else:
self._ics.move_down(self._i)
class PasteCommand(QUndoCommand):
def __init__(self, ics, tab, row, ic):
QUndoCommand.__init__(self)
self._ics = ics
self._row = row
self._ic = deepcopy(ic)
self._ic.reverse()
def undo(self):
self._ics.delete(self._ic)
def redo(self):
for ic in self._ic:
self._ics.insert(self._row, ic)
class DuplicateCommand(QUndoCommand):
def __init__(self, ics, tab, rows, ic):
QUndoCommand.__init__(self)
self._ics = ics
self._rows = rows
self._ic = deepcopy(ic)
self._ic.reverse()
def undo(self):
self._ics.delete(self._ic)
def redo(self):
for ic in self._ics:
self._ics.insert(self._rows[0], ic)

View File

@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
from tools import trace, timer
from View.ASubWindow import ASubMainWindow
from View.ListedSubWindow import ListedSubWindow
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
)
from View.InitialConditions.UndoCommand import (
SetCommand, AddCommand, DelCommand,
SortCommand, MoveCommand, PasteCommand,
DuplicateCommand,
)
from View.InitialConditions.Table import TableModel
from View.Plot.MplCanvas import MplCanvas
from View.InitialConditions.translate import *
_translate = QCoreApplication.translate
class InitialConditionsWindow(ASubMainWindow, ListedSubWindow):
def __init__(self, title="Initial condition",
study=None, parent=None):
title = title + " - " + study.name
super(InitialConditionsWindow, self).__init__(
name=title, ui="InitialConditions", parent=parent
)
self._study = study
self._reach = study.river.current_reach()
self._ics = self._study.river.initial_conditions.get(self._reach)
self.setup_sc()
self.setup_table()
self.setup_graph()
self.setup_connections()
self.ui.setWindowTitle(title)
def setup_sc(self):
self._undo_stack = QUndoStack()
self.undo_sc = QShortcut(QKeySequence.Undo, self)
self.redo_sc = QShortcut(QKeySequence.Redo, self)
self.copy_sc = QShortcut(QKeySequence.Copy, self)
self.paste_sc = QShortcut(QKeySequence.Paste, self)
def setup_table(self):
table = self.find(QTableView, f"tableView")
self._table = TableModel(
river = self._study.river,
reach = self._reach,
undo = self._undo_stack,
)
table.setModel(self._table)
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
def setup_graph(self):
print("TODO")
# self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
# self.canvas_1.setObjectName("canvas_1")
# self.plot_layout_1 = self.find(QVBoxLayout, "verticalLayout_1")
# self.plot_layout_1.addWidget(self.canvas_1)
# self.plot = PlotXY(
# canvas = self.canvas_1,
# data = None,
# toolbar = None,
# )
# self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
# self.canvas_2.setObjectName("canvas_2")
# self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
# self.plot_layout_2.addWidget(self.canvas_2)
# self.plot = PlotXY(
# canvas = self.canvas_2,
# data = None,
# 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_sort").triggered.connect(self.sort)
self.undo_sc.activated.connect(self.undo)
self.redo_sc.activated.connect(self.redo)
self.copy_sc.activated.connect(self.copy)
self.paste_sc.activated.connect(self.paste)
def index_selected_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 add(self):
rows = self.index_selected_rows()
if self._ics.len(tab) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
def sort(self):
self._table.sort(False)
def move_up(self):
row = self.index_selected_row()
self._table.move_up(row)
def move_down(self):
row = self.index_selected_row()
self._table.move_down(row)
def copy(self):
print("TODO")
def paste(self):
print("TODO")
def undo(self):
self._table.undo()
def redo(self):
self._table.redo()

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
_translate = QCoreApplication.translate
table_headers = {
"name": _translate("LateralContribution", "Name"),
"comment": _translate("LateralContribution", "Comment"),
"kp": _translate("LateralContribution", "KP (m)"),
"flow": _translate("LateralContribution", "Flow (m³/s)"),
"cote": _translate("LateralContribution", "Cote (m)"),
"tiran": _translate("LateralContribution", "Tiran (m)")
}

View File

@ -24,6 +24,7 @@ from View.Network.Window import NetworkWindow
from View.Geometry.Window import GeometryWindow from View.Geometry.Window import GeometryWindow
from View.BoundaryCondition.Window import BoundaryConditionWindow from View.BoundaryCondition.Window import BoundaryConditionWindow
from View.LateralContribution.Window import LateralContributionWindow from View.LateralContribution.Window import LateralContributionWindow
from View.InitialConditions.Window import InitialConditionsWindow
from View.Stricklers.Window import StricklersWindow from View.Stricklers.Window import StricklersWindow
from View.Sections.Window import SectionsWindow from View.Sections.Window import SectionsWindow
@ -49,6 +50,7 @@ define_model_action = [
"action_toolBar_boundary_cond", "action_toolBar_lateral_contrib", "action_toolBar_boundary_cond", "action_toolBar_lateral_contrib",
"action_toolBar_spills", "action_toolBar_sections", "action_toolBar_spills", "action_toolBar_sections",
"action_toolBar_stricklers", "action_toolBar_building", "action_toolBar_stricklers", "action_toolBar_building",
"action_toolBar_initial_cond",
] ]
action = ( action = (
@ -130,6 +132,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_toolBar_stricklers": self.open_stricklers, "action_toolBar_stricklers": self.open_stricklers,
"action_toolBar_sections": self.open_sections, "action_toolBar_sections": self.open_sections,
"action_toolBar_building": lambda: self.open_dummy("Ouvrages"), "action_toolBar_building": lambda: self.open_dummy("Ouvrages"),
"action_toolBar_initial_cond": self.open_initial_conditions,
} }
for action in actions: for action in actions:
@ -328,28 +331,40 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"into river network window to work on it") "into river network window to work on it")
def open_boundary_cond(self): def open_boundary_cond(self):
self.bound = BoundaryConditionWindow(study = self.model, parent=self) self.bound = BoundaryConditionWindow(study = self.model, parent = self)
self.bound.show() self.bound.show()
def open_lateral_contrib(self): def open_lateral_contrib(self):
self.lateral = LateralContributionWindow(study = self.model, parent=self) self.lateral = LateralContributionWindow(study = self.model, parent = self)
self.lateral.show() self.lateral.show()
def open_stricklers(self): def open_stricklers(self):
self.strick = StricklersWindow( self.strick = StricklersWindow(
study = self.model, study = self.model,
config = self.conf, config = self.conf,
parent=self parent = self
) )
self.strick.show() self.strick.show()
def open_sections(self): def open_sections(self):
self.sections = SectionsWindow( self.sections = SectionsWindow(
study = self.model, study = self.model,
parent=self parent = self
) )
self.sections.show() self.sections.show()
def open_initial_conditions(self):
print("xxx")
if self.model.river.has_current_reach():
print("yyy")
self.initial = InitialConditionsWindow(
study = self.model,
parent = self
)
self.initial.show()
print("zzz")
# TODO: Delete me ! # TODO: Delete me !
############### ###############
# DUMMY STUFF # # DUMMY STUFF #

View File

@ -0,0 +1,127 @@
<?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>889</width>
<height>480</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="">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButton_generate_1">
<property name="text">
<string>Generate 1</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_generate_2">
<property name="text">
<string>Generate 2</string>
</property>
</widget>
</item>
</layout>
</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>889</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>
<addaction name="action_add"/>
<addaction name="action_del"/>
<addaction name="action_sort"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add new initial condition</string>
</property>
</action>
<action name="action_del">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset>
</property>
<property name="text">
<string>delete</string>
</property>
<property name="toolTip">
<string>Delete inital condition</string>
</property>
</action>
<action name="action_sort">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset>
</property>
<property name="text">
<string>sort</string>
</property>
<property name="toolTip">
<string>Sort inital condition</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -302,6 +302,7 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_toolBar_building"/> <addaction name="action_toolBar_building"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_toolBar_initial_cond"/>
</widget> </widget>
<action name="action_menu_new"> <action name="action_menu_new">
<property name="checkable"> <property name="checkable">
@ -861,6 +862,11 @@
<string>French</string> <string>French</string>
</property> </property>
</action> </action>
<action name="action_toolBar_initial_cond">
<property name="text">
<string>Initial conditions</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>