1#!/usr/bin/env python3 2 3#****************************************************************************** 4# treespotlist.py, provides a class to do operations on groups of spots 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 collections 16import operator 17from PyQt5.QtWidgets import QApplication 18import treestructure 19import undo 20 21 22class TreeSpotList(list): 23 """Class to do operations on groups of spots. 24 25 Stores a list of nodes. 26 """ 27 def __init__(self, spotList=None, sortSpots=True): 28 """Initialize a tree spot group. 29 30 Arguments: 31 spotList -- the initial list of spots 32 sortSpots -- if True sort the spots in tree order 33 """ 34 super().__init__() 35 if spotList: 36 self[:] = spotList 37 if sortSpots: 38 self.sort(key=operator.methodcaller('sortKey')) 39 40 def relatedNodes(self): 41 """Return a list of nodes related to these spots. 42 43 Removes any duplicate (cloned) nodes. 44 """ 45 tmpDict = collections.OrderedDict() 46 for spot in self: 47 node = spot.nodeRef 48 tmpDict[node.uId] = node 49 return list(tmpDict.values()) 50 51 def pasteChild(self, treeStruct, treeView): 52 """Paste child nodes from the clipbaord. 53 54 Return True on success. 55 Arguments: 56 treeStruct -- a ref to the existing tree structure 57 treeView -- a ref to the tree view for expanding nodes 58 """ 59 mimeData = QApplication.clipboard().mimeData() 60 parentNodes = self.relatedNodes() 61 if not parentNodes: 62 parentNodes = [treeStruct] 63 undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, 64 treeFormats=treeStruct.treeFormats) 65 for parent in parentNodes: 66 newStruct = treestructure.structFromMimeData(mimeData) 67 if not newStruct: 68 treeStruct.undoList.removeLastUndo(undoObj) 69 return False 70 newStruct.replaceDuplicateIds(treeStruct.nodeDict) 71 treeStruct.addNodesFromStruct(newStruct, parent) 72 for spot in self: 73 treeView.expandSpot(spot) 74 return True 75 76 def pasteSibling(self, treeStruct, insertBefore=True): 77 """Paste a sibling at the these spots. 78 79 Return True on success. 80 Arguments: 81 treeStruct -- a ref to the existing tree structure 82 insertBefore -- if True, insert before these nodes, o/w after 83 """ 84 mimeData = QApplication.clipboard().mimeData() 85 parentNodes = [spot.parentSpot.nodeRef for spot in self] 86 undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, 87 treeFormats=treeStruct.treeFormats) 88 for spot in self: 89 newStruct = treestructure.structFromMimeData(mimeData) 90 if not newStruct: 91 treeStruct.undoList.removeLastUndo(undoObj) 92 return False 93 newStruct.replaceDuplicateIds(treeStruct.nodeDict) 94 parent = spot.parentSpot.nodeRef 95 pos = parent.childList.index(spot.nodeRef) 96 if not insertBefore: 97 pos += 1 98 treeStruct.addNodesFromStruct(newStruct, parent, pos) 99 return True 100 101 def pasteCloneChild(self, treeStruct, treeView): 102 """Paste child clones from the clipbaord. 103 104 Return True on success. 105 Arguments: 106 treeStruct -- a ref to the existing tree structure 107 treeView -- a ref to the tree view for expanding nodes 108 """ 109 mimeData = QApplication.clipboard().mimeData() 110 newStruct = treestructure.structFromMimeData(mimeData) 111 if not newStruct: 112 return False 113 try: 114 existNodes = [treeStruct.nodeDict[node.uId] for node in 115 newStruct.childList] 116 except KeyError: 117 return False # nodes copied from other file 118 parentNodes = self.relatedNodes() 119 if not parentNodes: 120 parentNodes = [treeStruct] 121 for parent in parentNodes: 122 if not parent.ancestors().isdisjoint(set(existNodes)): 123 return False # circular ref 124 for node in existNodes: 125 if parent in node.parents(): 126 return False # identical siblings 127 undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, 128 treeFormats=treeStruct.treeFormats) 129 for parent in parentNodes: 130 for node in existNodes: 131 parent.childList.append(node) 132 node.addSpotRef(parent) 133 for spot in self: 134 treeView.expandSpot(spot) 135 return True 136 137 def pasteCloneSibling(self, treeStruct, insertBefore=True): 138 """Paste sibling clones at the these spots. 139 140 Return True on success. 141 Arguments: 142 treeStruct -- a ref to the existing tree structure 143 insertBefore -- if True, insert before these nodes, o/w after 144 """ 145 mimeData = QApplication.clipboard().mimeData() 146 newStruct = treestructure.structFromMimeData(mimeData) 147 if not newStruct: 148 return False 149 try: 150 existNodes = [treeStruct.nodeDict[node.uId] for node in 151 newStruct.childList] 152 except KeyError: 153 return False # nodes copied from other file 154 parentNodes = [spot.parentSpot.nodeRef for spot in self] 155 for parent in parentNodes: 156 if not parent.ancestors().isdisjoint(set(existNodes)): 157 return False # circular ref 158 for node in existNodes: 159 if parent in node.parents(): 160 return False # identical siblings 161 undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, 162 treeFormats=treeStruct.treeFormats) 163 for spot in self: 164 parent = spot.parentSpot.nodeRef 165 pos = parent.childList.index(spot.nodeRef) 166 if not insertBefore: 167 pos += 1 168 for node in existNodes: 169 parent.childList.insert(pos, node) 170 node.addSpotRef(parent) 171 return True 172 173 def addChild(self, treeStruct, treeView): 174 """Add new child to these spots. 175 176 Return the new spots. 177 Arguments: 178 treeStruct -- a ref to the existing tree structure 179 treeView -- a ref to the tree view for expanding nodes 180 """ 181 selSpots = self 182 if not selSpots: 183 selSpots = list(treeStruct.spotRefs) 184 undo.ChildListUndo(treeStruct.undoList, [spot.nodeRef for spot in 185 selSpots]) 186 newSpots = [] 187 for spot in selSpots: 188 newNode = spot.nodeRef.addNewChild(treeStruct) 189 newSpots.append(newNode.matchedSpot(spot)) 190 if spot.parentSpot: # can't expand root struct spot 191 treeView.expandSpot(spot) 192 return newSpots 193 194 def insertSibling(self, treeStruct, insertBefore=True): 195 """Insert a new sibling node at these nodes. 196 197 Return the new spots. 198 Arguments: 199 treeStruct -- a ref to the existing tree structure 200 insertBefore -- if True, insert before these nodes, o/w after 201 """ 202 undo.ChildListUndo(treeStruct.undoList, [spot.parentSpot.nodeRef for 203 spot in self]) 204 newSpots = [] 205 for spot in self: 206 newNode = spot.parentSpot.nodeRef.addNewChild(treeStruct, 207 spot.nodeRef, 208 insertBefore) 209 newSpots.append(newNode.matchedSpot(spot.parentSpot)) 210 return newSpots 211 212 def delete(self, treeStruct): 213 """Delete these spots, return a new spot to select. 214 215 Arguments: 216 treeStruct -- a ref to the existing tree structure 217 """ 218 # gather next selected node in decreasing order of desirability 219 nextSel = [spot.nextSiblingSpot() for spot in self] 220 nextSel.extend([spot.prevSiblingSpot() for spot in self]) 221 nextSel.extend([spot.parentSpot for spot in self]) 222 while (not nextSel[0] or not nextSel[0].parentSpot or 223 nextSel[0] in self): 224 del nextSel[0] 225 spotSet = set(self) 226 branchSpots = [spot for spot in self if 227 spot.parentSpotSet().isdisjoint(spotSet)] 228 undoParents = {spot.parentSpot.nodeRef for spot in branchSpots} 229 undo.ChildListUndo(treeStruct.undoList, list(undoParents)) 230 for spot in branchSpots: 231 treeStruct.deleteNodeSpot(spot) 232 return nextSel[0] 233 234 def indent(self, treeStruct): 235 """Indent these spots. 236 237 Makes them children of their previous siblings. 238 Return the new spots. 239 Arguments: 240 treeStruct -- a ref to the existing tree structure 241 """ 242 undoSpots = ([spot.parentSpot for spot in self] + 243 [spot.prevSiblingSpot() for spot in self]) 244 undo.ChildListUndo(treeStruct.undoList, [spot.nodeRef for spot in 245 undoSpots]) 246 newSpots = [] 247 for spot in self: 248 node = spot.nodeRef 249 newParentSpot = spot.prevSiblingSpot() 250 node.changeParent(spot.parentSpot, newParentSpot) 251 newSpots.append(node.matchedSpot(newParentSpot)) 252 return newSpots 253 254 def unindent(self, treeStruct): 255 """Unindent these spots. 256 257 Makes them their parent's next sibling. 258 Return the new spots. 259 Arguments: 260 treeStruct -- a ref to the existing tree structure 261 """ 262 undoSpots = [spot.parentSpot for spot in self] 263 undoSpots.extend([spot.parentSpot for spot in undoSpots]) 264 undo.ChildListUndo(treeStruct.undoList, [spot.nodeRef for spot in 265 undoSpots]) 266 newSpots = [] 267 for spot in reversed(self): 268 node = spot.nodeRef 269 oldParentSpot = spot.parentSpot 270 newParentSpot = oldParentSpot.parentSpot 271 pos = (newParentSpot.nodeRef.childList.index(oldParentSpot.nodeRef) 272 + 1) 273 node.changeParent(oldParentSpot, newParentSpot, pos) 274 newSpots.append(node.matchedSpot(newParentSpot)) 275 return newSpots 276 277 def move(self, treeStruct, up=True): 278 """Move these spots up or down by one item. 279 280 Arguments: 281 treeStruct -- a ref to the existing tree structure 282 up -- if True move up, o/w down 283 """ 284 undo.ChildListUndo(treeStruct.undoList, [spot.parentSpot.nodeRef 285 for spot in self]) 286 if not up: 287 self.reverse() 288 for spot in self: 289 parent = spot.parentSpot.nodeRef 290 pos = parent.childList.index(spot.nodeRef) 291 del parent.childList[pos] 292 pos = pos - 1 if up else pos + 1 293 parent.childList.insert(pos, spot.nodeRef) 294 295 def moveToEnd(self, treeStruct, first=True): 296 """Move these spots to the first or last position. 297 298 Arguments: 299 treeStruct -- a ref to the existing tree structure 300 first -- if True move to first position, o/w last 301 """ 302 undo.ChildListUndo(treeStruct.undoList, [spot.parentSpot.nodeRef 303 for spot in self]) 304 if first: 305 self.reverse() 306 for spot in self: 307 parent = spot.parentSpot.nodeRef 308 parent.childList.remove(spot.nodeRef) 309 if first: 310 parent.childList.insert(0, spot.nodeRef) 311 else: 312 parent.childList.append(spot.nodeRef) 313