From 568be94e56955a73269e705cdc9f8a724e542f64 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 3 May 2023 11:48:49 +0200 Subject: [PATCH 01/19] BC: Reorganize edit window. --- .../BoundaryCondition/BoundaryConditionWindow.py | 11 ++--------- .../Window.py} | 6 ++++-- src/View/BoundaryCondition/translate.py | 13 +++++++++++++ 3 files changed, 19 insertions(+), 11 deletions(-) rename src/View/BoundaryCondition/{EditBoundaryConditionWindow.py => Edit/Window.py} (87%) create mode 100644 src/View/BoundaryCondition/translate.py diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index de56722d..3c763118 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -31,18 +31,11 @@ from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, TimeOverZ, TimeOverDebit, ZOverDebit ) -from View.BoundaryCondition.EditBoundaryConditionWindow import EditBoundaryConditionWindow +from View.BoundaryCondition.translate import long_types +from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow _translate = QCoreApplication.translate -long_types = { - "ND": _translate("BoundaryCondition", "Not defined"), - "PC": _translate("BoundaryCondition", "Ponctual contribution"), - "TZ": _translate("BoundaryCondition", "Time over Z"), - "TD": _translate("BoundaryCondition", "Time over Debit"), - "ZD": _translate("BoundaryCondition", "Z over Debit"), -} - table_headers = { "name": _translate("BoundaryCondition", "Name"), "type": _translate("BoundaryCondition", "Type"), diff --git a/src/View/BoundaryCondition/EditBoundaryConditionWindow.py b/src/View/BoundaryCondition/Edit/Window.py similarity index 87% rename from src/View/BoundaryCondition/EditBoundaryConditionWindow.py rename to src/View/BoundaryCondition/Edit/Window.py index 3695d260..895ac1b6 100644 --- a/src/View/BoundaryCondition/EditBoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -12,6 +12,8 @@ from PyQt5.QtWidgets import ( QFileDialog, QTableView, QAbstractItemView, ) +from View.BoundaryCondition.translate import long_types + _translate = QCoreApplication.translate class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): @@ -31,8 +33,8 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): else _translate("BoundaryCondition", "Not associate")) title = ( _translate("BoundaryCondition", self._title) + - f"{self._data.name} " + - f"({self._data.name} - {node_name})" + f" - {self._data.name} " + + f"({long_types[self._data.bctype]} - {node_name})" ) self.ui.setWindowTitle(title) else: diff --git a/src/View/BoundaryCondition/translate.py b/src/View/BoundaryCondition/translate.py new file mode 100644 index 00000000..bcd1be54 --- /dev/null +++ b/src/View/BoundaryCondition/translate.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +long_types = { + "ND": _translate("BoundaryCondition", "Not defined"), + "PC": _translate("BoundaryCondition", "Ponctual contribution"), + "TZ": _translate("BoundaryCondition", "Time over Z"), + "TD": _translate("BoundaryCondition", "Time over Debit"), + "ZD": _translate("BoundaryCondition", "Z over Debit"), +} From d49091a46941261c27c7763ff23983ae795b977c Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 3 May 2023 15:23:03 +0200 Subject: [PATCH 02/19] BC: Edit: Add table display. --- .../BoundaryCondition/BoundaryCondition.py | 49 ++++- .../BoundaryConditionTypes.py | 1 + src/View/BoundaryCondition/BCUndoCommand.py | 4 +- .../BoundaryConditionWindow.py | 1 + src/View/BoundaryCondition/Edit/Table.py | 173 ++++++++++++++++++ .../BoundaryCondition/Edit/UndoCommand.py | 147 +++++++++++++++ src/View/BoundaryCondition/Edit/Window.py | 94 ++++++++++ 7 files changed, 463 insertions(+), 6 deletions(-) create mode 100644 src/View/BoundaryCondition/Edit/Table.py create mode 100644 src/View/BoundaryCondition/Edit/UndoCommand.py diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index cca0ebe9..6978ca98 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -15,6 +15,9 @@ class BoundaryCondition(object): self._header = [] self._types = [int, float] + def __len__(self): + return len(self._data) + @property def name(self): return self._name @@ -46,9 +49,16 @@ class BoundaryCondition(object): def data(self): return self._data.copy() + def get_type_column(self, column): + if 0 <= column < 2: + return self._types[column] + return None + + @property def _default_0(self): return self._types[0](0) + @property def _default_1(self): return self._types[1](0.0) @@ -56,13 +66,14 @@ class BoundaryCondition(object): return self._data is not None def add(self, index:int): - value = (self.default_0, self_default_1) + value = (self._default_0, self._default_1) self._data.insert(index, value) + return value def insert(self, index:int, value): self._data.insert(index, value) - def delete(self, indexes): + def delete_i(self, indexes): self._data = list( map( lambda e: e[1], @@ -73,12 +84,30 @@ class BoundaryCondition(object): ) ) - def sort(self, _reverse): - self._data.sort(reverse=_reverse) + def delete(self, els): + self._data = list( + filter( + lambda e: e not in els, + self.data + ) + ) + + def sort(self, _reverse=False, key=None): + if key is None: + self._data.sort(reverse=_reverse) + else: + self._data.sort(reverse=_reverse, key=key) def get_i(self, index): return self.data[index] + def get_range(self, _range): + l = [] + for r in _range: + l.append(r) + return l + + def _set_i_c_v(self, index, column, value): v = list(self._data[index]) v[column] = self._types[column](value) @@ -103,3 +132,15 @@ class BoundaryCondition(object): new._set_i_c_v(ind, j, v[i]) return new + + def move_up(self, index): + if index < len(self): + next = index - 1 + d = self._data + d[index], d[next] = d[next], d[index] + + def move_down(self, index): + if index >= 0: + prev = index + 1 + d = self._data + d[index], d[prev] = d[prev], d[index] diff --git a/src/Model/BoundaryCondition/BoundaryConditionTypes.py b/src/Model/BoundaryCondition/BoundaryConditionTypes.py index 209e9ac7..327bc3a5 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionTypes.py +++ b/src/Model/BoundaryCondition/BoundaryConditionTypes.py @@ -41,5 +41,6 @@ class ZOverDebit(BoundaryCondition): self._header = ["z", "debit"] self._types = [float, float] + @property def _default_0(self): return 0.0 diff --git a/src/View/BoundaryCondition/BCUndoCommand.py b/src/View/BoundaryCondition/BCUndoCommand.py index dfddf82c..3cd3ec9f 100644 --- a/src/View/BoundaryCondition/BCUndoCommand.py +++ b/src/View/BoundaryCondition/BCUndoCommand.py @@ -174,5 +174,5 @@ class DuplicateCommand(QUndoCommand): self._lst.delete(self._bc) def redo(self): - for profile in self._profiles: - self._lst.insert(self._rows[0], profile) + for bc in self._bcs: + self._lst.insert(self._rows[0], bc) diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index 3c763118..07fdeb6f 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -319,6 +319,7 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): def index_selected_rows(self): table = self.find(QTableView, "tableView") return list( + # Delete duplicate set( map( lambda i: i.row(), diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py new file mode 100644 index 00000000..9235bee8 --- /dev/null +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, + QCoreApplication, QModelIndex, pyqtSlot, + QRect, +) + +from PyQt5.QtWidgets import ( + QTableView, QAbstractItemView, +) + +from View.BoundaryCondition.Edit.UndoCommand import ( + SetDataCommand, AddCommand, DelCommand, + SortCommand, MoveCommand, PasteCommand, + DuplicateCommand, +) + +from Model.BoundaryCondition.BoundaryConditionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) + +_translate = QCoreApplication.translate + +table_headers = { + "time": _translate("BoundaryCondition", "Time"), + "debit": _translate("BoundaryCondition", "Debit"), + "z": _translate("BoundaryCondition", "Z (m)") +} + + +class TableModel(QAbstractTableModel): + def __init__(self, data=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = data.header + self._data = data + self._undo = undo + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._data) + + 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() + + 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}" + else: + # TODO: Time format + value = f"{v}" + + return value + + 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() + + self._undo.push( + SetDataCommand( + self._data, row, 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._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, 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._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 undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/BoundaryCondition/Edit/UndoCommand.py b/src/View/BoundaryCondition/Edit/UndoCommand.py new file mode 100644 index 00000000..7f3bbef5 --- /dev/null +++ b/src/View/BoundaryCondition/Edit/UndoCommand.py @@ -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.BoundaryCondition.BoundaryCondition import BoundaryCondition + +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] + self._new = 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._bc = [] + for row in rows: + self._bc.append((row, self._data.get_i(row))) + self._bc.sort() + + def undo(self): + for row, el in self._bc: + 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.name + ) + 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, bc): + QUndoCommand.__init__(self) + + self._data = data + self._row = row + self._bc = deepcopy(bc) + self._bc.reverse() + + def undo(self): + self._data.delete(self._bc) + + def redo(self): + for bc in self._bc: + 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._bc = deepcopy(bc) + self._bc.reverse() + + def undo(self): + self._data.delete(self._bc) + + def redo(self): + for bc in self._bcs: + self._data.insert(self._rows[0], bc) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 895ac1b6..7de37c5e 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -3,6 +3,10 @@ from View.ASubWindow import ASubMainWindow from View.ListedSubWindow import ListedSubWindow +from PyQt5.QtGui import ( + QKeySequence, +) + from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, ) @@ -10,9 +14,11 @@ from PyQt5.QtCore import ( from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, + QUndoStack, QShortcut, QAction, QItemDelegate, ) from View.BoundaryCondition.translate import long_types +from View.BoundaryCondition.Edit.Table import TableModel _translate = QCoreApplication.translate @@ -26,6 +32,9 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self._title = title self.setup_window() + self.setup_sc() + self.setup_table() + self.setup_connections() def setup_window(self): if self._data is not None: @@ -39,3 +48,88 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.ui.setWindowTitle(title) else: self.ui.setWindowTitle(_translate("BoundaryCondition", self._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, "tableView") + self._table = TableModel( + data = self._data, + undo = self._undo_stack + ) + table.setModel(self._table) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.setAlternatingRowColors(True) + + 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, "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]) + + 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() From 2f0b5b2e31a368da2292e74af51b96eea8bdbed7 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 3 May 2023 17:14:12 +0200 Subject: [PATCH 03/19] BC: Edit: Add plot (not finish). --- .../BoundaryConditionWindow.py | 2 + src/View/BoundaryCondition/Edit/Plot.py | 67 +++++++++++++++++++ src/View/BoundaryCondition/Edit/Table.py | 19 ++---- .../BoundaryCondition/Edit/UndoCommand.py | 2 +- src/View/BoundaryCondition/Edit/Window.py | 16 +++++ src/View/BoundaryCondition/Edit/translate.py | 11 +++ src/View/ui/BoundaryConditions.ui | 48 +++++++++---- 7 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 src/View/BoundaryCondition/Edit/Plot.py create mode 100644 src/View/BoundaryCondition/Edit/translate.py diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index 07fdeb6f..0688b700 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -27,10 +27,12 @@ from View.BoundaryCondition.BCUndoCommand import ( AddCommand, DelCommand, SortCommand, MoveCommand, PasteCommand, DuplicateCommand, ) + from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, TimeOverZ, TimeOverDebit, ZOverDebit ) + from View.BoundaryCondition.translate import long_types from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow diff --git a/src/View/BoundaryCondition/Edit/Plot.py b/src/View/BoundaryCondition/Edit/Plot.py new file mode 100644 index 00000000..be3ad854 --- /dev/null +++ b/src/View/BoundaryCondition/Edit/Plot.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +from tools import timer, trace +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +from View.BoundaryCondition.Edit.translate import * + +_translate = QCoreApplication.translate + +class Plot(APlot): + def __init__(self, canvas=None, data=None, toolbar=None): + super(Plot, self).__init__( + canvas=canvas, + data=data, + toolbar=toolbar + ) + + @timer + def draw(self): + self.canvas.axes.cla() + self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) + + if len(self.data) == 0: + self._init = False + return + + # 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='r', lw=1., + markersize=5, marker='+', + picker=30 + ) + + # Plot label + header = self.data.header + self.canvas.axes.set_xlabel( + table_headers[header[0]], color='black', fontsize=10 + ) + self.canvas.axes.set_ylabel( + table_headers[header[1]], color='black', fontsize=10 + ) + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() + self.toolbar.update() + + self._init = True + + @timer + def update(self, ind=None): + if self._init == False: + self.draw() + return + + # if ind is not None: + + # else: + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index 9235bee8..1e9033d0 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -15,24 +15,19 @@ from PyQt5.QtWidgets import ( QTableView, QAbstractItemView, ) -from View.BoundaryCondition.Edit.UndoCommand import ( - SetDataCommand, AddCommand, DelCommand, - SortCommand, MoveCommand, PasteCommand, - DuplicateCommand, -) - from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, TimeOverZ, TimeOverDebit, ZOverDebit ) -_translate = QCoreApplication.translate +from View.BoundaryCondition.Edit.UndoCommand import ( + SetDataCommand, AddCommand, DelCommand, + SortCommand, MoveCommand, PasteCommand, + DuplicateCommand, +) +from View.BoundaryCondition.Edit.translate import * -table_headers = { - "time": _translate("BoundaryCondition", "Time"), - "debit": _translate("BoundaryCondition", "Debit"), - "z": _translate("BoundaryCondition", "Z (m)") -} +_translate = QCoreApplication.translate class TableModel(QAbstractTableModel): diff --git a/src/View/BoundaryCondition/Edit/UndoCommand.py b/src/View/BoundaryCondition/Edit/UndoCommand.py index 7f3bbef5..c20288ff 100644 --- a/src/View/BoundaryCondition/Edit/UndoCommand.py +++ b/src/View/BoundaryCondition/Edit/UndoCommand.py @@ -79,7 +79,7 @@ class SortCommand(QUndoCommand): def redo(self): self._data.sort( - reverse=self._reverse, + _reverse=self._reverse, key=lambda x: x.name ) if self._indexes is None: diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 7de37c5e..5823a2ce 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -17,8 +17,11 @@ from PyQt5.QtWidgets import ( QUndoStack, QShortcut, QAction, QItemDelegate, ) +from View.Plot.MplCanvas import MplCanvas + from View.BoundaryCondition.translate import long_types from View.BoundaryCondition.Edit.Table import TableModel +from View.BoundaryCondition.Edit.Plot import Plot _translate = QCoreApplication.translate @@ -34,6 +37,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.setup_window() self.setup_sc() self.setup_table() + self.setup_plot() self.setup_connections() def setup_window(self): @@ -67,6 +71,18 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): table.setSelectionBehavior(QAbstractItemView.SelectRows) table.setAlternatingRowColors(True) + def setup_plot(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.verticalLayout.addWidget(self.canvas) + + self.plot = Plot( + canvas = self.canvas, + data = self._data, + ) + 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) diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py new file mode 100644 index 00000000..e3fa7513 --- /dev/null +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "time": _translate("BoundaryCondition", "Time"), + "debit": _translate("BoundaryCondition", "Debit"), + "z": _translate("BoundaryCondition", "Z (m)") +} diff --git a/src/View/ui/BoundaryConditions.ui b/src/View/ui/BoundaryConditions.ui index 8a0f8f96..5d68ac0a 100644 --- a/src/View/ui/BoundaryConditions.ui +++ b/src/View/ui/BoundaryConditions.ui @@ -25,19 +25,43 @@ - - - - 0 - 0 - + + + 0 - - Qt::Horizontal - - - - + + + Liquid + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + + + + + Solid + + + + + Suspension + From 63d754106248a53ffb3961daf137b2f64e14ee52 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 09:44:19 +0200 Subject: [PATCH 04/19] BC: Edit: Fix sort command and scale column. --- src/View/BoundaryCondition/Edit/Table.py | 3 +++ src/View/BoundaryCondition/Edit/UndoCommand.py | 2 +- src/View/BoundaryCondition/Edit/Window.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index 1e9033d0..16670fe5 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -50,6 +50,9 @@ class TableModel(QAbstractTableModel): return len(self._headers) def data(self, index, role): + if role == Qt.TextAlignmentRole: + return Qt.AlignHCenter | Qt.AlignVCenter + if role != Qt.ItemDataRole.DisplayRole: return QVariant() diff --git a/src/View/BoundaryCondition/Edit/UndoCommand.py b/src/View/BoundaryCondition/Edit/UndoCommand.py index c20288ff..ba8fe77c 100644 --- a/src/View/BoundaryCondition/Edit/UndoCommand.py +++ b/src/View/BoundaryCondition/Edit/UndoCommand.py @@ -80,7 +80,7 @@ class SortCommand(QUndoCommand): def redo(self): self._data.sort( _reverse=self._reverse, - key=lambda x: x.name + key=lambda x: x[0] ) if self._indexes is None: self._indexes = list( diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 5823a2ce..4a2bbc55 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -15,6 +15,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, + QHeaderView, ) from View.Plot.MplCanvas import MplCanvas @@ -69,6 +70,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): ) table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) def setup_plot(self): From f8355021e7817dddf43b9fe8a16fe12ad6dfc6bb Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 10:22:39 +0200 Subject: [PATCH 05/19] BC: Edit: Fix update plot. --- src/View/BoundaryCondition/Edit/Plot.py | 12 ++++++++---- src/View/BoundaryCondition/Edit/Window.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Plot.py b/src/View/BoundaryCondition/Edit/Plot.py index be3ad854..39af012c 100644 --- a/src/View/BoundaryCondition/Edit/Plot.py +++ b/src/View/BoundaryCondition/Edit/Plot.py @@ -35,7 +35,7 @@ class Plot(APlot): x, y, color='r', lw=1., markersize=5, marker='+', - picker=30 + picker=30, ) # Plot label @@ -47,9 +47,10 @@ class Plot(APlot): table_headers[header[1]], color='black', fontsize=10 ) + self.canvas.axes.autoscale_view(True, True, True) self.canvas.figure.tight_layout() self.canvas.figure.canvas.draw_idle() - self.toolbar.update() + #self.toolbar.update() self._init = True @@ -59,9 +60,12 @@ class Plot(APlot): self.draw() return - # if ind is not None: + x = list(map(lambda v: v[0], self.data.data)) + y = list(map(lambda v: v[1], self.data.data)) - # else: + self._line.set_data(x, y) + self.canvas.axes.relim() + self.canvas.axes.autoscale() self.canvas.figure.tight_layout() self.canvas.figure.canvas.draw_idle() diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 4a2bbc55..1b2e554c 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -95,6 +95,11 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.copy_sc.activated.connect(self.copy) self.paste_sc.activated.connect(self.paste) + 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()\ @@ -121,33 +126,43 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): 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): print("TODO") + self.plot.update() def paste(self): print("TODO") + self.plot.update() def undo(self): self._table.undo() + self.plot.update() def redo(self): self._table.redo() + self.plot.update() From fff5668afeff2b2f8810c5c87b6f313a3e6cd808 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 14:53:51 +0200 Subject: [PATCH 06/19] BC: Edit: Add paste feature. --- .../BoundaryCondition/BoundaryCondition.py | 18 ++++++++++++++ src/View/BoundaryCondition/Edit/Table.py | 22 +++++++++++++++++ .../BoundaryCondition/Edit/UndoCommand.py | 10 ++++---- src/View/BoundaryCondition/Edit/Window.py | 24 ++++++++++++++++--- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index 6978ca98..d5486eaa 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -65,6 +65,24 @@ class BoundaryCondition(object): def is_define(self): return self._data is not None + def new_from_data(self, header, data): + new_0 = self._default_0 + new_1 = self._default_1 + + if len(header) != 0: + for i in [0,1]: + for j in range(len(header)): + if self._header[i] == header[j]: + if i == 0: + new_0 = self._types[i](data[j]) + else: + new_1 = self._types[i](data[j]) + else: + new_0 = self._types[0](data[0]) + new_1 = self._types[1](data[1]) + + return (new_0, new_1) + def add(self, index:int): value = (self._default_0, self._default_1) self._data.insert(index, value) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index 16670fe5..c6fa6722 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -162,6 +162,28 @@ class TableModel(QAbstractTableModel): 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() + + def undo(self): self._undo.undo() self.layoutChanged.emit() diff --git a/src/View/BoundaryCondition/Edit/UndoCommand.py b/src/View/BoundaryCondition/Edit/UndoCommand.py index ba8fe77c..b2eb09ac 100644 --- a/src/View/BoundaryCondition/Edit/UndoCommand.py +++ b/src/View/BoundaryCondition/Edit/UndoCommand.py @@ -114,19 +114,19 @@ class MoveCommand(QUndoCommand): class PasteCommand(QUndoCommand): - def __init__(self, data, row, bc): + def __init__(self, data, row, bcs): QUndoCommand.__init__(self) self._data = data self._row = row - self._bc = deepcopy(bc) - self._bc.reverse() + self._bcs = bcs + self._bcs.reverse() def undo(self): - self._data.delete(self._bc) + self._data.delete(self._bcs) def redo(self): - for bc in self._bc: + for bc in self._bcs: self._data.insert(self._row, bc) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 1b2e554c..5f0b38e4 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -152,11 +152,29 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): def copy(self): - print("TODO") - self.plot.update() + 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): - print("TODO") + 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): From 9d3830850c0f685c2706a95973e6707d6b70e219 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 16:08:19 +0200 Subject: [PATCH 07/19] BC: New file Table. --- .../BoundaryConditionWindow.py | 216 +---------------- src/View/BoundaryCondition/Table.py | 225 ++++++++++++++++++ src/View/BoundaryCondition/translate.py | 19 ++ 3 files changed, 249 insertions(+), 211 deletions(-) create mode 100644 src/View/BoundaryCondition/Table.py diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index 0688b700..d1597c13 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -33,221 +33,15 @@ from Model.BoundaryCondition.BoundaryConditionTypes import ( TimeOverZ, TimeOverDebit, ZOverDebit ) -from View.BoundaryCondition.translate import long_types +from View.BoundaryCondition.Table import ( + TableModel, ComboBoxDelegate +) + +from View.BoundaryCondition.translate import * from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow _translate = QCoreApplication.translate -table_headers = { - "name": _translate("BoundaryCondition", "Name"), - "type": _translate("BoundaryCondition", "Type"), - "node": _translate("BoundaryCondition", "Node") -} - -BC_types = { - "ND": NotDefined, - "PC": PonctualContribution, - "TZ": TimeOverZ, - "TD": TimeOverDebit, - "ZD": ZOverDebit -} - - -class ComboBoxDelegate(QItemDelegate): - def __init__(self, data=None, mode="type", parent=None): - super(ComboBoxDelegate, self).__init__(parent) - - self._data = data - self._mode = mode - - def createEditor(self, parent, option, index): - self.editor = QComboBox(parent) - - if self._mode == "type": - self.editor.addItems([long_types[k] for k in BC_types.keys()]) - else: - self.editor.addItems( - [_translate("BoundaryCondition", "Not associate")] + - self._data.nodes_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 and 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(QAbstractTableModel): - def __init__(self, data=None, undo=None): - super(QAbstractTableModel, self).__init__() - self._headers = list(table_headers.keys()) - self._data = data - self._undo = undo - self._lst = self._data.boundary_condition - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self._lst) - - 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] == "name": - return self._lst[row].name - elif self._headers[column] == "type": - t = self._lst[row].bctype - return long_types[t] - elif self._headers[column] == "node": - n = self._lst[row].node - if n is None: - return _translate("BoundaryCondition", "Not associate") - return n.name - - 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] == "name": - self._undo.push( - SetNameCommand( - self._lst, row, value - ) - ) - elif self._headers[column] == "type": - key = next(k for k, v in long_types.items() if v == value) - self._undo.push( - SetTypeCommand( - self._lst, row, BC_types[key] - ) - ) - elif self._headers[column] == "node": - self._undo.push( - SetNodeCommand( - self._lst, row, self._data.node(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._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, rows - ) - ) - - self.endRemoveRows() - - def sort(self, _reverse, parent=QModelIndex()): - self.layoutAboutToBeChanged.emit() - - self._undo.push( - SortCommand( - self._lst, 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, "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, "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() - class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): def __init__(self, title="BoundaryConditions", study=None, parent=None): diff --git a/src/View/BoundaryCondition/Table.py b/src/View/BoundaryCondition/Table.py new file mode 100644 index 00000000..850d3b21 --- /dev/null +++ b/src/View/BoundaryCondition/Table.py @@ -0,0 +1,225 @@ +# -*- 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.BoundaryCondition.BCUndoCommand import ( + SetNameCommand, SetNodeCommand, SetTypeCommand, + AddCommand, DelCommand, SortCommand, + MoveCommand, PasteCommand, DuplicateCommand, +) + +from Model.BoundaryCondition.BoundaryConditionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) +from View.BoundaryCondition.translate import * + +_translate = QCoreApplication.translate + +class ComboBoxDelegate(QItemDelegate): + def __init__(self, data=None, mode="type", parent=None): + super(ComboBoxDelegate, self).__init__(parent) + + self._data = data + self._mode = mode + + def createEditor(self, parent, option, index): + self.editor = QComboBox(parent) + + if self._mode == "type": + self.editor.addItems([long_types[k] for k in BC_types.keys()]) + else: + self.editor.addItems( + [_translate("BoundaryCondition", "Not associate")] + + self._data.nodes_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 and 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(QAbstractTableModel): + def __init__(self, data=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._data = data + self._undo = undo + self._lst = self._data.boundary_condition + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._lst) + + 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] == "name": + return self._lst[row].name + elif self._headers[column] == "type": + t = self._lst[row].bctype + return long_types[t] + elif self._headers[column] == "node": + n = self._lst[row].node + if n is None: + return _translate("BoundaryCondition", "Not associate") + return n.name + + 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] == "name": + self._undo.push( + SetNameCommand( + self._lst, row, value + ) + ) + elif self._headers[column] == "type": + key = next(k for k, v in long_types.items() if v == value) + self._undo.push( + SetTypeCommand( + self._lst, row, BC_types[key] + ) + ) + elif self._headers[column] == "node": + self._undo.push( + SetNodeCommand( + self._lst, row, self._data.node(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._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, rows + ) + ) + + self.endRemoveRows() + + def sort(self, _reverse, parent=QModelIndex()): + self.layoutAboutToBeChanged.emit() + + self._undo.push( + SortCommand( + self._lst, 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, "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, "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() diff --git a/src/View/BoundaryCondition/translate.py b/src/View/BoundaryCondition/translate.py index bcd1be54..762ca867 100644 --- a/src/View/BoundaryCondition/translate.py +++ b/src/View/BoundaryCondition/translate.py @@ -2,6 +2,11 @@ from PyQt5.QtCore import QCoreApplication +from Model.BoundaryCondition.BoundaryConditionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) + _translate = QCoreApplication.translate long_types = { @@ -11,3 +16,17 @@ long_types = { "TD": _translate("BoundaryCondition", "Time over Debit"), "ZD": _translate("BoundaryCondition", "Z over Debit"), } + +table_headers = { + "name": _translate("BoundaryCondition", "Name"), + "type": _translate("BoundaryCondition", "Type"), + "node": _translate("BoundaryCondition", "Node") +} + +BC_types = { + "ND": NotDefined, + "PC": PonctualContribution, + "TZ": TimeOverZ, + "TD": TimeOverDebit, + "ZD": ZOverDebit +} From d95e69ba9bab90a61b3be39141c6231cc3b53419 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 16:10:10 +0200 Subject: [PATCH 08/19] BC: Fix minor bug. --- src/View/BoundaryCondition/Table.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/View/BoundaryCondition/Table.py b/src/View/BoundaryCondition/Table.py index 850d3b21..f7bc377a 100644 --- a/src/View/BoundaryCondition/Table.py +++ b/src/View/BoundaryCondition/Table.py @@ -169,6 +169,7 @@ class TableModel(QAbstractTableModel): ) self.endRemoveRows() + self.layoutChanged.emit() def sort(self, _reverse, parent=QModelIndex()): self.layoutAboutToBeChanged.emit() From d13130c9a7ab260daf9970ce8fd7c328b4d9edd2 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 17:05:15 +0200 Subject: [PATCH 09/19] BC: Add graph display. --- .../BoundaryConditionWindow.py | 14 ++++++++++++- src/View/Network/GraphWidget.py | 21 ++++++++++++++++--- src/View/ui/BoundaryConditions.ui | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index d1597c13..d8ad1362 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -19,7 +19,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, - QComboBox, + QComboBox, QVBoxLayout, QHeaderView ) from View.BoundaryCondition.BCUndoCommand import ( @@ -37,6 +37,7 @@ from View.BoundaryCondition.Table import ( TableModel, ComboBoxDelegate ) +from View.Network.GraphWidget import GraphWidget from View.BoundaryCondition.translate import * from View.BoundaryCondition.Edit.Window import EditBoundaryConditionWindow @@ -54,6 +55,7 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.setup_sc() self.setup_table() + self.setup_graph() self.setup_connections() self.ui.setWindowTitle(title) @@ -91,8 +93,18 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): ) table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setAlternatingRowColors(True) + def setup_graph(self): + self.graph_widget = GraphWidget( + self._study.river, + min_size=None, size=(200,200), + only_display=True, + parent=self + ) + self.graph_layout = self.find(QVBoxLayout, "verticalLayout") + self.graph_layout.addWidget(self.graph_widget) def setup_connections(self): self.find(QAction, "action_add").triggered.connect(self.add) diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index e6281c0f..522f3210 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -79,12 +79,18 @@ class NodeItem(QGraphicsItem): def mousePressEvent(self, event): self.update() - super(NodeItem, self).mousePressEvent(event) + if not self.graph._only_display: + super(NodeItem, self).mousePressEvent(event) def mouseReleaseEvent(self, event): self.update() super(NodeItem, self).mouseReleaseEvent(event) + def mouseMoveEvent(self, event): + self.update() + if not self.graph._only_display: + super(NodeItem, self).mouseMoveEvent(event) + class EdgeItem(QGraphicsItem): Type = QGraphicsItem.UserType + 2 @@ -252,12 +258,15 @@ class GraphWidget(QGraphicsView): changeEdge = pyqtSignal(object) changeNode = pyqtSignal(object) - def __init__(self, graph, parent=None): + def __init__(self, graph, parent=None, + min_size=(400, 400), max_size=None, + size=None, only_display=False): super(GraphWidget, self).__init__(parent=parent) self.timerId = 0 self.parent = parent self._state = "move" + self._only_display = only_display self.graph = graph @@ -286,7 +295,13 @@ class GraphWidget(QGraphicsView): self.scale(1, 1) self.previousScale = 1 - self.setMinimumSize(400, 400) + + if min_size: + self.setMinimumSize(*min_size) + if max_size: + self.setMaximumSize(*max_size) + if size: + self.resize(*size) self.create_items() diff --git a/src/View/ui/BoundaryConditions.ui b/src/View/ui/BoundaryConditions.ui index 5d68ac0a..7f4ea44c 100644 --- a/src/View/ui/BoundaryConditions.ui +++ b/src/View/ui/BoundaryConditions.ui @@ -37,7 +37,7 @@ - + 0 0 From cbc249299cc20b802c13208ebfac352b321d4508 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 4 May 2023 17:31:25 +0200 Subject: [PATCH 10/19] BC: Split tabWidget and graph display. --- .../BoundaryConditionWindow.py | 6 +- src/View/ui/BoundaryConditions.ui | 74 ++++++++++--------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index d8ad1362..38b9e6b1 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -69,7 +69,7 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.paste_sc = QShortcut(QKeySequence.Paste, self) def setup_table(self): - table = self.find(QTableView, "tableView") + table = self.find(QTableView, "tableView_liquid") self._table = TableModel( data = self._study.river, undo = self._undo_stack @@ -119,13 +119,13 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): def index_selected_row(self): - table = self.find(QTableView, "tableView") + table = self.find(QTableView, "tableView_liquid") return table.selectionModel()\ .selectedRows()[0]\ .row() def index_selected_rows(self): - table = self.find(QTableView, "tableView") + table = self.find(QTableView, "tableView_liquid") return list( # Delete duplicate set( diff --git a/src/View/ui/BoundaryConditions.ui b/src/View/ui/BoundaryConditions.ui index 7f4ea44c..602a1887 100644 --- a/src/View/ui/BoundaryConditions.ui +++ b/src/View/ui/BoundaryConditions.ui @@ -25,43 +25,47 @@ - - - 0 + + + Qt::Horizontal - - - Liquid - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - - + + + 0 + + + + Liquid + + + + + + + + + + Solid + + + + + + + + + + Suspenssion + + + + + + + - - - Solid - - - - - Suspension - + + From 3617fde986cdc9fc24119888c4c1d297719b13d2 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 9 May 2023 13:36:44 +0200 Subject: [PATCH 11/19] BC: Add multiple tab for liquid, solid and suspenssion. --- .../BoundaryCondition/BoundaryCondition.py | 4 + .../BoundaryConditionList.py | 62 +++++++--- .../BoundaryConditionTypes.py | 16 +++ src/View/BoundaryCondition/BCUndoCommand.py | 106 ++++++++++-------- .../BoundaryConditionWindow.py | 93 +++++++++------ src/View/BoundaryCondition/Table.py | 48 +++++--- src/View/ui/BoundaryConditions.ui | 6 +- 7 files changed, 214 insertions(+), 121 deletions(-) diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index d5486eaa..5bd26189 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -18,6 +18,10 @@ class BoundaryCondition(object): def __len__(self): return len(self._data) + @classmethod + def compatibility(cls): + return ["liquid", "solid", "suspenssion"] + @property def name(self): return self._name diff --git a/src/Model/BoundaryCondition/BoundaryConditionList.py b/src/Model/BoundaryCondition/BoundaryConditionList.py index a44629d4..4b14d924 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionList.py +++ b/src/Model/BoundaryCondition/BoundaryConditionList.py @@ -11,56 +11,82 @@ from Model.BoundaryCondition.BoundaryConditionTypes import ( TimeOverZ, TimeOverDebit, ZOverDebit ) -class BoundaryConditionList(list): +class BoundaryConditionList(object): def __init__(self): super(BoundaryConditionList, self).__init__() - def new(self, index): + self._tabs = { + "liquid" : [], + "solid" : [], + "suspenssion" : [] + } + + def len(self, lst): + return len(self._tabs[lst]) + + def get_tab(self, lst): + return self._tabs[lst].copy() + + def get(self, lst, row): + return self._tabs[lst][row] + + def set(self, lst, row, new): + self._tabs[lst][row] = new + + def new(self, lst, index): n = NotDefined() - self.insert(index, n) + self._tabs[lst].insert(index, n) return n - def delete(self, bcs): - for bc in bcs: - self.remove(bc) + def insert(self, lst, index, new): + self._tabs[lst].insert(index, new) - def delete_i(self, indexes): + def delete(self, lst, bcs): + for bc in bcs: + self._tabs[lst].remove(bc) + + def delete_i(self, lst, indexes): bcs = list( map( lambda x: x[1], filter( lambda x: x[0] in indexes, - enumerate(self) + enumerate(self._tabs[lst]) ) ) ) - self.delete(bcs) + self.delete(lst, bcs) - def move_up(self, index): - if index < len(self): + def sort(self, lst, reverse=False, key=None): + self._tabs[lst].sort(reverse=reverse, key=key) + + def move_up(self, lst, index): + if index < len(self._tabs[lst]): next = index - 1 - self[index], self[next] = self[next], self[index] + l = self._tabs[lst] + l[index], l[next] = l[next], l[index] - def move_down(self, index): + def move_down(self, lst, index): if index >= 0: prev = index + 1 - self[index], self[prev] = self[prev], self[index] + l = self._tabs[lst] + l[index], l[prev] = l[prev], l[index] def __copy__(self): new = BoundaryConditionList() - for bc in self: - new.append(bc) + for l in self._tabs: + new.tabs[l] = self._tabs[l].copy() return new def __deepcopy__(self): new = BoundaryConditionList() - for bc in self: - new.append(deepcopy(bc)) + for l in self._tabs: + new.tabs[l] = self._tabs[l].deepcopy() return new diff --git a/src/Model/BoundaryCondition/BoundaryConditionTypes.py b/src/Model/BoundaryCondition/BoundaryConditionTypes.py index 327bc3a5..85113389 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionTypes.py +++ b/src/Model/BoundaryCondition/BoundaryConditionTypes.py @@ -19,6 +19,10 @@ class PonctualContribution(BoundaryCondition): self._type = "PC" self._header = ["time", "debit"] + @classmethod + def compatibility(cls): + return ["liquid"] + class TimeOverZ(BoundaryCondition): def __init__(self, name:str = ""): super(TimeOverZ, self).__init__(name=name) @@ -26,6 +30,10 @@ class TimeOverZ(BoundaryCondition): self._type = "TZ" self._header = ["time", "z"] + @classmethod + def compatibility(cls): + return ["liquid"] + class TimeOverDebit(BoundaryCondition): def __init__(self, name:str = ""): super(TimeOverDebit, self).__init__(name=name) @@ -33,6 +41,10 @@ class TimeOverDebit(BoundaryCondition): self._type = "TD" self._header = ["time", "debit"] + @classmethod + def compatibility(cls): + return ["liquid"] + class ZOverDebit(BoundaryCondition): def __init__(self, name:str = ""): super(ZOverDebit, self).__init__(name=name) @@ -41,6 +53,10 @@ class ZOverDebit(BoundaryCondition): self._header = ["z", "debit"] self._types = [float, float] + @classmethod + def compatibility(cls): + return ["liquid"] + @property def _default_0(self): return 0.0 diff --git a/src/View/BoundaryCondition/BCUndoCommand.py b/src/View/BoundaryCondition/BCUndoCommand.py index 3cd3ec9f..7b1c7f5e 100644 --- a/src/View/BoundaryCondition/BCUndoCommand.py +++ b/src/View/BoundaryCondition/BCUndoCommand.py @@ -11,105 +11,114 @@ from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList class SetNameCommand(QUndoCommand): - def __init__(self, lst, index, new_value): + def __init__(self, bcs, tab, index, new_value): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._index = index - self._old = self._lst[self._index].name + self._old = self._bcs.get(self._tab, self._index).name self._new = new_value def undo(self): - self._lst[self._index].name = self._old + self._bcs.get(self._tab, self._index).name = self._old def redo(self): - self._lst[self._index].name = self._new + self._bcs.get(self._tab, self._index).name = self._new class SetNodeCommand(QUndoCommand): - def __init__(self, lst, index, node): + def __init__(self, bcs, tab, index, node): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._index = index - self._old = self._lst[index].node + self._old = self._bcs.get(self._tab, self._index).node self._new = node def undo(self): - self._lst[self._index].node = self._old + self._bcs.get(self._tab, self._index).node = self._old def redo(self): - self._lst[self._index].node = self._new + self._bcs.get(self._tab, self._index).node = self._new class SetTypeCommand(QUndoCommand): - def __init__(self, lst, index, _type): + def __init__(self, bcs, tab, index, _type): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._index = index self._type = _type - self._old = self._lst[index] - self._new = self._lst[index].convert(self._type) + self._old = self._bcs.get(self._tab, self._index) + self._new = self._bcs.get(self._tab, self._index)\ + .convert(self._type) def undo(self): - self._lst[self._index] = self._old + self._bcs.set(self._tab, self._index, self._old) def redo(self): - self._lst[self._index] = self._new + self._bcs.set(self._tab, self._index, self._new) class AddCommand(QUndoCommand): - def __init__(self, lst, index): + def __init__(self, bcs, tab, index): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._index = index self._new = None def undo(self): - self._lst.delete_i([self._index]) + self._bcs.delete_i(self._tab, [self._index]) def redo(self): if self._new is None: - self._new = self._lst.new(self._index) + self._new = self._bcs.new(self._tab, self._index) else: - self._lst.insert(self._index, self._new) + self._bcs.insert(self._tab, self._index, self._new) class DelCommand(QUndoCommand): - def __init__(self, lst, rows): + def __init__(self, bcs, tab, rows): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._rows = rows self._bc = [] for row in rows: - self._bc.append((row, self._lst[row])) + self._bc.append((row, self._bcs.get(self._tab, row))) self._bc.sort() def undo(self): for row, el in self._bc: - self._lst.insert(row, el) + self._bcs.insert(self._tab, row, el) def redo(self): - self._lst.delete_i(self._rows) + self._bcs.delete_i(self._tab, self._rows) class SortCommand(QUndoCommand): - def __init__(self, lst, _reverse): + def __init__(self, bcs, tab, _reverse): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._reverse = _reverse - self._old = self._lst.copy() + self._old = self._bcs.get_tab(self._tab) self._indexes = None def undo(self): - ll = self._lst.copy() - self._lst.sort( + ll = self._bcs.get_tab(self._tab) + self._bcs.sort( + self._tab, key=lambda x: self._indexes[ll.index(x)] ) def redo(self): - self._lst.sort( + self._bcs.sort( + self._tab, reverse=self._reverse, key=lambda x: x.name ) @@ -117,62 +126,65 @@ class SortCommand(QUndoCommand): self._indexes = list( map( lambda p: self._old.index(p), - self._lst + self._bcs.get_tab(self._tab) ) ) self._old = None class MoveCommand(QUndoCommand): - def __init__(self, lst, up, i): + def __init__(self, bcs, tab, up, i): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._up = up == "up" self._i = i def undo(self): if self._up: - self._lst.move_up(self._i) + self._bcs.move_up(self._tab, self._i) else: - self._lst.move_down(self._i) + self._bcs.move_down(self._tab, self._i) def redo(self): if self._up: - self._lst.move_up(self._i) + self._bcs.move_up(self._tab, self._i) else: - self._lst.move_down(self._i) + self._bcs.move_down(self._tab, self._i) class PasteCommand(QUndoCommand): - def __init__(self, lst, row, bc): + def __init__(self, bcs, tab, row, bc): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._row = row self._bc = deepcopy(bc) self._bc.reverse() def undo(self): - self._lst.delete(self._bc) + self._bcs.delete(self._tab, self._bc) def redo(self): for bc in self._bc: - self._lst.insert(self._row, bc) + self._bcs.insert(self._tab, self._row, bc) class DuplicateCommand(QUndoCommand): - def __init__(self, lst, rows, bc): + def __init__(self, bcs, tab, rows, bc): QUndoCommand.__init__(self) - self._lst = lst + self._bcs = bcs + self._tab = tab self._rows = rows self._bc = deepcopy(bc) self._bc.reverse() def undo(self): - self._lst.delete(self._bc) + self._bcs.delete(self._tab, self._bc) def redo(self): for bc in self._bcs: - self._lst.insert(self._rows[0], bc) + self._bcs.insert(self._tab, self._rows[0], bc) diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index 38b9e6b1..c14ef005 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -19,7 +19,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox, QPushButton, QLineEdit, QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, - QComboBox, QVBoxLayout, QHeaderView + QComboBox, QVBoxLayout, QHeaderView, QTabWidget, ) from View.BoundaryCondition.BCUndoCommand import ( @@ -69,32 +69,40 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.paste_sc = QShortcut(QKeySequence.Paste, self) def setup_table(self): - table = self.find(QTableView, "tableView_liquid") - self._table = TableModel( - data = self._study.river, - undo = self._undo_stack - ) - table.setModel(self._table) + self._table = {} - self._delegate_type = ComboBoxDelegate( - data = self._study.river, - mode = "type" - ) - self._delegate_node = ComboBoxDelegate( - data = self._study.river, - mode = "node" - ) + for t in ["liquid", "solid", "suspenssion"]: + table = self.find(QTableView, f"tableView_{t}") + self._table[t] = TableModel( + data = self._study.river, + undo = self._undo_stack, + tab = t, + ) + table.setModel(self._table[t]) - table.setItemDelegateForColumn( - 1, self._delegate_type - ) - table.setItemDelegateForColumn( - 2, self._delegate_node - ) + self._delegate_type = ComboBoxDelegate( + data = self._study.river, + mode = "type", + tab = t, + parent=self + ) + self._delegate_node = ComboBoxDelegate( + data = self._study.river, + mode = "node", + tab = t, + parent=self + ) - table.setSelectionBehavior(QAbstractItemView.SelectRows) - table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - table.setAlternatingRowColors(True) + table.setItemDelegateForColumn( + 1, self._delegate_type + ) + table.setItemDelegateForColumn( + 2, self._delegate_node + ) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) def setup_graph(self): self.graph_widget = GraphWidget( @@ -117,15 +125,22 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.copy_sc.activated.connect(self.copy) self.paste_sc.activated.connect(self.paste) + def current_tab(self): + return self.find(QTabWidget, "tabWidget")\ + .currentWidget()\ + .objectName()\ + .replace("tab_", "") def index_selected_row(self): - table = self.find(QTableView, "tableView_liquid") + tab = self.current_tab() + table = self.find(QTableView, f"tableView_{tab}") return table.selectionModel()\ .selectedRows()[0]\ .row() def index_selected_rows(self): - table = self.find(QTableView, "tableView_liquid") + tab = self.current_tab() + table = self.find(QTableView, f"tableView_{tab}") return list( # Delete duplicate set( @@ -137,30 +152,34 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): ) def add(self): + tab = self.current_tab() rows = self.index_selected_rows() - if len(self._lst) == 0 or len(rows) == 0: - self._table.add(0) + if self._lst.len(tab) == 0 or len(rows) == 0: + self._table[tab].add(0) else: - self._table.add(rows[0]) + self._table[tab].add(rows[0]) def delete(self): + tab = self.current_tab() rows = self.index_selected_rows() if len(rows) == 0: return - self._table.delete(rows) + self._table[tab].delete(rows) def sort(self): - self._table.sort(False) + tab = self.current_tab() + self._table[tab].sort(False) def move_up(self): + tab = self.current_tab() row = self.index_selected_row() - self._table.move_up(row) + self._table[tab].move_up(row) def move_down(self): + tab = self.current_tab() row = self.index_selected_row() - self._table.move_down(row) - + self._table[tab].move_down(row) def copy(self): print("TODO") @@ -169,10 +188,12 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): print("TODO") def undo(self): - self._table.undo() + tab = self.current_tab() + self._table[tab].undo() def redo(self): - self._table.redo() + tab = self.current_tab() + self._table[tab].redo() def edit(self): rows = self.index_selected_rows() diff --git a/src/View/BoundaryCondition/Table.py b/src/View/BoundaryCondition/Table.py index f7bc377a..019f4642 100644 --- a/src/View/BoundaryCondition/Table.py +++ b/src/View/BoundaryCondition/Table.py @@ -30,17 +30,30 @@ from View.BoundaryCondition.translate import * _translate = QCoreApplication.translate class ComboBoxDelegate(QItemDelegate): - def __init__(self, data=None, mode="type", parent=None): + def __init__(self, data=None, mode="type", tab="", parent=None): super(ComboBoxDelegate, self).__init__(parent) self._data = data self._mode = mode + self._tab = tab def createEditor(self, parent, option, index): self.editor = QComboBox(parent) if self._mode == "type": - self.editor.addItems([long_types[k] for k in BC_types.keys()]) + lst = list( + map( + lambda k: long_types[k], + filter( + lambda k: self._tab in BC_types[k].compatibility(), + BC_types.keys() + ) + ) + ) + print(lst) + self.editor.addItems( + lst + ) else: self.editor.addItems( [_translate("BoundaryCondition", "Not associate")] + @@ -72,12 +85,13 @@ class ComboBoxDelegate(QItemDelegate): class TableModel(QAbstractTableModel): - def __init__(self, data=None, undo=None): + def __init__(self, data=None, undo=None, tab=""): super(QAbstractTableModel, self).__init__() self._headers = list(table_headers.keys()) self._data = data self._undo = undo - self._lst = self._data.boundary_condition + self._tab = tab + self._bcs = self._data.boundary_condition def flags(self, index): options = Qt.ItemIsEnabled | Qt.ItemIsSelectable @@ -86,7 +100,7 @@ class TableModel(QAbstractTableModel): return options def rowCount(self, parent): - return len(self._lst) + return self._bcs.len(self._tab) def columnCount(self, parent): return len(self._headers) @@ -99,12 +113,12 @@ class TableModel(QAbstractTableModel): column = index.column() if self._headers[column] == "name": - return self._lst[row].name + return self._bcs.get(self._tab, row).name elif self._headers[column] == "type": - t = self._lst[row].bctype + t = self._bcs.get(self._tab, row).bctype return long_types[t] elif self._headers[column] == "node": - n = self._lst[row].node + n = self._bcs.get(self._tab, row).node if n is None: return _translate("BoundaryCondition", "Not associate") return n.name @@ -127,20 +141,20 @@ class TableModel(QAbstractTableModel): if self._headers[column] == "name": self._undo.push( SetNameCommand( - self._lst, row, value + self._bcs, self._tab,row, value ) ) elif self._headers[column] == "type": key = next(k for k, v in long_types.items() if v == value) self._undo.push( SetTypeCommand( - self._lst, row, BC_types[key] + self._bcs, self._tab,row, BC_types[key] ) ) elif self._headers[column] == "node": self._undo.push( SetNodeCommand( - self._lst, row, self._data.node(value) + self._bcs, self._tab,row, self._data.node(value) ) ) @@ -152,7 +166,7 @@ class TableModel(QAbstractTableModel): self._undo.push( AddCommand( - self._lst, row + self._bcs, self._tab,row ) ) @@ -164,7 +178,7 @@ class TableModel(QAbstractTableModel): self._undo.push( DelCommand( - self._lst, rows + self._bcs, self._tab,rows ) ) @@ -176,7 +190,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SortCommand( - self._lst, False + self._bcs, self._tab,False ) ) @@ -193,7 +207,7 @@ class TableModel(QAbstractTableModel): self._undo_stack.push( MoveCommand( - self._lst, "up", row + self._bcs, self._tab,"up", row ) ) @@ -201,7 +215,7 @@ class TableModel(QAbstractTableModel): self.layoutChanged.emit() def move_down(self, index, parent=QModelIndex()): - if row > len(self._lst): + if row > len(self._bcs): return target = row @@ -210,7 +224,7 @@ class TableModel(QAbstractTableModel): self._undo_stack.push( MoveCommand( - self._lst, "down", row + self._bcs, self._tab,"down", row ) ) diff --git a/src/View/ui/BoundaryConditions.ui b/src/View/ui/BoundaryConditions.ui index 602a1887..3097fd63 100644 --- a/src/View/ui/BoundaryConditions.ui +++ b/src/View/ui/BoundaryConditions.ui @@ -33,7 +33,7 @@ 0 - + Liquid @@ -43,7 +43,7 @@ - + Solid @@ -53,7 +53,7 @@ - + Suspenssion From 0546b304befc8b80dbbfa267b1561560b1132a9e Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 9 May 2023 13:39:06 +0200 Subject: [PATCH 12/19] geometry: Make parent as optional. --- src/Model/Geometry/Reach.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index 0fff437c..16ee09f8 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -16,7 +16,7 @@ from Model.Geometry.ProfileXYZ import ProfileXYZ from Model.Except import FileFormatError, exception_message_box class Reach: - def __init__(self, parent): + def __init__(self, parent=None): self._parent = parent self._profiles: List[Profile] = [] @@ -39,6 +39,9 @@ class Reach: @property def name(self): + if self._parent == None: + return "" + return self._parent.name @property From e9879625107fd1e26efe24ae0831eeed3e95a9d2 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 9 May 2023 13:56:14 +0200 Subject: [PATCH 13/19] BC: Edit: Add X and Y column name for not defined bc type. --- src/Model/BoundaryCondition/BoundaryCondition.py | 2 +- src/Model/BoundaryCondition/BoundaryConditionTypes.py | 9 ++++++++- src/View/BoundaryCondition/BoundaryConditionWindow.py | 7 ++++--- src/View/BoundaryCondition/Edit/translate.py | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index 5bd26189..800bed8e 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -13,7 +13,7 @@ class BoundaryCondition(object): self._node = None self._data = [] self._header = [] - self._types = [int, float] + self._types = [float, float] def __len__(self): return len(self._data) diff --git a/src/Model/BoundaryCondition/BoundaryConditionTypes.py b/src/Model/BoundaryCondition/BoundaryConditionTypes.py index 85113389..a7db88ff 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionTypes.py +++ b/src/Model/BoundaryCondition/BoundaryConditionTypes.py @@ -10,7 +10,11 @@ class NotDefined(BoundaryCondition): super(NotDefined, self).__init__(name=name) self._type = "ND" - self._header = ["", ""] + self._header = ["x", "y"] + + @property + def _default_0(self): + return 0.0 class PonctualContribution(BoundaryCondition): def __init__(self, name:str = ""): @@ -18,6 +22,7 @@ class PonctualContribution(BoundaryCondition): self._type = "PC" self._header = ["time", "debit"] + self._types = [int, float] @classmethod def compatibility(cls): @@ -29,6 +34,7 @@ class TimeOverZ(BoundaryCondition): self._type = "TZ" self._header = ["time", "z"] + self._types = [int, float] @classmethod def compatibility(cls): @@ -40,6 +46,7 @@ class TimeOverDebit(BoundaryCondition): self._type = "TD" self._header = ["time", "debit"] + self._types = [int, float] @classmethod def compatibility(cls): diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index c14ef005..b9f75479 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -51,7 +51,7 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): ) self._study = study - self._lst = self._study.river.boundary_condition + self._bcs = self._study.river.boundary_condition self.setup_sc() self.setup_table() @@ -154,7 +154,7 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): def add(self): tab = self.current_tab() rows = self.index_selected_rows() - if self._lst.len(tab) == 0 or len(rows) == 0: + if self._bcs.len(tab) == 0 or len(rows) == 0: self._table[tab].add(0) else: self._table[tab].add(rows[0]) @@ -196,10 +196,11 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self._table[tab].redo() def edit(self): + tab = self.current_tab() rows = self.index_selected_rows() for row in rows: win = EditBoundaryConditionWindow( - data=self._lst[row], + data=self._bcs.get(tab, row), parent=self ) win.show() diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py index e3fa7513..9ab28f9c 100644 --- a/src/View/BoundaryCondition/Edit/translate.py +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -5,6 +5,8 @@ from PyQt5.QtCore import QCoreApplication _translate = QCoreApplication.translate table_headers = { + "x": _translate("BoundaryCondition", "X"), + "y": _translate("BoundaryCondition", "Y"), "time": _translate("BoundaryCondition", "Time"), "debit": _translate("BoundaryCondition", "Debit"), "z": _translate("BoundaryCondition", "Z (m)") From 7b93f62a644540c73b51260e0bf35022e109a9e5 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 10 May 2023 11:30:21 +0200 Subject: [PATCH 14/19] Study: Add time / date mode to study. --- src/Model/Study.py | 27 ++++++- src/View/ASubWindow.py | 29 ++++++- src/View/Main/NewStudyWindow.py | 45 ++++++++++- src/View/ui/NewStudy.ui | 136 +++++++++++++++++++++----------- src/tools.py | 22 ++++++ 5 files changed, 208 insertions(+), 51 deletions(-) diff --git a/src/Model/Study.py b/src/Model/Study.py index 59a2ec9c..f1a50131 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -13,6 +13,9 @@ class Study(Serializable): # Study general information self.name = "" self.description = "" + # Time system + self._time_system = "time" + self._date = datetime.fromtimestamp(0) self.creation_date = datetime.now() self.last_modification_date = datetime.now() @@ -21,13 +24,35 @@ class Study(Serializable): # Study data self.river = None + @property + def time_system(self): + return self._time_system + + def use_time(self): + self._time_system = "time" + + def use_date(self, date:datetime): + self._time_system = "date" + self._date = date + + @property + def date(self): + return self._date + + @date.setter + def date(self, timestamp): + self._date = timestamp + @classmethod def new(cls): return cls() @classmethod - def new(cls, name, description): + def new(cls, name, description, date = None): me = cls() me.name = name me.description = description + if date is not None: + me.use_date() + me.date = date return me diff --git a/src/View/ASubWindow.py b/src/View/ASubWindow.py index 3e1a138f..a7b40c9f 100644 --- a/src/View/ASubWindow.py +++ b/src/View/ASubWindow.py @@ -4,6 +4,7 @@ import os import csv from io import StringIO +from datetime import datetime from tools import trace @@ -16,9 +17,10 @@ from PyQt5.QtWidgets import ( QTimeEdit, QSpinBox, QTextEdit, QRadioButton, QComboBox, QFileDialog, QMessageBox, QTableView, QAction, + QDateTimeEdit, ) from PyQt5.QtCore import ( - QTime, + QTime, QDateTime, ) from PyQt5.uic import loadUi @@ -350,6 +352,31 @@ class ASubWindowFeatures(object): """ return self.find(QComboBox, name).currentText() + def get_datetime_edit(self, name:str): + """Get datetime of datetime edit + + Args: + name: The datetime edit component name + + Returns: + The datetime + """ + return self.find(QDateTimeEdit, name).dateTime().toPyDateTime() + + def set_datetime_edit(self, name:str, date:datetime): + """Set datetime of a datetime edit + + Args: + name: The datetime edit component name + date: The new datetime + + Returns: + Nothing + """ + qdate = QDateTime.fromString(date.isoformat(), "yyyy-MM-ddThh:mm:ss") + self.find(QDateTimeEdit, name).setDateTime(qdate) + + # Top level interface class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit): diff --git a/src/View/Main/NewStudyWindow.py b/src/View/Main/NewStudyWindow.py index 905815ea..6d313199 100644 --- a/src/View/Main/NewStudyWindow.py +++ b/src/View/Main/NewStudyWindow.py @@ -3,6 +3,11 @@ from Model.Study import Study from View.ASubWindow import ASubWindow +from PyQt5.QtWidgets import ( + QRadioButton, QLabel, QDateTimeEdit, +) + + class NewStudyWindow(ASubWindow): def __init__(self, study=None, title="New Study", parent=None): super(NewStudyWindow, self).__init__(name=title, ui="NewStudy", parent=parent) @@ -13,16 +18,50 @@ class NewStudyWindow(ASubWindow): self.study = study if not self.study is None: - self.set_line_edit_text("lineEdit_name", study.name) - self.set_text_edit_text("textEdit_description", study.description) + self.set_line_edit_text("lineEdit_name", self.study.name) + self.set_text_edit_text("textEdit_description", self.study.description) + self.set_datetime_edit("dateTimeEdit_date", self.study.date) + if self.study.time_system == "date": + self.set_radio_button("radioButton_date", True) + self.find(QLabel, "label_date").setEnabled(True) + self.find(QDateTimeEdit, "dateTimeEdit_date").setEnabled(True) + + self.connection() + + def connection(self): + time = self.find(QRadioButton, "radioButton_time") + date = self.find(QRadioButton, "radioButton_date") + + time.toggled.connect(self.set_time) + date.toggled.connect(self.set_date) + + def set_time(self): + if self.get_radio_button("radioButton_time"): + self.find(QLabel, "label_date").setEnabled(False) + self.find(QDateTimeEdit, "dateTimeEdit_date").setEnabled(False) + + def set_date(self): + if self.get_radio_button("radioButton_date"): + self.find(QLabel, "label_date").setEnabled(True) + self.find(QDateTimeEdit, "dateTimeEdit_date").setEnabled(True) + def accept(self): name = self.get_line_edit_text("lineEdit_name") description = self.get_text_edit_text("textEdit_description") if self.study is None: - self.parent.set_model(Study.new(name, description)) + study = Study.new(name, description) + if self.get_radio_button("radioButton_date"): + date = self.get_datetime_edit("dateTimeEdit_date") + study.use_date(date) + self.parent.set_model(study) else: self.study.name = name self.study.description = description + if self.get_radio_button("radioButton_date"): + date = self.get_datetime_edit("dateTimeEdit_date") + self.study.use_date(date) + else: + self.study.use_time() self.done(True) diff --git a/src/View/ui/NewStudy.ui b/src/View/ui/NewStudy.ui index 1805c10e..f95ff4a1 100644 --- a/src/View/ui/NewStudy.ui +++ b/src/View/ui/NewStudy.ui @@ -13,60 +13,104 @@ Dialog - + - + + + Name + + + + + + + MyNewStudy + + + + + + + Time system + + + + + - - - - - - - Name - - - - - - - Description - - - - - - - MyNewStudy - - - - - - - false - - - false - - - - - - - - - - - Qt::Horizontal + + + Time - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + true + + + + + + Date + + + + + + + Qt::Vertical + + + + + + + false + + + Staring date + + + + + + + false + + + + + + + + + Description + + + + + + + false + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + diff --git a/src/tools.py b/src/tools.py index d2f11917..be41a5b3 100644 --- a/src/tools.py +++ b/src/tools.py @@ -131,3 +131,25 @@ def flatten(lst): return [] return reduce(list.__add__, lst) + +def old_pamhyr_date_to_timestamp(date:str): + v = date.split(":") + if len(v) != 4: + return 0 + + m = [ + (24 * 60 * 60), # Day to sec + (60 * 60), # Hour to sec + 60, # Minute to sec + 1 # Sec + ] + + ts = reduce( + lambda acc, x: acc + x, + map( + lambda v, m: int(v) * int(m), + v, m + ) + ) + + return ts From 4039182689b9d932d1fd6877274866731c7ff392 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 10 May 2023 12:00:35 +0200 Subject: [PATCH 15/19] BC: Edit: Fix paste command for old pamhyr time format. --- src/Model/BoundaryCondition/BoundaryCondition.py | 13 ++++++++++--- .../BoundaryCondition/BoundaryConditionTypes.py | 6 +++--- src/View/BoundaryCondition/Edit/Table.py | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index 800bed8e..61015917 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from tools import trace, timer +from tools import trace, timer, old_pamhyr_date_to_timestamp from Model.Except import NotImplementedMethodeError @@ -22,6 +22,13 @@ class BoundaryCondition(object): def compatibility(cls): return ["liquid", "solid", "suspenssion"] + @classmethod + def time_convert(cls, data): + if type(data) == str and data.count(":") == 3: + return old_pamhyr_date_to_timestamp(data) + + return int(data) + @property def name(self): return self._name @@ -78,9 +85,9 @@ class BoundaryCondition(object): for j in range(len(header)): if self._header[i] == header[j]: if i == 0: - new_0 = self._types[i](data[j]) + new_0 = self._types[i](data[j].replace(",", ".")) else: - new_1 = self._types[i](data[j]) + new_1 = self._types[i](data[j].replace(",", ".")) else: new_0 = self._types[0](data[0]) new_1 = self._types[1](data[1]) diff --git a/src/Model/BoundaryCondition/BoundaryConditionTypes.py b/src/Model/BoundaryCondition/BoundaryConditionTypes.py index a7db88ff..4bc1d538 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionTypes.py +++ b/src/Model/BoundaryCondition/BoundaryConditionTypes.py @@ -22,7 +22,7 @@ class PonctualContribution(BoundaryCondition): self._type = "PC" self._header = ["time", "debit"] - self._types = [int, float] + self._types = [PonctualContribution.time_convert, float] @classmethod def compatibility(cls): @@ -34,7 +34,7 @@ class TimeOverZ(BoundaryCondition): self._type = "TZ" self._header = ["time", "z"] - self._types = [int, float] + self._types = [TimeOverZ.time_convert, float] @classmethod def compatibility(cls): @@ -46,7 +46,7 @@ class TimeOverDebit(BoundaryCondition): self._type = "TD" self._header = ["time", "debit"] - self._types = [int, float] + self._types = [TimeOverDebit.time_convert, float] @classmethod def compatibility(cls): diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index c6fa6722..a068a247 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -121,7 +121,7 @@ class TableModel(QAbstractTableModel): self._undo.push( SortCommand( - self._data, False + self._data, _reverse ) ) From 1775bf2729e33165bdfe44616bb4629957c3dfcf Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 11 May 2023 11:23:47 +0200 Subject: [PATCH 16/19] BC: Edit: Edit time column with custom widget. --- src/View/ASubWindow.py | 27 ++++++- src/View/BoundaryCondition/Edit/Plot.py | 35 +++++++++ src/View/BoundaryCondition/Edit/Table.py | 87 ++++++++++++++++++++++- src/View/BoundaryCondition/Edit/Window.py | 13 +++- src/View/BoundaryCondition/Table.py | 1 - src/View/ui/NewStudy.ui | 3 + src/View/ui/Widgets/extendedTimeEdit.ui | 60 ++++++++++++++++ 7 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 src/View/ui/Widgets/extendedTimeEdit.ui diff --git a/src/View/ASubWindow.py b/src/View/ASubWindow.py index a7b40c9f..61a77987 100644 --- a/src/View/ASubWindow.py +++ b/src/View/ASubWindow.py @@ -17,7 +17,7 @@ from PyQt5.QtWidgets import ( QTimeEdit, QSpinBox, QTextEdit, QRadioButton, QComboBox, QFileDialog, QMessageBox, QTableView, QAction, - QDateTimeEdit, + QDateTimeEdit, QWidget, ) from PyQt5.QtCore import ( QTime, QDateTime, @@ -409,7 +409,6 @@ class ASubMainWindow(QMainWindow, ASubWindowFeatures, WindowToolKit): return self.ui.findChild(qtype, name) - class ASubWindow(QDialog, ASubWindowFeatures, WindowToolKit): def __init__(self, name="", ui="dummy", parent=None): super(ASubWindow, self).__init__(parent=parent) @@ -439,3 +438,27 @@ class ASubWindow(QDialog, ASubWindowFeatures, WindowToolKit): qtype = self._qtype_from_component_name(name) return self.ui.findChild(qtype, name) + +class AWidget(QWidget, ASubWindowFeatures): + def __init__(self, ui="", parent=None): + super(AWidget, self).__init__(parent=parent) + self.ui = loadUi( + os.path.join(os.path.dirname(__file__), "ui", "Widgets", f"{ui}.ui"), + self + ) + self.parent = parent + + def find(self, qtype, name): + """Find an ui component + + Args: + qtype: Type of QT component + name: Name for component + + Returns: + return the component + """ + if qtype is None: + qtype = self._qtype_from_component_name(name) + + return self.ui.findChild(qtype, name) diff --git a/src/View/BoundaryCondition/Edit/Plot.py b/src/View/BoundaryCondition/Edit/Plot.py index 39af012c..efb039e7 100644 --- a/src/View/BoundaryCondition/Edit/Plot.py +++ b/src/View/BoundaryCondition/Edit/Plot.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from datetime import datetime + from tools import timer, trace from View.Plot.APlot import APlot @@ -19,6 +21,35 @@ class Plot(APlot): toolbar=toolbar ) + def custom_ticks(self): + t0 = datetime.fromtimestamp(0) + nb = len(self.data.data) + mod = int(nb / 5) + + fx = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] % mod == 0, + enumerate(self.data.data) + ) + ) + ) + xx = list(map(lambda v: v[0], fx)) + xt = list( + map( + lambda v: str( + datetime.fromtimestamp(v[0]) - t0 + ).split(",")[0]\ + .replace("days", _translate("BoundaryCondition", "days"))\ + .replace("day", _translate("BoundaryCondition", "day")), + fx + ) + ) + + self.canvas.axes.set_xticks(ticks=xx, labels=xt, rotation=45) + + @timer def draw(self): self.canvas.axes.cla() @@ -38,6 +69,8 @@ class Plot(APlot): picker=30, ) + self.custom_ticks() + # Plot label header = self.data.header self.canvas.axes.set_xlabel( @@ -65,6 +98,8 @@ class Plot(APlot): self._line.set_data(x, y) + self.custom_ticks() + self.canvas.axes.relim() self.canvas.axes.autoscale() self.canvas.figure.tight_layout() diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index a068a247..b6b5f36a 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -1,18 +1,21 @@ # -*- coding: utf-8 -*- +from datetime import date, time, datetime, timedelta + from tools import trace, timer -from View.ASubWindow import ASubMainWindow +from View.ASubWindow import ASubMainWindow, AWidget from View.ListedSubWindow import ListedSubWindow from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, QModelIndex, pyqtSlot, - QRect, + QRect, QTime, ) from PyQt5.QtWidgets import ( - QTableView, QAbstractItemView, + QTableView, QAbstractItemView, QSpinBox, + QTimeEdit, QItemDelegate, ) from Model.BoundaryCondition.BoundaryConditionTypes import ( @@ -30,6 +33,79 @@ from View.BoundaryCondition.Edit.translate import * _translate = QCoreApplication.translate +class ExtendedTimeEdit(AWidget): + def __init__(self, parent=None): + super(ExtendedTimeEdit, self).__init__( + ui="extendedTimeEdit", + parent=parent + ) + self.parent = parent + + self.spinBox_days = self.find(QSpinBox, "spinBox_days") + self.timeEdit = self.find(QTimeEdit, "timeEdit") + + def set_time(self, time): + days = 0 + stime = time + + if "," in time: + s = time.strip().split(" ") + days = int(s[0]) + stime = s[-1] + + qtime = QTime.fromString( + stime, + "h:mm:ss" + ) + self.spinBox_days.setValue(days) + self.timeEdit.setTime(qtime) + + def get_time(self): + days = self.spinBox_days.value() + time = self.timeEdit.time().toPyTime() + secs = ( + (time.hour * 3600) + + (time.minute * 60) + + time.second + ) + + return timedelta(days=days, seconds=secs) + + +class ExTimeDelegate(QItemDelegate): + def __init__(self, data=None, mode="time", parent=None): + super(ExTimeDelegate, self).__init__(parent) + + self._data = data + self._mode = mode + + def createEditor(self, parent, option, index): + self.editor = ExtendedTimeEdit(parent=parent) + value = index.data(Qt.DisplayRole) + self.editor.set_time(value) + 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): + time = editor.get_time() + model.setData(index, int(time.total_seconds())) + editor.close() + editor.deleteLater() + + def updateEditorGeometry(self, editor, option, index): + r = QRect(option.rect) + if self.editor.windowFlags() & Qt.Popup and 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(QAbstractTableModel): def __init__(self, data=None, undo=None): super(QAbstractTableModel, self).__init__() @@ -65,6 +141,11 @@ class TableModel(QAbstractTableModel): 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": + t0 = datetime.fromtimestamp(0) + t = datetime.fromtimestamp(v) + value = str(t - t0) + #value = v else: # TODO: Time format value = f"{v}" diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 5f0b38e4..8ba80740 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import ( from View.Plot.MplCanvas import MplCanvas from View.BoundaryCondition.translate import long_types -from View.BoundaryCondition.Edit.Table import TableModel +from View.BoundaryCondition.Edit.Table import TableModel, ExTimeDelegate from View.BoundaryCondition.Edit.Plot import Plot _translate = QCoreApplication.translate @@ -68,6 +68,17 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): data = self._data, undo = self._undo_stack ) + + self._delegate_time = ExTimeDelegate( + data = self._data, + mode = "type", + parent=self + ) + + table.setItemDelegateForColumn( + 0, self._delegate_time + ) + table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) diff --git a/src/View/BoundaryCondition/Table.py b/src/View/BoundaryCondition/Table.py index 019f4642..ead3ed7a 100644 --- a/src/View/BoundaryCondition/Table.py +++ b/src/View/BoundaryCondition/Table.py @@ -50,7 +50,6 @@ class ComboBoxDelegate(QItemDelegate): ) ) ) - print(lst) self.editor.addItems( lst ) diff --git a/src/View/ui/NewStudy.ui b/src/View/ui/NewStudy.ui index f95ff4a1..95100b5c 100644 --- a/src/View/ui/NewStudy.ui +++ b/src/View/ui/NewStudy.ui @@ -78,6 +78,9 @@ false + + dd/MM/yyyy HH:mm:ss + diff --git a/src/View/ui/Widgets/extendedTimeEdit.ui b/src/View/ui/Widgets/extendedTimeEdit.ui new file mode 100644 index 00000000..493a96a1 --- /dev/null +++ b/src/View/ui/Widgets/extendedTimeEdit.ui @@ -0,0 +1,60 @@ + + + Form + + + + 0 + 0 + 178 + 44 + + + + + 0 + 0 + + + + Form + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + days + + + + + + + + 0 + 0 + + + + false + + + HH:mm:ss + + + + + + + + From 31ca9bf70cd7dbfbd5f660d1bd2de04ba7981510 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 11 May 2023 14:21:30 +0200 Subject: [PATCH 17/19] BC: Edit: Minor change. --- src/View/BoundaryCondition/Edit/Table.py | 2 ++ src/View/BoundaryCondition/Edit/Window.py | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index b6b5f36a..f21539c4 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -48,6 +48,8 @@ class ExtendedTimeEdit(AWidget): days = 0 stime = time + # if ',' in time, time format is 'DD days, HH:MM:SS', + # otherelse is 'HH:MM:SS' if "," in time: s = time.strip().split(" ") days = int(s[0]) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 8ba80740..9637f7ab 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -69,15 +69,16 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): undo = self._undo_stack ) - self._delegate_time = ExTimeDelegate( - data = self._data, - mode = "type", - parent=self - ) + if self._data.header[0] == "time": + self._delegate_time = ExTimeDelegate( + data = self._data, + mode = "type", + parent=self + ) - table.setItemDelegateForColumn( - 0, self._delegate_time - ) + table.setItemDelegateForColumn( + 0, self._delegate_time + ) table.setModel(self._table) table.setSelectionBehavior(QAbstractItemView.SelectRows) From 7a7e714ea40972cc17514223c88fc66a02ab7b7a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 11 May 2023 15:31:15 +0200 Subject: [PATCH 18/19] BC: Edit: Add table display mode 'time' and 'date'. --- .../BoundaryCondition/BoundaryConditionWindow.py | 1 + src/View/BoundaryCondition/Edit/Table.py | 14 ++++++++------ src/View/BoundaryCondition/Edit/Window.py | 11 +++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/View/BoundaryCondition/BoundaryConditionWindow.py b/src/View/BoundaryCondition/BoundaryConditionWindow.py index b9f75479..9a22cf81 100644 --- a/src/View/BoundaryCondition/BoundaryConditionWindow.py +++ b/src/View/BoundaryCondition/BoundaryConditionWindow.py @@ -201,6 +201,7 @@ class BoundaryConditionWindow(ASubMainWindow, ListedSubWindow): for row in rows: win = EditBoundaryConditionWindow( data=self._bcs.get(tab, row), + study=self._study, parent=self ) win.show() diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index f21539c4..d41a3551 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -109,10 +109,11 @@ class ExTimeDelegate(QItemDelegate): class TableModel(QAbstractTableModel): - def __init__(self, data=None, undo=None): + def __init__(self, data=None, mode="time", undo=None): super(QAbstractTableModel, self).__init__() self._headers = data.header self._data = data + self._mode = mode self._undo = undo def flags(self, index): @@ -144,12 +145,13 @@ class TableModel(QAbstractTableModel): if self._data.get_type_column(column) == float: value = f"{v:.4f}" elif self._data.header[column] == "time": - t0 = datetime.fromtimestamp(0) - t = datetime.fromtimestamp(v) - value = str(t - t0) - #value = v + if self._mode == "time": + t0 = datetime.fromtimestamp(0) + t = datetime.fromtimestamp(v) + value = str(t - t0) + else: + value = str(datetime.fromtimestamp(v)) else: - # TODO: Time format value = f"{v}" return value diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 9637f7ab..24ec9fca 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -27,12 +27,14 @@ from View.BoundaryCondition.Edit.Plot import Plot _translate = QCoreApplication.translate class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): - def __init__(self, title="Edit BoundaryConditions", data=None, parent=None): + def __init__(self, title="Edit BoundaryConditions", + data=None, study=None, parent=None): super(EditBoundaryConditionWindow, self).__init__( name=title, ui="EditBoundaryConditions", parent=parent ) self._data = data + self._study = study self._title = title self.setup_window() @@ -66,14 +68,15 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): table = self.find(QTableView, "tableView") self._table = TableModel( data = self._data, - undo = self._undo_stack + undo = self._undo_stack, + mode = self._study.time_system ) if self._data.header[0] == "time": self._delegate_time = ExTimeDelegate( data = self._data, - mode = "type", - parent=self + mode = self._study.time_system, + parent = self ) table.setItemDelegateForColumn( From 67af95cb40f63b835c321cdecd0d8391ec705268 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Thu, 11 May 2023 16:34:29 +0200 Subject: [PATCH 19/19] BC: Edit: Add plot display mode 'time' and 'date'. --- src/View/BoundaryCondition/Edit/Plot.py | 31 +++++++++++++++-------- src/View/BoundaryCondition/Edit/Window.py | 1 + 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Plot.py b/src/View/BoundaryCondition/Edit/Plot.py index efb039e7..66878b7c 100644 --- a/src/View/BoundaryCondition/Edit/Plot.py +++ b/src/View/BoundaryCondition/Edit/Plot.py @@ -14,13 +14,16 @@ from View.BoundaryCondition.Edit.translate import * _translate = QCoreApplication.translate class Plot(APlot): - def __init__(self, canvas=None, data=None, toolbar=None): + def __init__(self, canvas=None, data=None, + mode = "time", toolbar=None): super(Plot, self).__init__( canvas=canvas, data=data, toolbar=toolbar ) + self._mode = mode + def custom_ticks(self): t0 = datetime.fromtimestamp(0) nb = len(self.data.data) @@ -36,16 +39,24 @@ class Plot(APlot): ) ) xx = list(map(lambda v: v[0], fx)) - xt = list( - map( - lambda v: str( - datetime.fromtimestamp(v[0]) - t0 - ).split(",")[0]\ - .replace("days", _translate("BoundaryCondition", "days"))\ - .replace("day", _translate("BoundaryCondition", "day")), - fx + if self._mode == "time": + xt = list( + map( + lambda v: str( + datetime.fromtimestamp(v[0]) - t0 + ).split(",")[0]\ + .replace("days", _translate("BoundaryCondition", "days"))\ + .replace("day", _translate("BoundaryCondition", "day")), + fx + ) + ) + else: + xt = list( + map( + lambda v: str(datetime.fromtimestamp(v[0]).date()), + fx + ) ) - ) self.canvas.axes.set_xticks(ticks=xx, labels=xt, rotation=45) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 24ec9fca..8cd8fbad 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -96,6 +96,7 @@ class EditBoundaryConditionWindow(ASubMainWindow, ListedSubWindow): self.plot = Plot( canvas = self.canvas, data = self._data, + mode = self._study.time_system, ) self.plot.draw()