diff --git a/src/Model/LateralContribution/LateralContribution.py b/src/Model/LateralContribution/LateralContribution.py new file mode 100644 index 0000000000000000000000000000000000000000..81e6c1f74eb4872ee7d7ecbec2a59be79e2443a2 --- /dev/null +++ b/src/Model/LateralContribution/LateralContribution.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer, old_pamhyr_date_to_timestamp + +from Model.Except import NotImplementedMethodeError + +class LateralContribution(object): + def __init__(self, name:str = ""): + super(LateralContribution, self).__init__() + + self._name = name + self._type = "" + self._edge = None + self._data = [] + self._header = [] + self._types = [float, float] + + def __len__(self): + return len(self._data) + + @classmethod + 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 + + @name.setter + def name(self, name): + self._name = name + + @property + def bctype(self): + return self._type + + @property + def edge(self): + return self._edge + + @edge.setter + def edge(self, edge): + self._edge = edge + + def has_edge(self): + return self._edge is not None + + @property + def header(self): + return self._header.copy() + + @property + 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) + + 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].replace(",", ".")) + else: + new_1 = self._types[i](data[j].replace(",", ".")) + else: + new_0 = self._types[0](data[0].replace(",", ".")) + new_1 = self._types[1](data[1].replace(",", ".")) + + return (new_0, new_1) + + def add(self, index:int): + 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_i(self, indexes): + self._data = list( + map( + lambda e: e[1], + filter( + lambda e: e[0] not in indexes, + enumerate(self.data) + ) + ) + ) + + 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) + self._data[index] = tuple(v) + + def set_i_0(self, index:int, value): + self._set_i_c_v(index, 0, value) + + def set_i_1(self, index:int, value): + self._set_i_c_v(index, 1, value) + + @timer + def convert(self, cls): + new = cls(name = self.name) + for i, _ in self.data: + new.add(i) + + for i in [0,1]: + for j in [0,1]: + if self._header[i] == new.header[j]: + for ind, v in self.data: + 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/LateralContribution/LateralContributionList.py b/src/Model/LateralContribution/LateralContributionList.py new file mode 100644 index 0000000000000000000000000000000000000000..b0fbf7ef12d20e60dd619db83a28a1a5dee0f2cc --- /dev/null +++ b/src/Model/LateralContribution/LateralContributionList.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +from copy import copy +from tools import trace, timer + +from Model.Except import NotImplementedMethodeError + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, + PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) + +class LateralContributionList(object): + def __init__(self): + super(LateralContributionList, self).__init__() + + 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._tabs[lst].insert(index, n) + return n + + def insert(self, lst, index, new): + self._tabs[lst].insert(index, new) + + 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._tabs[lst]) + ) + ) + ) + self.delete(lst, bcs) + + 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 + + l = self._tabs[lst] + l[index], l[next] = l[next], l[index] + + def move_down(self, lst, index): + if index >= 0: + prev = index + 1 + + l = self._tabs[lst] + l[index], l[prev] = l[prev], l[index] + + def __copy__(self): + new = LateralContributionList() + + for l in self._tabs: + new.tabs[l] = self._tabs[l].copy() + + return new + + def __deepcopy__(self): + new = LateralContributionList() + + for l in self._tabs: + new.tabs[l] = self._tabs[l].deepcopy() + + return new + + def copy(self): + return copy(self) diff --git a/src/Model/LateralContribution/LateralContributionTypes.py b/src/Model/LateralContribution/LateralContributionTypes.py new file mode 100644 index 0000000000000000000000000000000000000000..efecc8ada1bccdb883e3403cd621c542f88ee5b6 --- /dev/null +++ b/src/Model/LateralContribution/LateralContributionTypes.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +from Model.Except import NotImplementedMethodeError + +from Model.LateralContribution.LateralContribution import LateralContribution + + +class NotDefined(LateralContribution): + def __init__(self, name:str = ""): + super(NotDefined, self).__init__(name=name) + + self._type = "ND" + self._header = ["x", "y"] + + @property + def _default_0(self): + return 0.0 + +class PonctualContribution(LateralContribution): + def __init__(self, name:str = ""): + super(PonctualContribution, self).__init__(name=name) + + self._type = "PC" + self._header = ["time", "debit"] + self._types = [PonctualContribution.time_convert, float] + + @classmethod + def compatibility(cls): + return ["liquid"] + +class TimeOverZ(LateralContribution): + def __init__(self, name:str = ""): + super(TimeOverZ, self).__init__(name=name) + + self._type = "TZ" + self._header = ["time", "z"] + self._types = [TimeOverZ.time_convert, float] + + @classmethod + def compatibility(cls): + return ["liquid"] + +class TimeOverDebit(LateralContribution): + def __init__(self, name:str = ""): + super(TimeOverDebit, self).__init__(name=name) + + self._type = "TD" + self._header = ["time", "debit"] + self._types = [TimeOverDebit.time_convert, float] + + @classmethod + def compatibility(cls): + return ["liquid"] + +class ZOverDebit(LateralContribution): + def __init__(self, name:str = ""): + super(ZOverDebit, self).__init__(name=name) + + self._type = "ZD" + 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/LateralContribution/Edit/Plot.py b/src/View/LateralContribution/Edit/Plot.py new file mode 100644 index 0000000000000000000000000000000000000000..05d193b2ced96c422a9445c5ac52a48366fd519b --- /dev/null +++ b/src/View/LateralContribution/Edit/Plot.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime + +from tools import timer, trace +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +from View.LateralContribution.Edit.translate import * + +_translate = QCoreApplication.translate + +class Plot(APlot): + 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) + 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)) + if self._mode == "time": + xt = list( + map( + lambda v: str( + datetime.fromtimestamp(v[0]) - t0 + ).split(",")[0]\ + .replace("days", _translate("LateralContribution", "days"))\ + .replace("day", _translate("LateralContribution", "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) + + + @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, + ) + + self.custom_ticks() + + # 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.axes.autoscale_view(True, True, True) + 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 + + x = list(map(lambda v: v[0], self.data.data)) + y = list(map(lambda v: v[1], self.data.data)) + + self._line.set_data(x, y) + + self.custom_ticks() + + self.canvas.axes.relim() + self.canvas.axes.autoscale() + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() diff --git a/src/View/LateralContribution/Edit/Table.py b/src/View/LateralContribution/Edit/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..8016d050ab0cbbcea391f46b8e18d7db7f47ade7 --- /dev/null +++ b/src/View/LateralContribution/Edit/Table.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- + +from datetime import date, time, datetime, timedelta + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow, AWidget +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, + QCoreApplication, QModelIndex, pyqtSlot, + QRect, QTime, QDateTime, +) + +from PyQt5.QtWidgets import ( + QTableView, QAbstractItemView, QSpinBox, + QTimeEdit, QDateTimeEdit, QItemDelegate, +) + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) + +from View.LateralContribution.Edit.UndoCommand import ( + SetDataCommand, AddCommand, DelCommand, + SortCommand, MoveCommand, PasteCommand, + DuplicateCommand, +) +from View.LateralContribution.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, time format is 'DD days, HH:MM:SS', + # otherelse is 'HH:MM:SS' + 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 ExtendedDateTimeEdit(AWidget): + def __init__(self, parent=None): + super(ExtendedDateTimeEdit, self).__init__( + ui="extendedDateTimeEdit", + parent=parent + ) + self.parent = parent + + self.dateTimeEdit = self.find(QDateTimeEdit, "dateTimeEdit") + + def set_time(self, time): + qdatetime = QDateTime.fromString( + time, + "yyyy-MM-dd hh:mm:ss" + ) + self.dateTimeEdit.setDateTime(qdatetime) + + def get_time(self): + time = self.dateTimeEdit.dateTime().toPyDateTime() + return time + +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): + if self._mode == "time": + self.editor = ExtendedTimeEdit(parent=parent) + else: + self.editor = ExtendedDateTimeEdit(parent=parent) + value = index.data(Qt.DisplayRole) + self.editor.set_time(value) + print(value) + return self.editor + + def setModelData(self, editor, model, index): + time = editor.get_time() + if self._mode == "time": + model.setData(index, int(time.total_seconds())) + else: + print(time.timestamp()) + model.setData(index, int(time.timestamp())) + 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, 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): + 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.TextAlignmentRole: + return Qt.AlignHCenter | Qt.AlignVCenter + + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + row = index.row() + column = index.column() + + value = QVariant() + + if 0 <= column < 2: + v = self._data.get_i(row)[column] + if self._data.get_type_column(column) == float: + value = f"{v:.4f}" + elif self._data.header[column] == "time": + if self._mode == "time": + t0 = datetime.fromtimestamp(0) + t = datetime.fromtimestamp(v) + value = str(t - t0) + else: + value = str(datetime.fromtimestamp(v)) + else: + 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, _reverse + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() + + def move_up(self, row, parent=QModelIndex()): + if row <= 0: + return + + target = row + 2 + + self.beginMoveRows(parent, row - 1, row - 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._data, "up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, index, parent=QModelIndex()): + if row > len(self._data): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._data, "down", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def paste(self, row, header, data): + if len(data) == 0: + return + + self.layoutAboutToBeChanged.emit() + + self._undo.push( + PasteCommand( + self._data, row, + list( + map( + lambda d: self._data.new_from_data(header, d), + data + ) + ) + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() + + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/LateralContribution/Edit/UndoCommand.py b/src/View/LateralContribution/Edit/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..7039fa3018be80a7ea449254133edde28ccd096a --- /dev/null +++ b/src/View/LateralContribution/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.LateralContribution.LateralContribution import LateralContribution + +class SetDataCommand(QUndoCommand): + def __init__(self, data, index, column, new_value): + QUndoCommand.__init__(self) + + self._data = data + self._index = index + self._column = column + self._old = self._data.get_i(self._index)[self._column] + 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[0] + ) + if self._indexes is None: + self._indexes = list( + map( + lambda p: self._old.index(p), + self._data.data + ) + ) + self._old = None + + +class MoveCommand(QUndoCommand): + def __init__(self, data, up, i): + QUndoCommand.__init__(self) + + self._data = data + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._data.move_up(self._i) + else: + self._data.move_down(self._i) + + def redo(self): + if self._up: + self._data.move_up(self._i) + else: + self._data.move_down(self._i) + + +class PasteCommand(QUndoCommand): + def __init__(self, data, row, bcs): + QUndoCommand.__init__(self) + + self._data = data + self._row = row + self._bcs = bcs + self._bcs.reverse() + + def undo(self): + self._data.delete(self._bcs) + + def redo(self): + for bc in self._bcs: + 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/LateralContribution/Edit/Window.py b/src/View/LateralContribution/Edit/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..f8754cf17284c093aee57bb71b63b01d2303008c --- /dev/null +++ b/src/View/LateralContribution/Edit/Window.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- + +from tools import timer, trace + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, QCoreApplication, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QPushButton, QLineEdit, + QFileDialog, QTableView, QAbstractItemView, + QUndoStack, QShortcut, QAction, QItemDelegate, + QHeaderView, +) + +from View.Plot.MplCanvas import MplCanvas + +from View.LateralContribution.translate import long_types +from View.LateralContribution.Edit.Table import TableModel, ExTimeDelegate +from View.LateralContribution.Edit.Plot import Plot + +_translate = QCoreApplication.translate + +class EditLateralContributionWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Edit boundary condition", + data=None, study=None, parent=None): + self._data = data + self._study = study + self._title = title + + self.compute_title() + + super(EditLateralContributionWindow, self).__init__( + name=self._title, ui="EditLateralContributions", parent=parent + ) + + self.ui.setWindowTitle(self._title) + + self.setup_sc() + self.setup_table() + self.setup_plot() + self.setup_connections() + + def compute_title(self): + if self._data is not None: + node_name = (self._data.node.name if self._data.node is not None + else _translate("LateralContribution", "Not associate")) + self._title = ( + _translate("Edit boundary condition", self._title) + + f" - {self._study.name} " + + f" - {self._data.name} " + + f"({long_types[self._data.bctype]} - {node_name})" + ) + + 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, + mode = self._study.time_system + ) + + if self._data.header[0] == "time": + self._delegate_time = ExTimeDelegate( + data = self._data, + mode = self._study.time_system, + parent = self + ) + + table.setItemDelegateForColumn( + 0, self._delegate_time + ) + + table.setModel(self._table) + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_plot(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.verticalLayout.addWidget(self.canvas) + + self.plot = Plot( + canvas = self.canvas, + data = self._data, + mode = self._study.time_system, + ) + self.plot.draw() + + + def setup_connections(self): + self.find(QAction, "action_add").triggered.connect(self.add) + self.find(QAction, "action_del").triggered.connect(self.delete) + self.find(QAction, "action_sort").triggered.connect(self.sort) + + self.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) + + self._table.dataChanged.connect(self.update) + + def update(self): + self.plot.update() + + def index_selected_row(self): + table = self.find(QTableView, "tableView") + return table.selectionModel()\ + .selectedRows()[0]\ + .row() + + def index_selected_rows(self): + table = self.find(QTableView, "tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + + def add(self): + rows = self.index_selected_rows() + if len(self._data) == 0 or len(rows) == 0: + self._table.add(0) + else: + self._table.add(rows[0]) + + self.plot.update() + + def delete(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table.delete(rows) + self.plot.update() + + def sort(self): + self._table.sort(False) + self.plot.update() + + def move_up(self): + row = self.index_selected_row() + self._table.move_up(row) + self.plot.update() + + def move_down(self): + row = self.index_selected_row() + self._table.move_down(row) + self.plot.update() + + + def copy(self): + rows = self.index_selected_rows() + + table = [] + table.append(self._data.header) + + data = self._data.data + for row in rows: + table.append(list(data[row])) + + self.copyTableIntoClipboard(table) + + def paste(self): + header, data = self.parseClipboardTable() + + if len(data) == 0: + return + + row = 0 + rows = self.index_selected_rows() + if len(rows) != 0: + row = rows[0] + + self._table.paste(row, header, data) + self.plot.update() + + def undo(self): + self._table.undo() + self.plot.update() + + def redo(self): + self._table.redo() + self.plot.update() diff --git a/src/View/LateralContribution/Edit/translate.py b/src/View/LateralContribution/Edit/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..0c46ad049d0db8c07f617174b24928b89acd941f --- /dev/null +++ b/src/View/LateralContribution/Edit/translate.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "x": _translate("LateralContribution", "X"), + "y": _translate("LateralContribution", "Y"), + "time": _translate("LateralContribution", "Time"), + "debit": _translate("LateralContribution", "Debit"), + "z": _translate("LateralContribution", "Z (m)") +} diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..1ffdb431ef9f8206f6e5605eeafcf5c4d21fcbd8 --- /dev/null +++ b/src/View/LateralContribution/Table.py @@ -0,0 +1,239 @@ +# -*- 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.LateralContribution.UndoCommand import ( + SetNameCommand, SetNodeCommand, SetTypeCommand, + AddCommand, DelCommand, SortCommand, + MoveCommand, PasteCommand, DuplicateCommand, +) + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) +from View.LateralContribution.translate import * + +_translate = QCoreApplication.translate + +class ComboBoxDelegate(QItemDelegate): + 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": + lst = list( + map( + lambda k: long_types[k], + filter( + lambda k: self._tab in BC_types[k].compatibility(), + BC_types.keys() + ) + ) + ) + self.editor.addItems( + lst + ) + else: + self.editor.addItems( + [_translate("LateralContribution", "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, tab=""): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._data = data + self._undo = undo + self._tab = tab + self._lcs = self._data.boundary_condition + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return self._lcs.len(self._tab) + + 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._lcs.get(self._tab, row).name + elif self._headers[column] == "type": + t = self._lcs.get(self._tab, row).bctype + return long_types[t] + elif self._headers[column] == "node": + n = self._lcs.get(self._tab, row).node + if n is None: + return _translate("LateralContribution", "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._lcs, 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._lcs, self._tab,row, BC_types[key] + ) + ) + elif self._headers[column] == "node": + self._undo.push( + SetNodeCommand( + self._lcs, self._tab,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._lcs, self._tab,row + ) + ) + + self.endInsertRows() + self.layoutChanged.emit() + + def delete(self, rows, parent=QModelIndex()): + self.beginRemoveRows(parent, rows[0], rows[-1]) + + self._undo.push( + DelCommand( + self._lcs, self._tab,rows + ) + ) + + self.endRemoveRows() + self.layoutChanged.emit() + + def sort(self, _reverse, parent=QModelIndex()): + self.layoutAboutToBeChanged.emit() + + self._undo.push( + SortCommand( + self._lcs, self._tab,False + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() + + def move_up(self, row, parent=QModelIndex()): + if row <= 0: + return + + target = row + 2 + + self.beginMoveRows(parent, row - 1, row - 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._lcs, self._tab,"up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, index, parent=QModelIndex()): + if row > len(self._lcs): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._lcs, self._tab,"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/LateralContribution/UndoCommand.py b/src/View/LateralContribution/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..19c8006830100e2cc9044d2457af75daaa5393ed --- /dev/null +++ b/src/View/LateralContribution/UndoCommand.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.LateralContribution.LateralContribution import LateralContribution +from Model.LateralContribution.LateralContributionList import LateralContributionList + +class SetNameCommand(QUndoCommand): + def __init__(self, lcs, tab, index, new_value): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._old = self._lcs.get(self._tab, self._index).name + self._new = new_value + + def undo(self): + self._lcs.get(self._tab, self._index).name = self._old + + def redo(self): + self._lcs.get(self._tab, self._index).name = self._new + +class SetNodeCommand(QUndoCommand): + def __init__(self, lcs, tab, index, node): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._old = self._lcs.get(self._tab, self._index).node + self._new = node + + def undo(self): + self._lcs.get(self._tab, self._index).node = self._old + + def redo(self): + self._lcs.get(self._tab, self._index).node = self._new + +class SetTypeCommand(QUndoCommand): + def __init__(self, lcs, tab, index, _type): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._type = _type + self._old = self._lcs.get(self._tab, self._index) + self._new = self._lcs.get(self._tab, self._index)\ + .convert(self._type) + + def undo(self): + self._lcs.set(self._tab, self._index, self._old) + + def redo(self): + self._lcs.set(self._tab, self._index, self._new) + +class AddCommand(QUndoCommand): + def __init__(self, lcs, tab, index): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._index = index + self._new = None + + def undo(self): + self._lcs.delete_i(self._tab, [self._index]) + + def redo(self): + if self._new is None: + self._new = self._lcs.new(self._tab, self._index) + else: + self._lcs.insert(self._tab, self._index, self._new) + +class DelCommand(QUndoCommand): + def __init__(self, lcs, tab, rows): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._rows = rows + + self._bc = [] + for row in rows: + self._bc.append((row, self._lcs.get(self._tab, row))) + self._bc.sort() + + def undo(self): + for row, el in self._bc: + self._lcs.insert(self._tab, row, el) + + def redo(self): + self._lcs.delete_i(self._tab, self._rows) + +class SortCommand(QUndoCommand): + def __init__(self, lcs, tab, _reverse): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._reverse = _reverse + + self._old = self._lcs.get_tab(self._tab) + self._indexes = None + + def undo(self): + ll = self._lcs.get_tab(self._tab) + self._lcs.sort( + self._tab, + key=lambda x: self._indexes[ll.index(x)] + ) + + def redo(self): + self._lcs.sort( + self._tab, + reverse=self._reverse, + key=lambda x: x.name + ) + if self._indexes is None: + self._indexes = list( + map( + lambda p: self._old.index(p), + self._lcs.get_tab(self._tab) + ) + ) + self._old = None + + +class MoveCommand(QUndoCommand): + def __init__(self, lcs, tab, up, i): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._lcs.move_up(self._tab, self._i) + else: + self._lcs.move_down(self._tab, self._i) + + def redo(self): + if self._up: + self._lcs.move_up(self._tab, self._i) + else: + self._lcs.move_down(self._tab, self._i) + + +class PasteCommand(QUndoCommand): + def __init__(self, lcs, tab, row, bc): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._row = row + self._bc = deepcopy(bc) + self._bc.reverse() + + def undo(self): + self._lcs.delete(self._tab, self._bc) + + def redo(self): + for bc in self._bc: + self._lcs.insert(self._tab, self._row, bc) + + +class DuplicateCommand(QUndoCommand): + def __init__(self, lcs, tab, rows, bc): + QUndoCommand.__init__(self) + + self._lcs = lcs + self._tab = tab + self._rows = rows + self._bc = deepcopy(bc) + self._bc.reverse() + + def undo(self): + self._lcs.delete(self._tab, self._bc) + + def redo(self): + for bc in self._lcs: + self._lcs.insert(self._tab, self._rows[0], bc) diff --git a/src/View/LateralContribution/Window.py b/src/View/LateralContribution/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..90bb87ea17acda8f8a29e32986409bec4250a9b8 --- /dev/null +++ b/src/View/LateralContribution/Window.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, + QCoreApplication, QModelIndex, pyqtSlot, + QRect, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QPushButton, QLineEdit, + QFileDialog, QTableView, QAbstractItemView, + QUndoStack, QShortcut, QAction, QItemDelegate, + QComboBox, QVBoxLayout, QHeaderView, QTabWidget, +) + +from View.LateralContribution.UndoCommand import ( + SetNameCommand, SetNodeCommand, SetTypeCommand, + AddCommand, DelCommand, SortCommand, + MoveCommand, PasteCommand, DuplicateCommand, +) + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) + +from View.LateralContribution.Table import ( + TableModel, ComboBoxDelegate +) + +from View.Network.GraphWidget import GraphWidget +from View.LateralContribution.translate import * +from View.LateralContribution.Edit.Window import EditLateralContributionWindow + +_translate = QCoreApplication.translate + + +class LateralContributionWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Lateral contribution", study=None, parent=None): + title = title + " - " + study.name + + super(LateralContributionWindow, self).__init__( + name=title, ui="LateralContributions", parent=parent + ) + + self._study = study + self._lcs = self._study.river.lateral_contribution + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(title) + + def setup_sc(self): + self._undo_stack = QUndoStack() + + self.undo_sc = QShortcut(QKeySequence.Undo, self) + self.redo_sc = QShortcut(QKeySequence.Redo, self) + self.copy_sc = QShortcut(QKeySequence.Copy, self) + self.paste_sc = QShortcut(QKeySequence.Paste, self) + + def setup_table(self): + self._table = {} + + 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]) + + 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.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( + 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) + self.find(QAction, "action_del").triggered.connect(self.delete) + self.find(QAction, "action_edit").triggered.connect(self.edit) + self.find(QAction, "action_sort").triggered.connect(self.sort) + + 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 current_tab(self): + return self.find(QTabWidget, "tabWidget")\ + .currentWidget()\ + .objectName()\ + .replace("tab_", "") + + def index_selected_row(self): + tab = self.current_tab() + table = self.find(QTableView, f"tableView_{tab}") + return table.selectionModel()\ + .selectedRows()[0]\ + .row() + + def index_selected_rows(self): + tab = self.current_tab() + table = self.find(QTableView, f"tableView_{tab}") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def add(self): + tab = self.current_tab() + rows = self.index_selected_rows() + if self._lcs.len(tab) == 0 or len(rows) == 0: + self._table[tab].add(0) + else: + 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[tab].delete(rows) + + def sort(self): + tab = self.current_tab() + self._table[tab].sort(False) + + def move_up(self): + tab = self.current_tab() + row = self.index_selected_row() + self._table[tab].move_up(row) + + def move_down(self): + tab = self.current_tab() + row = self.index_selected_row() + self._table[tab].move_down(row) + + def copy(self): + print("TODO") + + def paste(self): + print("TODO") + + def undo(self): + tab = self.current_tab() + self._table[tab].undo() + + def redo(self): + tab = self.current_tab() + self._table[tab].redo() + + def edit(self): + tab = self.current_tab() + rows = self.index_selected_rows() + for row in rows: + win = EditLateralContributionWindow( + data=self._lcs.get(tab, row), + study=self._study, + parent=self + ) + win.show() diff --git a/src/View/LateralContribution/translate.py b/src/View/LateralContribution/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..a4a0175a5f1d513f9ede8c32b41bb3af0107f7a7 --- /dev/null +++ b/src/View/LateralContribution/translate.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, PonctualContribution, + TimeOverZ, TimeOverDebit, ZOverDebit +) + +_translate = QCoreApplication.translate + +long_types = { + "ND": _translate("LateralContribution", "Not defined"), + "PC": _translate("LateralContribution", "Ponctual contribution"), + "TZ": _translate("LateralContribution", "Time over Z"), + "TD": _translate("LateralContribution", "Time over Debit"), + "ZD": _translate("LateralContribution", "Z over Debit"), +} + +table_headers = { + "name": _translate("LateralContribution", "Name"), + "type": _translate("LateralContribution", "Type"), + "node": _translate("LateralContribution", "Node") +} + +BC_types = { + "ND": NotDefined, + "PC": PonctualContribution, + "TZ": TimeOverZ, + "TD": TimeOverDebit, + "ZD": ZOverDebit +} diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index a37a6a9eda1a13c4f40c4337397ab470cf7d4b42..1be5b60dff678c4768a8d756507887312ce7d62c 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -23,6 +23,7 @@ from View.Main.AboutWindow import AboutWindow from View.Network.NetworkWindow import NetworkWindow from View.Geometry.GeometryWindow import GeometryWindow from View.BoundaryCondition.BoundaryConditionWindow import BoundaryConditionWindow +from View.LateralContribution.Window import LateralContributionWindow from Model.Study import Study @@ -116,7 +117,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_toolBar_mesh": lambda: self.open_dummy("Mesh"), "action_toolBar_run_meshing_tool": lambda: self.open_dummy("Lancement mailleur externe"), "action_toolBar_boundary_cond": self.open_boundary_cond, - "action_toolBar_lateral_contrib": lambda: self.open_dummy("Apport Lateraux"), + "action_toolBar_lateral_contrib": self.open_lateral_contrib, "action_toolBar_spills": lambda: self.open_dummy("Deversement"), "action_toolBar_sections": lambda: self.open_dummy("Tronçons"), "action_toolBar_frictions": lambda: self.open_dummy("Frottements"), @@ -320,6 +321,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.bound = BoundaryConditionWindow(study = self.model, parent=self) self.bound.show() + def open_lateral_contrib(self): + self.lateral = LateralContributionWindow(study = self.model, parent=self) + self.lateral.show() # TODO: Delete me ! ############### diff --git a/src/View/ui/LateralContributions.ui b/src/View/ui/LateralContributions.ui new file mode 100644 index 0000000000000000000000000000000000000000..3097fd6363951c9822296f9a089f2e188e04d762 --- /dev/null +++ b/src/View/ui/LateralContributions.ui @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>450</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <property name="locale"> + <locale language="English" country="Europe"/> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab_liquid"> + <attribute name="title"> + <string>Liquid</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QTableView" name="tableView_liquid"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_solid"> + <attribute name="title"> + <string>Solid</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QTableView" name="tableView_solid"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_suspenssion"> + <attribute name="title"> + <string>Suspenssion</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QTableView" name="tableView_suspenssion"/> + </item> + </layout> + </widget> + </widget> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QToolBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="action_add"/> + <addaction name="action_del"/> + <addaction name="action_edit"/> + <addaction name="action_sort"/> + </widget> + <action name="action_add"> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> + </property> + <property name="text"> + <string>Add</string> + </property> + <property name="toolTip"> + <string>Add a new boundary condition or lateral contribution</string> + </property> + <property name="shortcut"> + <string>Ctrl+N</string> + </property> + </action> + <action name="action_del"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="toolTip"> + <string>Delete current selected rows</string> + </property> + <property name="shortcut"> + <string>Ctrl+D</string> + </property> + </action> + <action name="action_edit"> + <property name="icon"> + <iconset> + <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset> + </property> + <property name="text"> + <string>Edit</string> + </property> + <property name="toolTip"> + <string>Edit boundary condition or lateral contribution</string> + </property> + <property name="shortcut"> + <string>Ctrl+E</string> + </property> + </action> + <action name="action_sort"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset> + </property> + <property name="text"> + <string>Sort</string> + </property> + <property name="toolTip"> + <string>Sort boundary condition by name</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui>