Index: modules/menu/menu.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v
retrieving revision 1.7
diff -u -r1.7 menu.admin.inc
--- modules/menu/menu.admin.inc	17 Nov 2007 14:25:23 -0000	1.7
+++ modules/menu/menu.admin.inc	19 Nov 2007 08:20:27 -0000
@@ -28,7 +28,7 @@
  */
 function menu_overview_form(&$form_state, $menu) {
   global $menu_admin;
-  $sql ="
+  $sql = "
     SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.*
     FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
     WHERE ml.menu_name = '%s'
@@ -66,7 +66,7 @@
     $item = $data['link'];
     // Don't show callbacks; these have $item['hidden'] < 0.
     if ($item && $item['hidden'] >= 0) {
-      $mlid = $item['mlid'];
+      $mlid = 'mlid:'. $item['mlid'];
       $form[$mlid]['#item'] = $item;
       $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => 'menu-disabled') : array('class' => 'menu-enabled');
       $form[$mlid]['title']['#value'] = l($item['title'], $item['href'], $item['options']) . ($item['hidden'] ? ' ('. t('disabled') .')' : '');
@@ -78,6 +78,19 @@
         '#type' => 'checkbox',
         '#default_value' => $item['has_children'] && $item['expanded'],
       );
+      $form[$mlid]['weight'] = array(
+        '#type' => 'weight',
+        '#default_value' => isset($form_state[$mlid]['weight']) ? $form_state[$mlid]['weight'] : $item['weight'],
+      );
+      $form[$mlid]['mlid'] = array(
+        '#type' => 'hidden',
+        '#value' => $item['mlid'],
+      );
+      $form[$mlid]['plid'] = array(
+        '#type' => 'textfield',
+        '#default_value' => isset($form_state[$mlid]['plid']) ? $form_state[$mlid]['plid'] : $item['plid'],
+        '#size' => 6,
+      );
       // Build a list of operations.
       $operations = array();
       $operations['edit'] = l(t('edit'), 'admin/build/menu/item/'. $item['mlid'] .'/edit');
@@ -103,27 +116,65 @@
   return $form;
 }
 
