diff --git D6UPDATE.txt D6UPDATE.txt
index 8bda859..5fad7f5 100644
--- D6UPDATE.txt
+++ D6UPDATE.txt
@@ -18,7 +18,7 @@ administrative view of a layout can now have its own theme function.
 CONTENT TYPES
 =============
 
-panels_node_legacy module renamed to panels_node_content.module 
+panels_node_legacy module renamed to panels_node_content.module
 -- NEED UPDATE TO RENAME IN SYSTEM TABLE.
 
 'title callback' now has $subtype as the first argument.
@@ -49,4 +49,4 @@ Moved to CTOOLS
 
 When argument plugins fail to load a context at runtime, they must now return
 error codes instead of FALSE or NULL (previously the practice). The error codes,
-and their respective documentation, can be found at the top of panels.module. 
\ No newline at end of file
+and their respective documentation, can be found at the top of panels.module.
\ No newline at end of file
diff --git includes/display-render.inc includes/display-render.inc
index 7f327f9..9a969ba 100644
--- includes/display-render.inc
+++ includes/display-render.inc
@@ -8,77 +8,6 @@
  */
 
 /**
- * Render a display by loading the content into an appropriate
- * array and then passing through to panels_render_layout.
- *
- * if $incoming_content is NULL, default content will be applied. Use
- * an empty string to indicate no content.
- * @render
- * @ingroup hook_invocations
- */
-function _panels_render_display(&$display) {
-  $layout = panels_get_layout($display->layout);
-  if (!$layout) {
-    return NULL;
-  }
-
-  $output = '';
-
-  // Let modules act just prior to render.
-  foreach (module_implements('panels_pre_render') as $module) {
-    $function = $module . '_panels_pre_render';
-    $output .= $function($display);
-  }
-
-  $output .= panels_render_layout($layout, $display, $display->css_id, $display->layout_settings);
-
-  // Let modules act just after render.
-  foreach (module_implements('panels_post_render') as $module) {
-    $function = $module . '_panels_post_render';
-    $output .= $function($display);
-  }
-  return $output;
-}
-
-/**
- * Given a full layout structure and a content array, render a panel display.
- * @render
- */
-function panels_render_layout($layout, $content, $css_id = NULL, $settings = array(), $display = NULL) {
-  if (!empty($layout['css'])) {
-    if (file_exists(path_to_theme() . '/' . $layout['css'])) {
-      drupal_add_css(path_to_theme() . '/' . $layout['css']);
-    }
-    else {
-      drupal_add_css($layout['path'] . '/' . $layout['css']);
-    }
-  }
-  // This now comes after the CSS is added, because panels-within-panels must
-  // have their CSS added in the right order; inner content before outer content.
-
-  // If $content is an object, it's a $display and we have to render its panes.
-  if (is_object($content)) {
-    $display = $content;
-    if (empty($display->cache['method'])) {
-      $content = panels_render_panes($display);
-    }
-    else {
-      $cache = panels_get_cached_content($display, $display->args, $display->context);
-      if ($cache === FALSE) {
-        $cache = new panels_cache_object();
-        $cache->set_content(panels_render_panes($display));
-        panels_set_cached_content($cache, $display, $display->args, $display->context);
-      }
-      $content = $cache->content;
-    }
-  }
-
-  $output = theme($layout['theme'], check_plain($css_id), $content, $settings, $display);
-
-  return $output;
-}
-
-/**
  * Render the administrative layout of a display.
  *
  * This is used for the edit version, so that layouts can have different
@@ -104,91 +33,12 @@ function panels_render_layout_admin($layout, $content, $display) {
 }
 
 /**
- * Render all the panes in a display into a $content array to be used by
- * the display theme function.
- */
-function panels_render_panes(&$display) {
-  ctools_include('content');
-
-  $keywords = array();
-
-  // First, render all the panes into little boxes. We do this here because
-  // some panes request to be rendered after other panes (primarily so they
-  // can do the leftovers of forms).
-  $panes = array();
-  $later = array();
-
-  foreach ((array) $display->content as $pid => $pane) {
-    $pane->shown = !empty($pane->shown); // guarantee this field exists.
-    // If the user can't see this pane, do not render it.
-    if (!$pane->shown || !panels_pane_access($pane, $display)) {
-      continue;
-    }
-
-    // If this pane wants to render last, add it to the $later array.
-    $content_type = ctools_get_content_type($pane->type);
-
-    if (!empty($content_type['render last'])) {
-      $later[$pid] = $pane;
-      continue;
-    }
-
-    $panes[$pid] = panels_render_pane_content($display, $pane, $keywords);
-  }
-
-  foreach ($later as $pid => $pane) {
-    $panes[$pid] = panels_render_pane_content($display, $pane, $keywords);
-  }
-
-  // Loop through all panels, put all panes that belong to the current panel
-  // in an array, then render the panel. Primarily this ensures that the
-  // panes are in the proper order.
-  $content = array();
-  foreach ($display->panels as $panel_name => $pids) {
-    $panel = array();
-    foreach ($pids as $pid) {
-      if (!empty($panes[$pid])) {
-        $panel[$pid] = $panes[$pid];
-      }
-    }
-    $content[$panel_name] = panels_render_panel($display, $panel_name, $panel);
-  }
-
-  // Prevent notices by making sure that all panels at least have an entry:
-  $layout = panels_get_layout($display->layout);
-  $panels = panels_get_panels($layout, $display);
-  foreach ($panels as $id => $panel) {
-    if (!isset($content[$id])) {
-      $content[$id] = NULL;
-    }
-  }
-
-  return $content;
-}
-
-/**
- * Render a single pane, identifying its context, and put it into
- * the $panes array.
- */
-function panels_render_pane_content(&$display, &$pane, $keywords) {
-  $content = panels_get_pane_content($display, $pane, $keywords, $display->args, $display->context, $display->incoming_content);
-
-  // Pass long the css_id that is usually available.
-  if (!empty($pane->css['css_id'])) {
-    $content->css_id = $pane->css['css_id'];
-  }
-
-  // Pass long the css_class that is usually available.
-  if (!empty($pane->css['css_class'])) {
-    $content->css_class = $pane->css['css_class'];
-  }
-
-  return $content;
-}
-
-/**
  * Render a pane using the appropriate style.
  *
+ * Legacy function; this behavior has been moved onto the display renderer
+ * object. The function name here is included for backwards compatibility. New
+ * style plugins should NEVER call it.
+ *
  * $content
  *   The already rendered content via panels_render_pane_content()
  * $pane
@@ -223,7 +73,7 @@ function panels_render_pane($content, $pane, &$display) {
     }
   }
 
-  if (!empty($content->content)) {
+  if (!empty($content)) {
     // fallback
     return theme('panels_pane', $content, $pane, $display);
   }
@@ -253,64 +103,3 @@ function panels_get_panel_style_and_settings($panel_settings, $panel) {
 
   return array($style, $style_settings);
 }
-
-/**
- * Render a panel, by storing the content of each pane in an appropriate array
- * and then passing through to the theme function that will render the panel
- * in the configured panel style.
- *
- * @param $display
- *   A display object.
- * @param $panel
- *   The ID of the panel being rendered
- * @param $panes
- *   An array of panes that are assigned to the panel that's being rendered.
- *
- * @return
- *   The rendered HTML for a panel.
- * @render
- */
-function panels_render_panel($display, $panel, $panes) {
-  list($style, $style_settings) = panels_get_panel_style_and_settings($display->panel_settings, $panel);
-
-  // Retrieve the pid (can be a panel page id, a mini panel id, etc.), this
-  // might be used (or even necessary) for some panel display styles.
-  // TODO: Got to fix this to use panel page name instead of pid, since pid is
-  // no longer guaranteed. This needs an API to be able to set the final id.
-  $owner_id = 0;
-  if (isset($display->owner) && is_object($display->owner) && isset($display->owner->id)) {
-    $owner_id = $display->owner->id;
-  }
-
-  return theme($style['render panel'], $display, $owner_id, $panes, $style_settings, $panel);
-}
-
-/**
- * Get the title from a display.
- *
- * The display must have already been rendered, or the setting to set the display's title
- * from a pane's title will not have worked.
- *
- * @param $display
- *   The display to get the title from.
- *
- * @return
- *   The title to use. If NULL, this means to let any default title that may be in use
- *   pass through. i.e, do not actually set the title.
- */
-function panels_display_get_title($display) {
-  switch ($display->hide_title) {
-    case PANELS_TITLE_NONE:
-      return '';
-
-    case PANELS_TITLE_PANE:
-      return isset($display->stored_pane_title) ? $display->stored_pane_title : '';
-
-    case PANELS_TITLE_FIXED:
-    case FALSE; // For old exported panels that are not in the database.
-      if (!empty($display->title)) {
-        return filter_xss_admin(ctools_context_keyword_substitute($display->title, array(), $display->context));
-      }
-      return NULL;
-  }
-}
diff --git includes/plugins.inc includes/plugins.inc
index 63936e8..5ed1622 100644
--- includes/plugins.inc
+++ includes/plugins.inc
@@ -34,50 +34,6 @@ function panels_get_panels($layout, $display) {
 }
 
 /**
- * Get the content from a given pane.
- *
- * @param $pane
- *   The pane to retrieve content from.
- * @param $args
- *   The arguments sent to the display.
- * @param $context
- *   The panels context.
- * @param $incoming_content
- *   Any incoming content if this display is a wrapper.
- */
-function panels_get_pane_content($display, $pane, $keywords, $args = array(), $context = array(), $incoming_content = '') {
-  ctools_include('context');
-  if (!is_array($context)) {
-    $context = array();
-  }
-
-  if (!$incoming_content === '') {
-    $incoming_content = t('Incoming content will be displayed here.');
-  }
-
-  $content = FALSE;
-  $caching = !empty($pane->cache['method']) ? TRUE : FALSE;
-  if ($caching && ($cache = panels_get_cached_content($display, $args, $context, $pane))) {
-    $content = $cache->content;
-  }
-  else {
-    $content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, $keywords, $args, $context, $incoming_content);
-    foreach (module_implements('panels_pane_content_alter') as $module) {
-      $function = $module . '_panels_pane_content_alter';
-      $function($content, $pane, $args, $context);
-    }
-    if ($caching) {
-      $cache = new panels_cache_object();
-      $cache->set_content($content);
-      panels_set_cached_content($cache, $display, $args, $context, $pane);
-      $content = $cache->content;
-    }
-  }
-
-  return $content;
-}
-
-/**
  * Get cached content for a given display and possibly pane.
  *
  * @return
@@ -389,6 +345,30 @@ function panels_get_caches() {
 }
 
 /**
+ * Fetch metadata on a specific display renderer plugin.
+ *
+ * @return
+ *   An array of arrays with information about the requested panels display
+ *   renderer.
+ */
+function panels_get_display_renderer($dr) {
+  ctools_include('plugins');
+  return ctools_get_plugins('panels', 'display_renderers', $dr);
+}
+
+/**
+ * Fetch metadata for all display renderer plugins.
+ *
+ * @return
+ *   An array of arrays with information about all available panels display
+ *   renderer.
+ */
+function panels_get_display_renderers() {
+  ctools_include('plugins');
+  return ctools_get_plugins('panels', 'display_renderers');
+}
+
+/**
  * Get a function from a plugin, if it exists.
  *
  * @param $plugin
diff --git panels.module panels.module
index 13f6fa1..3689acc 100644
--- panels.module
+++ panels.module
@@ -473,6 +473,7 @@ class panels_display {
   var $css_id = NULL;
   var $context = array();
   var $did = 'new';
+  var $renderer = 'panels_renderer_standard';
 
   function add_pane($pane, $location = FALSE) {
     $pane->pid = $this->next_new_pid();
@@ -514,6 +515,74 @@ class panels_display {
     $next_id = max($id);
     return ++$next_id;
   }
+
+  /**
+   * Get the title from a display.
+   *
+   * The display must have already been rendered, or the setting to set the
+   * display's title from a pane's title will not have worked.
+   *
+   * @return
+   *   The title to use. If NULL, this means to let any default title that may be in use
+   *   pass through. i.e, do not actually set the title.
+   */
+  function get_title() {
+    switch ($this->hide_title) {
+      case PANELS_TITLE_NONE:
+        return '';
+
+      case PANELS_TITLE_PANE:
+        return isset($this->stored_pane_title) ? $this->stored_pane_title : '';
+
+      case PANELS_TITLE_FIXED:
+      case FALSE; // For old exported panels that are not in the database.
+        if (!empty($this->title)) {
+          return filter_xss_admin(ctools_context_keyword_substitute($this->title, array(), $this->context));
+        }
+        return NULL;
+    }
+  }
+
+  /**
+   * Render this panels display.
+   *
+   * After checking to ensure the designated layout plugin is valid, a
+   * display renderer object is spawned and runs its rendering logic.
+   *
+   * @param mixed $renderer
+   *    An instanciated display renderer object, or the name of a display
+   *    renderer plugin+class to be fetched. Defaults to NULL. When NULL, the
+   *    predesignated display renderer will be used.
+   */
+  function render($renderer = NULL) {
+    $layout = panels_get_layout($this->layout);
+    if (!$layout) {
+      return NULL;
+    }
+
+    if (!is_object($renderer)) {
+      $renderer = is_string($renderer) ? $renderer : $this->renderer;
+      $renderer_class = ctools_plugin_load_class('panels', 'display_renderers', $renderer, 'handler');
+      $renderer = new $renderer_class();
+    }
+    $renderer->build($this, $layout);
+
+    $output = '';
+    // Let modules act just prior to render.
+    foreach (module_implements('panels_pre_render') as $module) {
+      $function = $module . '_panels_pre_render';
+      $output .= $function($display, $renderer);
+    }
+
+    $output .= $renderer->render();
+
+    // Let modules act just after render.
+    foreach (module_implements('panels_post_render') as $module) {
+      $function = $module . '_panels_post_render';
+      $output .= $function($display, $renderer);
+    }
+    return $output;
+  }
 }
 
 /**
@@ -809,10 +878,9 @@ function panels_export_display($display, $prefix = '') {
  *
  * if $incoming_content is NULL, default content will be applied. Use
  * an empty string to indicate no content.
- * @render
  * @ingroup hook_invocations
  */
