=== templates/macros.cs
==================================================================
--- templates/macros.cs  (revision 1103)
+++ templates/macros.cs  (local)
@@ -179,10 +179,16 @@
       var c.name ?>" value="1" <?cs if c.selected ?>checked="checked"<?cs /if ?> />
     <label for="custom_<?cs var c.name ?>"><?cs alt c.label ?><?cs
       var c.name ?><?cs /alt ?></label><?cs
+   elif c.type == 'multi' ?>
+    <div style="float: left">
+     <label for="custom_<?cs var c.name ?>"><?cs alt c.label ?><?cs
+        var c.name ?><?cs /alt ?></label>:
+     <?cs call:hdf_select_multiple(c.option, 'custom_'+c.name, 4) ?>
+    </div><?cs
    elif c.type == 'select' ?>
     <select name="custom_<?cs var c.name ?>"><?cs each v = c.option ?>
      <option <?cs if v.selected ?>selected="selected"<?cs /if ?>><?cs
-       var v ?></option><?cs /each ?>
+       var v.name ?></option><?cs /each ?>
     </select><?cs
    elif c.type == 'radio' ?>
     <fieldset class="radio">
=== templates/query.cs
==================================================================
--- templates/query.cs  (revision 1103)
+++ templates/query.cs  (local)
@@ -81,7 +81,7 @@
   </div>
   <?cs if:len(query.custom) ?><?cs set:idx = 0 ?><?cs
    each:custom = query.custom ?><?cs
-    if:custom.type == 'select' || custom.type == 'radio' ?>
+    if:custom.type == 'select' || custom.type == 'radio' || custom.type == 'multi' ?>
      <?cs if:idx == 0 ?><br /><?cs /if ?><div>
       <label for="<?cs var:custom.name ?>"><?cs var:custom.label ?></label>
       <?cs call:hdf_select_multiple(custom.options, custom.name, 4) ?>
=== trac/Query.py
==================================================================
--- trac/Query.py  (revision 1103)
+++ trac/Query.py  (local)
@@ -129,7 +129,7 @@
 
         custom_fields = get_custom_fields(self.env)
         for custom in custom_fields:
-            if custom['type'] == 'select' or custom['type'] == 'radio':
+            if custom['type'] in ['select', 'radio', 'multi']:
                 check = constraints.has_key(custom['name'])
                 options = filter(None, custom['options'])
                 for i in range(len(options)):
=== trac/Ticket.py
==================================================================
--- trac/Ticket.py  (revision 1103)
+++ trac/Ticket.py  (local)
@@ -112,8 +112,11 @@
                        *std_values)
         id = db.db.sqlite_last_insert_rowid()
         for name in custom_fields:
+            value = self[name]
+            if isinstance(value, list):
+                value = '|'.join(value)
             cursor.execute('INSERT INTO ticket_custom(ticket,name,value)'
-                           ' VALUES(%d, %s, %s)', id, name[7:], self[name])
+                           ' VALUES(%d, %s, %s)', id, name[7:], value)
         db.commit()
         self['id'] = id
         self._forget_changes()
@@ -144,19 +147,22 @@
            
 
         for name in self._old.keys():
+            newvalue = self[name]
             if name[:7] == 'custom_':
                 fname = name[7:]
+                if isinstance(newvalue, list):
+                    newvalue = '|'.join(newvalue)
                 cursor.execute('REPLACE INTO ticket_custom(ticket,name,value)'
-                               ' VALUES(%s, %s, %s)', id, fname, self[name])
+                               ' VALUES(%s, %s, %s)', id, fname, newvalue)
             else:
                 fname = name
                 cursor.execute ('UPDATE ticket SET %s=%s WHERE id=%s',
-                                fname, self[name], id)
+                                fname, newvalue, id)
 
             cursor.execute ('INSERT INTO ticket_change '
                             '(ticket, time, author, field, oldvalue, newvalue) '
                             'VALUES (%s, %s, %s, %s, %s, %s)',
-                            id, when, author, fname, self._old[name], self[name])
+                            id, when, author, fname, self._old[name], newvalue)
         if comment:
             cursor.execute ('INSERT INTO ticket_change '
                             '(ticket,time,author,field,oldvalue,newvalue) '
@@ -226,7 +232,7 @@
             'label': items.get(name + '.label', ''),
             'value': items.get(name + '.value', '')
         }
-        if field['type'] == 'select' or field['type'] == 'radio':
+        if field['type'] in ['multi', 'select', 'radio']:
             field['options'] = items.get(name + '.options', '').split('|')
         elif field['type'] == 'textarea':
             field['width'] = items.get(name + '.cols', '')
@@ -248,12 +254,16 @@
         hdf.setValue('%s.type' % pfx, f['type'])
         hdf.setValue('%s.label' % pfx, f['label'])
         hdf.setValue('%s.value' % pfx, val)
-        if f['type'] == 'select' or f['type'] == 'radio':
+        if f['type'] in ['multi', 'select', 'radio']:
             j = 0
+            if f['type'] == 'multi':
+                multi = 1
+                val = val.split('|') # see Ticket.save_changes
             for option in f['options']:
-                hdf.setValue('%s.option.%d' % (pfx, j), option)
-                if val and (option == val or str(j) == val):
-                    hdf.setValue('%s.option.%i.selected' % (pfx, j), '1')
+                hdf.setValue('%s.option.%d.name' % (pfx, j), option)
+                if val:
+                    if (multi and option in val) or (option == val or str(j) == val):
+                        hdf.setValue('%s.option.%i.selected' % (pfx, j), '1')
                 j += 1
         elif f['type'] == 'checkbox':
             if val in util.TRUE:
