# menutree.py """ Hierarchical menu 20010903 JJD Created as sidebar.py for dxweb 20020224 JJD Modified to handle any number of indent levels 20021112 JJD Adapted for use in help pages 20030330 JJD Add setSelection method for use when bulk-generating pages """ from types import ListType, TupleType, StringType _INDENT = ' '*3 class MenuTree: def __init__(self, contentslist=None, selection=None): # The menu tree is structured like this: # [[first, second], [first, second, third],...] # In each sublist, the first item is the heading. # Each item is (title, page_id), where either # page_id is a page name (i.e., a relative reference), # or page_id is a literal URL, or page_id is None. self._selection = selection if contentslist: self._contents = contentslist[:] else: self._contents = [] def setSelection(self, selection): self._selection = selection def _itemtext(self, indentlevel, title, page_id): if title and (indentlevel <= 0): title = '%s' % title if page_id and (page_id != self._selection): text = '%s' % (page_id, title) elif page_id == self._selection: # Text for selected page does not need link but should stand out text = '%s' % title else: text = title return _INDENT*indentlevel + text def _render(self, alist, indentlevel): # Note that the head element of any list of menu tree items cannot be # a list; it is a menu item which represents the whole list when collapsed. # The indentlevel of the root list is 0. The indent level of the head element # of a list is 1 less than the indent level of the list. # Return a list containing: # A flag indicating whether the list is expanded, and then: #- The text for element 0, if neither alist nor any of its # children contains the selected element. #- The text for all the elements of alist, if any child contains # the selected element. selection = self._selection working_list = [] expanded = 0 first = 1 for x in alist: if isinstance(x, ListType): # This cannot be the first element of alist more_items = self._render(x, indentlevel+1) # first element of more_items is 'expanded' flag for x expanded = expanded or more_items[0] # if x was not expanded, only its head element is present working_list += more_items[1:] else: # x is a menu item (title, page_id) (title, page_id) = x if not first: working_list.append(self._itemtext(indentlevel, title, page_id)) expanded = expanded or (page_id == selection) else: # head list element is at indent level of parent working_list.append(self._itemtext(indentlevel-1, title, page_id)) # also expand if head element is not a link expanded = expanded or (not page_id) or (page_id == selection) first = 0 if (indentlevel > 0) and (not expanded): # Keep only the head list element working_list = working_list[0:1] working_list.insert(0, expanded) return working_list def __str__(self): rows = self._render(self._contents, 0) return "\n
".join(rows[1:]) if __name__ == '__main__': contents = [\ ('1 at level 0', 'crap'), ('2 at level 0', 'crap'), [\ ('Non-link heading at level 0', None), ('1 at level 1', 'crap'), ('2 at level 1', 'crap'), ], [\ ('Not expanded link heading', 'crap'), ('1 at level 1', 'crap'), ('2 at level 1', 'crap'), [\ ('Not expanded link heading', 'crap'), ('1 at level 2', 'crap'), ('2 at level 2', 'crap'), ], ('4 at level 1', 'crap'), ], [\ ('Expanded', 'crap'), ('1 at level 1', 'crap'), ('2 at level 1', 'crap'), [\ ('Not expanded', 'crap'), ('1 at level 2', 'crap'), ('2 at level 2', 'crap'), ], [\ ('Expanded', 'crap'), ('1 at level 3', 'selected'), ('2 at level 3', 'crap'), ], ('5 at level 1', 'crap'), ], ('6 at level 0', 'crap'), ] sb = MenuTree(contentslist=contents, selection='selected') parts = [ '', 'Testing Menu Tree', '', str(sb), '', '', ] print "\n".join(parts)