-function panels_render_display(&$display) {
+function panels_render_display(&$display, $renderer = NULL) {
   ctools_include('display-render', 'panels');
   ctools_include('plugins', 'panels');
   ctools_include('context');
@@ -825,7 +893,7 @@ function panels_render_display(&$display) {
       return drupal_render_form($form_context->form_id, $form_context->form);
     }
   }
-  return _panels_render_display($display);
+  return $display->render($renderer);
 }
 
 
@@ -861,7 +929,7 @@ function panels_print_layout($id, $content) {
  * then operate as a theme function of the form.
  */
 function theme_panels_render_display_form($form) {
-  $form['#children'] = _panels_render_display($form['#display']);
+  $form['#children'] = $form['#display']->render();
   drupal_render($form);
   return theme('form', $form);
 }
diff --git panels_ipe/images/dragger.png panels_ipe/images/dragger.png
new file mode 100644
index 0000000..bb3b57b
Binary files /dev/null and panels_ipe/images/dragger.png differ
diff --git panels_ipe/panels_ipe.css panels_ipe/panels_ipe.css
new file mode 100644
index 0000000..424cf55
--- /dev/null
+++ panels_ipe/panels_ipe.css
@@ -0,0 +1,141 @@
+div.panels-ipe-handlebar-wrapper {
+  margin-top: 1em;
+  border-bottom: #999 solid 1px;
+}
+
+div.panels-ipe-handlebar-wrapper ul {
+  float: left;
+  margin: 0;
+  padding: 0;
+  text-align: right;
+}
+
+div.panels-ipe-handlebar-wrapper li,
+div.panels-ipe-draghandle {
+  background: none repeat scroll 0 0 transparent;
+  list-style: none outside none;
+  margin: 0;
+  padding: 0;
+  float: left;
+}
+
+div.panels-ipe-handlebar-wrapper li {
+  border-top: 1px solid #CCC;
+  border-right: 1px solid #CCC;
+}
+
+div.panels-ipe-handlebar-wrapper li.first {
+  border-left: 1px solid #CCC;
+}
+
+div.panels-ipe-draghandle {
+  float: right;
+  border-right: none;
+  border-left: 1px solid #AAA;
+}
+
+/* Hide editor-state-on elements initially */
+.panels-ipe-on {
+  display: none;
+}
+
+.panels-ipe-editing .panels-ipe-on {
+	display: block;
+}
+
+/* Show editor-state-off elements initially */
+.panels-ipe-off {
+  display: block;
+}
+
+div.panels-ipe-handlebar-wrapper li a,
+div.panels-ipe-draghandle span,
+div.panels-ipe-newblock a {
+  background-color: #f6f6f6;
+  display: block;
+  padding: 0.2em 0.5em;
+}
+
+div.panels-ipe-newblock a {
+  display: inline;
+  border: 1px solid #CCC;
+}
+
+.panels-ipe-editing .panels-ipe-portlet-content {
+  margin-bottom: 10px;
+  border: transparent dotted 1px;
+  overflow: hidden;
+}
+
+.panels-ipe-editing .panels-ipe-portlet-content:hover {
+  border: #FF000A dotted 1px;
+}
+
+.panels-ipe-editing .panels-ipe-region {
+  border: transparent dotted 1px;
+  float: left;
+  width: 100%;
+  margin-bottom: 5px;
+}
+
+div.panels-ipe-draghandle {
+  border: none;
+}
+
+div.panels-ipe-draghandle span {
+  border: none;
+  background: #989896 url(images/dragger.png);
+  text-indent: -9999px;
+  width: 26px;
+  height: 17px;
+  padding-left: 0;
+  padding-right: 0;
+  padding-bottom: 0.25em;
+}
+
+.ui-sortable-placeholder { margin: 1em; border: 1px dotted black; visibility: visible !important; height: 50px !important; }
+.ui-sortable-placeholder * { visibility: hidden; }
+
+/* counteract panels_dnd.css - temporary */
+div.panels-ipe-display-container .panel-pane .pane-title {
+  padding: 0;
+}
+
+/** ============================================================================
+ * Controller form markup
+ */
+
+div#panels-ipe-control-container {
+  z-index: 100;
+  position: fixed;
+  margin: auto;
+  bottom: 0;
+  left: 50%;
+  display: block;
+  background-color: #000;
+  padding: 0.5em 1em;
+  -moz-border-radius-topleft:5px;
+  -moz-border-radius-topright:5px;
+  -moz-box-shadow: #333 0px 1px 0px;
+  -webkit-border-radius-topleft:5px;
+  -webkit-border-radius-topright:5px;
+  -webkit-box-shadow: #333 0px 1px 0px;
+}
+
+div.panels-ipe-pseudobutton {
+  cursor: pointer;
+  background-color: #333;
+  font:normal 11px/15px "Lucida Grande",Tahoma,Verdana,sans-serif;
+  color: #FFF;
+  -moz-border-radius:5px;
+  -moz-box-shadow: #333 0px 1px 0px;
+  -webkit-border-radius:5px;
+  -webkit-box-shadow: #333 0px 1px 0px;
+  padding: 0.3em 0.8em;
+  float: left;
+}
+
+div.panels-ipe-control .form-submit {
+  float: left;
+  margin: 0.3em 0.5em;
+}
diff --git panels_ipe/panels_ipe.info panels_ipe/panels_ipe.info
new file mode 100644
index 0000000..c27a2a9
--- /dev/null
+++ panels_ipe/panels_ipe.info
@@ -0,0 +1,7 @@
+; $Id$
+name = Panels In-Place Editor
+description = Provide a UI for managing some Panels directly on the frontend, instead of having to use the backend.
+package = "Panels"
+dependencies[] = panels
+dependencies[] = jquery_ui
+core = 6.x
diff --git panels_ipe/panels_ipe.js panels_ipe/panels_ipe.js
new file mode 100644
index 0000000..ebfe32c
--- /dev/null
+++ panels_ipe/panels_ipe.js
@@ -0,0 +1,196 @@
+// $Id$
+
+(function($) {
+  // A ready function should be sufficient for this, at least for now
+  $(function() {
+    $.each(Drupal.settings.PanelsIPECacheKeys, function() {
+      Drupal.PanelsIPE.editors[this] = new DrupalPanelsIPE(this, Drupal.settings.PanelsIPESettings[this]);
+    });
+  });
+
+  Drupal.PanelsIPE = {
+    editors: {},
+    bindClickDelete: function(context) {
+      $('a.pane-delete:not(.pane-delete-processed)', context)
+        .addClass('pane-delete-processed')
+        .click(function() {
+          if (confirm('Remove this pane?')) {
+            $(this).parents('div.panels-ipe-portlet-wrapper').fadeOut(1000, function() {
+              $(this).empty().remove();
+            });
+          }
+          return false;
+        });
+    },
+    initEditing: function(context) {
+      $(document.body).addClass('panels-ipe');
+      var draggable_options = {
+        revert: 'invalid',
+        helper: 'clone', // required for "flawless" interoperation with sortables
+        connectToSortable: ($.ui.version === '1.6') ? ['div.panels-ipe-region'] : 'div.panels-ipe-region',
+        appendTo: 'body',
+        handle: 'panels-ipe-draghandle',
+      };
+
+      // Add a class so that the direct parent container of the panes can be
+      // more easily identified
+      // $('div.panels-ipe-pane').parent().addClass('panels-ipe-region-innermost');
+    }
+  }
+
+  Drupal.behaviors.PanelsIPE = function(context) {
+    Drupal.PanelsIPE.bindClickDelete(context);
+  };
+
+  /**
+   * Base object (class) definition for the Panels In-Place Editor.
+   *
+   *  A new instance of this object is instanciated whenever an IPE is
+   *  initiated, and destroyed when editing is concluded (successfully or not).
+   *
+   * @param {string} cache_key
+   */
+  function DrupalPanelsIPE(cache_key, cfg) {
+    var ipe = this;
+    this.key = cache_key;
+    this.state = {};
+    this.topParent = $('div#panels-ipe-display-'+cache_key);
+    this.control = $('div#panels-ipe-control-'+ cache_key);
+    this.initButton = $('div.panels-ipe-startedit', this.control);
+    this.cfg = cfg;
+
+    /**
+     * Passthrough method to be attached to Drupal.behaviors
+     *
+     * @param {jQuery} context
+     */
+    this.behaviorsPassthrough = function(context) {
+      Drupal.PanelsIPE.bindClickDelete(context);
+    };
+    
+    Drupal.behaviors['PanelsIPE' + cache_key] = this.behaviorsPassthrough;
+
+    this.initEditing = function(formdata) {
+      // See http://jqueryui.com/demos/sortable/ for details on the configuration
+      // parameters used here.
+      var sortable_options = { // TODO allow the IPE plugin to control these
+        revert: true,
+        dropOnEmpty: true, // default
+        opacity: 0.75, // opacity of sortable while sorting
+        // placeholder: 'draggable-placeholder',
+        // forcePlaceholderSize: true,
+        items: 'div.panels-ipe-portlet-wrapper',
+        handle: 'div.panels-ipe-draghandle',
+        tolerance: 'pointer',
+        // containment: ipe.topParent,
+      };
+      $('div.panels-ipe-sort-container', ipe.topParent).sortable(sortable_options);
+      // Since the connectWith option only does a one-way hookup, iterate over
+      // all sortable regions to connect them with one another.
+      $('div.panels-ipe-sort-container', ipe.topParent)
+        .sortable('option', 'connectWith', ['div.panels-ipe-sort-container']);
+      
+      $('.panels-ipe-form-container', ipe.control).append(formdata);
+      // bind ajax submit to the form
+      $('form', ipe.control).submit(function(event) {
+        url = $(this).attr('action');
+        try {
+          var ajaxOptions = {
+            type: 'POST',
+            url: url,
+            data: { 'js': 1 },
+            global: true,
+            success: ipe.formRespond,
+            error: function(xhr) {
+              Drupal.CTools.AJAX.handleErrors(xhr, url);
+            },
+            dataType: 'json'
+          };
+          $(this).ajaxSubmit(ajaxOptions);
+        }
+        catch (err) {
+          alert("An error occurred while attempting to process " + url);
+          return false;
+        }
+        return false;
+      });
+      
+      $('input:submit', ipe.control).each(function() {
+        if ($(this).val() == 'Save') {
+          $(this).click(ipe.saveEditing);
+        };
+        if ($(this).val() == 'Cancel') {
+          $(this).click(ipe.cancelEditing);
+        };
+      });
+      
+      // Perform visual effects in a particular sequence.
+      ipe.control.fadeOut('normal', function() {
+        ipe.initButton.hide();
+        ipe.control.fadeIn('normal', function() {
+          // Show all the hidden IPE elements
+          $('.panels-ipe-on').show('slow', function() {
+        	ipe.topParent.addClass('panels-ipe-editing');
+          });
+        })
+      });
+    }
+    
+    this.formRespond = function(data) {
+      $('.panels-ipe-form-container', ipe.control).empty();
+      ipe.endEditing();
+    }
+    
+    this.showEditor = function() {
+
+    }
+
+    this.endEditing = function() {
+      // Re-hide all the IPE meta-elements
+      $('div.panels-ipe-on').hide('normal');
+      ipe.topParent.removeClass('panels-ipe-editing');
+      // Re-show all the IPE non-editing meta-elements
+      $('div.panels-ipe-off').show('normal');
+    };
+
+    this.saveEditing = function() {
+      $('div.panels-ipe-region', ipe.topParent).each(function() {
+        var val = '';
+        var region = $(this).attr('id').split('panels-ipe-regionid-')[1];
+        $(this).children('div.panels-ipe-portlet-wrapper').each(function() {
+          if (val) {
+            val += ',';
+          }
+          val += $(this).attr('id').split('panels-ipe-paneid-')[1];
+        });
+        $('input#edit-panel-pane-' + region, ipe.control).val(val);
+      });
+    };
+    
+    this.cancelEditing = function() {
+      $('div.panels-ipe-region', ipe.topParent).sortable('destroy');
+    };
+    
+    var ajaxOptions = {
+      type: "POST",
+      url: ipe.cfg.formPath,
+      data: { 'js': 1 },
+      global: true,
+      success: ipe.initEditing,
+      error: function(xhr) {
+        Drupal.CTools.AJAX.handleErrors(xhr, ipe.cfg.formPath);
+      },
+      dataType: 'json'
+    };
+
+    $('div.panels-ipe-region', this.topParent).each(function() {
+      $('div.panels-ipe-portlet-wrapper', this)
+        .wrapAll('<div class="panels-ipe-sort-container" />');
+    });
+
+    $('div.panels-ipe-startedit', this.control).click(function() {
+      var $this = $(this);
+      $.ajax(ajaxOptions);
+    });
+  };
+})(jQuery);
diff --git panels_ipe/panels_ipe.module panels_ipe/panels_ipe.module
new file mode 100644
index 0000000..7cfc73d
--- /dev/null
+++ panels_ipe/panels_ipe.module
@@ -0,0 +1,500 @@
+<?php
+
+/**
+ * Implementation of hook_menu().
+ */
+function panels_ipe_menu() {
+  $items = array();
+
+  $base = array(
+    'access arguments' => array('administer page manager'), // TODO this clearly needs changing
+    'type' => MENU_CALLBACK,
+    'page arguments' => array(3),
+  );
+
+  $items['panels_ipe/ajax/render-pane/%/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ipe_ajax_render_pane',
+    'page arguments' => array(3, 4),
+  ) + $base;
+  $items['panels_ipe/ajax/add-pane/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ipe_ajax_add_pane_choose',
+  ) + $base;
+  $items['panels_ipe/ajax/add-pane-config/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ipe_ajax_add_pane_config',
+  ) + $base;
+  $items['panels_ipe/ajax/configure/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ipe_ajax_configure_pane',
+  ) + $base;
+  $items['panels_ipe/ajax/save/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ipe_ajax_save_display',
+  ) + $base;
+  $items['panels_ipe/ajax/edit/%panels_edit_cache'] = array(
+    'page callback' => 'panels_ipe_ajax_edit',
+  ) + $base;
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_ctools_plugin_directory().
+ */
+function panels_ipe_ctools_plugin_directory($module, $plugin) {
+  if ($module == 'panels' && $plugin == 'display_renderers') {
+    return 'plugins/' . $plugin;
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function panels_ipe_theme() {
+  return array(
+    'panels_ipe_pane_wrapper' => array(
+      'arguments' => array('output' => NULL, 'pane' => NULL, 'display' => NULL),
+    ),
+    'panels_ipe_region_wrapper' => array(
+      'arguments' => array('output' => NULL, 'region_id' => NULL, 'display' => NULL),
+    ),
+    'panels_ipe_add_pane_button' => array(
+      'arguments' => array('region_id' => NULL, 'display' => NULL),
+    ),
+    'panels_ipe_placeholder_pane' => array(
+      'arguments' => array('region_id' => NULL, 'region_title' => NULL),
+    ),
+    'panels_ipe_dnd_form_container' => array(
+      'arguments' => array('link' => NULL, 'cache_key' => NULL, 'display' => NULL),
+    ),
+  );
+}
+
+/**
+ * Theme the 'placeholder' pane, which is shown on an active IPE when no panes
+ * live in that region.
+ *
+ * @param string $region_id
+ * @param string $region_title
+ */
+function theme_panels_ipe_placeholder_pane($region_id, $region_title) {
+  $output = '<div class="panels-ipe-placeholder-content">';
+  $output .= "<h3>$region_title</h3>";
+  $output .= '</div>';
+  return $output;
+}
+
+function theme_panels_ipe_pane_wrapper($output, $pane, $display) {
+  $links = array(
+    'edit' => array(
+      'title' => t('edit'),
+      'href' => "panels_ipe/ajax/configure/$display->cache_key/$pane->pid",
+      'attributes' => array(
+        'class' => 'ctools-use-modal',
+        // 'id' => "pane-edit-panel-pane-$pane->pid",
+      ),
+    ),
+    // Deleting is managed entirely in the js; this is just an attachment point
+    // for it
+    'delete' => array(
+      'title' => t('delete'),
+      'href' => '#',
+      'attributes' => array(
+        'class' => 'pane-delete',
+        'id' => "pane-delete-panel-pane-$pane->pid",
+      ),
+    ),
+  );
+  $attr = array(
+    'class' => 'panels-ipe-linkbar',
+  );
+
+  $links = theme('links', $links, $attr);
+  $links .= '<div class="panels-ipe-draghandle"><span>DRAGGER</span></div>';
+  $handlebar = '<div class="panels-ipe-handlebar-wrapper panels-ipe-on clear-block">' . $links . '</div>';
+  return $handlebar . $output;
+}
+
+function theme_panels_ipe_region_wrapper($output, $region_id, $display) {
+  return $output;
+}
+
+function theme_panels_ipe_add_pane_button($region_id, $display) {
+  $attr = array('class' => 'ctools-use-modal');
+  $link = l(t('add new pane'), "panels_ipe/ajax/add-pane/$display->cache_key/$region_id", array('attributes' => $attr));
+  return '<div class="panels-ipe-newblock panels-ipe-on">' . $link . '</div>';
+}
+
+function panels_ipe_ajax_render_pane($pid, $cache) {
+  $class = ctools_plugin_load_class('panels', 'display_renderers', 'panels_renderer_single_pane', 'handler');
+  $renderer = new $class();
+  $renderer->build($cache->display, $pid);
+  $output = array();
+  $output[] = ctools_ajax_command_replace("#panels-ipe-paneid-$pid > .panels-ipe-proper-pane", $renderer->render());
+
+  ctools_ajax_render($output);
+}
+
+function panels_ipe_get_cache_key($key = NULL) {
+  static $cache;
+  if (isset($key)) {
+    $cache = $key;
+  }
+  return $cache;
+}
+
+/**
+ * AJAX entry point to create the controller form for an IPE.
+ *
+ * TODO this can be moved into the IPE plugin probably
+ */
+function panels_ipe_ajax_edit($cache) {
+  $display = $cache->display;
+  ctools_include('form');
+
+  $form_state = array(
+    'display' => &$display,
+    'content_types' => $cache->content_types,
+    'rerender' => FALSE,
+    'no_redirect' => TRUE,
+  );
+
+  $output = ctools_build_form('panels_ipe_edit_control_form', $form_state);
+  // no output == submit
+  if (!$output) {
+    if (!empty($form_state['clicked_button']['#save-display'])) {
+      panels_save_display($display);
+    }
+    // panels_cache_clear('display', $display->did);
+    drupal_json(1);
+  }
+  else {
+    drupal_json($output);
+  }
+  exit;
+}
+
+function panels_ipe_edit_control_form(&$form_state) {
+  $display = &$form_state['display'];
+  $display->cache_key = isset($display->cache_key) ? $display->cache_key : $display->did;
+
+  // Annoyingly, theme doesn't have access to form_state so we have to do this.
+  $form['#display'] = $display;
+
+  $layout = panels_get_layout($display->layout);
+  $layout_panels = panels_get_panels($layout, $display);
+
+  $form['panel'] = array('#tree' => TRUE);
+  $form['panel']['pane'] = array('#tree' => TRUE);
+
+  foreach ($layout_panels as $panel_id => $title) {
+    // Make sure we at least have an empty array for all possible locations.
+    if (!isset($display->panels[$panel_id])) {
+      $display->panels[$panel_id] = array();
+    }
+
+    $form['panel']['pane'][$panel_id] = array(
+      // Use 'hidden' instead of 'value' so the js can access it.
+      '#type' => 'hidden',
+      '#default_value' => implode(',', (array) $display->panels[$panel_id]),
+    );
+  }
+
+  $form['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#id' => 'panels-ipe-save',
+    '#submit' => array('panels_edit_display_form_submit'),
+    '#save-display' => TRUE,
+  );
+  $form['buttons']['cancel'] = array(
+    '#type' => 'submit',
+    '#value' => t('Cancel'),
+  );
+  return $form;
+}
+
+/**
+ * Experimenting with this as a means for outputting the display editor form.
+ * @param unknown_type $main
+ */
+function panels_ipe_footer($main = 0) {
+  $key = panels_ipe_get_cache_key();
+  if (!isset($key)) {
+    return;
+  }
+  // TODO should be moved into the IPE plugin - WAAAY too hardcoded right now
+  $output = "<div id='panels-ipe-control-container' class='clear-block'>";
+  $output .= "<div id='panels-ipe-control-$key' class='panels-ipe-control'>";
+  $output .= "<div class='panels-ipe-startedit panels-ipe-pseudobutton panels-ipe-off'>";
+  $output .= "<span>" . t('Customize this page') . "</span>";
+  $output .= "</div>";
+  $output .= "<div class='panels-ipe-form-container panels-ipe-on clear-block'</div>";
+  $output .= "</div></div>";
+  return $output;
+}
+
+/**
+ * COPIED STRAIGHT OUT OF DISPLAY-EDIT.INC.
+ *
+ * This duplication will go away...once I figure out how to refactor out the
+ * hard-codedness of much of display-edit.inc.
+ */
+
+
+/**
+ * Entry point for AJAX: 'Add Content' modal form, from which the user selects the
+ * type of pane to add.
+ *
+ * @param int $cache
+ *  The display id of the $display object currently being edited.
+ * @param string $panel_id
+ *  A string with the name of the panel region to which the selected
+ *  pane type will be added.
+ */
+function panels_ipe_ajax_add_pane_choose($cache, $panel_id = NULL, $category = NULL) {
+  $display = $cache->display;
+  $layout = panels_get_layout($display->layout);
+  $layout_panels = panels_get_panels($layout, $display);
+
+  if ($layout && array_key_exists($panel_id, $layout_panels)) {
+    ctools_modal_render(
+      t('Add content to !s', array('!s' => $layout_panels[$panel_id])),
+      panels_ipe_add_content($cache, $panel_id, $category)
+    );
+  }
+
+  ctools_modal_render(t('Error'), t('Invalid input'));
+}
+
+/**
+ * Display a list of content available.
+ */
+function panels_ipe_add_content($cache, $panel_id, $key) {
+  ctools_include('content');
+  $cache_key = $cache->display->cache_key;
+
+  $category_names = array();
+  $categories = array();
+  $titles     = array();
+
+  foreach ($cache->content_types as $type_name => $subtypes) {
+    if (is_array($subtypes)) {
+      $content_type = ctools_get_content_type($type_name);
+      foreach ($subtypes as $subtype_name => $subtype_info) {
+        $title = filter_xss_admin($subtype_info['title']);
+        $description = isset($subtype_info['description']) ? $subtype_info['description'] : $title;
+
+        $icon = ctools_content_admin_icon($subtype_info);
+
+        if (isset($subtype_info['top level'])) {
+          $category = 'root';
+        }
+        else if (isset($subtype_info['category'])) {
+          if (is_array($subtype_info['category'])) {
+            list($category, $weight) = $subtype_info['category'];
+          }
+          else {
+            $category = $subtype_info['category'];
+          }
+        }
+        else {
+          $category = t('Uncategorized');
+        }
+
+        $category_key = preg_replace('/[^a-z0-9]/', '-', strtolower($category));
+
+        $output = '<div class="content-type-button clear-block">';
+        $url = "panels_ipe/ajax/add-pane-config/$cache_key/$panel_id/$type_name/$subtype_name";
+        $output .= ctools_ajax_image_button($icon, $url, $description, 'panels-modal-add-config');
+        $output .= '<div>' . ctools_ajax_text_button($title, $url, $description, 'panels-modal-add-config') . '</div>';
+        $output .= '</div>';
+        if (!isset($categories[$category_key])) {
+          $category_names[$category_key] = $category;
+          $categories[$category_key] = array();
+          $titles[$category_key] = array();
+        }
+
+        $categories[$category_key][] = $output;
+        $titles[$category_key][] = $title;
+      }
+    }
+  }
+  if (!$categories) {
+    $output = t('There are no content types you may add to this display.');
+  }
+  else {
+    if (!empty($category_names['root'])) {
+      unset($category_names['root']);
+    }
+
+    $output = '<div class="panels-add-content-modal">';
+    natcasesort($category_names);
+    $selector = '<div class="panels-categories-box">';
+
+    // Render our list of categories in column 0.
+    foreach ($category_names as $category => $name) {
+      $class = 'panels-modal-add-category';
+      if ($category == $key) {
+        $class .= ' active';
+      }
+
+      $url = "panels_ipe/ajax/add-pane/$cache_key/$panel_id/$category";
+      $selector .= ctools_ajax_text_button($name, $url, '', $class);
+    }
+
+    $selector .= '</div>';
+    if (!empty($categories['root'])) {
+      foreach ($titles['root'] as $id => $title) {
+        $selector .= $categories['root'][$id];
+      }
+    }
+
+    if (empty($key) || empty($category_names[$key]) || $key == 'root') {
+//      $key = current(array_keys($category_names));
+      $center = '<div class="panels-categories-description">';
+      $center .= t('Content options are divided by category. Please select a category from the left to proceed.');
+      $center .= '</div>';
+    }
+    else {
+      natcasesort($titles[$key]);
+
+      // Fill out the info for our current category.
+      $columns = 2;
+      $col[1] = '';
+      $col[2] = '';
+
+      $col_size = count($titles[$key]) / $columns;
+      $count = 0;
+      foreach ($titles[$key] as $id => $title) {
+        $which = floor($count++ / $col_size) + 1; // we leave 0 for the categories.
+        $col[$which] .= $categories[$key][$id];
+      }
+
+      $center = '';
+      foreach ($col as $id => $column) {
+        $center .= '<div class="panels-section-column panels-section-column-' . $id . '">'
+        . '<div class="inside">' . $column . '</div></div>';
+      }
+      $center .= '</div>'; // columns
+    }
+    $output .= '<div class="panels-section-column panels-section-column-categories">'
+      . '<div class="inside">' . $selector . '</div></div>';
+    $output .= '<div class="panels-section-columns">';
+    $output .= $center;
+    $output .= '</div>'; // categories box
+  }
+  return $output;
+}
+
+/**
+ * AJAX entry point for to configure a pane that has just been added.
+ */
+function panels_ipe_ajax_add_pane_config($cache, $panel_id = NULL, $type_name = NULL, $subtype_name = NULL, $step = NULL) {
+  ctools_include('content');
+  $cache_key = $cache->display->cache_key;
+  $content_type = ctools_get_content_type($type_name);
+  $subtype = ctools_content_get_subtype($content_type, $subtype_name);
+
+  if (!isset($step) || !isset($cache->new_pane)) {
+    $pane = panels_new_pane($type_name, $subtype_name);
+    $pane->configuration = ctools_content_get_defaults($content_type, $subtype);
+    $pane->panel = $panel_id;
+    $cache->new_pane = &$pane;
+  }
+  else {
+    $pane = $cache->new_pane;
+  }
+
+  $form_state = array(
+    'display' => &$cache->display,
+    'contexts' => $cache->display->context,
+    'pane' => &$pane,
+    'cache_key' => $cache_key,
+    'cache' => &$cache,
+    'ajax' => TRUE,
+    'modal' => TRUE,
+    'commands' => array(),
+  );
+
+  $form_info = array(
+    'path' => "panels_ipe/ajax/add-pane-config/$cache_key/$panel_id/$type_name/$subtype_name/%step",
+    'next callback' => 'panels_ipe_ajax_edit_pane_config_next',
+    'finish callback' => 'panels_ipe_ajax_add_pane_config_finish',
+  );
+
+  $rc = ctools_content_form('add', $form_info, $form_state, $content_type, $pane->subtype, $subtype, $pane->configuration, $step);
+  if ($rc === FALSE) {
+    panels_ipe_ajax_add_pane_config_finish($form_state);
+    $form_state['commands'][] = ctools_modal_command_dismiss();
+    ctools_ajax_render($form_state['commands']);
+  }
+}
+
+/**
+ * AJAX entry point for to configure a pane that has just been added.
+ */
+function panels_ipe_ajax_configure_pane($cache, $pid = NULL, $step = NULL) {
+  ctools_include('content');
+  $cache_key = $cache->display->cache_key;
+  if (empty($cache->display->content[$pid])) {
+    ctools_modal_render(t('Error'), t('Invalid pane id.'));
+  }
+
+  $pane = &$cache->display->content[$pid];
+
+  $content_type = ctools_get_content_type($pane->type);
+  $subtype = ctools_content_get_subtype($content_type, $pane->subtype);
+
+  $form_state = array(
+    'display' => &$cache->display,
+    'contexts' => $cache->display->context,
+    'pane' => &$pane,
+    'cache' => &$cache,
+    'ajax' => TRUE,
+    'modal' => TRUE,
+    'commands' => array(),
+  );
+
+  $form_info = array(
+    'path' => "panels_ipe/ajax/configure/$cache_key/$pid/%step",
+    'next callback' => 'panels_ipe_ajax_edit_pane_config_next',
+    'finish callback' => 'panels_ipe_ajax_edit_pane_config_finish',
+  );
+
+  ctools_content_form('edit', $form_info, $form_state, $content_type, $pane->subtype,  $subtype, $pane->configuration, $step);
+}
+
+function panels_ipe_ajax_edit_pane_config_next(&$form_state) {
+  $form_state['cache']->new_pane = $form_state['pane'];
+  panels_edit_cache_set($form_state['cache']);
+}
+
+function panels_ipe_ajax_add_pane_config_finish(&$form_state) {
+  $cache = &$form_state['cache'];
+  $pane = $cache->new_pane;
+  unset($cache->new_pane);
+
+  // Get a real pid for this pane.
+  $pane->pid = "new-" . $cache->display->next_new_pid();
+  // Put the pane into the display where it belongs
+
+  $cache->display->content[$pane->pid] = $pane;
+  $cache->display->panels[$pane->panel][] = $pane->pid;
+
+  $class = ctools_plugin_load_class('panels', 'display_renderers', 'panels_renderer_ipe', 'handler');
+  $renderer = new $class();
+  $renderer->build($cache->display, array());
+  panels_edit_cache_set($cache);
+
+  $form_state['commands'][] = ctools_ajax_command_append("#panels-ipe-regionid-$pane->panel", $renderer->render_pane($pane));
+}
+
+function panels_ipe_ajax_edit_pane_config_finish(&$form_state) {
+  $cache = &$form_state['cache'];
+  $pane = &$form_state['pane'];
+
+  $class = ctools_plugin_load_class('panels', 'display_renderers', 'panels_renderer_single_pane', 'handler');
+  $renderer = new $class();
+  $renderer->build($cache->display, $pane->pid);
+  panels_edit_cache_set($cache);
+
+  $form_state['commands'][] = ctools_ajax_command_replace("#panels-ipe-paneid-$pane->pid > .panels-ipe-portlet-content", $renderer->render());
+}
diff --git panels_ipe/plugins/display_renderers/panels_renderer_ipe.class.php panels_ipe/plugins/display_renderers/panels_renderer_ipe.class.php
new file mode 100644
index 0000000..a50a86b
--- /dev/null
+++ panels_ipe/plugins/display_renderers/panels_renderer_ipe.class.php
@@ -0,0 +1,84 @@
+<?php
+
+
+/**
+ * Base renderer class for all In-Place Editor (IPE) behavior.
+ *
+ */
+class panels_renderer_ipe extends panels_renderer_standard {
+  function render() {
+    $output = parent::render();
+    return "<div id='panels-ipe-display-{$this->display->cache_key}' class='panels-ipe-display-container'>$output</div>";
+  }
+
+  function add_meta() {
+    $this->display->cache_key = $this->display->did;
+    panels_ipe_get_cache_key($this->display->cache_key);
+    panels_load_include('display-edit');
+    $cache = new stdClass();
+    $cache->display = $this->display;
+    ctools_include('content');
+    // NO good reason for this to need to be set way out here!?
+    $cache->content_types = ctools_content_get_available_types();
+    panels_edit_cache_set($cache);
+    ctools_include('ajax');
+    ctools_include('modal');
+    ctools_modal_add_js();
+    drupal_add_css(panels_get_path('css/panels_admin.css'));
+    drupal_add_css(panels_get_path('css/panels_dnd.css'));
+    drupal_add_css(drupal_get_path('module', 'panels_ipe') . '/panels_ipe.css');
+    drupal_add_js(drupal_get_path('module', 'panels_ipe') . '/panels_ipe.js');
+    $settings = array(
+      'formPath' => '/panels_ipe/ajax/edit/' . $this->display->cache_key,
+    );
+    drupal_add_js(array('PanelsIPECacheKeys' => array($this->display->cache_key)), 'setting');
+    drupal_add_js(array('PanelsIPESettings' => array($this->display->cache_key => $settings)), 'setting');
+    jquery_ui_add(array('ui.draggable', 'ui.droppable', 'ui.sortable'));
+    parent::add_meta();
+  }
+
+  /**
+   * Override & call the parent, then pass output through to the dnd wrapper
+   * theme function.
+   *
+   * @param $pane
+   */
+  function render_pane($pane) {
+    $output = parent::render_pane($pane);
+    if (empty($output)) {
+      return;
+    }
+    // Add an inner layer wrapper to the pane content before placing it into
+    // draggable portlet
+    $output = "<div class='panels-ipe-portlet-content'>$output</div>";
+    // Hand it off to the plugin/theme for placing draggers/buttons
+    $output = theme('panels_ipe_pane_wrapper', $output, $pane, $this->display);
+    return "<div id='panels-ipe-paneid-{$pane->pid}' class='panels-ipe-portlet-wrapper'>" . $output . "</div>";
+  }
+
+  /**
+   * Add an 'empty' pane placeholder above all the normal panes, and
+   * @param $region_id
+   * @param $panes
+   */
+  function render_region($region_id, $panes) {
+    // Generate this region's 'empty' placeholder pane from the IPE plugin.
+    $empty_ph = theme('panels_ipe_placeholder_pane', $region_id, $this->plugins['layout']['panels'][$region_id]);
+    // Wrap the placeholder in some guaranteed markup.
+    $panes['empty_placeholder'] = '<div class="panels-ipe-placeholder panels-ipe-on">' . $empty_ph . "</div>";
+    // Generate this region's add new pane button. FIXME waaaaay too hardcoded
+    $panes['add_button'] = theme('panels_ipe_add_pane_button', $region_id, $this->display);
+
+    $output = parent::render_region($region_id, $panes);
+    $output = theme('panels_ipe_region_wrapper', $output, $region_id, $this->display);
+    $classes = 'panels-ipe-region';
+    // Use the canonical list of empty regions to determine whether or not this
+    // region should initially be marked empty. It is important that we use the
+    // list instead of introspecting on render data so that this behavior is
+    // easily controlled externally.
+    if (array_search($region_id, $this->prepared['empty regions'])) {
+      $classes .= ' panels-ipe-region-empty';
+    }
+    return "<div id='panels-ipe-regionid-$region_id' class='panels-ipe-region'>" . $output . "</div>";
+  }
+}
diff --git panels_ipe/plugins/display_renderers/panels_renderer_ipe.inc panels_ipe/plugins/display_renderers/panels_renderer_ipe.inc
new file mode 100644
index 0000000..5474db7
--- /dev/null
+++ panels_ipe/plugins/display_renderers/panels_renderer_ipe.inc
@@ -0,0 +1,8 @@
+<?php
+
+$plugin = array(
+  'handler' => array(
+    'class' => 'panels_renderer_ipe',
+    'parent' => 'panels_renderer_standard',
+  ),
+);
\ No newline at end of file
diff --git panels_ipe/plugins/display_renderers/panels_renderer_single_pane.class.php panels_ipe/plugins/display_renderers/panels_renderer_single_pane.class.php
new file mode 100644
index 0000000..2fdd76d
--- /dev/null
+++ panels_ipe/plugins/display_renderers/panels_renderer_single_pane.class.php
@@ -0,0 +1,31 @@
+<?php
+
+class panels_renderer_single_pane extends panels_renderer_standard {
+  /**
+   * The pane id of the pane that will be rendered by a call to the render()
+   * method. Numeric int or string (typically if a new-# id has been used).
+   * @var mixed
+   */
+  var $render_pid;
+
+  /**
+   * Modified build method (vs. panels_renderer_standard::build()); takes a display and the pid of the pane to render.
+   * @param $display
+   */
+  function build(&$display, $pid) {
+    $this->display = &$display;
+    $this->render_pid = $pid;
+  }
+
+  function render() {
+    // If the requested pid does not exist,
+    if (empty($this->display->content[$this->render_pid])) {
+      return NULL;
+    }
+    return $this->render_pane($this->display->content[$this->render_pid]);
+  }
+
+  function render_single($pid) {
+    return $this->render_pane($this->display->content[$pid]);
+  }
+}
\ No newline at end of file
diff --git panels_ipe/plugins/display_renderers/panels_renderer_single_pane.inc panels_ipe/plugins/display_renderers/panels_renderer_single_pane.inc
new file mode 100644
index 0000000..d284239
--- /dev/null
+++ panels_ipe/plugins/display_renderers/panels_renderer_single_pane.inc
@@ -0,0 +1,8 @@
+<?php
+
+$plugin = array(
+  'handler' => array(
+    'class' => 'panels_renderer_single_pane',
+    'parent' => 'panels_renderer_standard',
+  ),
+);
\ No newline at end of file
diff --git panels_mini/panels_mini.module panels_mini/panels_mini.module
index d96ef26..81b93b1 100644
--- panels_mini/panels_mini.module
+++ panels_mini/panels_mini.module
@@ -77,7 +77,7 @@ function panels_mini_block($op = 'list', $delta = 0, $edit = array()) {
     $block = array();
 
     $block['content'] = panels_render_display($panel_mini->display);
-    $block['subject'] = panels_display_get_title($panel_mini->display);
+    $block['subject'] = $panel_mini->display->get_title();
     // If NULL, fall back to this title:
     if (!isset($block['subject'])) {
       $block['subject'] = $panel_mini->title;
diff --git plugins/display_renderers/panels_renderer_legacy.class.php plugins/display_renderers/panels_renderer_legacy.class.php
new file mode 100644
index 0000000..cfe9195
--- /dev/null
+++ plugins/display_renderers/panels_renderer_legacy.class.php
@@ -0,0 +1,238 @@
+<?php
+
+/**
+ * Legacy render pipeline for a panels display.
+ *
+ * This render pipeline mirrors the old procedural system exactly, and plugins
+ * written for the legacy system will work exactly as they did before with this
+ * renderer.
+ *
+ * Most plugins will work with the newer renderer. These are the exceptions:
+ *  - Style plugins that implement panel styling no longer need to call
+ *    panels_render_pane() on all contained panes; rendered pane HTML is now
+ *    passed in directly.
+ *  - Cache plugins are now triggered on rendered HTML, rather than on
+ *    unrendered datastructures.
+ *
+ * If your site relies on any of these plugin behaviors, you will need to use
+ * this renderer instead of the new panels_renderer_standard() until those
+ * plugins are updated.
+ */
+class panels_renderer_legacy {
+  var $display;
+  var $plugins = array();
+
+  function build(&$display, $layout) {
+    $this->display = &$display;
+    $this->plugins['layout'] = $layout;
+  }
+
+  /**
+   * Builds inner content, then hands off to layout-specified theme function for
+   * final render step.
+   *
+   * This is the outermost method in the Panels render pipeline. It calls the
+   * inner methods, which return a content array, which is in turn passed to the
+   * theme function specified in the layout plugin.
+   *
+   * @return string
+   *  Themed & rendered HTML output.
+   */
+  function render() {
+    // TODO can probably make this early-add CSS business go away by making
+    // mini panels use a slightly different plugin
+    if (!empty($this->plugins['layout']['css'])) {
+      if (file_exists(path_to_theme() . '/' . $this->plugins['layout']['css'])) {
+        drupal_add_css(path_to_theme() . '/' . $this->plugins['layout']['css']);
+      }
+      else {
+        drupal_add_css($this->plugins['layout']['path'] . '/' . $this->plugins['layout']['css']);
+      }
+    }
+    // This now comes after the CSS is added, because panels-within-panels must
+    // have their CSS added in the right order; inner content before outer content.
+
+    if (empty($this->display->cache['method'])) {
+      $content = $this->render_regions();
+    }
+    else {
+      // TODO This whole approach can & probably should be refactored now. Maybe
+      // invert it, and have the caching agent act from the outside?
+      $cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context);
+      if ($cache === FALSE) {
+        $cache = new panels_cache_object();
+        $cache->set_content($this->render_regions());
+        panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context);
+      }
+      $content = $cache->content;
+    }
+
+    $output = theme($this->plugins['layout']['theme'], check_plain($this->display->css_id), $content, $this->display->layout_settings, $this->display);
+
+    return $output;
+  }
+
+  /**
+   * Render all panes in the attached display into their panel regions, then
+   * render those regions.
+   *
+   * @return array $content
+   *    An array of rendered panel regions, keyed on the region name.
+   */
+  function render_regions() {
+    ctools_include('content');
+
+    // First, render all the panes into little boxes. We do this here because
+    // some panes request to be rendered after other panes (primarily so they
+    // can do the leftovers of forms).
+    $panes = array();
+    $later = array();
+
+    foreach ($this->display->content as $pid => $pane) {
+      // TODO remove in 7.x and ensure the upgrade path weeds out any stragglers; it's been long enough
+      $pane->shown = !empty($pane->shown); // guarantee this field exists.
+      // If the user can't see this pane, do not render it.
+      if (!$pane->shown || !panels_pane_access($pane, $this->display)) {
+        continue;
+      }
+
+      // If this pane wants to render last, add it to the $later array.
+      $content_type = ctools_get_content_type($pane->type);
+
+      if (!empty($content_type['render last'])) {
+        $later[$pid] = $pane;
+        continue;
+      }
+
+      $panes[$pid] = $this->render_pane($pane);
+    }
+
+    foreach ($later as $pid => $pane) {
+      $panes[$pid] = $this->render_pane($pane);
+    }
+
+    // Loop through all panels, put all panes that belong to the current panel
+    // in an array, then render the panel. Primarily this ensures that the
+    // panes are in the proper order.
+    $content = array();
+    foreach ($this->display->panels as $panel_name => $pids) {
+      $panel_panes = array();
+      foreach ($pids as $pid) {
+        if (!empty($panes[$pid])) {
+          $panel_panes[$pid] = $panes[$pid];
+        }
+      }
+      $content[$panel_name] = $this->render_region($panel_name, $panel_panes);
+    }
+
+    // Prevent notices by making sure that all panels at least have an entry:
+    // TODO refactor to make this unnecessary (optimization)
+    $panels = panels_get_panels($this->plugins['layout'], $this->display);
+    foreach ($panels as $id => $panel) {
+      if (!isset($content[$id])) {
+        $content[$id] = NULL;
+      }
+    }
+
+    return $content;
+  }
+
+  /**
+   * Render a pane using the appropriate style.
+   *
+   * @param object $pane
+   *   The $pane information from the display
+   */
+  function render_pane($pane) {
+    $content = $this->render_pane_content($pane);
+
+    // Pass long the css_id that is usually available.
+    if (!empty($pane->css['css_id'])) {
+      $content->css_id = $pane->css['css_id'];
+    }
+
+    // Pass long the css_class that is usually available.
+    if (!empty($pane->css['css_class'])) {
+      $content->css_class = $pane->css['css_class'];
+    }
+  }
+
+  /**
+   * Render the contents of a single pane.
+   *
+   * This method retrieves pane content and produces a ready-to-render content
+   * object. It also manages pane-specific caching.
+   *
+   * @param stdClass $pane
+   *    A Panels pane object, as loaded from the database.
+   */
+  function render_pane_content($pane) { // TODO remove this method by collapsing it into $this->render_panes()
+    ctools_include('context');
+    // TODO finally safe to remove this check?
+    if (!is_array($this->display->context)) {
+      $this->display->context = array();
+    }
+
+    // TODO this should be moved up to fully cache styled output, rather than
+    // just rendered output; no reason to reevaluaate all that every time
+    $content = FALSE;
+    $caching = !empty($pane->cache['method']) ? TRUE : FALSE;
+    if ($caching && ($cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context, $pane))) {
+      $content = $cache->content;
+    }
+    else {
+      $content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, array(), $this->display->args, $this->display->context);
+      foreach (module_implements('panels_pane_content_alter') as $module) {
+        $function = $module . '_panels_pane_content_alter';
+        $function($content, $pane, $this->display->args, $this->display->context);
+      }
+      if ($caching) {
+        $cache = new panels_cache_object();
+        $cache->set_content($content);
+        panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context, $pane);
+        $content = $cache->content;
+      }
+    }
+
+    // Pass long the css_id that is usually available.
+    if (!empty($pane->css['css_id'])) {
+      $content->css_id = $pane->css['css_id'];
+    }
+
+    // Pass long the css_class that is usually available.
+    if (!empty($pane->css['css_class'])) {
+      $content->css_class = $pane->css['css_class'];
+    }
+
+    return $content;
+  }
+
+  /**
+   * Render a single panel region.
+   *
+   * Primarily just a passthrough to the panel region rendering callback
+   * specified by the style plugin that is attached to the current panel region.
+   *
+   * @param $region_name
+   *   The ID of the panel region being rendered
+   * @param $panes
+   *   An array of panes that are assigned to the panel that's being rendered.
+   *
+   * @return
+   *   The rendered HTML for the passed-in panel region.
+   */
+  function render_region($region_name, $panes) {
+    list($style, $style_settings) = panels_get_panel_style_and_settings($this->display->panel_settings, $region_name);
+
+    // Retrieve the pid (can be a panel page id, a mini panel id, etc.), this
+    // might be used (or even necessary) for some panel display styles.
+    // TODO: Got to fix this to use panel page name instead of pid, since pid is
+    // no longer guaranteed. This needs an API to be able to set the final id.
+    $owner_id = 0;
+    if (isset($this->display->owner) && is_object($this->display->owner) && isset($this->display->owner->id)) {
+      $owner_id = $this->display->owner->id;
+    }
+
+    return theme($style['render panel'], $this->display, $owner_id, $panes, $style_settings, $region_name);
+  }
+}
diff --git plugins/display_renderers/panels_renderer_legacy.inc plugins/display_renderers/panels_renderer_legacy.inc
new file mode 100644
index 0000000..2082105
--- /dev/null
+++ plugins/display_renderers/panels_renderer_legacy.inc
@@ -0,0 +1,7 @@
+<?php
+
+$plugin = array(
+  'handler' => array(
+    'class' => 'panels_renderer_legacy',
+  ),
+);
\ No newline at end of file
diff --git plugins/display_renderers/panels_renderer_standard.class.php plugins/display_renderers/panels_renderer_standard.class.php
new file mode 100644
index 0000000..4231ac5
--- /dev/null
+++ plugins/display_renderers/panels_renderer_standard.class.php
@@ -0,0 +1,372 @@
+<?php
+
+/**
+ * Standard render pipeline for a panels display.
+ *
+ */
+class panels_renderer_standard {
+  var $display;
+  var $plugins = array();
+
+  /**
+   * A multilevel array of rendered data. The first level of the array
+   * indicates the type of rendered data, typically with up to three keys:
+   * 'layout', 'regions', and 'panes'. The relevant rendered data is stored as
+   * the value for each of these keys as it is generated:
+   *  - 'panes' are an associative array of rendered output, keyed on pane id.
+   *  - 'regions' are an associative array of rendered output, keyed on region
+   *    name.
+   *  - 'layout' is the whole of the rendered output.
+   *
+   * @var array
+   */
+  var $rendered = array();
+
+  /**
+   * A multilevel array of data prepared for rendering. The first level of the
+   * array indicates the type of prepared data. The standard renderer populates
+   * and uses two top-level keys, 'panes' and 'regions':
+   *  - 'panes' are an associative array of pane objects to be rendered, keyed
+   *    on pane id and sorted into proper rendering order.
+   *  - 'regions' are an associative array of regions, keyed on region name,
+   *    each of which is itself an indexed array of pane ids in the order in
+   *    which those panes appear in that region.
+   *
+   * @var array
+   */
+  var $prepared = array();
+
+  /**
+   * Boolean state variable, indicating whether or not the prepare() method has
+   * been run.
+   *
+   * This state is checked in panels_renderer_standard::render_layouts() to
+   * determine whether the prepare method should be automatically triggered.
+   * @var bool
+   */
+  var $prep_run = FALSE;
+
+  function build(&$display, $layout) {
+    $this->display = &$display;
+    $this->plugins['layout'] = $layout;
+    if (!isset($layout['panels'])) {
+      $this->plugins['layout']['panels'] = panels_get_panels($layout, $display);
+    }
+  }
+
+  /**
+   * Prepare the attached display for rendering.
+   *
+   * This is the outermost prepare method. It calls several sub-methods as part
+   * of the overall preparation process. This compartmentalization is intended
+   * to ease the task of modifying renderer behavior in child classes.
+   */
+  function prepare() {
+    $this->prepare_panes($this->display->content);
+    $this->prepare_regions($this->display->panels, $this->display->panel_settings);
+    $this->prepared['empty regions'] = array_diff(array_keys($this->plugins['layout']['panels']), array_keys($this->prepared['regions']));
+    $this->prep_run = TRUE;
+  }
+
+  /**
+   * Prepare the list of panes to be rendered, accounting for visibility/access
+   * settings and rendering order.
+   *
+   * This method represents the standard approach for determining the list of
+   * panes to be rendered that is compatible with all parts of the Panels
+   * architecture. It first applies visibility & access checks, then sorts panes
+   * into their proper rendering order, and returns the result as an array.
+   *
+   * Inheriting classes should override this method if that renderer needs to
+   * regularly make additions to the set of panes that will be rendered.
+   *
+   * @param array $panes
+   *  An associative array of panes, keyed on pane id.
+   * @return array
+   *  An associative array of panes to be rendered, keyed on pane id and sorted
+   *  into proper rendering order.
+   */
+  function prepare_panes($panes) {
+    ctools_include('content');
+    // Use local variables as writing to them is very slightly faster
+    $normal = $last = array();
+
+    // Prepare the list of panes to be rendered
+    foreach ($panes as $pid => $pane) {
+      // TODO remove in 7.x and ensure the upgrade path weeds out any stragglers; it's been long enough
+      $pane->shown = !empty($pane->shown); // guarantee this field exists.
+      // If this pane is not visible to the user, skip out and do the next one
+      if (!$pane->shown || !panels_pane_access($pane, $this->display)) {
+        continue;
+      }
+
+      $ct_plugin_def = ctools_get_content_type($pane->type);
+
+      // If this pane wants to render last, add it to the $last array. We allow
+      // this because some panes need to be rendered after other panes,
+      // primarily so they can do things like the leftovers of forms.
+      if (!empty($ct_plugin_def['render last'])) {
+        $last[$pid] = $pane;
+      }
+      // Otherwise, render it in the normal order.
+      else {
+        $normal[$pid] = $pane;
+      }
+    }
+    $this->prepared['panes'] = $normal + $last;
+    return $this->prepared['panes'];
+  }
+
+  /**
+   * @param array $regions
+   * @param array $settings
+   */
+  function prepare_regions($region_list, $settings) {
+    // Initialize defaults to be used for regions without their own explicit
+    // settings. Use display settings if they exist, else hardcoded defaults
+    $default = array(
+      'style' => !empty($settings['style']) ? $settings['style'] : panels_get_style('default'),
+      'style settings' => isset($settings['style_settings']['default']) ? $settings['style_settings']['default'] : array(),
+    );
+
+    $regions = array();
+    if (empty($settings)) {
+      // No display/panel region settings exist, init all with the defaults.
+      foreach ($region_list as $region_id => $panes) {
+        $regions[$region_id] = $default;
+        $regions[$region_id]['pids'] = $panes;
+      }
+    }
+    else {
+      // Some settings exist; iterate through each region and set individually
+      foreach ($region_list as $region_id => $panes) {
+        if (empty($settings[$region_id]['style']) || $settings[$region_id]['style'] == -1) {
+          $regions[$region_id] = $default;
+        }
+        else {
+          $regions[$region_id]['style'] = panels_get_style($settings[$region_id]['style']);
+          $regions[$region_id]['style settings'] = isset($settings['style_settings'][$region_id]) ? $settings['style_settings'][$region_id] : array();
+        }
+        $regions[$region_id]['pids'] = $panes;
+      }
+    }
+    $this->prepared['regions'] = $regions;
+    return $this->prepared['regions'];
+  }
+
+  /**
+   * Builds inner content, then hands off to layout-specified theme function for
+   * final render step.
+   *
+   * This is the outermost method in the Panels render pipeline. It calls the
+   * inner methods, which return a content array, which is in turn passed to the
+   * theme function specified in the layout plugin.
+   *
+   * @return string
+   *  Themed & rendered HTML output.
+   */
+  function render() {
+    $this->add_meta();
+    if (empty($this->display->cache['method'])) {
+      return $this->render_layout();
+    }
+    else {
+      $cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context);
+      if ($cache === FALSE) {
+        $cache = new panels_cache_object();
+        $cache->set_content($this->render_layout());
+        panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context);
+      }
+      return $cache->content;
+    }
+  }
+
+  function render_layout() {
+    if (empty($this->prep_run)) {
+      $this->prepare();
+    }
+    $this->render_panes();
+    $this->render_regions();
+    $this->rendered['layout'] = theme($this->plugins['layout']['theme'], check_plain($this->display->css_id), $this->rendered['regions'], $this->display->layout_settings, $this->display);
+    return $this->rendered['layout'];
+  }
+
+  /**
+   * Attach 'out-of-band' page metadata (e.g., CSS and JS).
+   *
+   * This must be done before render, because panels-within-panels must have
+   * their CSS added in the right order: inner content before outer content.
+   */
+  function add_meta() {
+    if (!empty($this->plugins['layout']['css'])) {
+      if (file_exists(path_to_theme() . '/' . $this->plugins['layout']['css'])) {
+        drupal_add_css(path_to_theme() . '/' . $this->plugins['layout']['css']);
+      }
+      else {
+        drupal_add_css($this->plugins['layout']['path'] . '/' . $this->plugins['layout']['css']);
+      }
+    }
+  }
+
+  function render_panes() {
+    ctools_include('content');
+
+    // First, render all the panes into little boxes.
+    $this->rendered['panes'] = array();
+    foreach ($this->prepared['panes'] as $pid => $pane) {
+      $this->rendered['panes'][$pid] = $this->render_pane($pane);
+    }
+  }
+
+  /**
+   * Render a pane using the appropriate style.
+   *
+   * @param object $pane
+   *  The $pane information from the display
+   */
+  function render_pane($pane) {
+    $content = $this->render_pane_content($pane);
+    if ($this->display->hide_title == PANELS_TITLE_PANE && !empty($this->display->title_pane) && $this->display->title_pane == $pane->pid) {
+
+      // If the user selected to override the title with nothing, and selected
+      // this as the title pane, assume the user actually wanted the original
+      // title to bubble up to the top but not actually be used on the pane.
+      if (empty($content->title) && !empty($content->original_title)) {
+        $this->display->stored_pane_title = $content->original_title;
+      }
+      else {
+        $this->display->stored_pane_title = !empty($content->title) ? $content->title : '';
+      }
+    }
+
+    if (!empty($pane->style['style'])) {
+      $style = panels_get_style($pane->style['style']);
+
+      if (isset($style) && isset($style['render pane'])) {
+        $output = theme($style['render pane'], $content, $pane, $this->display);
+
+        // This could be null if no theme function existed.
+        if (isset($output)) {
+          return $output;
+        }
+      }
+    }
+
+    if (!empty($content->content)) {
+      // fallback
+      return theme('panels_pane', $content, $pane, $this->display);
+    }
+  }
+
+  /**
+   * Render the contents of a single pane.
+   *
+   * This method retrieves pane content and produces a ready-to-render content
+   * object. It also manages pane-specific caching.
+   *
+   * @param stdClass $pane
+   *  A Panels pane object, as loaded from the database.
+   */
+  function render_pane_content($pane) { // TODO remove this method by collapsing it into $this->render_panes()
+    ctools_include('context');
+    // TODO finally safe to remove this check?
+    if (!is_array($this->display->context)) {
+      watchdog('panels', 'renderer:render_pane_content() hit with a non-array for the context', $this->display, WATCHDOG_DEBUG);
+      $this->display->context = array();
+    }
+
+    $content = FALSE;
+    $caching = !empty($pane->cache['method']) ? TRUE : FALSE;
+    if ($caching && ($cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context, $pane))) {
+      $content = $cache->content;
+    }
+    else {
+      $content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, array(), $this->display->args, $this->display->context);
+      foreach (module_implements('panels_pane_content_alter') as $module) {
+        $function = $module . '_panels_pane_content_alter';
+        $function($content, $pane, $this->display->args, $this->display->context);
+      }
+      if ($caching) {
+        $cache = new panels_cache_object();
+        $cache->set_content($content);
+        panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context, $pane);
+        $content = $cache->content;
+      }
+    }
+
+    // Pass long the css_id that is usually available.
+    if (!empty($pane->css['css_id'])) {
+      $content->css_id = $pane->css['css_id'];
+    }
+
+    // Pass long the css_class that is usually available.
+    if (!empty($pane->css['css_class'])) {
+      $content->css_class = $pane->css['css_class'];
+    }
+
+    return $content;
+  }
+
+  /**
+   * Render all panes in the attached display into their panel regions, then
+   * render those regions.
+   *
+   * @return array
+   *   An array of rendered panel regions, keyed on the region name.
+   */
+  function render_regions() {
+    // Initialize the regions array with keys for each region and NULL values
+    // for each; this prevents warnings on empty regions later
+    $this->rendered['regions'] = array();
+    foreach (array_keys($this->plugins['layout']['panels']) as $region_id) {
+      $this->rendered['regions'][$region_id] = NULL;
+    }
+
+    // Loop through all panel regions, put all panes that belong to the current
+    // region in an array, then render the region. Primarily this ensures that
+    // the panes are arranged in the proper order.
+    $content = array();
+    foreach ($this->prepared['regions'] as $region_id => $conf) {
+      $region_panes = array();
+      foreach ($conf['pids'] as $pid) {
+        // Only include panes for region rendering if they had some output.
+        if (!empty($this->rendered['panes'][$pid])) {
+          $region_panes[$pid] = $this->rendered['panes'][$pid];
+        }
+      }
+      $this->rendered['regions'][$region_id] = $this->render_region($region_id, $region_panes);
+    }
+
+    return $this->rendered['regions'];
+  }
+
+  /**
+   * Render a single panel region.
+   *
+   * Primarily just a passthrough to the panel region rendering callback
+   * specified by the style plugin that is attached to the current panel region.
+   *
+   * @param $region_id
+   *   The ID of the panel region being rendered
+   * @param $panes
+   *   An array of panes that are assigned to the panel that's being rendered.
+   *
+   * @return string
+   *   The rendered, HTML string output of the passed-in panel region.
+   */
+  function render_region($region_id, $panes) {
+    $style = $this->prepared['regions'][$region_id]['style'];
+    $style_settings = $this->prepared['regions'][$region_id]['style settings'];
+
+    // Retrieve the pid (can be a panel page id, a mini panel id, etc.), this
+    // might be used (or even necessary) for some panel display styles.
+    // TODO: Got to fix this to use panel page name instead of pid, since pid is
+    // no longer guaranteed. This needs an API to be able to set the final id.
+    $owner_id = 0;
+    if (isset($this->display->owner) && is_object($this->display->owner) && isset($this->display->owner->id)) {
+      $owner_id = $this->display->owner->id;
+    }
+
+    return theme($style['render panel'], $this->display, $owner_id, $panes, $style_settings, $region_id);
+  }
+}
diff --git plugins/display_renderers/panels_renderer_standard.inc plugins/display_renderers/panels_renderer_standard.inc
new file mode 100644
index 0000000..39b235d
--- /dev/null
+++ plugins/display_renderers/panels_renderer_standard.inc
@@ -0,0 +1,7 @@
+<?php
+
+$plugin = array(
+  'handler' => array(
+    'class' => 'panels_renderer_standard',
+  ),
+);
\ No newline at end of file
diff --git plugins/styles/corners/rounded_corners.inc plugins/styles/corners/rounded_corners.inc
index 0dd7db7..84f7fa0 100644
--- plugins/styles/corners/rounded_corners.inc
+++ plugins/styles/corners/rounded_corners.inc
@@ -35,19 +35,18 @@ function theme_panels_rounded_corners_style_render_panel($display, $panel_id, $p
   $where = empty($settings['corner_location']) ? 'pane' : $settings['corner_location'];
 
   $print_separator = FALSE;
-  foreach ($panes as $pane_id => $pane) {
-    $text = panels_render_pane($pane, $display->content[$pane_id], $display);
-    if ($text) {
+  foreach ($panes as $pane_id => $pane_output) {
+    if ($pane_output) {
       // Add the separator if we've already displayed a pane.
       if ($print_separator) {
         $output .= '<div class="panel-separator">&nbsp;</div>';
       }
 
       if ($where == 'pane') {
-        $output .= theme('panels_rounded_corners_box', $text);
+        $output .= theme('panels_rounded_corners_box', $pane_output);
       }
       else {
-        $output .= $text;
+        $output .= $pane_output;
         $print_separator = TRUE;
       }
     }
diff --git plugins/styles/default.inc plugins/styles/default.inc
index 2020676..b4665ce 100644
--- plugins/styles/default.inc
+++ plugins/styles/default.inc
@@ -22,18 +22,17 @@ function theme_panels_default_style_render_panel($display, $panel_id, $panes, $s
   $output = '';
 
   $print_separator = FALSE;
-  foreach ($panes as $pane_id => $content) {
+  foreach ($panes as $pane_id => $pane_output) {
     // Add the separator if we've already displayed a pane.
     if ($print_separator) {
       $output .= '<div class="panel-separator"></div>';
     }
-    $output .= $text = panels_render_pane($content, $display->content[$pane_id], $display);
 
+    $output .= $pane_output;
     // If we displayed a pane, this will become true; if not, it will become
     // false.
-    $print_separator = (bool) $text;
+    $print_separator = (bool) $pane_output;
   }
 
   return $output;
 }
-
diff --git plugins/task_handlers/panel_context.inc plugins/task_handlers/panel_context.inc
index 7e215fb..946c8c5 100644
--- plugins/task_handlers/panel_context.inc
+++ plugins/task_handlers/panel_context.inc
@@ -185,6 +185,7 @@ $plugin = array(
   'default conf' => array(
     'title' => t('Panel'),
     'no_blocks' => FALSE,
+    'use_ipe' => FALSE,
     'css_id' => '',
     'css' => '',
     'contexts' => array(),
@@ -267,12 +268,20 @@ function panels_panel_context_render($handler, $base_contexts, $args, $test = TR
   // With an argument, this actually sets the display.
   panels_get_current_page_display($display);
 
+  $renderer = NULL; // NULL means the default renderer will be used
+
+  // TODO this is a temporary, hacky approach to ACLs for the IPE, and will be
+  // modified in a later release
+  if (module_exists('panels_ipe') && !empty($handler->conf['use_ipe']) && user_access('administer page manager')) {
+    $renderer = 'panels_renderer_ipe';
+  }
+
   $info = array(
-    'content' => panels_render_display($display),
+    'content' => panels_render_display($display, $renderer),
     'no_blocks' => !empty($handler->conf['no_blocks']),
   );
 
-  $info['title'] = panels_display_get_title($display);
+  $info['title'] = $display->get_title();
 
   return $info;
 }
@@ -694,6 +703,17 @@ function panels_panel_context_edit_settings(&$form, &$form_state) {
     '#description' => t('Check this to have the page disable all regions displayed in the theme. Note that some themes support this setting better than others. If in doubt, try with stock themes to see.'),
   );
 
+  if (module_exists('panels_ipe')) { // Temporary approach till we get something better
+    // Initialize the value if needed...hacky hacky
+    $conf['use_ipe'] = isset($conf['use_ipe']) ? $conf['use_ipe'] : FALSE;
+    $form['conf']['use_ipe'] = array(
+      '#type' => 'checkbox',
+      '#default_value' => $conf['use_ipe'],
+      '#title' => t('Use In-Place Editor'),
+      '#description' => t('Use the new Panels In-Place Editor on this panel for users with global panel-editing permissions (Warning: EXPERIMENTAL!!)'),
+    );
+  }
+
   $form['conf']['css_id'] = array(
     '#type' => 'textfield',
     '#size' => 35,
@@ -715,6 +735,7 @@ function panels_panel_context_edit_settings(&$form, &$form_state) {
  */
 function panels_panel_context_edit_settings_submit(&$form, &$form_state) {
   $form_state['handler']->conf['no_blocks'] = $form_state['values']['no_blocks'];
+  $form_state['handler']->conf['use_ipe'] = $form_state['values']['use_ipe'];
   $form_state['handler']->conf['css_id'] = $form_state['values']['css_id'];
   $form_state['handler']->conf['css'] = $form_state['values']['css'];
   $form_state['handler']->conf['title'] = $form_state['values']['title'];
