| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright 2008 Optaros, Inc. |
|---|
| 4 | # |
|---|
| 5 | import re |
|---|
| 6 | from urlparse import urlsplit |
|---|
| 7 | |
|---|
| 8 | from trac.core import * |
|---|
| 9 | from trac.web.api import IRequestFilter |
|---|
| 10 | from trac.web.chrome import add_script, add_stylesheet,ITemplateProvider |
|---|
| 11 | from trac.config import ListOption, BoolOption |
|---|
| 12 | from trac.util.html import html |
|---|
| 13 | |
|---|
| 14 | class MenuManagerModule(Component): |
|---|
| 15 | implements(IRequestFilter, ITemplateProvider) |
|---|
| 16 | |
|---|
| 17 | managed_menus = ListOption('menu-custom', 'managed_menus', 'mainnav,metanav', |
|---|
| 18 | doc="""List of menus to be controlled by the Menu Manager""") |
|---|
| 19 | serve_ui_files = BoolOption('menu-custom', 'serve_ui_files', True) |
|---|
| 20 | |
|---|
| 21 | # ITemplateProvider |
|---|
| 22 | def get_templates_dirs(self): |
|---|
| 23 | return [] |
|---|
| 24 | def get_htdocs_dirs(self): |
|---|
| 25 | from pkg_resources import resource_filename |
|---|
| 26 | return [('tracmenus',resource_filename(__name__, 'htdocs'))] |
|---|
| 27 | |
|---|
| 28 | # IRequestFilter methods |
|---|
| 29 | def pre_process_request(self, req, handler): |
|---|
| 30 | return handler |
|---|
| 31 | def post_process_request(self, req, template, data, content_type): |
|---|
| 32 | if 'nav_orig' in req.chrome: |
|---|
| 33 | return template, data, content_type |
|---|
| 34 | req.chrome['nav_orig'] = req.chrome['nav'].copy() |
|---|
| 35 | if 'ctxtnav' in self.managed_menus and 'ctxtnav' in req.chrome: |
|---|
| 36 | req.chrome['nav_orig']['ctxtnav']=[dict(name='ctxtnav_'+str(idx), label=ctx_label) |
|---|
| 37 | for idx, ctx_label in enumerate(req.chrome['ctxtnav'])] |
|---|
| 38 | for menu_name in self.managed_menus: |
|---|
| 39 | req.chrome['nav'][menu_name] = list(self._get_menu(req, menu_name, |
|---|
| 40 | req.chrome['nav_orig'])) |
|---|
| 41 | if menu_name=='ctxtnav': |
|---|
| 42 | req.chrome['ctxtnav'] = [ ctxt_item.get('label') for ctxt_item in req.chrome['nav'][menu_name] ] |
|---|
| 43 | |
|---|
| 44 | if self.serve_ui_files: |
|---|
| 45 | add_script(req, 'tracmenus/js/superfish.js') |
|---|
| 46 | add_script(req, 'tracmenus/js/tracmenus.js') |
|---|
| 47 | add_script(req, 'tracmenus/js/jquery.hoverIntent.minified.js') |
|---|
| 48 | add_stylesheet(req, 'tracmenus/css/superfish.css') |
|---|
| 49 | return template, data, content_type |
|---|
| 50 | |
|---|
| 51 | def _get_menu(self, req, menu_name, nav_orig): |
|---|
| 52 | config_menu, config_options = self._get_config_menus(req, menu_name) |
|---|
| 53 | menu_orig = nav_orig.get(menu_name, []) |
|---|
| 54 | |
|---|
| 55 | if 'inherit' in config_options: |
|---|
| 56 | menu_orig += nav_orig.get(config_options['inherit'], []) |
|---|
| 57 | |
|---|
| 58 | tree_menu={} |
|---|
| 59 | for option in sorted(menu_orig+[{'name':key} for key in config_menu.keys() if config_menu[key].get('hide_if_disabled',False)==False], |
|---|
| 60 | key=lambda x:int(config_menu.get(x['name'],{}).get('order',999))): |
|---|
| 61 | name = option['name'] |
|---|
| 62 | if 'visited' in tree_menu.get(name, []) \ |
|---|
| 63 | or config_menu.get(name, {}).get('enabled', True)==False \ |
|---|
| 64 | or config_menu.get(name, {}).get('if_path_info', True)==False \ |
|---|
| 65 | or False in [req.perm.has_permission(perm) for perm in config_menu.get(name, {}).get('perm', [])]: |
|---|
| 66 | continue |
|---|
| 67 | |
|---|
| 68 | tree_node = tree_menu.setdefault(name, {}) |
|---|
| 69 | tree_node.update(option.copy()) |
|---|
| 70 | |
|---|
| 71 | if 'label' in option and 'label' in config_menu.get(name, []): |
|---|
| 72 | del config_menu[name]['label'] |
|---|
| 73 | tree_node.update(config_menu.get(name, {'parent_name':'unassigned'})) |
|---|
| 74 | |
|---|
| 75 | tree_node['label'] = html(tree_node.setdefault('label', html.a(name))) |
|---|
| 76 | tree_node['visited'] = True |
|---|
| 77 | if tree_node.get('href'): |
|---|
| 78 | tree_node.setdefault('active', urlsplit(tree_node['href']).path==req.path_info) |
|---|
| 79 | |
|---|
| 80 | if '_tmp_children' in tree_node: |
|---|
| 81 | tree_node['children'] = html.ul() |
|---|
| 82 | tree_node['label'].append(tree_node['children']) |
|---|
| 83 | tree_node['children'].children.extend(tree_node['_tmp_children']) |
|---|
| 84 | del tree_node['_tmp_children'] |
|---|
| 85 | |
|---|
| 86 | if (tree_node['parent_name']=='unassigned' and not 'unassigned' in config_menu) \ |
|---|
| 87 | or tree_node['parent_name']=='top': |
|---|
| 88 | yield tree_node |
|---|
| 89 | continue |
|---|
| 90 | |
|---|
| 91 | tree_node['parent'] = tree_menu.setdefault(tree_node['parent_name'], {}) |
|---|
| 92 | |
|---|
| 93 | child_node = html.li(class_=tree_node.get('active')==True and 'active' or None) |
|---|
| 94 | child_node.children=[tree_node['label']] |
|---|
| 95 | if 'label' in tree_node['parent']: |
|---|
| 96 | if not 'children' in tree_node['parent']: |
|---|
| 97 | tree_node['parent']['children'] = html.ul() |
|---|
| 98 | tree_node['parent']['label'].append(tree_node['parent']['children']) |
|---|
| 99 | tree_node['parent']['children'].append(child_node) |
|---|
| 100 | else: |
|---|
| 101 | tree_node['parent'].setdefault('_tmp_children',[]).append(child_node) |
|---|
| 102 | |
|---|
| 103 | def _get_config_menus(self, req, menu_name): |
|---|
| 104 | new_menu_option=lambda name: dict(name=name, parent_name='top') |
|---|
| 105 | menu, options = {}, {} |
|---|
| 106 | for option, value in self.config[menu_name].options(): |
|---|
| 107 | item_parts = option.split('.',1) |
|---|
| 108 | name, prop_name = item_parts[0], len(item_parts)>1 and item_parts[1] or 'enabled' |
|---|
| 109 | if name in ['inherit']: |
|---|
| 110 | options[name] = value |
|---|
| 111 | continue |
|---|
| 112 | menu.setdefault(name, new_menu_option(name)) |
|---|
| 113 | if prop_name=='parent': |
|---|
| 114 | menu[name]['parent_name']=value |
|---|
| 115 | continue |
|---|
| 116 | elif prop_name=='enabled': |
|---|
| 117 | value=self.config[menu_name].getbool(option, True) |
|---|
| 118 | elif prop_name=='href': |
|---|
| 119 | value = value.replace('$PATH_INFO', req.path_info) |
|---|
| 120 | menu[name]['label']=menu[name].setdefault('label', html.a())(href=value.startswith('/') and req.href()+value or value) |
|---|
| 121 | elif prop_name=='label': |
|---|
| 122 | menu[name].setdefault('label', html.a())(value) |
|---|
| 123 | continue |
|---|
| 124 | elif prop_name=='path_info': |
|---|
| 125 | menu[name]['if_path_info'] = re.match(value, req.path_info) and True or False |
|---|
| 126 | elif prop_name=='hide_if_disabled': |
|---|
| 127 | menu[name][prop_name] = self.config[menu_name].getbool(option, False) |
|---|
| 128 | continue |
|---|
| 129 | elif prop_name=='perm': |
|---|
| 130 | menu[name][prop_name] = self.config[menu_name].getlist(option, default=[], sep=',') |
|---|
| 131 | continue |
|---|
| 132 | menu[name][prop_name]=value |
|---|
| 133 | return menu, options |
|---|