Index: API.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/API.txt,v
retrieving revision 1.45
diff -u -F^f -r1.45 API.txt
--- API.txt	18 Jul 2008 00:11:03 -0000	1.45
+++ API.txt	18 Jul 2008 13:44:17 -0000
@@ -63,6 +63,11 @@
       'editability' => array(
         'status'           => 0,
         'item_types'       => array(),
+        'allowed_levels'   => array(
+          0 => 0,
+          1 => 0,
+          2 => 1,
+        ),
         'allow_new_levels' => 0,
         'max_levels'       => 3,
       ),
@@ -140,6 +145,16 @@
    to create a new item, the default label for the new item will be of the
    form "new <item type>", e.g. "new region".
 
+ - editability['allowed_levels'] (optional, defaults to 1 for each level)
+   Only meaningful when editable is set to TRUE.
+   Specify in which levels the user is allowed to create new items. In the
+   example, the user is only allowed to create new items in the third level.
+   When a setting for a level is ommitted, it defaults to 1 (i.e. allowed for
+   that level). This means you only have to specify in which levels the user
+   is not allowed to create new items.
+   This only applies to *existing* levels: it does not affect the
+   allow_new_levels setting (the next setting).
+
  - editability['allow_new_levels'] (optional, defaults to 0)
    Only meaningful when editable is set to TRUE.
    Allow the user to create new levels, i.e. when a certain item does not yet
Index: hierarchical_select.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/hierarchical_select.admin.inc,v
retrieving revision 1.9
diff -u -F^f -r1.9 hierarchical_select.admin.inc
--- hierarchical_select.admin.inc	18 Jul 2008 00:11:03 -0000	1.9
+++ hierarchical_select.admin.inc	18 Jul 2008 13:44:17 -0000
@@ -254,6 +254,11 @@ function _hierarchical_select_create_exp
   $output .= "      $depth => '$item_type',\n";
   }
   $output .= "    ),\n";
+  $output .= "    'allowed_levels' => array(\n";
+  foreach ($config['editability']['allowed_levels'] as $depth => $allowed_level) {
+  $output .= "      $depth => '$allowed_level',\n";
+  }
+  $output .= "    ),\n";
   $output .= "    'allow_new_levels' => ". $config['editability']['allow_new_levels'] .",\n";
   $output .= "    'max_levels'       => ". $config['editability']['max_levels'] .",\n";
   $output .= "  ),\n";
Index: hierarchical_select.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/hierarchical_select.module,v
retrieving revision 1.117
diff -u -F^f -r1.117 hierarchical_select.module
--- hierarchical_select.module	18 Jul 2008 00:11:03 -0000	1.117
+++ hierarchical_select.module	18 Jul 2008 13:44:17 -0000
@@ -125,6 +125,7 @@ function hierarchical_select_elements() 
       'editability' => array(
         'status'           => 0,
         'item_types'       => array(),
+        'allowed_levels'   => array(),
         'allow_new_levels' => 0,
         'max_levels'       => 3,
       ),
