#806874 by ximo: Allow panes to specify "render first" as well as "render last".
[project/panels.git] / plugins / layouts / flexible / flexible.inc
CommitLineData
26e7b0ef
SB
1<?php
2// $Id$
3
4/**
5 * Implementation of hook_panels_layouts()
6 */
7// Plugin definition
8$plugin = array(
9 'title' => t('Flexible'),
946024d2 10 'category' => t('Builders'),
26e7b0ef
SB
11 'icon' => 'flexible.png',
12 'theme' => 'panels_flexible',
13 'admin theme' => 'panels_flexible_admin',
14 'css' => 'flexible.css',
15 'admin css' => 'flexible-admin.css',
16 'settings form' => 'panels_flexible_settings_form',
17 'settings submit' => 'panels_flexible_settings_submit',
18 'settings validate' => 'panels_flexible_settings_validate',
19 'panels function' => 'panels_flexible_panels',
20 'hook menu' => 'panels_flexible_menu',
946024d2
SB
21
22 // Reuisable layout Builder specific directives
23 'builder' => TRUE,
24 'builder tab title' => 'Add flexible layout', // menu so translated elsewhere
25
26 'get child' => 'panels_flexible_get_sublayout',
27 'get children' => 'panels_flexible_get_sublayouts',
28
29 // Define ajax callbacks
30 'ajax' => array(
31 'settings' => 'panels_ajax_flexible_edit_settings',
32 'add' => 'panels_ajax_flexible_edit_add',
33 'remove' => 'panels_ajax_flexible_edit_remove',
34 'resize' => 'panels_ajax_flexible_edit_resize',
35 'reuse' => 'panels_ajax_flexible_edit_reuse',
36 ),
26e7b0ef
SB
37);
38
39/**
946024d2
SB
40 * Merge the main flexible plugin with a layout to create a sub plugin.
41 *
42 * This is used for both panels_flexible_get_sublayout and
43 * panels_flexible_get_sublayouts.
26e7b0ef 44 */
946024d2
SB
45function panels_flexible_merge_plugin($plugin, $layout) {
46 $plugin['name'] = 'flexible:' . $layout->name;
47 $plugin['category'] = !empty($layout->category) ? check_plain($layout->category) : t('Miscellaneous');
48 $plugin['title'] = check_plain($layout->admin_title);
49 $plugin['description'] = check_plain($layout->admin_description);
50 $plugin['layout'] = $layout;
51 $plugin['builder'] = FALSE;
52 $plugin['builder tab title'] = NULL;
53 return $plugin;
54}
55
56/**
57 * Callback to provide a single stored flexible layout.
58 */
59function panels_flexible_get_sublayout($plugin, $layout_name, $sublayout_name) {
60 // Do not worry about caching; Panels is handling that for us.
61 ctools_include('export');
62 $item = ctools_export_crud_load('panels_layout', $sublayout_name);
63 if ($item) {
64 return panels_flexible_merge_plugin($plugin, $item);
65 }
66}
67
68/**
69 * Callback to provide all stored flexible layouts.
70 */
71function panels_flexible_get_sublayouts($plugin, $layout_name) {
72 $layouts[$layout_name] = $plugin;
73 ctools_include('export');
74 $items = ctools_export_load_object('panels_layout', 'conditions', array('plugin' => 'flexible'));
75 foreach ($items as $name => $item) {
76 $layouts['flexible:' . $name] = panels_flexible_merge_plugin($plugin, $item);
77 }
26e7b0ef 78
946024d2 79 return $layouts;
26e7b0ef
SB
80}
81
82/**
83 * Convert settings from old style to new, or provide defaults for
84 * empty settings.
85 * @param <type> $settings
86 */
946024d2
SB
87function panels_flexible_convert_settings(&$settings, &$layout) {
88 // This indicates that this is a layout that they used the checkbox
89 // on. The layout is still 'flexible' but it's actually pointing
90 // to another stored one and we have to load it.
91 if (!empty($settings['layout'])) {
92 $layout = panels_get_layout('flexible:' . $settings['layout']);
93 }
94
95 if (!empty($layout['layout'])) {
96 $settings = $layout['layout']->settings;
97 return $settings;
98 }
99
26e7b0ef
SB
100 if (empty($settings)) {
101 // set up a default
102 $settings = array(
103 'items' => array(
104 // The 'canvas' is a special row that does not get rendered
105 // normally, but is used to contain the columns.
106 'canvas' => array(
107 'type' => 'row',
108 'contains' => 'column',
109 'children' => array('main'),
110 'parent' => NULL,
111 ),
112 'main' => array(
113 'type' => 'column',
114 'width' => 100,
115 'width_type' => '%',
116 'children' => array('main-row'),
117 'parent' => 'canvas',
118 ),
119 'main-row' => array(
120 'type' => 'row',
121 'contains' => 'region',
122 'children' => array('center'),
123 'parent' => 'main',
124 ),
125 'center' => array(
126 'type' => 'region',
127 'title' => t('Center'),
128 'width' => 100,
129 'width_type' => '%',
130 'parent' => 'main-row',
131 ),
132 ),
133 );
134 }
135 else if (!isset($settings['items'])) {
136 // Convert an old style flexible to a new style flexible.
137 $old = $settings;
138 $settings = array();
139 $settings['items']['canvas'] = array(
140 'type' => 'row',
141 'contains' => 'column',
142 'children' => array(),
143 'parent' => NULL,
144 );
145 // add the left sidebar column, row and region if it exists.
146 if (!empty($old['sidebars']['left'])) {
147 $settings['items']['canvas']['children'][] = 'sidebar-left';
148 $settings['items']['sidebar-left'] = array(
149 'type' => 'column',
150 'width' => $old['sidebars']['left_width'],
151 'width_type' => $old['sidebars']['width_type'],
152 'children' => array('sidebar-left-row'),
153 'parent' => 'canvas',
154 );
155 $settings['items']['sidebar-left-row'] = array(
156 'type' => 'row',
157 'contains' => 'region',
158 'children' => array('sidebar_left'),
159 'parent' => 'sidebar-left',
160 );
161 $settings['items']['sidebar_left'] = array(
162 'type' => 'region',
163 'title' => t('Left sidebar'),
164 'width' => 100,
165 'width_type' => '%',
166 'parent' => 'sidebar-left-row',
167 );
168 }
169
170 $settings['items']['canvas']['children'][] = 'main';
171
172 if (!empty($old['sidebars']['right'])) {
173 $settings['items']['canvas']['children'][] = 'sidebar-right';
174 $settings['items']['sidebar-right'] = array(
175 'type' => 'column',
176 'width' => $old['sidebars']['right_width'],
177 'width_type' => $old['sidebars']['width_type'],
178 'children' => array('sidebar-right-row'),
179 'parent' => 'canvas',
180 );
181 $settings['items']['sidebar-right-row'] = array(
182 'type' => 'row',
183 'contains' => 'region',
184 'children' => array('sidebar_right'),
185 'parent' => 'sidebar-right',
186 );
187 $settings['items']['sidebar_right'] = array(
188 'type' => 'region',
189 'title' => t('Right sidebar'),
190 'width' => 100,
191 'width_type' => '%',
192 'parent' => 'sidebar-right-row',
193 );
194 }
195
196 // Add the main column.
197 $settings['items']['main'] = array(
198 'type' => 'column',
199 'width' => 100,
200 'width_type' => '%',
201 'children' => array(),
202 'parent' => 'canvas',
203 );
204
205 // Add rows and regions.
206 for ($row = 1; $row <= intval($old['rows']); $row++) {
207 // Create entry for the row
208 $settings['items']["row_$row"] = array(
209 'type' => 'row',
210 'contains' => 'region',
211 'children' => array(),
212 'parent' => 'main',
213 );
214 // Add the row to the parent's children
215 $settings['items']['main']['children'][] = "row_$row";
216
217 for ($col = 1; $col <= intval($old["row_$row"]['columns']); $col++) {
218 // Create entry for the region
219 $settings['items']["row_${row}_$col"] = array(
220 'type' => 'region',
221 'width' => $old["row_$row"]["width_$col"],
222 'width_type' => '%',
223 'parent' => "row_$row",
224 );
225 // Add entry for the region to the row's children
226 $settings['items']["row_$row"]['children'][] = "row_${row}_$col";
227
228 // Apply the proper title to the region
229 if (!empty($old["row_$row"]['names'][$col - 1])) {
230 $settings['items']["row_${row}_$col"]['title'] = $old["row_$row"]['names'][$col - 1];
231 }
232 else {
233 $settings['items']["row_${row}_$col"]['title'] = t("Row @row, Column @col", array('@row' => $row, '@col' => $col));
234 }
235 }
236 }
237 }
238 else if (isset($settings['canvas'])) {
239 // Convert the old 'canvas' to the new canvas row.
240 $settings['items']['canvas'] = array(
241 'type' => 'row',
242 'contains' => 'column',
243 'children' => $settings['canvas'],
244 'parent' => NULL,
245 );
246 unset($settings['canvas']);
247 }
248}
249
250/**
251 * Define the actual list of columns and rows for this flexible panel.
252 */
946024d2 253function panels_flexible_panels($display, $settings, $layout) {
26e7b0ef 254 $items = array();
946024d2 255 panels_flexible_convert_settings($settings, $layout);
26e7b0ef
SB
256 foreach ($settings['items'] as $id => $item) {
257 if ($item['type'] == 'region') {
258 $items[$id] = $item['title'];
259 }
260 }
261
262 return $items;
263}
264
265/**
946024d2
SB
266 * Create a renderer object.
267 *
268 * The renderer object contains data that is passed around from function
269 * to function allowing us to render our CSS and HTML easily.
270 *
271 * @todo Convert the functions to methods and make this properly OO.
26e7b0ef 272 */
946024d2 273function panels_flexible_create_renderer($admin, $id, $content, $settings, &$display, $layout, $handler) {
26e7b0ef
SB
274 $renderer = new stdClass;
275 $renderer->settings = $settings;
276 $renderer->content = $content;
277 $renderer->css_id = $id;
946024d2
SB
278 $renderer->did = &$display->did;
279 if ($admin) {
280 // always scale in admin mode.
281 $renderer->scale_base = 99.0;
282 }
283 else {
284 $renderer->scale_base = !empty($settings['items']['canvas']['no_scale']) ? 100.0 : 99.0;
285 }
26e7b0ef 286 $renderer->id_str = $id ? 'id="' . $id . '"' : '';
946024d2
SB
287 $renderer->admin = $admin;
288 $renderer->handler = $handler;
289
290 // Set up basic classes for all of our components.
291 $renderer->name = !empty($layout['layout']) ? $layout['layout']->name : $display->did;
292 $renderer->base_class = $renderer->name;
293 $renderer->item_class['column'] = 'panels-flexible-column';
294 $renderer->item_class['row'] = 'panels-flexible-row';
295 $renderer->item_class['region'] = 'panels-flexible-region';
296 $renderer->base['canvas'] = 'panels-flexible-' . $renderer->base_class;
297
298 // Override these if selected from the UI and not in admin mode.
299 if (!$admin) {
300 if (!empty($settings['items']['canvas']['class'])) {
301 $renderer->base_class = $settings['items']['canvas']['class'];
302 $renderer->base['canvas'] = $renderer->base_class;
303 }
304 if (!empty($settings['items']['canvas']['column_class'])) {
305 $renderer->item_class['column'] = $settings['items']['canvas']['column_class'];
306 }
307 if (!empty($settings['items']['canvas']['row_class'])) {
308 $renderer->item_class['row'] = $settings['items']['canvas']['row_class'];
309 }
310 if (!empty($settings['items']['canvas']['region_class'])) {
311 $renderer->item_class['region'] = $settings['items']['canvas']['region_class'];
312 }
313 }
314
315 // Get the separation values out of the canvas settings.
316 $renderer->column_separation = !empty($settings['items']['canvas']['column_separation']) ? $settings['items']['canvas']['column_separation'] : '0.5em';
317
318 $renderer->region_separation = !empty($settings['items']['canvas']['region_separation']) ? $settings['items']['canvas']['region_separation'] : '0.5em';
319
320 $renderer->row_separation = !empty($settings['items']['canvas']['row_separation']) ? $settings['items']['canvas']['row_separation'] : '0.5em';
321
322 // Make some appended classes so it's easier to reference them.
323
324 $renderer->base['column'] = $renderer->item_class['column'] . '-' . $renderer->base_class;
325 $renderer->base['row'] = $renderer->item_class['row'] . '-' . $renderer->base_class;
326 $renderer->base['region'] = $renderer->item_class['region'] . '-' . $renderer->base_class;
327
328 if ($renderer->name != 'new') {
329 // Use v2 to guarantee all CSS gets regenerated to account for changes in
330 // how some divs will be rendered.
331 $renderer->css_cache_name = 'flexiblev2:' . $renderer->name;
332 if ($admin) {
333 ctools_include('css');
334 ctools_css_clear($renderer->css_cache_name);
335 }
336 }
337 return $renderer;
338}
339
340/**
341 * Draw the flexible layout.
342 */
343function theme_panels_flexible($id, $content, $settings, $display, $layout, $handler) {
344 panels_flexible_convert_settings($settings, $layout);
345
346 $renderer = panels_flexible_create_renderer(FALSE, $id, $content, $settings, $display, $layout, $handler);
26e7b0ef
SB
347
348 // CSS must be generated because it reports back left/middle/right
349 // positions.
350 $css = panels_flexible_render_css($renderer);
351
946024d2 352 if (!empty($renderer->css_cache_name) && empty($display->editing_layout)) {
26e7b0ef
SB
353 ctools_include('css');
354 // Generate an id based upon rows + columns:
946024d2 355 $filename = ctools_css_retrieve($renderer->css_cache_name);
26e7b0ef 356 if (!$filename) {
946024d2
SB
357 $filename = ctools_css_store($renderer->css_cache_name, $css, FALSE);
358 }
359
360 // Give the CSS to the renderer to put where it wants.
361 if ($handler) {
362 $handler->add_css($filename, 'module', 'all', FALSE);
363 }
364 else {
365 ctools_css_add_css($filename, 'module', 'all', FALSE);
26e7b0ef 366 }
26e7b0ef
SB
367 }
368 else {
369 // If the id is 'new' we can't reliably cache the CSS in the filesystem
370 // because the display does not truly exist, so we'll stick it in the
946024d2
SB
371 // head tag. We also do this if we've been told we're in the layout
372 // editor so that it always gets fresh CSS.
26e7b0ef
SB
373 drupal_set_html_head("<style type=\"text/css\">\n$css</style>\n");
374 }
375
946024d2
SB
376 // Also store the CSS on the display in case the live preview or something
377 // needs it
378 $display->add_css = $css;
379
380 $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clear-block\" $renderer->id_str>\n";
381 $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside\">\n";
26e7b0ef 382
946024d2 383 $output .= panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], $renderer->base['canvas']);
26e7b0ef
SB
384
385 // Wrap the whole thing up nice and snug
386 $output .= "</div>\n</div>\n";
387
388 return $output;
389}
390
391/**
392 * Draw the flexible layout.
393 */
946024d2
SB
394function theme_panels_flexible_admin($id, $content, $settings, $display, $layout, $handler) {
395 // We never draw stored flexible layouts in admin mode; they must be edited
396 // from the stored layout UI at that point.
397 if (!empty($layout['layout'])) {
398 return theme_panels_flexible($id, $content, $settings, $display, $layout, $handler);
399 }
26e7b0ef 400
946024d2
SB
401 panels_flexible_convert_settings($settings, $layout);
402 $renderer = panels_flexible_create_renderer(TRUE, $id, $content, $settings, $display, $layout, $handler);
26e7b0ef 403
26e7b0ef
SB
404 $css = panels_flexible_render_css($renderer);
405
406 // For the administrative view, add CSS directly to head.
407 drupal_set_html_head("<style type=\"text/css\">\n$css</style>\n");
408
946024d2
SB
409 if (empty($display->editing_layout)) {
410 $output = '<input type="submit" id="panels-flexible-toggle-layout" value ="' .
411 t('Show layout designer') . '">';
412 if (user_access('administer panels layouts')) {
413 $output .= '<input type="hidden" class="panels-flexible-reuse-layout-url" value="' . url($handler->get_url('layout', 'reuse'), array('absolute' => TRUE)) . '">';
414 $output .= '<input type="submit" id="panels-flexible-reuse-layout" class="ctools-use-modal" value ="' .
415 t('Reuse layout') . '">';
416 }
417 $output .= "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clear-block panel-flexible-admin panel-flexible-no-edit-layout\" $renderer->id_str>\n";
418 }
419 else {
420 $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clear-block panel-flexible-admin panel-flexible-edit-layout\" $renderer->id_str>\n";
421 }
422 $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside \">\n";
26e7b0ef 423
946024d2
SB
424 $content = panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], $renderer->base['row'] . '-canvas');
425 $output .= panels_flexible_render_item($renderer, $settings['items']['canvas'], $content, 'canvas', 0, 0, TRUE);
26e7b0ef
SB
426
427 // Wrap the whole thing up nice and snug
428 $output .= "</div>\n</div>\n";
429
946024d2
SB
430 drupal_add_js($layout['path'] . '/flexible-admin.js');
431 drupal_add_js(array('flexible' => array('resize' => url($handler->get_url('layout', 'resize'), array('absolute' => TRUE)))), 'setting');
26e7b0ef
SB
432 return $output;
433}
434
435/**
436 * Render a piece of a flexible layout.
437 */
438function panels_flexible_render_items($renderer, $list, $owner_id) {
439 $output = '';
440 $groups = array('left' => '', 'middle' => '', 'right' => '');
441 $max = count($list) - 1;
442 $prev = NULL;
443
444 foreach ($list as $position => $id) {
445 $item = $renderer->settings['items'][$id];
446 $location = isset($renderer->positions[$id]) ? $renderer->positions[$id] : 'middle';
447
448 if ($renderer->admin && $item['type'] != 'row' && $prev ) {
449 $groups[$location] .= panels_flexible_render_splitter($renderer, $prev, $id);
450 }
451
452 switch ($item['type']) {
453 case 'column':
946024d2
SB
454 $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['column'] . '-' . $id);
455 $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
26e7b0ef
SB
456 break;
457 case 'row':
946024d2
SB
458 $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['row'] . '-' . $id);
459 $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, TRUE);
26e7b0ef
SB
460 break;
461 case 'region':
462 $content = isset($renderer->content[$id]) ? $renderer->content[$id] : "&nbsp;";
946024d2 463 $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
26e7b0ef
SB
464 break;
465 }
466
467 // If all items are fixed then we have a special splitter on the right to
468 // control the overall width.
469 if (!empty($renderer->admin) && $max == $position && $location == 'left') {
470 $groups[$location] .= panels_flexible_render_splitter($renderer, $id, NULL);
471 }
472 $prev = $id;
473 }
474
946024d2
SB
475 $group_count = count(array_filter($groups));
476
477 // Render each group. We only render the group div if we're in admin mode
478 // or if there are multiple groups.
26e7b0ef
SB
479 foreach ($groups as $position => $content) {
480 if (!empty($content) || $renderer->admin) {
946024d2
SB
481 if ($group_count > 1 || $renderer->admin) {
482 $output .= '<div class="' . $owner_id . '-' . $position . '">' . $content . '</div>';
483 }
484 else {
485 $output .= $content;
486 }
26e7b0ef
SB
487 }
488 }
489
490 return $output;
491}
492
493/**
494 * Render a column in the flexible layout.
495 */
946024d2
SB
496function panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, $clear = FALSE) {
497
498 // If we are rendering a row and there is just one row, we don't need to
499 // render the row unless there is fixed_width content inside it.
500 if (empty($renderer->admin) && $item['type'] == 'row' && $max == 0) {
501 $fixed = FALSE;
502 foreach ($item['children'] as $id) {
503 if ($renderer->settings['items'][$id]['width_type'] != '%') {
504 $fixed = TRUE;
505 break;
506 }
507 }
508
509 if (!$fixed) {
510 return $content;
511 }
512 }
513
514 // If we are rendering a column and there is just one column, we don't
515 // need to render the column unless it has a fixed_width.
516 if (empty($renderer->admin) && $item['type'] == 'column' && $max == 0 && $item['width_type'] == '%') {
517 return $content;
518 }
519
520 $base = $renderer->item_class[$item['type']];
521 $output = '<div class="' . $base . ' ' . $renderer->base[$item['type']] . '-' . $id;
26e7b0ef
SB
522 if ($position == 0) {
523 $output .= ' ' . $base . '-first';
524 }
525 if ($position == $max) {
526 $output .= ' ' . $base . '-last';
527 }
528 if ($clear) {
529 $output .= ' clear-block';
530 }
531
532 if (isset($item['class'])) {
533 $output .= ' ' . check_plain($item['class']);
534 }
535
536 $output .= '">' . "\n";
537
538 if (!empty($renderer->admin)) {
539 $output .= panels_flexible_render_item_links($renderer, $id, $item);
540 }
541
946024d2 542 $output .= ' <div class="inside ' . $base . '-inside ' . $base . '-' . $renderer->base_class . '-' . $id . '-inside';
26e7b0ef
SB
543 if ($position == 0) {
544 $output .= ' ' . $base . '-inside-first';
545 }
546 if ($position == $max) {
547 $output .= ' ' . $base . '-inside-last';
548 }
549 if ($clear) {
550 $output .= ' clear-block';
551 }
552
553 $output .= "\">\n";
554 $output .= $content;
555 $output .= ' </div>' . "\n";
556 $output .= '</div>' . "\n";
557
558 return $output;
559}
560/**
561 * Render a splitter div to place between the $left and $right items.
562 *
563 * If the right ID is NULL that means there isn't actually a box to the
564 * right, but we need a splitter anyway. We'll mostly use info about the
565 * left, but pretend it's 'fluid' so that the javascript won't actually
566 * modify the right item.
567 */
568function panels_flexible_render_splitter($renderer, $left_id, $right_id) {
569 $left = $renderer->settings['items'][$left_id];
570
946024d2 571 $left_class = $renderer->base[$left['type']] . '-' . $left_id;
26e7b0ef
SB
572 if ($right_id) {
573 $right = $renderer->settings['items'][$right_id];
946024d2 574 $right_class = $renderer->base[$left['type']] . '-' . $right_id;
26e7b0ef
SB
575 }
576 else {
577 $right = $left;
578 $right_class = $left_class;
579 }
580
581 $output = '<div class="panels-flexible-splitter flexible-splitter-for-' . $left_class . '">';
582
583 // Name the left object
584 $output .= '<span class="panels-flexible-splitter-left">';
585 $output .= '.' . $left_class;
586 $output .= '</span>';
587
588 $output .= '<span class="panels-flexible-splitter-left-id">';
589 $output .= $left_id;
590 $output .= '</span>';
591
592 $output .= '<span class="panels-flexible-splitter-left-width ' . $left_class . '-width">';
593 $output .= $left['width'];
594 $output .= '</span>';
595
596 $output .= '<span class="panels-flexible-splitter-left-scale">';
597 $output .= isset($renderer->scale[$left_id]) ? $renderer->scale[$left_id] : 1;
598 $output .= '</span>';
599
600 $output .= '<span class="panels-flexible-splitter-left-width-type">';
601 $output .= $left['width_type'];
602 $output .= '</span>';
603
604 // Name the right object
605 $output .= '<span class="panels-flexible-splitter-right">';
606 $output .= '.' . $right_class;
607 $output .= '</span>';
608
609 $output .= '<span class="panels-flexible-splitter-right-id">';
610 $output .= $right_id;
611 $output .= '</span>';
612
613 $output .= '<span class="panels-flexible-splitter-right-width ' . $right_class . '-width">';
614 $output .= $right['width'];
615 $output .= '</span>';
616
617 $output .= '<span class="panels-flexible-splitter-right-scale">';
618 $output .= isset($renderer->scale[$right_id]) ? $renderer->scale[$right_id] : 1;
619 $output .= '</span>';
620
621 $output .= '<span class="panels-flexible-splitter-right-width-type">';
622 // If there is no right, make it fluid.
623 $output .= $right_id ? $right['width_type'] : '%';
624 $output .= '</span>';
625
626 $output .= '</div>';
627 return $output;
628}
629
630/**
631 * Render the dropdown links for an item.
632 */
633function panels_flexible_render_item_links($renderer, $id, $item) {
634 $links = array();
635 $remove = '';
636 $add = '';
637 if ($item['type'] == 'column') {
638 $title = t('Column');
639 $settings = t('Column settings');
640 if (empty($item['children'])) {
641 $remove = t('Remove column');
642 $add = t('Add row');
643 }
644 else {
645 $add = t('Add row to top');
646 $add2 = t('Add row to bottom');
647 }
648 }
649 else if ($item['type'] == 'row') {
650 if ($id == 'canvas') {
651 $title = t('Canvas');
946024d2 652 $settings = t('Canvas settings');
26e7b0ef
SB
653 }
654 else {
655 $title = t('Row');
656 $settings = t('Row settings');
657 }
658 if (empty($item['children'])) {
946024d2 659 if ($id != 'canvas') {
26e7b0ef
SB
660 $remove = t('Remove row');
661 }
662 $add = $item['contains'] == 'region' ? t('Add region') : t('Add column');
663 }
664 else {
665 $add = $item['contains'] == 'region' ? t('Add region to left') : t('Add column to left');
666 $add2 = $item['contains'] == 'region' ? t('Add region to right') : t('Add column to right');
667 }
668 }
669 else if ($item['type'] == 'region') {
670 $title = t('Region');
671 $settings = t('Region settings');
672 $remove = t('Remove region');
673 }
674
675 if (!empty($settings)) {
676 $links[] = array(
677 'title' => $settings,
946024d2 678 'href' => $renderer->handler->get_url('layout', 'settings', $id),
26e7b0ef
SB
679 'attributes' => array('class' => 'ctools-use-modal'),
680 );
681 }
682 if ($add) {
683 $links[] = array(
684 'title' => $add,
946024d2 685 'href' => $renderer->handler->get_url('layout', 'add', $id),
26e7b0ef
SB
686 'attributes' => array('class' => 'ctools-use-modal'),
687 );
688 }
689 if (isset($add2)) {
690 $links[] = array(
691 'title' => $add2,
946024d2 692 'href' => $renderer->handler->get_url('layout', 'add', $id, 'right'),
26e7b0ef
SB
693 'attributes' => array('class' => 'ctools-use-modal'),
694 );
695 }
696 if ($remove) {
697 $links[] = array(
698 'title' => $remove,
946024d2 699 'href' => $renderer->handler->get_url('layout', 'remove', $id),
26e7b0ef
SB
700 'attributes' => array('class' => 'ctools-use-ajax'),
701 );
702 }
703
704 return theme('ctools_dropdown', $title, $links, FALSE,
705 'flexible-layout-only flexible-links flexible-title flexible-links-' . $id);
706}
707/**
708 * Provide CSS for a flexible layout.
709 */
710function panels_flexible_render_css($renderer) {
946024d2
SB
711 if ($renderer->admin) {
712 $parent_class = '.' . $renderer->base['row'] . '-canvas';
713 }
714 else {
715 $parent_class = '.' . $renderer->base['canvas'];
716 }
717 return panels_flexible_render_css_group($renderer, $renderer->settings['items']['canvas']['children'], $parent_class, 'column', 'canvas');
26e7b0ef
SB
718}
719
720/**
721 * Render the CSS for a group of items to be displayed together.
722 *
723 * Columns and regions, when displayed as a group, need to cooperate in
724 * order to share margins and make sure that percent widths add up
725 * to the right total.
726 */
946024d2 727function panels_flexible_render_css_group($renderer, $list, $owner_id, $type, $id) {
26e7b0ef 728 $css = array();
946024d2
SB
729
730 // Start off with some generic CSS to properly pad regions
731 $css['.' . $renderer->item_class['region']] = array(
732 'padding' => '0',
733 );
734
735 $css['.' . $renderer->item_class['region'] . '-inside'] = array(
736 'padding-right' => $renderer->region_separation,
737 'padding-left' => $renderer->region_separation,
738 );
739
740 $css['.' . $renderer->item_class['region'] . '-inside-first'] = array(
741 'padding-left' => '0',
742 );
743
744 $css['.' . $renderer->item_class['region'] . '-inside-last'] = array(
745 'padding-right' => '0',
746 );
747
748 $css['.' . $renderer->item_class['column']] = array(
749 'padding' => '0',
750 );
751
752 $css['.' . $renderer->item_class['column'] . '-inside'] = array(
753 'padding-right' => $renderer->column_separation,
754 'padding-left' => $renderer->column_separation,
755 );
756
757 $css['.' . $renderer->item_class['column'] . '-inside-first'] = array(
758 'padding-left' => '0',
759 );
760
761 $css['.' . $renderer->item_class['column'] . '-inside-last'] = array(
762 'padding-right' => '0',
763 );
764
765 // And properly pad rows too
766 $css['.' . $renderer->item_class['row']] = array(
767 'padding' => '0 0 ' . $renderer->row_separation . ' 0',
768 'margin' => '0',
769 );
770
771 $css['.' . $renderer->item_class['row'] . '-last'] = array(
772 'padding-bottom' => '0',
773 );
774
775 panels_flexible_get_css_group($css, $renderer, $list, $owner_id, $type, $id);
26e7b0ef
SB
776
777 ctools_include('css');
778 return ctools_css_assemble($css);
779}
780
781/**
782 * Construct an array with all of the CSS properties for a group.
783 *
784 * This will parse down into children and produce all of the CSS needed if you
785 * start from the top.
786 */
946024d2 787function panels_flexible_get_css_group(&$css, $renderer, $list, $owner_id, $type, $item_id) {
26e7b0ef
SB
788 if ($type != 'row') {
789 // Go through our items and break up into right/center/right groups so we
790 // can figure out our offsets.
791
792 // right == any items on the right that are 'fixed'.
793 // middle == all fluid items.
794 // right == any items on the right that are 'fixed'.
795 $left = $middle = $right = array();
796 $left_total = $right_total = $middle_total = 0;
797 $current = 'left';
798 foreach ($list as $id) {
799 if ($renderer->settings['items'][$id]['width_type'] == 'px') {
800 // fixed
801 if ($current == 'left') {
802 $left[] = $id;
803 $renderer->positions[$id] = 'left';
804 $left_total += $renderer->settings['items'][$id]['width'];
805 }
806 else {
807 $current = 'right';
808 $right[] = $id;
809 $renderer->positions[$id] = 'right';
810 $right_total += $renderer->settings['items'][$id]['width'];
811 }
812 }
813 else {
814 // fluid
815 if ($current != 'right') {
816 $current = 'middle';
817 $middle[] = $id;
818 $renderer->positions[$id] = 'middle';
819 $middle_total += $renderer->settings['items'][$id]['width'];
820 }
821 // fall through: if current is 'right' and we ran into a 'fluid' then
822 // it gets *dropped* because that is invalid.
823 }
824 }
825
826 // Go through our right sides and create CSS.
827 foreach ($left as $id) {
946024d2 828 $class = "." . $renderer->base[$type] . "-$id";
26e7b0ef
SB
829 $css[$class] = array(
830 'position' => 'relative',
831 'float' => 'left',
832 'background-color' => 'transparent',
833 'width' => $renderer->settings['items'][$id]['width'] . "px",
834 );
835 }
836
837 // Do the same for right.
838 $right_pixels = 0;
839
840 foreach ($right as $id) {
946024d2 841 $class = "." . $renderer->base[$type] . "-$id";
26e7b0ef
SB
842 $css[$class] = array(
843 'float' => 'left',
844 'width' => $renderer->settings['items'][$id]['width'] . "px",
845 );
846 }
847
848 $max = count($middle) - 1;
849
850 if ($middle_total) {
851 // Because we love IE so much, auto scale everything to 99%. This
852 // means adding up the actual widths and then providing a multiplier
853 // to each so that the total is 99%.
946024d2 854 $scale = $renderer->scale_base / $middle_total;
26e7b0ef 855 foreach ($middle as $position => $id) {
946024d2 856 $class = "." . $renderer->base[$type] . "-$id";
26e7b0ef
SB
857 $css[$class] = array(
858 'float' => 'left',
946024d2 859 'width' => number_format($renderer->settings['items'][$id]['width'] * $scale, 4, '.', '') . "%",
26e7b0ef
SB
860 );
861
862 // Store this so we can use it later.
863 // @todo: Store the scale, not the new width, so .js can adjust
864 // bi-directionally.
865 $renderer->scale[$id] = $scale;
866 }
867 }
868
869 // If there is any total remaining, we need to offset the splitter
870 // by this much too.
871 if ($left_total) {
946024d2
SB
872 if ($renderer->admin || count($middle)) {
873 $css["$owner_id-middle"]['margin-left'] = $left_total . 'px';
874 // IE hack
875 $css["* html $owner_id-left"]['left'] = $left_total . "px";
876 }
877 else {
878 $css["$owner_id-inside"]['margin-left'] = '-' . $left_total . 'px';
879 // IE hack
880 $css["* html $owner_id-inside"]['left'] = $left_total . "px";
881 }
26e7b0ef
SB
882 }
883 // Add this even if it's 0 so we can handle removals.
946024d2 884 $css["$owner_id-inside"]['padding-left'] = '0px';
26e7b0ef 885 if ($right_total) {
946024d2 886 $css["$owner_id-middle"]['margin-right'] = $right_total . 'px';
26e7b0ef 887 }
946024d2
SB
888 $css["$owner_id-inside"]['padding-right'] = '0px';
889 }
890
891 // If the canvas has a fixed width set, and this is the canvas, fix the
892 // width.
893 if ($item_id == 'canvas') {
894 $item = $renderer->settings['items'][$item_id];
26e7b0ef 895
946024d2
SB
896 if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
897 $css['.' . $renderer->base['canvas']]['width'] = intval($item['fixed_width']) . 'px';
898 }
899 else {
900 $css['.' . $renderer->base['canvas']]['width'] = 'auto';
901 }
26e7b0ef
SB
902 }
903
904 // Go through each item and process children.
905 foreach ($list as $id) {
906 $item = $renderer->settings['items'][$id];
907 if (empty($item['children'])) {
908 continue;
909 }
910
911 if ($type == 'column') {
912 // Columns can only contain rows.
913 $child_type = 'row';
914 }
915 else {
916 $child_type = isset($item['contains']) ? $item['contains'] : 'region';
917 }
918
946024d2
SB
919 $class = "." . $renderer->base[$type] . "-$id";
920 panels_flexible_get_css_group($css, $renderer, $item['children'], $class, $child_type, $id);
26e7b0ef
SB
921 }
922}
923
924/**
925 * AJAX responder to edit flexible settings for an item.
946024d2
SB
926 *
927 * $handler object
928 * The display renderer handler object.
26e7b0ef 929 */
946024d2
SB
930function panels_ajax_flexible_edit_settings($handler, $id) {
931 $settings = &$handler->display->layout_settings;
932 panels_flexible_convert_settings($settings, $handler->plugins['layout']);
26e7b0ef
SB
933
934 if (empty($settings['items'][$id])) {
935 ctools_modal_render(t('Error'), t('Invalid item id.'));
936 }
937
938 $item = &$settings['items'][$id];
946024d2
SB
939 $siblings = array();
940
941 if ($id != 'canvas') {
942 $siblings = $settings['items'][$item['parent']]['children'];
943 }
944
26e7b0ef
SB
945
946 switch ($item['type']) {
947 case 'column':
948 $title = t('Configure column');
949 break;
950 case 'row':
946024d2
SB
951 if ($id == 'canvas') {
952 $title = t('Configure canvas');
953 }
954 else {
955 $title = t('Configure row');
956 }
26e7b0ef
SB
957 break;
958 case 'region':
959 $title = t('Configure region');
960 break;
961 }
962
963 $form_state = array(
946024d2 964 'display' => &$handler->display,
26e7b0ef
SB
965 'item' => &$item,
966 'id' => $id,
967 'siblings' => $siblings,
968 'settings' => &$settings,
969 'ajax' => TRUE,
970 'title' => $title,
971 'op' => 'edit',
972 );
973
974 $output = ctools_modal_form_wrapper('panels_flexible_config_item_form', $form_state);
975 if (empty($output)) {
976 // If the width type changed then other nearby items will have
977 // to have their widths adjusted.
946024d2
SB
978 panels_edit_cache_set($handler->cache);
979
980 $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
981 $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
982
26e7b0ef
SB
983 $output = array();
984 // If the item is a region, replace the title.
946024d2 985 $class = $renderer->base[$item['type']] . '-' . $id;
26e7b0ef
SB
986 if ($item['type'] == 'region') {
987 $output[] = ctools_ajax_command_replace(".$class h2.label",
988 '<h2 class="label">' . check_plain($item['title']) . '</h2>');
989 }
946024d2
SB
990
991 // Rerender our links in case something changed.
992 $output[] = ctools_ajax_command_replace('.flexible-links-' . $id,
993 panels_flexible_render_item_links($renderer, $id, $item));
994
995 // If editing the canvas, reset the CSS width
996 if ($id == 'canvas') {
997 // update canvas CSS.
998 $css = array(
999 '.' . $renderer->item_class['column'] . '-inside' => array(
1000 'padding-left' => $renderer->column_separation,
1001 'padding-right' => $renderer->column_separation,
1002 ),
1003 '.' . $renderer->item_class['region'] . '-inside' => array(
1004 'padding-left' => $renderer->region_separation,
1005 'padding-right' => $renderer->region_separation,
1006 ),
1007 '.' . $renderer->item_class['row'] => array(
1008 'padding-bottom' => $renderer->row_separation,
1009 ),
1010 );
1011 if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
1012 $css['.' . $renderer->base['canvas']] = array('width' => intval($item['fixed_width']) . 'px');
1013 }
1014 else {
1015 $css['.' . $renderer->base['canvas']] = array('width' => 'auto');
1016 }
1017 foreach ($css as $selector => $data) {
1018 $output[] = ctools_ajax_command_css($selector, $data);
1019 }
1020 }
26e7b0ef
SB
1021
1022 $output[] = ctools_modal_command_dismiss();
1023 }
1024
946024d2 1025 $handler->commands = $output;
26e7b0ef
SB
1026}
1027
1028/**
1029 * Configure a row, column or region on the flexible page.
1030 *
1031 * @param <type> $form_state
1032 * @return <type>
1033 */
1034function panels_flexible_config_item_form(&$form_state) {
1035 $display = &$form_state['display'];
1036 $item = &$form_state['item'];
1037 $siblings = &$form_state['siblings'];
1038 $settings = &$form_state['settings'];
1039 $id = &$form_state['id'];
1040
26e7b0ef
SB
1041 if ($item['type'] == 'region') {
1042 $form['title'] = array(
1043 '#title' => t('Region title'),
1044 '#type' => 'textfield',
1045 '#default_value' => $item['title'],
1046 '#required' => TRUE,
1047 );
946024d2 1048 }
26e7b0ef 1049
946024d2 1050 if ($id == 'canvas') {
26e7b0ef 1051 $form['class'] = array(
946024d2 1052 '#title' => t('Canvas class'),
26e7b0ef
SB
1053 '#type' => 'textfield',
1054 '#default_value' => isset($item['class']) ? $item['class'] : '',
946024d2 1055 '#description' => t('This class will the primary class for this layout. It will also be appended to all column, row and region_classes to ensure that layouts within layouts will not inherit CSS from each other. If left blank, the name of the layout or ID of the display will be used.'),
26e7b0ef 1056 );
26e7b0ef 1057
946024d2
SB
1058 $form['column_class'] = array(
1059 '#title' => t('Column class'),
1060 '#type' => 'textfield',
1061 '#default_value' => isset($item['column_class']) ? $item['column_class'] : '',
1062 '#description' => t('This class will be applied to all columns of the layout. If left blank this will be panels-flexible-column.'),
1063 );
26e7b0ef 1064
946024d2
SB
1065 $form['row_class'] = array(
1066 '#title' => t('Row class'),
1067 '#type' => 'textfield',
1068 '#default_value' => isset($item['row_class']) ? $item['row_class'] : '',
1069 '#description' => t('This class will be applied to all rows of the layout. If left blank this will be panels-flexible-row.'),
1070 );
1071
1072 $form['region_class'] = array(
1073 '#title' => t('Region class'),
1074 '#type' => 'textfield',
1075 '#default_value' => isset($item['region_class']) ? $item['region_class'] : '',
1076 '#description' => t('This class will be applied to all regions of the layout. If left blank this will be panels-flexible-region.'),
1077 );
1078
1079 $form['no_scale'] = array(
1080 '#type' => 'checkbox',
1081 '#title' => t('Scale fluid widths for IE6'),
1082 '#description' => t('IE6 does not do well with 100% widths. If checked, width will be scaled to 99% to compensate.'),
1083 '#default_value' => empty($item['no_scale']),
1084 );
1085
1086 $form['fixed_width'] = array(
1087 '#type' => 'textfield',
1088 '#title' => t('Fixed width'),
1089 '#description' => t('If a value is entered, the layout canvas will be fixed to the given pixel width.'),
1090 '#default_value' => isset($item['fixed_width']) ? $item['fixed_width'] : '',
1091 );
1092
1093 $form['column_separation'] = array(
1094 '#type' => 'textfield',
1095 '#title' => t('Column separation'),
1096 '#description' => t('How much padding to put on columns that are that are next to other columns. Note that this is put on both columns so the real amount is doubled.'),
1097 '#default_value' => isset($item['column_separation']) ? $item['column_separation'] : '0.5em',
1098 );
1099
1100 $form['region_separation'] = array(
1101 '#type' => 'textfield',
1102 '#title' => t('Region separation'),
1103 '#description' => t('How much padding to put on regions that are that are next to other regions. Note that this is put on both regions so the real amount is doubled.'),
1104 '#default_value' => isset($item['region_separation']) ? $item['region_separation'] : '0.5em',
1105 );
1106
1107 $form['row_separation'] = array(
1108 '#type' => 'textfield',
1109 '#title' => t('Row separation'),
1110 '#description' => t('How much padding to put on beneath rows to separate them from each other. Because this is placed only on the bottom, not hte top, this is NOT doubled like column/region separation.'),
1111 '#default_value' => isset($item['row_separation']) ? $item['row_separation'] : '0.5em',
26e7b0ef
SB
1112 );
1113 }
1114 else {
946024d2
SB
1115 $form['class'] = array(
1116 '#title' => t('CSS class'),
1117 '#type' => 'textfield',
1118 '#default_value' => isset($item['class']) ? $item['class'] : '',
1119 '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
26e7b0ef
SB
1120 );
1121
946024d2
SB
1122 if ($item['type'] != 'row') {
1123 // Test to see if there are fluid items to the left or the right. If there
1124 // are fluid items on both sides, this item cannot be set to fixed.
1125 $left = $right = FALSE;
1126 $current = 'left';
1127 foreach ($siblings as $sibling) {
1128 if ($sibling == $id) {
1129 $current = 'right';
1130 }
1131 else if ($settings['items'][$sibling]['width_type'] == '%') {
1132 $$current = TRUE; // Indirection.
1133 }
1134 }
1135
1136 $form['width_type'] = array(
1137 '#type' => 'select',
1138 '#title' => t('Width'),
1139 '#default_value' => $item['width_type'],
1140 '#options' => array(
1141 '%' => t('Fluid'),
1142 'px' => t('Fixed'),
1143 ),
1144 '#disabled' => TRUE,
1145 );
1146 }
1147 else {
1148 $form['contains'] = array(
1149 '#type' => 'select',
1150 '#title' => t('Contains'),
1151 '#default_value' => $item['contains'],
1152 '#options' => array(
1153 'region' => t('Regions'),
1154 'column' => t('Columns'),
1155 ),
1156 );
1157
1158 if (!empty($item['children'])) {
1159 $form['contains']['#disabled'] = TRUE;
1160 $form['contains']['#value'] = $item['contains'];
1161 $form['contains']['#description'] = t('You must remove contained items to change the row container type.');
1162 }
26e7b0ef
SB
1163 }
1164 }
1165
1166 $form['save'] = array(
1167 '#type' => 'submit',
1168 '#value' => t('Save'),
1169 );
1170
1171 return $form;
1172}
1173
1174/**
1175 * Submit handler for editing a flexible item.
1176 */
1177function panels_flexible_config_item_form_submit(&$form, &$form_state) {
1178 $item = &$form_state['item'];
1179 if ($item['type'] == 'region') {
1180 $item['title'] = $form_state['values']['title'];
26e7b0ef
SB
1181 }
1182
946024d2
SB
1183 $item['class'] = $form_state['values']['class'];
1184
1185 if ($form_state['id'] == 'canvas') {
1186 $item['column_class'] = $form_state['values']['column_class'];
1187 $item['row_class'] = $form_state['values']['row_class'];
1188 $item['region_class'] = $form_state['values']['region_class'];
1189 // Reverse this as the checkbox is backward from how we actually store
1190 // it to make it simpler to default to scaling.
1191 $item['no_scale'] = !$form_state['values']['no_scale'];
1192 $item['fixed_width'] = $form_state['values']['fixed_width'];
1193 $item['column_separation'] = $form_state['values']['column_separation'];
1194 $item['region_separation'] = $form_state['values']['region_separation'];
1195 $item['row_separation'] = $form_state['values']['row_separation'];
1196 }
1197 else if ($item['type'] != 'row') {
26e7b0ef
SB
1198 $item['width_type'] = $form_state['values']['width_type'];
1199 }
1200 else {
1201 $item['contains'] = $form_state['values']['contains'];
1202 }
946024d2 1203
26e7b0ef
SB
1204}
1205
1206/**
1207 * AJAX responder to add a new row, column or region to a flexible layout.
1208 */
946024d2 1209function panels_ajax_flexible_edit_add($handler, $id, $location = 'left') {
26e7b0ef
SB
1210 ctools_include('modal');
1211 ctools_include('ajax');
946024d2
SB
1212 $settings = &$handler->display->layout_settings;
1213 panels_flexible_convert_settings($settings, $handler->plugins['layout']);
26e7b0ef
SB
1214
1215 if (empty($settings['items'][$id])) {
1216 ctools_modal_render(t('Error'), t('Invalid item id.'));
1217 }
1218
1219 $parent = &$settings['items'][$id];
1220
1221 switch ($parent['type']) {
1222 case 'column':
1223 $title = t('Add row');
1224 // Create the new item with defaults.
1225 $item = array(
1226 'type' => 'row',
1227 'contains' => 'region',
1228 'children' => array(),
1229 'parent' => $id,
1230 );
1231 break;
1232 case 'row':
1233 switch ($parent['contains']) {
1234 case 'region':
1235 $title = $location == 'left' ? t('Add region to left') : t('Add region to right');
1236 $item = array(
1237 'type' => 'region',
1238 'title' => '',
1239 'width' => 100,
1240 'width_type' => '%',
1241 'parent' => $id,
1242 );
1243 break;
1244 case 'column':
1245 $title = $location == 'left' ? t('Add column to left') : t('Add column to right');
1246 $item = array(
1247 'type' => 'column',
1248 'width' => 100,
1249 'width_type' => '%',
1250 'parent' => $id,
1251 'children' => array(),
1252 );
1253 break;
1254 }
1255 // Create the new item with defaults.
1256 break;
1257 case 'region':
1258 // Cannot add items to regions.
1259 break;
1260 }
1261
1262 $form_state = array(
946024d2 1263 'display' => &$handler->display,
26e7b0ef
SB
1264 'parent' => &$parent,
1265 'item' => &$item,
1266 'id' => $id,
1267 'settings' => &$settings,
1268 'ajax' => TRUE,
1269 'title' => $title,
1270 'location' => $location,
1271 );
1272
1273 $output = ctools_modal_form_wrapper('panels_flexible_add_item_form', $form_state);
1274 if (empty($output)) {
1275 // If the width type changed then other nearby items will have
1276 // to have their widths adjusted.
946024d2 1277 panels_edit_cache_set($handler->cache);
26e7b0ef
SB
1278 $output = array();
1279
946024d2 1280 $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
26e7b0ef 1281 // Create a renderer object so we can render our new stuff.
946024d2 1282 $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
26e7b0ef
SB
1283
1284 $content = '';
1285 if ($item['type'] == 'region') {
946024d2
SB
1286 $handler->plugins['layout']['panels'][$form_state['key']] = $item['title'];
1287
1288 $content = $handler->render_region($form_state['key'], array());
26e7b0ef 1289
26e7b0ef
SB
1290 // Manually add the hidden field that our region uses to store pane info.
1291 $content .= '<input type="hidden" name="panel[pane][' .
1292 $form_state['key'] . ']" id="edit-panel-pane-' . $form_state['key'] . '" value="" />';
1293
1294 }
1295 else {
1296 // We need to make sure the left/middle/right divs exist inside this
1297 // so that more stuff can be added inside it as needed.
1298 foreach (array('left', 'middle', 'right') as $position) {
1299 if (!empty($content) || $renderer->admin) {
946024d2 1300 $content .= '<div class="' . $renderer->base[$item['type']] . '-' . $form_state['key'] . '-' . $position . '"></div>';
26e7b0ef
SB
1301 }
1302 }
1303
1304 }
1305
1306 // render the item
946024d2
SB
1307 $parent_class = $renderer->base[$parent['type']] . '-' . $id;
1308 $item_output = panels_flexible_render_item($renderer, $item, $content, $form_state['key'], 0, 0, $item['type'] == 'row');
26e7b0ef
SB
1309
1310 // Get all the CSS necessary for the entire row (as width adjustments may
1311 // have cascaded).
1312 $css = array();
946024d2 1313 panels_flexible_get_css_group($css, $renderer, $parent['children'], '.' . $parent_class, $item['type'], $id);
26e7b0ef
SB
1314
1315 $position = isset($renderer->positions[$form_state['key']]) ? $renderer->positions[$form_state['key']] : 'middle';
1316 // If there's a nearby item, add the splitter and rewrite the width
1317 // of the nearby item as it probably got adjusted.
1318 // The blocks of code in this else look very similar but are not actually
1319 // duplicated because the order changes based on left or right.
1320 switch ($position) {
1321 case 'left':
1322 if ($location == 'left') {
1323 $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
946024d2 1324 $output[] = ctools_ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-left', $item_output);
26e7b0ef
SB
1325 }
1326 else if ($location == 'right') {
1327 // If we are adding to the right side of the left box, there is
1328 // a splitter that we have to remove; then we add our box normally,
1329 // and then add a new splitter for just our guy.
946024d2 1330 $output[] = ctools_ajax_command_remove('panels-flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $form_state['key']);
26e7b0ef
SB
1331 $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1332 $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], NULL);
946024d2 1333 $output[] = ctools_ajax_command_append('#panels-dnd-main .' . $parent_class . '-left', $item_output);
26e7b0ef
SB
1334 }
1335 break;
1336 case 'right':
1337 if (!empty($form_state['sibling'])) {
1338 $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1339 }
946024d2 1340 $output[] = ctools_ajax_command_append('#panels-dnd-main .' . $parent_class . '-right', $item_output);
26e7b0ef
SB
1341 break;
1342 case 'middle':
1343 if ($location == 'left') {
1344 if (!empty($form_state['sibling'])) {
1345 $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
1346 }
946024d2 1347 $output[] = ctools_ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
26e7b0ef
SB
1348 }
1349 else {
1350 if (!empty($form_state['sibling'])) {
1351 $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1352 }
946024d2 1353 $output[] = ctools_ajax_command_append('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
26e7b0ef
SB
1354 }
1355 break;
1356
1357 }
1358
1359 // Send our fix height command.
1360 $output[] = array('command' => 'flexible_fix_height');
1361
1362 if (!empty($form_state['sibling'])) {
946024d2 1363 $sibling_width = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $form_state['sibling'] . '-width';
26e7b0ef
SB
1364 $output[] = ctools_ajax_command_html($sibling_width, $settings['items'][$form_state['sibling']]['width']);
1365 }
1366 foreach ($css as $selector => $data) {
1367 $output[] = ctools_ajax_command_css($selector, $data);
1368 }
1369
1370 // Rerender our parent item links:
1371 $output[] = ctools_ajax_command_replace('.flexible-links-' . $id,
1372 panels_flexible_render_item_links($renderer, $id, $parent));
1373
1374 $output[] = array(
1375 'command' => 'flexible_fix_firstlast',
1376 'selector' => '.' . $parent_class . '-inside',
1377 'base' => 'panels-flexible-' . $item['type'],
1378 );
1379
1380 $output[] = ctools_modal_command_dismiss();
1381 }
1382
946024d2 1383 $handler->commands = $output;
26e7b0ef
SB
1384}
1385/**
1386 * Form to add a row, column or region to a flexible layout.
1387 * @param <type> $form_state
1388 * @return <type>
1389 */
1390function panels_flexible_add_item_form(&$form_state) {
1391 $display = &$form_state['display'];
1392 $item = &$form_state['item'];
1393 $parent = &$form_state['parent'];
1394 $settings = &$form_state['settings'];
1395 $location = &$form_state['location'];
1396 $id = &$form_state['id'];
1397
26e7b0ef
SB
1398 if ($item['type'] == 'region') {
1399 $form['title'] = array(
1400 '#title' => t('Region title'),
1401 '#type' => 'textfield',
1402 '#default_value' => $item['title'],
1403 '#required' => TRUE,
1404 );
1405 }
1406
946024d2
SB
1407 $form['class'] = array(
1408 '#title' => t('CSS Class'),
1409 '#type' => 'textfield',
1410 '#default_value' => isset($item['class']) ? $item['class'] : '',
1411 '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1412 );
1413
26e7b0ef
SB
1414 if ($item['type'] != 'row') {
1415 // If there is a 'fixed' type on the side we're adding to, then this
1416 // must also be fixed. Otherwise it can be either and should default to
1417 // fluid.
1418 $restrict = FALSE;
1419
1420 if (!empty($parent['children'])) {
1421 if ($location == 'left') {
1422 $sibling = reset($parent['children']);
1423 }
1424 else {
1425 $sibling = end($parent['children']);
1426 }
1427 if ($settings['items'][$sibling]['width_type'] == 'px') {
1428 $restrict = TRUE;
1429 $item['width_type'] = 'px';
1430 }
1431 }
1432
1433 $form['width_type'] = array(
1434 '#type' => 'select',
1435 '#title' => t('Width'),
1436 '#default_value' => $item['width_type'],
1437 '#options' => array(
1438 '%' => t('Fluid'),
1439 'px' => t('Fixed'),
1440 ),
1441 '#disabled' => $restrict,
1442 );
1443 if ($restrict) {
1444 // This forces the value because disabled items don't always send
1445 // their data back.
1446 $form['width_type']['#value'] = $item['width_type'];
1447 $form['width_type']['#description'] = t('Items cannot be set to fluid if there are fixed items already on that side.');
1448 }
1449 }
1450 else {
1451 $form['contains'] = array(
1452 '#type' => 'select',
1453 '#title' => t('Contains'),
1454 '#default_value' => $item['contains'],
1455 '#options' => array(
1456 'region' => t('Regions'),
1457 'column' => t('Columns'),
1458 ),
1459 );
1460 }
1461
1462 $form['save'] = array(
1463 '#type' => 'submit',
1464 '#value' => t('Save'),
1465 );
1466
1467 return $form;
1468}
1469
1470/**
1471 * Submit handler for editing a flexible item.
1472 */
1473function panels_flexible_add_item_form_submit(&$form, &$form_state) {
1474 $item = &$form_state['item'];
1475 $parent = &$form_state['parent'];
1476 $location = &$form_state['location'];
1477 $settings = &$form_state['settings'];
1478
946024d2
SB
1479 $item['class'] = $form_state['values']['class'];
1480
26e7b0ef
SB
1481 if ($item['type'] == 'region') {
1482 $item['title'] = $form_state['values']['title'];
1483 }
1484
1485 if ($item['type'] != 'row') {
1486 $item['width_type'] = $form_state['values']['width_type'];
1487 }
1488 else {
1489 $item['contains'] = $form_state['values']['contains'];
1490 }
1491
1492 if ($item['type'] == 'region') {
1493 // derive the region key from the title
1494 $key = preg_replace("/[^a-z0-9]/", '_', drupal_strtolower($item['title']));
1495 while (isset($settings['items'][$key])) {
1496 $key .= '_';
1497 }
1498 $form_state['key'] = $key;
1499 }
1500 else {
1501 $form_state['key'] = $key = max(array_keys($settings['items'])) + 1;
1502 }
1503
1504 $form_state['sibling'] = NULL;
1505 if ($item['type'] != 'row' && !empty($parent['children'])) {
1506 // Figure out what the width should be and adjust our sibling if
1507 // necessary.
1508 if ($location == 'left') {
1509 $form_state['sibling'] = reset($parent['children']);
1510 }
1511 else {
1512 $form_state['sibling'] = end($parent['children']);
1513
1514 }
1515
1516 // If there is no sibling, or the sibling is of a different type,
1517 // the default 100 will work for either fixed or fluid.
1518 if ($form_state['sibling'] && $settings['items'][$form_state['sibling']]['width_type'] == $item['width_type']) {
1519 // steal half of the sibling's space.
1520 $width = $settings['items'][$form_state['sibling']]['width'] / 2;
1521 $settings['items'][$form_state['sibling']]['width'] = $width;
1522 $item['width'] = $width;
1523 }
1524 }
1525
1526 // Place the item.
1527 $settings['items'][$key] = $item;
1528 if ($location == 'left') {
1529 array_unshift($parent['children'], $key);
1530 }
1531 else {
1532 $parent['children'][] = $key;
1533 }
1534}
1535
1536/**
1537 * AJAX responder to remove an existing row, column or region from a flexible
1538 * layout.
1539 */
946024d2
SB
1540function panels_ajax_flexible_edit_remove($handler, $id) {
1541 $settings = &$handler->display->layout_settings;
1542 panels_flexible_convert_settings($settings, $handler->plugins['layout']);
26e7b0ef
SB
1543
1544 if (empty($settings['items'][$id])) {
26e7b0ef
SB
1545 ctools_ajax_render_error(t('Invalid item id.'));
1546 }
1547
1548 $item = &$settings['items'][$id];
946024d2
SB
1549 $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
1550 // Create a renderer object so we can render our new stuff.
1551 $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
1552
26e7b0ef
SB
1553
1554 $siblings = &$settings['items'][$item['parent']]['children'];
946024d2 1555 $parent_class = '.' . $renderer->base[$settings['items'][$item['parent']]['type']] . '-' . $item['parent'];
26e7b0ef
SB
1556
1557 // Find the offset of our array. This will also be the key because
1558 // this is a simple array.
1559 $offset = array_search($id, $siblings);
1560
1561 // Only bother with this stuff if our item is fluid, since fixed is
1562 // as fixed does.
1563 if ($item['type'] != 'row') {
1564 if (isset($siblings[$offset + 1])) {
1565 $next = $siblings[$offset + 1];
1566 }
1567 if (isset($siblings[$offset - 1])) {
1568 $prev = $siblings[$offset - 1];
1569 }
1570
1571 if ($item['width_type'] == '%') {
1572 // First, try next.
1573 if (isset($next) && $settings['items'][$next]['width_type'] == '%') {
1574 $settings['items'][$next]['width'] += $item['width'];
1575 }
1576 // If that failed, try the previous one.
1577 else if (isset($prev) && $settings['items'][$prev]['width_type'] == '%') {
1578 $settings['items'][$prev]['width'] += $item['width'];
1579 }
1580 }
1581 // Not sure what happens if they both failed. Maybe nothing.
1582 }
1583
1584 // Remove the item.
1585 array_splice($siblings, $offset, 1);
1586
1587 unset($settings['items'][$id]);
1588
1589 // Save our new state.
946024d2
SB
1590 panels_edit_cache_set($handler->cache);
1591 $class = $renderer->base[$item['type']] . '-' . $id;
26e7b0ef
SB
1592 $output = array();
1593
946024d2 1594 $output[] = ctools_ajax_command_remove('#panels-dnd-main .' . $class);
26e7b0ef
SB
1595
1596 // Regenerate the CSS for siblings.
1597 if (!empty($siblings)) {
1598 // Get all the CSS necessary for the entire row (as width adjustments may
1599 // have cascaded).
1600 $css = array();
946024d2 1601 panels_flexible_get_css_group($css, $renderer, $siblings, $parent_class, $item['type'], $item['parent']);
26e7b0ef
SB
1602 foreach ($css as $selector => $data) {
1603 $output[] = ctools_ajax_command_css($selector, $data);
1604 }
1605 }
1606
1607 // There are potentially two splitters linked to this item to be removed.
1608 if (!empty($prev)) {
946024d2 1609 $output[] = ctools_ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $prev);
26e7b0ef
SB
1610 }
1611
1612 // Try to remove the 'next' one even if there isn't a $next.
946024d2 1613 $output[] = ctools_ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $id);
26e7b0ef
SB
1614
1615 if (!empty($prev) && !empty($next)) {
1616 // Add a new splitter that links $prev and $next:
1617 $splitter = panels_flexible_render_splitter($renderer, $prev, $next);
946024d2 1618 $prev_class = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $prev;
26e7b0ef
SB
1619 $output[] = ctools_ajax_command_after($prev_class, $splitter);
1620 // Send our fix height command.
1621 $output[] = array('command' => 'flexible_fix_height');
1622 }
1623 // Rerender our parent item links:
1624 $output[] = ctools_ajax_command_replace('.flexible-links-' . $item['parent'],
1625 panels_flexible_render_item_links($renderer, $item['parent'], $settings['items'][$item['parent']]));
1626
1627 $output[] = array(
1628 'command' => 'flexible_fix_firstlast',
1629 'selector' => $parent_class . '-inside',
1630 'base' => 'panels-flexible-' . $item['type'],
1631 );
1632
946024d2 1633 $handler->commands = $output;
26e7b0ef
SB
1634}
1635
1636/**
1637 * AJAX responder to store resize information when the user adjusts the
1638 * splitter.
1639 */
946024d2 1640function panels_ajax_flexible_edit_resize($handler) {
26e7b0ef 1641 ctools_include('ajax');
946024d2
SB
1642 $settings = &$handler->display->layout_settings;
1643 panels_flexible_convert_settings($settings, $handler->plugins['layout']);
26e7b0ef
SB
1644
1645 $settings['items'][$_POST['left']]['width'] = $_POST['left_width'];
1646 if (!empty($_POST['right']) && $_POST['right'] != $_POST['left']) {
1647 $settings['items'][$_POST['right']]['width'] = $_POST['right_width'];
1648 }
1649
1650 // Save our new state.
946024d2 1651 panels_edit_cache_set($handler->cache);
26e7b0ef 1652
946024d2 1653 $handler->commands = array('ok');
26e7b0ef
SB
1654}
1655
946024d2
SB
1656/**
1657 * AJAX form to bring up the "reuse" modal.
1658 */
1659function panels_ajax_flexible_edit_reuse($handler) {
1660 $settings = &$handler->display->layout_settings;
1661 panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1662
1663 $form_state = array(
1664 'display' => &$handler->display,
1665 'settings' => &$settings,
1666 'ajax' => TRUE,
1667 'title' => t('Save this layout for reuse'),
1668 );
1669
1670 $output = ctools_modal_form_wrapper('panels_flexible_reuse_form', $form_state);
1671 if (empty($output)) {
1672 // Create the new layout.
1673 ctools_include('export');
1674 $layout = ctools_export_crud_new('panels_layout');
1675 $layout->plugin = 'flexible';
1676 $layout->name = $form_state['values']['name'];
1677 $layout->admin_title = $form_state['values']['admin_title'];
1678 $layout->admin_description = $form_state['values']['admin_description'];
1679 $layout->category = $form_state['values']['category'];
1680 $layout->settings = $handler->display->layout_settings;
1681
1682 // Save it.
1683 ctools_export_crud_save('panels_layout', $layout);
1684
1685 if (empty($form_state['values']['keep'])) {
1686 // Set the actual layout_settings to now use the newly minted layout:
1687 $handler->display->layout = 'flexible:' . $layout->name;
1688 $handler->display->layout_settings = array();
1689
1690 // Save our new state.
1691 panels_edit_cache_set($handler->cache);
1692 }
1693
1694 // Dismiss the modal.
1695 $output[] = ctools_modal_command_dismiss();
1696 }
1697
1698 $handler->commands = $output;
1699}
1700
1701function panels_flexible_reuse_form(&$form_state) {
1702 $form['markup'] = array(
1703 '#prefix' => '<div class="description">',
1704 '#suffix' => '</div>',
1705 '#value' => t('If you save this layout for reuse it will appear in the list of reusable layouts at admin/build/panels/layouts, and you will need to go there to edit it. This layout will then become an option for all future panels you make.'),
1706 );
1707
1708 $form['admin_title'] = array(
1709 '#type' => 'textfield',
1710 '#title' => t('Administrative title'),
1711 '#description' => t('This will appear in the administrative interface to easily identify it.'),
1712 );
1713
1714 $form['name'] = array(
1715 '#type' => 'textfield',
1716 '#title' => t('Machine name'),
1717 '#description' => t('The machine readable name of this layout. It must be unique, and it must contain only alphanumeric characters and underscores. Once created, you will not be able to change this value!'),
1718 );
1719
1720 $form['category'] = array(
1721 '#type' => 'textfield',
1722 '#title' => t('Category'),
1723 '#description' => t('What category this layout should appear in. If left blank the category will be "Miscellaneous".'),
1724 );
1725
1726 $form['admin_description'] = array(
1727 '#type' => 'textarea',
1728 '#title' => t('Administrative description'),
1729 '#description' => t('A description of what this layout is, does or is for, for administrative use.'),
1730 );
1731
1732 $form['keep'] = array(
1733 '#type' => 'checkbox',
1734 '#title' => t('Keep current panel layout flexible'),
1735 '#description' => t('If checked, this panel will continue to use a generic flexible layout and will not use the saved layout. Use this option if you wish to clone this layout.'),
1736 );
1737
1738 $form['submit'] = array(
1739 '#type' => 'submit',
1740 '#value' => t('Save'),
1741 );
1742
1743 return $form;
1744}
1745
1746function panels_flexible_reuse_form_validate(&$form, &$form_state) {
1747 if (empty($form_state['values']['name'])) {
1748 form_error($form['name'], t('You must choose a machine name.'));
1749 }
1750
1751 ctools_include('export');
1752 $test = ctools_export_crud_load('panels_layout', $form_state['values']['name']);
1753 if ($test) {
1754 form_error($form['name'], t('That name is used by another layout: @layout', array('@layout' => $test->admin_title)));
1755 }
1756
1757 // Ensure name fits the rules:
1758 if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
1759 form_error($form['name'], t('Name must be alphanumeric or underscores only.'));
1760 }
1761}