1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """A specialized container whose contents can be accessed like it was a
17 tree-like structure."""
18
19 from muntjac.util import OrderedSet
20
21 from muntjac.data.container import IContainer, IHierarchical
22 from muntjac.data.util.indexed_container import IndexedContainer
23
24
26 """A specialized Container whose contents can be accessed like it was a
27 tree-like structure.
28
29 @author: Vaadin Ltd.
30 @author: Richard Lincoln
31 """
32
34 super(HierarchicalContainer, self).__init__()
35
36
37 self._noChildrenAllowed = set()
38
39
40 self._parent = dict()
41
42
43
44 self._filteredParent = None
45
46
47 self._children = dict()
48
49
50 self._filteredChildren = None
51
52
53 self._roots = list()
54
55
56 self._filteredRoots = None
57
58
59 self._includeParentsWhenFiltering = True
60
61 self._contentChangedEventsDisabled = False
62
63 self._contentsChangedEventPending = None
64
65 self._filterOverride = None
66
67
69
70 if itemId in self._noChildrenAllowed:
71 return False
72
73 return self.containsId(itemId)
74
75
77
78 if self._filteredChildren is not None:
79 c = self._filteredChildren.get(itemId)
80 else:
81 c = self._children.get(itemId)
82
83 if c is None:
84 return None
85
86 return list(c)
87
88
90
91 if self._filteredParent is not None:
92 return self._filteredParent.get(itemId)
93
94 return self._parent.get(itemId)
95
96
98
99 if self._filteredChildren is not None:
100 return itemId in self._filteredChildren
101 else:
102 return itemId in self._children
103
104
106
107
108
109
110 if self._filteredRoots is not None:
111 if itemId not in self._filteredRoots:
112 return False
113 elif itemId in self._parent:
114 return False
115
116
117 return self.containsId(itemId)
118
119
121
122 if self._filteredRoots is not None:
123 return list(self._filteredRoots)
124 else:
125 return list(self._roots)
126
127
129 """Sets the given Item's capability to have children. If the Item
130 identified with the itemId already has children and the
131 areChildrenAllowed is false this method fails and C{False}
132 is returned; the children must be first explicitly removed with
133 L{setParent} or L{IContainer.removeItem}.
134
135 @param itemId:
136 the ID of the Item in the container whose child capability
137 is to be set.
138 @param childrenAllowed:
139 the boolean value specifying if the Item can have children
140 or not.
141 @return: C{True} if the operation succeeded, C{False} if not
142 """
143
144 if not self.containsId(itemId):
145 return False
146
147
148 if childrenAllowed:
149 if itemId in self._noChildrenAllowed:
150 self._noChildrenAllowed.remove(itemId)
151 else:
152 self._noChildrenAllowed.add(itemId)
153
154 return True
155
156
158 """Sets the parent of an Item. The new parent item must exist and be
159 able to have children. (C{canHaveChildren(newParentId) == True}). It
160 is also possible to detach a node from the hierarchy (and thus make
161 it root) by setting the parent C{None}.
162
163 @param itemId:
164 the ID of the item to be set as the child of the Item
165 identified with newParentId.
166 @param newParentId:
167 the ID of the Item that's to be the new parent of the Item
168 identified with itemId.
169 @return: C{True} if the operation succeeded, C{False} if not
170 """
171
172 if not self.containsId(itemId):
173 return False
174
175
176 oldParentId = self._parent.get(itemId)
177
178
179 if ((newParentId is None and oldParentId is None)
180 or (newParentId is not None)
181 and (newParentId == oldParentId)):
182 return True
183
184
185 if newParentId is None:
186
187
188
189
190
191
192
193 l = self._children.get(oldParentId)
194 if l is not None:
195 l.remove(itemId)
196 if len(l) == 0:
197 del self._children[oldParentId]
198
199
200 self._roots.append(itemId)
201
202
203 del self._parent[itemId]
204
205 if self.hasFilters():
206
207
208
209 self.doFilterContainer(self.hasFilters())
210
211 self.fireItemSetChange()
212
213 return True
214
215
216
217
218
219
220
221
222
223
224
225 if ((not self.containsId(newParentId))
226 or (newParentId in self._noChildrenAllowed)):
227 return False
228
229
230 o = newParentId
231 while (o is not None) and (o != itemId):
232 o = self._parent.get(o)
233
234 if o is not None:
235 return False
236
237
238 self._parent[itemId] = newParentId
239 pcl = self._children.get(newParentId)
240 if pcl is None:
241
242
243 pcl = list()
244 self._children[newParentId] = pcl
245 pcl.append(itemId)
246
247
248 if oldParentId is None:
249 self._roots.remove(itemId)
250 else:
251 l = self._children.get(oldParentId)
252 if l is not None:
253 l.remove(itemId)
254 if len(l) == 0:
255 del self._children[oldParentId]
256
257 if self.hasFilters():
258
259
260
261 self.doFilterContainer(self.hasFilters())
262
263 self.fireItemSetChange()
264
265 return True
266
267
269 return self._filteredRoots is not None
270
271
273 """Moves a node (an Item) in the container immediately after a sibling
274 node. The two nodes must have the same parent in the container.
275
276 @param itemId:
277 the identifier of the moved node (Item)
278 @param siblingId:
279 the identifier of the reference node (Item), after which the
280 other node will be located
281 """
282 parent2 = self.getParent(itemId)
283 if parent2 is None:
284 childrenList = self._roots
285 else:
286 childrenList = self._children.get(parent2)
287
288 if siblingId is None:
289 childrenList.remove(itemId)
290 childrenList.insert(0, itemId)
291
292 else:
293 oldIndex = childrenList.index(itemId)
294 indexOfSibling = childrenList.index(siblingId)
295 if (indexOfSibling != -1) and (oldIndex != -1):
296 if oldIndex > indexOfSibling:
297 newIndex = indexOfSibling + 1
298 else:
299 newIndex = indexOfSibling
300
301 del childrenList[oldIndex]
302 childrenList.insert(newIndex, itemId)
303 else:
304 raise ValueError('Given identifiers do not have the '
305 'same parent.')
306
307 self.fireItemSetChange()
308
309
334
335
344
345
347 return not self._contentChangedEventsDisabled
348
349
351 self._contentChangedEventsDisabled = True
352
353
355 self._contentChangedEventsDisabled = False
356 if self._contentsChangedEventPending:
357 self.fireItemSetChange()
358 self._contentsChangedEventPending = False
359
360
382
383
385 self.disableContentsChangeEvents()
386 success = super(HierarchicalContainer, self).removeItem(itemId)
387
388 if success:
389
390 if itemId in self._roots:
391 self._roots.remove(itemId)
392
393
394
395 if self._filteredRoots is not None:
396 self._filteredRoots.remove(itemId)
397
398
399 childNodeIds = self._children.pop(itemId, None)
400 if childNodeIds is not None:
401 if self._filteredChildren is not None:
402 del self._filteredChildren[itemId]
403
404 for childId in childNodeIds:
405 self.setParent(childId, None)
406
407
408
409 parentItemId = self._parent.get(itemId)
410 if parentItemId is not None:
411 c = self._children.get(parentItemId)
412 if c is not None:
413 c.remove(itemId)
414 if len(c) == 0:
415 del self._children[parentItemId]
416
417
418
419 if self._filteredChildren is not None:
420 f = self._filteredChildren.get(parentItemId)
421 if f is not None:
422 f.remove(itemId)
423 if len(f) == 0:
424 del self._filteredChildren[parentItemId]
425
426 if itemId in self._parent:
427 del self._parent[itemId]
428
429 if self._filteredParent is not None:
430
431
432 del self._filteredParent[itemId]
433
434 if itemId in self._noChildrenAllowed:
435 self._noChildrenAllowed.remove(itemId)
436
437 self.enableAndFireContentsChangeEvents()
438
439 return success
440
441
479
480
486
487
489 """Used to control how filtering works. @see
490 L{setIncludeParentsWhenFiltering} for more information.
491
492 @return: true if all parents for items that match the filter are
493 included when filtering, false if only the matching items
494 are included
495 """
496 return self._includeParentsWhenFiltering
497
498
500 """Controls how the filtering of the container works. Set this to true
501 to make filtering include parents for all matched items in addition to
502 the items themselves. Setting this to false causes the filtering to
503 only include the matching items and make items with excluded parents
504 into root items.
505
506 @param includeParentsWhenFiltering:
507 true to include all parents for items that match the filter,
508 false to only include the matching items
509 """
510 self._includeParentsWhenFiltering = includeParentsWhenFiltering
511 if self._filteredRoots is not None:
512
513 self.doFilterContainer(True)
514
515
568
569
571 """Adds the given childItemId as a filteredChildren for the
572 parentItemId and sets it filteredParent.
573 """
574 parentToChildrenList = self._filteredChildren.get(parentItemId)
575 if parentToChildrenList is None:
576 parentToChildrenList = list()
577 self._filteredChildren[parentItemId] = parentToChildrenList
578 self._filteredParent[childItemId] = parentItemId
579 parentToChildrenList.append(childItemId)
580
581
583 """Recursively adds all items in the includedItems list to the
584 filteredChildren map in the same order as they are in the children map.
585 Starts from parentItemId and recurses down as long as child items that
586 should be included are found.
587
588 @param parentItemId:
589 The item id to start recurse from. Not added to a
590 filteredChildren list
591 @param includedItems:
592 Set containing the item ids for the items that should be
593 included in the filteredChildren map
594 """
595 childList = self._children.get(parentItemId)
596 if childList is None:
597 return
598
599 for childItemId in childList:
600 if childItemId in includedItems:
601 self.addFilteredChild(parentItemId, childItemId)
602 self.addFilteredChildrenRecursively(childItemId, includedItems)
603
604
606 """Scans the itemId and all its children for which items should be
607 included when filtering. All items which passes the filters are
608 included. Additionally all items that have a child node that should be
609 included are also themselves included.
610
611 @return: true if the itemId should be included in the filtered
612 container.
613 """
614 toBeIncluded = self.passesFilters(itemId)
615
616 childList = self._children.get(itemId)
617 if childList is not None:
618 for childItemId in self._children.get(itemId):
619 toBeIncluded = toBeIncluded | self.filterIncludingParents(
620 childItemId, includedItems)
621 if toBeIncluded:
622 includedItems.add(itemId)
623
624 return toBeIncluded
625
626
632