Index: htdocs/css/timeline.css
===================================================================
--- htdocs/css/timeline.css	(revision 1003)
+++ htdocs/css/timeline.css	(working copy)
@@ -30,6 +30,7 @@
 /* Apply icon background-image twice to avoid hover-flicker in IE/Win */
 dt.changeset, dt.changeset a { background-image: url(../changeset.png) !important }
 dt.newticket, dt.newticket a { background-image: url(../newticket.png) !important }
+dt.resolvedticket, dt.resolvedticket a { background-image: url(../resolvedticket.png) !important }
 dt.closedticket, dt.closedticket a { background-image: url(../closedticket.png) !important }
 dt.wiki, dt.wiki a { background-image: url(../wiki.png) !important }
 dt.milestone, dt.milestone a { background-image: url(../milestone.png) !important }
Index: htdocs/closedticket.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Index: wiki-default/TracIni
===================================================================
--- wiki-default/TracIni	(revision 1003)
+++ wiki-default/TracIni	(working copy)
@@ -22,6 +22,7 @@
 See also: TracLogging
 
 == [ticket] ==
+|| workflow_version  || Workflow version: 1 for simple, 2 for advanced ||
 || default_version   || Default version for newly created tickets ||
 || default_severity  || Default severity for newly created tickets ||
 || default_priority  || Default priority for newly created tickets ||
Index: wiki-default/TracAdmin
===================================================================
--- wiki-default/TracAdmin	(revision 1003)
+++ wiki-default/TracAdmin	(working copy)
@@ -23,10 +23,10 @@
 permission add <user> <action>      -- Add a new permission rule
 permission remove <user> <action>   -- Remove permission rule
 component list                      -- Show available components
-component add <name> <owner>        -- Add a new component
+component add <name> <owner> [<qaowner>]   -- Add a new component
 component rename <name> <newname>   -- Rename a component
 component remove <name>             -- Remove/uninstall component
-component chown <name> <owner>      -- Change component ownership
+component chown <name> <owner> [<qaowner>] -- Change component ownership
 priority list                       -- Show possible ticket priorities
 priority add <value>                -- Add a priority value option
 priority change <value> <newvalue>  -- Change a priority value
@@ -41,9 +41,10 @@
 version time <name> <time>          -- Set version date/time
 version remove <name>               -- Remove version
 milestone list                      -- Show milestones
-milestone add <name> [time]         -- Add milestone
+milestone add <name> [<owner> [time]] -- Add milestone
 milestone rename <name> <newname>   -- Rename milestone
 milestone time <name> <time>        -- Set milestone date/time
+milestone chown <name> <owner>      -- Change milestone ownership
 milestone remove <name>             -- Remove milestone
 }}}
 
Index: scripts/trac-admin
===================================================================
--- scripts/trac-admin	(revision 1003)
+++ scripts/trac-admin	(working copy)
@@ -290,10 +290,10 @@
     
 #    ## Component
     _help_component = [('component list', 'Show available components'),
-                       ('component add <name> <owner>', 'Add a new component'),
+                       ('component add <name> <owner> [<qaowner>]', 'Add a new component'),
                        ('component rename <name> <newname>', 'Rename a component'),
                        ('component remove <name>', 'Remove/uninstall component'),
-                       ('component chown <name> <owner>', 'Change component ownership')]
+                       ('component chown <name> <owner> [<qaowner>]', 'Change component ownership')]
 
     def complete_component (self, text, line, begidx, endidx):
         if begidx in [16,17]:
@@ -309,10 +309,14 @@
         try:
             if arg[0]  == 'list':
                 self._do_component_list()
-            elif arg[0] == 'add' and len(arg)==3:
+            elif arg[0] == 'add' and len(arg) in [3,4]:
                 name = arg[1]
                 owner = arg[2]
-                self._do_component_add(name, owner)
+                if len(arg) == 4:
+                    qaowner = arg[3]
+                else:
+                    qaowner = owner
+                self._do_component_add(name, owner, qaowner)
             elif arg[0] == 'rename' and len(arg)==3:
                 name = arg[1]
                 newname = arg[2]
@@ -320,22 +324,26 @@
             elif arg[0] == 'remove'  and len(arg)==2:
                 name = arg[1]
                 self._do_component_remove(name)
-            elif arg[0] == 'chown' and len(arg)==3:
+            elif arg[0] == 'chown' and len(arg) in [3,4]:
                 name = arg[1]
                 owner = arg[2]
-                self._do_component_set_owner(name, owner)
+                if len(arg) == 4:
+                    qaowner = arg[3]
+                else:
+                    qaowner = owner
+                self._do_component_set_owner(name, owner, qaowner)
             else:    
                 self.do_help ('component')
         except Exception, e:
             print 'Component %s failed:' % arg[0], e
 
     def _do_component_list(self):
-        data = self.db_execsql('SELECT name, owner FROM component') 
-        self.print_listing(['Name', 'Owner'], data)
+        data = self.db_execsql('SELECT name, owner, qaowner FROM component') 
+        self.print_listing(['Name', 'Owner', 'QA Owner'], data)
 
-    def _do_component_add(self, name, owner):
-        data = self.db_execsql("INSERT INTO component VALUES('%s', '%s')"
-                               % (name, owner))
+    def _do_component_add(self, name, owner, qaowner):
+        data = self.db_execsql("INSERT INTO component VALUES('%s', '%s', '%s')"
+                               % (name,owner,qaowner))
 
     def _do_component_rename(self, name, newname):
         cnx = self.db_open()
@@ -349,6 +357,9 @@
         self.db_execsql("UPDATE ticket SET component='%s' WHERE component='%s'"
                         % (newname,name), cursor)
         cnx.commit()
+        if self.__env.get_config('ticket', 'default_component') == name:
+            self.__env.set_config('ticket', 'default_component', newname)
+            self.__env.save_config()
 
     def _do_component_remove(self, name):
         cnx = self.db_open()
@@ -359,16 +370,19 @@
             raise Exception("No such component '%s'" % name)
         data = self.db_execsql("DELETE FROM component WHERE name='%s'"
                                % (name))
+        if self.__env.get_config('ticket', 'default_component') == name:
+            self.__env.set_config('ticket', 'default_component', '')
+            self.__env.save_config()
 
-    def _do_component_set_owner(self, name, owner):
+    def _do_component_set_owner(self, name, owner, qaowner):
         cnx = self.db_open()
         cursor = cnx.cursor ()
         cursor.execute('SELECT name FROM component WHERE name=%s', name)
         data = cursor.fetchone()
         if not data:
             raise Exception("No such component '%s'" % name)
-        data = self.db_execsql("UPDATE component SET owner='%s' WHERE name='%s'"
-                               % (owner,name))
+        data = self.db_execsql("UPDATE component SET owner='%s', qaowner='%s' WHERE name='%s'"
+                               % (owner,qaowner,name))
 
 
     ## Permission
@@ -783,6 +797,11 @@
             raise Exception, "No such value '%s'" % name
         data = self.db_execsql("UPDATE enum SET name='%(newname)s'" 
                                " WHERE type='%(type)s' AND name='%(name)s'" % d)
