Search code examples
pythonpyqtpyqt5qtreeviewqabstractitemmodel

Insert child item to dict() in QAbsrtactItemModel


I have the next QJsonTreeItem with insertChildren() function::

class QJsonTreeItem(object):
    ...
    def setData(self, column, value) -> None:
        if column is 0:
            self.key = value
        if column is 1:
            self.value = value

    def insertChildren(self, position, rows, columns) -> bool:
        if position < 0 or position > len(self._children):
            return False

        for row in range(rows):
            data = [None for v in range(columns)]
            item = QJsonTreeItem(data, self)
            self._children.insert(position, item)

        return True

With the next QJsonTreeModel insertRows() and setData() functions:

class QJsonTreeModel(QAbstractItemModel):
    ...
    def insertRows(self, position: int, rows: int, parent: QModelIndex) -> bool:
        parent_item = self.getItem(parent)

        self.beginInsertRows(parent, position, position + rows - 1)
        success = parent_item.insertChildren(position, rows, self._root_item.columnCount())
        self.endInsertRows()

        return success

    def setData(self,
            index: QModelIndex,
            value: str=None,
            role: Qt.ItemDataRole=Qt.EditRole
            ) -> bool:
        if role == Qt.EditRole:
            item = index.internalPointer()
            item.setData(index.column(), value)
            self.dataChanged.emit(index, index, [Qt.EditRole])
            return True
        if role == Qt.DecorationRole:
            item = index.internalPointer()
            item.setData(0, value)
            item.setData(1, "[No string value]")
            self.dataChanged.emit(index, index, [Qt.EditRole])
            return True
        if role == Qt.ToolTipRole:
            item = index.internalPointer()
            item.setData(0, value)
            item.setData(1, int(1))
            self.dataChanged.emit(index, index, [Qt.EditRole])
            return True
        if role == Qt.StatusTipRole:
            item = index.internalPointer()
            item.setData(0, value)
            item.setData(1, False)
            self.dataChanged.emit(index, index, [Qt.EditRole])
            return True
        if role == Qt.WhatsThisRole:
            item = index.internalPointer()
            item.setData(0, "[No dict() name]")
            item.setData(1, dict())
            self.dataChanged.emit(index, index, [Qt.EditRole])
            return True
        if role == Qt.SizeHintRole:
            item = index.internalPointer()
            item.setData(0, "[No list() name]")
            item.setData(1, list())
            self.dataChanged.emit(index, index, [Qt.EditRole])
            return True
        return False

In my MainWindow file I insert data like this:

def tree_add_item(self, role: Qt.ItemDataRole) -> None:
    index = self.tree_view.selectionModel().currentIndex()
    parent = index.parent()

    if self.model.data(parent, Qt.EditRole) is None:
        if not self.model.insertRow(index.row() + 1, parent):
            return

        for column in range(self.model.columnCount(parent)):
            child = self.model.index(index.row() + 1, column, parent)
            if role == Qt.DecorationRole:
                self.model.setData(
                    index=child,
                    value="[No string key]",
                    role=role)
                return
            elif role == Qt.ToolTipRole:
                self.model.setData(
                    index=child,
                    value="[No int data]",
                    role=role)
                return
            elif role == Qt.StatusTipRole:
                self.model.setData(
                    index=child,
                    value="[No bool data]",
                    role=role)
                return
            elif role == Qt.WhatsThisRole or Qt.SizeHintRole:
                self.model.setData(index=child, value=None, role=role)
                # self.action_save_json_file()
                # self.action_refresh_json_file()
                return
            else:
                return

def tree_add_item_child(self, role: Qt.ItemDataRole) -> None:
    index = self.tree_view.selectionModel().currentIndex()
    parent = index

    if not self.model.insertRow(0, parent):
        return

    for column in range(self.model.columnCount(parent)):
        child = self.model.index(0, column, parent)
        if role == Qt.DecorationRole:
            self.model.setData(
                index=child,
                value="[No string key]",
                role=role)
        elif role == Qt.ToolTipRole:
            self.model.setData(
                index=child,
                value="[No int data]",
                role=role)
        elif role == Qt.StatusTipRole:
            self.model.setData(
                index=child,
                value="[No bool data]",
                role=role)
        elif role == Qt.WhatsThisRole or Qt.SizeHintRole:
            self.model.setData(
                index=child, 
                value=None, 
                role=role)
            # self.action_save_json_file()
            # self.action_refresh_json_file()

The problem is it successfuly adds new dict() or list() to the QTreeView like this

New dict() added

I want to add new child items, which my programm successfully adds

New child string

But when I`m saving back model to the file there is no recently added child string

No child string

Am I doing something wrong or it should work like this? At the moment my solution is to save back to the .json file new added dict() or list() and then load it againg to the QTreeView (commented lines: self.action_save_json_file() and self.action_refresh_json_file()). It`s working but it seems like a bad idea since not every user want to save immediately new added dict() or list(). Where am I wrong and how to solve this problem?

Update: My method for getting json from tree is this. It generates dict() from tree which I`m saving to some file.

Small clarification: after saving json to file and loading it back to QTreeView I`m able to add child elements to recently added dict() or list() and they can be edited or removed

    def get_json_from_tree(self, root: QJsonTreeItem=None) -> dict:
        root = root or self._root_item
        return self.generate_json_from_free(root)

    def generate_json_from_free(self, item) -> dict:
        amount_of_child = item.childCount()

        if item.type is dict:
            document = {}
            for i in range(amount_of_child):
                child = item.child(i)
                document[child.key] = self.generate_json_from_free(child)
            return document
        elif item.type == list:
            document = []
            for i in range(amount_of_child):
                child = item.child(i)
                document.append(self.generate_json_from_free(child))
            return document
        else:
            return item.value

Solution

  • The actual problem was that my generate_json_from_free() function did not catch QJsonTreeItem.type of newly added dictionary or list. item.type returned None and iti s simply did not enter to the if section.

    The solution is to set type of the QJsonTreeItem when user wants to add dict() or list() like this:

    self.model.getItem(child).type = dict
    

    for dict() and for list() is this:

    self.model.getItem(child).type = list
    

    The complete code is this:

    def tree_add_item(self, role: Qt.ItemDataRole) -> None:
    
        if self.model.data(parent, Qt.EditRole) is None:
        if not self.model.insertRow(index.row() + 1, parent):
            return
    
        for column in range(self.model.columnCount(parent)):
            child = self.model.index(index.row() + 1, column, parent)
            if role == Qt.DecorationRole:
                self.model.setData(
                    index=child,
                    value="[No string key]",
                    role=role)
                return
            elif role == Qt.ToolTipRole:
                self.model.setData(
                    index=child,
                    value="[No int data]",
                    role=role)
                return
            elif role == Qt.StatusTipRole:
                self.model.setData(
                    index=child,
                    value="[No bool data]",
                    role=role)
                return
            elif role == Qt.WhatsThisRole:
                self.model.setData(index=child, value=None, role=role)
                self.model.getItem(child).type = dict
                return
            elif role == Qt.SizeHintRole:
                self.model.setData(index=child, value=None, role=role)
                self.model.getItem(child).type = list
                return
            else:
                return