diff --git a/editors/ckeditor.inc b/editors/ckeditor.inc
index 95353b2..90b19d8 100644
--- a/editors/ckeditor.inc
+++ b/editors/ckeditor.inc
@@ -27,6 +27,9 @@ function wysiwyg_ckeditor_editor() {
         ),
       ),
     ),
+    'toolbar groups' => TRUE,
+    'toolbar rows' => TRUE,
+    'toolbar separator' => '-',
     'install note callback' => 'wysiwyg_ckeditor_install_note',
     'version callback' => 'wysiwyg_ckeditor_version',
     'themes callback' => 'wysiwyg_ckeditor_themes',
@@ -37,7 +40,8 @@ function wysiwyg_ckeditor_editor() {
     'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings',
     'proxy plugin' => array(
       'drupal' => array(
-        'load' => TRUE,
+        'title' => t('Drupal proxy plugin'),
+        'load' => FALSE,
         'proxy' => TRUE,
       ),
     ),
@@ -273,51 +277,47 @@ function wysiwyg_ckeditor_settings($editor, $config, $theme) {
     $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['plugins'] as $plugin => $enabled) {
+    if (!$enabled || !isset($plugins[$plugin])) {
+      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']);
+    }
+  }
+
+  foreach ($config['toolbar'] as $config_row) {
+    foreach ($config_row as $buttons) {
+      $row = array();
+      foreach ($buttons as $button) {
+        // Cross-editor plugin buttons are prefixed by the proxy plugin name.
+        if (strpos($button, '.') !== FALSE) {
+          list($plugin, $button) = explode('.', $button);
+          // @todo Proxy-plugins: remove when allowing multiple buttons.
+          $extra_plugins[] = $button;
         }
+        $row[] = $button;
       }
+      $toolbar[] = $row;
     }
-    if (!empty($extra_plugins)) {
-      $settings['extraPlugins'] = implode(',', $extra_plugins);
-    }
+    $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']);
 
   return $settings;
 }
@@ -417,6 +417,7 @@ function wysiwyg_ckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
 function wysiwyg_ckeditor_plugins($editor) {
   $plugins = array(
     'default' => array(
+      'title' => t('CKEditor core'),
       'buttons' => array(
         'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
         'Strike' => t('Strike-through'),
diff --git a/editors/css/tinymce-2.css b/editors/css/tinymce-2.css
index 4aa201d..b076335 100644
--- a/editors/css/tinymce-2.css
+++ b/editors/css/tinymce-2.css
@@ -6,22 +6,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;
-}
diff --git a/editors/css/tinymce-3.css b/editors/css/tinymce-3.css
index 5d0ebe9..ce13c63 100644
--- a/editors/css/tinymce-3.css
+++ b/editors/css/tinymce-3.css
@@ -6,19 +6,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;
-}
diff --git a/editors/fckeditor.inc b/editors/fckeditor.inc
index 27bcb7b..d5db2d1 100644
--- a/editors/fckeditor.inc
+++ b/editors/fckeditor.inc
@@ -19,6 +19,9 @@ function wysiwyg_fckeditor_editor() {
         'files' => array('fckeditor.js'),
       ),
     ),
+    'toolbar groups' => TRUE,
+    'toolbar rows' => TRUE,
+    'toolbar separator' => '-',
     'version callback' => 'wysiwyg_fckeditor_version',
     'themes callback' => 'wysiwyg_fckeditor_themes',
     'settings callback' => 'wysiwyg_fckeditor_settings',
@@ -26,7 +29,8 @@ function wysiwyg_fckeditor_editor() {
     'plugin settings callback' => 'wysiwyg_fckeditor_plugin_settings',
     'proxy plugin' => array(
       'drupal' => array(
-        'load' => TRUE,
+        'title' => t('Drupal proxy plugin'),
+        'load' => FALSE,
         'proxy' => TRUE,
       ),
     ),
@@ -138,32 +142,36 @@ function wysiwyg_fckeditor_settings($editor, $config, $theme) {
 
   // 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['plugins'] as $plugin => $enabled) {
+    if (!$enabled || !isset($plugins[$plugin])) {
+      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_name) {
+        // Cross-editor plugin buttons are prefixed with the proxy plugin name.
+        if (strpos($button_name, '.') !== FALSE) {
+          list($plugin, $button_name) = explode('.', $button_name);
         }
+        $group[] = $button_name;
       }
+      $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;
 }
@@ -214,6 +222,7 @@ function wysiwyg_fckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
 function wysiwyg_fckeditor_plugins($editor) {
   $plugins = array(
     'default' => array(
+      'title' => t('FCKeditor core'),
       'buttons' => array(
         'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
         'StrikeThrough' => t('Strike-through'),
diff --git a/editors/js/fckeditor.config.js b/editors/js/fckeditor.config.js
index 42efc32..1d3f7a8 100644
--- a/editors/js/fckeditor.config.js
+++ b/editors/js/fckeditor.config.js
@@ -16,28 +16,7 @@ var pluginSettings = (Drupal.settings.wysiwyg.plugins[wysiwygFormat] ? Drupal.se
  * 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];
 }
 
 // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode.
diff --git a/editors/js/tinymce-3.js b/editors/js/tinymce-3.js
index 0fc1a27..318e384 100644
--- a/editors/js/tinymce-3.js
+++ b/editors/js/tinymce-3.js
@@ -52,17 +52,6 @@ Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) {
   ed.onEvent.add(function(ed, e) {
     Drupal.wysiwyg.activeId = ed.id;
   });
-  // Indicate that the DOM has been loaded (in case of Ajax).
-  tinymce.dom.Event.domLoaded = true;
-  // 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
diff --git a/editors/markitup.inc b/editors/markitup.inc
index 57a37e8..ef6916e 100644
--- a/editors/markitup.inc
+++ b/editors/markitup.inc
@@ -155,13 +155,9 @@ function wysiwyg_markitup_settings($editor, $config, $theme) {
     ),
   );
   $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 $button) {
+      $settings['markupSet'][$button] = $default_buttons[$button];
     }
   }
 
@@ -174,6 +170,7 @@ function wysiwyg_markitup_settings($editor, $config, $theme) {
 function wysiwyg_markitup_plugins($editor) {
   return array(
     'default' => array(
+      'title' => t('markItUp core'),
       'buttons' => array(
         'bold' => t('Bold'), 'italic' => t('Italic'),
         'stroke' => t('Strike-through'),
diff --git a/editors/nicedit.inc b/editors/nicedit.inc
index 6acc800..6189888 100644
--- a/editors/nicedit.inc
+++ b/editors/nicedit.inc
@@ -66,12 +66,8 @@ function wysiwyg_nicedit_settings($editor, $config, $theme) {
 
   // Add configured buttons or all available.
   $settings['buttonList'] = array();
-  if (!empty($config['buttons'])) {
-    $buttons = array();
-    foreach ($config['buttons'] as $plugin) {
-      $buttons = array_merge($buttons, $plugin);
-    }
-    $settings['buttonList'] = array_keys($buttons);
+  if (!empty($config['toolbar'])) {
+    $settings['buttonList'] = $config['toolbar'];
   }
 
   // Add editor content stylesheet.
@@ -96,6 +92,7 @@ function wysiwyg_nicedit_settings($editor, $config, $theme) {
 function wysiwyg_nicedit_plugins($editor) {
   return array(
     'default' => array(
+      'title' => t('NicEdit core'),
       'buttons' => array(
         'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
         'strikethrough' => t('Strike-through'),
diff --git a/editors/openwysiwyg.inc b/editors/openwysiwyg.inc
index c135b2c..480ee1c 100644
--- a/editors/openwysiwyg.inc
+++ b/editors/openwysiwyg.inc
@@ -20,6 +20,8 @@ function wysiwyg_openwysiwyg_editor() {
         'files' => array('wysiwyg.js'),
       ),
     ),
+    'toolbar rows' => TRUE,
+    'toolbar separator' => 'seperator',
     'version callback' => 'wysiwyg_openwysiwyg_version',
     'themes callback' => 'wysiwyg_openwysiwyg_themes',
     'settings callback' => 'wysiwyg_openwysiwyg_settings',
@@ -109,21 +111,13 @@ function wysiwyg_openwysiwyg_settings($editor, $config, $theme) {
   }
 
   $settings['Toolbar'] = array();
-  if (!empty($config['buttons'])) {
-    $plugins = wysiwyg_get_plugins($editor['name']);
-    foreach ($config['buttons'] as $plugin => $buttons) {
-      foreach ($buttons as $button => $enabled) {
-        foreach (array('buttons', 'extensions') as $type) {
-          // Skip unavailable plugins.
-          if (!isset($plugins[$plugin][$type][$button])) {
-            continue;
-          }
-          // Add buttons.
-          if ($type == 'buttons') {
-            $settings['Toolbar'][0][] = $button;
-          }
-        }
+  if (!empty($config['toolbar'])) {
+    foreach ($config['toolbar'] as $row) {
+      $toolbar_row = array();
+      foreach ($row as $button) {
+        $toolbar_row[] = $button;
       }
+      $settings['Toolbar'][] = $toolbar_row;
     }
   }
 
@@ -141,6 +135,7 @@ function wysiwyg_openwysiwyg_settings($editor, $config, $theme) {
 function wysiwyg_openwysiwyg_plugins($editor) {
   $plugins = array(
     'default' => array(
+      'title' => t('openWYSIWYG core'),
       'buttons' => array(
         'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
         'strikethrough' => t('Strike-through'),
diff --git a/editors/tinymce.inc b/editors/tinymce.inc
index f3a92dd..e679703 100644
--- a/editors/tinymce.inc
+++ b/editors/tinymce.inc
@@ -26,6 +26,8 @@ function wysiwyg_tinymce_editor() {
         'files' => array('tiny_mce_src.js'),
       ),
     ),
+    'toolbar rows' => TRUE,
+    'toolbar separator' => '|',
     'version callback' => 'wysiwyg_tinymce_version',
     'themes callback' => 'wysiwyg_tinymce_themes',
     'init callback' => 'wysiwyg_tinymce_init',
@@ -34,7 +36,8 @@ function wysiwyg_tinymce_editor() {
     'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings',
     'proxy plugin' => array(
       'drupal' => array(
-        'load' => TRUE,
+        'title' => t('Drupal proxy plugin'),
+        'load' => FALSE,
         'proxy' => TRUE,
       ),
     ),
@@ -230,69 +233,49 @@ function wysiwyg_tinymce_settings($editor, $config, $theme) {
     }
   }
 
-  // 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();
+  $toolbar = array();
+  $extensions = array();
+  $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.
-          elseif ($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.
-          elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
-            $settings['extensions'][$plugin] = 1;
-          }
-          // Add plain extensions.
-          elseif ($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']);
-          }
-        }
+  $plugins = wysiwyg_get_plugins($editor['name']);
+  foreach ($config['plugins'] as $plugin => $enabled) {
+    if (!$enabled || !isset($plugins[$plugin])) {
+      continue;
+    }
+    // Add plain extensions.
+    if (!empty($plugins[$plugin]['load'])) {
+      if (!empty($plugins[$plugin]['internal'])) {
+        $extensions[$plugin] = 1;
       }
+      else {
+        $extensions[_wysiwyg_tinymce_plugin_name('add', $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']);
     }
-    // Clean-up.
-    $settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']);
-    if ($settings['extensions']) {
-      $settings['plugins'] = array_keys($settings['extensions']);
+    // Allow plugins to add or override global configuration settings.
+    if (!empty($plugins[$plugin]['options'])) {
+      $settings = array_merge($settings, $plugins[$plugin]['options']);
     }
-    unset($settings['extensions']);
+  }
+
+  foreach ($config['toolbar'] as $config_row) {
+    $row = array();
+    foreach ($config_row as $button) {
+      // Cross-editor plugin buttons are prefixed by the proxy plugin name.
+      if (strpos($button, '.') !== FALSE) {
+        list($plugin, $button) = explode('.', $button);
+        $extensions['-' . $button ] = 1;
+      }
+      $row[] = $button;
+    }
+    $toolbar[] = $row;
+  }
+  $settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']);
+  if ($extensions) {
+    $settings['plugins'] = array_keys($extensions);
   }
 
   // Add theme-specific settings.
@@ -309,25 +292,26 @@ function wysiwyg_tinymce_settings($editor, $config, $theme) {
       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(
-          'theme_advanced_buttons1' => array(),
-          'theme_advanced_buttons2' => array(),
-          'theme_advanced_buttons3' => array(),
+          'theme_advanced_buttons1' => '',
+          'theme_advanced_buttons2' => '',
+          'theme_advanced_buttons3' => '',
         );
-        // @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++) {
+          $settings['theme_advanced_buttons' . ($i + 1)] = $toolbar[$i];
         }
       }
       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');
+  $csv_settings = array('plugins', 'extended_valid_elements');
+  for ($i = 0; $i < count($toolbar); $i++) {
+    $csv_settings[] = 'theme_advanced_buttons' . ($i + 1);
+  }
   foreach ($csv_settings as $key) {
     if (isset($settings[$key]) && is_array($settings[$key])) {
       $settings[$key] = implode(',', $settings[$key]);
@@ -408,6 +392,7 @@ function _wysiwyg_tinymce_plugin_name($op, $name) {
 function wysiwyg_tinymce_plugins($editor) {
   $plugins = array(
     'default' => array(
+      'title' => t('TinyMCE core'),
       'path' => $editor['library path'] . '/themes/advanced',
       'buttons' => array(
         'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
@@ -425,7 +410,7 @@ function wysiwyg_tinymce_plugins($editor) {
         'sup' => t('Superscript'), 'sub' => t('Subscript'),
         'blockquote' => t('Blockquote'), 'code' => t('Source code'),
         'hr' => t('Horizontal rule'),
-        'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'),
+        'cut' => t('Cut'), 'copy' => t('Copy'), 'pastetext' => t('Paste text'),
         'visualaid' => t('Visual aid'),
         'removeformat' => t('Remove format'),
         'charmap' => t('Character map'),
diff --git a/editors/whizzywig.inc b/editors/whizzywig.inc
index acfc7de..d8e9685 100644
--- a/editors/whizzywig.inc
+++ b/editors/whizzywig.inc
@@ -22,6 +22,8 @@ function wysiwyg_whizzywig_editor() {
     'version callback' => 'wysiwyg_whizzywig_version',
     'settings callback' => 'wysiwyg_whizzywig_settings',
     'plugin callback' => 'wysiwyg_whizzywig_plugins',
+    'toolbar separator' => '|',
+    'toolbar rows' => TRUE,
     'versions' => array(
       '55' => array(
         'js files' => array('whizzywig.js'),
@@ -93,13 +95,13 @@ function wysiwyg_whizzywig_settings($editor, $config, $theme) {
 
   // Add configured buttons or all available.
   $settings['buttons'] = array();
-  if (!empty($config['buttons'])) {
-    $buttons = array();
-    foreach ($config['buttons'] as $plugin) {
-      $buttons = array_merge($buttons, $plugin);
+  $toolbar = array();
+  if (!empty($config['toolbar'])) {
+    foreach ($config['toolbar'] as $config_row) {
+      $toolbar[] = implode(' ', $config_row);
     }
-    $settings['buttons'] = implode(' ', array_keys($buttons));
   }
+  $settings['buttons'] = implode(' newline ', $toolbar);
 
   // Add editor content stylesheet.
   if (isset($config['css_setting'])) {
@@ -123,6 +125,7 @@ function wysiwyg_whizzywig_settings($editor, $config, $theme) {
 function wysiwyg_whizzywig_plugins($editor) {
   return array(
     'default' => array(
+      'title' => t('Whizzywig core'),
       'buttons' => array(
         'formatblock' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'),
         'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
diff --git a/editors/wymeditor.inc b/editors/wymeditor.inc
index 5f44d64..2f3b0e6 100644
--- a/editors/wymeditor.inc
+++ b/editors/wymeditor.inc
@@ -113,34 +113,33 @@ function wysiwyg_wymeditor_settings($editor, $config, $theme) {
 
   // Add configured buttons.
   $settings['toolsItems'] = array();
-  if (!empty($config['buttons'])) {
+  if (!empty($config['toolbar'])) {
     $buttoninfo = _wysiwyg_wymeditor_button_info();
     $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') {
-            // Merge meta-data for internal default buttons.
-            if (isset($buttoninfo[$button])) {
-              $buttoninfo[$button] += array('name' => $button);
-              $settings['toolsItems'][] = $buttoninfo[$button];
-            }
-            // For custom buttons, try to provide a valid button definition.
-            else {
-              $settings['toolsItems'][] = array(
-                'name' => $button,
-                'title' => $plugins[$plugin][$type][$button],
-                'css' => 'wym_tools_' . $button,
-              );
-            }
-          }
-        }
+    // Allow looking up which button is provided by which plugin.
+    $plugin_lookup = array();
+    foreach ($plugins as $plugin => $meta) {
+      if (empty($meta['buttons'])) {
+        continue;
+      }
+      foreach ($meta['buttons'] as $button_name => $button) {
+        $plugin_lookup[$button_name] = $plugin;
+      }
+    }
+    foreach ($config['toolbar'] as $button) {
+      // Merge meta-data for internal default buttons.
+      if (isset($buttoninfo[$button])) {
+        $buttoninfo[$button] += array('name' => $button);
+        $settings['toolsItems'][] = $buttoninfo[$button];
+      }
+      // For custom buttons, try to provide a valid button definition.
+      else {
+        $plugin = $plugin_lookup[$button];
+        $settings['toolsItems'][] = array(
+          'name' => $button,
+          'title' => $plugins[$plugin]['buttons'][$button],
+          'css' => 'wym_tools_' . $button,
+        );
       }
     }
   }
diff --git a/editors/yui.inc b/editors/yui.inc
index 36d0a59..948f690 100644
--- a/editors/yui.inc
+++ b/editors/yui.inc
@@ -45,9 +45,12 @@ function wysiwyg_yui_editor() {
     'load callback' => 'wysiwyg_yui_load',
     'settings callback' => 'wysiwyg_yui_settings',
     'plugin callback' => 'wysiwyg_yui_plugins',
+    'toolbar groups' => TRUE,
+    'toolbar separator' => TRUE,
     'plugin settings callback' => 'wysiwyg_yui_plugin_settings',
     'proxy plugin' => array(
       'drupal' => array(
+        'title' => t('Drupal proxy plugin'),
         'load' => TRUE,
         'proxy' => TRUE,
       ),
@@ -159,10 +162,21 @@ function wysiwyg_yui_settings($editor, $config, $theme) {
       'buttons' => array(),
     ),
   );
-  if (!empty($config['buttons'])) {
+  $plugins = wysiwyg_get_plugins($editor['name']);
+  foreach ($config['plugins'] as $plugin => $enabled) {
+    if (!$enabled || !isset($plugins[$plugin])) {
+      continue;
+    }
+    // Allow plugins to add or override global configuration settings.
+    if (!empty($plugins[$plugin]['options'])) {
+      $settings = array_merge($settings, $plugins[$plugin]['options']);
+    }
+  }
+  if (!empty($config['toolbar'])) {
     $buttons = array();
-    foreach ($config['buttons'] as $plugin => $enabled_buttons) {
-      foreach ($enabled_buttons as $button => $enabled) {
+    foreach ($config['toolbar'] as $group_index => $group_config) {
+      $group = array('group' => 'group' . $group_index, 'label' => '', 'buttons' => array());
+      foreach ($group_config as $button) {
         $extra = array();
         if ($button == 'heading') {
           $extra = array('menu' => array(
@@ -198,12 +212,11 @@ function wysiwyg_yui_settings($editor, $config, $theme) {
             array('text' => 'Verdana'),
           ));
         }
-        $buttons[] = wysiwyg_yui_button_setting($editor, $plugin, $button, $extra);
+        $group['buttons'][] = wysiwyg_yui_button_setting($editor, $button, $extra);
       }
+      $buttons[] = $group;
     }
-    // Group buttons in a dummy group.
-    $buttons = array('group' => 'default', 'label' => '', 'buttons' => $buttons);
-    $settings['toolbar']['buttons'] = array($buttons);
+    $settings['toolbar']['buttons'] = $buttons;
   }
 
   if (isset($config['css_setting'])) {
@@ -229,25 +242,41 @@ function wysiwyg_yui_settings($editor, $config, $theme) {
  *
  * @param $editor
  *   A processed hook_editor() array of editor properties.
- * @param $plugin
- *   The internal name of a plugin.
  * @param $button
- *   The internal name of a button, defined by $plugin.
+ *   The internal name of a button, defined by a plugin.
  * @param $extra
  *   (optional) An array containing arbitrary other elements to add to the
  *   resulting button.
  */
-function wysiwyg_yui_button_setting($editor, $plugin, $button, $extra = array()) {
+function wysiwyg_yui_button_setting($editor, $button, $extra = array()) {
   static $plugins;
+  static $plugin_lookup;
 
   if (!isset($plugins)) {
     $plugins = wysiwyg_get_plugins($editor['name']);
+    // Allow looking up which button is provided by which plugin.
+    $plugin_lookup = array();
+    foreach ($plugins as $plugin => $meta) {
+      if (empty($meta['buttons'])) {
+        continue;
+      }
+      foreach ($meta['buttons'] as $button_name => $button_info) {
+        $plugin_lookup[$button_name] = $plugin;
+      }
+    }
   }
 
   // Return a simple separator.
   if ($button === 'separator') {
     return array('type' => 'separator');
   }
+  // Cross-editor plugin buttons are prefixed by the proxy plugin name.
+  if (strpos($button, '.') !== FALSE) {
+    list($plugin, $button) = explode('.', $button);
+  }
+  else {
+    $plugin = $plugin_lookup[$button];
+  }
   // Setup defaults.
   $type = 'push';
   $label = $plugins[$plugin]['buttons'][$button];
@@ -317,6 +346,7 @@ function wysiwyg_yui_proxy_plugin_settings($editor, $profile, $plugins) {
 function wysiwyg_yui_plugins($editor) {
   return array(
     'default' => array(
+      'title' => t('YUI Editor core'),
       'buttons' => array(
         'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
         'strikethrough' => t('Strike-through'),
diff --git a/images/add.png b/images/add.png
new file mode 100644
index 0000000..6332fef
Binary files /dev/null and b/images/add.png differ
diff --git a/images/draggable.png b/images/draggable.png
new file mode 100644
index 0000000..47e8a02
Binary files /dev/null and b/images/draggable.png differ
diff --git a/images/remove.png b/images/remove.png
new file mode 100644
index 0000000..1514d51
Binary files /dev/null and b/images/remove.png differ
diff --git a/wysiwyg.admin.css b/wysiwyg.admin.css
new file mode 100644
index 0000000..d39d118
--- /dev/null
+++ b/wysiwyg.admin.css
@@ -0,0 +1,77 @@
+#toolbar-rows {
+  margin: 1em 0;
+}
+#toolbar-rows div, #toolbar-rows span {
+  vertical-align: top;
+}
+#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;
+  text-indent: 18px;
+  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;
+}
diff --git a/wysiwyg.admin.inc b/wysiwyg.admin.inc
index 1122fa8..45015ef 100644
--- a/wysiwyg.admin.inc
+++ b/wysiwyg.admin.inc
@@ -6,9 +6,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(
@@ -26,7 +26,7 @@ function wysiwyg_profile_form($form, &$form_state, $profile) {
     'language' => 'en',
     'access' => 1,
     'access_pages' => "node/*\nuser/*\ncomment/*",
-    'buttons' => array(),
+    'toolbar' => array(),
     'toolbar_loc' => 'top',
     'toolbar_align' => 'left',
     'path_loc' => 'bottom',
@@ -44,6 +44,14 @@ function wysiwyg_profile_form($form, &$form_state, $profile) {
     '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);
 
   $formats = filter_formats();
   $editor = wysiwyg_get_editor($profile->editor);
@@ -111,51 +119,68 @@ function wysiwyg_profile_form($form, &$form_state, $profile) {
   asort($predefined);
   $form['basic']['language']['#options'] = $predefined;
 
-  $form['buttons'] = array(
+  // Retrieve all available plugins and buttons.
+  $plugins = wysiwyg_get_plugins($profile->editor);
+  $form['plugins'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Buttons and plugins'),
-    '#collapsible' => FALSE,
+    '#title' => t('Plugins'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
     '#tree' => TRUE,
-    '#theme' => 'wysiwyg_admin_button_table',
+    '#theme' => 'wysiwyg_admin_plugin_table',
+    '#access' => !empty($plugins),
   );
 
-  $plugins = wysiwyg_get_plugins($profile->editor);
-  // Generate the button list.
+  $buttons = array();
+  $proxy = (isset($editor['proxy plugin']) ? key($editor['proxy plugin']) : FALSE);
   foreach ($plugins as $name => $meta) {
+    $title = (!empty($meta['title']) ? $meta['title'] : (!empty($meta['extensions']) ? $meta['extensions'][$name] : $name));
+    $form['plugins'][$name] = array(
+      '#type' => 'checkbox',
+      '#title' => check_plain($title),
+      '#default_value' => !empty($profile->settings['plugins'][$name]),
+      '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
+    );
     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 = (!empty($icon) ? $icon . ' ' . check_plain($title) : check_plain($title));
-        $form['buttons'][$name][$button] = array(
-          '#type' => 'checkbox',
-          '#title' => $title,
-          '#default_value' => !empty($profile->settings['buttons'][$name][$button]) ? $profile->settings['buttons'][$name][$button] : FALSE,
-          '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
-        );
-      }
-    }
-    elseif (isset($meta['extensions']) && is_array($meta['extensions'])) {
-      foreach ($meta['extensions'] as $extension => $title) {
-        $form['buttons'][$name][$extension] = array(
-          '#type' => 'checkbox',
-          '#title' => check_plain($title),
-          '#default_value' => !empty($profile->settings['buttons'][$name][$extension]) ? $profile->settings['buttons'][$name][$extension] : FALSE,
-          '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
+      foreach ($meta['buttons'] as $button_name => $button) {
+        $button_name = ($name === $proxy ? $name . '.' . $button_name : $button_name);
+        $buttons[$button_name] = (is_array($button) ? $button : array('title' => $button));
+        $buttons[$button_name] += array(
+          'plugin' => $name,
         );
       }
     }
   }
 
+  $form['toolbar'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Toolbar definition'),
+    '#default_value' => json_encode($profile->settings['toolbar']),
+    '#access' => !empty($buttons),
+  );
+
+  // Separator.
+  if (!empty($editor['toolbar separator'])) {
+    $separator = $editor['toolbar separator'] === TRUE ? 'separator' : $editor['toolbar separator'];
+    $buttons[$separator] = array(
+      'plugin' => 'none',
+      'title' => t('Separator'),
+      'multiple' => TRUE,
+    );
+  }
+  $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']) ? $editor['toolbar separator'] : FALSE,
+    'buttons' => $buttons,
+  );
+  $form['toolbar']['#attached']['js'][] = array(
+    'data' => array('wysiwyg_toolbar' => $toolbar_support),
+    'type' => 'setting',
+  );
+  $form['toolbar']['#attached']['library'][] = array('wysiwyg', 'wysiwyg.admin');
+
   $form['appearance'] = array(
     '#type' => 'fieldset',
     '#title' => t('Editor appearance'),
@@ -331,15 +356,73 @@ function wysiwyg_profile_form($form, &$form_state, $profile) {
  * @see wysiwyg_profile_form()
  */
 function wysiwyg_profile_form_submit($form, &$form_state) {
-  $values = $form_state['values'];
-  if (isset($values['buttons'])) {
-    // Store only enabled buttons for each plugin.
-    foreach ($values['buttons'] as $plugin => $buttons) {
-      $values['buttons'][$plugin] = array_filter($values['buttons'][$plugin]);
-    }
+  $values = &$form_state['values'];
+  $editor = $form_state['wysiwyg']['editor'];
+  if (isset($values['plugins'])) {
     // Store only enabled plugins.
-    $values['buttons'] = array_filter($values['buttons']);
+    $values['plugins'] = array_filter($values['plugins']);
+  }
+  else {
+    $values['plugins'] = array();
+  }
+  $values['toolbar'] = json_decode($values['toolbar'], FALSE);
+  $plugins = wysiwyg_get_plugins($values['editor']);
+  // Allow looking up which button is provided by which plugin.
+  $plugin_lookup = array();
+  foreach ($plugins as $plugin => $meta) {
+    if (empty($meta['buttons'])) {
+      continue;
+    }
+    foreach ($meta['buttons'] as $button_name => $button) {
+      $plugin_lookup[$button_name] = $plugin;
+    }
+  }
+
+  // Verify all dependencies are also enabled.
+  $additional_plugins = array();
+  $dependencies = array();
+  $toolbar = (!empty($editor['toolbar rows']) ? $values['toolbar'] : array($values['toolbar']));
+  foreach ($toolbar as $row) {
+    if (empty($editor['toolbar groups'])) {
+      $row = array($row);
+    }
+    foreach ($row as $group) {
+      foreach ($group as $button) {
+        // Cross-editor plugin buttons are prefixed by the proxy plugin name.
+        if (strpos($button, '.') !== FALSE) {
+          list($plugin, $button_id) = explode('.', $button);
+        }
+        elseif (isset($plugin_lookup[$button]) && !isset($values['plugins'][$plugin_lookup[$button]])) {
+          $plugin = $plugin_lookup[$button];
+        }
+        if (!isset($values['plugins'][$plugin])) {
+          $additional_plugins[$plugin] = 1;
+        }
+        if (!empty($plugins[$plugin]['dependencies'])) {
+          $dependencies = array_unique(array_merge($dependencies, $plugins[$plugin]['dependencies']));
+        }
+      }
+    }
+  }
+  foreach ($values['plugins'] as $plugin => $enabled) {
+    if (!empty($plugins[$plugin]['dependencies'])) {
+      $dependencies = array_unique(array_merge($dependencies, $plugins[$plugin]['dependencies']));
+    }
+  }
+  // Walk up the dependency chain until a dependency is already enabled.
+  while ($dependency = array_pop($dependencies)) {
+    if (!isset($values['plugins'][$dependency]) && !isset($additional_plugins[$dependency])) {
+      $additional_plugins[$dependency] = 1;
+      if (!empty($plugins[$dependency]['dependencies'])) {
+        $dependencies = array_unique(array_merge($dependencies, $plugins[$dependency]['dependencies']));
+      }
+    }
+  }
+  if (!empty($additional_plugins)) {
+    drupal_set_message(t('Additional plugins were automatically enabled because they were required by already enabled plugins or buttons.<br />The enabled plugins were: @plugins', array('@plugins' => implode(', ', array_keys($additional_plugins)))));
+    $values['plugins'] = array_merge($values['plugins'], $additional_plugins);
   }
+
   // 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']);
@@ -350,9 +433,8 @@ function wysiwyg_profile_form_submit($form, &$form_state) {
   $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')
@@ -370,27 +452,25 @@ function wysiwyg_profile_form_submit($form, &$form_state) {
 }
 
 /**
- * Layout for the buttons in the Wysiwyg Editor profile form.
+ * Layout for the plugins in the Wysiwyg Editor profile form.
  */
-function theme_wysiwyg_admin_button_table($variables) {
+function theme_wysiwyg_admin_plugin_table($variables) {
   $form = $variables['form'];
-  $buttons = array();
+  $plugins = array();
 
   // Flatten forms array.
-  foreach (element_children($form) as $name) {
-    foreach (element_children($form[$name]) as $button) {
-      $buttons[] = drupal_render($form[$name][$button]);
-    }
+  foreach (element_children($form) as $plugin) {
+    $plugins[] = drupal_render($form[$plugin]);
   }
 
   // Split checkboxes into rows with 3 columns.
-  $total = count($buttons);
+  $total = count($plugins);
   $rows = array();
   for ($i = 0; $i < $total; $i += 3) {
     $row = array();
-    $row_buttons = array_slice($buttons, $i, 3) + array_fill(0, 3, array());
-    foreach ($row_buttons as $row_button) {
-      $row[] = array('data' => $row_button);
+    $row_plugins = array_slice($plugins, $i, 3) + array_fill(0, 3, array());
+    foreach ($row_plugins as $row_plugin) {
+      $row[] = array('data' => $row_plugin);
     }
     $rows[] = $row;
   }
@@ -570,11 +650,17 @@ function theme_wysiwyg_profile_overview($variables) {
  * Submit callback for Wysiwyg profile overview form.
  */
 function wysiwyg_profile_overview_submit($form, &$form_state) {
+  // The settings handling relies on these to be defined and arrays.
+  $settings = array(
+    'toolbar' => array(),
+    'plugins' => array(),
+  );
   foreach ($form_state['values']['formats'] as $format => $values) {
     db_merge('wysiwyg')
       ->key(array('format' => $format))
       ->fields(array(
         'editor' => $values['editor'],
+        'settings' => serialize($settings),
       ))
       ->execute();
   }
diff --git a/wysiwyg.admin.js b/wysiwyg.admin.js
new file mode 100644
index 0000000..6b0d496
--- /dev/null
+++ b/wysiwyg.admin.js
@@ -0,0 +1,252 @@
+(function ($, undefined) {
+
+Drupal.behaviors.wysiwygToolbarDesigner = {
+  attach: function (context, settings) {
+    var settings = settings.wysiwyg_toolbar;
+    $('#edit-toolbar').parent().parent().hide();
+    if (!settings.buttons || settings.buttons.length == 0) {
+      return;
+    }
+    // Insert toolbar designer.
+    $('#edit-plugins').after(Drupal.theme('toolbar_designer', settings));
+    var workspace = $('#wysiwyg-toolbar-designer');
+    var designArea = $('#toolbar-rows');
+    var changeNotification = $('#wysiwyg-toolbar-designer div.toolbar-changed-warning');
+    var availableButtons = $('#toolbar-available-buttons');
+
+    // Set up sortables.
+    $('.wysiwyg-button',availableButtons).addClass('template-button').draggable({
+      handle: '.handler',
+      helper: 'clone',
+      connectToSortable: '.toolbar-group',
+      revert: 'invalid',
+      addClasses: false,
+      start: function(event, ui) {
+        // Workaround for jQuery UI bug fixed in version 1.8.11.
+        // @see http://bugs.jqueryui.com/ticket/5811
+        $('#toolbar-rows .toolbar-group').sortable('refreshPositions');
+      }
+    });
+
+    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 = buttons.add(item);
+        }
+
+        // Remove each button and enable in template.
+        buttons.each(function(){
+          var $button = $(this);
+          $('.wysiwyg-button[data-plugin="' + $button.attr('data-plugin')  + '"][data-button="' + $button.attr('data-button')  + '"]').show();
+        });
+
+        item.parent().sortable('refresh');
+        item.remove();
+        changeNotification.fadeIn();
+      }
+    });
+
+    $('#toolbar-rows').sortable({
+      items: '.toolbar-row',
+      handle: '.row-handler',
+      addClass: false,
+      stop: updateToolbarTextarea,
+    });
+
+    // Design actions buttons.
+    $('.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;
+    });
+
+    $('#reset-design').click(function() {
+      if (!changeNotification.is(':hidden') && confirm(Drupal.t('Do you want to reset the changes ?')))
+        reset();
+      return false;
+    });
+
+    if (!settings['toolbar rows']) {
+      $('.add-toolbar-row').hide();
+    }
+
+    reset();
+
+    function createRow (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',
+        receive: function(event, ui) {
+          // ui.sender is posibly the available button which was cloned and
+          // and dragged to this group. Separators can be used multiple times.
+          if (ui.sender.hasClass('template-button') && !ui.sender.attr('data-multiple-instances')) {
+            ui.sender.hide();
+          }
+        },
+        stop: updateToolbarTextarea
+      });
+      if (settings['toolbar groups']) {
+        row.find('.add-group').click(function() {
+          var group = createGroup();
+          row.append(group);
+          row.sortable('refresh');
+          changeNotification.fadeIn();
+          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;
+    };
+
+    function createGroup () {
+      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: updateToolbarTextarea,
+        beforeStop: function(event, ui) {
+          // ui.item is the clone of an available button dragged to this group.
+          ui.item.removeClass('template-button').addClass('toolbar-button');
+        },
+        receive: function(event, ui) {
+          // ui.sender is posibly the available button which was cloned and
+          // and dragged to this group. Separators can be used multiple times.
+          if (ui.sender.hasClass('template-button') && !ui.sender.attr('data-multiple-instances')) {
+            ui.sender.hide();
+          }
+        }
+
+      });
+      return group;
+    }
+
+    function reset () {
+      $('.toolbar-row',designArea).remove();
+      // Enable all buttons and then disable it later.
+      $('.wysiwyg-button',availableButtons).show();
+
+      var rows = (settings['toolbar rows'] || !settings.toolbar ? settings.toolbar : [settings.toolbar]);
+      for (var i in rows) {
+        var row = createRow(true);
+        var groups = (settings['toolbar groups'] ? rows[i] : [rows[i]]);
+        for (var j in groups) {
+          var group = createGroup();
+          var buttons = groups[j];
+
+          for (var k in buttons) {
+            var buttonInfo = settings.buttons[buttons[k]];
+            var template_button = $('.wysiwyg-button[data-plugin="' + buttonInfo.plugin + '"][data-button="' + buttons[k] + '"]', availableButtons);
+            if (template_button.length) {
+              button = template_button.clone().show();
+              button.removeClass('template-button').addClass('toolbar-button');
+              group.append(button);
+
+              if (!buttonInfo.multiple) {
+                // Disable button in template area.
+                template_button.hide();
+              }
+            }
+          }
+          row.append(group);
+        }
+        designArea.append(row);
+      }
+      // Make sure we always have at least one row.
+      if ($('.toolbar-row',designArea).length <= 0) {
+        var row = createRow();
+        designArea.append(row);
+      }
+      changeNotification.fadeOut();
+    }
+
+    function updateToolbarTextarea(event, ui) {
+      // 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){
+            group.push($(button).attr('data-button'));
+          })
+          if (settings['toolbar groups']) {
+            row.push(group);
+          }
+          else {
+            row = group;
+          }
+        });
+        if (settings['toolbar rows']) {
+           toolbar.push(row);
+        }
+        else {
+          toolbar = row;
+        }
+      });
+      // Assign to hidden field.
+      $('#edit-toolbar').val(JSON.stringify(toolbar));
+      changeNotification.fadeIn();
+    }
+  }
+};
+
+Drupal.theme.prototype.toolbar_designer = function (settings) {
+  var markup = '<div id="wysiwyg-toolbar-designer">';
+  markup += '<div id="toolbar-available-buttons"><label for="toolbar-available-buttons">' + Drupal.t('Available buttons') + '</label>';
+  for (var button_name in settings.buttons) {
+    var button = settings.buttons[button_name];
+    markup += '<span class="wysiwyg-button" data-plugin="' + button.plugin + '" data-button="' + button_name
+      + (button.multiple ? '" data-multiple-instances="yes"' : '')
+      + '">' + Drupal.checkPlain(button.title) + '</span>';
+  }
+  markup += '</div><div id="stage"><label for="stage">' + Drupal.t('Toolbar') + '</label>'
+    + '<div id="tolbar-description">' + Drupal.t('Drag any buttons you need from the area above into the toolbar layout below. Drag any item back to the area above to remove them from the toolbar layout.') + '</div>'
+    + '<div id="toolbar-rows"></div>'
+    + '<div id="toolbar-actions">'
+      + '<a href="#" class="add add-toolbar-row">' + Drupal.t('Add new row') + '</a>'
+    + '</div>'
+    + '<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>'
+  + '</div>'
+  + '<div class="toolbar-changed-warning messages warning"><span class="warning">*</span>'
+    + Drupal.t('Changes made to this toolbar will not be saved until the form is submitted.') + '</div>';
+  return markup;
+}
+
+})(jQuery);
diff --git a/wysiwyg.api.php b/wysiwyg.api.php
index c4d8857..9dc9f63 100644
--- a/wysiwyg.api.php
+++ b/wysiwyg.api.php
@@ -41,6 +41,10 @@ function hook_wysiwyg_plugin($editor, $version) {
       if ($version > 3) {
         return array(
           'myplugin' => array(
+            // The plugin's title; defaulting to its internal name ('myplugin'),
+            // or the value of the first item in the 'extensions' key, if it
+            // exists..
+            'title' => t('My plugin'),
             // A URL to the plugin's homepage.
             'url' => 'http://drupal.org/project/img_assist',
             // The full path to the native editor plugin, no trailing slash.
@@ -79,6 +83,10 @@ function hook_wysiwyg_plugin($editor, $version) {
             // other modules. TRUE means 'path' and 'filename' above are ignored
             // and the plugin is instead loaded from the editor's plugin folder.
             'internal' => TRUE,
+            // A lost of internal names of other native plugins this plugin
+            // depends on to function. These plugins will also be automatically
+            // enabled if this plugin or one of its buttons are enabled.
+            'dependencies' => array('dialog'),
             // TinyMCE-specific: Additional HTML elements to allow in the markup.
             'extended_valid_elements' => array(
               'img[class|src|border=0|alt|title|width|height|align|name|style]',
diff --git a/wysiwyg.module b/wysiwyg.module
index aba3920..648caee 100644
--- a/wysiwyg.module
+++ b/wysiwyg.module
@@ -92,6 +92,28 @@ function wysiwyg_menu() {
 }
 
 /**
+ * Implements hook_library().
+ */
+function wysiwyg_library() {
+  $path = drupal_get_path('module', 'wysiwyg');
+  $libraries['wysiwyg.admin'] = array(
+    'title' => 'Wysiwyg admin',
+    'version' => VERSION,
+    'js' => array(
+      $path . '/wysiwyg.admin.js' => array(),
+    ),
+    'css' => array(
+      $path . '/wysiwyg.admin.css' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'ui.droppable'),
+      array('system', 'ui.sortable'),
+    ),
+  );
+  return $libraries;
+}
+
+/**
  * Implements hook_element_info().
  */
 function wysiwyg_element_info() {
@@ -115,7 +137,7 @@ function wysiwyg_theme() {
     'wysiwyg_profile_overview' => array(
       'render element' => 'form',
     ),
-    'wysiwyg_admin_button_table' => array(
+    'wysiwyg_admin_plugin_table' => array(
       'render element' => 'form',
     ),
     // @see wysiwyg_dialog()
@@ -491,18 +513,17 @@ function wysiwyg_add_plugin_settings($profile) {
   $proxy = (isset($editor['proxy plugin']) ? key($editor['proxy plugin']) : '');
 
   // Process native editor plugins.
-  if (isset($editor['plugin settings callback'])) {
-    // @todo Require PHP 5.1 in 3.x and use array_intersect_key().
+  if (isset($editor['plugin settings callback']) && !empty($profile->settings['plugins'])) {
     $profile_plugins_native = array();
-    foreach ($plugins[$editor['name']] as $plugin => $meta) {
+    foreach ($profile->settings['plugins'] as $plugin => $enabled) {
+      if (!$enabled) {
+        continue;
+      }
       // 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;
-      }
+      $profile_plugins_native[$plugin] = $plugins[$editor['name']][$plugin];
     }
     // Invoke the editor's plugin settings callback, so it can populate the
     // settings for native external plugins with required values.
@@ -514,24 +535,40 @@ function wysiwyg_add_plugin_settings($profile) {
   }
 
   // Process Drupal plugins.
-  if ($proxy && isset($editor['proxy plugin settings callback'])) {
+  // @todo Perhaps introduce $profile->settings['proxy plugins'] instead of
+  // finding their buttons in the toolbar?
+  if ($proxy && isset($editor['proxy plugin settings callback']) && !empty($profile->settings['toolbar'])) {
+    $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');
+    $toolbar = (!empty($editor['toolbar rows']) ? $profile->settings['toolbar'] : array($profile->settings['toolbar']));
+    foreach ($toolbar as $row) {
+      if (empty($editor['toolbar groups'])) {
+        $row = array($row);
+      }
+      foreach ($row as $group) {
+        foreach ($group as $button) {
+          if (strpos($button, '.') === FALSE) {
+            continue;
+          }
+          list($plugin, $button_id) = explode('.', $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];
         }
       }
     }