+        if self.__env.get_config('ticket', 'default_' + type) == name:
+            self.__env.set_config('ticket', 'default_' + type, newname)
+            self.__env.save_config()
+        data = self.db_execsql("UPDATE ticket SET %(type)s='%(newname)s'"
+                               " WHERE %(type)s='%(name)s'" % d)
 
     def _do_enum_remove(self, type, name):
         data = self.db_execsql("SELECT name FROM enum" 
@@ -791,13 +810,21 @@
             raise Exception, "No such value '%s'" % name
         data = self.db_execsql("DELETE FROM enum WHERE type='%s' AND name='%s'"
                                % (type, name))
+        if self.__env.get_config('ticket', 'default_' + type) == name:
+            self.__env.set_config('ticket', 'default_' + type, '')
+            self.__env.save_config()
+        newname = self.__env.get_config('ticket', 'default_' + type)
+        d = {'name':name, 'newname':newname, 'type':type}
+        data = self.db_execsql("UPDATE ticket SET %(type)s='%(newname)s'"
+                               " WHERE %(type)s='%(name)s'" % d)
 
 
     ## Milestone
     _help_milestone = [('milestone list', 'Show milestones'),
-                       ('milestone add <name> [time]', 'Add milestone'),
+                       ('milestone add <name> [<owner> [time]]', 'Add milestone'),
                        ('milestone rename <name> <newname>',
                         'Rename milestone'),
+                       ('milestone chown <name> <newowner>', 'Change milestone owner'),
                        ('milestone time <name> <time>', 'Set milestone date (Format: "Jun 3, 2003")'),
                        ('milestone remove <name>', 'Remove milestone')]
 
@@ -805,14 +832,54 @@
 
         if begidx in [15,17]:
             comp = self.get_milestone_list ()
+        elif begidx > 15 and line.startswith('milestone chown '):
+            comp = self.get_user_list()
         elif begidx < 15:
-            comp = ['list','add','rename','time','remove']
+            comp = ['list','add','rename','chown','time','remove']
         return self.word_complete(text, comp)
 
     def do_milestone(self, line):
-        self._do_mile_ver('milestone', line)
+        type = 'milestone'
+        arg = self.arg_tokenize(line)
+        try:
+            if arg[0]  == 'list':
+                self._do_milestone_list()
+            elif arg[0] == 'add' and len(arg) in [2,3,4]:
+                name = arg[1]
+                self._do_mile_ver_add(type, name)
+                if len(arg) >= 3:
+                    owner = arg[2]
+                    self._do_mile_ver_chown(type, name, owner)
+                if len(arg) >= 4:
+                    time = arg[3]
+                    self._do_mile_ver_time(type, name, time)
+            elif arg[0] == 'rename' and len(arg)==3:
+                name = arg[1]
+                newname = arg[2]
+                self._do_mile_ver_rename(type, name, newname)
+            elif arg[0] == 'chown' and len(arg)==3:
+                name = arg[1]
+                owner = arg[2]
+                self._do_mile_ver_chown(type, name, owner)
+            elif arg[0] == 'time' and len(arg)==3:
+                name = arg[1]
+                time = arg[2]
+                self._do_mile_ver_time(type, name, time)
+            elif arg[0] == 'remove' and len(arg)==2:
+                name = arg[1]
+                self._do_mile_ver_remove(type, name)
+            else:
+                self.do_help (type)
+        except Exception, e:
+            print 'Command %s failed:' % arg[0], e
 
+    def _do_milestone_list(self):
+        data = self.db_execsql("SELECT name,owner,time FROM milestone ORDER BY time,name")
+        data = map(lambda x: (x[0], x[1], x[2] and time.strftime('%c', time.localtime(x[2]))), data)
+        #print data
+        self.print_listing(['Name', 'Owner', 'Time'], data)
 
+
     ## Version
     _help_version = [('version list', 'Show versions'),
                        ('version add <name> [time]', 'Add version'),
@@ -832,7 +899,7 @@
     def do_version(self, line):
         self._do_mile_ver('version', line)
 
-    # Milestone and Version are identical,  methods
+    # Milestone and Version are almost identical,  methods
 
     def _do_mile_ver(self, type, line):
         arg = self.arg_tokenize(line)
@@ -876,6 +943,11 @@
             raise Exception, "No such %s '%s'" % (type, name)
         data = self.db_execsql("UPDATE %(type)s SET name='%(newname)s'" 
                                " WHERE name='%(name)s'" % d)
+        if self.__env.get_config('ticket', 'default_' + type) == name:
+            self.__env.set_config('ticket', 'default_' + type, newname)
+            self.__env.save_config()
+        data = self.db_execsql("UPDATE ticket SET %(type)s='%(newname)s'"
+                               " WHERE %(type)s='%(name)s'" % d)
 
     def _do_mile_ver_add(self, type, name):
         sql = ("INSERT INTO %(type)s('name', 'time') "
@@ -891,6 +963,11 @@
             raise Exception, "No such %s '%s'" % (type, name)
         data = self.db_execsql("DELETE FROM %(type)s" 
                                " WHERE name='%(name)s'" % d)
+        if self.__env.get_config('ticket', 'default_' + type) == name:
+            self.__env.set_config('ticket', 'default_' + type, '')
+            self.__env.save_config()
+        data = self.db_execsql("UPDATE ticket SET %(type)s=''"
+                               " WHERE %(type)s='%(name)s'" % d)
 
     def _do_mile_ver_time(self, type, name, t):
         d = {'name':name, 'type':type}
@@ -923,6 +1000,10 @@
         else:
             print >> sys.stderr, 'Unknown time format'
 
+    def _do_mile_ver_chown(self, type, name, owner):
+        data = self.db_execsql("UPDATE %s SET owner='%s' WHERE name='%s'"
+                               % (type, owner, name));
+
     _help_upgrade = [('upgrade', 'Upgrade database to current version.')]
     def do_upgrade(self, line):
         arg = self.arg_tokenize(line)
Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(revision 1003)
+++ trac/db_default.py	(working copy)
@@ -21,7 +21,9 @@
 
 
 # Database version identifier. Used for automatic upgrades.
-db_version = 7
+db_version = 8
+# Current workflow version identifier
+current_workflow_version = 2
 
 def __mkreports(reps):
     """Utility function used to create report data in same syntax as the
@@ -125,12 +127,14 @@
 );
 CREATE TABLE component (
          name            text PRIMARY KEY,
-         owner           text
+         owner           text,
+         qaowner         text
 );
 CREATE TABLE milestone (
          id              integer PRIMARY KEY,
          name            text,
          time            integer,
+         owner           text,
          descr           text,
          UNIQUE(name)
 );
@@ -209,8 +213,8 @@
 """,
 """
 SELECT p.value AS __color__,
-   version AS __group__,
-   id AS ticket, summary, component, version, severity, 
+   (CASE WHEN IFNULL(version, '') = '' THEN 'Not Specified' ELSE 'Version ' || version END) AS __group__,
+   id AS ticket, summary, component, milestone, severity, 
    (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
    time AS created,
    changetime AS _changetime, description AS _description,
@@ -218,10 +222,10 @@
   FROM ticket t, enum p
   WHERE status IN ('new', 'assigned', 'reopened') 
 AND p.name = t.priority AND p.type = 'priority'
-  ORDER BY (version IS NULL),version, p.value, severity, time
+  ORDER BY (IFNULL(version, '') = '') DESC,version, p.value, severity, time
 """),
 #----------------------------------------------------------------------------
-('All Tickets by Milestone',
+('Active Tickets by Milestone',
 """
 This report shows how to color results by priority,
 while grouping results by milestone.
@@ -231,7 +235,7 @@
 """,
 """
 SELECT p.value AS __color__,
-   milestone||' Release' AS __group__,
+   (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__,
    id AS ticket, summary, component, version, severity, 
    (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
    time AS created,
@@ -240,7 +244,7 @@
   FROM ticket t, enum p
   WHERE status IN ('new', 'assigned', 'reopened') 
 AND p.name = t.priority AND p.type = 'priority'
-  ORDER BY (milestone IS NULL),milestone, p.value, severity, time
+  ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time
 """),
 #----------------------------------------------------------------------------
 ('Assigned, Active Tickets by Owner',
@@ -248,16 +252,15 @@
 List assigned tickets, group by ticket owner, sorted by priority.
 """,
 """
-
 SELECT p.value AS __color__,
-   owner AS __group__,
-   id AS ticket, summary, component, milestone, severity, time AS created,
+   (CASE WHEN IFNULL(owner, '') = '' THEN 'Not Assigned' ELSE owner END) AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, time AS created,
    changetime AS _changetime, description AS _description,
    reporter AS _reporter
   FROM ticket t,enum p
   WHERE status = 'assigned'
 AND p.name=t.priority AND p.type='priority'
-  ORDER BY owner, p.value, severity, time
+  ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time
 """),
 #----------------------------------------------------------------------------
 ('Assigned, Active Tickets by Owner (Full Description)',
@@ -268,7 +271,7 @@
 """
 SELECT p.value AS __color__,
    owner AS __group__,
-   id AS ticket, summary, component, milestone, severity, time AS created,
+   id AS ticket, summary, component, version, milestone, severity, time AS created,
    description AS _description_,
    changetime AS _changetime, reporter AS _reporter
   FROM ticket t, enum p
@@ -283,7 +286,7 @@
 """,
 """
 SELECT p.value AS __color__,
-   t.milestone AS __group__,
+   (CASE WHEN IFNULL(t.milestone, '') = '' THEN 'Not Assigned' ELSE t.milestone || ' Release' END) AS __group__,
    (CASE status 
       WHEN 'closed' THEN 'color: #777; background: #ddd; border-color: #ccc;'
       ELSE 
@@ -295,7 +298,7 @@
    time AS _time,reporter AS _reporter
   FROM ticket t,enum p
   WHERE p.name=t.priority AND p.type='priority'
-  ORDER BY (milestone IS NULL), milestone DESC, (status = 'closed'), 
+  ORDER BY (IFNULL(milestone, '') = '') DESC, milestone DESC, (status = 'closed'), 
         (CASE status WHEN 'closed' THEN modified ELSE -p.value END) DESC
 """),
 #----------------------------------------------------------------------------
@@ -308,12 +311,12 @@
 """
 SELECT p.value AS __color__,
    (CASE status WHEN 'assigned' THEN 'Assigned' ELSE 'Owned' END) AS __group__,
-   id AS ticket, summary, component, version, milestone,
+   id AS ticket, summary, component, status, version, milestone,
    severity, priority, time AS created,
    changetime AS _changetime, description AS _description,
    reporter AS _reporter
   FROM ticket t, enum p
-  WHERE t.status IN ('new', 'assigned', 'reopened') 
+  WHERE t.status <> 'closed' 
 AND p.name = t.priority AND p.type = 'priority' AND owner = '$USER'
   ORDER BY (status = 'assigned') DESC, p.value, milestone, severity, time
 """),
@@ -338,6 +341,173 @@
   WHERE status IN ('new', 'assigned', 'reopened') 
 AND p.name = t.priority AND p.type = 'priority'
   ORDER BY (owner = '$USER') DESC, p.value, milestone, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Open Tickets, Mine first',
+"""
+ * List all not closed tickets by priority.
+ * Show all tickets owned by the logged in user in a group first.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE owner 
+     WHEN '$USER' THEN 'My Tickets' 
+     ELSE 'Open Tickets' 
+    END) AS __group__,
+   id AS ticket, summary, component, status, version, milestone, severity, 
+   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status <> 'closed' 
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY (owner = '$USER') DESC, p.value, milestone, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Open Tickets by Version',
+"""
+ * List all not closed tickets by priority.
+ * Group results by version.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(version, '') = '' THEN 'Not Specified' ELSE 'Version ' || version END) AS __group__,
+   id AS ticket, summary, component, status, milestone, severity, 
+   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status <> 'closed'
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY (IFNULL(version, '') = '') desc,version, p.value, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Open Tickets by Milestone',
+"""
+ * List all not closed tickets by priority.
+ * Group results by milestone.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__,
+   id AS ticket, summary, component, status, version, severity, 
+   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status <> 'closed' 
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Open Tickets by Owner',
+"""
+List not closed tickets, group by ticket owner, sorted by priority.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(owner, '') = '' THEN 'Not Assigned' ELSE owner END) AS __group__,
+   id AS ticket, summary, component, status, version, milestone, severity,
+   time AS created, changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t,enum p
+  WHERE status <> 'closed'
+AND p.name=t.priority AND p.type='priority'
+  ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Open Tickets by Status',
+"""
+ * List all not closed tickets by priority.
+ * Group results by status.
+""",
+"""
+SELECT p.value AS __color__,
+   status AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum q, enum p
+  WHERE status <> 'closed' 
+AND q.name = t.status AND q.type = 'status'
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY q.value, p.value, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Resolved Tickets, Mine first',
+"""
+ * List all resolved tickets by priority.
+ * Show all tickets owned by the logged in user in a group first.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE owner 
+     WHEN '$USER' THEN 'My Tickets' 
+     ELSE 'Active Tickets' 
+    END) AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, 
+   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status = 'resolved' 
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY (owner = '$USER') DESC, p.value, milestone, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Resolved Tickets by Milestone',
+"""
+List resolved tickets, sorted by priority, grouped by milestone
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__,
+   id AS ticket, summary, component, version, severity, 
+   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status = 'resolved' 
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Resolved Tickets by Owner',
+"""
+List resolved tickets, group by ticket owner, sorted by priority.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(owner, '') = '' THEN 'Not Assigned' ELSE owner END) AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t,enum p
+  WHERE status = 'resolved'
+AND p.name=t.priority AND p.type='priority'
+  ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time
+"""),
+#----------------------------------------------------------------------------
+('Verified Tickets by Milestone (Full Description)',
+"""
+Release Notes: List verified and closed tickets, group by milestone, include description.
+""",
+"""
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__,
+   id AS ticket, summary, component, status, version, severity, time AS created,
+   description AS _description_,
+   changetime AS _changetime, reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status IN ('verified', 'closed')
+AND p.name = t.priority AND p.type = 'priority'
+  ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time
 """))
 
 
@@ -347,9 +517,9 @@
 
 # (table, (column1, column2), ((row1col1, row1col2), (row2col1, row2col2)))
 data = (('component',
-             ('name', 'owner'),
-               (('component1', 'somebody'),
-                ('component2', 'somebody'))),
+             ('name', 'owner', 'qaowner'),
+               (('component1', 'somebody', 'qasomebody'),
+                ('component2', 'somebody', 'qasomebody'))),
            ('milestone',
              ('name', 'time'),
                (('', 0), 
@@ -367,12 +537,15 @@
                (('status', 'new', 1),
                 ('status', 'assigned', 2),
                 ('status', 'reopened', 3),
-                ('status', 'closed', 4),
+                ('status', 'resolved', 4),
+                ('status', 'verified', 5),
+                ('status', 'closed', 6),
                 ('resolution', 'fixed', 1),
                 ('resolution', 'invalid', 2),
                 ('resolution', 'wontfix', 3),
                 ('resolution', 'duplicate', 4),
                 ('resolution', 'worksforme', 5),
+                ('resolution', 'moved', 6),
                 ('severity', 'blocker', 1),
                 ('severity', 'critical', 2),
                 ('severity', 'major', 3),
@@ -426,6 +599,7 @@
   ('project', 'footer',
    ' Visit the Trac open source project at<br />'
    '<a href="http://trac.edgewall.com/">http://trac.edgewall.com/</a>'),
+  ('ticket', 'workflow_version', str(current_workflow_version)),
   ('ticket', 'default_version', ''),
   ('ticket', 'default_severity', 'normal'),
   ('ticket', 'default_priority', 'normal'),
Index: trac/Milestone.py
===================================================================
--- trac/Milestone.py	(revision 1003)
+++ trac/Milestone.py	(working copy)
@@ -60,10 +60,10 @@
     if not group:
         queries['all_tickets'] = env.href.query({'milestone': milestone})
         queries['active_tickets'] = env.href.query({
-            'milestone': milestone, 'status': ['new', 'assigned', 'reopened']
+            'milestone': milestone, 'status': ['new', 'assigned', 'reopened', 'resolved']
         })
         queries['closed_tickets'] = env.href.query({
-            'milestone': milestone, 'status': 'closed'
+            'milestone': milestone, 'status': ['closed', 'verified']
         })
     else:
         queries['all_tickets'] = env.href.query({
@@ -71,17 +71,17 @@
         })
         queries['active_tickets'] = env.href.query({
             'milestone': milestone, grouped_by: group,
-            'status': ['new', 'assigned', 'reopened']
+            'status': ['new', 'assigned', 'reopened', 'resolved']
         })
         queries['closed_tickets'] = env.href.query({
             'milestone': milestone, grouped_by: group,
-            'status': 'closed'
+            'status': ['closed', 'verified']
         })
     return queries
 
 def calc_ticket_stats(tickets):
     total_cnt = len(tickets)
-    active = [ticket for ticket in tickets if ticket['status'] != 'closed']
+    active = [ticket for ticket in tickets if ticket['status'] != 'closed' and ticket['status'] != 'verified']
     active_cnt = len(active)
     closed_cnt = total_cnt - active_cnt
 
@@ -116,10 +116,11 @@
                 if datestr:
                     date = self.parse_date(datestr)
             descr = self.args.get('descr', '')
+            owner = self.args.get('owner', '')
             if not id:
-                self.create_milestone(name, date, descr)
+                self.create_milestone(name, date, descr, owner)
             else:
-                self.update_milestone(id, name, date, descr)
+                self.update_milestone(id, name, date, descr, owner)
         elif id:
             self.req.redirect(self.env.href.milestone(id))
         else:
@@ -141,15 +142,15 @@
                             'Invalid Date Format')
         return seconds
 
-    def create_milestone(self, name, date=0, descr=''):
+    def create_milestone(self, name, date=0, descr='', owner=''):
         self.perm.assert_permission(perm.MILESTONE_CREATE)
         if not name:
             raise TracError('You must provide a name for the milestone.',
                             'Required Field Missing')
         cursor = self.db.cursor()
         self.env.log.debug("Creating new milestone '%s'" % name)
-        cursor.execute("INSERT INTO milestone (id, name, time, descr) "
-                       "VALUES (NULL, %s, %d, %s)", name, date, descr)
+        cursor.execute("INSERT INTO milestone (id, name, time, descr, owner) "
+                       "VALUES (NULL, %s, %d, %s, %s)", name, date, descr, owner)
         self.db.commit()
         self.req.redirect(self.env.href.milestone(name))
 
@@ -166,12 +167,18 @@
                                       'milestone %s' % (id, target))
                     cursor.execute ('UPDATE ticket SET milestone = %s '
                                     'WHERE milestone = %s', target, id)
+                    if self.env.get_config('ticket', 'default_milestone') == id:
+                        self.env.set_config('ticket', 'default_milestone', target)
+                        self.env.save_config()
                 else:
                     self.env.log.info('Resetting milestone field of all '
                                       'tickets associated with milestone %s'
                                       % id)
                     cursor.execute ('UPDATE ticket SET milestone = NULL '
                                     'WHERE milestone = %s', id)
+                    if self.env.get_config('ticket', 'default_milestone') == id:
+                        self.env.set_config('ticket', 'default_milestone', '')
+                        self.env.save_config()
             self.env.log.debug('Deleting milestone %s' % id)
             cursor.execute("DELETE FROM milestone WHERE name = %s", id)
             self.db.commit()
@@ -179,7 +186,7 @@
         else:
             self.req.redirect(self.env.href.milestone(id))
 
-    def update_milestone(self, id, name, date, descr):
+    def update_milestone(self, id, name, date, descr, owner):
         self.perm.assert_permission(perm.MILESTONE_MODIFY)
         cursor = self.db.cursor()
         self.env.log.debug("Updating milestone '%s'" % id)
@@ -189,9 +196,13 @@
             cursor.execute('UPDATE ticket SET milestone = %s '
                             'WHERE milestone = %s', name, id)
             cursor.execute("UPDATE milestone SET name = %s, time = %d, "
-                           "descr = %s WHERE name = %s",
-                           name, date, descr, id)
+                           "descr = %s, owner = %s WHERE name = %s",
+                           name, date, descr, owner, id)
             self.db.commit()
+            if self.env.get_config('ticket', 'default_milestone') == id:
+                self.env.set_config('ticket', 'default_milestone', name)
+                self.env.save_config()
+
             self.req.redirect(self.env.href.milestone(name))
         else:
             self.req.redirect(self.env.href.milestone(id))
@@ -223,7 +234,7 @@
 
     def get_milestone(self, name):
         cursor = self.db.cursor()
-        cursor.execute("SELECT name, time, descr FROM milestone "
+        cursor.execute("SELECT name, time, descr, owner FROM milestone "
                        "WHERE name = %s ORDER BY time, name", name)
         row = cursor.fetchone()
         cursor.close()
@@ -238,6 +249,9 @@
         t = row['time'] and int(row['time'])
         if t > 0:
             milestone['date'] = time.strftime('%x', time.localtime(t))
+        owner = row['owner']
+        if not owner: owner = ''
+        milestone['owner'] = owner
         return milestone
 
     def render(self):
Index: trac/Query.py
===================================================================
--- trac/Query.py	(revision 1003)
+++ trac/Query.py	(working copy)
@@ -111,32 +111,36 @@
             if check:
                 del constraints[field]
 
-        def add_db_options(field, constraints, prefix, cursor, sql):
+        def add_db_options(field, constraints, prefix, cursor, sql, withnull=0):
             cursor.execute(sql)
             options = []
+            if withnull: options.append({'name': '(empty)'})
             while 1:
                 row = cursor.fetchone()
                 if not row: break
                 if row[0]: options.append({'name': row[0]})
             add_options(field, constraints, prefix, options)
 
-        add_options('status', constraints, 'query.options.',
-                    [{'name': 'new'}, {'name': 'assigned'},
-                     {'name': 'reopened'}, {'name': 'closed'}])
-        add_options('resolution', constraints, 'query.options.',
-                    [{'name': 'fixed'}, {'name': 'invalid'}, {'name': 'wontfix'},
-                     {'name': 'duplicate'}, {'name': 'worksforme'}])
         cursor = self.db.cursor()
+        if self.env.get_workflow_version() <= 1:
+            add_options('status', constraints, 'query.options.',
+                        [{'name': 'new'}, {'name': 'assigned'},
+                         {'name': 'reopened'}, {'name': 'closed'}])
+        else:
+            add_db_options('status', constraints, 'query.options.', cursor,
+                           'SELECT name FROM enum WHERE type=\'status\' ORDER BY value')
+        add_db_options('resolution', constraints, 'query.options.', cursor,
+                       'SELECT name FROM enum WHERE type=\'resolution\' ORDER BY value')
         add_db_options('component', constraints, 'query.options.', cursor,
-                       'SELECT name FROM component ORDER BY name', )
+                       'SELECT name FROM component ORDER BY name', 1)
         add_db_options('milestone', constraints, 'query.options.', cursor,
-                       'SELECT name FROM milestone ORDER BY name')
+                       'SELECT name FROM milestone ORDER BY name', 1)
         add_db_options('version', constraints, 'query.options.', cursor,
-                       'SELECT name FROM version ORDER BY name')
+                       'SELECT name FROM version ORDER BY name', 1)
         add_db_options('priority', constraints, 'query.options.', cursor,
-                       'SELECT name FROM enum WHERE type=\'priority\'')
+                       'SELECT name FROM enum WHERE type=\'priority\' ORDER BY value')
         add_db_options('severity', constraints, 'query.options.', cursor,
-                       'SELECT name FROM enum WHERE type=\'severity\'')
+                       'SELECT name FROM enum WHERE type=\'severity\' ORDER BY value')
 
         custom_fields = get_custom_fields(self.env)
         for custom in custom_fields:
@@ -147,6 +151,9 @@
                     options[i] = {'name': options[i]}
                     if check and (options[i]['name'] in constraints[custom['name']]):
                         options[i]['selected'] = 1
+                # FIXME: Don't know how to define "empty" queries for custom fields
+                #        Below line can be uncommented when these queries work
+                # options.insert(0, {'name': '(empty)'})
                 custom['options'] = options
         util.add_to_hdf(custom_fields, self.req.hdf, 'query.custom')
 
@@ -195,13 +202,20 @@
             col = k
             if not col in Ticket.std_fields:
                 col = 'value'
+            # Build clause array
+            if len(v) != 0 and v[0] == '(empty)':
+                # FIXME: Don't know how to define "empty" queries for custom fields
+                clause.append("IFNULL(%s, '') = ''" % col)
+                v.pop(0)
             if len(v) > 1:
                 inlist = ["'" + util.sql_escape(item) + "'" for item in v]
                 clause.append("%s IN (%s)" % (col, ", ".join(inlist)))
-            elif k in ['keywords', 'cc']:
-                clause.append("%s LIKE '%%%s%%'" % (col, util.sql_escape(v[0])))
-            else:
-                clause.append("%s = '%s'" % (col, util.sql_escape(v[0])))
+            elif len(v) != 0:
+                if k in ['keywords', 'cc']:
+                    clause.append("%s LIKE '%%%s%%'" % (col, util.sql_escape(v[0])))
+                else:
+                    clause.append("%s = '%s'" % (col, util.sql_escape(v[0])))
+            # Combine clause into clauses array
             if not k in Ticket.std_fields:
                 clauses.append("(name='%s' AND (" % k + " OR ".join(clause) + "))")
             else:
Index: trac/Timeline.py
===================================================================
--- trac/Timeline.py	(revision 1003)
+++ trac/Timeline.py	(working copy)
@@ -52,7 +52,9 @@
         REOPENED_TICKET = 4
         WIKI = 5
         MILESTONE = 6
-
+        VERIFIED_TICKET = 7
+        RESOLVED_TICKET = 8
+        
         q = []
         if changeset:
             q.append("SELECT time, rev AS idata, '' AS tdata, 1 AS type, "
@@ -69,17 +71,40 @@
                      "FROM ticket_change WHERE field='status' "
                      "AND newvalue='reopened' AND time>=%s AND 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"
-                     " FROM ticket_change t1"
-                     "   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"
-                     "     AND t1.ticket = t3.ticket AND t3.field = 'comment'"
-                     " WHERE t1.field = 'status' AND t1.newvalue = 'closed'"
-                     "   AND t2.field = 'resolution'"
-                     "   AND t1.time >= %s AND t1.time <= %s" % (start,stop))
+            if self.env.get_workflow_version() <= 1:
+                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"
+                         " FROM ticket_change t1"
+                         "   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"
+                         "     AND t1.ticket = t3.ticket AND t3.field = 'comment'"
+                         " WHERE t1.field = 'status' AND t1.newvalue = 'closed'"
+                         "   AND t2.field = 'resolution'"
+                         "   AND t1.time >= %s AND t1.time <= %s" % (start,stop))
+            else:
+                q.append("SELECT time, ticket AS idata, '' AS tdata, 3 AS type, "
+                         "'' AS message, author "
+                         "FROM ticket_change WHERE field='status' "
+                         "AND newvalue='closed' AND time>=%s AND time<=%s" %
+                         (start, stop))
+                q.append("SELECT time, ticket AS idata, '' AS tdata, 7 AS type, "
+                         "'' AS message, author "
+                         "FROM ticket_change WHERE field='status' "
+                         "AND newvalue='verified' AND oldvalue<>'closed' AND time>=%s AND time<=%s" %
+                         (start, stop))
+                q.append("SELECT t1.time AS time, t1.ticket AS idata,"
+                         "       t2.newvalue AS tdata, 8 AS type,"
+                         "       t3.newvalue AS message, t1.author AS author"
+                         " FROM ticket_change t1"
+                         "   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"
+                         "     AND t1.ticket = t3.ticket AND t3.field = 'comment'"
+                         " WHERE t1.field = 'status' AND t1.newvalue = 'resolved' AND t1.oldvalue NOT IN ('verified', 'closed')"
+                         "   AND t2.field = 'resolution'"
+                         "   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 "
@@ -87,7 +112,7 @@
                      (start, stop))
         if milestone:
             q.append("SELECT time, -1 AS idata, '' AS tdata, 6 AS type, "
-                     "name AS message, '' AS author " 
+                     "name AS message, owner AS author " 
                      "FROM milestone WHERE time>=%s AND time<=%s" %
                      (start, stop))
 