-function menu_overview_form_submit($form) {
+/**
+ * Submit handler for the menu overview form.
+ * 
+ * This function takes great care in saving parent items first, then items
+ * underneath them. Saving items in the incorrect order can break the menu tree.
+ * 
+ * @see menu_overview_form()
+ */
+function menu_overview_form_submit($form, &$form_state) {
+  // When dealing with saving menu items, the order in which these items are
+  // saved is critical. If a changed child item is saved before its parent,
+  // the child item could be saved with an invalid path past its immediate
+  // parent. To prevent this, save items in the form in the same order they
+  // are sent by $_POST, ensuring parents are saved first, then their children.
+  // See http://drupal.org/node/181126#comment-632270
+  $order = array_flip(array_keys($form['#post'])); // Get the $_POST order.
+  $form = array_merge($order, $form); // Update our original form with the new order.
+
+  $updated_items = array();
+  $fields = array('expanded', 'weight', 'plid');
   foreach (element_children($form) as $mlid) {
-    if (isset($form[$mlid]['hidden'])) {
+    if (isset($form[$mlid]['#item'])) {
       $element = $form[$mlid];
+      // Update any fields that have changed in this menu item.
+      foreach ($fields as $field) {
+        if ($element[$field]['#value'] != $element[$field]['#default_value']) {
+          $element['#item'][$field] = $element[$field]['#value'];
+          $updated_items[$mlid] = $element['#item'];
+        }
+      }
+      // Hidden is a special case, the value needs to be reversed.
       if ($element['hidden']['#value'] != $element['hidden']['#default_value']) {
         $element['#item']['hidden'] = !$element['hidden']['#value'];
-        menu_link_save($element['#item']);
-      }
-      if ($element['expanded']['#value'] != $element['expanded']['#default_value']) {
-        $element['#item']['expanded'] = $element['expanded']['#value'];
-        menu_link_save($element['#item']);
+        $updated_items[$mlid] = $element['#item'];
       }
     }
   }
+
+  // Save all our changed items to the database.
+  foreach ($updated_items as $item) {
+    menu_link_save($item);
+  }
 }
 
 /**
  * Theme the menu overview form into a table.
  */
 function theme_menu_overview_form($form) {
-  $header = array(t('Enabled'), t('Expanded'), t('Menu item'), array('data' => t('Operations'), 'colspan' => '3'));
+  drupal_add_tabledrag('menu-overview', 'match', 'parent', 'menu-plid', 'menu-plid', 'menu-mlid');
+  drupal_add_tabledrag('menu-overview', 'order', 'sibling', 'menu-weight');
+
+  $header = array(
+    t('Menu item'),
+    array('data' => t('Expanded'), 'class' => 'checkbox'),
+    array('data' => t('Enabled'), 'class' => 'checkbox'),
+    t('Weight'),
+    array('data' => t('Operations'), 'colspan' => '3'),
+  );
+
   $rows = array();
   foreach (element_children($form) as $mlid) {
     if (isset($form[$mlid]['hidden'])) {
@@ -137,15 +188,23 @@
         $operations[] = '';
       }
 
+      // Add special classes to be used for tabledrag.js.
+      $element['plid']['#attributes']['class'] = 'menu-plid';
+      $element['mlid']['#attributes']['class'] = 'menu-mlid';
+      $element['weight']['#attributes']['class'] = 'menu-weight';
+
+      // Change the parent field to a hidden. This allows any value but hides the field.
+      $element['plid']['#type'] = 'hidden';
+
       $row = array();
-      $row[] = array('data' => drupal_render($element['hidden']), 'align' => 'center');
-      $row[] = array('data' => drupal_render($element['expanded']), 'align' => 'center');
-      $depth = $element['#item']['depth'];
-      $indentation = str_repeat('&nbsp;&nbsp;', $depth - 1) . ($depth > 1 ? '-&nbsp;' : '');
-      $row[] = $indentation . drupal_render($element['title']);
+      $row[] = theme('indentation', $element['#item']['depth'] - 1) . drupal_render($element['title']);
+      $row[] = array('data' => drupal_render($element['expanded']), 'class' => 'checkbox');
+      $row[] = array('data' => drupal_render($element['hidden']), 'class' => 'checkbox');
+      $row[] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']);
       $row = array_merge($row, $operations);
 
       $row = array_merge(array('data' => $row), $element['#attributes']);
+      $row['class'] = !empty($row['class']) ? $row['class'] .' draggable' : 'draggable';
       $rows[] = $row;
     }
   }
Index: misc/tabledrag.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/tabledrag.js,v
retrieving revision 1.1
diff -u -r1.1 tabledrag.js
--- misc/tabledrag.js	14 Nov 2007 09:49:30 -0000	1.1
+++ misc/tabledrag.js	19 Nov 2007 08:20:27 -0000
@@ -53,15 +53,26 @@
   this.scrollY = 0;
   this.windowHeight = 0;
 
-  // Check if this table contains indentations.
-  var indents = $('div.indentation', table);
-  this.indentEnabled = indents.size() > 0 ? true : false;
+  // Check this table's settings to see if there are parent relationships in
+  // this table. For efficiency, large sections of code can be skipped if we
+  // don't need to track horizontal movement and indentations.
+  this.indentEnabled = false;
+  for (group in tableSettings) {
+    for (n in tableSettings[group]) {
+      if (tableSettings[group][n]['relationship'] == 'parent') {
+        this.indentEnabled = true;
+      }
+    }
+  }
   if (this.indentEnabled) {
-    var indentSize = indents.css('width');
     this.oldX = 0;
     this.indentCount = 1; // Total width of indents, set in makeDraggable.
-    this.indentIncrement = indentSize.replace(/[0-9\.]*/, '');
-    this.indentAmount = parseInt(indentSize);
+    // Find the width of indentations to measure mouse movements against.
+    // Because the table doesn't need to start with any indentations, we
+    // manually create an empty div, check it's width, then remove.
+    var indent = $(Drupal.theme('tableDragIndentation')).appendTo('body');
+    this.indentAmount = parseInt(indent.css('width'));
+    indent.remove();
   }
 
   // Make each applicable row draggable.
@@ -253,7 +264,7 @@
           self.safeBlur = false; // Do not allow the onBlur cleanup.
           self.rowObject.direction = 'up';
           keyChange = true;
-          if (self.rowObject.isValidSwap(previousRow, 'up', 0)) {
+          if (self.rowObject.isValidSwap(previousRow, 0)) {
             self.rowObject.swap('before', previousRow);
             window.scrollBy(0, -parseInt(item.offsetHeight));
           }
@@ -280,7 +291,7 @@
           self.safeBlur = false; // Do not allow the onBlur cleanup.
           self.rowObject.direction = 'down';
           keyChange = true;
-          if (self.rowObject.isValidSwap(nextRow, 'down', 0)) {
+          if (self.rowObject.isValidSwap(nextRow, 0)) {
             self.rowObject.swap('after', nextRow);
           }
           else {
@@ -549,8 +560,12 @@
     // the source rows for each seperately.
     var rowSettings = this.rowSettings(group, changedRow);
 
+    // Set the row as it's own target.
+    if (rowSettings.relationship == 'self') {
+      var sourceRow = changedRow;
+    }
     // Siblings are easy, check previous and next rows.
-    if (rowSettings.relationship == 'sibling') {
+    else if (rowSettings.relationship == 'sibling') {
       var previousRow = $(changedRow).prev('tr').get(0);
       var nextRow = $(changedRow).next('tr').get(0);
       var sourceRow = changedRow;
@@ -586,11 +601,16 @@
       if (previousRow.length) {
         sourceRow = previousRow[0];
       }
-      // Otherwise we went all the way to the top of the table.
-      // Assume that the first item has no indentions.
+      // Otherwise we went all the way to the left of the table without finding
+      // a parent, meaning this item has been placed at the root level.
       else {
-        // Basically copy first item's value.
-        sourceRow = $('tr.draggable:first')[0];
+        // Use the first row in the table as source, because it's garanteed to
+        // be at the root level. Find the first item, then compare this row
+        // against it as a sibling.
+        sourceRow = $('tr.draggable:first').get(0);
+        if (sourceRow == this.rowObject.element) {
+          sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
+        }
         var useSibling = true;
       }
     }
@@ -615,6 +635,10 @@
       var sourceClass = '.' + rowSettings.source;
       var sourceElement = $(sourceClass, sourceRow).get(0);
       switch (rowSettings.action) {
+        case 'depth':
+          // Get the depth of the target row.
+          targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
+          break;
         case 'match':
           // Update the value.
           targetElement.value = sourceElement.value;
@@ -634,7 +658,7 @@
           }
           else {
             // Assume a numeric input field.
-            var weight = 0;
+            var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
             $(targetClass, siblings).each(function() {
               this.value = weight;
               weight++;
@@ -832,7 +856,7 @@
   if (this.table.tBodies[0].rows[0] == row) {
     // Do not let the first row contain indentations
     // or let an un-draggable first row have anything put before it.
-    if (this.indents > 0 || $(row).is(':not(.draggable)')) {
+    if ((this.indents + indentDiff) > 0 || $(row).is(':not(.draggable)')) {
       return false;
     }
   }
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.715
diff -u -r1.715 common.inc
--- includes/common.inc	16 Nov 2007 15:35:24 -0000	1.715
+++ includes/common.inc	19 Nov 2007 08:20:26 -0000
@@ -1954,19 +1954,19 @@
  * In a situation where a single weight column is being sorted in the table, the
  * classes could be added like this (in the theme function):
  * @code
- * $form['my_elements'][$delta]['weight']['attributes']['class'] = "my-elements-weight";
+ * $form['my_elements'][$delta]['weight']['#attributes']['class'] = "my-elements-weight";
  * @endcode
  *
  * Calling drupal_add_tabledrag() would then be written as such:
  * @code
- * drupal_add_tabledrag('my-module-table', 'sort', 'sibling', 'my-elements-weight');
+ * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight');
  * @endcode
  *
  * In a more complex case where there are several groups in one column (such as
  * the block regions on the admin/build/block page), a separate subgroup class
  * must also be added to differentiate the groups.
  * @code
- * $form['my_elements'][$region][$delta]['weight']['attributes']['class'] = "my-elements-weight my-elements-weight-". $region;
+ * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = "my-elements-weight my-elements-weight-". $region;
  * @endcode
  *
  * $group is still 'my-element-weight', and the additional $subgroup variable
@@ -1975,7 +1975,7 @@
  *
  * @code
  * foreach ($regions as $region) {
- *   drupal_add_tabledrag('my-module-table', 'sort', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
+ *   drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
  * }
  * @endcode
  *
@@ -1996,13 +1996,15 @@
  *   String containing the target table's id attribute. If the table does not
  *   have an id, one will need to be set, such as <table id="my-module-table">.
  * @param $action
- *   String describing the action to be done on the form item. Either 'match' or
- *   'sort'. Match is typically used for parent relationships, sort is typically
- *   used to set weights on other form elements with the same group.
+ *   String describing the action to be done on the form item. Either 'match'
+ *   'depth', or 'order'. Match is typically used for parent relationships.
+ *   Order is typically used to set weights on other form elements with the same
+ *   group. Depth updates the target element with the current indentation.
  * @param $relationship
  *   String describing where the $action variable should be performed. Either
- *   'parent' or 'sibling'. Parent will only look for fields up the tree.
- *   Sibling will look for fields in the same group in rows above and below it.
+ *   'parent', 'sibling', or 'self'. Parent will only look for fields up the
+ *   tree. Sibling will look for fields in the same group in rows above and
+ *   below it. Self affects the dragged row itself.
  * @param $group
  *   A class name applied on all related form elements for this action.
  * @param $subgroup
@@ -2929,6 +2931,9 @@
     'progress_bar' => array(
       'arguments' => array('percent' => NULL, 'message' => NULL),
     ),
+    'indentation' => array(
+      'arguments' => array('size' => 1),
+    ),
     // from pager.inc
     'pager' => array(
       'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()),
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.394
diff -u -r1.394 theme.inc
--- includes/theme.inc	11 Nov 2007 22:43:44 -0000	1.394
+++ includes/theme.inc	19 Nov 2007 08:20:27 -0000
@@ -1672,6 +1672,22 @@
 }
 
 /**
+ * Create a standard indentation div. Used for drag and drop tables.
+ * 
+ * @param $size
+ *   Optional. The number of indentations to create.
+ * @return
+ *   A string containing indentations.
+ */
+function theme_indentation($size = 1) {
+  $output = '';
+  for ($n = 0; $n < $size; $n++) {
+    $output .= '<div class="indentation">&nbsp;</div>';
+  }
+  return $output;
+}
+
+/**
  * @} End of "defgroup themeable".
  */
 
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.40
diff -u -r1.40 system.css
--- modules/system/system.css	16 Nov 2007 13:16:50 -0000	1.40
+++ modules/system/system.css	19 Nov 2007 08:20:28 -0000
@@ -46,8 +46,9 @@
 }
 div.indentation {
   width: 20px;
+  height: 1.7em;
   margin: -0.4em 0.2em -0.4em -0.4em;
-  padding: 0.4em 0 0.4em 0.6em;
+  padding: 0.42em 0 0.42em 0.6em;
   float: left;
 }
 div.tree-child {
@@ -367,7 +368,7 @@
   cursor: move;
   float: left;
   height: 1.7em;
-  margin: -0.42em 0 -0.42em -0.5em;
+  margin: -0.4em 0 -0.4em -0.5em;
   padding: 0.42em 1.5em 0.42em 0.5em;
   text-decoration: none;
 }
