# 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 = [
'',
'