diff options
Diffstat (limited to 'examples/widgets/itemviews/editabletreemodel/treemodel.py')
-rw-r--r-- | examples/widgets/itemviews/editabletreemodel/treemodel.py | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/examples/widgets/itemviews/editabletreemodel/treemodel.py b/examples/widgets/itemviews/editabletreemodel/treemodel.py new file mode 100644 index 000000000..b4e807349 --- /dev/null +++ b/examples/widgets/itemviews/editabletreemodel/treemodel.py @@ -0,0 +1,241 @@ +############################################################################# +## +## Copyright (C) 2022 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + + +from PySide6.QtCore import QModelIndex, Qt, QAbstractItemModel, Signal +from treeitem import TreeItem + + +class TreeModel(QAbstractItemModel): + # Define signals + dataChanged = Signal(QModelIndex, QModelIndex, object) + headerDataChanged = Signal(Qt.Orientation, int, int) + + def __init__(self, headers: list, data: str, parent=None): + super().__init__(parent) + + self.root_data = headers + self.root_item = TreeItem(self.root_data.copy()) + self.setup_model_data(data.split("\n"), self.root_item) + + def columnCount(self, parent: QModelIndex = None) -> int: + return self.root_item.column_count() + + def data(self, index: QModelIndex, role: int = None): + if not index.isValid(): + return None + + if role != Qt.DisplayRole and role != Qt.EditRole: + return None + + item: TreeItem = self.get_item(index) + + return item.data(index.column()) + + def flags(self, index: QModelIndex) -> Qt.ItemFlags: + if not index.isValid(): + return Qt.NoItemFlags + + return Qt.ItemIsEditable | QAbstractItemModel.flags(self, index) + + def get_item(self, index: QModelIndex = QModelIndex()) -> TreeItem: + if index.isValid(): + item: TreeItem = index.internalPointer() + if item: + return item + + return self.root_item + + def headerData(self, section: int, orientation: Qt.Orientation, + role: int = Qt.DisplayRole): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.root_item.data(section) + + return None + + def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: + if parent.isValid() and parent.column() != 0: + return QModelIndex() + + parent_item: TreeItem = self.get_item(parent) + if not parent_item: + return QModelIndex() + + child_item: TreeItem = parent_item.child(row) + if child_item: + return self.createIndex(row, column, child_item) + return QModelIndex() + + def insertColumns(self, position: int, columns: int, + parent: QModelIndex = QModelIndex()) -> bool: + self.beginInsertColumns(parent, position, position + columns - 1) + success: bool = self.root_item.insert_columns(position, columns) + self.endInsertColumns() + + return success + + def insertRows(self, position: int, rows: int, + parent: QModelIndex = QModelIndex()) -> bool: + parent_item: TreeItem = self.get_item(parent) + if not parent_item: + return False + + self.beginInsertRows(parent, position, position + rows - 1) + column_count = self.root_item.column_count() + success: bool = parent_item.insert_children(position, rows, column_count) + self.endInsertRows() + + return success + + def parent(self, index: QModelIndex = QModelIndex()) -> QModelIndex: + if not index.isValid(): + return QModelIndex() + + child_item: TreeItem = self.get_item(index) + if child_item: + parent_item: TreeItem = child_item.parent() + else: + parent_item = None + + if parent_item == self.root_item or not parent_item: + return QModelIndex() + + return self.createIndex(parent_item.child_number(), 0, parent_item) + + def removeColumns(self, position: int, columns: int, + parent: QModelIndex = QModelIndex()) -> bool: + self.beginRemoveColumns(parent, position, position + columns - 1) + success: bool = self.root_item.remove_columns(position, columns) + self.endRemoveColumns() + + if self.root_item.column_count() == 0: + self.removeRows(0, self.rowCount()) + + return success + + def removeRows(self, position: int, rows: int, + parent: QModelIndex = QModelIndex()) -> bool: + parent_item: TreeItem = self.get_item(parent) + if not parent_item: + return False + + self.beginRemoveRows(parent, position, position + rows - 1) + success: bool = parent_item.remove_children(position, rows) + self.endRemoveRows() + + return success + + def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: + if parent.isValid() and parent.column() > 0: + return 0 + + parent_item: TreeItem = self.get_item(parent) + if not parent_item: + return 0 + return parent_item.child_count() + + def setData(self, index: QModelIndex, value, role: int) -> bool: + if role != Qt.EditRole: + return False + + item: TreeItem = self.get_item(index) + result: bool = item.set_data(index.column(), value) + + if result: + self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole]) + + return result + + def setHeaderData(self, section: int, orientation: Qt.Orientation, value, + role: int = None) -> bool: + if role != Qt.EditRole or orientation != Qt.Horizontal: + return False + + result: bool = self.root_item.set_data(section, value) + + if result: + # todo: Check if emit headerDataChanged signal is correct + # emit headerDataChanged(orientation, section, section) + self.headerDataChanged(orientation, section, section) + + return result + + def setup_model_data(self, lines: list, parent: TreeItem): + parents = [parent] + indentations = [0] + + for line in lines: + line = line.rstrip() + if line and "\t" in line: + + position = 0 + while position < len(line): + if line[position] != " ": + break + position += 1 + + column_data = line[position:].split("\t") + column_data = [string for string in column_data if string] + + if position > indentations[-1]: + if parents[-1].child_count() > 0: + parents.append(parents[-1].last_child()) + indentations.append(position) + else: + while position < indentations[-1] and parents: + parents.pop() + indentations.pop() + + parent: TreeItem = parents[-1] + col_count = self.root_item.column_count() + parent.insert_children(parent.child_count(), 1, col_count) + + for column in range(len(column_data)): + child = parent.last_child() + child.set_data(column, column_data[column]) + + def _repr_recursion(self, item: TreeItem, indent: int = 0) -> str: + result = " " * indent + repr(item) + "\n" + for child in item.child_items: + result += self._repr_recursion(child, indent + 2) + return result + + def __repr__(self) -> str: + return self._repr_recursion(self.root_item) |