The implementation of the full expansion feature is nothing but a programming exercise.
Some modifications in method expand: are convenient:
expand: idx |cls subclassesnewItems currentLevel | cls := collectionOfObjects at: idx. currentLevel := level at: idx. (idx < collectionOfObjects size and: [(level at: idx + 1) > currentLevel]) ifTrue: [^self].subclasses := cls subclasses sort: [:cl1 :cl2 | cl1 name < cl2 name].newItems := self expandOneLevel: idx. collectionOfObjects := (collectionOfObjects copyFrom: 1 to: idx),subclasses,newItems first, (collectionOfObjects copyFrom: idx + 1 to: collectionOfObjects size). level := (level copyFrom: 1 to: idx),(RunArray new: subclasses size withAll: currentLevel + 1),newItems last, (level copyFrom: idx + 1 to: level size). self changed: #getClassesList; changed: #getClassSelection.
The new method expandOneLevel: fetches the immediately subordinated elements, but it does not update the view:
expandOneLevel: idx | cls subclasses currentLevel | cls := collectionOfObjects at: idx. currentLevel := level at: idx. subclasses := cls subclasses sort: [:cl1 :cl2 | cl1 name < cl2 name]. ^Array with: subclasses with: (RunArray new: subclasses size withAll: currentLevel + 1)
Full expansion is implemented by a new method that is similar to the method that implements one-level expansion. Like expand:, this message is designed to implement a menu item. It performs the required action and sends change-messages to update the view.
expandAll: idx | currentLevel | currentLevel := level at: idx. (idx < collectionOfObjects size and: [(level at: idx + 1) > currentLevel]) ifTrue: [^self]. self expandAllLevels: idx. self changed: #getClassesList; changed: #getClassSelection.
The real work is done in the method expandAllLevels:
which expands one level and is then recursively called once
for every new element. This method is designed to serve as a
working-horse behind the scene - it does not send messages to
update the view.
Note that it is crucial for the
correctness of the algorithm to process the new elements from
the last one to the first one.
expandAllLevels: idx | oneLevel newItems newLevel | oneLevel := self expandOneLevel: idx. newItems := oneLevel first. newLevel := oneLevel last. newItems isEmpty ifFalse: [collectionOfObjects := (collectionOfObjects copyFrom: 1 to: idx), newItems, (collectionOfObjects copyFrom: idx + 1 to: collectionOfObjects size). level := (level copyFrom: 1 to: idx), newLevel, (level copyFrom: idx + 1 to: level size). ]. idx + newItems size to: idx + 1 by: -1 do: [:i | self expandAllLevels: i]
The condition newItems isEmpty is not strictly necessary, it is useful to avoid the splitting and concatenation of the collections when nothing will be inserted.
To make the new feature available, we have to add one option to the view menu:
getMenu: aCustomMenu selectionIdx = 0 ifTrue: [^nil]. aCustomMenu add: 'expand' target: self selector: #expand: argument: selectionIdx; add: 'expand all' target: self selector: #expandAll: argument: selectionIdx; add: 'collapse' target: self selector: #collapse: argument: selectionIdx. ^aCustomMenu
The change set for this example is TreeView2.1.cs.
This works beautiful, but it is a very special application. It is of course possible to copy the methods of this mini-application into every model that has to support a hierarchical list, but it is certainly better to have a component that implements the hierarchical list in a way that allows easy reuse. This is our next topic.