diff --git a/src/View/Network/Table.py b/src/View/Network/Table.py index b24865e8b15a6daf9671e4b05d9e017f19b1cb7d..733a910d5e8194c62cf0928aa7a5fdc4b6fc363f 100644 --- a/src/View/Network/Table.py +++ b/src/View/Network/Table.py @@ -25,6 +25,7 @@ from Model.Network.Graph import Graph from View.ASubWindow import ASubWindow from View.Network.GraphWidget import GraphWidget from View.Network.UndoCommand import * +from View.Tools.PamhyrTable import PamhyrTableModel from PyQt5.QtCore import ( Qt, QRect, QVariant, QAbstractTableModel, pyqtSlot, pyqtSignal, @@ -69,151 +70,105 @@ class ComboBoxDelegate(QItemDelegate): def currentItemChanged(self): self.commitData.emit(self.sender()) -class TrueFalseComboBoxDelegate(QItemDelegate): - def __init__(self, parent=None): - super(TrueFalseComboBoxDelegate, self).__init__(parent) - - def createEditor(self, parent, option, index): - self.editor = QComboBox(parent) - self.editor.addItems(["true", "false"]) - self.editor.setCurrentText("true" if index.data(Qt.DisplayRole) else "false") - return self.editor - - def setEditorData(self, editor, index): - value = str(index.data(Qt.DisplayRole)) - self.editor.currentTextChanged.connect(self.currentItemChanged) - - def setModelData(self, editor, model, index): - value = str(editor.currentText()) == "true" - model.setData(index, value) - 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 GraphTableModel(QAbstractTableModel): - def __init__(self, headers=[], graph=None, undo = None, rows_type="nodes"): - super(QAbstractTableModel, self).__init__() - self.headers = headers - self.graph = graph - self._type = rows_type - self._undo = undo - - if self._type == "nodes": - self.rows = graph.nodes() - elif self._type == "edges": - self.rows = graph.edges() - - def flags(self, index): - options = Qt.ItemIsEnabled | Qt.ItemIsSelectable - - if self.headers[index.column()] != "type": - options |= Qt.ItemIsEditable - - return options - - def rowCount(self, parent): - return len(self.rows) - - def columnCount(self, parent): - return len(self.headers) +class NodeTableModel(PamhyrTableModel): + def _setup_lst(self): + self._lst = self._data.nodes() def data(self, index, role): if role != Qt.ItemDataRole.DisplayRole: return QVariant() - if self.headers[index.column()] == "type": - node = self.rows[index.row()] + if self._headers[index.column()] == "type": + node = self._lst[index.row()] ret = "internal" - if not self.graph.is_enable_node(node): + if not self._data.is_enable_node(node): ret = "disable" - elif self.graph.is_upstream_node(node): + elif self._data.is_upstream_node(node): ret = "upstream" - elif self.graph.is_downstream_node(node): + elif self._data.is_downstream_node(node): ret = "downstream" return ret - return self.rows[index.row()][self.headers[index.column()]] - - def headerData(self, section, orientation, role): - if (role == Qt.ItemDataRole.DisplayRole and - orientation == Qt.Orientation.Horizontal): - return self.headers[section].capitalize() - - return QVariant() + return self._lst[index.row()][self._headers[index.column()]] @pyqtSlot() def setData(self, index, value, role=Qt.EditRole): - if index.isValid(): - if role == Qt.EditRole: - try: - if (self.headers[index.column()] == "node1" or - self.headers[index.column()] == "node2"): - node = self.graph.node(value) - self._undo.push( - SetNodeCommand( - self.graph, - self.rows[index.row()], - self.headers[index.column()], - node - ) - ) - # elif self.headers[index.column()] == "enable": - # self._undo.push( - # EnableEdgeCommand( - # self.rows[index.row()], value - # ) - # ) - else: - self._undo.push( - SetCommand( - self.rows[index.row()], - self.headers[index.column()], - value - ) - ) - except Exception as e: - logger.info(e) - logger.debug(traceback.format_exc()) + if not index.isValid(): + return False + + if role == Qt.EditRole: + try: + self._undo.push( + SetCommand( + self._lst[index.row()], + self._headers[index.column()], + value + ) + ) + except Exception as e: + logger.info(e) + logger.debug(traceback.format_exc()) self.dataChanged.emit(index, index, [Qt.DisplayRole]) self.layoutChanged.emit() return True self.dataChanged.emit(index, index) - else: - return False def update(self): - if self._type == "nodes": - self.rows = self.graph.nodes() - elif self._type == "edges": - self.rows = self.graph.edges() - + self._lst = self._data.nodes() self.layoutChanged.emit() - def reverse_edge(self, index): - if self._type == "edges": - tmp = self.rows[index.row()].node1 - self.rows[index.row()].node1 = self.rows[index.row()].node2 - self.rows[index.row()].node2 = tmp +class EdgeTableModel(PamhyrTableModel): + def _setup_lst(self): + self._lst = self._data.edges() + + def data(self, index, role): + if role != Qt.ItemDataRole.DisplayRole: + return QVariant() + + return self._lst[index.row()][self._headers[index.column()]] + + @pyqtSlot() + def setData(self, index, value, role=Qt.EditRole): + if not index.isValid(): + return False + + if role != Qt.EditRole: + return QVariant() + + try: + if (self._headers[index.column()] == "node1" or + self._headers[index.column()] == "node2"): + node = self.graph.node(value) + self._undo.push( + SetNodeCommand( + self._data, + self._lst[index.row()], + self._headers[index.column()], + node + ) + ) + else: + self._undo.push( + SetCommand( + self._lst[index.row()], + self._headers[index.column()], + value + ) + ) + except Exception as e: + logger.info(e) + logger.debug(traceback.format_exc()) + self.dataChanged.emit(index, index, [Qt.DisplayRole]) self.layoutChanged.emit() + return True - def undo(self): - self._undo.undo() - self.layoutChanged.emit() + self.dataChanged.emit(index, index) - def redo(self): - self._undo.redo() + def update(self): + self._lst = self._data.edges() self.layoutChanged.emit() diff --git a/src/View/Network/Window.py b/src/View/Network/Window.py index 0505186287d9f67354e5536e022923d52c9f9e5d..3fc91d797b8f3f72f3f0350901367f043e161e9a 100644 --- a/src/View/Network/Window.py +++ b/src/View/Network/Window.py @@ -37,8 +37,9 @@ from Model.River import RiverNode, RiverReach, River from View.ASubWindow import ASubMainWindow from View.Network.GraphWidget import GraphWidget from View.Network.UndoCommand import * +from View.Network.translate import * from View.Network.Table import ( - GraphTableModel, ComboBoxDelegate, TrueFalseComboBoxDelegate, + ComboBoxDelegate, NodeTableModel, EdgeTableModel, ) class NetworkWindow(ASubMainWindow): @@ -70,42 +71,38 @@ class NetworkWindow(ASubMainWindow): def setup_table(self): # Nodes table - - self._nodes_model = GraphTableModel( - headers = ["name", "type"], - graph = self._graph, - rows_type = "nodes", + table = self.find(QTableView, "tableView_nodes") + self._nodes_model = NodeTableModel( + table_view = table, + table_headers = table_headers_node, + editable_headers = ["name"], + data = self._graph, undo = self._undo_stack, ) - table = self.find(QTableView, "tableView_nodes") table.setModel(self._nodes_model) - #table.resizeColumnsToContents() table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # Edges table - - self._reachs_model = GraphTableModel( - headers = ["name", # "enable", - "node1", "node2"], - graph = self._graph, - rows_type = "edges", - undo = self._undo_stack, - ) + table = self.find(QTableView, "tableView_reachs") self.delegate_combobox = ComboBoxDelegate( graph = self._graph, parent = self, ) - self.delegate_true_false_combobox = TrueFalseComboBoxDelegate( - parent = self, - ) - table = self.find(QTableView, "tableView_reachs") + self._reachs_model = EdgeTableModel( + table_view = table, + table_headers = table_headers_edge, + editable_headers = ["name", "node1", "node2"], + delegates = { + "node1": self.delegate_combobox, + "node2": self.delegate_combobox, + }, + data = self._graph, + undo = self._undo_stack, + ) table.setModel(self._reachs_model) - # table.setItemDelegateForColumn(1, self.delegate_true_false_combobox) - table.setItemDelegateForColumn(1, self.delegate_combobox) - table.setItemDelegateForColumn(2, self.delegate_combobox) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) #table.resizeColumnsToContents() diff --git a/src/View/Tools/PamhyrTable.py b/src/View/Tools/PamhyrTable.py new file mode 100644 index 0000000000000000000000000000000000000000..8d266ca2eb3dc5243fee205d4918ea192b761fc1 --- /dev/null +++ b/src/View/Tools/PamhyrTable.py @@ -0,0 +1,152 @@ +# PamhyrTable.py -- Pamhyr abstract table model +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +import logging +import traceback + +from tools import trace, timer + +from Model.Except import NotImplementedMethodeError + +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, QStyledItemDelegate, +) + +logger = logging.getLogger() + + +class PamhyrTextDelegate(QStyledItemDelegate): + def __init__(self, parent=None): + super(PamhyrTextDelegate, self).__init__(parent) + + def createEditor(self, parent, option, index): + index.model().data(index, Qt.DisplayRole) + return QLineEdit(parent) + + def setEditorData(self, editor, index): + value = index.model().data(index, Qt.DisplayRole) + editor.setText(str(value)) + + def setModelData(self, editor, model, index): + model.setData(index, editor.text()) + + def updateEditorGeometry(self, editor, option, index): + editor.setGeometry(option.rect) + +class PamhyrTableModel(QAbstractTableModel): + def _setup_delegates(self): + if self._table_view is None: + return + + for h in self._headers: + if h in self._delegates: + self._table_view.setItemDelegateForColumn( + self._headers.index(h), self._delegates[h] + ) + else: + self._table_view.setItemDelegateForColumn( + self._headers.index(h), PamhyrTextDelegate( + parent=self + ) + ) + + def __init__(self, + table_view=None, + table_headers={}, + editable_headers=[], + delegates = {}, + data=None, + undo=None): + super(PamhyrTableModel, self).__init__() + + self._table_view = table_view + + self._table_headers = table_headers + self._headers = list(table_headers.keys()) + self._editable_headers = editable_headers + self._delegates = delegates + + self._data = data + self._undo = undo + self._lst = [] + + self._setup_delegates() + self._setup_lst() + + def _setup_lst(self): + self._lst = self.data + + def flags(self, index): + column = index.column() + + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + + if self._headers[column] in self._editable_headers: + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._lst) + + def columnCount(self, parent): + return len(self._headers) + + def headerData(self, section, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return self._table_headers[self._headers[section]] + + return QVariant() + + def data(self, index, role): + raise NotImplementedMethodeError(self, self.data) + + def setData(self, index, value, role=Qt.EditRole): + raise NotImplementedMethodeError(self, self.setData) + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() + + def add(self, row, parent=QModelIndex()): + raise NotImplementedMethodeError(self, self.add) + + def delete(self, rows, parent=QModelIndex()): + raise NotImplementedMethodeError(self, self.delete) + + def sort(self, _reverse, parent=QModelIndex()): + raise NotImplementedMethodeError(self, self.sort) + + def move_up(self, row, parent=QModelIndex()): + raise NotImplementedMethodeError(self, self.move_up) + + def move_down(self, index, parent=QModelIndex()): + raise NotImplementedMethodeError(self, self.move_down)