Index: wysiwyg.admin.css
===================================================================
RCS file: wysiwyg.admin.css
diff -N wysiwyg.admin.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ wysiwyg.admin.css	1 Feb 2011 03:52:24 -0000
@@ -0,0 +1,76 @@
+/* $Id$ */
+
+#toolbar-rows {
+  margin: 1em 0;
+}
+#toolbar-rows .toolbar-row {
+  border: 1px solid gray;
+  padding: 0.2em 0.2em 0.2em 20px;
+  min-height: 2em;
+  position: relative;
+  margin-bottom: 0.5em;
+}
+#toolbar-rows .single-group .toolbar-group {
+  border: 0;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+}
+#toolbar-rows .single-group .group-handler {
+  display: none;
+}
+#toolbar-rows .toolbar-group {
+  border: 1px dotted silver;
+  display: inline-block;
+  min-width: 10em;
+  min-height: 27px;
+  padding: 0 0.5em;
+  margin: 0 0.5em 0.1em 0;
+  position: relative;
+}
+.toolbar-row-template, .toolbar-group-template  {
+  display: none;
+}
+#toolbar-rows .row-handler {
+  position: absolute;
+  top: 0.2em;
+  left: 0.2em;
+}
+#toolbar-available-buttons {
+  border: 1px dashed silver;
+  padding: 0.5em;
+  margin-bottom: 1em;
+  position: relative;
+}
+#wysiwyg-toolbar-designer .add {
+  display: inline-block;
+  background: transparent url(images/add.png) no-repeat;
+  width: 16px;
+  height: 16px;
+  text-decoration: none;
+  outline: none;
+}
+#wysiwyg-toolbar-designer .add-group {
+  position: absolute;
+  right: 0.2em;
+  top: 0.2em;
+}
+#wysiwyg-toolbar-designer .handler {
+  background: url(images/draggable.png) no-repeat 0 4px;
+  width: 16px;
+  height: 16px;
+  display: inline-block;
+  text-decoration: none;
+}
+#wysiwyg-toolbar-designer .wysiwyg-button {
+  border: 1px solid silver;
+  display: inline-block;
+  margin: 0.2em 0.2em 0.2em 0;
+  padding: 0 0.5em;
+  height: 20px;
+  cursor: pointer;
+}
+#wysiwyg-toolbar-designer .ahah-progress, #wysiwyg-toolbar-designer div.warning {
+  /* Hidden by default. */
+  display: none;
+}
Index: wysiwyg.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.admin.inc,v
retrieving revision 1.30
diff -u -p -r1.30 wysiwyg.admin.inc
--- wysiwyg.admin.inc	23 Jan 2011 01:11:12 -0000	1.30
+++ wysiwyg.admin.inc	3 Feb 2011 23:49:33 -0000
@@ -7,9 +7,9 @@
  */
 
 /**
- * Form builder for Wysiwyg profile form.
+ * Prepare default properties for profile.
  */
