File: treespot.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 (242 lines) | stat: -rw-r--r-- 8,461 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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/env python3

#******************************************************************************
# treespot.py, provides a class to store locations of tree node instances
#
# TreeLine, an information storage program
# Copyright (C) 2018, 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 sys
import operator


class TreeSpot:
    """Class to store location info for tree node instances.

    Used to generate breadcrumb navigation and interface with tree views.
    A spot without a parent spot is an imaginary root spot, wihout a real node.
    """
    def __init__(self, nodeRef, parentSpot):
        """Initialize a tree spot.

        Arguments:
            nodeRef -- reference to the associated tree node
            parentSpot -- the parent TreeSpot object
        """
        self.nodeRef = nodeRef
        self.parentSpot = parentSpot

    def index(self, modelRef):
        """Returns the index of this spot in the tree model.

        Arguments:
            modelRef -- a ref to the tree model
        """
        return modelRef.createIndex(self.row(), 0, self)

    def row(self):
        """Return the rank of this spot in its parent's child list.

        Should never be called from the imaginary root spot.
        """
        try:
            return self.parentSpot.nodeRef.childList.index(self.nodeRef)
        except ValueError:
            return 0  #  avoid error message from interim view updates

    def instanceNumber(self):
        """Return this spot's rank in the node's spot list.
        """
        spotList = sorted(list(self.nodeRef.spotRefs),
                          key=operator.methodcaller('sortKey'))
        return spotList.index(self)

    def spotId(self):
        """Return a spot ID string, in the form "nodeID:spotInstance".
        """
        return '{0}:{1:d}'.format(self.nodeRef.uId, self.instanceNumber())

    def isValid(self):
        """Return True if spot references and all parents are valid.
        """
        spot = self
        while spot.parentSpot:
            if not (spot in spot.nodeRef.spotRefs and
                    spot.nodeRef in spot.parentSpot.nodeRef.childList):
                return False
            spot = spot.parentSpot
        if not spot in spot.nodeRef.spotRefs:
            return False
        return True

    def spotDescendantGen(self):
        """Return a generator to step through all spots in this branch.

        Includes self.
        """
        yield self
        for childSpot in self.childSpots():
            for spot in childSpot.spotDescendantGen():
                yield spot

    def spotDescendantOnlyGen(self):
        """Return a generator to step through the spots in this branch.

        Does not include self.
        """
        for childSpot in self.childSpots():
            yield childSpot
            for spot in childSpot.spotDescendantGen():
                yield spot

    def expandedSpotDescendantGen(self, treeView):
        """Return a generator to step through expanded spots in this branch.

        Does not include root spot.
        Arguments:
            treeView -- a ref to the treeview
        """
        for childSpot in self.childSpots():
            if treeView.isSpotExpanded(childSpot):
                yield childSpot
                for spot in childSpot.expandedSpotDescendantGen(treeView):
                    yield spot

    def levelSpotDescendantGen(self, treeView, includeRoot=True, maxLevel=None,
                               openOnly=False, initLevel=0):
        """Return generator with (spot, level) tuples for this branch.

        Arguments:
            treeView -- a ref to the treeview, requiired to check if open
            includeRoot -- if True, the root spot is included
            maxLevel -- the max number of levels to return (no limit if none)
            openOnly -- if True, only include children open in the given view
            initLevel -- the level number to start with
        """
        if maxLevel == None:
            maxLevel = sys.maxsize
        if includeRoot:
            yield (self, initLevel)
            initLevel += 1
        if initLevel < maxLevel and (not openOnly or
                                     treeView.isSpotExpanded(self)):
            for childSpot in self.childSpots():
                for spot, level in childSpot.levelSpotDescendantGen(treeView,
                                                                    True,
                                                                    maxLevel,
                                                                    openOnly,
                                                                    initLevel):
                    yield (spot, level)

    def childSpots(self):
        """Return a list of immediate child spots.
        """
        return [childNode.matchedSpot(self) for childNode in
                self.nodeRef.childList]

    def prevSiblingSpot(self):
        """Return the nearest previous sibling spot or None.
        """
        if self.parentSpot:
            pos = self.row()
            if pos > 0:
                node = self.parentSpot.nodeRef.childList[pos - 1]
                return node.matchedSpot(self.parentSpot)
        return None

    def nextSiblingSpot(self):
        """Return the nearest next sibling spot or None.
        """
        if self.parentSpot:
            childList = self.parentSpot.nodeRef.childList
            pos = self.row() + 1
            if pos < len(childList):
                return childList[pos].matchedSpot(self.parentSpot)
        return None

    def prevTreeSpot(self, loop=False):
        """Return the previous node in the tree order.

        Return None at the start of the tree unless loop is true.
        Arguments:
            loop -- return the last node of the tree after the first if true
        """
        sibling = self.prevSiblingSpot()
        if sibling:
            return sibling.lastDescendantSpot()
        if self.parentSpot.parentSpot:
            return self.parentSpot
        elif loop:
            return self.rootSpot().lastDescendantSpot()
        return None

    def nextTreeSpot(self, loop=False):
        """Return the next node in the tree order.

        Return None at the end of the tree unless loop is true.
        Arguments:
            loop -- return the root node at the end of the tree if true
        """
        if self.nodeRef.childList:
            return self.nodeRef.childList[0].matchedSpot(self)
        ancestor = self
        while ancestor.parentSpot:
            sibling = ancestor.nextSiblingSpot()
            if sibling:
                return sibling
            ancestor = ancestor.parentSpot
        if loop:
            return ancestor.nodeRef.childList[0].matchedSpot(ancestor)
        return None

    def lastDescendantSpot(self):
        """Return the last spot of this spots's branch (last in tree order).
        """
        spot = self
        while spot.nodeRef.childList:
            spot = spot.nodeRef.childList[-1].matchedSpot(spot)
        return spot

    def spotChain(self):
        """Return a list of parent spots, including self.
        """
        chain = []
        spot = self
        while spot.parentSpot:
            chain.insert(0, spot)
            spot = spot.parentSpot
        return chain

    def parentSpotSet(self):
        """Return a set of ancestor spots, not including self.
        """
        result = set()
        spot = self.parentSpot
        while spot.parentSpot:
            result.add(spot)
            spot = spot.parentSpot
        return result

    def rootSpot(self):
        """Return the root spot that references the tree structure.
        """
        spot = self
        while spot.parentSpot:
            spot = spot.parentSpot
        return spot

    def sortKey(self):
        """Return a tuple of parent row positions for sorting in tree order.
        """
        positions = []
        spot = self
        while spot.parentSpot:
            positions.insert(0, spot.row())
            spot = spot.parentSpot
        return tuple(positions)