Index: trac/Report.py
===================================================================
--- trac/Report.py	(revision 1003)
+++ trac/Report.py	(working copy)
@@ -196,7 +196,11 @@
 
         if copy:
             title += ' copy'
-        self.req.hdf.setValue('title', 'Create New Report')
+
+        if action == 'commit':
+            self.req.hdf.setValue('title', 'Edit {%s} %s (report)' % (id, row['title']))
+        else:
+            self.req.hdf.setValue('title', 'Create New Report')
         self.req.hdf.setValue('report.mode', 'editor')
         self.req.hdf.setValue('report.title', title)
         self.req.hdf.setValue('report.id', str(id))
Index: trac/Roadmap.py
===================================================================
--- trac/Roadmap.py	(revision 1003)
+++ trac/Roadmap.py	(working copy)
@@ -48,14 +48,14 @@
             icalhref += '&show=all'
             self.req.hdf.setValue('roadmap.href.list',
                                    self.env.href.roadmap())
-            query = "SELECT name, time, descr FROM milestone " \
+            query = "SELECT name, time, descr, owner FROM milestone " \
                     "WHERE name != '' " \
                     "ORDER BY time DESC, name"
         else:
             self.req.hdf.setValue('roadmap.showall', '1')
             self.req.hdf.setValue('roadmap.href.list',
                                    self.env.href.roadmap('all'))
-            query = "SELECT name, time, descr FROM milestone " \
+            query = "SELECT name, time, descr, owner FROM milestone " \
                     "WHERE name != '' " \
                     "AND (time IS NULL OR time = 0 OR time > %d) " \
                     "ORDER BY time DESC, name" % time()
@@ -74,13 +74,16 @@
             milestone = {
                 'name': row['name'],
                 'href': self.env.href.milestone(row['name']),
-                'time': row['time'] and int(row['time'])
+                'time': row['time'] and int(row['time']),
             }
             descr = row['descr']
             if descr:
                 milestone['descr'] = wiki_to_html(descr, self.req.hdf,
                                                   self.env, self.db)
                 milestone['descr_text'] = descr
+            owner = row['owner']
+            if not owner: owner = ''
+            milestone['owner'] = owner
             if milestone['time'] > 0:
                 milestone['date'] = strftime('%x', localtime(milestone['time']))
                 self.milestones.insert(0, milestone)
@@ -115,7 +118,7 @@
             status = ticket['status']
             if status == 'new' or status == 'reopened' and not ticket['owner']:
                 return 'NEEDS-ACTION'
-            elif status == 'assigned' or status == 'reopened':
+            elif status != 'closed':
                 return 'IN-PROCESS'
             elif status == 'closed':
                 if ticket['resolution'] == 'fixed': return 'COMPLETED'
Index: trac/upgrades/db8.py
===================================================================
--- trac/upgrades/db8.py	(revision 0)
+++ trac/upgrades/db8.py	(revision 0)
@@ -0,0 +1,280 @@
+sql = """
+-- Add statuses 'resolved' and 'verified'
+UPDATE enum SET value = 6 WHERE type = 'status' AND name = 'closed';
+INSERT INTO enum (type, name, value) VALUES ('status', 'resolved', 4);
+INSERT INTO enum (type, name, value) VALUES ('status', 'verified', 5);
+
+-- Add resolution 'moved'
+INSERT INTO enum (type, name, value) VALUES ('resolution', 'moved', 6);
+
+-- Add QA Contact to 'component'
+CREATE TEMPORARY TABLE component_backup AS SELECT * FROM component;
+DROP TABLE component;
+CREATE TABLE component (
+         name            text PRIMARY KEY,
+         owner           text,
+         qaowner         text
+);
+INSERT INTO component SELECT name, owner, owner AS qaowner FROM component_backup;
+DROP TABLE component_backup;
+
+-- Add Release Manager Contact to 'milestone'
+CREATE TEMPORARY TABLE milestone_backup AS SELECT * FROM milestone;
+DROP TABLE milestone;
+CREATE TABLE milestone (
+         id              integer PRIMARY KEY,
+         name            text,
+         time            integer,
+         owner           text,
+         descr           text,
+         UNIQUE(name)
+);
+INSERT INTO milestone SELECT id, name, time, '' AS owner, descr FROM milestone_backup;
+DROP TABLE milestone_backup;
+
+-- Modify 'All Tickets by Version' report
+UPDATE report SET sql = '
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(version, '''') = '''' THEN ''Not Specified'' ELSE ''Version '' || version END) AS __group__,
+   id AS ticket, summary, component, milestone, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status IN (''new'', ''assigned'', ''reopened'') 
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (IFNULL(version, '''') = '''') DESC,version, p.value, severity, time
+' WHERE title = 'Active Tickets by Version';
+
+-- Modify 'All Tickets by Milestone' report
+UPDATE report SET sql = '
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__,
+   id AS ticket, summary, component, version, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status IN (''new'', ''assigned'', ''reopened'') 
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time
+', title = 'Active Tickets by Milestone'
+WHERE title = 'All Tickets by Milestone';
+
+-- Modify 'Assigned, Active Tickets by Owner' report
+UPDATE report SET sql = '
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(owner, '''') = '''' THEN ''Not Assigned'' ELSE owner END) AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t,enum p
+  WHERE status = ''assigned''
+AND p.name=t.priority AND p.type=''priority''
+  ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time
+' WHERE title = 'Assigned, Active Tickets by Owner';
+
+-- Modify 'Assigned, Active Tickets by Owner (Full Description)' report
+UPDATE report SET sql = '
+SELECT p.value AS __color__,
+   owner AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, time AS created,
+   description AS _description_,
+   changetime AS _changetime, reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status = ''assigned''
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY owner, p.value, severity, time
+' WHERE title = 'Assigned, Active Tickets by Owner (Full Description)';
+
+-- Modify 'All Tickets By Milestone  (Including closed)' report
+UPDATE report SET sql = '
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(t.milestone, '''') = '''' THEN ''Not Assigned'' ELSE t.milestone || '' Release'' END) AS __group__,
+   (CASE status 
+      WHEN ''closed'' THEN ''color: #777; background: #ddd; border-color: #ccc;''
+      ELSE 
+        (CASE owner WHEN ''$USER'' THEN ''font-weight: bold'' END)
+    END) AS __style__,
+   id AS ticket, summary, component, status, 
+   resolution,version, severity, priority, owner,
+   changetime AS modified,
+   time AS _time,reporter AS _reporter
+  FROM ticket t,enum p
+  WHERE p.name=t.priority AND p.type=''priority''
+  ORDER BY (IFNULL(milestone, '''') = '''') DESC, milestone DESC, (status = ''closed''), 
+        (CASE status WHEN ''closed'' THEN modified ELSE -p.value END) DESC
+' WHERE title = 'All Tickets By Milestone  (Including closed)';
+
+-- Modify 'My Tickets' report
+UPDATE report SET sql = '
+SELECT p.value AS __color__,
+   (CASE status WHEN ''assigned'' THEN ''Assigned'' ELSE ''Owned'' END) AS __group__,
+   id AS ticket, summary, component, status, version, milestone,
+   severity, priority, time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE t.status <> ''closed'' 
+AND p.name = t.priority AND p.type = ''priority'' AND owner = ''$USER''
+  ORDER BY (status = ''assigned'') DESC, p.value, milestone, severity, time
+' WHERE title = 'My Tickets';
+
+-- New reports
+
+INSERT INTO report VALUES(NULL,NULL,'Open Tickets, Mine first','
+SELECT p.value AS __color__,
+   (CASE owner 
+     WHEN ''$USER'' THEN ''My Tickets'' 
+     ELSE ''Open Tickets'' 
+    END) AS __group__,
+   id AS ticket, summary, component, status, version, milestone, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status <> ''closed'' 
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (owner = ''$USER'') DESC, p.value, milestone, severity, time
+','
+ * List all not closed tickets by priority.
+ * Show all tickets owned by the logged in user in a group first.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Version','
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(version, '''') = '''' THEN ''Not Specified'' ELSE ''Version '' || version END) AS __group__,
+   id AS ticket, summary, component, status, milestone, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status <> ''closed''
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (IFNULL(version, '''') = '''') desc,version, p.value, severity, time
+','
+ * List all not closed tickets by priority.
+ * Group results by version.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Milestone','
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__,
+   id AS ticket, summary, component, status, version, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status <> ''closed'' 
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time
+','
+ * List all not closed tickets by priority.
+ * Group results by milestone.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Owner','
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(owner, '''') = '''' THEN ''Not Assigned'' ELSE owner END) AS __group__,
+   id AS ticket, summary, component, status, version, milestone, severity,
+   time AS created, changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t,enum p
+  WHERE status <> ''closed''
+AND p.name=t.priority AND p.type=''priority''
+  ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time
+','
+List not closed tickets, group by ticket owner, sorted by priority.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Status','
+SELECT p.value AS __color__,
+   status AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum q, enum p
+  WHERE status <> ''closed'' 
+AND q.name = t.status AND q.type = ''status''
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY q.value, p.value, severity, time
+','
+ * List all not closed tickets by priority.
+ * Group results by status.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets, Mine first','
+SELECT p.value AS __color__,
+   (CASE owner 
+     WHEN ''$USER'' THEN ''My Tickets'' 
+     ELSE ''Active Tickets'' 
+    END) AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status = ''resolved'' 
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (owner = ''$USER'') DESC, p.value, milestone, severity, time
+','
+ * List all resolved tickets by priority.
+ * Show all tickets owned by the logged in user in a group first.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets by Milestone','
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__,
+   id AS ticket, summary, component, version, severity, 
+   (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner,
+   time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status = ''resolved'' 
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time
+','
+List resolved tickets, sorted by priority, grouped by milestone
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets by Owner','
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(owner, '''') = '''' THEN ''Not Assigned'' ELSE owner END) AS __group__,
+   id AS ticket, summary, component, version, milestone, severity, time AS created,
+   changetime AS _changetime, description AS _description,
+   reporter AS _reporter
+  FROM ticket t,enum p
+  WHERE status = ''resolved''
+AND p.name=t.priority AND p.type=''priority''
+  ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time
+','
+List resolved tickets, group by ticket owner, sorted by priority.
+');
+
+INSERT INTO report VALUES(NULL,NULL,'Verified Tickets by Milestone (Full Description)','
+SELECT p.value AS __color__,
+   (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__,
+   id AS ticket, summary, component, status, version, severity, time AS created,
+   description AS _description_,
+   changetime AS _changetime, reporter AS _reporter
+  FROM ticket t, enum p
+  WHERE status IN (''verified'', ''closed'')
+AND p.name = t.priority AND p.type = ''priority''
+  ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time
+','
+Release Notes: List verified and closed tickets, group by milestone, include description.
+');
+"""
+
+def do_upgrade(env, ver, cursor):
+    cursor.execute(sql)
+    env.set_config('ticket', 'workflow_version', '1')
+    env.save_config()
Index: trac/Environment.py
===================================================================
--- trac/Environment.py	(revision 1003)
+++ trac/Environment.py	(working copy)
@@ -163,6 +163,9 @@
         row = cursor.fetchone()
         return row and int(row[0])
 