-function wysiwyg_profile_form($form, &$form_state, $profile) {
+function wysiwyg_profile_default($profile) {
   // Merge in defaults.
   $profile = (array) $profile;
   $profile += array(
@@ -27,7 +27,7 @@ function wysiwyg_profile_form($form, &$f
     'language' => 'en',
     'access' => 1,
     'access_pages' => "node/*\nuser/*\ncomment/*",
-    'buttons' => array(),
+    'toolbar' => array(),
     'toolbar_loc' => 'top',
     'toolbar_align' => 'left',
     'path_loc' => 'bottom',
@@ -45,6 +45,15 @@ function wysiwyg_profile_form($form, &$f
     'css_classes' => NULL,
   );
   $profile = (object) $profile;
+  return $profile;
+}
+
+/**
+ * Form builder for Wysiwyg profile form.
+ */
+function wysiwyg_profile_form($form, &$form_state, $profile) {
+  $profile = wysiwyg_profile_default($profile);
+  $module_path = drupal_get_path('module', 'wysiwyg');
 
   $formats = filter_formats();
   $editor = wysiwyg_get_editor($profile->editor);
@@ -111,50 +120,118 @@ function wysiwyg_profile_form($form, &$f
   asort($predefined);
   $form['basic']['language']['#options'] = $predefined;
 
-  $form['buttons'] = array(
+  $form['extensions'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Buttons and plugins'),
+    '#title' => t('Extensions'),
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
     '#tree' => TRUE,
     '#theme' => 'wysiwyg_admin_button_table',
   );
 
+  // Retrieve all available extensions and buttons.
   $plugins = wysiwyg_get_plugins($profile->editor);
-  // Generate the button list.
+  $buttons = array();
   foreach ($plugins as $name => $meta) {
     if (isset($meta['buttons']) && is_array($meta['buttons'])) {
       foreach ($meta['buttons'] as $button => $title) {
-        $icon = '';
-        if (!empty($meta['path'])) {
-          // @todo Button icon locations are different in editors, editor versions,
-          //   and contrib/custom plugins (like Image Assist, f.e.).
-          $img_src = $meta['path'] . "/images/$name.gif";
-          // Handle plugins that have more than one button.
-          if (!file_exists($img_src)) {
-            $img_src = $meta['path'] . "/images/$button.gif";
-          }
-          $icon = file_exists($img_src) ? '<img src="' . base_path() . $img_src . '" title="' . $button . '" style="border: 1px solid grey; vertical-align: middle;" />' : '';
-        }
-        $title = (isset($meta['url']) ? l($title, $meta['url'], array('target' => '_blank')) : $title);
-        $title = (!empty($icon) ? $icon . ' ' . $title : $title);
-        $form['buttons'][$name][$button] = array(
-          '#type' => 'checkbox',
-          '#title' => $title,
-          '#default_value' => !empty($profile->settings['buttons'][$name][$button]) ? $profile->settings['buttons'][$name][$button] : FALSE,
+        $buttons[] = array(
+          'plugin' => $name,
+          'button' => $button,
+          'title' => $title,
         );
       }
     }
-    else if (isset($meta['extensions']) && is_array($meta['extensions'])) {
+    if (isset($meta['extensions']) && is_array($meta['extensions'])) {
       foreach ($meta['extensions'] as $extension => $title) {
-        $form['buttons'][$name][$extension] = array(
+        $form['extensions'][$name][$extension] = array(
           '#type' => 'checkbox',
           '#title' => isset($meta['url']) ? l($title, $meta['url'], array('target' => '_blank')) : $title,
-          '#default_value' => !empty($profile->settings['buttons'][$name][$extension]) ? $profile->settings['buttons'][$name][$extension] : FALSE,
+          '#default_value' => !empty($profile->settings['extensions'][$name][$extension]) ? $profile->settings['extensions'][$name][$extension] : 0,
         );
       }
     }
   }
+  // Hide the extensions fieldset if there are no extensions.
+  if (!element_children($form['extensions'])) {
+    $form['extensions']['#access'] = FALSE;
+  }
+
+  $toolbar_support = array(
+    'toolbar' => $profile->settings['toolbar'],
+    'toolbar rows' => !empty($editor['toolbar rows']),
+    'toolbar groups' => !empty($editor['toolbar groups']),
+    'toolbar separator' => !empty($editor['toolbar separator']),
+  );
+  // Separator.
+  if ($toolbar_support['toolbar separator']) {
+    array_unshift($buttons, array(
+      'plugin' => 'default',
+      'button' => 'separator',
+      'title' => t('Separator'),
+    ));
+  }
+
+  $form['toolbar_designer'] = array(
+    '#type' => 'container',
+    '#id' => 'wysiwyg-toolbar-designer',
+    '#access' => !empty($buttons),
+  );
+  $form['toolbar_designer']['available'] = array(
+    '#type' => 'item',
+    '#title' => t('Available buttons'),
+    '#id' => 'toolbar-available-buttons',
+  );
+  foreach ($buttons as $button) {
+    $form['toolbar_designer']['available'][$button['plugin']][$button['button']] = array(
+      '#theme' => 'html_tag',
+      '#tag' => 'span',
+      '#value' => check_plain($button['title']),
+      '#attributes' => array('class' => array('wysiwyg-button', 'wysiwyg-button-' . $button['plugin'] . '-' . $button['button'])),
+    );
+  }
+  $form['toolbar_designer']['toolbar'] = array(
+    '#type' => 'item',
+    '#title' => t('Toolbar'),
+  );
+  $form['toolbar_designer']['toolbar']['toolbar'] = array(
+    '#markup' => '<div id="toolbar-rows"></div>',
+  );
+  $form['toolbar_designer']['toolbar']['actions'] = array(
+    '#type' => 'container',
+    '#id' => 'toolbar-actions',
+  );
+  $form['toolbar_designer']['toolbar']['actions']['add-row'] = array(
+    '#type' => 'link',
+    '#title' => t('Add new row'),
+    '#href' => '#',
+    '#attributes' => array(
+      'class' => array('add', 'add-toolbar-row'),
+    ),
+  );
+  $form['toolbar_designer']['toolbar']['templates'] = array(
+    '#markup' => '<div class="toolbar-row-template">
+  <a href="javascript:;" class="row-handler handler">&nbsp;</a>
+  <a href="javascript:;" class="add add-group">&nbsp;</a>
+</div>
+<div class="toolbar-group-template">
+  <a href="javascript:;" class="group-handler handler">&nbsp;</a>
+</div>
+',
+  );
+
+  $form['toolbar_designer']['#attached']['js'][] = array(
+    'data' => array('wysiwyg_toolbar' => $toolbar_support),
+    'type' => 'setting',
+  );
+  $form['toolbar_designer']['#attached']['library'][] = array('system', 'ui.droppable');
+  $form['toolbar_designer']['#attached']['library'][] = array('system', 'ui.sortable');
+  $form['toolbar_designer']['#attached']['js'][] = $module_path . '/wysiwyg.admin.js';
+  $form['toolbar_designer']['#attached']['css'][] = $module_path . '/wysiwyg.admin.css';
+  $form['toolbar_designer']['_toolbar'] = array(
+    '#type' => 'hidden',
+    '#attributes' => array('id' => 'edit-toolbar'),
+  );
 
   $form['appearance'] = array(
     '#type' => 'fieldset',
@@ -309,15 +386,44 @@ function wysiwyg_profile_form($form, &$f
  * @see wysiwyg_profile_form()
  */
 function wysiwyg_profile_form_submit($form, &$form_state) {
-  $values = $form_state['values'];
-  if (isset($values['buttons'])) {
+  $values = &$form_state['values'];
+  if (isset($values['extensions'])) {
     // Store only enabled buttons for each plugin.
-    foreach ($values['buttons'] as $plugin => $buttons) {
-      $values['buttons'][$plugin] = array_filter($values['buttons'][$plugin]);
+    foreach ($values['extensions'] as $plugin => $buttons) {
+      $values['extensions'][$plugin] = array_filter($values['extensions'][$plugin]);
     }
     // Store only enabled plugins.
-    $values['buttons'] = array_filter($values['buttons']);
+    $values['extensions'] = array_filter($values['extensions']);
+  }
+  else {
+    $values['extensions'] = array();
   }
+
+  // Restore toolbar array
+  $raw_toolbar = explode("\n", trim($values['toolbar']));
+  $values['toolbar'] = array();
+  foreach ($raw_toolbar as $raw_row) {
+    $row = array();
+    foreach (explode('|', trim($raw_row, '|')) as $raw_group) {
+      $group = array();
+      foreach (explode(',', trim($raw_group, ',')) as $button) {
+        list($plugin, $name) = explode('.', $button, 2);
+        $group[] = array(
+          'button' => $name,
+          'plugin' => $plugin,
+        );
+      }
+      if ($group) {
+        $row[] = $group;
+      }
+    }
+    if ($row) {
+      $values['toolbar'][] = $row;
+    }
+  }
+  // Remove available plugins/button information.
+  unset($values['buttons']);
+
   // Remove any white-space from 'block_formats' setting, since editor
   // implementations rely on a comma-separated list to explode().
   $values['block_formats'] = preg_replace('@\s+@', '', $values['block_formats']);
@@ -328,9 +434,8 @@ function wysiwyg_profile_form_submit($fo
   $editor = $values['editor'];
   unset($values['format'], $values['input_format'], $values['editor']);
 
-  // Remove FAPI values.
-  // @see system_settings_form_submit()
-  unset($values['submit'], $values['form_id'], $values['op'], $values['form_token'], $values['form_build_id']);
+  // Remove internal Form API values.
+  form_state_values_clean($form_state);
 
   // Insert new profile data.
   db_merge('wysiwyg')
Index: wysiwyg.admin.js
===================================================================
RCS file: wysiwyg.admin.js
diff -N wysiwyg.admin.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ wysiwyg.admin.js	3 Feb 2011 23:52:32 -0000
@@ -0,0 +1,205 @@
+// $Id$
+(function ($, undefined) {
+
+Drupal.behaviors.wysiwygToolbarDesigner = {
+  attach: function (context, settings) {
+    var settings = settings.wysiwyg_toolbar;
+    var workspace = $('#wysiwyg-toolbar-designer');
+    var designArea = $('#toolbar-rows');
+    var changeNotification = $('#wysiwyg-toolbar-designer div.warning');
+    var availableButtons = $('#toolbar-available-buttons');
+    var separator = $('.wysiwyg-button-default-Separator',availableButtons);
+
+    var createRow = function(noGroup) {
+      var row = $('.toolbar-row-template',workspace).clone().removeClass('toolbar-row-template');
+      row.addClass('toolbar-row').sortable({
+        handle: '.group-handler',
+        revert: true,
+        items: '.toolbar-group',
+        addClasses: false,
+        connectWith: '#toolbar-rows .toolbar-row',
+        stop: function(event,ui) {
+          changeNotification.fadeIn();
+        }
+      });
+      if (settings['toolbar groups']) {
+        row.find('.add-group').click(function() {
+          var group = createGroup();
+          row.append(group);
+          row.sortable('refresh');
+          return false;
+        });
+      }
+      else {
+        row.find('.add-group').hide();
+        row.addClass('single-group');
+      }
+      // add required group
+      if (!noGroup) {
+        var group = createGroup();
+        row.append(group);
+      }
+      return row;
+    };
+
+    var createGroup = function() {
+      var group = $('.toolbar-group-template').clone().removeClass('toolbar-group-template');
+      group.addClass('toolbar-group');
+      group.sortable({
+        revert: true,
+        items: '.wysiwyg-button',
+        connectWith: '#toolbar-rows .toolbar-group',
+        addClasses: false,
+        stop: function(event,ui) {
+          changeNotification.fadeIn();
+        }
+      });
+
+      group.droppable({
+        accept: '.template-button',
+        drop: function(event,ui) {
+          var button = ui.draggable.clone();
+          button.removeClass('template-button').addClass('toolbar-button');
+
+          // Disable this button in template area.
+          ui.draggable.hide();
+          separator.show();
+
+          $(this).append(button).sortable('refresh');
+          changeNotification.fadeIn();
+        }
+      });
+      return group;
+    }
+
+    var reset = function() {
+      $('.toolbar-row',designArea).remove();
+      // Enable all buttons and then disable it later.
+      $('.wysiwyg-button',availableButtons).show();
+
+      for (var i in settings.toolbar) {
+        var groups = settings.toolbar[i];
+        var row = createRow(true);
+        for (var j in groups) {
+          var group = createGroup();
+          var buttons = groups[j];
+
+          for (var k in buttons) {
+            var buttonClass = '.wysiwyg-button-' + buttons[k].plugin + '-' + buttons[k].button;
+            var template_button = $(buttonClass,$('#toolbar-available-buttons'));
+            if (template_button.length) {
+              button = template_button.clone().show();
+              button.removeClass('template-button').addClass('toolbar-button');
+              group.append(button);
+
+              // Disable button in template area.
+              template_button.hide();
+            }
+          }
+          row.append(group);
+        }
+        designArea.append(row);
+      }
+      // Make sure we always have at least one row.
+      if (!settings['toolbar rows']) {
+        if ($('.toolbar-row',designArea).length <= 0) {
+          var row = createRow();
+          designArea.append(row);
+        }
+      }
+      separator.show();
+      changeNotification.fadeOut();
+    }
+
+    $('.add-toolbar-row',workspace).click(function(){
+      // clone from toolbar template
+      var row = createRow();
+
+      // Append row to design area.
+      designArea.append(row).sortable('refresh');
+      changeNotification.fadeIn();
+      return false;
+    });
+
+    $('.wysiwyg-button',availableButtons).addClass('template-button').draggable({
+      handle: '.handler',
+      helper: 'clone',
+      revert: 'invalid',
+      addClasses: false
+    });
+
+    availableButtons.droppable({
+      accept: '.toolbar-button, .toolbar-group, .toolbar-row',
+      drop: function(event, ui) {
+        var item = ui.draggable;
+        var parent = item.parent();
+
+        // Guarantee there is at least 1 row and 1 group.
+        if (item.hasClass('toolbar-row') && parent.find('.toolbar-row').not('.ui-sortable-placeholder').length == 1) {
+          return;
+        } else if (item.hasClass('toolbar-group') && parent.find('.toolbar-group').not('.ui-sortable-placeholder').length == 1) {
+          return;
+        }
+
+        var buttons = $('.toolbar-button', item);
+        if (item.hasClass('toolbar-button')) {
+          buttons.add(item);
+        }
+
+        // Remove each button and enable in template.
+        buttons.each(function(){
+          var button_id = /wysiwyg-button-([^-]+-[^\s]+)/.exec($(this).attr('class'));
+          $('.wysiwyg-button-' + button_id[1]).show();
+        });
+
+        item.parent().sortable('refresh');
+        item.remove();
+
+        changeNotification.fadeIn();
+      }
+    });
+
+    $('#toolbar-rows').sortable({
+      items: '.toolbar-row',
+      handle: '.row-handler',
+      addClass: false,
+      stop: function(event, ui) {
+        changeNotification.fadeIn();
+      }
+    });
+
+    // Design actions buttons.
+    $('#reset-design').click(function() {
+      if (!changeNotification.is(':hidden') && confirm(Drupal.t('Do you want to reset the changes ?')))
+        reset();
+      return false;
+    });
+
+    $('#wysiwyg-profile-form').submit(function() {
+      // Prepare toolbar data to submit.
+      var toolbar = "";
+      designArea.find('.toolbar-row').each(function(key,rowDom){
+        var row = "";
+        $('.toolbar-group',rowDom).each(function(key,groupDom){
+          var group = "";
+          $('.wysiwyg-button',groupDom).each(function(key,button){
+            var cls = /wysiwyg-button-([^-]+)-([^\s]+)/.exec($(button).attr('class'));
+            group += cls[1] + "." + cls[2] + ",";
+          })
+          row += group + "|";
+        });
+        toolbar += row + "\n";
+      });
+      // Assign to hidden field.
+      $('#edit-toolbar').val(toolbar);
+    });
+
+    reset();
+
+    if (!settings['toolbar rows']) {
+      $('.add-toolbar-row').hide();
+    }
+  }
+};
+
+})(jQuery);
Index: wysiwyg.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/wysiwyg.module,v
retrieving revision 1.54
diff -u -p -r1.54 wysiwyg.module
--- wysiwyg.module	30 Jan 2011 04:06:47 -0000	1.54
+++ wysiwyg.module	3 Feb 2011 23:38:41 -0000
@@ -457,16 +457,17 @@ function wysiwyg_add_plugin_settings($pr
 
   // Process native editor plugins.
   if (isset($editor['plugin settings callback'])) {
-    // @todo Require PHP 5.1 in 3.x and use array_intersect_key().
     $profile_plugins_native = array();
-    foreach ($plugins[$editor['name']] as $plugin => $meta) {
-      // Skip Drupal plugins (handled below).
-      if ($plugin === $proxy) {
-        continue;
-      }
-      // Only keep native plugins that are enabled in this profile.
-      if (isset($profile->settings['buttons'][$plugin])) {
-        $profile_plugins_native[$plugin] = $meta;
+    foreach ($profile->settings['toolbar'] as $row) {
+      foreach ($row as $group) {
+        foreach ($group as $button) {
+          $plugin = $button['plugin'];
+          // Skip Drupal plugins (handled below).
+          if ($plugin === $proxy) {
+            continue;
+          }
+          $profile_plugins_native[$plugin] = $plugins[$plugin];
+        }
       }
     }
     // Invoke the editor's plugin settings callback, so it can populate the
@@ -480,24 +481,32 @@ function wysiwyg_add_plugin_settings($pr
 
   // Process Drupal plugins.
   if ($proxy && isset($editor['proxy plugin settings callback'])) {
+    $drupal_plugins = wysiwyg_get_all_plugins();
     $profile_plugins_drupal = array();
-    foreach (wysiwyg_get_all_plugins() as $plugin => $meta) {
-      if (isset($profile->settings['buttons'][$proxy][$plugin])) {
-        // JavaScript and plugin-specific settings for Drupal plugins must be
-        // loaded and processed only once. Plugin information is cached
-        // statically to pass it to the editor's proxy plugin settings callback.
-        if (!isset($processed_plugins[$proxy][$plugin])) {
-          $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin] = $meta;
-          // Load the Drupal plugin's JavaScript.
-          drupal_add_js($meta['js path'] . '/' . $meta['js file']);
-          // Add plugin-specific settings.
-          if (isset($meta['settings'])) {
-            drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($plugin => $meta['settings'])))), 'setting');
+    foreach ($profile->settings['toolbar'] as $row) {
+      foreach ($row as $group) {
+        foreach ($group as $button) {
+          $plugin = $button['plugin'];
+          $button_id = $button['button'];
+          if ($plugin == $proxy && isset($drupal_plugins[$button_id])) {
+            $meta = $drupal_plugins[$button_id];
+            // JavaScript and plugin-specific settings for Drupal plugins must be
+            // loaded and processed only once. Plugin information is cached
+            // statically to pass it to the editor's proxy plugin settings callback.
+            if (!isset($processed_plugins[$proxy][$button_id])) {
+              $profile_plugins_drupal[$button_id] = $processed_plugins[$proxy][$button_id] = $meta;
+              // Load the Drupal plugin's JavaScript.
+              drupal_add_js($meta['js path'] . '/' . $meta['js file']);
+              // Add plugin-specific settings.
+              if (isset($meta['settings'])) {
+                drupal_add_js(array('wysiwyg' => array('plugins' => array('drupal' => array($button_id => $meta['settings'])))), 'setting');
+              }
+            }
+            else {
+              $profile_plugins_drupal[$button_id] = $processed_plugins[$proxy][$button_id];
+            }
           }
         }
-        else {
-          $profile_plugins_drupal[$plugin] = $processed_plugins[$proxy][$plugin];
-        }
       }
     }
     // Invoke the editor's proxy plugin settings callback, so it can populate
Index: editors/ckeditor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/ckeditor.inc,v
retrieving revision 1.13
diff -u -p -r1.13 ckeditor.inc
--- editors/ckeditor.inc	14 Jan 2011 15:42:47 -0000	1.13
+++ editors/ckeditor.inc	1 Feb 2011 04:32:16 -0000
@@ -28,6 +28,9 @@ function wysiwyg_ckeditor_editor() {
         ),
       ),
     ),
+    'toolbar groups' => TRUE,
+    'toolbar rows' => TRUE,
+    'toolbar separator' => TRUE,
     'version callback' => 'wysiwyg_ckeditor_version',
     'themes callback' => 'wysiwyg_ckeditor_themes',
     'settings callback' => 'wysiwyg_ckeditor_settings',
@@ -192,49 +195,62 @@ function wysiwyg_ckeditor_settings($edit
     $settings['toolbarLocation'] = $config['toolbar_loc'];
   }
 
-  $settings['toolbar'] = array();
-  if (!empty($config['buttons'])) {
-    $extra_plugins = array();
-    $plugins = wysiwyg_get_plugins($editor['name']);
-    foreach ($config['buttons'] as $plugin => $buttons) {
-      foreach ($buttons as $button => $enabled) {
-        // Iterate separately over buttons and extensions properties.
-        foreach (array('buttons', 'extensions') as $type) {
-          // Skip unavailable plugins.
-          if (!isset($plugins[$plugin][$type][$button])) {
-            continue;
-          }
-          // Add buttons.
-          if ($type == 'buttons') {
-            $settings['toolbar'][] = $button;
-          }
-          // Add external Drupal plugins to the list of extensions.
-          if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
-            $extra_plugins[] = $button;
-          }
-          // Add external plugins to the list of extensions.
-          elseif ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
-            $extra_plugins[] = $plugin;
-          }
-          // Add internal buttons that also need to be loaded as extension.
-          elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
-            $extra_plugins[] = $plugin;
-          }
-          // Add plain extensions.
-          elseif ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
-            $extra_plugins[] = $plugin;
-          }
-          // Allow plugins to add or override global configuration settings.
-          if (!empty($plugins[$plugin]['options'])) {
-            $settings = array_merge($settings, $plugins[$plugin]['options']);
-          }
-        }
+  $toolbar = array();
+  $extra_plugins = array();
+  $plugins = wysiwyg_get_plugins($editor['name']);
+  foreach ($config['extensions'] as $plugin => $buttons) {
+    foreach ($buttons as $button => $enabled) {
+      if (!isset($plugins[$plugin]['extensions'][$button])) {
+        continue;
+      }
+      // Add plain extensions.
+      if (!empty($plugins[$plugin]['load'])) {
+        $extra_plugins[] = $plugin;
+      }
+      // Allow plugins to add or override global configuration settings.
+      if (!empty($plugins[$plugin]['options'])) {
+        $settings = array_merge($settings, $plugins[$plugin]['options']);
       }
     }
-    if (!empty($extra_plugins)) {
-      $settings['extraPlugins'] = implode(',', $extra_plugins);
+  }
+
+  foreach ($config['toolbar'] as $row) {
+    foreach ($row as $buttons) {
+      $group = array();
+      foreach ($buttons as $button_config) {
+        $plugin = $button_config['plugin'];
+        $button = $button_config['button'];
+        if ($button == 'separator') {
+          $group[] = '-';
+          continue;
+        }
+        if (!isset($plugins[$plugin]['buttons'][$button])) {
+          continue;
+        }
+        $group[] = $button;
+
+        // Add external Drupal plugins to the list of extensions.
+        if (!empty($plugins[$plugin]['proxy']) || empty($plugins[$plugin]['internal']) || !empty($plugins[$plugin]['load'])) {
+          $extra_plugins[] = $button;
+        }
+        // Allow plugins to add or override global configuration settings.
+        if (!empty($plugins[$plugin]['options'])) {
+          $settings = array_merge($settings, $plugins[$plugin]['options']);
+        }
+      }
+      $toolbar[] = $group;
     }
+    $toolbar[] = '/';
   }
+
+  // Remove the last '/'.
+  array_pop($toolbar);
+  $settings['toolbar'] = $toolbar;
+
+  if (!empty($extra_plugins)) {
+    $settings['extraPlugins'] = implode(',', array_unique($extra_plugins));
+  }
+
   // For now, all buttons are placed into one row.
   $settings['toolbar'] = array($settings['toolbar']);
 
Index: editors/fckeditor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/fckeditor.inc,v
retrieving revision 1.22
diff -u -p -r1.22 fckeditor.inc
--- editors/fckeditor.inc	19 Dec 2010 23:26:56 -0000	1.22
+++ editors/fckeditor.inc	1 Feb 2011 04:36:49 -0000
@@ -20,6 +20,9 @@ function wysiwyg_fckeditor_editor() {
         'files' => array('fckeditor.js'),
       ),
     ),
+    'toolbar groups' => TRUE,
+    'toolbar rows' => TRUE,
+    'toolbar separator' => TRUE,
     'version callback' => 'wysiwyg_fckeditor_version',
     'themes callback' => 'wysiwyg_fckeditor_themes',
     'settings callback' => 'wysiwyg_fckeditor_settings',
@@ -139,32 +142,47 @@ function wysiwyg_fckeditor_settings($edi
 
   // Use our custom toolbar set.
   $settings['ToolbarSet'] = 'Wysiwyg';
-  // Populate our custom toolbar set for fckeditor.config.js.
-  $settings['buttons'] = array();
-  if (!empty($config['buttons'])) {
-    $plugins = wysiwyg_get_plugins($editor['name']);
-    foreach ($config['buttons'] as $plugin => $buttons) {
-      foreach ($buttons as $button => $enabled) {
-        // Iterate separately over buttons and extensions properties.
-        foreach (array('buttons', 'extensions') as $type) {
-          // Skip unavailable plugins.
-          if (!isset($plugins[$plugin][$type][$button])) {
-            continue;
-          }
-          // Add buttons.
-          if ($type == 'buttons') {
-            $settings['buttons'][] = $button;
-          }
-          // Allow plugins to add or override global configuration settings.
-          if (!empty($plugins[$plugin]['options'])) {
-            $settings = array_merge($settings, $plugins[$plugin]['options']);
-          }
+  $toolbar = array();
+  $plugins = wysiwyg_get_plugins($editor['name']);
+  foreach ($config['extensions'] as $plugin => $buttons) {
+    foreach ($buttons as $button => $enabled) {
+      if (!isset($plugins[$plugin]['extensions'][$button])) {
+        continue;
+      }
+      // Allow plugins to add or override global configuration settings.
+      if (!empty($plugins[$plugin]['options'])) {
+        $settings = array_merge($settings, $plugins[$plugin]['options']);
+      }
+    }
+  }
+
+  foreach ($config['toolbar'] as $row) {
+    foreach ($row as $buttons) {
+      $group = array();
+      foreach ($buttons as $button_config) {
+        $plugin = $button_config['plugin'];
+        $button = $button_config['button'];
+        if ($button == 'separator') {
+          $group[] = '-';
+          continue;
+        }
+        if (!isset($plugins[$plugin]['buttons'][$button])) {
+          continue;
+        }
+        $group[] = $button;
+        // Allow plugins to add or override global configuration settings.
+        if (!empty($plugins[$plugin]['options'])) {
+          $settings = array_merge($settings, $plugins[$plugin]['options']);
         }
       }
+      $toolbar[] = $group;
     }
+    $toolbar[] = '/';
   }
-  // For now, all buttons are placed into one row.
-  $settings['buttons'] = array($settings['buttons']);
+  // Remove the last '/'.
+  array_pop($toolbar);
+
+  $settings['ToolbarSets']['Wysiwyg'] = $toolbar;
 
   return $settings;
 }
Index: editors/markitup.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/markitup.inc,v
retrieving revision 1.12
diff -u -p -r1.12 markitup.inc
--- editors/markitup.inc	19 Dec 2010 19:19:56 -0000	1.12
+++ editors/markitup.inc	31 Jan 2011 04:22:59 -0000
@@ -156,11 +156,11 @@ function wysiwyg_markitup_settings($edit
     ),
   );
   $settings['markupSet'] = array();
-  if (!empty($config['buttons'])) {
-    foreach ($config['buttons'] as $plugin) {
-      foreach ($plugin as $button => $enabled) {
-        if (isset($default_buttons[$button])) {
-          $settings['markupSet'][$button] = $default_buttons[$button];
+  if (!empty($config['toolbar'])) {
+    foreach ($config['toolbar'] as $row) {
+      foreach ($row as $group) {
+        foreach ($group as $button) {
+          $settings['markupSet'][$button['button']] = $default_buttons[$button['button']];
         }
       }
     }
Index: editors/nicedit.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/nicedit.inc,v
retrieving revision 1.8
diff -u -p -r1.8 nicedit.inc
--- editors/nicedit.inc	13 Nov 2010 18:31:20 -0000	1.8
+++ editors/nicedit.inc	1 Feb 2011 04:38:42 -0000
@@ -67,12 +67,16 @@ function wysiwyg_nicedit_settings($edito
 
   // Add configured buttons or all available.
   $settings['buttonList'] = array();
-  if (!empty($config['buttons'])) {
+  if (!empty($config['toolbar'])) {
     $buttons = array();
-    foreach ($config['buttons'] as $plugin) {
-      $buttons = array_merge($buttons, $plugin);
+    foreach ($config['toolbar'] as $row) {
+      foreach ($row as $group) {
+        foreach ($group as $button) {
+          $buttons[] = $button['button'];
+        }
+      }
     }
-    $settings['buttonList'] = array_keys($buttons);
+    $settings['buttonList'] = $buttons;
   }
 
   // Add editor content stylesheet.
Index: editors/openwysiwyg.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/openwysiwyg.inc,v
retrieving revision 1.7
diff -u -p -r1.7 openwysiwyg.inc
--- editors/openwysiwyg.inc	13 Nov 2010 18:31:21 -0000	1.7
+++ editors/openwysiwyg.inc	1 Feb 2011 04:39:59 -0000
@@ -21,6 +21,8 @@ function wysiwyg_openwysiwyg_editor() {
         'files' => array('wysiwyg.js'),
       ),
     ),
+    'toolbar rows' => TRUE,
+    'toolbar separator' => TRUE,
     'version callback' => 'wysiwyg_openwysiwyg_version',
     'themes callback' => 'wysiwyg_openwysiwyg_themes',
     'settings callback' => 'wysiwyg_openwysiwyg_settings',
@@ -109,21 +111,25 @@ function wysiwyg_openwysiwyg_settings($e
   }
 
   $settings['Toolbar'] = array();
-  if (!empty($config['buttons'])) {
+  if (!empty($config['toolbar'])) {
     $plugins = wysiwyg_get_plugins($editor['name']);
-    foreach ($config['buttons'] as $plugin => $buttons) {
-      foreach ($buttons as $button => $enabled) {
-        foreach (array('buttons', 'extensions') as $type) {
+    foreach ($config['toolbar'] as $row) {
+      $toolbar_row = array();
+      foreach ($row as $buttons) {
+        foreach ($buttons as $button_config) {
           // Skip unavailable plugins.
-          if (!isset($plugins[$plugin][$type][$button])) {
-            continue;
+          $plugin = $button_config['plugin'];
+          $button = $button_config['button'];
+          if ($button == 'separator') {
+            $toolbar_row[] = 'seperator';
           }
-          // Add buttons.
-          if ($type == 'buttons') {
-            $settings['Toolbar'][0][] = $button;
+          elseif (!isset($plugins[$plugin]['buttons'][$button])) {
+            continue;
           }
+          $toolbar_row[] = $button;
         }
       }
+      $settings['Toolbar'][] = $toolbar_row;
     }
   }
 
Index: editors/tinymce.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/tinymce.inc,v
retrieving revision 1.48
diff -u -p -r1.48 tinymce.inc
--- editors/tinymce.inc	3 Feb 2011 11:46:02 -0000	1.48
+++ editors/tinymce.inc	3 Feb 2011 22:19:01 -0000
@@ -27,6 +27,8 @@ function wysiwyg_tinymce_editor() {
         'files' => array('tiny_mce_src.js'),
       ),
     ),
+    'toolbar rows' => TRUE,
+    'toolbar separator' => TRUE,
     'version callback' => 'wysiwyg_tinymce_version',
     'themes callback' => 'wysiwyg_tinymce_themes',
     'settings callback' => 'wysiwyg_tinymce_settings',
@@ -195,69 +197,72 @@ function wysiwyg_tinymce_settings($edito
     }
   }
 
-  // Find the enabled buttons and the button row they belong on.
-  // Also map the plugin metadata for each button.
-  // @todo What follows is a pain; needs a rewrite.
-  // $settings['buttons'] are stacked into $settings['theme_advanced_buttons1']
-  // later.
-  $settings['buttons'] = array();
-  if (!empty($config['buttons']) && is_array($config['buttons'])) {
-    // Only array keys in $settings['extensions'] matter; added to
-    // $settings['plugins'] later.
-    $settings['extensions'] = array();
-    // $settings['extended_valid_elements'] are just stacked, unique'd later,
-    // and transformed into a comma-separated string in
-    // wysiwyg_add_editor_settings().
-    // @todo Needs a complete plugin API redesign using arrays for
-    //   tag => attributes definitions and array_merge_recursive().
-    $settings['extended_valid_elements'] = array();
-
-    $plugins = wysiwyg_get_plugins($editor['name']);
-    foreach ($config['buttons'] as $plugin => $buttons) {
-      foreach ($buttons as $button => $enabled) {
-        // Iterate separately over buttons and extensions properties.
-        foreach (array('buttons', 'extensions') as $type) {
-          // Skip unavailable plugins.
-          if (!isset($plugins[$plugin][$type][$button])) {
-            continue;
-          }
-          // Add buttons.
-          if ($type == 'buttons') {
-            $settings['buttons'][] = $button;
-          }
-          // Add external Drupal plugins to the list of extensions.
-          if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
-            $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1;
-          }
-          // Add external plugins to the list of extensions.
-          else if ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
-            $settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1;
-          }
-          // Add internal buttons that also need to be loaded as extension.
-          else if ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
-            $settings['extensions'][$plugin] = 1;
-          }
-          // Add plain extensions.
-          else if ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
-            $settings['extensions'][$plugin] = 1;
-          }
-          // Allow plugins to add valid HTML elements.
-          if (!empty($plugins[$plugin]['extended_valid_elements'])) {
-            $settings['extended_valid_elements'] = array_merge($settings['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']);
-          }
-          // Allow plugins to add or override global configuration settings.
-          if (!empty($plugins[$plugin]['options'])) {
-            $settings = array_merge($settings, $plugins[$plugin]['options']);
-          }
-        }
+  $toolbar = array();
+  $extensions = array();
+  $settings['extended_valid_elements'] = array();
+
+  $plugins = wysiwyg_get_plugins($editor['name']);
+  foreach ($config['extensions'] as $plugin => $buttons) {
+    foreach ($buttons as $button => $enabled) {
+      if (!isset($plugins[$plugin], $plugins[$plugin]['extensions'], $plugins[$plugin]['extensions'][$button])) {
+        continue;
+      }
+      // Add plain extensions.
+      if (!empty($plugins[$plugin]['load'])) {
+        $extensions[$plugin] = 1;
+      }
+      // Allow plugins to add valid HTML elements.
+      if (!empty($plugins[$plugin]['extended_valid_elements'])) {
+        $settings['extended_valid_elements'] = array_merge($settings['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']);
+      }
+      // Allow plugins to add or override global configuration settings.
+      if (!empty($plugins[$plugin]['options'])) {
+        $settings = array_merge($settings, $plugins[$plugin]['options']);
       }
     }
-    // Clean-up.
-    $settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']);
-    if ($settings['extensions']) {
-      $settings['plugins'] = array_keys($settings['extensions']);
+  }
+
+  foreach ($config['toolbar'] as $row) {
+    $group = array();
+    foreach ($row as $buttons) {
+      foreach ($buttons as $button_config) {
+        $plugin = $button_config['plugin'];
+        $button = $button_config['button'];
+        if ($button == 'separator') {
+          $group[] = '|';
+          continue;
+        }
+        if (!isset($plugins[$plugin]['buttons'][$button])) {
+          continue;
+        }
+        $group[] = $button;
+        // Add external Drupal plugins to the list of extensions.
+        if (!empty($plugins[$plugin]['proxy'])) {
+          $extensions[_wysiwyg_tinymce_plugin_name('add', $button)] = 1;
+        }
+        // Add external plugins to the list of extensions.
+        elseif (empty($plugins[$plugin]['internal'])) {
+          $extensions[_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1;
+        }
+        // Add internal buttons that also need to be loaded as extension.
+        elseif (!empty($plugins[$plugin]['load'])) {
+          $extensions[$plugin] = 1;
+        }
+        // Allow plugins to add or override global configuration settings.
+        if (!empty($plugins[$plugin]['options'])) {
+          $settings = array_merge($settings, $plugins[$plugin]['options']);
+        }
+        // Allow plugins to add valid HTML elements.
+        if (!empty($plugins[$plugin]['extended_valid_elements'])) {
+          $settings['extended_valid_elements'] = array_merge($settings['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']);
+        }
+      }
     }
-    unset($settings['extensions']);
+    $toolbar[] = $group;
+  }
+  $settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']);
+  if ($extensions) {
+    $settings['plugins'] = array_keys($extensions);
   }
 
   // Add theme-specific settings.
@@ -274,7 +279,7 @@ function wysiwyg_tinymce_settings($edito
       if (isset($config['block_formats'])) {
         $settings['theme_advanced_blockformats'] = $config['block_formats'];
       }
-      if (isset($settings['buttons'])) {
+      if ($toolbar) {
         // These rows explicitly need to be set to be empty, otherwise TinyMCE
         // loads its default buttons of the advanced theme for each row.
         $settings += array(
@@ -282,14 +287,14 @@ function wysiwyg_tinymce_settings($edito
           'theme_advanced_buttons2' => array(),
           'theme_advanced_buttons3' => array(),
         );
-        // @todo Allow to sort/arrange editor buttons.
-        for ($i = 0; $i < count($settings['buttons']); $i++) {
-          $settings['theme_advanced_buttons1'][] = $settings['buttons'][$i];
+        for ($i = 0; $i < count($toolbar); $i++) {
+          foreach ($toolbar[$i] as $button) {
+            $settings['theme_advanced_buttons' . ($i + 1)][] = $button;
+          }
         }
       }
       break;
   }
-  unset($settings['buttons']);
 
   // Convert the config values into the form expected by TinyMCE.
   $csv_settings = array('plugins', 'extended_valid_elements', 'theme_advanced_buttons1', 'theme_advanced_buttons2', 'theme_advanced_buttons3');
Index: editors/css/tinymce-2.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/css/tinymce-2.css,v
retrieving revision 1.2
diff -u -p -r1.2 tinymce-2.css
--- editors/css/tinymce-2.css	29 Oct 2008 00:35:11 -0000	1.2
+++ editors/css/tinymce-2.css	1 Feb 2011 04:37:38 -0000
@@ -7,22 +7,3 @@ table.mceEditor {
   clear: left;
 }
 
-/**
- * Align all buttons and separators in a single row, so they wrap into multiple
- * rows if required.
- */
-.mceToolbarTop a, .mceToolbarBottom a {
-  float: left;
-}
-.mceSeparatorLine {
-  float: left;
-  margin-top: 3px;
-}
-.mceSelectList {
-  float: left;
-  margin-bottom: 1px;
-}
-/* Place table plugin buttons into new row */
-#mce_editor_0_table, #mce_editor_1_table {
-  clear: left;
-}
Index: editors/css/tinymce-3.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/css/tinymce-3.css,v
retrieving revision 1.3
diff -u -p -r1.3 tinymce-3.css
--- editors/css/tinymce-3.css	29 Oct 2008 00:35:11 -0000	1.3
+++ editors/css/tinymce-3.css	1 Feb 2011 04:37:31 -0000
@@ -7,19 +7,3 @@ table.mceLayout {
   clear: left;
 }
 
-/**
- * Align all buttons and separators in a single row, so they wrap into multiple
- * rows if required.
- */
-.mceToolbar td {
-  display: inline;
-}
-.mceToolbar a,
-.mceSeparator {
-  float: left;
-}
-.mceListBox,
-.mceSplitButton {
-  float: left;
-  margin-bottom: 1px;
-}
Index: editors/js/fckeditor.config.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/fckeditor.config.js,v
retrieving revision 1.7
diff -u -p -r1.7 fckeditor.config.js
--- editors/js/fckeditor.config.js	29 Sep 2009 01:48:23 -0000	1.7
+++ editors/js/fckeditor.config.js	31 Jan 2011 04:22:59 -0000
@@ -17,28 +17,7 @@ var pluginSettings = (Drupal.settings.wy
  * Apply format-specific settings.
  */
 for (var setting in wysiwygSettings) {
-  if (setting == 'buttons') {
-    // Apply custom Wysiwyg toolbar for this format.
-    // FCKConfig.ToolbarSets['Wysiwyg'] = wysiwygSettings.buttons;
-
-    // Temporarily stack buttons into multiple button groups and remove
-    // separators until #277954 is solved.
-    FCKConfig.ToolbarSets['Wysiwyg'] = [];
-    for (var i = 0; i < wysiwygSettings.buttons[0].length; i++) {
-      FCKConfig.ToolbarSets['Wysiwyg'].push([wysiwygSettings.buttons[0][i]]);
-    }
-    FCKTools.AppendStyleSheet(document, '#xToolbar .TB_Start { display:none; }');
-    // Set valid height of select element in silver and office2003 skins.
-    if (FCKConfig.SkinPath.match(/\/office2003\/$/)) {
-      FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 24px; } #xToolbar .TB_End { display: none; }');
-    }
-    else if (FCKConfig.SkinPath.match(/\/silver\/$/)) {
-      FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 27px; }');
-    }
-  }
-  else {
-    FCKConfig[setting] = wysiwygSettings[setting];
-  }
+  FCKConfig[setting] = wysiwygSettings[setting];
 }
 
 /**
Index: editors/js/tinymce-3.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/wysiwyg/editors/js/tinymce-3.js,v
retrieving revision 1.22
diff -u -p -r1.22 tinymce-3.js
--- editors/js/tinymce-3.js	17 Oct 2010 20:52:40 -0000	1.22
+++ editors/js/tinymce-3.js	31 Jan 2011 04:22:59 -0000
@@ -51,15 +51,6 @@ Drupal.wysiwyg.editor.attach.tinymce = f
   ed.onEvent.add(function(ed, e) {
     Drupal.wysiwyg.activeId = ed.id;
   });
-  // Make toolbar buttons wrappable (required for IE).
-  ed.onPostRender.add(function (ed) {
-    var $toolbar = $('<div class="wysiwygToolbar"></div>');
-    $('#' + ed.editorContainer + ' table.mceToolbar > tbody > tr > td').each(function () {
-      $('<div></div>').addClass(this.className).append($(this).children()).appendTo($toolbar);
-    });
-    $('#' + ed.editorContainer + ' table.mceLayout td.mceToolbar').append($toolbar);
-    $('#' + ed.editorContainer + ' table.mceToolbar').remove();
-  });
 
   // Remove TinyMCE's internal mceItem class, which was incorrectly added to
   // submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class
Index: images/add.png
===================================================================
RCS file: images/add.png
diff -N images/add.png
Binary files /dev/null and add.png differ
Index: images/draggable.png
===================================================================
RCS file: images/draggable.png
diff -N images/draggable.png
Binary files /dev/null and draggable.png differ
Index: images/remove.png
===================================================================
RCS file: images/remove.png
diff -N images/remove.png
Binary files /dev/null and remove.png differ
