roadmapplugin-r11241/0002755000175000017500000000000011762432272012714 5ustar wmbwmbroadmapplugin-r11241/trunk/0002755000175000017500000000000011762433776014071 5ustar wmbwmbroadmapplugin-r11241/trunk/setup.cfg0000644000175000017500000000122211564201600015660 0ustar wmbwmb[egg_info] #tag_build = dev #tag_date = True [extract_messages] add_comments = TRANSLATOR: #copyright_holder = Edgewall Software #msgid_bugs_address = trac-dev@googlegroups.com output_file = roadmapplugin/locale/messages.pot #keywords = _ ngettext:1,2 N_ tag_ keywords = _ ngettext:1,2 N_ tag_ tagn_:1,2 width = 72 [init_catalog] input_file = roadmapplugin/locale/messages.pot output_dir = roadmapplugin/locale domain = roadmapplugin [compile_catalog] directory = roadmapplugin/locale domain = roadmapplugin [update_catalog] input_file = roadmapplugin/locale/messages.pot output_dir = roadmapplugin/locale domain = roadmappluginroadmapplugin-r11241/trunk/setup.py0000644000175000017500000000244711705244256015576 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2010 daveappendix # Copyright (C) 2010-2012 Franz Mayer # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this # notice you can do whatever you want with this stuff. If we meet some day, # and you think this stuff is worth it, you can buy me a beer in return. # Franz Mayer # # Author: Franz Mayer from setuptools import find_packages, setup # name can be any name. This name will be used to create the .egg file. # name that is used in packages is the one that is used in the trac.ini file. # use package name as entry_points setup( name='Roadmap Plugin', version='0.4.1', author = 'Franz Mayer, Gefasoft AG', author_email = 'franz.mayer@gefasoft.de', description = 'Sorts roadmap in descending order and adds an filter fields.', url = 'http://www.gefasoft-muenchen.de', download_url = 'http://trac-hacks.org/wiki/RoadmapPlugin', packages=find_packages(exclude=['*.tests*']), entry_points = """ [trac.plugins] roadmapplugin = roadmapplugin """, package_data={'roadmapplugin': ['locale/*.*', 'locale/*/LC_MESSAGES/*.*']}, ) roadmapplugin-r11241/trunk/roadmapplugin/0002755000175000017500000000000011762432272016721 5ustar wmbwmbroadmapplugin-r11241/trunk/roadmapplugin/__init__.py0000644000175000017500000000002711564201600021014 0ustar wmbwmbfrom roadmap import * roadmapplugin-r11241/trunk/roadmapplugin/locale/0002755000175000017500000000000011762432272020160 5ustar wmbwmbroadmapplugin-r11241/trunk/roadmapplugin/locale/messages.pot0000644000175000017500000000217511623457774022530 0ustar wmbwmb# Translations template for Roadmap-Plugin. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the # RoadmapPlugin project. # FIRST AUTHOR , 2010. # # copied from TracSubTicketsPlugin project, see # http://trac.edgewall.org/attachment/wiki/CookBook/PluginL10N/trac-subtickets-plugin_i18n-l10n.patch #, fuzzy msgid "" msgstr "" "Project-Id-Version: RoadmapPlugin 0.0.x\n" "POT-Creation-Date: 2011-03-10 01:38+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.0dev-r482\n" msgid "Sort descending" msgstr "" msgid "Show milestone descriptions" msgstr "" msgid "Descending" msgstr "" msgid "Ascending" msgstr "" msgid "Due" msgstr "" msgid "Name" msgstr "" msgid "Filter: " msgstr "" msgid "Sort by: " msgstr "" msgid "available prefixes: contains: ~, starts with: ^, ends with: $" msgstr "" roadmapplugin-r11241/trunk/roadmapplugin/locale/de_DE/0002755000175000017500000000000011762432272021120 5ustar wmbwmbroadmapplugin-r11241/trunk/roadmapplugin/locale/de_DE/LC_MESSAGES/0002755000175000017500000000000011762432272022705 5ustar wmbwmbroadmapplugin-r11241/trunk/roadmapplugin/locale/de_DE/LC_MESSAGES/roadmapplugin.po0000644000175000017500000000231211623457774026115 0ustar wmbwmb# German (Germany) translations for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-03-10 01:38+0200\n" "PO-Revision-Date: 2011-08-05 11:17+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: de_DE \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Sort descending" msgstr "Meilensteine absteigend nach Namen sortieren" msgid "Show milestone descriptions" msgstr "Zeige Beschreibung der Meilensteine an" msgid "Descending" msgstr "Absteigend" msgid "Ascending" msgstr "Aufsteigend" msgid "Due" msgstr "Fälligkeit" msgid "Name" msgstr "Name" msgid "Filter: " msgstr "Filter: " msgid "Sort by: " msgstr "Sortierung: " msgid "available prefixes: contains: ~, starts with: ^, ends with: $" msgstr "Verfügbare Prefixes: enhält: ~, beginnt mit: ^, endet mit: $" roadmapplugin-r11241/trunk/roadmapplugin/roadmap.py0000644000175000017500000003460211712761624020722 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2010 daveappendix # Copyright (C) 2010-2012 Franz Mayer # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this # notice you can do whatever you want with this stuff. If we meet some day, # and you think this stuff is worth it, you can buy me a beer in return. # Franz Mayer # # Author: Franz Mayer from genshi.filters import Transformer from genshi.builder import tag from genshi import HTML from trac.web.api import IRequestFilter, ITemplateStreamFilter from trac.core import Component, implements from trac.util.translation import domain_functions from operator import attrgetter from pkg_resources import resource_filename #@UnresolvedImport import re _, tag_, N_, add_domain = domain_functions('roadmapplugin', '_', 'tag_', 'N_', 'add_domain') keys = [u'noduedate',u'completed'] showkeys = {'noduedate':'hidenoduedate','completed':'showcompleted'} # returns the name of the attribute plus the prefix roadmap.filter def _get_session_key_name(name): return "roadmap.filter.%s" % name # saves a single attribute as a session key def _set_session_attrib(req, name, value): req.session[_get_session_key_name(name)] = value def _save_show_to_session(req): if req.args.has_key('show'): for key in keys: if key in req.args['show']: _set_session_attrib(req, showkeys[key], '1') else: _set_session_attrib(req, showkeys[key], '0') else: for key in keys: _set_session_attrib(req, showkeys[key], '0') def _get_show(req): show = [] for key in keys: erg = req.session.get(_get_session_key_name(showkeys[key]),'0') if erg == '1': if len(show) == 0: show = [key] else: show.append(key) return show def _get_settings(req, name, default): sessionKey = _get_session_key_name(name) #user pressed submit button in the config area so this settings have to be used if req.args.has_key('user_modification'): if not (req.args.has_key(name)): #key with given name does not exist in request return default else: #value of the given key is saved to session keys req.session[sessionKey] = req.args[name] return req.args[name] #user reloaded the page or gave no attribs, so session keys will be given, if existing elif req.session.has_key(sessionKey): return req.session[sessionKey] else: return default class FilterRoadmap(Component): """Filters roadmap milestones. Existing Trac convention says that the following prefixes on the filter do different things: - `~` contains - `^` starts with - `$` ends with This plugin uses same convention. For more information about this plugin, see [http://trac-hacks.org/wiki/RoadmapPlugin trac-hacks page]. Mainly copied from [https://trac-hacks.org/wiki/RoadmapFilterPlugin RoadmapFilterPlugin] and modified a bit. Thanks to [http://trac-hacks.org/wiki/daveappendix daveappendix].""" implements(IRequestFilter, ITemplateStreamFilter) def _session(self, req, name, default): return req.session.get(_get_session_key_name(name), default) def _getCheckbox(self, req, name, default): sessionKey = _get_session_key_name(name) result = '0' if req.args.has_key('user_modification'): # User has hit the update button on the form, # so update the session data. if req.args.has_key(name): result = '1' if result == '1': req.session[sessionKey] = '1' else: req.session[sessionKey] = '0' elif req.session.get(sessionKey, default) == '1': result = '1' return result def _matchFilter(self, name, filters): # Existing Trac convention says that the following prefixes # on the filter do different things: # ~ - contains # ^ - starts with # $ = ends with for filter in filters: if filter.startswith('^'): if name.startswith(filter[1:]): return True elif filter.startswith('$'): if name.endswith(filter[1:]): return True elif filter.startswith('~'): if name.find(filter[1:]) >= 0: return True elif name == filter: return True return False # IRequestFilter methods def pre_process_request(self, req, handler): if req.args.has_key('user_modification'): _save_show_to_session(req) else: req.args['show'] = _get_show(req) return handler def post_process_request(self, req, template, data, content_type): if template == 'roadmap.html': inc_milestones = _get_settings(req, 'inc_milestones','') exc_milestones = _get_settings(req, 'exc_milestones','') show_descriptions = self._getCheckbox(req, 'show_descriptions', '1') == '1' if inc_milestones != '': inc_milestones = [m.strip() for m in inc_milestones.split('|')] filteredMilestones = [] filteredStats = [] for i in range(len(data['milestones'])): m = data['milestones'][i] if self._matchFilter(m.name, inc_milestones): filteredMilestones.append(m) filteredStats.append(data['milestone_stats'][i]) data['milestones'] = filteredMilestones data['milestone_stats'] = filteredStats if exc_milestones != '': exc_milestones = [m.strip() for m in exc_milestones.split('|')] filteredMilestones = [] filteredStats = [] for i in range(len(data['milestones'])): m = data['milestones'][i] if not self._matchFilter(m.name, exc_milestones): filteredMilestones.append(m) filteredStats.append(data['milestone_stats'][i]) data['milestones'] = filteredMilestones data['milestone_stats'] = filteredStats if not show_descriptions: for m in data['milestones']: m.description = '' return (template, data, content_type) # ITemplateStreamFilter methods def filter_stream(self, req, method, filename, stream, data): if filename == 'roadmap.html': # Insert the new field for entering user names filter = Transformer('//form[@id="prefs"]/div[@class="buttons"]') return stream | filter.before(self._user_field_input(req)) return stream def _filterBox(self, req, label, name): return tag.label(label, tag.input(type="text", name=name, value=self._session(req, name, ''), style_="width:60%", title=_('available prefixes: contains: ~, starts with: ^, ends with: $'))) def _toggleBox(self, req, label, name, default): if self._session(req, name, default) == '1': checkbox = tag.input(type='checkbox', id=name, name=name, value='true', checked='checked') else: checkbox = tag.input(type='checkbox', id=name, name=name, value='true') return checkbox + ' ' + tag.label(label, for_=name) def _hackedHiddenField(self): # Hack: shove a hidden field in so we can tell if the update # button has been hit. return tag.input(type='hidden', name='user_modification', value='true') def _user_field_input(self, req): add_fields = tag.div(self._hackedHiddenField() + self._toggleBox(req, _('Show milestone descriptions'), 'show_descriptions', 'true') ) add_fields += tag.br() add_fields += self._filterBox(req, _('Filter: '), "inc_milestones") return add_fields class SortRoadMap(Component): """Shows another checkbox in roadmap view, which allows you to sort milestones in descending order of due date.""" implements(IRequestFilter, ITemplateStreamFilter) directions = ['Descending', 'Ascending'] criterias = ['Name', 'Due'] def __init__(self): locale_dir = resource_filename(__name__, 'locale') add_domain(self.env.path, locale_dir) def _comparems(self, m1, m2, sort_crit): if sort_crit == self.criterias[0]: # the milestone names are divided at the dots to compare (sub)versions v1 = m1.name.upper().split('.') v2 = m2.name.upper().split('.') depth = 0 # As long as both have entries and no result so far while depth < len(v1) and depth < len(v2): # if (sub)version is different if v1[depth] != v2[depth]: # Find leading Numbers in both entrys leadnum1 = re.search(r"\A\d+", v1[depth]) leadnum2 = re.search(r"\A\d+", v2[depth]) if leadnum1 and leadnum2: if leadnum1 != leadnum2: return int(leadnum1.group(0)) - int(leadnum2.group(0)) else: r1 = v1[depth].lstrip(leadnum1.group(0)) r2 = v2[depth].lstrip(leadnum2.group(0)) return 1 if (r1 > r2) else -1 elif leadnum1: return 1 elif leadnum2: return -1 else: return 1 if (v1[depth] > v2[depth]) else -1 # otherwise look in next depth depth += 1 # End of WHILE # At least one of the arrays ended and all numbers were equal so far # milestone with more numbers is bigger return len(v1) - len(v2) # other criteria not needed. Can be sorted easier by buildin methods else: return 0 def pre_process_request(self, req, handler): if req.args.has_key('user_modification'): _save_show_to_session(req) else: req.args['show'] = _get_show(req) """ overridden from IRequestFilter""" return handler def post_process_request(self, req, template, data, content_type): """ overridden from IRequestFilter""" if template == 'roadmap.html': sort_direct = _get_settings(req, 'sortdirect', self.directions[0]) sort_crit = _get_settings(req, 'sortcrit', self.criterias[0]) milestones = data['milestones'] sortedmilestones = [] if sort_crit == self.criterias[0]: for m in milestones: if len(sortedmilestones) == 0: sortedmilestones.append(m) else: index = 0 inserted = False while not inserted and index < len(sortedmilestones): sm = sortedmilestones[index] if self._comparems(m, sm, sort_crit) >= 0: sortedmilestones.insert(index, m) inserted = True else: index += 1 if inserted: break # All milestonenames were lower so append the milestone if not inserted: sortedmilestones.append(m) else: ms_with_due = [] ms_wo_due = [] for m in milestones: if m.due: ms_with_due.append(m) else: ms_wo_due.append(m) stats = data['milestone_stats'] new_stats = [] sortedmilestones = sorted(ms_with_due, key=attrgetter('due')) sortedmilestones.extend(ms_wo_due) if sort_direct == self.directions[1]: sortedmilestones.reverse() stats = data['milestone_stats'] new_stats = [] for m in sortedmilestones: for j, om in enumerate(milestones): if m.name == om.name: new_stats.append(stats[j]) continue data['milestones'] = sortedmilestones data['milestone_stats'] = new_stats return template, data, content_type def filter_stream(self, req, method, filename, stream, data): if filename == 'roadmap.html': sortcrit = _get_settings(req, 'sortcrit', self.criterias[0]) sortdirect = _get_settings(req, 'sortdirect', self.directions[0]) sel = ' selected = "selected"' html_str = '
' + _('Sort by: ') html_str += '' html_str += '
' html = HTML(html_str) filter = Transformer('//form[@id="prefs"]/div[@class="buttons"]') return stream | filter.before(html) return stream roadmapplugin-r11241/0.12/0002755000175000017500000000000011762432272013274 5ustar wmbwmbroadmapplugin-r11241/0.12/setup.cfg0000644000175000017500000000122211564201600015075 0ustar wmbwmb[egg_info] #tag_build = dev #tag_date = True [extract_messages] add_comments = TRANSLATOR: #copyright_holder = Edgewall Software #msgid_bugs_address = trac-dev@googlegroups.com output_file = roadmapplugin/locale/messages.pot #keywords = _ ngettext:1,2 N_ tag_ keywords = _ ngettext:1,2 N_ tag_ tagn_:1,2 width = 72 [init_catalog] input_file = roadmapplugin/locale/messages.pot output_dir = roadmapplugin/locale domain = roadmapplugin [compile_catalog] directory = roadmapplugin/locale domain = roadmapplugin [update_catalog] input_file = roadmapplugin/locale/messages.pot output_dir = roadmapplugin/locale domain = roadmappluginroadmapplugin-r11241/0.12/setup.py0000644000175000017500000000145411623457774015022 0ustar wmbwmbfrom setuptools import find_packages, setup # name can be any name. This name will be used to create the .egg file. # name that is used in packages is the one that is used in the trac.ini file. # use package name as entry_points setup( name='Roadmap Plugin', version='0.4.1', author = 'Franz Mayer, Gefasoft AG', author_email = 'franz.mayer@gefasoft.de', description = 'Sorts roadmap in descending order and adds an filter fields.', url = 'http://www.gefasoft-muenchen.de', download_url = 'TBD', packages=find_packages(exclude=['*.tests*']), entry_points = """ [trac.plugins] roadmapplugin = roadmapplugin """, package_data={'roadmapplugin': ['locale/*.*', 'locale/*/LC_MESSAGES/*.*']}, ) roadmapplugin-r11241/0.12/roadmapplugin/0002755000175000017500000000000011762432272016136 5ustar wmbwmbroadmapplugin-r11241/0.12/roadmapplugin/__init__.py0000644000175000017500000000002711564201600020231 0ustar wmbwmbfrom roadmap import * roadmapplugin-r11241/0.12/roadmapplugin/locale/0002755000175000017500000000000011762432272017375 5ustar wmbwmbroadmapplugin-r11241/0.12/roadmapplugin/locale/messages.pot0000644000175000017500000000217511623457774021745 0ustar wmbwmb# Translations template for Roadmap-Plugin. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the # RoadmapPlugin project. # FIRST AUTHOR , 2010. # # copied from TracSubTicketsPlugin project, see # http://trac.edgewall.org/attachment/wiki/CookBook/PluginL10N/trac-subtickets-plugin_i18n-l10n.patch #, fuzzy msgid "" msgstr "" "Project-Id-Version: RoadmapPlugin 0.0.x\n" "POT-Creation-Date: 2011-03-10 01:38+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.0dev-r482\n" msgid "Sort descending" msgstr "" msgid "Show milestone descriptions" msgstr "" msgid "Descending" msgstr "" msgid "Ascending" msgstr "" msgid "Due" msgstr "" msgid "Name" msgstr "" msgid "Filter: " msgstr "" msgid "Sort by: " msgstr "" msgid "available prefixes: contains: ~, starts with: ^, ends with: $" msgstr "" roadmapplugin-r11241/0.12/roadmapplugin/locale/de_DE/0002755000175000017500000000000011762432272020335 5ustar wmbwmbroadmapplugin-r11241/0.12/roadmapplugin/locale/de_DE/LC_MESSAGES/0002755000175000017500000000000011762432272022122 5ustar wmbwmbroadmapplugin-r11241/0.12/roadmapplugin/locale/de_DE/LC_MESSAGES/roadmapplugin.po0000644000175000017500000000231211623457774025332 0ustar wmbwmb# German (Germany) translations for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-03-10 01:38+0200\n" "PO-Revision-Date: 2011-08-05 11:17+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: de_DE \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Sort descending" msgstr "Meilensteine absteigend nach Namen sortieren" msgid "Show milestone descriptions" msgstr "Zeige Beschreibung der Meilensteine an" msgid "Descending" msgstr "Absteigend" msgid "Ascending" msgstr "Aufsteigend" msgid "Due" msgstr "Fälligkeit" msgid "Name" msgstr "Name" msgid "Filter: " msgstr "Filter: " msgid "Sort by: " msgstr "Sortierung: " msgid "available prefixes: contains: ~, starts with: ^, ends with: $" msgstr "Verfügbare Prefixes: enhält: ~, beginnt mit: ^, endet mit: $" roadmapplugin-r11241/0.12/roadmapplugin/roadmap.py0000644000175000017500000003141111623457774020143 0ustar wmbwmbfrom trac.core import * from genshi.filters import Transformer from genshi.builder import tag from genshi import HTML from trac.web.api import IRequestFilter, ITemplateStreamFilter from operator import attrgetter from trac.util.translation import domain_functions import pkg_resources import re #from trac.ticket.model import * _, tag_, N_, add_domain = domain_functions('roadmapplugin', '_', 'tag_', 'N_', 'add_domain') # copied from RoadmapFilterPlugin.py, see https://trac-hacks.org/wiki/RoadmapFilterPlugin def get_session_key(name): return "roadmap.filter.%s" % name # copied from RoadmapFilterPlugin.py, see https://trac-hacks.org/wiki/RoadmapFilterPlugin class FilterRoadmap(Component): """Filters roadmap milestones. Mainly copied from [https://trac-hacks.org/wiki/RoadmapFilterPlugin RoadmapFilterPlugin] and modified a bit. Thanks to daveappendix """ implements(IRequestFilter, ITemplateStreamFilter) def _session(self, req, name, default): return req.session.get(get_session_key(name), default) def _getFilter(self, req, name): sessionKey = get_session_key(name) result = '' if req.args.has_key(name): result = req.args[name] # make value persistent... req.session[sessionKey] = result elif req.session.has_key(sessionKey): # use persistent value... result = req.session[sessionKey] return result def _getCheckbox(self, req, name, default): sessionKey = get_session_key(name) result = False if req.args.has_key('user_modification'): # User has hit the update button on the form, # so update the session data. if req.args.has_key(name): result = True if result: req.session[sessionKey] = 'true' else: req.session[sessionKey] = 'false' elif req.session.get(sessionKey, default) == 'true': result = True return result def _matchFilter(self, name, filters): # Existing Trac convention says that the following prefixes # on the filter do different things: # ~ - contains # ^ - starts with # $ = ends with for filter in filters: if filter.startswith('^'): if name.startswith(filter[1:]): return True elif filter.startswith('$'): if name.endswith(filter[1:]): return True elif filter.startswith('~'): if name.find(filter[1:]) >= 0: return True elif name == filter: return True return False # IRequestFilter methods def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, data, content_type): if template == 'roadmap.html': inc_milestones = self._getFilter(req, 'inc_milestones') exc_milestones = self._getFilter(req, 'exc_milestones') show_descriptions = self._getCheckbox(req, 'show_descriptions', 'true') if inc_milestones != '': inc_milestones = [m.strip() for m in inc_milestones.split('|')] filteredMilestones = [] filteredStats = [] for i in range(len(data['milestones'])): m = data['milestones'][i] if self._matchFilter(m.name, inc_milestones): filteredMilestones.append(m) filteredStats.append(data['milestone_stats'][i]) data['milestones'] = filteredMilestones data['milestone_stats'] = filteredStats if exc_milestones != '': exc_milestones = [m.strip() for m in exc_milestones.split('|')] filteredMilestones = [] filteredStats = [] for i in range(len(data['milestones'])): m = data['milestones'][i] if not self._matchFilter(m.name, exc_milestones): filteredMilestones.append(m) filteredStats.append(data['milestone_stats'][i]) data['milestones'] = filteredMilestones data['milestone_stats'] = filteredStats if not show_descriptions: for m in data['milestones']: m.description = '' return (template, data, content_type) # ITemplateStreamFilter methods def filter_stream(self, req, method, filename, stream, data): if filename == 'roadmap.html': # Insert the new field for entering user names filter = Transformer('//form[@id="prefs"]/div[@class="buttons"]') return stream | filter.before(self._user_field_input(req)) return stream def _filterBox(self, req, label, name): return tag.label(label, tag.input(type="text", name=name, value=self._session(req, name, ''), style_="width:60%", title=_('available prefixes: contains: ~, starts with: ^, ends with: $'))) def _toggleBox(self, req, label, name, default): if self._session(req, name, default) == 'true': checkbox = tag.input(type='checkbox', name=name, value='true', checked='checked') else: checkbox = tag.input(type='checkbox', name=name, value='true') return checkbox + ' ' + tag.label(label, for_=name) def _hackedHiddenField(self): # Hack: shove a hidden field in so we can tell if the update # button has been hit. return tag.input(type='hidden', name='user_modification', value='true') def _user_field_input(self, req): return tag.div(self._hackedHiddenField() + self._toggleBox(req, _('Show milestone descriptions'), 'show_descriptions', 'true') + tag.br() + self._filterBox(req, _('Filter: '), "inc_milestones") # + tag.br() # + self._filterBox(req, "Exclude: ", "exc_milestones") ) class SortRoadMap(Component): """Shows another checkbox in roadmap view, which allows you to sort milestones in descending order of due date.""" implements(IRequestFilter, ITemplateStreamFilter) directions = ['Descending', 'Ascending'] criterias = ['Name', 'Due'] def __init__(self): locale_dir = pkg_resources.resource_filename(__name__, 'locale') add_domain(self.env.path, locale_dir) def _comparems(self, m1, m2, sort_crit): if sort_crit == self.criterias[0]: # the milestone names are divided at the dots to compare (sub)versions v1 = m1.name.upper().split('.') v2 = m2.name.upper().split('.') depth = 0 # As long as both have entries and no result so far while depth < len(v1) and depth < len(v2): # if (sub)version is different if v1[depth] != v2[depth]: # Find leading Numbers in both entrys leadnum1 = re.search(r"\A\d+", v1[depth]) leadnum2 = re.search(r"\A\d+", v2[depth]) if leadnum1 and leadnum2: if leadnum1 != leadnum2: return int(leadnum1.group(0)) - int(leadnum2.group(0)) else: r1 = v1[depth].lstrip(leadnum1.group(0)) r2 = v2[depth].lstrip(leadnum2.group(0)) return 1 if (r1 > r2) else -1 elif leadnum1: return 1 elif leadnum2: return -1 else: return 1 if (v1[depth] > v2[depth]) else -1 # otherwise look in next depth depth += 1 # End of WHILE # At least one of the arrays ended and all numbers were equal so far # milestone with more numbers is bigger return len(v1) - len(v2) # other criteria not needed. Can be sorted easier by buildin methods else: return 0 def pre_process_request(self, req, handler): """ overridden from IRequestFilter""" return handler def post_process_request(self, req, template, data, content_type): """ overridden from IRequestFilter""" if template == 'roadmap.html': # sort_desc = self._get_settings(req) # sort_desc = self._get_settings(req) sort_direct = self._get_settings(req, 'sortdirect', self.directions[0]) sort_crit = self._get_settings(req, 'sortcrit', self.criterias[0]) milestones = data['milestones'] sortedmilestones = [] if sort_crit == self.criterias[0]: for m in milestones: if len(sortedmilestones) == 0: sortedmilestones.append(m) else: index = 0 inserted = False while not inserted and index < len(sortedmilestones): sm = sortedmilestones[index] if self._comparems(m, sm, sort_crit) >= 0: sortedmilestones.insert(index, m) inserted = True else: index += 1 if inserted: break # All milestonenames were lower so append the milestone if not inserted: sortedmilestones.append(m) else: ms_with_due = [] ms_wo_due = [] for m in milestones: if m.due: ms_with_due.append(m) else: ms_wo_due.append(m) stats = data['milestone_stats'] new_stats = [] sortedmilestones = sorted(ms_with_due, key=attrgetter('due')) sortedmilestones.extend(ms_wo_due) if sort_direct == self.directions[1]: sortedmilestones.reverse() stats = data['milestone_stats'] new_stats = [] for i, m in enumerate(sortedmilestones): for j, om in enumerate(milestones): if m.name == om.name: new_stats.append(stats[j]) continue data['milestones'] = sortedmilestones data['milestone_stats'] = new_stats return template, data, content_type def filter_stream(self, req, method, filename, stream, data): if filename == 'roadmap.html': sortcrit = self._get_settings(req, 'sortcrit', self.criterias[0]) sortdirect = self._get_settings(req, 'sortdirect', self.directions[0]) sel = ' selected = "selected"' html_str = '
' + _('Sort by: ') html_str += '' html_str += '
' html = HTML(html_str) filter = Transformer('//form[@id="prefs"]/div[@class="buttons"]') return stream | filter.before(html) return stream def _get_settings(self, req, name, default): sessionKey = get_session_key(name) if req.args.has_key('user_modification'): # User has hit the update button on the form, # so update the session data. req.session[sessionKey] = req.args[name] return req.args[name] elif req.session.has_key(sessionKey): return req.session[sessionKey] else: return default