+    def get_workflow_version(self):
+        return int(self.get_config('ticket', 'workflow_version', str(db_default.current_workflow_version)))
+
     def load_config(self):
         self.cfg = ConfigParser.ConfigParser()
         self.cfg.read(os.path.join(self.path, 'conf', 'trac.ini'))
Index: trac/Notify.py
===================================================================
--- trac/Notify.py	(revision 1003)
+++ trac/Notify.py	(working copy)
@@ -176,6 +176,10 @@
         NotifyEmail.__init__(self, env, self.template_name)
 
     def notify(self, ticket, newticket=1, modtime=0):
+        enabled = self.env.get_config('notification', 'smtp_enabled', '0')
+        if not enabled.lower() in TRUE:
+            return
+
         self.ticket = ticket
         self.modtime = modtime
         self.newticket = newticket
Index: trac/Ticket.py
===================================================================
--- trac/Ticket.py	(revision 1003)
+++ trac/Ticket.py	(working copy)
@@ -37,7 +37,7 @@
 class Ticket(UserDict):
     std_fields = ['time', 'component', 'severity', 'priority', 'milestone',
                   'reporter', 'owner', 'cc', 'url', 'version', 'status', 'resolution',
-                  'keywords', 'summary', 'description', 'reporter']
+                  'keywords', 'summary', 'description']
 
     def __init__(self, *args):
         UserDict.__init__(self)
@@ -93,6 +93,35 @@
             if not dict.has_key(name):
                 self[name] = '0'
 
+    def calc_owner(self, db):
+        """Calculate owner field if the owner is not specified or empty.
+           It depends on ticket.status, component.owner, component.qaowner, and
+           milestone.owner"""
+        cursor = db.cursor()
+        status = self.get('status', 'new')
+        component = self.get('component')
+        milestone = self.get('milestone')
+
+        # 1. The owner field defaults to the component owner for active tickets
+        if self.get('owner', '') == '' and component and status in ['new', 'reopened']:
+            cursor.execute('SELECT owner FROM component WHERE name=%s', component)
+            newowner = cursor.fetchone()[0]
+            if newowner: self['owner'] = newowner
+        # 2. The owner field defaults to component QA owner for testing tickets
+        if self.get('owner', '') == '' and component and status == 'resolved':
+            cursor.execute('SELECT qaowner FROM component WHERE name=%s', component)
+            newowner = cursor.fetchone()[0]
+            if newowner: self['owner'] = newowner
+        # 3. The owner field defaults to milestone owner for open tickets
+        if self.get('owner', '') == '' and milestone and status != 'closed':
+            cursor.execute('SELECT owner FROM milestone WHERE name=%s', milestone)
+            newowner = cursor.fetchone()[0]
+            if newowner: self['owner'] = newowner
+        # 4. The owner field defaults to reporter for verified tickets
+        if self.get('owner', '') == '' and status == 'verified':
+            reporter = self.get('reporter', '')
+            if reporter: self['owner'] = reporter
+
     def insert(self, db):
         """Add ticket to database"""
         cursor = db.cursor()
@@ -132,6 +161,7 @@
 
         # If the component is changed on a 'new' ticket then owner field
         # is updated accordingly. (#623).
+        owner_updated = 0
         if self['status'] == 'new' and self._old.has_key('component') and \
                not self._old.has_key('owner'):
             cursor.execute('SELECT owner FROM component '
@@ -140,8 +170,22 @@
             if self['owner'] == old_owner:
                 cursor.execute('SELECT owner FROM component '
                                'WHERE name=%s', self['component'])
-                self['owner'] = cursor.fetchone()[0]
-           
+                newowner = cursor.fetchone()[0]
+                if newowner:
+                    self['owner'] = newowner
+                    owner_updated = 1
+        if owner_updated == 0 and self['status'] == 'new' and \
+               self._old.has_key('milestone') and not self._old.has_key('owner'):
+            cursor.execute('SELECT owner FROM milestone '
+                           'WHERE name=%s', self._old['milestone'])
+            old_owner = cursor.fetchone()[0]
+            if self['owner'] == old_owner:
+                cursor.execute('SELECT owner FROM milestone '
+                               'WHERE name=%s', self['milestone'])
+                newowner = cursor.fetchone()[0]
+                if newowner:
+                    self['owner'] = newowner
+                    owner_updated = 1
 
         for name in self._old.keys():
             if name[:7] == 'custom_':
@@ -261,15 +305,8 @@
         ticket = Ticket()
         ticket.populate(self.args)
         ticket.setdefault('reporter',self.req.authname)
+        ticket.calc_owner(self.db)
 
-        # The owner field defaults to the component owner
-        cursor = self.db.cursor()
-        if ticket.get('component') and ticket.get('owner', '') == '':
-            cursor.execute('SELECT owner FROM component '
-                           'WHERE name=%s', ticket['component'])
-            owner = cursor.fetchone()[0]
-            ticket['owner'] = owner
-
         tktid = ticket.insert(self.db)
 
         # Notify
@@ -304,6 +341,7 @@
                                                self.req.hdf, self.env, self.db))
 
         self.req.hdf.setValue('title', 'New Ticket')
+        self.req.hdf.setValue('workflow_version', str(self.env.get_workflow_version()))
         evals = util.mydict(zip(ticket.keys(),
                                 map(lambda x: util.escape(x), ticket.values())))
         util.add_to_hdf(evals, self.req.hdf, 'newticket')
@@ -336,12 +374,35 @@
 
         # TODO: this should not be hard-coded like this
         action = self.args.get('action', None)
-        if action == 'accept':
-            ticket['status'] =  'assigned'
-            ticket['owner'] = self.req.authname
-        if action == 'resolve':
+        wv = self.env.get_workflow_version()
+        if wv == 2 and action == 'verify2':
+            ticket['status'] = 'verified'
+            ticket['owner'] = ''
+        elif wv == 2 and action == 'close2':
             ticket['status'] = 'closed'
