Index: wiki-default/TracIni
===================================================================
--- wiki-default/TracIni	(revision 1120)
+++ wiki-default/TracIni	(working copy)
@@ -32,6 +32,7 @@
 || default_priority  || Default priority for newly created tickets ||
 || default_milestone || Default milestone for newly created tickets ||
 || default_component || Default component for newly created tickets ||
+|| restrict_owner    || Select ticket owner from combo box that lists all logged users ||
 
 See also: TracTicketsCustomFields
 
@@ -39,11 +40,12 @@
 || max_size || Maximum allowed file size for ticket and wiki attachments ||
 
 == [notification] ==
-|| smtp_enabled   || Enable SMTP (email) notification (true, false) ||
-|| smtp_server    || SMTP server to use for email notifications ||
-|| smtp_from      || Sender address to use in notification emails ||
-|| smtp_replyto   || Reply-To address to use in notification emails ||
-|| smtp_always_cc || Email address(es) to always send notifications to ||
+|| smtp_enabled           || Enable SMTP (email) notification (true, false) ||
+|| smtp_server            || SMTP server to use for email notifications ||
+|| smtp_from              || Sender address to use in notification emails ||
+|| smtp_replyto           || Reply-To address to use in notification emails ||
+|| smtp_always_cc         || Email address(es) to always send notifications to ||
+|| always_notify_owner    || Always send notifications to ticket owners ||
 || always_notify_reporter || Always send notifications to any address in the ''reporter'' field ||
 
 See also: TracNotification
Index: wiki-default/TracNotification
===================================================================
--- wiki-default/TracNotification	(revision 1120)
+++ wiki-default/TracNotification	(working copy)
@@ -21,6 +21,7 @@
  * '''smtp_from''': Email address to use for ''Sender''-headers in notification emails.
  * '''smtp_replyto''': Email address to use for ''Reply-To''-headers in notification emails.
  * '''smtp_always_cc''': List of email addresses to always send notifications to. ''Typically used to post ticket changes to a dedicated mailing list.''
+ * '''always_notify_owner''': Always send notifications to ticket owners.
  * '''always_notify_reporter''':  Always send notifications to any address in the reporter field.
 
 Either '''smtp_from''' or '''smtp_replyto''' (or both) ''must'' be set, otherwise Trac refuses to send notification mails.
Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(revision 1120)
+++ trac/db_default.py	(working copy)
@@ -431,6 +431,7 @@
   ('ticket', 'default_priority', 'normal'),
   ('ticket', 'default_milestone', ''),
   ('ticket', 'default_component', 'component1'),
+  ('ticket', 'restrict_owner', 'false'),
   ('header_logo', 'link', 'http://trac.edgewall.com/'),
   ('header_logo', 'src', 'trac_banner.png'),
   ('header_logo', 'alt', 'Trac'),
@@ -442,6 +443,7 @@
   ('notification', 'smtp_enabled', 'false'),
   ('notification', 'smtp_server', 'localhost'),
   ('notification', 'smtp_always_cc', ''),
+  ('notification', 'always_notify_owner', 'false'),
   ('notification', 'always_notify_reporter', 'false'),
   ('notification', 'smtp_from', 'trac@localhost'),
   ('notification', 'smtp_replyto', 'trac@localhost'),
Index: trac/Session.py
===================================================================
--- trac/Session.py	(revision 1120)
+++ trac/Session.py	(working copy)
@@ -104,13 +104,21 @@
         self.sid = sid
         curs = self.db.cursor()
         curs.execute("SELECT username,var_name,var_value FROM session"
-                    " WHERE sid=%s", self.sid)
+                     " WHERE sid=%s", self.sid)
         rows = curs.fetchall()
         if (not rows                              # No session data yet
             or rows[0][0] == 'anonymous'          # Anon session
             or rows[0][0] == self.req.authname):  # Session is mine
             for u,k,v in rows:
                 self.vars[k] = v
+            # For authenticated users: Load global settings for new session
+            if self.req.authname != 'anonymous':
+                curs.execute("SELECT var_name,var_value FROM session"
+                             " WHERE sid ISNULL AND username = %s",
+                             self.req.authname)
+                rows = curs.fetchall()
+                for k,v in rows:
+                    if not self.vars.has_key(k): self.vars[k] = v
             self.update_sess_time()
             self.bake_cookie()
             self.populate_hdf()
@@ -143,11 +151,28 @@
             curs.execute('UPDATE session SET username=%s,var_value=%s'
                          ' WHERE sid=%s AND var_name=%s',
                          self.req.authname, val, self.sid, key)