@@ -405,7 +406,7 @@ function hierarchical_select_process($el
   // Check if a new item is being created.
   if (isset($element['#value']['hierarchical_select']['selects'])) {
     foreach ($element['#value']['hierarchical_select']['selects'] as $depth => $value) {
-      if ($value == 'create_new_item') {
+      if ($value == 'create_new_item' && _hierarchical_select_create_new_item_is_allowed($config, $depth)) {
         // We want to override the select in which the "create_new_item"
         // option was selected and hide all selects after that, if they exist.
         $element['hierarchical_select']['selects'] = array_slice($element['hierarchical_select']['selects'], 0, $depth + 1);
@@ -450,7 +451,7 @@ function hierarchical_select_process($el
 
   if ($config['dropbox']['status']) {
     // Append an "Add" button to the selects.
-    $element['hierarchical_select']['dropbox-add'] = array(
+    $element['hierarchical_select']['dropbox_add'] = array(
       '#type'       => 'button',
       '#value'      => t('Add'),
       '#attributes' => array('class' => 'add-to-dropbox'),
@@ -793,16 +794,22 @@ function _hierarchical_select_process_ca
 
       $label = trim($element['#value']['hierarchical_select']['create_new_item']['input']);
       $selects = $element['#value']['hierarchical_select']['selects'];
+      $depth = count($selects);
 
       // Disallow items with empty labels; allow the user again to create a
       // (proper) new item.
       if (empty($label)) {
         $element['#value']['hierarchical_select']['selects'][count($selects)] = 'create_new_item';
       }
-      // Ensure that this new item will not violate the max_levels setting.
-      else if ($config['editability']['max_levels'] == 0 || count($selects) < $config['editability']['max_levels']) {
+      // Ensure that this new item will not violate the max_levels and
+      // allowed_levels settings.
+      else if (
+        ($config['editability']['max_levels'] == 0 || $depth < $config['editability']['max_levels'])
+        &&
+        (_hierarchical_select_create_new_item_is_allowed($config, $depth))
+      ) {
         // Create the new item in the hierarchy and retrieve its value.
-        $parent = (count($selects) > 0) ? end($selects) : 0;
+        $parent = ($depth > 0) ? end($selects) : 0;
         $value = module_invoke($config['module'], 'hierarchical_select_create_item', check_plain($label), $parent, $config['params']);
 
         // Ensure the newly created item will be selected after rendering.
@@ -1153,6 +1160,10 @@ function _hierarchical_select_mark_as_di
   }
 }
 
+function _hierarchical_select_create_new_item_is_allowed($config, $depth) {
+  return (isset($config['editability']['allowed_levels'][$depth])) ? $config['editability']['allowed_levels'][$depth] : 1;
+}
+
 /**
  * Helper function that generates the help text is that is displayed to the
  * user when Javascript is disabled.
@@ -1390,13 +1401,17 @@ function _hierarchical_select_hierarchy_
   // Start building the levels, initialize with the root level.
   $hierarchy->levels[0] = module_invoke($config['module'], 'hierarchical_select_root_level', $config['params']);
 
-  // Prepend a "<create new item>" option to the root level when the editable
-  // setting is enabled.
-  // NOTE: this is an optional hook, so we also check if it's implemented.
-  if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item')) {
+  // Prepend a "<create new item>" option to the root level when:
+  // - the editability setting is enabled, and
+  // - the hook is implemented (this is an optional hook), and
+  // - the allowed_levels setting allows to create new items at this level.
+  if ($config['editability']['status']
+      && module_hook($config['module'], 'hierarchical_select_create_item')
+      && _hierarchical_select_create_new_item_is_allowed($config, 0)
+  ) {
     $item_type = $config['editability']['item_types'][0];
-    $item_type = (!empty($item_type)) ? $item_type : 'item';
-    $hierarchy->levels[0] = array('create_new_item' => '<'. t('create new !item', array('!item' => $item_type)) .'>') + $hierarchy->levels[0];
+    $item_type = (!empty($item_type)) ? $item_type : t('item');
+    $hierarchy->levels[0] = array('create_new_item' => '<'. t('create new !item_type', array('!item_type' => $item_type)) .'>') + $hierarchy->levels[0];
   }
 
   // Prepend a "<none>" option to the root level when:
@@ -1421,14 +1436,19 @@ function _hierarchical_select_hierarchy_
   }
 
   if ($config['enforce_deepest']) {
-    // If the editable setting is enabled, create "<create new item>" options
-    // in all levels below the root level.
-    // NOTE: this is an optional hook, so we also check if it's implemented.
-    if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item')) {
+    // Prepend a "<create new item>" option to each level below the root level
+    // when:
+    // - the editability setting is enabled, and
+    // - the hook is implemented (this is an optional hook), and
+    // - the allowed_levels setting allows to create new items at this level.
+    if ($config['editability']['status']
+        && module_hook($config['module'], 'hierarchical_select_create_item')
+        && _hierarchical_select_create_new_item_is_allowed($config, $depth)
+    ) {
       for ($depth = 1; $depth <= $max_depth; $depth++) {
         $item_type = $config['editability']['item_types'][$depth];
-        $item_type = (!empty($item_type)) ? $item_type : 'item';
-        $hierarchy->levels[$depth] = array('create_new_item' => '<'. t('create new !item', array('!item' => $item_type)) .'>') + $hierarchy->levels[$depth];
+        $item_type = (!empty($item_type)) ? $item_type : t('item');
+        $hierarchy->levels[$depth] = array('create_new_item' => '<'. t('create new !item_type', array('!item_type' => $item_type)) .'>') + $hierarchy->levels[$depth];
       }
     }
 
@@ -1440,12 +1460,19 @@ function _hierarchical_select_hierarchy_
   else if (!$config['enforce_deepest']) {
     // Prepend special options to every level.
     for ($depth = 0; $depth <= $max_depth; $depth++) {
-      // A "<create new item>" option, if the editable setting is enabled.
-      // NOTE: this is an optional hook, so we also check if it's implemented.
-      if ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item') && $depth > 0) { // The root level already has this.
+      // Prepend a "<create new item>" option to the current level when:
+      // - this is not the root level (the root level already has this), and
+      // - the editability setting is enabled, and
+      // - the hook is implemented (this is an optional hook), and
+      // - the allowed_levels setting allows to create new items at this level.
+      if ($depth > 0
+          && $config['editability']['status']
+          && module_hook($config['module'], 'hierarchical_select_create_item')
+          && _hierarchical_select_create_new_item_is_allowed($config, $depth)
+      ) {
         $item_type = $config['editability']['item_types'][$depth];
-        $item_type = (!empty($item_type)) ? $item_type : 'item';
-        $hierarchy->levels[$depth] = array('create_new_item' => '<'. t('create new !item', array('!item' => $item_type)) .'>') + $hierarchy->levels[$depth];
+        $item_type = (!empty($item_type)) ? $item_type : t('item');
+        $hierarchy->levels[$depth] = array('create_new_item' => '<'. t('create new !item_type', array('!item_type' => $item_type)) .'>') + $hierarchy->levels[$depth];
       }
       // Level label: set an empty level label if they've been disabled.
       $label = ($config['level_labels']['status']) ? $config['level_labels']['labels'][$depth] : '';
@@ -1460,11 +1487,20 @@ function _hierarchical_select_hierarchy_
         // We're good, let's add one level!
         $depth = $max_depth + 1;
 
-        // A "<create new item>" option, if the editable setting is enabled.
-        // NOTE: this uses an optional hook, so we also check if it's implemented.
-        $item_type = $config['editability']['item_types'][$depth];
-        $item_type = (!empty($item_type)) ? $item_type : 'item';
-        $hierarchy->levels[$depth] = ($config['editability']['status'] && module_hook($config['module'], 'hierarchical_select_create_item')) ? array('create_new_item' => '<'. t('create new !item', array('!item' => $item_type)) .'>') : array();
+        $hierarchy->levels[$depth] = array();
+
+        // Prepend a "<create new item>" option to the current level when:
+        // - the editability setting is enabled, and
+        // - the hook is implemented (this is an optional hook), and
+        // - the allowed_levels setting allows to create new items at this level.
+        if ($config['editability']['status']
+            && module_hook($config['module'], 'hierarchical_select_create_item')
+            && _hierarchical_select_create_new_item_is_allowed($config, $depth)
+        ) {
+          $item_type = $config['editability']['item_types'][$depth];
+          $item_type = (!empty($item_type)) ? $item_type : t('item');
+          $hierarchy->levels[$depth] = array('create_new_item' => '<'. t('create new !item_type', array('!item_type' => $item_type)) .'>');
+        }
 
         // Level label: set an empty level label if they've been disabled.
         $hierarchy->lineage[$depth] = 'label_'. $depth;
@@ -1490,7 +1526,7 @@ function _hierarchical_select_hierarchy_
 
   // Add an extra level with only a level label and a "<create new item>"
   // option, if:
-  // - the editable setting is enabled
+  // - the editability setting is enabled
   // - the allow_new_levels setting is enabled
   // - an additional level is permitted by the max_levels setting
   // - the deepest item of the lineage is a valid item
@@ -1503,19 +1539,18 @@ function _hierarchical_select_hierarchy_
   ) {
     $depth = $max_depth + 1;
 
-    // A "<create new item>" option, if the editable setting is enabled.
-    // NOTE: this uses an optional hook, so we also check if it's implemented.
-    $item_type = $config['editability']['item_types'][$depth];
-    $item_type = (!empty($item_type)) ? $item_type : 'item';
-
     // Level label: set an empty level label if they've been disabled.
     $hierarchy->lineage[$depth] = 'label_'. $depth;
     $label = ($config['level_labels']['status']) ? $config['level_labels']['labels'][$depth] : '';
 
-    // The new level.
+    // Item type.
+    $item_type = $config['editability']['item_types'][$depth];
+    $item_type = (!empty($item_type)) ? $item_type : t('item');
+
+    // The new level with only a level label and a "<create new item>" option.
     $hierarchy->levels[$depth] = array(
       'label_'. $depth  => $label,
-      'create_new_item' => '<'. t('create new !item', array('!item' => $item)) .'>',
+      'create_new_item' => '<'. t('create new !item_type', array('!item_type' => $item_type)) .'>',
     );
   }
 
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/includes/common.inc,v
retrieving revision 1.19
diff -u -F^f -r1.19 common.inc
--- includes/common.inc	16 Jul 2008 21:12:23 -0000	1.19
+++ includes/common.inc	18 Jul 2008 13:44:17 -0000
@@ -36,6 +36,7 @@ function hierarchical_select_common_conf
     'editability' => array(
       'status'           => 0,
       'item_types'       => array(),
+      'allowed_levels'   => array(),
       'allow_new_levels' => 0,
       'max_levels'       => 3,
     ),
@@ -202,8 +203,7 @@ function hierarchical_select_common_conf
   for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
     $form['level_labels']['labels'][$depth] = array(
       '#type' => 'textfield',
-      '#title' => t(($depth == 0) ? 'Label for the root level' : 'Label for sublevel !depth', array('!depth' => $depth)),
-      '#size' => 40,
+      '#size' => 20,
       '#maxlength' => 255,
       '#default_value' => $config['level_labels']['labels'][$depth],
       '#attributes' => array('class' => 'level-label'),
@@ -226,7 +226,7 @@ function hierarchical_select_common_conf
     '#type' => 'textfield',
     '#title' => t('Title'),
     '#description' => t('The title you enter here appears above the dropbox.'),
-    '#size' => 40,
+    '#size' => 20,
     '#maxlength' => 255,
     '#default_value' => $config['dropbox']['title'],
     '#attributes' => array('class' => 'dropbox-title'),
@@ -287,22 +287,18 @@ function hierarchical_select_common_conf
     for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
       $form['editability']['item_types'][$depth] = array(
         '#type' => 'textfield',
-        '#title' => t(($depth == 0) ? 'The !item_type for the root level' : 'The !item_type for sublevel !depth', array('!item_type' => $strings['item_type'], '!depth' => $depth)),
         '#size' => 20,
         '#maxlength' => 255,
         '#default_value' => $config['editability']['item_types'][$depth],
-        '#description' => ($depth > 0) ? '' : t(
-          'The %item_type you enter for each level is what will be used in
-          each level to replace a "&lt;create new item&gt;" option with a
-          "&lt;create new %item_type&gt;" option, which is often more
-          intuitive.',
-          array(
-            '%item_type' => $strings['item_type'],
-          )
-        ),
         '#attributes' => array('class' => 'editability-item-type'),
       );
     }
+    for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
+      $form['editability']['allowed_levels'][$depth] = array(
+        '#type' => 'checkbox',
+        '#default_value' => (isset($config['editability']['allowed_levels'][$depth])) ? $config['editability']['allowed_levels'][$depth] : 1,
+      );
+    }
     $form['editability']['allow_new_levels'] = array(
       '#type' => 'checkbox',
       '#title' => t('Allow creation of new levels'),
@@ -328,12 +324,107 @@ function hierarchical_select_common_conf
       ),
       '#attributes' => array('class' => 'editability-max-levels'),
     );
+
+    $form['level_labels']['#theme'] = 'hierarchical_select_common_config_form_level_labels';
+    $form['level_labels']['#strings'] = $strings;
+    $form['editability']['#theme'] = 'hierarchical_select_common_config_form_editability';
+    $form['editability']['#strings'] = $strings;
   }
 
   return $form;
 }
 
 /**
+ * Themeing function to render the level_labels settings as a table.
+ */
+function theme_hierarchical_select_common_config_form_level_labels($form) {
+  // Recover the stored strings.
+  $strings = $form['#strings'];
+
+  $output = '';
+  $header = array(t('Level'), t('Label'));
+  $rows = array();
+
+  $output .= drupal_render($form['status']);
+
+  $output .= '<div class="level-labels-settings">';
+  if (count(element_children($form['labels']))) {
+    foreach (element_children($form['labels']) as $depth) {
+      $row = array();
+      $row[]['data'] = ($depth == 0) ? t('Root level') : t('Sublevel !depth', array('!depth' => $depth));
+      $row[]['data'] = drupal_render($form['labels'][$depth]);
+      $rows[] = $row;
+    }
+
+    // Render the table.
+    $output .= theme('table', $header, $rows, array('style' => 'width: auto;'));
+  }
+  else {  
+    // No levels exist yet in the hierarchy!
+    $output .= '<p><strong>';
+    $output .= t('There are no levels yet in this !hierarchy!', array('!hierarchy' => $strings['hierarchy']));
+    $output .= '</strong></p>';
+  }
+  $output .= '</div>';
+
+  // Render the remaining form items.
+  $output .= drupal_render($form);
+
+  return $output;
+}
+/**
+ * Themeing function to render the per-level editability settings as a table,
+ * (these are the item_types and allowed_levels settings).
+ */
+function theme_hierarchical_select_common_config_form_editability($form) {
+  // Recover the stored strings.
+  $strings = $form['#strings'];
+
+  $output = '';
+  $header = array(t('Level'), t('Allow'), t('!item_type', array('!item_type' => ucfirst($strings['item_type']))));
+  $rows = array();
+
+  $output .= drupal_render($form['status']);
+
+  $output .= '<div class="editability-per-level-settings">';
+  if (count(element_children($form['item_types']))) {
+    foreach (element_children($form['item_types']) as $depth) {
+      $row = array();
+      $row[]['data'] = ($depth == 0) ? t('Root level') : t('Sublevel !depth', array('!depth' => $depth));
+      $row[]['data'] = drupal_render($form['allowed_levels'][$depth]);
+      $row[]['data'] = drupal_render($form['item_types'][$depth]);
+      $rows[] = $row;
+    }
+
+    // Render the table and description.
+    $output .= theme('table', $header, $rows, array('style' => 'width: auto;'), '<em>'. t('Per-level settings for creating new !items.', array('!items' => $strings['items'])));
+    $output .= '<div class="description">';
+    $output .= t(
+      'The %item_type you enter for each level is what will be used in
+      each level to replace a "&lt;create new item&gt;" option with a
+      "&lt;create new %item_type&gt;" option, which is often more
+      intuitive.',
+      array(
+        '%item_type' => $strings['item_type'],
+      )
+    );
+    $output .= '</div>';
+  }
+  else {  
+    // No levels exist yet in the hierarchy!
+    $output .= '<p><strong>';
+    $output .= t('There are no levels yet in this !hierarchy!', array('!hierarchy' => $strings['hierarchy']));
+    $output .= '</strong></p>';
+  }
+  $output .= '</div>';
+
+  // Render the remaining form items.
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
  * Submit callback for the hierarchical_select_common_config_form form.
  */
 function hierarchical_select_common_config_form_submit($form_id, $form_values, $parents) {
Index: includes/common_config_form.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/includes/common_config_form.js,v
retrieving revision 1.6
diff -u -F^f -r1.6 common_config_form.js
--- includes/common_config_form.js	28 Jun 2008 18:12:23 -0000	1.6
+++ includes/common_config_form.js	18 Jul 2008 13:44:17 -0000
@@ -18,17 +18,24 @@
   var $enforceDeepest = $('.enforce-deepest input', cfg.context(configId));
 
   var showHide = function(speed) {
-    $affected = $('.level-label', cfg.context(configId)); 
+    $affected = $('.level-labels-settings', cfg.context(configId)); 
     if (!$status.is(':checked')) {
-      $affected.parent().hide(speed);
+      $affected.hide(speed);
     }
     else {
-      if ($enforceDeepest.eq(1).is(':checked')) {
-        $affected.parent().show(speed);
+      if ($enforceDeepest.slice(1, 2).is(':checked')) {
+        $affected
+        .find('tr').slice(2) // Make sure all tr's are shown.
+        .add($affected) // The entire table should be shown.
+        .show(speed, function() {
+          // jQuery leaves style="display:block;" behind, which causes badly
+          // displayed tr's.
+          $(this).removeAttr('style');
+        });
       }
       else {
-        $affected.gt(0).parent().hide(speed);
-        $affected.lt(1).parent().show(speed);
+        $affected.find('tr').slice(0, 2).show(speed); // Show header tr and root level tr.
+        $affected.find('tr').slice(2).hide(speed); // Hide all other tr's.
       }
     }
   };
@@ -60,7 +67,7 @@
   var $allowNewLevels = $('.editability-allow-new-levels', cfg.context(configId)); 
 
   var showHide = function(speed) {
-    var $affected = $('.editability-item-type, label:has(.editability-allow-new-levels)', cfg.context(configId)).parent();
+    var $affected = $('.editability-per-level-settings, .form-item:has(.editability-allow-new-levels)', cfg.context(configId));
     var $maxLevels = $('.editability-max-levels', cfg.context(configId)).parent();
     if ($status.is(':checked')) {
       if ($allowNewLevels.is(':checked')) {
