| 161 | | def index_nodes(self, index_key): |
|---|
| | 161 | def _add_to_nodes(self, node, parent): |
|---|
| | 162 | """ |
|---|
| | 163 | Called only from add() |
|---|
| | 164 | """ |
|---|
| | 165 | nodes = self._nodes |
|---|
| | 166 | if parent: |
|---|
| | 167 | try: |
|---|
| | 168 | next_uncle = self.get_next_sibling(parent) |
|---|
| | 169 | except IndexError: |
|---|
| | 170 | # parent has no younger brothers.. |
|---|
| | 171 | # place it before the next uncle of grant_parent: |
|---|
| | 172 | self._add_to_nodes(node, self.get_parent(parent)) |
|---|
| | 173 | else: |
|---|
| | 174 | nodes.insert(nodes.index(next_uncle), node) |
|---|
| | 175 | else: |
|---|
| | 176 | # append to root node: |
|---|
| | 177 | nodes.append(node) |
|---|
| | 178 | |
|---|
| | 179 | @observed |
|---|
| | 180 | def add(self, node, parent=None): |
|---|
| | 181 | """ |
|---|
| | 182 | Add node to the tree. parent is the parent node, which may |
|---|
| | 183 | be None if the item should be added to the root item. |
|---|
| | 184 | |
|---|
| | 185 | For usage, see the unit tests. |
|---|
| | 186 | """ |
|---|
| | 187 | assert not self._children.get(node) |
|---|
| | 188 | siblings = self._children[parent] |
|---|
| | 189 | self._add_to_nodes(node, parent) |
|---|
| | 190 | siblings.append(node) |
|---|
| | 191 | # Create new entry for it's own children: |
|---|
| | 192 | self._children[node] = [] |
|---|
| | 193 | if parent: |
|---|
| | 194 | self._parents[node] = parent |
|---|
| | 195 | |
|---|
| | 196 | @observed |
|---|
| | 197 | def _remove(self, node): |
|---|
| | 198 | # Remove from parent item |
|---|
| | 199 | self.get_siblings(node).remove(node) |
|---|
| | 200 | # Remove data entries: |
|---|
| | 201 | del self._children[node] |
|---|
| | 202 | self._nodes.remove(node) |
|---|
| | 203 | try: |
|---|
| | 204 | del self._parents[node] |
|---|
| | 205 | except KeyError: |
|---|
| | 206 | pass |
|---|
| | 207 | |
|---|
| | 208 | def remove(self, node): |
|---|
| | 209 | """ |
|---|
| | 210 | Remove ``node`` from the tree. |
|---|
| | 211 | |
|---|
| | 212 | For usage, see the unit tests. |
|---|
| | 213 | """ |
|---|
| | 214 | # First remove children: |
|---|
| | 215 | for c in reversed(list(self._children[node])): |
|---|
| | 216 | self.remove(c) |
|---|
| | 217 | self._remove(node) |
|---|
| | 218 | |
|---|
| | 219 | def _reparent_nodes(self, node, parent): |
|---|
| | 220 | """ |
|---|
| | 221 | Helper for reparent(). |
|---|
| | 222 | The _children and _parent trees can be left intact as far as children |
|---|
| | 223 | of the reparented node are concerned. Only the position in the |
|---|
| | 224 | _nodes list changes. |
|---|
| | 225 | """ |
|---|
| | 226 | self._nodes.remove(node) |
|---|
| | 227 | self._add_to_nodes(node, parent) |
|---|
| | 228 | for c in self._children[node]: |
|---|
| | 229 | self._reparent_nodes(c, node) |
|---|
| | 230 | |
|---|
| | 231 | @observed |
|---|
| | 232 | def reparent(self, node, parent): |
|---|
| | 233 | """ |
|---|
| | 234 | Set new parent for a ``node``. ``Parent`` can be ``None``, indicating |
|---|
| | 235 | it's added to the top. |
|---|
| | 236 | |
|---|
| | 237 | >>> tree = Tree() |
|---|
| | 238 | >>> tree.add('n1') |
|---|
| | 239 | >>> tree.add('n2', parent='n1') |
|---|
| | 240 | >>> tree.add('n3', parent='n1') |
|---|
| | 241 | >>> tree.nodes |
|---|
| | 242 | ['n1', 'n2', 'n3'] |
|---|
| | 243 | >>> tree.reparent('n2', 'n3') |
|---|
| | 244 | >>> tree.get_parent('n2') |
|---|
| | 245 | 'n3' |
|---|
| | 246 | >>> tree.get_children('n3') |
|---|
| | 247 | ['n2'] |
|---|
| | 248 | >>> tree.nodes |
|---|
| | 249 | ['n1', 'n3', 'n2'] |
|---|
| | 250 | |
|---|
| | 251 | If a node contains children, those are also moved: |
|---|
| | 252 | |
|---|
| | 253 | >>> tree.add('n4') |
|---|
| | 254 | >>> tree.nodes |
|---|
| | 255 | ['n1', 'n3', 'n2', 'n4'] |
|---|
| | 256 | >>> tree.reparent('n1', 'n4') |
|---|
| | 257 | >>> tree.get_parent('n1') |
|---|
| | 258 | 'n4' |
|---|
| | 259 | >>> list(tree.get_all_children('n4')) |
|---|
| | 260 | ['n1', 'n3', 'n2'] |
|---|
| | 261 | >>> tree.nodes |
|---|
| | 262 | ['n4', 'n1', 'n3', 'n2'] |
|---|
| | 263 | """ |
|---|
| | 264 | # Add to new parent's children: |
|---|
| | 265 | self.get_siblings(node).remove(node) |
|---|
| | 266 | self._children[parent].append(node) |
|---|
| | 267 | self._parents[node] = parent |
|---|
| | 268 | |
|---|
| | 269 | # reorganize nodes |
|---|
| | 270 | self._reparent_nodes(node, parent) |
|---|
| | 271 | |
|---|
| | 272 | |
|---|
| | 273 | reversible_pair(add, _remove, |
|---|
| | 274 | bind1={'parent': lambda self, node: self.get_parent(node) }) |
|---|
| | 275 | |
|---|
| | 276 | reversible_method(reparent, reverse=reparent, |
|---|
| | 277 | bind={'parent': lambda self, node: self.get_parent(node) }) |
|---|
| | 278 | |
|---|
| | 279 | # Disable add/remove by default, since they are handled by canvas.Canvas |
|---|
| | 280 | disable_dispatching(add) |
|---|
| | 281 | disable_dispatching(_remove) |
|---|
| | 282 | |
|---|
| | 283 | |
|---|
| | 284 | class TreeIndexer(object): |
|---|
| | 285 | """ |
|---|
| | 286 | The ``TreeIndexer`` is a straight-forward indexer that adds an |
|---|
| | 287 | index-attribute to each object in the tree. This attribute can be used |
|---|
| | 288 | to do fast sorting. |
|---|
| | 289 | """ |
|---|
| | 290 | |
|---|
| | 291 | def __init__(self, tree, index_key): |
|---|
| | 292 | self._tree = tree |
|---|
| | 293 | self._index_key = index_key |
|---|
| | 294 | |
|---|
| | 295 | |
|---|
| | 296 | def index_tree(self): |
|---|
| 208 | | """ |
|---|
| 209 | | if index_key: |
|---|
| 210 | | return sorted(nodes, key=attrgetter(index_key), reverse=reverse) |
|---|
| 211 | | else: |
|---|
| 212 | | raise NotImplemented('index_key should be provided.') |
|---|
| 213 | | |
|---|
| 214 | | def _add_to_nodes(self, node, parent): |
|---|
| 215 | | """ |
|---|
| 216 | | Called only from add() |
|---|
| 217 | | """ |
|---|
| 218 | | nodes = self._nodes |
|---|
| 219 | | if parent: |
|---|
| 220 | | try: |
|---|
| 221 | | next_uncle = self.get_next_sibling(parent) |
|---|
| 222 | | except IndexError: |
|---|
| 223 | | # parent has no younger brothers.. |
|---|
| 224 | | # place it before the next uncle of grant_parent: |
|---|
| 225 | | self._add_to_nodes(node, self.get_parent(parent)) |
|---|
| 226 | | else: |
|---|
| 227 | | nodes.insert(nodes.index(next_uncle), node) |
|---|
| 228 | | else: |
|---|
| 229 | | # append to root node: |
|---|
| 230 | | nodes.append(node) |
|---|
| 231 | | |
|---|
| 232 | | @observed |
|---|
| 233 | | def add(self, node, parent=None): |
|---|
| 234 | | """ |
|---|
| 235 | | Add node to the tree. parent is the parent node, which may |
|---|
| 236 | | be None if the item should be added to the root item. |
|---|
| 237 | | |
|---|
| 238 | | For usage, see the unit tests. |
|---|
| 239 | | """ |
|---|
| 240 | | assert not self._children.get(node) |
|---|
| 241 | | siblings = self._children[parent] |
|---|
| 242 | | self._add_to_nodes(node, parent) |
|---|
| 243 | | siblings.append(node) |
|---|
| 244 | | # Create new entry for it's own children: |
|---|
| 245 | | self._children[node] = [] |
|---|
| 246 | | if parent: |
|---|
| 247 | | self._parents[node] = parent |
|---|
| 248 | | |
|---|
| 249 | | @observed |
|---|
| 250 | | def _remove(self, node): |
|---|
| 251 | | # Remove from parent item |
|---|
| 252 | | self.get_siblings(node).remove(node) |
|---|
| 253 | | # Remove data entries: |
|---|
| 254 | | del self._children[node] |
|---|
| 255 | | self._nodes.remove(node) |
|---|
| | 346 | |
|---|
| | 347 | If the tree is not yet indexed, it is indexed on the fly. |
|---|
| | 348 | |
|---|
| | 349 | >>> idx = TreeIndexer(t, 'my_key') |
|---|
| | 350 | >>> selection = (t.nodes[2], t.nodes[1]) |
|---|
| | 351 | >>> idx.sort(selection) |
|---|
| | 352 | [c, b] |
|---|
| | 353 | """ |
|---|
| 257 | | del self._parents[node] |
|---|
| 258 | | except KeyError: |
|---|
| 259 | | pass |
|---|
| 260 | | |
|---|
| 261 | | def remove(self, node): |
|---|
| 262 | | """ |
|---|
| 263 | | Remove ``node`` from the tree. |
|---|
| 264 | | |
|---|
| 265 | | For usage, see the unit tests. |
|---|
| 266 | | """ |
|---|
| 267 | | # First remove children: |
|---|
| 268 | | for c in reversed(list(self._children[node])): |
|---|
| 269 | | self.remove(c) |
|---|
| 270 | | self._remove(node) |
|---|
| 271 | | |
|---|
| 272 | | def _reparent_nodes(self, node, parent): |
|---|
| 273 | | """ |
|---|
| 274 | | Helper for reparent(). |
|---|
| 275 | | The _children and _parent trees can be left intact as far as children |
|---|
| 276 | | of the reparented node are concerned. Only the position in the |
|---|
| 277 | | _nodes list changes. |
|---|
| 278 | | """ |
|---|
| 279 | | self._nodes.remove(node) |
|---|
| 280 | | self._add_to_nodes(node, parent) |
|---|
| 281 | | for c in self._children[node]: |
|---|
| 282 | | self._reparent_nodes(c, node) |
|---|
| 283 | | |
|---|
| 284 | | @observed |
|---|
| 285 | | def reparent(self, node, parent): |
|---|
| 286 | | """ |
|---|
| 287 | | Set new parent for a ``node``. ``Parent`` can be ``None``, indicating |
|---|
| 288 | | it's added to the top. |
|---|
| 289 | | |
|---|
| 290 | | >>> tree = Tree() |
|---|
| 291 | | >>> tree.add('n1') |
|---|
| 292 | | >>> tree.add('n2', parent='n1') |
|---|
| 293 | | >>> tree.add('n3', parent='n1') |
|---|
| 294 | | >>> tree.nodes |
|---|
| 295 | | ['n1', 'n2', 'n3'] |
|---|
| 296 | | >>> tree.reparent('n2', 'n3') |
|---|
| 297 | | >>> tree.get_parent('n2') |
|---|
| 298 | | 'n3' |
|---|
| 299 | | >>> tree.get_children('n3') |
|---|
| 300 | | ['n2'] |
|---|
| 301 | | >>> tree.nodes |
|---|
| 302 | | ['n1', 'n3', 'n2'] |
|---|
| 303 | | |
|---|
| 304 | | If a node contains children, those are also moved: |
|---|
| 305 | | |
|---|
| 306 | | >>> tree.add('n4') |
|---|
| 307 | | >>> tree.nodes |
|---|
| 308 | | ['n1', 'n3', 'n2', 'n4'] |
|---|
| 309 | | >>> tree.reparent('n1', 'n4') |
|---|
| 310 | | >>> tree.get_parent('n1') |
|---|
| 311 | | 'n4' |
|---|
| 312 | | >>> list(tree.get_all_children('n4')) |
|---|
| 313 | | ['n1', 'n3', 'n2'] |
|---|
| 314 | | >>> tree.nodes |
|---|
| 315 | | ['n4', 'n1', 'n3', 'n2'] |
|---|
| 316 | | """ |
|---|
| 317 | | # Add to new parent's children: |
|---|
| 318 | | self.get_siblings(node).remove(node) |
|---|
| 319 | | self._children[parent].append(node) |
|---|
| 320 | | self._parents[node] = parent |
|---|
| 321 | | |
|---|
| 322 | | # reorganize nodes |
|---|
| 323 | | self._reparent_nodes(node, parent) |
|---|
| 324 | | |
|---|
| 325 | | |
|---|
| 326 | | reversible_pair(add, _remove, |
|---|
| 327 | | bind1={'parent': lambda self, node: self.get_parent(node) }) |
|---|
| 328 | | |
|---|
| 329 | | reversible_method(reparent, reverse=reparent, |
|---|
| 330 | | bind={'parent': lambda self, node: self.get_parent(node) }) |
|---|
| 331 | | |
|---|
| 332 | | # Disable add/remove by default, since they are handled by canvas.Canvas |
|---|
| 333 | | disable_dispatching(add) |
|---|
| 334 | | disable_dispatching(_remove) |
|---|
| | 355 | return sorted(nodes, key=attrgetter(self._index_key), reverse=reverse) |
|---|
| | 356 | except AttributeError: |
|---|
| | 357 | # Looks like not all elements are indexed, so let's do it instantly |
|---|
| | 358 | self.index_tree() |
|---|
| | 359 | return sorted(nodes, key=attrgetter(self._index_key), reverse=reverse) |
|---|