+        # For authenticated users: Set global settings (ignore session-specific
+        # attributes)
+        if self.req.authname != 'anonymous' and \
+                not key in ['mod_time', 'last_visit']:
+            curs.execute("DELETE FROM session WHERE sid ISNULL AND"
+                         " username = %s AND var_name = %s",
+                         self.req.authname, key)
+            curs.execute("INSERT INTO session(sid,username,var_name,var_value)"
+                         " VALUES(NULL,%s,%s,%s)", self.req.authname, key, val)
         self.db.commit()
         self.vars[key] = val
 
     def create_new_sid(self):
         self.sid = hex_entropy(24)
+        # For authenticated users: Load (existing) global settings for new session
+        if self.req.authname != 'anonymous':
+            curs = self.db.cursor()
+            curs.execute("SELECT var_name,var_value FROM session"
+                         " WHERE sid ISNULL AND username = %s", self.req.authname)
+            rows = curs.fetchall()
+            for k,v in rows:
+                if not self.vars.has_key(k): self.vars[k] = v
         self.bake_cookie()
         self.populate_hdf()
 
@@ -173,5 +198,7 @@
         curs.execute("DELETE FROM session WHERE sid IN"
                      " (SELECT sid FROM session WHERE var_name='mod_time'"
                      "  AND var_value  < %i)", mintime)
+        # For authenticated users: Delete global settings when all sessions are removed
+        curs.execute("DELETE FROM session WHERE sid ISNULL AND username NOT IN"
+                     " (SELECT username FROM session WHERE NOT sid ISNULL)")
         self.db.commit()
-
Index: trac/Notify.py
===================================================================
--- trac/Notify.py	(revision 1120)
+++ trac/Notify.py	(working copy)
@@ -24,6 +24,7 @@
 import time
 import smtplib
 import os.path
+import email.Utils
 
 import neo_cgi
 import neo_cs
@@ -106,7 +107,15 @@
     from_email = 'trac+tickets@localhost'
     subject = ''
     server = None
+    useremails = {}
 
+    def __init__(self, env, msg_template):
+        Notify.__init__(self, env, msg_template)
+        cursor = self.db.cursor()
+        cursor.execute("SELECT username,var_value FROM session WHERE sid ISNULL AND var_name = 'email'")
+        rows = cursor.fetchall()
+        for k,v in rows: self.useremails[k] = v
+
     def notify(self, resid, subject):
         self.subject = subject
 
@@ -131,8 +140,8 @@
         Notify.notify(self, resid)
 
     def get_email_addresses(self, txt):
-        import email.Utils
-        emails = [x[1] for x in  email.Utils.getaddresses([str(txt)])]
+        emails = [x[1] for x in email.Utils.getaddresses([str(txt), \
+            self.useremails.has_key(txt) and self.useremails[txt] or ''])]
         return filter(lambda x: x.find('@') > -1, emails)
 
     def begin_send(self):
@@ -279,7 +288,13 @@
         return txt
 
     def parse_cc(self, txt):
