=== htdocs/css/ticket.css
==================================================================
--- htdocs/css/ticket.css  (revision 1103)
+++ htdocs/css/ticket.css  (local)
@@ -21,6 +21,8 @@
 #ticket th { color: #996; font-weight: normal; text-align: left }
 #ticket hr { color: #dd9; border-color: #dd9; border-width: 1px 0 0; height: 1px; margin: .5em 0 0 }
 
+#ticket .category { font-size: 200% } 
+
 #attachments { border: 1px outset #996; padding: 1em }
 #attachments .attachments { list-style: square; margin-left: 2em; padding: 0 }
 
=== scripts/trac-admin
==================================================================
--- scripts/trac-admin  (revision 1103)
+++ scripts/trac-admin  (local)
@@ -706,6 +706,23 @@
                 self._do_wiki_import(filename, page, cursor)
 
 
+    ## (Ticket) Category
+    _help_category = [('category list', 'Show possible ticket categories'),
+                       ('category add <value>', 'Add a category value option'),
+                       ('category change <value> <newvalue>',
+                        'Change a category value'),
+                       ('category remove <value>', 'Remove category value')]
+
+    def complete_category (self, text, line, begidx, endidx):
+        if begidx == 16:
+            comp = self.get_enum_list ('category')
+        elif begidx < 15:
+            comp = ['list','add','change','remove']
+        return self.word_complete(text, comp)
+
+    def do_category(self, line):
+        self._do_enum('category', line)
+
     ## (Ticket) Priority
     _help_priority = [('priority list', 'Show possible ticket priorities'),
                        ('priority add <value>', 'Add a priority value option'),
=== templates/newticket.cs
==================================================================
--- templates/newticket.cs  (revision 1103)
+++ templates/newticket.cs  (local)
@@ -9,8 +9,10 @@
 
 <div id="content" class="ticket">
 
-<h3>Create New Ticket:</h3>
 <form id="newticket" action="<?cs var:cgi_location ?>#preview" method="post">
+ <h3>Create New Ticket of <label for="category">category:</label><?cs
+  call:hdf_select(enums.category, "category", newticket.category) ?>
+ </h3>
  <div class="field">
   <label for="reporter">Your email or username:</label><br />
   <input type="text" id="reporter" name="reporter" size="40" value="<?cs
=== templates/query.cs
==================================================================
--- templates/query.cs  (revision 1103)
+++ templates/query.cs  (local)
@@ -2,11 +2,16 @@
 <?cs include:"header.cs" ?>
 <?cs include:"macros.cs" ?>
 
-<div id="ctxtnav" class="nav"><?cs if:query.edit_href ?>
- <ul>
-  <li class="last"><a href="<?cs var:query.edit_href ?>">Refine Query</a></li>
+<div id="ctxtnav" class="nav">
+ <ul><?cs 
+  if:query.edit_href ?>
+   <li class="last"><a href="<?cs var:query.edit_href ?>">Refine Query</a></li><?cs 
+  /if ?><?cs 
+  if:query.clear_href ?>
+   <li class="last"><a href="<?cs var:query.clear_href ?>">Clear Query</a></li><?cs 
+  /if ?>
  </ul>
-<?cs /if ?></div>
+</div>
 
 <div id="content" class="query">
  <h1><?cs var:title ?></h1>
@@ -20,6 +25,10 @@
   <?cs if:query.desc ?><input type="hidden" name="desc" value="1" /><?cs /if ?>
   <legend>Ticket Properties</legend>
   <div>
+   <label for="category" accesskey="t">Category:</label>
+   <?cs call:hdf_select_multiple(query.options.category, 'category', 4) ?>
+  </div>
+  <div>
    <label for="component" accesskey="c">Component:</label>
    <?cs call:hdf_select_multiple(query.options.component, 'component', 4) ?>
   </div>
@@ -112,7 +121,7 @@
  /if ?> matched this query.</p>
  <table id="tktlist" class="listing">
   <thead><tr><?cs each:header = query.headers ?><?cs
-   if:name(header) == 0 ?><th class="ticket<?cs
+   if:header.name == 'id' ?><th class="ticket<?cs
     if:header.order ?> <?cs var:header.order ?><?cs /if ?>">
     <a href="<?cs var:header.href ?>" title="Sort by ID (<?cs
       if:header.order == 'asc' ?>descending<?cs
@@ -131,8 +140,8 @@
      if:name(result) % 2 ?>odd<?cs else ?>even<?cs /if ?> <?cs
      var:result.priority ?>">
     <?cs each:header = query.headers ?><?cs
-     if:name(header) == 0 ?>
-      <td class="ticket"><a href="<?cs var:result.href ?>" title="View ticket"><?cs
+     if:header.name == 'id' ?>
+      <td class="ticket"><a href="<?cs var:result.href ?>" title="View ticket">#<?cs
         var:result.id ?></a></td><?cs
      else ?>
       <td><?cs if:header.name == 'summary' ?>
=== templates/ticket.cs
==================================================================
--- templates/ticket.cs  (revision 1103)
+++ templates/ticket.cs  (local)
@@ -32,7 +32,7 @@
 
 <div id="ticket">
  <div class="date"><?cs var:ticket.opened ?></div>
- <h1>Ticket #<?cs var:ticket.id ?> <?cs
+ <h1><?cs var:ticket.category ?> Ticket #<?cs var:ticket.id ?> <?cs
  if:ticket.status == 'closed' ?>(Closed: <?cs var:ticket.resolution ?>)<?cs
  elif:ticket.status != 'new' ?>(<?cs var:ticket.status ?>)<?cs
  /if ?></h1>
@@ -166,6 +166,9 @@
      var:ticket.summary ?>" /><?cs
    if $trac.acl.TICKET_ADMIN ?>
     <br />
+    <label for="category">Category:</label><?cs
+    call:hdf_select(enums.category, "category", ticket.category) ?>
+    <br />
     <label for="description">Description:</label>
     <div style="float: left">
      <textarea id="description" name="description" rows="10" cols="68"><?cs
=== trac/Query.py
==================================================================
--- trac/Query.py  (revision 1103)
+++ trac/Query.py  (local)
@@ -59,6 +59,7 @@
             results.append({
                 'id': id,
                 'href': self.env.href.ticket(id),
+                'category': row['category'],
                 'summary': util.escape(row['summary'] or '(no summary)'),
                 'status': row['status'] or '',
                 'component': row['component'] or '',
@@ -78,6 +79,8 @@
         if self.args.has_key('search'):
             self.req.redirect(self.env.href.query(constraints, order, desc,
                                                   action='view'))
+        if self.args.has_key('clear'):
+            self.req.redirect(self.env.href.query())
 
         action = self.args.get('action')
         if not action and not constraints:
@@ -91,6 +94,7 @@
 
     def _render_editor(self, constraints, order, desc):
         self.req.hdf.setValue('title', 'Custom Query')
+        self.req.hdf.setValue('query.clear_href', self.env.href.query(action='edit'))
         util.add_to_hdf(constraints, self.req.hdf, 'query.constraints')
         self.req.hdf.setValue('query.order', order)
         if desc: self.req.hdf.setValue('query.desc', '1')
@@ -112,6 +116,8 @@
                 del constraints[field]
 
         cursor = self.db.cursor()
+        add_options('category', constraints, 'query.options.', cursor,
+                    "SELECT name FROM enum WHERE type='category' ORDER BY value")
         add_options('status', constraints, 'query.options.', cursor,
                     "SELECT name FROM enum WHERE type='status' ORDER BY value")
         add_options('resolution', constraints, 'query.options.', cursor,
@@ -146,7 +152,7 @@
 
         # FIXME: the user should be able to configure which columns should
         # be displayed
-        headers = [ 'id', 'summary', 'status', 'component', 'owner' ]
+        headers = [ 'category', 'id', 'summary', 'status', 'component', 'owner' ]
         cols = headers
         if not 'priority' in cols:
             cols.append('priority')
@@ -176,7 +182,7 @@
                       "(id=%s.ticket AND %s.name='%s')"
                       % (k, k, k, k))
 
-        for col in [c for c in ['status', 'resolution', 'priority', 'severity']
+        for col in [c for c in ['category', 'status', 'resolution', 'priority', 'severity']
                     if c in cols]:
             sql.append(" INNER JOIN (SELECT name AS %s_name, value AS %s_value " \
                                    "FROM enum WHERE type='%s')" \
@@ -194,7 +200,7 @@
         if clauses:
             sql.append(" WHERE " + " AND ".join(clauses))
 
-        if order in ['status', 'resolution', 'priority', 'severity']:
+        if order in ['category', 'status', 'resolution', 'priority', 'severity']:
             sql.append(" ORDER BY %s_value" % order)
         else:
             sql.append(" ORDER BY " + order)
=== trac/Ticket.py
==================================================================
--- trac/Ticket.py  (revision 1103)
+++ trac/Ticket.py  (local)
@@ -37,7 +37,7 @@
 class Ticket(UserDict):
     std_fields = ['time', 'component', 'severity', 'priority', 'milestone',
                   'reporter', 'owner', 'cc', 'url', 'version', 'status', 'resolution',
-                  'keywords', 'summary', 'description']
+                  'keywords', 'summary', 'description', 'category']
 
     def __init__(self, *args):
         UserDict.__init__(self)
@@ -303,6 +303,8 @@
                           self.env.get_config('ticket', 'default_component'))
         ticket.setdefault('milestone',
                           self.env.get_config('ticket', 'default_milestone'))
+        ticket.setdefault('category',
+                          self.env.get_config('ticket', 'default_category'))
         ticket.setdefault('priority',
                           self.env.get_config('ticket', 'default_priority'))
         ticket.setdefault('severity',
@@ -393,6 +395,7 @@
         util.hdf_add_if_missing(self.req.hdf, 'ticket.components', ticket['component'])
         util.hdf_add_if_missing(self.req.hdf, 'ticket.milestones', ticket['milestone'])
         util.hdf_add_if_missing(self.req.hdf, 'ticket.versions', ticket['version'])
+        util.hdf_add_if_missing(self.req.hdf, 'enums.category', ticket['category'])
         util.hdf_add_if_missing(self.req.hdf, 'enums.priority', ticket['priority'])
         util.hdf_add_if_missing(self.req.hdf, 'enums.severity', ticket['severity'])
         util.hdf_add_if_missing(self.req.hdf, 'enums.resolution', 'fixed')
=== trac/core.py
==================================================================
--- trac/core.py  (revision 1103)
+++ trac/core.py  (local)
@@ -199,6 +199,8 @@
     pass
 
 def populate_hdf(hdf, env, db, req):
+    sql_to_hdf(db, "SELECT name FROM enum WHERE type='category' "
+               "ORDER BY value", hdf, 'enums.category')
     sql_to_hdf(db, "SELECT name FROM enum WHERE type='priority' "
                "ORDER BY value", hdf, 'enums.priority')
     sql_to_hdf(db, "SELECT name FROM enum WHERE type='severity' "
=== trac/db_default.py
==================================================================
--- trac/db_default.py  (revision 1103)
+++ trac/db_default.py  (local)
@@ -21,7 +21,7 @@
 
 
 # Database version identifier. Used for automatic upgrades.
-db_version = 7
+db_version = 8
 
 def __mkreports(reps):
     """Utility function used to create report data in same syntax as the
=== trac/Timeline.py
==================================================================
--- trac/Timeline.py  (revision 1103)
+++ trac/Timeline.py  (local)
@@ -56,23 +56,26 @@
         q = []
         if changeset:
             q.append("SELECT time, rev AS idata, '' AS tdata, 1 AS type, "
-                     " message, author "
+                     " message, author, '' AS category "
                      "FROM revision WHERE time>=%s AND time<=%s" %
                      (start, stop))
         if tickets:
             q.append("SELECT time, id AS idata, '' AS tdata, 2 AS type, "
-                     "summary AS message, reporter AS author "
+                     " summary AS message, reporter AS author, category "
                      "FROM ticket WHERE time>=%s AND time<=%s" %
                      (start, stop))
-            q.append("SELECT time, ticket AS idata, '' AS tdata, 4 AS type, "
-                     "'' AS message, author "
-                     "FROM ticket_change WHERE field='status' "
-                     "AND newvalue='reopened' AND time>=%s AND time<=%s" %
+            q.append("SELECT t1.time, t1.ticket AS idata, '' AS tdata, 4 AS type, "
+                     " '' AS message, t1.author, t0.category AS category "
+                     "FROM ticket_change t1 "
+                     " LEFT JOIN ticket t0 ON t0.id = t1.ticket "
+                     "WHERE t1.field='status' "
+                     "AND t1.newvalue='reopened' AND t1.time>=%s AND t1.time<=%s" %
                      (start, stop))
             q.append("SELECT t1.time AS time, t1.ticket AS idata,"
                      "       t2.newvalue AS tdata, 3 AS type,"
-                     "       t3.newvalue AS message, t1.author AS author"
+                     "       t3.newvalue AS message, t1.author AS author, t0.category AS category"
                      " FROM ticket_change t1"
+                     "   LEFT JOIN ticket t0 ON t0.id = t1.ticket"
                      "   INNER JOIN ticket_change t2 ON t1.ticket = t2.ticket"
                      "     AND t1.time = t2.time"
                      "   LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time"
@@ -82,12 +85,12 @@
                      "   AND t1.time >= %s AND t1.time <= %s" % (start,stop))
         if wiki:
             q.append("SELECT time, -1 AS idata, name AS tdata, 5 AS type, "
-                     "comment AS message, author "
+                     "comment AS message, author, '' AS category "
                         "FROM wiki WHERE time>=%s AND time<=%s" %
                      (start, stop))
         if milestone:
             q.append("SELECT time, -1 AS idata, '' AS tdata, 6 AS type, "
-                     "name AS message, '' AS author " 
+                     "name AS message, '' AS author, '' AS category " 
                      "FROM milestone WHERE time>=%s AND time<=%s" %
                      (start, stop))
 
@@ -113,6 +116,7 @@
                     'tdata': row['tdata'],
                     'type': int(row['type']),
                     'message': row['message'] or '',
+                    'category': row['category'],
                     'author': util.escape(row['author'] or 'anonymous')
                     }
 