+        elif wv == 2 and action == 'retest2':
+            ticket['status'] = 'resolved'
+            ticket['owner'] = ''
+        elif wv == 2 and action == 'resolve':
             ticket['resolution'] = self.args.get('resolve_resolution')
+            ticket['status'] = 'resolved'
+            ticket['owner'] = ''
+        elif wv == 2 and action == 'reassign':
+            newowner = self.args.get('reassign_owner')
+            if ticket['owner'] != newowner:
+                ticket['owner'] = newowner
+                if ticket['status'] == 'assigned': ticket['status'] = 'new'
+        elif wv == 2 and action == 'reopen':
+            ticket['status'] = 'reopened'
+            ticket['resolution'] = ''
+            ticket['owner'] = ''
+        elif wv in [1,2] and action == 'accept':
+            if self.req.authname:
+                ticket['status'] =  'assigned'
+                ticket['owner'] = self.req.authname
+        elif action == 'resolve':
+            ticket['status'] = 'closed'
+            ticket['resolution'] = self.args.get('resolve_resolution')
         elif action == 'reassign':
             ticket['owner'] = self.args.get('reassign_owner')
             ticket['status'] = 'new'
@@ -350,6 +411,7 @@
             ticket['resolution'] = ''
 
         ticket.populate(self.args)
+        ticket.calc_owner(self.db)
 
         now = int(time.time())
 
@@ -374,14 +436,18 @@
                         self.req.hdf, 'ticket.milestones')
         util.sql_to_hdf(self.db, 'SELECT name FROM version ORDER BY name',
                         self.req.hdf, 'ticket.versions')
+        util.sql_to_hdf(self.db, "SELECT name FROM enum WHERE type='resolution' ORDER BY value",
+                        self.req.hdf, 'enums.resolution')
         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.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')
 
         self.req.hdf.setValue('ticket.reporter_id', util.escape(reporter_id))
         self.req.hdf.setValue('title', '#%d (%s)' % (id,ticket['summary']))
+        self.req.hdf.setValue('workflow_version', str(self.env.get_workflow_version()))
         self.req.hdf.setValue('ticket.description.formatted',
                               wiki_to_html(ticket['description'], self.req.hdf,
                                            self.env, self.db))
@@ -423,7 +489,9 @@
         id = int(self.args.get('id'))
 
         if not preview \
-               and action in ['leave', 'accept', 'reopen', 'resolve', 'reassign']:
+               and (action in ['leave', 'accept', 'reopen', 'resolve', 'reassign'] \
+                    or self.env.get_workflow_version() >= 2 \
+                       and action in ['verify2', 'close2', 'retest2']):
             self.save_changes (id)
 
         ticket = Ticket(self.db, id)
Index: trac/WikiFormatter.py
===================================================================
--- trac/WikiFormatter.py	(revision 1003)
+++ trac/WikiFormatter.py	(working copy)
@@ -125,7 +125,7 @@
             elif row[1] == 'closed':
                 return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number)
             else:
-                return '<a href="%s" title="%s">#%d</a>' % (self._href.ticket(number), summary, number)
+                return '<a href="%s" title="%s : %s">#%d</a>' % (self._href.ticket(number), row[1].upper(), summary, number)
 
     def _changesethref_formatter(self, match, fullmatch):
         number = int(match[1:-1])
@@ -158,7 +158,7 @@
 		elif row[1] == 'closed':
 		    return self._href.ticket(args), '<del>%s:%s</del>' % (module, args), 0, 'CLOSED: ' + summary
 		else:
-		    return self._href.ticket(args), '%s:%s' % (module, args), 0, summary
+		    return self._href.ticket(args), '%s:%s' % (module, args), 0, row[1].upper() + ': ' + summary
 	    else:
 		return self._href.ticket(args), '%s:%s' % (module, args), 1, ''
         elif module == 'wiki':
Index: templates/report.cs
===================================================================
--- templates/report.cs	(revision 1003)
+++ templates/report.cs	(working copy)
@@ -175,7 +175,7 @@
 
  <?cs elif report.mode == "delete" ?>
 
-  <h1 id="report-hdr">Delete Report</h1> 
+  <h1 id="report-hdr">Delete Report {<?cs var:report.id ?>}: <?cs var:report.title ?></h1> 
   <form action="<?cs var:cgi_location ?>" method="post">
    <input type="hidden" name="mode" value="report" />   <input type="hidden" name="id" value="<?cs var:report.id ?>" />   <input type="hidden" name="action" value="confirm_delete" />
    <p><strong>Are you sure you want to delete this report?</strong></p>
@@ -187,7 +187,11 @@
  
  <?cs elif report.mode == "editor" ?>
  
-   <h1 id="report-hdr">Create New Report</h1>
+   <h1 id="report-hdr">
+     <?cs if report.action == "commit" ?>Edit Report {<?cs var:report.id ?>}: <?cs var:report.title ?>
+     <?cs else ?>Create New Report
+     <?cs /if ?>
+   </h1>
    
    <form action="<?cs var:cgi_location ?>" method="post">
      <div>
Index: templates/ticket.cs
===================================================================
--- templates/ticket.cs	(revision 1003)
+++ templates/ticket.cs	(working copy)
@@ -22,7 +22,8 @@
 <div id="ticket">
  <div class="date"><?cs var:ticket.opened ?></div>
  <h1>Ticket #<?cs var:ticket.id ?> <?cs
- if:ticket.status == 'closed' ?>(Closed: <?cs var:ticket.resolution ?>)<?cs
+ if:ticket.status == 'closed' || ticket.status == 'verified' || ticket.status == 'resolved' ?>
+  (<?cs var:ticket.status ?>: <?cs var:ticket.resolution ?>)<?cs
  elif:ticket.status != 'new' ?>(<?cs var:ticket.status ?>)<?cs
  /if ?></h1>
  <h2><?cs var:ticket.summary ?></h2>
@@ -208,35 +209,39 @@
   /def ?>
   <?cs call:action_radio('leave') ?>
   <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs
-  if $ticket.status == "new" ?>
+  if $ticket.status == "new" || $workflow_version == "2" && $ticket.status == "reopened" ?>
    <?cs call:action_radio('accept') ?>
    <label for="accept">accept ticket</label><br /><?cs
   /if ?><?cs
-  if $ticket.status == "closed" ?>
-   <?cs call:action_radio('reopen') ?>
-   <label for="reopen">reopen ticket</label><br /><?cs
-  /if ?><?cs
   if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?>
    <?cs call:action_radio('resolve') ?>
    <label for="resolve">resolve</label>
    <label for="resolve_resolution">as:</label>
-   <select id="resolve_resolution" name="resolve_resolution">
-    <option<?cs
-     if:args.resolve_resolution == 'fixed' ?> selected="selected"<?cs
-     /if ?>>fixed</option>
-    <option<?cs
-     if:args.resolve_resolution == 'invalid' ?> selected="selected"<?cs
-     /if ?>>invalid</option>
-    <option<?cs
-     if:args.resolve_resolution == 'wontfix' ?> selected="selected"<?cs
-     /if ?>>wontfix</option>
-    <option<?cs
-     if:args.resolve_resolution == 'duplicate' ?> selected="selected"<?cs
-     /if ?>>duplicate</option>
-    <option<?cs
-     if:args.resolve_resolution == 'worksforme' ?> selected="selected"<?cs
-     /if ?>>worksforme</option>
-   </select><br />
+   <?cs call:hdf_select(enums.resolution, "resolve_resolution", args.resolve_resolution) ?><br /><?cs
+  /if ?>
+
+  <?cs if $workflow_version == "2" && $ticket.status == "resolved" ?>
+   <?cs call:action_radio('verify2') ?>
+   <label for="verify2">verify ticket</label><br /><?cs
+  /if ?>
+
+  <?cs if $workflow_version == "2" && ($ticket.status == "resolved" || $ticket.status == "verified") ?>
+   <?cs call:action_radio('close2') ?>
+   <label for="close2">close ticket</label><br /><?cs
+  /if ?>
+
+  <?cs if $ticket.status == "closed" || $workflow_version == "2" && ($ticket.status == "verified" || $ticket.status == "resolved") ?>
+   <?cs call:action_radio('reopen') ?>
+   <label for="reopen">reopen ticket</label><br /><?cs
+  /if ?>
+
+  <?cs if $workflow_version == "2" && ($ticket.status == "verified" || $ticket.status == "closed") ?>
+   <?cs call:action_radio('retest2') ?>
+   <label for="retest2">retest ticket</label><br /><?cs
+  /if ?>
+
+  <?cs
+  if $ticket.status != "closed" ?>
    <?cs call:action_radio('reassign') ?>
    <label for="reassign">reassign</label>
    <label for="reassign_owner">to:</label>
@@ -244,21 +249,31 @@
      if:args.reassign_to ?><?cs var:args.reassign_to ?><?cs
      else ?><?cs var:trac.authname ?><?cs /if ?>" /><?cs
   /if ?><?cs
-  if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?>
+  if $ticket.status != "closed" ?>
    <script type="text/javascript">
-     var resolve = document.getElementById("resolve");
+     <?cs if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?>
+       var resolve = document.getElementById("resolve");<?cs /if ?>
      var reassign = document.getElementById("reassign");
      var updateActionFields = function() {
-       enableControl('resolve_resolution', resolve.checked);
+       <?cs if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?>
+         enableControl('resolve_resolution', resolve.checked);<?cs /if ?>
        enableControl('reassign_owner', reassign.checked);
      };
      addEvent(window, 'load', updateActionFields);
-     addEvent(document.getElementById("leave"), 'click', updateActionFields);<?cs
-    if $ticket.status == "new" ?>
-     addEvent(document.getElementById("accept"), 'click', updateActionFields);<?cs
-    /if ?>
-    addEvent(resolve, 'click', updateActionFields);
-    addEvent(reassign, 'click', updateActionFields);
+     addEvent(document.getElementById("leave"), 'click', updateActionFields);
+     <?cs if $ticket.status == "new" || $workflow_version == "2" && $ticket.status == "reopened" ?>
+       addEvent(document.getElementById("accept"), 'click', updateActionFields);<?cs /if ?>
+     <?cs if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?>
+       addEvent(resolve, 'click', updateActionFields);<?cs /if ?>
+     <?cs if $workflow_version == "2" && $ticket.status == "resolved" ?>
+       addEvent(document.getElementById("verify2"), 'click', updateActionFields);<?cs /if ?>
+     <?cs if $workflow_version == "2" && ($ticket.status == "resolved" || $ticket.status == "verified") ?>
+       addEvent(document.getElementById("close2"), 'click', updateActionFields);<?cs /if ?>
+     <?cs if $ticket.status == "closed" || $workflow_version == "2" && ($ticket.status == "verified" || $ticket.status == "resolved") ?>
+       addEvent(document.getElementById("reopen"), 'click', updateActionFields);<?cs /if ?>
+     <?cs if $workflow_version == "2" && ($ticket.status == "verified" || $ticket.status == "closed") ?>
+       addEvent(document.getElementById("retest2"), 'click', updateActionFields);<?cs /if ?>
+     addEvent(reassign, 'click', updateActionFields);
    </script><?cs
   /if ?>
  </fieldset>
Index: templates/roadmap.cs
===================================================================
--- templates/roadmap.cs	(revision 1003)
+++ templates/roadmap.cs	(working copy)
@@ -22,6 +22,7 @@
       var:milestone.name ?></em></a></h2>
     <p class="date"><?cs if:milestone.date ?>
      <?cs var:milestone.date ?><?cs else ?>No date set<?cs /if ?>
+     <?cs if:milestone.owner ?>&nbsp;(<?cs var:milestone.owner ?>)<?cs /if ?>
     </p>
     <?cs with:stats = milestone.stats ?>
      <?cs if:#stats.total_tickets > #0 ?>
@@ -33,7 +34,7 @@
        <dt>Active tickets:</dt>
        <dd><a href="<?cs var:milestone.queries.active_tickets ?>"><?cs
          var:stats.active_tickets ?></a></dd>
-       <dt>Resolved tickets:</dt>
+       <dt>Completed tickets:</dt>
        <dd><a href="<?cs var:milestone.queries.closed_tickets ?>"><?cs
          var:stats.closed_tickets ?></a></dd>
       </dl>
Index: templates/timeline_rss.cs
===================================================================
--- templates/timeline_rss.cs	(revision 1003)
+++ templates/timeline_rss.cs	(working copy)
@@ -43,12 +43,20 @@
                              $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #3
         ?><!-- Closed ticket --> <?cs call:rss_item('Ticket',
-                             'Ticket #'+$item.idata+' resolved: '+$item.shortmsg,
+                             'Ticket #'+$item.idata+' closed: '+$item.shortmsg,
                              $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #4 
         ?><!-- Reopened ticket --><?cs call:rss_item('Ticket',
                              '#'+$item.idata+' reopened: '+$item.shortmsg,
                              $item.href, $item.msg_escwiki) 
+        ?><?cs elif:item.type == #7 
+        ?><!-- Verified ticket --><?cs call:rss_item('Ticket',
+                             '#'+$item.idata+' verified: '+$item.shortmsg,
+                             $item.href, $item.msg_escwiki) 
+        ?><?cs elif:item.type == #8
+        ?><!-- Resolved ticket --> <?cs call:rss_item('Ticket',
+                             'Ticket #'+$item.idata+' resolved: '+$item.shortmsg,
+                             $item.href, $item.msg_escwiki) 
         ?><?cs elif:item.type == #5 
         ?><!-- Wiki change --><?cs call:rss_item('Wiki',
                              $item.tdata+" page edited.",
Index: templates/milestone.cs
===================================================================
--- templates/milestone.cs	(revision 1003)
+++ templates/milestone.cs	(working copy)
@@ -56,6 +56,11 @@
       var:milestone.name ?>" />
    </div>
    <div class="field">
+    <label for="owner">Owner of the milestone (Release Manager):</label><br />
+    <input type="text" id="owner" name="owner" size="32" value="<?cs
+      var:milestone.owner ?>" />
+   </div>
+   <div class="field">
     <label for="datemode">Completion date:</label><br />
     <select name="datemode" id="datemode"
         onchange="enableControl('date',this.value=='manual');
@@ -108,6 +113,7 @@
  <?cs else ?>
   <em class="date"><?cs if:milestone.date ?>
    <?cs var:milestone.date ?><?cs else ?>No date set<?cs /if ?>
+   <?cs if:milestone.owner ?>&nbsp;(<?cs var:milestone.owner ?>)<?cs /if ?>
   </em>
   <div class="descr"><?cs var:milestone.descr ?></div>
  <?cs /if ?>
@@ -120,10 +126,10 @@
   <thead><tr>
    <th class="name" rowspan="2"><?cs var:milestone.stats.grouped_by ?></th>
    <th class="tickets" scope="col" colspan="2">Tickets</th>
-   <th class="progress" rowspan="2">Percent Resolved</th>
+   <th class="progress" rowspan="2">Percent Completed</th>
   </tr><tr>
    <th class="open" scope="col">Active</th>
-   <th class="closed" scope="col">Resolved</th>
+   <th class="closed" scope="col">Completed</th>
   </tr></thead>
   <?cs if:len(milestone.stats.groups) ?><tbody>
    <?cs each:group = milestone.stats.groups ?>
Index: templates/query.cs
===================================================================
--- templates/query.cs	(revision 1003)
+++ templates/query.cs	(working copy)
@@ -48,8 +48,19 @@
    <script type="text/javascript">
      var status = document.getElementById("status");
      var updateResolution = function() {
-       enableControl('resolution', status.selectedIndex == -1 ||
-                                   status.options[3].selected);
+       var bEnable = true
+       if (status.selectedIndex != -1) {
+         for (i=0; i < status.options.length; ++i) {
+           if (status.options[i].selected &&
+               (status.options[i].text == 'new'   ||
+                status.options[i].text == 'assigned' ||
+                status.options[i].text == 'reopened')) {
+             bEnable = false
+             break
+           }
+         }
+       }
+       enableControl('resolution', bEnable);
      };
      addEvent(window, 'load', updateResolution);
      addEvent(status, 'change', updateResolution);
Index: templates/timeline.cs
===================================================================
--- templates/timeline.cs	(revision 1003)
+++ templates/timeline.cs	(working copy)
@@ -78,11 +78,23 @@
    <?cs set:imessage = '' ?>
   <?cs /if ?>
   <?cs call:tlitem(item.href, 'closedticket',
-    'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, 
+    'Ticket <em>#'+$item.idata+'</em> closed by '+$item.author, 
     $item.tdata+$imessage) ?>
  <?cs elif:item.type == #4 ?><!-- Reopened ticket -->
   <?cs call:tlitem(item.href, 'newticket',
     'Ticket <em>#'+$item.idata+'</em> reopened by '+$item.author, '') ?>
+ <?cs elif:item.type == #7 ?><!-- Verified ticket -->
+  <?cs call:tlitem(item.href, 'closedticket',
+    'Ticket <em>#'+$item.idata+'</em> verified by '+$item.author, '') ?>
+ <?cs elif:item.type == #8 ?><!-- Resolved ticket -->
+  <?cs if:item.message ?>
+   <?cs set:imessage = ' - ' + $item.message ?>
+  <?cs else ?>
+   <?cs set:imessage = '' ?>
+  <?cs /if ?>
+  <?cs call:tlitem(item.href, 'resolvedticket',
+    'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, 
+    $item.tdata+$imessage) ?>
  <?cs elif:item.type == #5 ?><!-- Wiki change -->
   <?cs call:tlitem(item.href, 'wiki',
     '<em>'+$item.tdata+'</em> edited by '+$item.author, item.message) ?>

