+The API for expanding the panels module comes in two pieces. First there is the \r
+layout API, which adds to the list of layouts you need. Second is the content\r
+types API, which lets modules supply content to the panels. Natively, panels\r
+module supports the content types of 'block', which just renders the output\r
+of a block, 'node' which simply renders a node_view, 'custom' which allows the\r
+user to enter custom content with filtering, and finally 'views' because I\r
+wrote them both.\r
+\r
+Where to put your code:\r
+=======================\r
+\r
+With both types, there are two ways to implement a new type. First, you can\r
+implement the hook in your module and provide the necessary data. Or you\r
+can create a .inc file in the right format, and drop it into the proper\r
+directory in the panels module. Both are very similar, and only requires\r
+a minor naming adjustment.\r
+\r
+When using the .inc file, in place of 'hook' in the various hooks, use\r
+panels_FILENAME. \r
+\r
+Creating a new Layout Type:\r
+===========================\r
+\r
+A layout consists of 4 things:\r
+\r
+1) A bit of HTML in a theme function. I use heredoc notation to make it\r
+ extra easy to convert these to .tpl.inc files in case they are to\r
+ be overridden in php template.\r
+2) A bit of CSS to describe how the layout should be, well, laid out.\r
+3) An icon that is 50x75 which gives the user a visual indication of\r
+ what the layout looks like.\r
+4) An implementation of hook_panels_layouts() to tell panels the necessary \r
+ information.\r
+\r
+hook_panels_layouts returns an array with the following information:\r
+\r
+'module' => The module name providing this. This is necessary because it\r
+ uses drupal_get_path('module', $module) to get the proper\r
+ path for included CSS.\r
+'title' => The title of the layout presented to the user. Use t().\r
+'icon' => The filename of the icon to use when listing avialable layouts.\r
+'theme' => The theme function that contains the HTML, without the theme_ \r
+ part.\r
+'css' => The CSS file.\r
+'content areas' => an array in the form of 'name' => t('Title') of content\r
+ areas supported by the layout. For example, the simple\r
+ 2 column layout uses array('left' => t('Left side'), \r
+ 'right' => t('Right side')); -- the name is the internal\r
+ identifier. Your theme function will see it as \r
+ $content['name'] (so twocol gets $content['left'] and\r
+ $content['right']).\r
+\r
+\r
+Creating a new Content Type:\r
+============================\r
+\r
+Content types require 1 hook and two callbacks. The hook defines what content \r
+types are available, the first callback displays the content in a dashboard, \r
+and the other callback does all of the administrative functions.\r
+\r
+hook_panels_content_types returns an array with the following information:\r
+'callback' => The function to display the content.\r
+'admin' => The function to administer the content.\r
+\r
+The callback function receives one argument: The $configuration array, as\r
+defined by the administrative callback.\r
+\r
+The administrative callback recieves 3 arguments:\r
+\r
+$op -- the operation to perform. See below.\r
+&$arg1 -- The first argument should be a reference and its meaning varies \r
+ based on the op.\r
+$arg2 -- The second argument is optional, not a reference, and should \r
+ default to NULL.\r
+\r
+Administrative operations:\r
+\r
+'list': $arg1 is the configuration array.\r
+ This op is called when panels lists what content is in a content\r
+ area. It generally returns something similar to this:\r
+ return '<strong>Views</strong>: ' . $view->name . ' (' . $view->description . ')';\r
+\r
+'add button': arguments not used here.\r
+ This op is called to display the 'add content type' button; it can also\r
+ display additional information (such as the list of blocks or the\r
+ autocomplete to select a node).\r
+\r
+ The actual button should look something like this:\r
+ $form['submit'] = array(\r
+ '#type' => 'button',\r
+ '#value' => t('Add view'),\r
+ );\r
+\r
+ And it's a good idea to do this, but it's not required:\r
+\r
+ $form['#prefix'] = '<div class="container-inline">';\r
+ $form['#suffix'] = '</div>';\r
+\r
+'add': $arg1 == the $configuration array\r
+ This op is called to see if your add button has been clicked. It *must*\r
+ start off by checking to see if this is true:\r
+\r
+ if ($_POST['op'] != t('Add view')) {\r
+ return;\r
+ }\r
+\r
+ If it is true, it should process that information and return a $configuration\r
+ array populated from whatever other form items were presented in 'add button'\r
+ and whatever defaults make sense.\r
+\r
+'edit': $arg1 == the $configuration array\r
+ This op is called to provide an edit form for a content type. It *must*\r
+ ensure *all* information from the conf array is available, even if it\r
+ is just hidden; panels has no way to remember this data between form\r
+ clicks, so any data not put here will be lost. No buttons need to be\r
+ added to the form.\r
+\r
+'validate': $arg1 == $form_values, $arg2 == $form\r
+ Called to validate the 'edit' form above.\r
+\r
+'save': $arg1 == $form_values\r
+ Called to convert a $form_values back into a $configuration array. All\r
+ of the default types just send $form_values back as $configuration,\r
+ but if you need to do some kind of transformation, this is where it\r
+ happens.\r