-        return filter(lambda x: '@' in x, txt.replace(',', ' ').split())
+        recipients = txt.replace(',', ' ').split()
+        additional = []
+        for r in recipients:
+            if self.useremails.has_key(r):
+                additional.append(self.useremails[r])
+        emails = [x[1] for x in email.Utils.getaddresses(recipients + additional)]
+        return filter(lambda x: '@' in x, emails)
 
     def format_hdr(self):
         return '#%s: %s' % (self.ticket['id'],
@@ -295,16 +310,20 @@
         # is set to true
         val = self.env.get_config('notification', 'always_notify_reporter', 'false')
         notify_reporter = val.lower() in TRUE
+        val = self.env.get_config('notification', 'always_notify_owner', 'false')
+        notify_owner = val.lower() in TRUE
         
         emails = self.prev_cc
         cursor = self.db.cursor()
         # Harvest email addresses from the cc field
-        cursor.execute('SELECT cc,reporter FROM ticket WHERE id=%s', tktid)
+        cursor.execute('SELECT cc,reporter,owner FROM ticket WHERE id=%s', tktid)
         row = cursor.fetchone()
         if row:
             emails += row[0] and self.parse_cc(row[0]) or []
             if notify_reporter:
                 emails += row[1] and self.get_email_addresses(row[1]) or []
+            if notify_owner:
+                emails += row[2] and self.get_email_addresses(row[2]) or []
 
         if notify_reporter:
             cursor.execute('SELECT DISTINCT author,ticket FROM ticket_change '
Index: trac/Ticket.py
===================================================================
--- trac/Ticket.py	(revision 1120)
+++ trac/Ticket.py	(working copy)
@@ -196,6 +196,29 @@
         return log
 
 
+def get_users(env, db):
+    users = []
+    option = env.get_config('ticket', 'restrict_owner')
+    if option.lower() in util.TRUE:
+        cursor = db.cursor()
+        cursor.execute("SELECT DISTINCT s1.username, s2.var_value "
+                       "FROM session s1 "
+                       "LEFT OUTER JOIN session s2 "
+                       "  ON (s2.sid ISNULL AND s1.username = s2.username "
+                       "      AND s2.var_name = 'name') "
+                       "WHERE s1.sid ISNULL "
+                       "ORDER BY s1.username")
+        while 1:
+            row = cursor.fetchone()
+            if not row:
+                break
+            user = {'name': row[0]}
+            if row[1]:
+                user['label'] = '%s (%s)' % (row[0], row[1])
+            users.append(user)
+    return users
+
+
 def cmp_by_order(a, b):
     try:
         return int(a['order']) - int(b['order'])
@@ -334,6 +357,9 @@
                                  " ORDER BY value",
                         self.req.hdf, 'enums.severity')
 
+        util.add_to_hdf(get_users(self.env, self.db), self.req.hdf,
+                        'newticket.users')
+
         insert_custom_fields(self.env, self.req.hdf, ticket)
 
 
@@ -402,6 +428,11 @@
         util.sql_to_hdf(self.db, "SELECT name FROM enum WHERE type='resolution'"
                                  " ORDER BY value",
                         self.req.hdf, 'enums.resolution')
+
+        util.add_to_hdf(get_users(self.env, self.db), self.req.hdf,
+                        'ticket.users')
+
+
         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'])
Index: templates/ticket.cs
===================================================================
--- templates/ticket.cs	(revision 1120)
+++ templates/ticket.cs	(working copy)
@@ -229,10 +229,18 @@
    <?cs call:hdf_select(enums.resolution, "resolve_resolution", args.resolve_resolution) ?><br />
    <?cs call:action_radio('reassign') ?>
    <label for="reassign">reassign</label>
-   <label for="reassign_owner">to:</label>
-   <input type="text" id="reassign_owner" name="reassign_owner" size="40" value="<?cs
-     if:args.reassign_to ?><?cs var:args.reassign_to ?><?cs
-     else ?><?cs var:trac.authname ?><?cs /if ?>" /><?cs
+   <label for="reassign_owner">to:</label><?cs
+   if:args.reassign_to ?><?cs
+     set default_owner=args.reassign_to ?><?cs
+   else ?><?cs
+     set default_owner=trac.authname ?><?cs
+   /if ?><?cs
+   if:len(ticket.users) ?><?cs
+     call:hdf_select(ticket.users, "reassign_owner", default_owner) ?><?cs
+   else ?>
+     <input type="text" id="reassign_owner" name="reassign_owner" size="40"
+            value="<?cs var:default_owner ?>" /><?cs
+   /if ?><?cs
   /if ?><?cs
   if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?>
    <script type="text/javascript">
Index: templates/macros.cs
===================================================================
--- templates/macros.cs	(revision 1120)
+++ templates/macros.cs	(working copy)
@@ -1,11 +1,10 @@
 <?cs def:hdf_select(options, name, selected) ?>
  <select size="1" id="<?cs var:name ?>" name="<?cs var:name ?>"><?cs
-  each:option = options ?><?cs
-   if option.name == $selected ?>
-    <option selected="selected"><?cs var:option.name ?></option><?cs
-   else ?>
-    <option><?cs var:option.name ?></option><?cs
-   /if ?><?cs
+  each:option = options ?>
+   <option<?cs
+    if:option.name == selected ?> selected="selected"<?cs /if ?><?cs
+    if:option.label ?> value="<?cs var:option.name ?>"<?cs /if ?>><?cs
+    alt:option.label ?><?cs var:option.name ?><?cs /alt ?></option><?cs
   /each ?>
  </select><?cs
 /def?>
Index: templates/newticket.cs
===================================================================
--- templates/newticket.cs	(revision 1120)
+++ templates/newticket.cs	(working copy)
@@ -59,8 +59,12 @@
    <label for="milestone">Milestone:</label><?cs
    call:hdf_select(newticket.milestones, "milestone", newticket.milestone) ?><br />
    <label for="owner">Assign to:</label>
+   <?cs if:len(newticket.users) ?><?cs
+     call:hdf_select(newticket.users, "owner", newticket.owner) ?><br /><?cs
+   else ?>
    <input type="text" id="owner" name="owner" size="20" value="<?cs
      var:newticket.owner ?>" /><br />
+   <?cs /if ?>
    <label for="cc">Cc:</label>
    <input type="text" id="cc" name="cc" size="30" value="<?cs var:newticket.cc ?>" />
   </div>
