File: treemodel.py

package info (click to toggle)
treeline 3.1.5-1.1
  • links: PTS
  • area: main
  • in suites: trixie
  • size: 6,508 kB
  • sloc: python: 20,489; javascript: 998; makefile: 54
file content (215 lines) | stat: -rw-r--r-- 8,726 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/env python3

#******************************************************************************
# treemodel.py, provides a class for the tree's data
#
# TreeLine, an information storage program
# Copyright (C) 2022, Douglas W. Bell
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License, either Version 2 or any later
# version.  This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY.  See the included LICENSE file for details.
#******************************************************************************

import json
from PyQt5.QtCore import (QAbstractItemModel, QMimeData, QModelIndex, Qt,
                          pyqtSignal)
import undo
import treestructure
import globalref


class TreeModel(QAbstractItemModel):
    """Class interfacing between the tree structure and the tree view.
    """
    # first arg is set file modified, second is update trees in other views
    treeModified = pyqtSignal(bool, bool)
    def __init__(self, treeStructure, parent=None):
        """Initialize a TreeModel.

        Arguments:
            treeStructure -- a ref to the main tree structure
            parent -- optional QObject parent for the model
        """
        super().__init__(parent)
        self.treeStructure = treeStructure

    def index(self, row, column, parentIndex):
        """Returns the index of a spot in the model based on the parent index.

        Uses createIndex() to generate the model indices.
        Arguments:
            row         -- the row of the model node
            column      -- the column (always 0 for now)
            parentIndex -- the parent's model index in the tree structure
        """
        try:
            if not parentIndex.isValid():
                node = self.treeStructure.childList[row]
                fakeSpot = list(self.treeStructure.spotRefs)[0]
                spot = node.matchedSpot(fakeSpot)
                return self.createIndex(row, column, spot)
            parentSpot = parentIndex.internalPointer()
            node = parentSpot.nodeRef.childList[row]
            return self.createIndex(row, column, node.matchedSpot(parentSpot))
        except IndexError:
            return QModelIndex()

    def parent(self, index):
        """Returns the parent model index of the spot at the given index.

        Arguments:
            index -- the child model index
        """
        try:
            parentSpot = index.internalPointer().parentSpot
            if parentSpot.parentSpot:
                return self.createIndex(parentSpot.row(), 0, parentSpot)
        except AttributeError:
            # attempt to fix an unreproducable bug deleting deep nodes
            pass
        return QModelIndex()

    def rowCount(self, parentIndex):
        """Returns the number of children for the spot at the given index.

        Arguments:
            parentIndex -- the parent model index
        """
        try:
            parentSpot = parentIndex.internalPointer()
            return parentSpot.nodeRef.numChildren()
        except AttributeError:
            # top level if no parentIndex
            return len(self.treeStructure.childList)

    def columnCount(self, parentIndex):
        """The number of columns -- always 1 for now.
        """
        return 1

    def data(self, index, role=Qt.DisplayRole):
        """Return the output data for the node in the given role.

        Arguments:
            index -- the spot's model index
            role  -- the type of data requested
        """
        spot = index.internalPointer()
        if not spot:
            return None
        node = spot.nodeRef
        if role in (Qt.DisplayRole, Qt.EditRole):
            return node.title()
        if (role == Qt.DecorationRole and
            globalref.genOptions['ShowTreeIcons']):
            return globalref.treeIcons.getIcon(node.formatRef.iconName, True)
        return None

    def setData(self, index, value, role=Qt.EditRole):
        """Set node title after edit operation.

        Return True on success.
        Arguments:
            index -- the node's model index
            value -- the string result of the editing
            role -- the edit role of the data
        """
        if role != Qt.EditRole:
            return super().setData(index, value, role)
        node = index.internalPointer().nodeRef
        dataUndo = undo.DataUndo(self.treeStructure.undoList, node)
        if node.setTitle(value):
            self.dataChanged.emit(index, index)
            self.treeModified.emit(True, False)
            return True
        self.treeStructure.undoList.removeLastUndo(dataUndo)
        return False

    def flags(self, index):
        """Return the flags for the node at the given index.

        Arguments:
            index -- the node's model index
        """
        return (Qt.ItemIsEnabled | Qt.ItemIsSelectable |
                Qt.ItemIsEditable | Qt.ItemIsDragEnabled |
                Qt.ItemIsDropEnabled)

    def mimeData(self, indexList):
        """Return a mime data object for the given node index branches.

        Arguments:
            indexList -- a list of node indexes to convert
        """
        spots = [index.internalPointer() for index in indexList]
        # remove selections from the same branch
        TreeModel.storedDragSpots = [spot for spot in spots if
                                     spot.parentSpotSet().
                                     isdisjoint(set(spots))]
        nodes = [spot.nodeRef for spot in TreeModel.storedDragSpots]
        TreeModel.storedDragModel = self
        struct = treestructure.TreeStructure(topNodes=nodes, addSpots=False)
        generics = {formatRef.genericType for formatRef in
                    struct.treeFormats.values() if formatRef.genericType}
        for generic in generics:
            genericRef = self.treeStructure.treeFormats[generic]
            struct.treeFormats.addTypeIfMissing(genericRef)
            for formatRef in genericRef.derivedTypes:
                struct.treeFormats.addTypeIfMissing(formatRef)
        data = struct.fileData()
        dataStr = json.dumps(data, indent=0, sort_keys=True)
        mime = QMimeData()
        mime.setData('application/json', bytes(dataStr, encoding='utf-8'))
        return mime

    def mimeTypes(self):
        """Return a list of supported mime types for model objects.
        """
        return ['application/json']

    def supportedDropActions(self):
        """Return drop action enum values that are supported by this model.
        """
        return Qt.CopyAction | Qt.MoveAction

    def dropMimeData(self, mimeData, dropAction, row, column, index):
        """Decode mime data and add as a child node to the given index.

        Return True if successful.
        Arguments:
            mimeData -- data for the node branch to be added
            dropAction -- a drop type enum value
            row -- a row number for the drop location
            column -- the column number for the drop location (normally 0)
            index -- the index of the parent node for the drop

        """
        parent = (index.internalPointer().nodeRef if index.internalPointer()
                  else self.treeStructure)
        isMove = (dropAction == Qt.MoveAction and
                  TreeModel.storedDragModel == self)
        undoParents = [parent]
        if isMove:
            moveParents = {spot.parentSpot.nodeRef for spot in
                           TreeModel.storedDragSpots}
            undoParents.extend(list(moveParents))
        newStruct = treestructure.structFromMimeData(mimeData)
        # check for valid structure and no circular clone ref and not siblings:
        if newStruct and (not isMove or (not parent.uId in newStruct.nodeDict
                                         and (row >= 0 or {node.uId for node in
                                                           parent.childList}.
                                              isdisjoint({node.uId for node in
                                                      newStruct.childList})))):
            undo.ChildListUndo(self.treeStructure.undoList, undoParents,
                               treeFormats=self.treeStructure.treeFormats)
            if isMove:
                for spot in TreeModel.storedDragSpots:
                    self.treeStructure.deleteNodeSpot(spot)
                newStruct.replaceClonedBranches(self.treeStructure)
            else:
                newStruct.replaceDuplicateIds(self.treeStructure.nodeDict)
            self.treeStructure.addNodesFromStruct(newStruct, parent, row)
            return True
        return False