diff -rupN modules/content_multigroup/content_multigroup.css modules/content_multigroup/content_multigroup.css
--- modules/content_multigroup/content_multigroup.css	2008-10-14 17:16:50.000000000 +0200
+++ modules/content_multigroup/content_multigroup.css	2008-12-18 20:30:37.000000000 +0100
@@ -2,8 +2,29 @@
 
 label.content-multigroup {
   font-weight:bold;
-}  
+}
 
-/* Not styled by default, but available to style */
+/* Not styled by default, but available to style. */
 hr.content-multigroup {
-}  
\ No newline at end of file
+}
+
+/* Inline field labels visible within the content of multigroups. */
+.content-multigroup-wrapper .field .field-label-inline {
+  visibility:visible;
+}
+
+/* Hide field labels when using table display mode. */
+.content-multigroup-display-table .field .field-label,
+.content-multigroup-display-table .field .field-label-inline,
+.content-multigroup-display-table .field .field-label-inline-first {
+  display:none;
+}
+
+/**
+ * Hide field labels and description on the node edit form when the multiple
+ * columns option is enabled.
+ */
+.content-multigroup-edit-table-multiple-columns label,
+.content-multigroup-edit-table-multiple-columns .description {
+  display:none;
+}
diff -rupN modules/content_multigroup/content_multigroup.module modules/content_multigroup/content_multigroup.module
--- modules/content_multigroup/content_multigroup.module	2008-10-22 13:02:41.000000000 +0200
+++ modules/content_multigroup/content_multigroup.module	2008-12-18 20:35:21.000000000 +0100
@@ -37,6 +37,9 @@ function content_multigroup_theme() {
     'content_multigroup_display_simple' => array(
       'arguments' => array('element' => NULL),
     ),
+    'content_multigroup_display_fieldset' => array(
+      'arguments' => array('element' => NULL),
+    ),
     'content_multigroup_display_hr' => array(
       'arguments' => array('element' => NULL),
     ),
@@ -47,6 +50,15 @@ function content_multigroup_theme() {
 }
 
 /**
+ * Implementation of hook_elements().
+ */
+function content_multigroup_elements() {
+  return array(
+    'content_multigroup_display_fieldset' => array('#value' => NULL),
+  );
+}
+
+/**
  * Implementation of hook_fieldgroup_types().
  */
 function content_multigroup_fieldgroup_types() {
@@ -61,7 +73,7 @@ function content_multigroup_fieldgroup_d
     module_load_include('inc', 'content', 'includes/content.admin');
     $settings = array('multigroup' => array('multiple' => 1));
     foreach (array_keys(content_build_modes()) as $key) {
-      $settings['multigroup']['display_settings'][$key]['format'] = 'fieldset';
+      $settings['display'][$key]['format'] = 'fieldset';
     }
     return $settings;
   }
@@ -108,12 +120,12 @@ function content_multigroup_form_alter(&
     content_multigroup_field_overview_form($form, $form_state);
     $form['#validate'][] = 'content_multigroup_field_overview_form_validate';
   }
-  elseif ($form_id == 'content_display_overview_form') {
+  elseif ($form_id == 'content_display_overview_form' && !empty($form['#groups'])) {
     content_multigroup_display_overview_form($form, $form_state, $form_id);
     $form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']);
   }
   elseif ($form_id == 'fieldgroup_group_edit_form') {
-    return content_multigroup_group_edit_form($form, $form_state, $form_id);
+    content_multigroup_group_edit_form($form, $form_state, $form_id);
   }
 }
 
@@ -180,39 +192,37 @@ function content_multigroup_field_overvi
     }
   }
 
