1#!/usr/bin/env python3 2 3#****************************************************************************** 4# treespot.py, provides a class to store locations of tree node instances 5# 6# TreeLine, an information storage program 7# Copyright (C) 2018, Douglas W. Bell 8# 9# This is free software; you can redistribute it and/or modify it under the 10# terms of the GNU General Public License, either Version 2 or any later 11# version. This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY. See the included LICENSE file for details. 13#****************************************************************************** 14 15import sys 16import operator 17 18 19class TreeSpot: 20 """Class to store location info for tree node instances. 21 22 Used to generate breadcrumb navigation and interface with tree views. 23 A spot without a parent spot is an imaginary root spot, wihout a real node. 24 """ 25 def __init__(self, nodeRef, parentSpot): 26 """Initialize a tree spot. 27 28 Arguments: 29 nodeRef -- reference to the associated tree node 30 parentSpot -- the parent TreeSpot object 31 """ 32 self.nodeRef = nodeRef 33 self.parentSpot = parentSpot 34 35 def index(self, modelRef): 36 """Returns the index of this spot in the tree model. 37 38 Arguments: 39 modelRef -- a ref to the tree model 40 """ 41 return modelRef.createIndex(self.row(), 0, self) 42 43 def row(self): 44 """Return the rank of this spot in its parent's child list. 45 46 Should never be called from the imaginary root spot. 47 """ 48 try: 49 return self.parentSpot.nodeRef.childList.index(self.nodeRef) 50 except ValueError: 51 return 0 # avoid error message from interim view updates 52 53 def instanceNumber(self): 54 """Return this spot's rank in the node's spot list. 55 """ 56 spotList = sorted(list(self.nodeRef.spotRefs), 57 key=operator.methodcaller('sortKey')) 58 return spotList.index(self) 59 60 def spotId(self): 61 """Return a spot ID string, in the form "nodeID:spotInstance". 62 """ 63 return '{0}:{1:d}'.format(self.nodeRef.uId, self.instanceNumber()) 64 65 def isValid(self): 66 """Return True if spot references and all parents are valid. 67 """ 68 spot = self 69 while spot.parentSpot: 70 if not (spot in spot.nodeRef.spotRefs and 71 spot.nodeRef in spot.parentSpot.nodeRef.childList): 72 return False 73 spot = spot.parentSpot 74 if not spot in spot.nodeRef.spotRefs: 75 return False 76 return True 77 78 def spotDescendantGen(self): 79 """Return a generator to step through all spots in this branch. 80 81 Includes self. 82 """ 83 yield self 84 for childSpot in self.childSpots(): 85 for spot in childSpot.spotDescendantGen(): 86 yield spot 87 88 def spotDescendantOnlyGen(self): 89 """Return a generator to step through the spots in this branch. 90 91 Does not include self. 92 """ 93 for childSpot in self.childSpots(): 94 yield childSpot 95 for spot in childSpot.spotDescendantGen(): 96 yield spot 97 98 def expandedSpotDescendantGen(self, treeView): 99 """Return a generator to step through expanded spots in this branch. 100 101 Does not include root spot. 102 Arguments: 103 treeView -- a ref to the treeview 104 """ 105 for childSpot in self.childSpots(): 106 if treeView.isSpotExpanded(childSpot): 107 yield childSpot 108 for spot in childSpot.expandedSpotDescendantGen(treeView): 109 yield spot 110 111 def levelSpotDescendantGen(self, treeView, includeRoot=True, maxLevel=None, 112 openOnly=False, initLevel=0): 113 """Return generator with (spot, level) tuples for this branch. 114 115 Arguments: 116 treeView -- a ref to the treeview, requiired to check if open 117 includeRoot -- if True, the root spot is included 118 maxLevel -- the max number of levels to return (no limit if none) 119 openOnly -- if True, only include children open in the given view 120 initLevel -- the level number to start with 121 """ 122 if maxLevel == None: 123 maxLevel = sys.maxsize 124 if includeRoot: 125 yield (self, initLevel) 126 initLevel += 1 127 if initLevel < maxLevel and (not openOnly or 128 treeView.isSpotExpanded(self)): 129 for childSpot in self.childSpots(): 130 for spot, level in childSpot.levelSpotDescendantGen(treeView, 131 True, 132 maxLevel, 133 openOnly, 134 initLevel): 135 yield (spot, level) 136 137 def childSpots(self): 138 """Return a list of immediate child spots. 139 """ 140 return [childNode.matchedSpot(self) for childNode in 141 self.nodeRef.childList] 142 143 def prevSiblingSpot(self): 144 """Return the nearest previous sibling spot or None. 145 """ 146 if self.parentSpot: 147 pos = self.row() 148 if pos > 0: 149 node = self.parentSpot.nodeRef.childList[pos - 1] 150 return node.matchedSpot(self.parentSpot) 151 return None 152 153 def nextSiblingSpot(self): 154 """Return the nearest next sibling spot or None. 155 """ 156 if self.parentSpot: 157 childList = self.parentSpot.nodeRef.childList 158 pos = self.row() + 1 159 if pos < len(childList): 160 return childList[pos].matchedSpot(self.parentSpot) 161 return None 162 163 def prevTreeSpot(self, loop=False): 164 """Return the previous node in the tree order. 165 166 Return None at the start of the tree unless loop is true. 167 Arguments: 168 loop -- return the last node of the tree after the first if true 169 """ 170 sibling = self.prevSiblingSpot() 171 if sibling: 172 return sibling.lastDescendantSpot() 173 if self.parentSpot.parentSpot: 174 return self.parentSpot 175 elif loop: 176 return self.rootSpot().lastDescendantSpot() 177 return None 178 179 def nextTreeSpot(self, loop=False): 180 """Return the next node in the tree order. 181 182 Return None at the end of the tree unless loop is true. 183 Arguments: 184 loop -- return the root node at the end of the tree if true 185 """ 186 if self.nodeRef.childList: 187 return self.nodeRef.childList[0].matchedSpot(self) 188 ancestor = self 189 while ancestor.parentSpot: 190 sibling = ancestor.nextSiblingSpot() 191 if sibling: 192 return sibling 193 ancestor = ancestor.parentSpot 194 if loop: 195 return ancestor.nodeRef.childList[0].matchedSpot(ancestor) 196 return None 197 198 def lastDescendantSpot(self): 199 """Return the last spot of this spots's branch (last in tree order). 200 """ 201 spot = self 202 while spot.nodeRef.childList: 203 spot = spot.nodeRef.childList[-1].matchedSpot(spot) 204 return spot 205 206 def spotChain(self): 207 """Return a list of parent spots, including self. 208 """ 209 chain = [] 210 spot = self 211 while spot.parentSpot: 212 chain.insert(0, spot) 213 spot = spot.parentSpot 214 return chain 215 216 def parentSpotSet(self): 217 """Return a set of ancestor spots, not including self. 218 """ 219 result = set() 220 spot = self.parentSpot 221 while spot.parentSpot: 222 result.add(spot) 223 spot = spot.parentSpot 224 return result 225 226 def rootSpot(self): 227 """Return the root spot that references the tree structure. 228 """ 229 spot = self 230 while spot.parentSpot: 231 spot = spot.parentSpot 232 return spot 233 234 def sortKey(self): 235 """Return a tuple of parent row positions for sorting in tree order. 236 """ 237 positions = [] 238 spot = self 239 while spot.parentSpot: 240 positions.insert(0, spot.row()) 241 spot = spot.parentSpot 242 return tuple(positions) 243