Index: trac/admin/templates/admin_subcomponents.html
===================================================================
--- trac/admin/templates/admin_subcomponents.html	(revision 0)
+++ trac/admin/templates/admin_subcomponents.html	(revision 0)
@@ -0,0 +1,123 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+  <xi:include href="admin.html" />
+  <head>
+    <title>Sub-Components</title>
+  </head>
+
+  <body>
+    <h2>Manage Sub-Components</h2>
+
+    <py:def function="owner_field(default_owner='')">
+      <div class="field">
+        <label>Owner: <br />
+          <py:choose>
+            <select py:when="owners" size="1" id="owner" name="owner">
+              <option py:for="owner in owners"
+                      selected="${owner==default_owner or None}">$owner</option>
+              <option py:if="default_owner and default_owner not in owners"
+                      selected="selected">$default_owner</option>
+            </select>
+            <input py:otherwise="" type="text" name="owner" value="$default_owner" />
+          </py:choose>
+        </label>
+      </div>
+    </py:def>
+
+    <py:choose test="view">
+      <form py:when="'detail'" class="mod" id="submodcomp" method="post">
+        <fieldset>
+          <legend>Modify Sub-Component:</legend>
+          <div class="field">
+            <label>Component:<br /><input type="text" name="parent" value="${subcomponent.parent}" disabled="disabled" /></label>
+          </div>
+          <div class="field">
+            <label>Name:<br /><input type="text" name="name" value="${subcomponent.name}" /></label>
+          </div>
+          ${owner_field(subcomponent.owner)}
+          <div class="field">
+            <fieldset class="iefix">
+              <label for="description">
+                Description (you may use
+                <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a>
+                here):
+              </label>
+              <p>
+                <textarea id="description" name="description" class="wikitext"
+                          rows="6" cols="60">
+$subcomponent.description</textarea>
+              </p>
+            </fieldset>
+          </div>
+          <div class="buttons">
+            <input type="submit" name="cancel" value="Cancel" />
+            <input type="submit" name="save" value="Save" />
+          </div>
+        </fieldset>
+      </form>
+
+      <py:otherwise>
+        <form class="addnew" id="addsubcomp" method="post">
+          <fieldset>
+            <legend>Add Subcomponent:</legend>
+            <input type="hidden" name="parent" value="${parent}" />
+            <div class="field">
+              <label>Name:<br /><input type="text" name="name" /></label>
+            </div>
+            ${owner_field()}
+            <div class="buttons">
+              <input type="submit" name="add" value="Add"/>
+            </div>
+          </fieldset>
+        </form>
+
+        <py:choose>
+          <form py:when="subcomponents" method="POST">
+            Component: <select id="parent" name="parent" onchange="this.form.submit()">
+            <option py:for="component in components"
+            selected="${parent == component.name or None}">${component.name}</option>
+            </select>
+            <table class="listing" id="subcomplist">
+              <thead>
+                <tr><th class="sel">&nbsp;</th>
+                  <th>Name</th><th>Owner</th><th>Default</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr py:for="scomp in subcomponents">
+                  <td class="sel"><input type="checkbox" name="sel" value="$scomp.name" /></td>
+                  <td class="name">
+                    <a href="${panel_href(parent + '/' + scomp.name)}">$scomp.name</a>
+                  </td>
+                  <td class="owner">$scomp.owner</td>
+                  <td class="default">
+                    <input type="radio" name="default" value="$scomp.name"
+                           checked="${scomp.name==default or None}" />
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+            <div class="buttons">
+              <input type="submit" name="remove" value="Remove selected items" />
+              <input type="submit" name="apply" value="Apply changes" />
+            </div>
+            <p class="help">
+              You can remove all items from this list to completely hide this
+              field from the user interface.
+            </p>
+          </form>
+
+          <p py:otherwise="" class="help">
+            As long as you don't add any items to the list, this field
+            will remain completely hidden from the user interface.
+          </p>
+        </py:choose>
+      </py:otherwise>
+    </py:choose>
+  </body>
+
+</html>
Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(revision 6371)
+++ trac/db_default.py	(working copy)
@@ -17,7 +17,7 @@
 from trac.db import Table, Column, Index
 
 # Database version identifier. Used for automatic upgrades.