-  if (!empty($fields)) {
-    foreach ($fields as $field_name => $field) {
-      $new_group = $form_values[$field_name]['parent'];
-      $old_group = $form_values[$field_name]['prev_parent'];
-      if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') {
-        $allowed_in = content_multigroup_allowed_in($field, $groups[$new_group]);
-        if (!$allowed_in['allowed']) {
-          form_set_error($field_name, $allowed_in['message']);
-        }
-        else {
-          if (!empty($allowed_in['message'])) {
-            drupal_set_message($allowed_in['message']);
-          }
-          module_load_include('inc', 'content', 'includes/content.crud');
-          $content_type = content_types($type_name);
-          $multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
-          $multiple_values = content_multigroup_multiple_values();
-          $field = $content_type['fields'][$field_name];
-          $field['multiple'] = $multiple;
-          $field = content_field_instance_collapse($field);
-          content_field_instance_update($field);
-          drupal_set_message(t('The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group.', array(
-            '%field' => $field['label'], '%multiple' => $multiple_values[$multiple], '%group' => $groups[$new_group]['label'])));
-        }
+  foreach ($fields as $field_name => $field) {
+    $new_group = $form_values[$field_name]['parent'];
+    $old_group = $form_values[$field_name]['prev_parent'];
+    if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') {
+      $allowed_in = content_multigroup_allowed_in($field, $groups[$new_group]);
+      if (!$allowed_in['allowed']) {
+        form_set_error($field_name, $allowed_in['message']);
       }
-      elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
-        $allowed_out = content_multigroup_allowed_out($field, $groups[$old_group]);
-        if (!$allowed_out['allowed']) {
-          form_set_error($field_name, $allowed_out['message']);
-        }
-        elseif (!empty($allowed_out['message'])) {
-          drupal_set_message($allowed_out['message']);
+      else {
+        if (!empty($allowed_in['message'])) {
+          drupal_set_message($allowed_in['message']);
         }
+        module_load_include('inc', 'content', 'includes/content.crud');
+        $content_type = content_types($type_name);
+        $group_multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
+        $multiple_values = content_multigroup_multiple_values();
+        $field = $content_type['fields'][$field_name];
+        $field['multiple'] = $group_multiple;
+        $field = content_field_instance_collapse($field);
+        content_field_instance_update($field);
+        drupal_set_message(t('The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group.', array(
+          '%field' => $field['label'], '%multiple' => $multiple_values[$group_multiple], '%group' => $groups[$new_group]['label'])));
+      }
+    }
+    elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
+      $allowed_out = content_multigroup_allowed_out($field, $groups[$old_group]);
+      if (!$allowed_out['allowed']) {
+        form_set_error($field_name, $allowed_out['message']);
+      }
+      elseif (!empty($allowed_out['message'])) {
+        drupal_set_message($allowed_out['message']);
       }
     }
   }
@@ -230,12 +240,12 @@ function content_multigroup_allowed_in($
   // We can't allow fields with more multiple values than the group has
   // to be moved into it.
   $max_existing = content_max_delta($field['field_name']);
-  $group_max = $group['settings']['multigroup']['multiple'];
+  $group_multiple = $group['settings']['multigroup']['multiple'];
   $multiple_values = content_multigroup_multiple_values();
-  if ($group_max != 1 && $max_existing > $group_max) {
+  if ($group_multiple != 1 && $max_existing > $group_multiple) {
     return array(
       'allowed' => FALSE,
-      'message' => t('This change is not allowed. The field %field already has %multiple values in the database but the group %group only allows %group_max. Making this change would result in the loss of data.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing, '%group' => $group['label'], '%group_max' => $multiple_values[$group_max]))
+      'message' => t('This change is not allowed. The field %field already has %multiple values in the database but the group %group only allows %group_max. Making this change would result in the loss of data.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing, '%group' => $group['label'], '%group_max' => $multiple_values[$group_multiple]))
     );
   }
 
@@ -254,7 +264,7 @@ function content_multigroup_allowed_in($
       'nodereference_select',
       'userreference_buttons',
       'userreference_select',
-      );
+    );
     $allowed_widgets = array_merge($allowed_widgets, module_invoke_all('content_multigroup_allowed_widgets'));
     if (!in_array($field['widget']['type'], $allowed_widgets)) {
       return array(
@@ -295,14 +305,14 @@ function content_multigroup_allowed_out(
 
   $max_existing = content_max_delta($field['field_name']);
   $no_remove_widgets = array(
-      'optionwidgets_select',
-      'optionwidgets_buttons',
-      'optionwidgets_onoff',
-      'nodereference_buttons',
-      'nodereference_select',
-      'userreference_buttons',
-      'userreference_select',
-      );
+    'optionwidgets_select',
+    'optionwidgets_buttons',
+    'optionwidgets_onoff',
+    'nodereference_buttons',
+    'nodereference_select',
+    'userreference_buttons',
+    'userreference_select',
+  );
   $no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('content_multigroup_no_remove_widgets'));
   if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) {
     return array(
@@ -336,14 +346,17 @@ function content_multigroup_display_over
   $contexts_selector = $form['#contexts'];
 
   // Gather type information.
-  $type = content_types($type_name);
-  $field_types = _content_field_types();
-  $fields = $type['fields'];
+  $content_type = content_types($type_name);
+
+  // The content module stops building the form if the type has no fields.
+  if (empty($content_type['fields'])) {
+    return;
+  }
 
   $groups = $group_options = array();
   if (module_exists('fieldgroup')) {
-    $groups = fieldgroup_groups($type['type']);
-    $group_options = _fieldgroup_groups_label($type['type']);
+    $groups = fieldgroup_groups($type_name);
+    $group_options = _fieldgroup_groups_label($type_name);
   }
   $contexts = content_build_modes($contexts_selector);
 
@@ -353,41 +366,41 @@ function content_multigroup_display_over
     'hidden' => t('<Hidden>'),
   );
   $options = array(
-    'none' => t('none'),
+    'simple' => t('Simple'),
     'fieldset' => t('Fieldset'),
     'hr' => t('Horizontal line'),
-    //'table' => t('Table'), // TODO add this later
-    'hidden' => t('<Hidden>'),
+    'table' => t('Table'),
   );
-  foreach ($groups as $name => $group) {
+  foreach ($groups as $group_name => $group) {
     if ($group['group_type'] != 'multigroup') {
       continue;
     }
-    $defaults = $group['settings']['multigroup']['display_settings'];
+    $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
 
-    $form_name = $name .'_subgroup';
-    $form['#fields'] = array_merge(array($form_name), $form['#fields']);
-    $form[$form_name] = array(
+    $subgroup_name = $group_name .'_subgroup';
+    $form['#fields'] = array_merge(array($subgroup_name), $form['#fields']);
+    $form[$subgroup_name] = array(
       'human_name' => array('#value' => t('[Subgroup format]')),
       'weight' => array('#type' => 'value', '#value' => -20),
-      'parent' => array('#type' => 'value', '#value' => $name),
+      'parent' => array('#type' => 'value', '#value' => $group_name),
+      'subgroup' => array('#type' => 'value', '#value' => 1),
     );
     if ($contexts_selector == 'basic') {
-      $form[$form_name]['label'] = array(
+      $form[$subgroup_name]['label'] = array(
         '#type' => 'select',
         '#options' => $label_options,
-        '#default_value' => isset($defaults['label']) ? $defaults['label'] : 'above',
+        '#default_value' => isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above',
       );
     }
     foreach ($contexts as $key => $title) {
-      $form[$form_name][$key]['format'] = array(
+      $form[$subgroup_name][$key]['format'] = array(
         '#type' => 'select',
         '#options' => $options,
-        '#default_value' => isset($defaults[$key]) ? $defaults[$key] : 'fieldset',
+        '#default_value' => isset($subgroup_settings[$key]['format']) ? $subgroup_settings[$key]['format'] : 'fieldset',
       );
+      $form[$subgroup_name][$key]['exclude'] = array('#type' => 'value', '#value' => 0);
     }
   }
-  return $form;
 }
 
 /**
@@ -397,19 +410,18 @@ function content_multigroup_display_over
  * tries to use our 'field'.
  */
 function content_multigroup_display_overview_form_submit($form, &$form_state) {
-  $form_values = $form_state['values'];
-
   // Find any groups we inserted into the display fields form,
   // save our settings, and remove them from $form_state.
-  foreach ($form_values as $key => $values) {
-    if (in_array($key, $form['#fields']) && substr($key, -9) == '_subgroup') {
-      $group_name = str_replace('_subgroup', '', $key);
+  foreach ($form_state['values'] as $key => $values) {
+    if (in_array($key, $form['#fields']) && !empty($values['parent']) && !empty($values['subgroup'])) {
+      $group_name = $values['parent'];
       $groups = fieldgroup_groups($form['#type_name']);
       $group = $groups[$group_name];
+      unset($values['subgroup'], $values['parent']);
 
       // We have some numeric keys here, so we can't use array_merge.
       foreach ($values as $k => $v) {
-        $group['settings']['multigroup']['display_settings'][$k] = $v;
+        $group['settings']['multigroup']['subgroup'][$k] = $v;
       }
       fieldgroup_save_group($form['#type_name'], $group);
 
@@ -430,7 +442,7 @@ function content_multigroup_group_edit_f
   $group_name = $form['group_name']['#default_value'];
 
   $content_type = content_types($type_name);
-  $groups = fieldgroup_groups($content_type['type']);
+  $groups = fieldgroup_groups($type_name);
   $group = $groups[$group_name];
 
   if ($group['group_type'] != 'multigroup') {
@@ -442,25 +454,38 @@ function content_multigroup_group_edit_f
   $form['group_type'] = array(
     '#type' => 'hidden',
     '#value' => $group['group_type'],
-    );
+  );
   $form['settings']['multigroup'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Other settings'),
+    '#title' => t('Multigroup settings'),
     '#collapsed' => FALSE,
     '#collapsible' => TRUE,
   );
 
+  $form['settings']['multigroup']['multiple-columns'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Multiple columns'),
+    '#default_value' => isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0,
+    '#description' => t('Enable this option to render each field on a separate column on the node edit form.'),
+  );
+
+  $form['settings']['multigroup']['required'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Required'),
+    '#default_value' => isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 1,
+    '#description' => t('Enable this option to require a minimum of one collection of fields in this Multigroup.'),
+  );
+
   $description = t('Number of times to repeat the collection of Multigroup fields.') . ' ';
-  $description .= t("'Unlimited' will provide an 'Add more' button so the users can add repeat it as many times as they like.") . ' ';
+  $description .= t("'Unlimited' will provide an 'Add more' button so the users can add items as many times as they like.") . ' ';
   $description .= t('All fields in this group will automatically be set to allow this number of values.');
 
-  $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
+  $group_multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
   $form['settings']['multigroup']['multiple'] = array(
-    '#tree' => TRUE,
     '#type' => 'select',
     '#title' => t('Number of repeats'),
     '#options' => content_multigroup_multiple_values(),
-    '#default_value' => $multiple,
+    '#default_value' => $group_multiple,
     '#description' => $description,
   );
 
@@ -469,8 +494,8 @@ function content_multigroup_group_edit_f
     '#title' => t('Labels'),
     '#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."),
   );
-  if ($multiple < 2) {
-    $multiple = 0;
+  if ($group_multiple < 2) {
+    $group_multiple = 0;
   }
   for ($i = 0; $i < 10; $i++) {
     $form['settings']['multigroup']['labels'][$i] = array(
@@ -482,7 +507,6 @@ function content_multigroup_group_edit_f
 
   $form['#validate'][] = 'content_multigroup_group_edit_form_validate';
   $form['#submit'][] = 'content_multigroup_group_edit_form_submit';
-  return $form;
 }
 
 /**
@@ -523,10 +547,9 @@ function content_multigroup_group_edit_f
   $content_type = $form['#content_type'];
   $groups = fieldgroup_groups($content_type['type']);
   $group = $groups[$form_values['group_name']];
-  $multiple = $form_values['settings']['multigroup']['multiple'];
-  foreach ($group['fields'] as $field_name => $data) {
-    $field = $content_type['fields'][$field_name];
-    $field['multiple'] = $multiple;
+  $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+  foreach ($group_fields as $field_name => $field) {
+    $field['multiple'] = $form_values['settings']['multigroup']['multiple'];
     $field = content_field_instance_collapse($field);
     content_field_instance_update($field);
   }
@@ -541,65 +564,107 @@ function content_multigroup_group_edit_f
  * d-n-d each collection of fields as a single delta item.
  */
 function content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) {
-  if ($group['group_type'] != 'multigroup' ||
-    !empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) {
+  $group_name = $group['group_name'];
+  if ($group['group_type'] != 'multigroup' || !empty($form[$group_name]['#access']) || empty($form[$group_name])) {
     return;
   }
 
   $node = $form['#node'];
-  $fields = $group['fields'];
-  $content_fields = content_fields();
-  $group_name = $group['group_name'];
-
-  // Use the first field in the group to get the item counts.
-  $first_field_name = array_shift(array_keys($group['fields']));
-  $first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array();
-  $first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array();
+  $content_type = content_types($group['type_name']);
+  $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+  $group_multiple = $group['settings']['multigroup']['multiple'];
 
-  $group['multiple'] = $group['settings']['multigroup']['multiple'];
-  switch ($group['multiple']) {
+  switch ($group_multiple) {
     case 0:
-      $max = 0;
+      $group_deltas = array(0);
+      $max_delta = 0;
       break;
+
     case 1:
-      // Is this a new node?
-      if (empty($first_field_items)) {
-        $max = 1;
+      // Compute deltas based on the field with the highest number of items.
+      $group_deltas = array();
+      $max_delta = -1;
+      foreach ($group_fields as $field_name => $field) {
+        $field_items = isset($node->$field_name) ? $node->$field_name : array();
+        if (!empty($field_items)) {
+          $field = $group_fields[$field_name];
+          $field_deltas = array_keys(content_set_empty($field, $field_items));
+          $field_max = (!empty($field_deltas) ? max($field_deltas) : 0);
+          if ($field_max > $max_delta || empty($group_deltas)) {
+            $max_delta = $field_max;
+            $group_deltas = $field_deltas;
+          }
+        }
+      }
+      $current_item_count = (isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : count($group_deltas));
+      if ($current_item_count > 0) {
+        // We always want at least one empty item for the user to fill in.
+        $current_item_count++;
       }
       else {
-        $filled_items = content_set_empty($first_field, $first_field_items);
-        $current_item_count = isset($form_state['item_count'][$group_name])
-                            ? $form_state['item_count'][$group_name]
-                            : count($first_field_items);
-        // We always want at least one empty icon for the user to fill in.
-        $max = ($current_item_count > count($filled_items))
-              ? $current_item_count - 1
-              : count($filled_items);
+        // Default number of empty items when none is present.
+        $current_item_count = 1;
+      }
+      while (count($group_deltas) < $current_item_count) {
+        $max_delta++;
+        $group_deltas[] = $max_delta;
       }
       break;
+
     default:
-      $max = $group['multiple'] - 1;
+      $group_deltas = array_keys(array_fill(0, $group_multiple, 0));
+      $max_delta = $group_multiple - 1;
       break;
   }
 
   $form[$group_name]['#theme'] = 'content_multigroup_node_form';
-  $form[$group_name]['#multiple'] = !empty($max);
+  $form[$group_name]['#item_count'] = count($group_deltas);
   $form[$group_name]['#type_name'] = $group['type_name'];
   $form[$group_name]['#group_name'] = $group_name;
   $form[$group_name]['#group_label'] = $group['label'];
-  $form[$group_name]['#element_validate'] = array('content_multigroup_node_form_validate');
   $form[$group_name]['#tree'] = TRUE;
+  if (!isset($form['#multigroups'])) {
+    $form['#multigroups'] = array();
+  }
+  $form['#multigroups'][$group_name] = $group_fields;
+
+  // Attach our own after build handler to the form, used to fix posting data
+  // and the form structure, moving fields back to their original positions.
+  // That is, move them from group->delta->field back to field->delta.
+  if (!isset($form['#after_build'])) {
+    $form['#after_build'] = array();
+  }
+  if (!in_array('content_multigroup_node_form_after_build', $form['#after_build'])) {
+    array_unshift($form['#after_build'], 'content_multigroup_node_form_after_build');
+  }
+
+  // Attach our own validation handler to the form, used to check for empty fields.
+  if (!isset($form['#validate'])) {
+    $form['#validate'] = array();
+  }
+  if (!in_array('content_multigroup_node_form_validate', $form['#validate'])) {
+    array_unshift($form['#validate'], 'content_multigroup_node_form_validate');
+  }
+
+  // Attach our own pre_render handler to the form, used to fix the required
+  // attribute of all fields in multigroups.
+  if (!isset($form['#pre_render'])) {
+    $form['#pre_render'] = array();
+  }
+  if (!in_array('content_multigroup_node_form_pre_render', $form['#pre_render'])) {
+    array_unshift($form['#pre_render'], 'content_multigroup_node_form_pre_render');
+  }
 
-  for ($delta = 0; $delta <= $max; $delta++) {
+  foreach ($group_deltas as $delta) {
     content_multigroup_group_form($form, $form_state, $group, $delta);
   }
 
   // Unset the original group field values now that we've moved them.
-  foreach ($fields as $field_name => $field) {
+  foreach (array_keys($group_fields) as $field_name) {
     unset($form[$group_name][$field_name]);
   }
 
-  if ($add_more = content_multigroup_add_more($form, $form_state, $group)) {
+  if (($add_more = content_multigroup_add_more($form, $form_state, $group)) !== FALSE) {
     $form[$group_name] += $add_more;
   }
 }
@@ -617,13 +682,14 @@ function content_multigroup_group_form(&
   module_load_include('inc', 'content', 'includes/content.node_form');
 
   $node = $form['#node'];
-  $fields = $group['fields'];
-  $content_fields = content_fields();
+  $type_name = $group['type_name'];
+  $content_type = content_types($type_name);
+  $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
   $group_name = $group['group_name'];
-  $group['multiple'] = $group['settings']['multigroup']['multiple'];
-  $form[$group_name]['#fields'] = array_keys($group['fields']);
+  $group_multiple = $group['settings']['multigroup']['multiple'];
+  $form[$group_name]['#fields'] = array_keys($group_fields);
 
-  foreach ($fields as $field_name => $group_field) {
+  foreach ($group_fields as $field_name => $field) {
     if (empty($form[$group_name][$delta])) {
       $form[$group_name] += array($delta => array($field_name => array()));
     }
@@ -631,19 +697,13 @@ function content_multigroup_group_form(&
       $form[$group_name][$delta][$field_name] = array();
     }
 
+    $item_count = (isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : $form[$group_name]['#item_count']);
     $form[$group_name][$delta]['_weight'] = array(
       '#type' => 'weight',
-      '#delta' => $delta, // this 'delta' is the 'weight' element's property
+      '#delta' => $item_count, // this 'delta' is the 'weight' element's property
       '#default_value' => $delta,
       '#weight' => 100,
     );
-    $form[$group_name][$delta]['_delta'] = array(
-      '#type' => 'hidden',
-      '#value' => $delta,
-    );
-
-
-    $field = $content_fields[$field_name];
 
     // Make each field into a pseudo single value field
     // with the right delta value.
@@ -668,325 +728,424 @@ function content_multigroup_group_form(&
       $node_copy->$field_name = array($delta => NULL);
     }
     $form['#node'] = $node_copy;
-    $field_form = content_field_form($form, $form_state, $field, $delta);
 
-    // Place the new $field_form into the $delta position in the group form.
+    // Place the new element into the $delta position in the group form.
     if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
+      $field_form = content_field_form($form, $form_state, $field, $delta);
       $value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0;
       $form[$group_name][$delta][$field_name] = $field_form[$field_name][$value];
     }
     else {
+      // When the form is submitted, get the element data from the form values.
+      if (isset($form_state['values'][$field_name])) {
+        $form_state_copy = $form_state;
+        if (isset($form_state_copy['values'][$field_name][$delta])) {
+          $form_state_copy['values'][$field_name] = array($delta => $form_state_copy['values'][$field_name][$delta]);
+        }
+        else {
+          $form_state_copy['values'][$field_name] = array($delta => NULL);
+        }
+        $field_form = content_field_form($form, $form_state_copy, $field, $delta);
+      }
+      else {
+        $field_form = content_field_form($form, $form_state, $field, $delta);
+      }
+
+      // Multiple value fields have an additional level in the array form that
+      // needs to get fixed in $form_state['values'].
+      if (!isset($field_form[$field_name]['#element_validate'])) {
+        $field_form[$field_name]['#element_validate'] = array();
+      }
+      $field_form[$field_name]['#element_validate'][] = 'content_multigroup_fix_multivalue_fields';
+
       $form[$group_name][$delta][$field_name] = $field_form[$field_name];
     }
     $form[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight'];
-
-    // Add in our validation step, and make sure it preceeds other
-    // processing so we can massage the element back to the normal position.
-    if (empty($form[$group_name][$delta][$field_name]['#element_validate'])) {
-      $form[$group_name][$delta][$field_name]['#element_validate'] = array();
-    }
-    array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'content_multigroup_node_item_validate');
   }
 
   // Reset the form '#node' back to its original value.
   $form['#node'] = $node;
+}
 
+/**
+ * Fix required flag during form rendering stage.
+ *
+ * Required fields should display the required star in the rendered form.
+ */
+function content_multigroup_node_form_pre_render(&$form) {
+  foreach ($form['#multigroups'] as $group_name => $group_fields) {
+    $required_fields = array();
+    foreach ($group_fields as $field_name => $field) {
+      if ($field['required']) {
+        $required_fields[] = $field_name;
+      }
+    }
+    if (!empty($required_fields)) {
+      content_multigroup_node_form_fix_required($form[$group_name], $required_fields, TRUE);
+    }
+  }
   return $form;
 }
 
 /**
- * Swap transposed field/delta values back
- * to their normal positions in the node.
- */
-function content_multigroup_node_item_validate($element, &$form_state) {
-  static $weights = array();
+ * Fix form and posting data when the form is submitted.
+ *
+ * FormAPI uses form_builder() during form processing to map incoming $_POST
+ * data to the proper elements in the form. It builds the '#parents' array,
+ * copies the $_POST array to the '#post' member of all form elements, and it
+ * also builds the $form_state['values'] array. Then the '#after_build' hook is
+ * invoked to allow custom processing of the form structure, and that happens
+ * just before validation and submit handlers are executed.
+ *
+ * During hook_form_alter(), the multigroup module altered the form structure
+ * moving elements from field->delta to multigroup->delta->field position,
+ * which is what has been processed by FormAPI to build the form structures,
+ * but field validation (and submit) handlers expect their data to be located
+ * in their original positions.
+ *
+ * We now need to move the fields back to their original positions in the form,
+ * and we need to do so without altering the form rendering process, which is
+ * now reflecting the structure the multigroup is interested in. We just need
+ * to fix the parts of the form that affect validation and submit processing.
+ */
+function content_multigroup_node_form_after_build($form, &$form_state) {
+  // Disable required flag during FormAPI validation.
+  foreach ($form['#multigroups'] as $group_name => $group_fields) {
+    $required_fields = array();
+    foreach ($group_fields as $field_name => $field) {
+      if ($field['required']) {
+        $required_fields[] = $field_name;
+      }
+    }
+    if (!empty($required_fields)) {
+      content_multigroup_node_form_fix_required($form[$group_name], $required_fields, FALSE);
+    }
+  }
 
-  //dsm($form_state['values']);
-  $form_values = $form_state['values'];
-  $field_name = array_pop($element['#parents']);
-  $delta = array_pop($element['#parents']);
-  $group_name = array_pop($element['#parents']);
-
-  // Identify the new delta value for each field.
-
-  // Find the original delta values for this group, save as static value
-  // because the group will acquire and lose values while we process it.
-  if (!array_key_exists($group_name, $weights)) {
-    $items = $form_state['values'][$group_name];
-    $weights[$group_name] = array();
-    foreach ($items as $count => $value) {
-      // Allow for the possibility of matching _weights and missing deltas.
-      $weight = floatval($value['_weight']);
-      $old_delta = intval($value['_delta']);
-      if (empty($weights[$group_name][$weight]) || !in_array($old_delta, $weights[$group_name][$weight])) {
-        $weights[$group_name][$weight][] = $old_delta;
-      }
-    }
-    ksort($weights[$group_name]);
-  }
-  $count = 0;
-  foreach ($weights[$group_name] as $weight => $values) {
-    foreach ($values as $old_delta) {
-      if ($old_delta === $delta) {
-        $delta = $count;
-        //dsm('moving delta values: '.$group_name.'>'.$field_name.'>'.'from '. $old_delta .' to '. $delta);
-        break 2;
-      }
-      $count++;
-    }
-  }
-  // We figured out what the new order for the fields is,
-  // so set the value for the new delta.
-
-  // We move these new values back up to the top level of the
-  // node and out of the group so the Content module will find and
-  // save the new values and so they don't get mixed into the
-  // remaining, unaltered, values in the group.
-  array_push($element['#parents'], $field_name);
-  array_push($element['#parents'], $delta);
-
-  // It's very important to use $form_values instead of $element['#value']
-  // here, because $element['#value'] is sometimes missing changes
-  // made in #element_validate processing done by other modules.
-  $value = isset($form_values[$group_name][$delta][$field_name]) ? $form_values[$group_name][$delta][$field_name] : NULL;
-
-  // Fields that use optionwidgets have an extra array level in the value
-  // because of the optionwidgets transposition that forces a delta value
-  // into the result array. This works fine when a delta value is between
-  // the field name and the field value, as in normal nodes, but not when
-  // we reverse the field and the delta, so in this case we need to
-  // promote the nested delta value back up to the field level.
-  if (is_array($value) && content_multigroup_uses_optionwidgets($field_name, $element['#type_name'])) {
-    $value = array_shift($value);
+  if ($form_state['submitted']) {
+    // Fix value positions in $form_state for the fields in multigroups.
+    foreach (array_keys($form['#multigroups']) as $group_name) {
+      content_multigroup_node_form_fix_values($form, $form_state, $form['#node']->type, $group_name);
+    }
+
+    // Fix form element parents for all fields in multigroups.
+    content_multigroup_node_form_fix_parents($form, $form['#multigroups']);
+
+    // Update posting data to reflect delta changes in the form structure.
+    if (!empty($_POST)) {
+      content_multigroup_node_form_fix_post($form);
+    }
   }
 
-  //dsm('setting value of '. $field_name.'>'.$delta);
-  //dsm($value);
-  form_set_value($element, $value, $form_state);
+  return $form;
 }
 
 /**
- * Helper function for identifying fields that use
- * optionwidgets transpositions.
+ * Fix required flag for required fields.
+ *
+ * We need to let the user enter an empty set of fields for a delta subgroup,
+ * even if it contains required fields, which is equivalent to say a subgroup
+ * should be ignored, not to be stored into the database.
+ * So, we need to check for required fields, but only for non-empty subgroups.
+ *
+ * When the form is processed for rendering, the required flag is enabled for
+ * all required fields, so the user can see what's required and what's not.
+ *
+ * When the form is processed for validation, the required flag is disabled,
+ * so that FormAPI does not report errors for empty fields.
+ *
+ * @see content_multigroup_node_form_validate().
  */
-function content_multigroup_uses_optionwidgets($field_name, $type_name) {
-  static $optionwidgets;
-  if (empty($optionwidgets)) {
-    $optionwidgets = array(
-      'optionwidgets_select',
-      'optionwidgets_buttons',
-      'optionwidgets_onoff',
-      'nodereference_buttons',
-      'nodereference_select',
-      'userreference_buttons',
-      'userreference_select',
-      );
-    // Add hook where other widgets that use optionwidgets can announce it.
-    $optionwidgets = array_merge($optionwidgets, module_invoke_all('content_multigroup_uses_optionwidgets'));
-  }
+function content_multigroup_node_form_fix_required(&$elements, $required_fields, $required) {
+  foreach (element_children($elements) as $key) {
+    if (isset($elements[$key]) && $elements[$key]) {
+
+      if (count($elements[$key]['#array_parents']) >= 3 && in_array($elements[$key]['#array_parents'][2], $required_fields) && isset($elements[$key]['#required'])) {
+        $elements[$key]['#required'] = $required;
+      }
 
-  $types = content_types($type_name);
-  $fields = $types['fields'];
-  $field = $fields[$field_name];
-  if (in_array($field['widget']['type'], $optionwidgets)) {
-    return TRUE;
+      // Recurse through all children elements.
+      content_multigroup_node_form_fix_required($elements[$key], $required_fields, $required);
+    }
   }
-  return FALSE;
 }
 
 /**
- * Validation for the whole node group.
+ * Node form validation handler.
+ *
+ * If all fields in a delta subgroup are empty, then we can let the
+ * content module remove them.
+ *
+ * @see content_set_empty().
  */
-function content_multigroup_node_form_validate($element, $form_state) {
-  // We moved all the new field values out of the field group
-  // and up to the top level of the node, now get rid of the
-  // original group values.
-  form_set_value($element, NULL, $form_state);
-  return;
+function content_multigroup_node_form_validate($form, &$form_state) {
+  $type_name = $form['#node']->type;
+  $groups = fieldgroup_groups($type_name);
+
+  foreach ($form['#multigroups'] as $group_name => $group_fields) {
+    $group = $groups[$group_name];
+    $group_required = isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 1;
+
+    $empty_required_fields = array();
+    foreach ($group_fields as $field_name => $field) {
+      if ($field['required']) {
+        $empty_required_fields[$field_name] = array();
+      }
+    }
+
+    $empty_deltas = array();
+    foreach ($group_fields as $field_name => $field) {
+      $is_empty_function = $field['module'] .'_content_is_empty';
+      foreach ($form_state['values'][$field_name] as $delta => $item) {
+        if (!isset($empty_deltas[$delta])) {
+          $empty_deltas[$delta] = TRUE;
+        }
+        if (!($is_empty = $is_empty_function($item, $field))) {
+          $empty_deltas[$delta] = FALSE;
+        }
+        $form_state['values'][$field_name][$delta]['_is_empty'] = $is_empty;
+      }
+    }
+
+    if ($group_required) {
+      $non_empty_count = count(array_filter($empty_deltas, create_function('$a', 'return !$a;')));
+      if ($non_empty_count == 0) {
+        form_set_error('', t('Group %name requires one collection of fields minimum.', array('%name' => $group_name)));
+      }
+    }
+
+    foreach ($empty_deltas as $delta => $is_empty) {
+      foreach ($group_fields as $field_name => $field) {
+        $form_state['values'][$field_name][$delta]['_keep_empty'] = $is_empty ? 0 : 1;
+
+        if (isset($empty_required_fields[$field_name]) && !$is_empty && $form_state['values'][$field_name][$delta]['_is_empty']) {
+          $empty_required_fields[$field_name][$delta] = $form_state['values'][$field_name][$delta];
+        }
+        unset($form_state['values'][$field_name][$delta]['_is_empty']);
+      }
+    }
+
+    foreach ($empty_required_fields as $field_name => $items) {
+      foreach ($items as $delta => $item) {
+        if (!empty($item['_error_element'])) {
+          $error_element = explode('][', $item['_error_element']);
+          array_shift($error_element);
+          array_shift($error_element);
+          array_shift($error_element);
+          array_unshift($error_element, $field_name, $delta);
+          $error_element = implode('][', $error_element);
+        }
+        else {
+          $error_element = '';
+        }
+        form_set_error($error_element, t('%name field is required.', array('%name' => $form[$group_name][$delta][$field_name]['#title'])));
+      }
+    }
+  }
 }
 
 /**
- * Implementation of hook_fieldgroup_view().
+ * Fix value positions in $form_state for the fields in a multigroup.
  */
-function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
-  if ($group['group_type'] != 'multigroup') {
-    return;
+function content_multigroup_node_form_fix_values(&$form, &$form_state, $type_name, $group_name) {
+  $content_type = content_types($type_name);
+  $groups = fieldgroup_groups($type_name);
+  $group = $groups[$group_name];
+  $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+
+  // Move group data from group->delta->field to field->delta.
+  $group_data = array();
+  foreach ($form_state['values'][$group_name] as $delta => $items) {
+    // Skip 'add more' button.
+    if (!is_array($items) || !isset($items['_weight'])) {
+      continue;
+    }
+    foreach ($group_fields as $field_name => $field) {
+      if (!isset($group_data[$field_name])) {
+        $group_data[$field_name] = array();
+      }
+      // Get the field weight from the group and keep track of the current
+      // delta for each field item.
+      $group_data[$field_name][$delta] = array_merge($items[$field_name], array(
+        '_weight' => $items['_weight'],
+        '_old_delta' => $delta,
+      ));
+    }
   }
 
-  $group_name = $group['group_name'];
-  $node_copy = drupal_clone($node);
-  $max = $group['settings']['multigroup']['multiple'];
+  $form_group_sorted = FALSE;
+  foreach ($group_data as $field_name => $items) {
+
+    // Sort field items according to drag-n-drop reordering. Deltas are also
+    // rebuilt to start counting from 0 to n. Note that since all fields in the
+    // group share the same weight, their deltas remain in sync.
+    usort($items, '_content_sort_items_helper');
+
+    // Now we need to apply the same ordering to the form elements. Also,
+    // note that deltas have changed during the sort operation, so we need
+    // to reflect this delta conversion in the form.
+    if (!$form_group_sorted) {
+      $form_group_items = array();
+      $form_deltas = array();
+      foreach ($items as $new_delta => $item) {
+        $form_deltas[$item['_old_delta']] = $new_delta;
+        $form_group_items[$new_delta] = $form[$group_name][$item['_old_delta']];
+        unset($form[$group_name][$item['_old_delta']]);
+      }
+      foreach ($form_group_items as $new_delta => $form_group_item) {
+        $form[$group_name][$new_delta] = $form_group_item;
+      }
+      content_multigroup_node_form_fix_deltas($form[$group_name], $form_deltas);
+      $form_group_sorted = TRUE;
+    }
 
-  $count = 0;
-  foreach ($group['fields'] as $field_name => $field) {
-    $count = max($count, count($node->$field_name));
-  }
+    // Get rid of the old delta value.
+    foreach (array_keys($items) as $delta) {
+      unset($items[$delta]['_old_delta']);
+    }
 
-  $group['multiple'] = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
-  $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
-  $format = isset($group['settings']['multigroup']['display_settings'][$context]['format']) ? $group['settings']['multigroup']['display_settings'][$context]['format'] : 'fieldset';
-  $show_label = isset($group['settings']['multigroup']['display_settings']['label']) ? $group['settings']['multigroup']['display_settings']['label'] : 'above';
+    // Fix field and delta positions in the $_POST array.
+    if (!empty($_POST)) {
+      $_POST[$field_name] = array();
+      foreach ($items as $new_delta => $item) {
+        $_POST[$field_name][$new_delta] = $item;
+      }
+      if (isset($_POST[$group_name])) {
+        unset($_POST[$group_name]);
+      }
+    }
 
-  switch ($group['multiple']) {
-    case 0:
-      $max = 0;
-      break;
-    case 1:
-      $max = $count;
-      break;
-    default:
-      $max = $group['multiple'];
-      break;
+    // Move field items back to their original positions.
+    $form_state['values'][$field_name] = $items;
   }
 
-  for ($delta = 0; $delta < $max; $delta++) {
-    $element[$delta] = array('#weight' => $delta);
+  // Finally, get rid of the group data in form values.
+  unset($form_state['values'][$group_name]);
+}
 
-    $label = !empty($labels[$delta]) && $show_label == 'above' ? $labels[$delta] : '';
+/**
+ * Fix deltas for all affected form elements.
+ */
+function content_multigroup_node_form_fix_deltas(&$elements, $form_deltas) {
+  foreach (element_children($elements) as $key) {
+    if (isset($elements[$key]) && $elements[$key]) {
 
-    foreach ($group['fields'] as $field_name => $field) {
+      // Fix the second item, the delta value, of the element's '#parents' array.
+      $elements[$key]['#parents'][1] = $form_deltas[$elements[$key]['#parents'][1]];
 
-      // Create a pseudo node that only has the value we want
-      // in this group and pass it to the formatter.
-      if (isset($node->content[$field_name])) {
-        $node_copy->content[$field_name]['field']['items'] = array(
-          $delta => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL,
-          );
-        $element[$delta][$field_name] = $node_copy->content[$field_name];
-        $element[$delta][$field_name]['#delta'] = $delta;
+      // If present, fix delta value in '#delta' attribute of the element.
+      if (isset($elements[$key]['#delta']) && isset($form_deltas[$elements[$key]['#delta']])) {
+        $elements[$key]['#delta'] = $form_deltas[$elements[$key]['#delta']];
       }
-    }
-    switch ($format) {
-      case 'table':
-        $element[$delta]['#theme'] = 'content_multigroup_display_table';
-        $element[$delta]['#title'] = $label;
-        break;
-      case 'fieldset':
-        $element[$delta]['#type'] = 'fieldset';
-        $element[$delta]['#title'] = $label;
-        break;
-      case 'hr':
-        $element[$delta]['#theme'] = 'content_multigroup_display_hr';
-        $element[$delta]['#title'] = $label;
-        break;
-      default:
-        $element[$delta]['#theme'] = 'content_multigroup_display_simple';
-        $element[$delta]['#title'] = $label;
-        break;
-    }
 
-  }
-
-  foreach ($group['fields'] as $field_name => $field) {
-    if (isset($element[$field_name])) {
-      unset($element[$field_name]);
+      // Recurse through all children elements.
+      content_multigroup_node_form_fix_deltas($elements[$key], $form_deltas);
     }
   }
 }
 
 /**
- * Theme an individual form element.
+ * Fix form element parents for all fields in multigroups.
  *
- * Combine multiple values into a table with drag-n-drop reordering.
- */
-function theme_content_multigroup_node_form($element) {
-  $output = '';
-  if ($element['#multiple'] >= 1) {
-    $table_id = $element['#group_name'] .'_values';
-    $order_class = $element['#group_name'] .'-delta-order';
-
-    $header = array(
-      array(
-        'data' => '',
-        'colspan' => 2
-      ),
-      t('Order'),
-    );
-    $rows = array();
-    $groups = fieldgroup_groups($element['#type_name']);
-    $group = $groups[$element['#group_name']];
-    $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
-    $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
+ * The $element['#parents'] array needs to reflect the position of the fields
+ * in the $form_state['values'] array so that form_set_value() can be safely
+ * used by field validation handlers.
+ */
+function content_multigroup_node_form_fix_parents(&$elements, $multigroups) {
+  foreach (element_children($elements) as $key) {
+    if (isset($elements[$key]) && $elements[$key]) {
+
+      // Check if the current element is child of a multigroup. The #parents
+      // array for field values has, at least, 3 parent elements, being the
+      // first one the name of a multigroup.
+      if (count($elements[$key]['#parents']) >= 3 && isset($multigroups[$elements[$key]['#parents'][0]])) {
+
+        // Extract group name, delta and field name from the #parents array.
+        array_shift($elements[$key]['#parents']);
+        $delta = array_shift($elements[$key]['#parents']);
+        $field_name = array_shift($elements[$key]['#parents']);
 
-    $i = 0;
-    foreach (element_children($element) as $delta => $key) {
-      if ($key !== $element['#group_name'] .'_add_more') {
-        $label = !empty($labels[$i]) ? theme('content_multigroup_node_label', $labels[$i]) : '';
-        $element[$key]['_weight']['#attributes']['class'] = $order_class;
-        $delta_element = drupal_render($element[$key]['_weight']);
-        $cells = array(
-          array('data' => '', 'class' => 'content-multiple-drag'),
-          $label . drupal_render($element[$key]),
-          array('data' => $delta_element, 'class' => 'delta-order'),
-        );
-        $rows[] = array(
-          'data' => $cells,
-          // TODO Tablesort drag n drop is not working with complex
-          // field validation. The fields appear to work correctly,
-          // but element validation seems to get missed or confused
-          // causing validation errors. Need to investigate why.
-          'class' => 'draggable',
-        );
+        // Now, insert field name and delta to the #parents array.
+        array_unshift($elements[$key]['#parents'], $field_name, $delta);
       }
-      $i++;
+
+      // Recurse through all children elements.
+      content_multigroup_node_form_fix_parents($elements[$key], $multigroups);
     }
+  }
+}
 
-    $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
-    $output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
-    $output .= drupal_render($element[$element['#group_name'] .'_add_more']);
+/**
+ * Update posting data to reflect delta changes in the form structure.
+ *
+ * The $_POST array is fixed in content_multigroup_node_form_fix_values().
+ */
+function content_multigroup_node_form_fix_post(&$elements) {
+  foreach (element_children($elements) as $key) {
+    if (isset($elements[$key]) && $elements[$key]) {
 
-    drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
-  }
-  else {
-    foreach (element_children($element) as $key) {
-      $output .= drupal_render($element[$key]);
+      // Update the element copy of the $_POST array.
+      $elements[$key]['#post'] = $_POST;
+
+      // Recurse through all children elements.
+      content_multigroup_node_form_fix_post($elements[$key]);
     }
   }
 
-  return $output;
+  // Update the form copy of the $_POST array.
+  $elements['#post'] = $_POST;
 }
 
+/**
+ * Fix the value for fields that deal with multiple values themselves.
+ */
+function content_multigroup_fix_multivalue_fields($element, &$form_state) {
+  $field_name = $element['#field_name'];
+  $delta = $element['#delta'];
+  $value = $form_state['values'][$field_name][$delta][0];
+  form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Add AHAH add more button, if not working with a programmed form.
+ */
 function content_multigroup_add_more(&$form, &$form_state, $group) {
-  // Add AHAH add more button, if not working with a programmed form.
-  $multiple = $group['settings']['multigroup']['multiple'];
-  $form_element = array();
-  if ($multiple != 1 || !empty($form['#programmed'])) {
-    return $form_element;
+  $group_multiple = $group['settings']['multigroup']['multiple'];
+  if ($group_multiple != 1 || !empty($form['#programmed'])) {
+    return FALSE;
   }
-  else {
-    // Make sure the form is cached so ahah can work.
-    $form['#cache'] = TRUE;
-    $content_type = content_types($group['type_name']);
-    $group_name = $group['group_name'];
-    $group_name_css = str_replace('_', '-', $group_name);
-
-    $form_element[$group_name .'_add_more'] = array(
-      '#type' => 'submit',
-      '#name' => $group_name .'_add_more',
-      '#value' => t('Add more values'),
-      '#weight' => $multiple + 1,
-      // Submit callback for disabled JavaScript. drupal_get_form() might get
-      // the form from the cache, so we can't rely on content_form_alter()
-      // including this file. Therefore, call a proxy function to do this.
-      '#submit' => array('content_multigroup_add_more_submit_proxy'),
-      '#ahah' => array(
-        'path' => 'content_multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name,
-        'wrapper' => $group_name_css .'-items',
-        'method' => 'replace',
-        'effect' => 'fade',
-      ),
-      // When JS is disabled, the content_add_more_submit handler will find
-      // the relevant field using these entries.
-      '#group_name' => $group_name,
-      '#type_name' => $group['type_name'],
-    );
 
-    // Add wrappers for the group and 'more' button.
-    // TODO: could be simplified ?
-    $form_element['#prefix'] = '<div class="clear-block" id="'. $group_name_css .'-add-more-wrapper"><div id="'. $group_name_css .'-items">';
-    $form_element[$group_name .'_add_more']['#prefix'] = '<div class="content-add-more">';
-    $form_element[$group_name .'_add_more']['#suffix'] =  '</div></div></div>';
-  }
+  // Make sure the form is cached so ahah can work.
+  $form['#cache'] = TRUE;
+  $content_type = content_types($group['type_name']);
+  $group_name = $group['group_name'];
+  $group_name_css = str_replace('_', '-', $group_name);
+
+  $form_element = array();
+  $form_element[$group_name .'_add_more'] = array(
+    '#type' => 'submit',
+    '#name' => $group_name .'_add_more',
+    '#value' => t('Add more values'),
+    '#weight' => $group_multiple + 1,
+    '#submit' => array('content_multigroup_add_more_submit'),
+    '#ahah' => array(
+      'path' => 'content_multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name,
+      'wrapper' => $group_name_css .'-items',
+      'method' => 'replace',
+      'effect' => 'fade',
+    ),
+    // When JS is disabled, the content_multigroup_add_more_submit handler will
+    // find the relevant field using these entries.
+    '#group_name' => $group_name,
+    '#type_name' => $group['type_name'],
+  );
+
+  // Add wrappers for the group and 'more' button.
+  // TODO: could be simplified ?
+  $form_element['#prefix'] = '<div class="clear-block" id="'. $group_name_css .'-add-more-wrapper"><div id="'. $group_name_css .'-items">';
+  $form_element[$group_name .'_add_more']['#prefix'] = '<div class="content-add-more">';
+  $form_element[$group_name .'_add_more']['#suffix'] =  '</div></div></div>';
+
   return $form_element;
 }
 
@@ -1003,7 +1162,7 @@ function content_multigroup_add_more_sub
 
   // Make the changes we want to the form state.
   if ($form_state['values'][$group_name][$group_name .'_add_more']) {
-    $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]);
+    $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]) - 1;
   }
 }
 
@@ -1013,12 +1172,11 @@ function content_multigroup_add_more_sub
  * Adapted from content_add_more_js to work with groups instead of fields.
  */
 function content_multigroup_add_more_js($type_name_url, $group_name) {
-  $type = content_types($type_name_url);
-  $groups = fieldgroup_groups($type['type']);
+  $content_type = content_types($type_name_url);
+  $groups = fieldgroup_groups($content_type['type']);
   $group = $groups[$group_name];
-  $group['multiple'] = $group['settings']['multigroup']['multiple'];
 
-  if (($group['multiple'] != 1) || empty($_POST['form_build_id'])) {
+  if (($group['settings']['multigroup']['multiple'] != 1) || empty($_POST['form_build_id'])) {
     // Invalid request.
     drupal_json(array('data' => ''));
     exit;
@@ -1059,17 +1217,23 @@ function content_multigroup_add_more_js(
   unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']);
   foreach ($_POST[$group_name] as $delta => $item) {
     $form_state['values'][$group_name][$delta]['_weight'] = $item['_weight'];
-    $form_state['values'][$group_name][$delta]['_delta'] = $item['_delta'];
   }
+  $group['multiple'] = $group['settings']['multigroup']['multiple'];
   $form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]);
   $_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]);
 
   // Build our new form element for the whole group, asking for one more element.
-
-  $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1);
   $delta = max(array_keys($_POST[$group_name])) + 1;
+  $form_state['item_count'] = array($group_name => count($_POST[$group_name]));
   content_multigroup_group_form($form, $form_state, $group, $delta);
 
+  // Rebuild weight deltas to make sure they all are equally dimensioned.
+  foreach ($form[$group_name] as $key => $item) {
+    if (is_numeric($key) && isset($item['_weight']) && is_array($item['_weight'])) {
+      $form[$group_name][$key]['_weight']['#delta'] = $delta;
+    }
+  }
+
   // Save the new definition of the form.
   $form_state['values'] = array();
   form_set_cache($form_build_id, $form, $form_state);
@@ -1086,7 +1250,6 @@ function content_multigroup_add_more_js(
 
   // Render the new output.
   $group_form = $form[$group_name];
-
   // We add a div around the new content to receive the ahah effect.
   $group_form[$delta]['#prefix'] = '<div class="ahah-new-content">'. (isset($group_form[$delta]['#prefix']) ? $group_form[$delta]['#prefix'] : '');
   $group_form[$delta]['#suffix'] = (isset($group_form[$delta]['#suffix']) ? $group_form[$delta]['#suffix'] : '') .'</div>';
@@ -1105,46 +1268,245 @@ function content_multigroup_add_more_js(
 }
 
 /**
+ * Implementation of hook_fieldgroup_view().
+ */
+function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
+  if ($group['group_type'] != 'multigroup') {
+    return;
+  }
+
+  $group_name = $group['group_name'];
+  $node_copy = drupal_clone($node);
+  $content_type = content_types($group['type_name']);
+  $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+  $group_multiple = $group['settings']['multigroup']['multiple'];
+  $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
+  $show_label = isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above';
+  $subgroup_labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
+  $subgroup_format = isset($subgroup_settings[$context]['format']) ? $subgroup_settings[$context]['format'] : 'fieldset';
+
+
+  switch ($group_multiple) {
+    case 0:
+      $group_deltas = array(0);
+      break;
+
+    case 1:
+      // Compute deltas based on the field with the highest number of items.
+      $group_deltas = array();
+      $max_delta = -1;
+      foreach (array_keys($group_fields) as $field_name) {
+        $field_deltas = is_array($node->content[$field_name]['field']['items']) ? array_keys($node->content[$field_name]['field']['items']) : array();
+        $field_max = (!empty($field_deltas) ? max($field_deltas) : 0);
+        if ($field_max > $max_delta) {
+          $max_delta = $field_max;
+          $group_deltas = $field_deltas;
+        }
+      }
+      break;
+
+    default:
+      $group_deltas = array_keys(array_fill(0, $group_multiple - 1, 0));
+      break;
+  }
+
+  foreach ($group_deltas as $i => $delta) {
+    $element[$delta] = array(
+      '#title' => ($show_label == 'above' && !empty($subgroup_labels[$i]) ? check_plain(t($subgroup_labels[$i])) : ''),
+      '#attributes' => array('class' => 'content-multigroup-wrapper content-multigroup-'. $i),
+      '#weight' => $delta,
+    );
+
+    // Create a pseudo node that only has the value we want in this group and
+    // pass it to the formatter.
+    // Default implementation of content-field.tpl.php uses a different CSS
+    // class for inline labels when delta is zero, but this is not needed in
+    // the context of multigroup, so we place the field into index 1 of the
+    // item list. Note that CSS class "field-label-inline" is overridden in the
+    // multigroup stylesheet because here labels should always be visible.
+    foreach (array_keys($group_fields) as $field_name) {
+      if (isset($node->content[$field_name])) {
+        $node_copy->content[$field_name]['field']['items'] = array(
+          1 => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL,
+        );
+        $element[$delta][$field_name] = $node_copy->content[$field_name];
+        $element[$delta][$field_name]['#delta'] = $delta;
+      }
+    }
+
+    switch ($subgroup_format) {
+      case 'simple':
+        $element[$delta]['#theme'] = 'content_multigroup_display_simple';
+        break;
+      case 'fieldset':
+        $element[$delta]['#type'] = 'content_multigroup_display_fieldset';
+        break;
+      case 'hr':
+        $element[$delta]['#theme'] = 'content_multigroup_display_hr';
+        break;
+      case 'table':
+        $element['#theme'] = 'content_multigroup_display_table';
+        $element['#attributes']['class'] = 'content-multigroup-display-table';
+        $element['#fields'] = $group_fields;
+        break;
+    }
+  }
+
+  foreach (array_keys($group_fields) as $field_name) {
+    if (isset($element[$field_name])) {
+      unset($element[$field_name]);
+    }
+  }
+}
+
+/**
+ * Theme an individual form element.
+ *
+ * Combine multiple values into a table with drag-n-drop reordering.
+ */
+function theme_content_multigroup_node_form($element) {
+  $groups = fieldgroup_groups($element['#type_name']);
+  $group_name = $element['#group_name'];
+  $group = $groups[$group_name];
+  $group_multiple = $group['settings']['multigroup']['multiple'];
+  $output = '';
+
+  if ($group_multiple >= 1) {
+    $table_id = $element['#group_name'] .'_values';
+    $order_class = $element['#group_name'] .'-delta-order';
+    $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
+    $show_label = isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above';
+    $subgroup_labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array();
+    $multiple_columns = isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0;
+
+    $header = array(array('data' => ''));
+    if ($multiple_columns) {
+      $content_type = content_types($group['type_name']);
+      $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+      foreach ($group_fields as $field_name => $field) {
+        $header[] = check_plain(t($field['widget']['label']));
+      }
+      $table_class = 'content-multigroup-edit-table-multiple-columns';
+    }
+    else {
+      $header[0]['colspan'] = 2;
+      $table_class = 'content-multigroup-edit-table-one-column';
+    }
+    $header[] = t('Order');
+    $rows = array();
+
+    $i = 0;
+    foreach (element_children($element) as $delta => $key) {
+      if ($key !== $group_name .'_add_more') {
+        $label = ($show_label == 'above' && !empty($subgroup_labels[$i]) ? theme('content_multigroup_node_label', check_plain(t($subgroup_labels[$i]))) : '');
+        $element[$key]['_weight']['#attributes']['class'] = $order_class;
+        $delta_element = drupal_render($element[$key]['_weight']);
+        $cells = array(array('data' => '', 'class' => 'content-multiple-drag'));
+        if ($multiple_columns) {
+          foreach ($group_fields as $field_name => $field) {
+            $cells[] = drupal_render($element[$key][$field_name]);
+          }
+        }
+        else {
+          $cells[] = $label . drupal_render($element[$key]);
+        }
+        $cells[] = array('data' => $delta_element, 'class' => 'delta-order');
+        $rows[] = array('data' => $cells, 'class' => 'draggable');
+      }
+      $i++;
+    }
+
+    $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => $table_class));
+    $output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
+    $output .= drupal_render($element[$group_name .'_add_more']);
+
+    drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
+  }
+  else {
+    foreach (element_children($element) as $key) {
+      $output .= drupal_render($element[$key]);
+    }
+  }
+
+  return $output;
+}
+
+/**
  * Theme the sub group label in the node form.
  */
 function theme_content_multigroup_node_label($text) {
-  if (!empty($text)) {
-    return '<h3>'. check_plain($text) .'</h3>';
-  }
+  return !empty($text) ? '<h3>'. $text .'</h3>' : '';
 }
 
+/**
+ * Theme a subgroup of fields in 'simple' format.
+ *
+ * No output is generated if all fields are empty.
+ */
 function theme_content_multigroup_display_simple($element) {
-  $label = '';
-  if (!empty($element['#title'])) {
-    $label .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
-  }
-  $output = $label;
+  $children = $output = '';
   foreach (element_children($element) as $key) {
-    $output .= drupal_render($element[$key]);
+    $children .= drupal_render($element[$key]);
+  }
+  if (!empty($children)) {
+    $output .= '<div'. drupal_attributes($element['#attributes']) .'>';
+    if (!empty($element['#title'])) {
+      $output .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
+    }
+    $output .= $children .'</div>';
   }
   return $output;
 }
 
-function theme_content_multigroup_display_hr($element) {
-  $label = '';
-  if (!empty($element['#title'])) {
-    $label .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
+/**
+ * Theme a subgroup of fields in 'fieldset' format.
+ *
+ * No output is generated if all fields are empty.
+ */
+function theme_content_multigroup_display_fieldset($element) {
+  if (empty($element['#children']) && empty($element['#value'])) {
+    return '';
   }
-  $output = '<hr class="content-multigroup" />'. $label;
+  return theme('fieldset', $element);
+}
+
+/**
+ * Theme a subgroup of fields in 'hr' format.
+ *
+ * No output is generated if all fields are empty.
+ */
+function theme_content_multigroup_display_hr($element) {
+  $children = $output = '';
   foreach (element_children($element) as $key) {
-    $output .= drupal_render($element[$key]);
+    $children .= drupal_render($element[$key]);
+  }
+  if (!empty($children)) {
+    $output .= '<div'. drupal_attributes($element['#attributes']) .'><hr class="content-multigroup" />';
+    if (!empty($element['#title'])) {
+      $output .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
+    }
+    $output .= $children .'</div>';
   }
   return $output;
 }
 
+/**
+ * Theme a subgroup of fields in 'table' format.
+ *
+ * No output is generated if all fields are empty.
+ */
 function theme_content_multigroup_display_table($element) {
-  $label = '';
-  if (!empty($element['#title'])) {
-    $label .= '<label class="content-multigroup">'. $element['#title'] .':</label>';
-  }
-  $output = $label;
-  foreach (element_children($element) as $key) {
-    $output .= drupal_render($element[$key]);
+  $header = array();
+  foreach ($element['#fields'] as $field_name => $field) {
+    $header[] = check_plain(t($field['widget']['label']));
+  }
+  $rows = array();
+  foreach (element_children($element) as $delta) {
+    $cells = array();
+    foreach ($element['#fields'] as $field_name => $field) {
+      $cells[] = array('data' => drupal_render($element[$delta][$field_name]), 'class' => $element[$delta]['#attributes']['class']);
+    }
+    $rows[] = $cells;
   }
-  return $output;
-}
\ No newline at end of file
+  return count($rows) ? theme('table', $header, $rows, $element['#attributes']) : '';
+}
