Index: issue.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/issue.inc,v
retrieving revision 1.345
diff -u -p -r1.345 issue.inc
--- issue.inc	4 Apr 2009 07:03:28 -0000	1.345
+++ issue.inc	10 Apr 2009 08:30:15 -0000
@@ -554,9 +554,15 @@ function project_issue_form($node, $form
     foreach (variable_get('project_issue_invalid_releases', array()) as $rid) {
       unset($releases[$rid]);
     }
-    $components = array();
+    // Setup components and default component.
+    if (!empty($node->project_issue['component'])) {
+      $default_component = $node->project_issue['component'];
+    }
+    else {
+      $default_component = $project->project_issue['default_component'];
+    }
+    $components = empty($default_component) ? array(t('<none>')) : array();
     if ($project->project_issue['components']) {
-      $components = array(t('<none>'));
       foreach ($project->project_issue['components'] as $component) {
         $component = check_plain($component);
         $components[$component] = $component;
@@ -614,7 +620,7 @@ function project_issue_form($node, $form
     $form['project_info']['component'] = array(
       '#type' => 'select',
       '#title' => t('Component'),
-      '#default_value' => $node->project_issue['component'],
+      '#default_value' => $default_component,
       '#options' => $components,
       '#required' => TRUE,
     );
Index: project_issue.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/project_issue.install,v
retrieving revision 1.59
diff -u -p -r1.59 project_issue.install
--- project_issue.install	17 Mar 2009 23:32:30 -0000	1.59
+++ project_issue.install	10 Apr 2009 08:30:15 -0000
@@ -27,6 +27,12 @@ function project_issue_schema() {
         'type' => 'text',
         'not null' => FALSE,
       ),
+      'default_component' => array(
+        'description' => t('The default component for new issues of this project, or an empty string if the user needs to select a component.'),
+        'type' => 'text',
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'help' => array(
         'description' => t('Submission guidelines for issues added to this project.'),
         'type' => 'text',
@@ -415,3 +421,12 @@ function project_issue_update_6000() {
   return array();
 }
 
+/**
+ * Add the 'default_component' field to {project_issue_projects}.
+ */
+function project_issue_update_6001() {
+  $ret = array();
+  db_add_field($ret, 'project_issue_projects', 'default_component', array('type' => 'text', 'not null' => TRUE, 'default' => ''));
+  return $ret;
+}
+
Index: project_issue.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/project_issue.module,v
retrieving revision 1.157
diff -u -p -r1.157 project_issue.module
--- project_issue.module	4 Apr 2009 07:03:28 -0000	1.157
+++ project_issue.module	10 Apr 2009 08:30:16 -0000
@@ -130,6 +130,16 @@ function project_issue_menu() {
     'type' => MENU_LOCAL_TASK,
     'file' => 'includes/project_edit_issues.inc',
   );
+  $items['node/%project_node/edit/component/delete/%'] = array(
+    'title' => 'Delete component',
+    'description' => 'Delete component',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('project_issue_component_delete_form', 1, 5),
+    'access callback' => 'node_access',
+    'access arguments' => array('update', 1),
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/project_edit_issues.inc',
+  );
 
   $items['node/add/project-issue/%'] = array(
     'page callback' => 'node_add',
@@ -327,6 +337,12 @@ function project_issue_theme() {
         'form' => NULL,
       ),
     ),
+    'project_issue_project_edit_form' => array(
+      'file' => 'includes/project_edit_issues.inc',
+      'arguments' => array(
+        'form' => NULL,
+      ),
+    ),
     'project_issue_query_result_links' => array(
       'file' => 'issue.inc',
       'arguments' => array(
@@ -952,6 +968,7 @@ function project_issue_project_load(&$no
   }
   $node->project_issue = array();
   $node->project_issue['components'] = unserialize($project->components);
+  $node->project_issue['default_component'] = $project->default_component;
   $node->project_issue['mail_copy_filter'] = unserialize($project->mail_copy_filter);
   $node->project_issue['mail_copy_filter_state'] = unserialize($project->mail_copy_filter_state);
   foreach (array('issues', 'help', 'mail_digest', 'mail_copy', 'mail_reminder') as $field) {
Index: includes/project_edit_issues.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project_issue/includes/project_edit_issues.inc,v
retrieving revision 1.1
diff -u -p -r1.1 project_edit_issues.inc
--- includes/project_edit_issues.inc	4 Apr 2009 06:09:28 -0000	1.1
+++ includes/project_edit_issues.inc	10 Apr 2009 08:30:16 -0000
@@ -12,11 +12,59 @@
 function project_issue_project_edit_issues($node) {
   project_project_set_breadcrumb($node);
   drupal_set_title(check_plain($node->title));
+  return drupal_get_form('project_issue_project_edit_form', $node);
+}
+
+/**
+ * Theme function to render the issue subtab form.
+ */
+function theme_project_issue_project_edit_form($form) {
+  drupal_add_tabledrag('project-issue-edit-project-components-table', 'order', 'self', 'project-issue-component-weight');
 
-  if (isset($node->project_issue['components']) && is_array($node->project_issue['components'])) {
-    $node->project_issue['components'] = implode("\n", $node->project_issue['components']);
+  $header = array(
+    array('data' => t('Name')),
+    array('data' => t('Default')),
+    array('data' => t('Weight')),
+    array('data' => t('Operations'))
+  );
+  // Add the locked row at the top of the table for selecting no default.
+  $rows[] = array(
+    'data' => array(
+      check_plain(t('None (force users to select a component)')),
+      drupal_render($form['default_component'][0]),
+      NULL,
+      NULL,
+    ),
+  );
+  // Add the existing components.
+  foreach (element_children($form['component']) as $key) {
+    $rows[] = array(
+      'class' => 'draggable',
+      'data' => array(
+        drupal_render($form['component'][$key]['name']),
+        drupal_render($form['default_component'][$key]),
+        drupal_render($form['component'][$key]['weight']),
+        drupal_render($form['component'][$key]['delete']),
+      ),
+    );
   }
-  return drupal_get_form('project_issue_project_edit_form', $node);
+  // Add a row to add a new component.
+  $rows[] = array(
+    'class' => 'draggable',
+    'data' => array(
+      drupal_render($form['component_add']['name']),
+      NULL,
+      drupal_render($form['component_add']['weight']),
+      NULL,
+    ),
+  );
+
+  $output = drupal_render($form['issue']);
+  $output .= drupal_render($form['email']);
+  $output .= '<h3>' . t('Issue components') . '</h3>';
+  $output .= '<div>' . theme('table', $header, $rows, array('id' => 'project-issue-edit-project-components-table')) . '</div>';
+  $output .= drupal_render($form);
+  return $output;
 }
 
 /**
@@ -40,13 +88,6 @@ function project_issue_project_edit_form
     '#default_value' => isset($node->project_issue['issues']) ? $node->project_issue['issues'] : 1,
     '#description' => t('Let users submit bug requests, patches, feature requests, support requests, etc.'),
   );
-  $form['issue']['components'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Components'),
-    '#default_value' => isset($node->project_issue['components']) ? $node->project_issue['components'] : NULL,
-    '#cols' => 20,
-    '#rows' => 5,
-  );
   $form['issue']['help'] = array(
     '#type' => 'textarea',
     '#title' => t('Submission guidelines'),
@@ -55,11 +96,56 @@ function project_issue_project_edit_form
     '#rows' => 5,
   );
 
+  $weight = 0;
+  $default_component_options[0] = '';
+  $form['component']['#tree'] = TRUE;
+  foreach ($node->project_issue['components'] as $component) {
+    $default_component_options[$component] = '';
+    $form['component'][$component]['name'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $component,
+      '#size' => 20,
+    );
+    $form['component'][$component]['weight'] = array(
+      '#type' => 'weight',
+      '#default_value' => $weight,
+      '#delta' => 15,
+      '#attributes' => array('class' => 'project-issue-component-weight'),
+    );
+    // For the delete links, we just need the offset of the component as
+    // currently stored in the DB or node. So, we can just use the $weight.
+    // Using an integer offset avoids problems with urlencoding for component
+    // names that have '/' in them.
+    $del_link = ($component != $node->project_issue['default_component']) ? l(t('Delete'), 'node/'. $node->nid .'/edit/component/delete/'. $weight) : '';
+    $form['component'][$component]['delete'] = array(
+      '#type' => 'markup',
+      '#value' => $del_link,
+    );
+    $weight++;
+  }
+  $form['component_add']['#tree'] = TRUE;
+  $form['component_add']['name'] = array(
+    '#type' => 'textfield',
+    '#size' => 20,
+  );
+  $form['component_add']['weight'] = array(
+    '#type' => 'weight',
+    '#default_value' => $weight,
+    '#delta' => 15,
+    '#attributes' => array('class' => 'project-issue-component-weight'),
+  );
+  $form['default_component'] = array(
+    '#type' => 'radios',
+    '#options' => $default_component_options,
+    '#default_value' => !empty($node->project_issue['default_component']) ? $node->project_issue['default_component'] : 0,
+  );
+
   /* E-mail options */
   $form['email'] = array(
     '#type' => 'fieldset',
     '#title' => t('Issue e-mail options'),
     '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
   $form['email']['mail_digest'] = array(
     '#type' => 'textfield',
@@ -151,14 +237,65 @@ function project_issue_project_edit_form
  * @see project_issue_project_edit_issues
  */
 function project_issue_project_edit_form_submit($form, &$form_state) {
-  $components = explode("\n", str_replace("\r", '', $form_state['values']['components']));
-  array_walk($components, 'project_issue_trim');
-  $components = serialize(array_values(array_filter($components)));
+  $components = array();
+  if (!empty($form_state['values']['component_add']['name'])) {
+    $components[trim($form_state['values']['component_add']['name'])] = $form_state['values']['component_add']['weight'];
+  }
+  if (!empty($form_state['values']['component'])) {
+    foreach ($form_state['values']['component'] as $component) {
+      $components[trim($component['name'])] = $component['weight'];
+    }
+  }
+  asort($components);
+  $components = serialize(array_keys($components));
+  $default_component = !empty($form_state['values']['default_component']) ? $form_state['values']['default_component'] : '';
   $mail_copy_filter = serialize($form_state['values']['mail_copy_filter']);
   $mail_copy_filter_state = serialize($form_state['values']['mail_copy_filter_state']);
 
-  db_query("UPDATE {project_issue_projects} SET issues = %d, components = '%s', mail_digest = '%s', mail_reminder = %d, mail_copy = '%s', mail_copy_filter = '%s', mail_copy_filter_state = '%s', help = '%s' WHERE nid = %d", $form_state['values']['issues'], $components, $form_state['values']['mail_digest'], $form_state['values']['mail_reminder'], $form_state['values']['mail_copy'], $mail_copy_filter, $mail_copy_filter_state, $form_state['values']['help'], $form_state['values']['nid']);
+  db_query("UPDATE {project_issue_projects} SET issues = %d, components = '%s',default_component = '%s', mail_digest = '%s', mail_reminder = %d, mail_copy = '%s', mail_copy_filter = '%s', mail_copy_filter_state = '%s', help = '%s' WHERE nid = %d", $form_state['values']['issues'], $components, $default_component, $form_state['values']['mail_digest'], $form_state['values']['mail_reminder'], $form_state['values']['mail_copy'], $mail_copy_filter, $mail_copy_filter_state, $form_state['values']['help'], $form_state['values']['nid']);
   db_query("UPDATE {node} SET changed = %d WHERE nid = %d", time(), $form_state['values']['nid']);
   drupal_set_message(t('Issue settings have been saved.'));
 }
 
+function project_issue_component_delete_form($form_state, $project, $component_offset) {
+  $component = $project->project_issue['components'][$component_offset];
+  $form['component'] = array(
+    '#type' => 'value',
+    '#value' => $component,
+  );
+  $form['project'] = array(
+    '#type' => 'value',
+    '#value' => $project,
+  );
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the component %component?', array('%component' => $component)),
+    'node/' . $project->nid . '/edit/issues',
+    t('This action cannot be undone.'),
+    t('Delete'), t('Cancel')
+  );
+}
+
+function project_issue_component_delete_form_submit($form, &$form_state) {
+  $project = $form_state['values']['project'];
+  $component = $form_state['values']['component'];
+  project_issue_delete_component($project, $component);
+  drupal_set_message(t('Issue component %component deleted.', array('%component' => $component)));
+  $form_state['redirect'] = 'node/' . $project->nid . '/edit/issues';
+}
+
+/**
+ * Remove a component from a given project.
+ *
+ * @param $project
+ *   The project to remove the component from.
+ * @param $component
+ *   The component to remove.
+ */
+function project_issue_delete_component($project, $component) {
+  $components = array_diff($project->project_issue['components'], array($component));
+  db_query("UPDATE {project_issue_projects} SET components = '%s' WHERE nid = %d", serialize($components), $project->nid);
+  db_query("UPDATE {node} SET changed = %d WHERE nid = %d", time(), $project->nid);
+}
+
+