-db_version = 21
+db_version = 22
 
 def __mkreports(reports):
     """Utility function used to create report data in same syntax as the
@@ -104,6 +104,7 @@
         Column('time', type='int'),
         Column('changetime', type='int'),
         Column('component'),
+        Column('subcomponent'),
         Column('severity'),
         Column('priority'),
         Column('owner'),
@@ -139,6 +140,11 @@
         Column('name'),
         Column('owner'),
         Column('description')],
+    Table('subcomponent', key=('parent', 'name'))[
+        Column('parent'),
+        Column('name'),
+        Column('owner'),
+        Column('description')],
     Table('milestone', key='name')[
         Column('name'),
         Column('due', type='int'),
@@ -335,7 +341,13 @@
              ('name', 'owner'),
                (('component1', 'somebody'),
                 ('component2', 'somebody'))),
-           ('milestone',
+             ('subcomponent',
+             ('parent', 'name', 'owner'),
+               (('component1', 'subcomponent1-1', 'somebody'),
+                ('component1', 'subcomponent1-2', 'somebody'),
+                ('component2', 'subcomponent2-1', 'somebody'),
+                ('component2', 'subcomponent2-2', 'somebody'))),
+             ('milestone',
              ('name', 'due', 'completed'),
                (('milestone1', 0, 0),
                 ('milestone2', 0, 0),
Index: trac/htdocs/js/query.js
===================================================================
--- trac/htdocs/js/query.js	(revision 6371)
+++ trac/htdocs/js/query.js	(working copy)
@@ -157,6 +157,24 @@
       return e;
     }
 
+    // Convenience function for creating a <select> dependent on other <select>
+    function createDepSelect(name, options, optional) {
+      var e = document.createElement("select");
+      if (name) e.name = name;
+      if (optional) e.options[0] = new Option();
+      if (options) {
+        for (var i = 0; i < options.length; i++) {
+          var option;
+          if (typeof(options[i]) == "object") {
+            option = new Option(options[i].text, options[i].value);
+          } else {
+            option = new Option(options[i], options[i]);
+          }
+          e.options[e.options.length] = option;
+        }
+      }
+      return e;
+    }
     var propertyName = select.options[select.selectedIndex].value;
     var property = properties[propertyName];
     var table = document.getElementById("filters").getElementsByTagName("table")[0];
@@ -219,6 +237,8 @@
       td.className = "filter";
       if (property.type == "select") {
         var element = createSelect(propertyName, property.options, true);
+      } else if (property.type == "depselect") {
+        var element = createDepSelect(propertyName, property.options, true);
       } else if (property.type == "text") {
         var element = document.createElement("input");
         element.type = "text";
Index: trac/htdocs/js/sublist.js
===================================================================
--- trac/htdocs/js/sublist.js	(revision 0)
+++ trac/htdocs/js/sublist.js	(revision 0)
@@ -0,0 +1,27 @@
+/* 
+Based on code found at:
+http://www.ajaxray.com/blog/2007/11/08/jquery-controlled-dependent-or-cascading-select-list-2/ 
+*/
+function makeSublist(parent,child,isSubselectOptional,childVal)
+{
+  $("body").append("<select style='display:none' id='" + parent + child + "'></select>");
+  $('#'+parent+child).html($("#"+child+" option"));
+  
+          var parentValue = $('#'+parent).attr('value');
+          $('#'+child).html($("#"+parent+child+" .sub_"+parentValue).clone());
+  
+  childVal = (typeof childVal == "undefined")? "" : childVal ;
+  if(isSubselectOptional) $('#'+child).prepend("<option></option>");
+  $("#"+child+' option[@value="'+ childVal +'"]').attr('selected','selected'); 
+  $('#'+parent).change( 
+          function()
+          {
+                  var parentValue = $('#'+parent).attr('value');
+                  $('#'+child).html($("#"+parent+child+" .sub_"+parentValue).clone());
+                  if(isSubselectOptional) $('#'+child).prepend("<option></option>");
+                  $('#'+child+' option:first').attr('selected', 'selected');
+                  $('#'+child).trigger("change"); 
+                  $('#'+child).focus();
+          }
+  );
+}
Index: trac/ticket/admin.py
===================================================================
--- trac/ticket/admin.py	(revision 6371)
+++ trac/ticket/admin.py	(working copy)
@@ -12,6 +12,7 @@
 # history and logs, available at http://trac.edgewall.org/.
 
 from datetime import datetime
+import re
 
 from trac.admin import IAdminPanelProvider
 from trac.core import *
@@ -119,6 +120,100 @@
         return 'admin_components.html', data
 
 
+class SubcomponentAdminPage(TicketAdminPage):
+
+    _type = 'subcomponents'
+    _label = ('Subcomponent', 'Subcomponents')
+
+    # TicketAdminPage methods
+
+    def _render_admin_panel(self, req, cat, page, subcomponent):
+        match = None
+        # Detail view?
+        if subcomponent:
+            match = re.match('([^/]+)/(.*)$', subcomponent) # Looking for pattern <component>/<subcomponent> in url
+ 
+        if match: # Detail view
+            subcomp = model.Subcomponent(self.env, match.group(2), match.group(1))
+            if req.method == 'POST':
+                if req.args.get('save'):
+                    subcomp.name = req.args.get('name')
+                    subcomp.owner = req.args.get('owner')
+                    subcomp.description = req.args.get('description')
+                    subcomp.update()
+                    req.redirect(req.href.admin(cat, page, match.group(1)))
+                elif req.args.get('cancel'):
+                    req.redirect(req.href.admin(cat, page, match.group(1)))
+
+            add_script(req, 'common/js/wikitoolbar.js')
+            data = {'view': 'detail', 'subcomponent': subcomp}
+
+        else:
+            if req.method == 'POST':
+                # Add Component
+                if req.args.get('add') and req.args.get('name') and req.args.get('parent'):
+                    subcomp = model.Subcomponent(self.env)
+                    subcomp.name = req.args.get('name')
+                    subcomp.parent = req.args.get('parent')
+                    if req.args.get('owner'):
+                        subcomp.owner = req.args.get('owner')
+                    subcomp.insert()
+                    req.redirect(req.href.admin(cat, page, subcomp.parent))
+
+                # Remove components
+                elif req.args.get('remove') and req.args.get('sel'):
+                    sel = req.args.get('sel')
+                    sel = isinstance(sel, list) and sel or [sel]
+                    parent = req.args.get('parent')
+                    if not sel:
+                        raise TracError(_('No subcomponent selected'))
+                    db = self.env.get_db_cnx()
+                    for subcompname in sel:
+                        subcomp = model.Subcomponent(self.env, subcompname, parent, db=db)
+                        subcomp.delete(db=db)
+                    db.commit()
+                    req.redirect(req.href.admin(cat, page, req.args.get('parent')))
+
+                # Set default component
+                elif req.args.get('apply'):
+                    if req.args.get('default'):
+                        name = req.args.get('default')
+                        self.log.info('Setting default subcomponent to %s', name)
+                        # Todo: One default for each parent?
+                        self.config.set('ticket', 'default_subcomponent', name)
+                        self.config.save()
+                        req.redirect(req.href.admin(cat, page, req.args.get('parent')))
+                
+                # Changed selected parent
+                elif req.args.get('parent'):
+                    req.redirect(req.href.admin(cat, page, req.args.get('parent')))
+
+            components = list(model.Component.select(self.env))
+            if subcomponent:
+                parent = subcomponent # Catches redirects
+            else:
+                parent = components[0].name # Just use first in list as default
+
+            default = self.config.get('ticket', 'default_subcomponent')
+            data = {'view': 'list',
+                    'components': components,
+                    'subcomponents': model.Subcomponent.select(self.env, parent=parent),
+                    'parent': parent,
+                    'default': default}
+
+        if self.config.getbool('ticket', 'restrict_owner'):
+            perm = PermissionSystem(self.env)
+            def valid_owner(username):
+                return perm.get_user_permissions(username).get('TICKET_MODIFY')
+            data['owners'] = [username for username, name, email
+                              in self.env.get_known_users()
+                              if valid_owner(username)]
+        else:
+            data['owners'] = None
+
+        return 'admin_subcomponents.html', data
+
+
 class MilestoneAdminPage(TicketAdminPage):
 
     _type = 'milestones'
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py	(revision 6371)
+++ trac/ticket/api.py	(working copy)
@@ -227,6 +227,20 @@
             elif name in ('milestone', 'version'):
                 field['optional'] = True
             fields.append(field)
+            
+        # Dependent select fields
+        depselects = [('subcomponent', model.Subcomponent, 'component')]
+        for name, cls, parent in depselects:
+            options = [(val.name, val.parent) for val in cls.select(self.env, db=db)]
+            if not options:
+                continue
+            field = {'name': name, 'type': 'depselect', 'label': name.title(),
+                     'value': self.config.get('ticket', 'default_' + name),
+                     'options': options}
+            field['optional'] = True
+            fields.append(field)
+            
+                
 
         # Advanced text fields
         for name in ('keywords', 'cc', ):
Index: trac/ticket/model.py
===================================================================
--- trac/ticket/model.py	(revision 6371)
+++ trac/ticket/model.py	(working copy)
@@ -31,7 +31,7 @@
 from trac.util.translation import _
 
 __all__ = ['Ticket', 'Type', 'Status', 'Resolution', 'Priority', 'Severity',
-           'Component', 'Milestone', 'Version']
+           'Component', 'Subcomponent', 'Milestone', 'Version']
 
 
 class Ticket(object):
@@ -149,15 +149,27 @@
 
         cursor = db.cursor()
 
-        # The owner field defaults to the component owner
+        # The owner field defaults to the sub-component owner or component owner if sub-component doesn't exist
         if self.values.get('component') and not self.values.get('owner'):
-            try:
-                component = Component(self.env, self['component'], db=db)
-                if component.owner:
-                    self['owner'] = component.owner
-            except TracError, e:
-                # Assume that no such component exists
-                pass
+            foundowner = False
+            # Try to get owner field from sub-component first
+            if self.values.get('subcomponent'):
+                try:
+                    subcomponent = Subcomponent(self.env, self['subcomponent'], self['component'], db=db)
+                    if subcomponent.owner:
+                        self['owner'] = subcomponent.owner
+                        foundowner = True
+                except TracError, e:
+                    pass
+            # If no owner found, try component
+            if not foundowner:
+                try:
+                    component = Component(self.env, self['component'], db=db)
+                    if component.owner:
+                        self['owner'] = component.owner
+                except TracError, e:
+                    # Assume that no such component exists
+                    pass
 
         # Insert ticket record
         created = to_timestamp(self.time_created)
@@ -519,6 +531,8 @@
         cursor = db.cursor()
         self.env.log.info('Deleting component %s' % self.name)
         cursor.execute("DELETE FROM component WHERE name=%s", (self.name,))
+        # Delete all subcomponents while we're at it
+        cursor.execute("DELETE FROM subcomponent WHERE parent=%s", (self.name,))
 
         self.name = self._old_name = None
 
@@ -564,6 +578,9 @@
             # Update tickets
             cursor.execute("UPDATE ticket SET component=%s WHERE component=%s",
                            (self.name, self._old_name))
+            # Update subcomponents
+            cursor.execute("UPDATE subcomponent SET parent=%s WHERE parent=%s",
+                           (self.name, self._old_name))
             self._old_name = self.name
 
         if handle_ta:
@@ -584,6 +601,118 @@
     select = classmethod(select)
 
 
+class Subcomponent(object):
+
+    def __init__(self, env, name=None, parent=None, db=None):
+        self.env = env
+        if name and parent:
+            if not db:
+                db = self.env.get_db_cnx()
+            cursor = db.cursor()
+            cursor.execute("SELECT owner,description FROM subcomponent "
+                           "WHERE name=%s AND parent=%s", (name,parent))
+            row = cursor.fetchone()
+            if not row:
+                raise TracError(_('Subcomponent %(name) does not exist.',
+                                  name=name))
+            self.name = self._old_name = name
+            self.parent = self._old_parent = parent
+            self.owner = row[0] or None
+            self.description = row[1] or ''
+        else:
+            self.name = self._old_name = None
+            self.owner = None
+            self.description = None
+            self.parent = self._old_parent = None
+
+    exists = property(fget=lambda self: self._old_name is not None)
+
+    def delete(self, db=None):
+        assert self.exists, 'Cannot deleting non-existent component'
+        if not db:
+            db = self.env.get_db_cnx()
+            handle_ta = True
+        else:
+            handle_ta = False
+
+        cursor = db.cursor()
+        self.env.log.info('Deleting subcomponent %s' % self.name)
+        cursor.execute("DELETE FROM subcomponent WHERE name=%s AND parent=%s", (self.name,self.parent))
+
+        self.name = self._old_name = None
+        self.parent = self._old_parent = None
+
+        if handle_ta:
+            db.commit()
+
+    def insert(self, db=None):
+        assert not self.exists, 'Cannot insert existing subcomponent'
+        self.name = simplify_whitespace(self.name)
+        self.parent = simplify_whitespace(self.parent)
+        assert self.name, 'Cannot create component with no name'
+        assert self.parent, 'Cannot create subcomponent without a parent'
+        if not db:
+            db = self.env.get_db_cnx()
+            handle_ta = True
+        else:
+            handle_ta = False
+
+        cursor = db.cursor()
+        self.env.log.debug("Creating new subcomponent '%s'" % self.name)
+        cursor.execute("INSERT INTO subcomponent (name,owner,description,parent) "
+                       "VALUES (%s,%s,%s,%s)",
+                       (self.name, self.owner, self.description, self.parent))
+
+        if handle_ta:
+            db.commit()
+
+    def update(self, db=None):
+        assert self.exists, 'Cannot update non-existent subcomponent'
+        self.name = simplify_whitespace(self.name)
+        self.parent = simplify_whitespace(self.parent)
+        assert self.name, 'Cannot update subcomponent with no name'
+        assert self.parent, 'Cannot create subcomponent without a parent'
+        if not db:
+            db = self.env.get_db_cnx()
+            handle_ta = True
+        else:
+            handle_ta = False
+
+        cursor = db.cursor()
+        self.env.log.info('Updating subcomponent "%s"' % self.name)
+        cursor.execute("UPDATE subcomponent SET name=%s,owner=%s,description=%s, parent=%s "
+                       "WHERE name=%s AND parent=%s",
+                       (self.name, self.owner, self.description, self.parent, self._old_name, self._old_parent))
+        if self.name != self._old_name or self.parent != self._old_parent:
+            # Update tickets
+            cursor.execute("UPDATE ticket SET component=%s, subcomponent=%s WHERE component=%s AND subcomponent=%s",
+                           (self.parent, self.name, self._old_parent, self._old_name))
+            self._old_name = self.name
+            self._old_parent = self.parent
+
+        if handle_ta:
+            db.commit()
+
+    def select(cls, env, db=None, parent=None):
+        if not db:
+            db = env.get_db_cnx()
+        cursor = db.cursor()
+        if parent:
+            cursor.execute("SELECT name,parent,owner,description FROM subcomponent "
+                           "WHERE parent=%s ORDER BY name", (parent,))
+        else:
+            cursor.execute("SELECT name,parent,owner,description FROM subcomponent "
+                           "ORDER BY parent,name")
+        for name, parent, owner, description in cursor:
+            subcomponent = cls(env)
+            subcomponent.parent = parent
+            subcomponent.name = name
+            subcomponent.owner = owner or None
+            subcomponent.description = description or ''
+            yield subcomponent
+    select = classmethod(select)
+
+
 class Milestone(object):
 
     def __init__(self, env, name=None, db=None):
Index: trac/ticket/query.py
===================================================================
--- trac/ticket/query.py	(revision 6371)
+++ trac/ticket/query.py	(working copy)
@@ -511,6 +511,10 @@
             {'name': _("is"), 'value': ""},
             {'name': _("is not"), 'value': "!"}
         ]
+        modes['depselect'] = [
+            {'name': _("is"), 'value': ""},
+            {'name': _("is not"), 'value': "!"}
+        ]
 
         groups = {}
         groupsequence = []
Index: trac/ticket/templates/query.html
===================================================================
--- trac/ticket/templates/query.html	(revision 6371)
+++ trac/ticket/templates/query.html	(working copy)
@@ -75,6 +75,16 @@
                           </select>
                         </py:when>
 
+                        <py:when test="'depselect'">
+                          <select name="${constraint_name}">
+                            <option></option>
+                            <option py:for="option, parent in field.options"
+                              class="sub_$parent"
+                              selected="${option == constraint_value and 'selected' or None}">$option
+                            </option>
+                          </select>
+                        </py:when>
+
                         <py:when test="'radio'">
                           <py:for each="option in field.options">
                             <input type="checkbox" id="${field_name}_$option" name="${field_name}"
@@ -150,7 +160,7 @@
           <select name="group" id="group">
             <option></option>
             <option py:for="field_name, field in fields.items()"
-                    py:if="field.type in ('select', 'radio') or field_name == 'owner'"
+                    py:if="field.type in ('select', 'depselect', 'radio') or field_name == 'owner'"
                     selected="${field_name == query.group or None}"
                     value="${field_name}">${field.label}</option>
           </select>
@@ -183,8 +193,16 @@
         <py:for each="(field_name, field), sep in separated(fields.iteritems())">
           $field_name: { type: "$field.type", label: "$field.label"
           <py:if test="'options' in field">, options: [
-            <py:for each="option, sep in separated(field.options)">"$option"$sep
-            </py:for>]
+            <py:choose test="field.type">
+              <py:when test="'depselect'">
+                <py:for each="option, sep in separated([o] for o, p in field.options)">"$option"$sep
+                </py:for>]
+              </py:when>
+              <py:otherwise>
+                <py:for each="option, sep in separated(field.options)">"$option"$sep
+                </py:for>]
+              </py:otherwise>
+            </py:choose>
           </py:if>}$sep
         </py:for>
         };
Index: trac/ticket/templates/ticket.html
===================================================================
--- trac/ticket/templates/ticket.html	(revision 6371)
+++ trac/ticket/templates/ticket.html	(working copy)
@@ -19,8 +19,10 @@
       </py:choose>
     </title>
     <script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
-    <script type="text/javascript" py:choose="">
+    <script type="text/javascript" src="${chrome.htdocs_location}js/sublist.js"></script>
+  <script type="text/javascript" py:choose="">
       $(document).ready(function() {
+        makeSublist('field-component', 'field-subcomponent', true, '${ticket["subcomponent"]}'); 
         $("div.description").find("h1,h2,h3,h4,h5,h6").addAnchor("Link to this section");
       <py:when test="ticket.exists">
         $("#changelog h3.change").addAnchor("Link to this change");
@@ -39,7 +41,7 @@
         updateActionFields();
       </py:when>
       <py:otherwise>
-        $(document).ready(function() {$("#field-summary").get(0).focus()});
+        $(document).ready(function() {makeSublist('parent', 'child', false, '1'); $("#field-summary").get(0).focus();});
       </py:otherwise>
       });
     </script>
@@ -311,6 +313,7 @@
                       <option py:if="field.optional"></option>
                       <option py:for="option in field.options"
                               selected="${ticket[field.name] == option or None}"
+                              value="$option"
                               py:content="option"></option>
                       <optgroup py:for="optgroup in field.optgroups"
                                 label="${optgroup.label}">
@@ -319,6 +322,20 @@
                                 py:content="option"></option>
                       </optgroup>
                     </select>
+                    <select py:when="'depselect'" id="field-${field.name}" name="field_${field.name}">
+                      <option py:for="(option, parent) in field.options"
+                              class="sub_${parent}"
+                              value="$option"
+                              py:content="option"></option>
+                      <optgroup py:for="optgroup in field.optgroups"
+                                class="subgroup_${parent}"
+                                label="${optgroup.label}">
+                        <option py:for="(option, parent) in optgroup.options"
+                                class="sub_${parent}"
+                                py:content="option"></option>
+                      </optgroup>
+                    </select>
+
                     <textarea py:when="'textarea'" id="field-${field.name}" name="field_${field.name}"
                               cols="${field.width}" rows="${field.height}"
                               py:content="ticket[field.name]"></textarea>
Index: trac/ticket/web_ui.py
===================================================================
--- trac/ticket/web_ui.py	(revision 6371)
+++ trac/ticket/web_ui.py	(working copy)
@@ -77,6 +77,9 @@
     default_component = Option('ticket', 'default_component', '',
         """Default component for newly created tickets""")
 
+    default_subcomponent = Option('ticket', 'default_subcomponent', '',
+        """Default sub-component for newly created tickets""")
+
     default_resolution = Option('ticket', 'default_resolution', 'fixed',
         """Default resolution for resolving (closing) tickets
         (''since 0.11'').""")
@@ -871,10 +874,16 @@
             if name in ticket.values and name in ticket._old:
                 value = ticket[name]
                 if value:
-                    if value not in field['options']:
-                        add_warning(req, '"%s" is not a valid value for '
-                                    'the %s field.' % (value, name))
-                        valid = False
+                    if field['type'] == 'depselect':
+                        if value not in [opt for opt, par in field['options']]:
+                            add_warning(req, '"%s" is not a valid value for '
+                                        'the %s field.' % (value, name))
+                            valid = False
+                    else:
+                        if value not in field['options']:
+                            add_warning(req, '"%s" is not a valid value for '
+                                        'the %s field.' % (value, name))
+                            valid = False
                 elif not field.get('optional', False):
                     add_warning(req, 'field %s must be set' % name)
                     valid = False
Index: trac/upgrades/db22.py
===================================================================
--- trac/upgrades/db22.py	(revision 0)
+++ trac/upgrades/db22.py	(revision 0)
@@ -0,0 +1,16 @@
+from trac.db import Table, Column, Index, DatabaseManager
+
+def do_upgrade(env, ver, cursor):
+    """Upgrade to add subcomponent support"""
+    table = Table('subcomponent', key=('parent', 'name'))[
+        Column('parent'),
+        Column('name'),
+        Column('owner'),
+        Column('description')]
+
+    db_connector, _ = DatabaseManager(env)._get_connector()
+    for stmt in db_connector.to_sql(table):
+        cursor.execute(stmt)
+
+    cursor.execute("ALTER TABLE ticket ADD COLUMN subcomponent TEXT AFTER component")
+
