Catching HEAD back up with all the changes on DRUPAL-6--3. Now ready to move forward...
[project/panels.git] / plugins / layouts / flexible / flexible.inc
1 <?php
2 // $Id$
3
4 /**
5 * Implementation of hook_panels_layouts()
6 */
7 // Plugin definition
8 $plugin = array(
9 'title' => t('Flexible'),
10 'icon' => 'flexible.png',
11 'theme' => 'panels_flexible',
12 'admin theme' => 'panels_flexible_admin',
13 'css' => 'flexible.css',
14 'admin css' => 'flexible-admin.css',
15 'settings form' => 'panels_flexible_settings_form',
16 'settings submit' => 'panels_flexible_settings_submit',
17 'settings validate' => 'panels_flexible_settings_validate',
18 'panels function' => 'panels_flexible_panels',
19 'hook menu' => 'panels_flexible_menu',
20 );
21
22 /**
23 * Delegated implementation of hook_menu().
24 */
25 function panels_flexible_menu(&$items, &$info) {
26 $base = array(
27 'access arguments' => array('access content'),
28 'page arguments' => array(4),
29 'type' => MENU_CALLBACK,
30 'file' => $info['file'],
31 'file path' => $info['path'],
32 );
33
34 $items['panels/ajax/flexible/settings/%panels_edit_cache'] = array(
35 'page callback' => 'panels_ajax_flexible_edit_settings',
36 ) + $base;
37 $items['panels/ajax/flexible/add/%panels_edit_cache'] = array(
38 'page callback' => 'panels_ajax_flexible_edit_add',
39 ) + $base;
40 $items['panels/ajax/flexible/remove/%panels_edit_cache'] = array(
41 'page callback' => 'panels_ajax_flexible_edit_remove',
42 ) + $base;
43 $items['panels/ajax/flexible/resize/%panels_edit_cache'] = array(
44 'page callback' => 'panels_ajax_flexible_edit_resize',
45 ) + $base;
46 }
47
48 /**
49 * Convert settings from old style to new, or provide defaults for
50 * empty settings.
51 * @param <type> $settings
52 */
53 function panels_flexible_convert_settings(&$settings) {
54 if (empty($settings)) {
55 // set up a default
56 $settings = array(
57 'items' => array(
58 // The 'canvas' is a special row that does not get rendered
59 // normally, but is used to contain the columns.
60 'canvas' => array(
61 'type' => 'row',
62 'contains' => 'column',
63 'children' => array('main'),
64 'parent' => NULL,
65 ),
66 'main' => array(
67 'type' => 'column',
68 'width' => 100,
69 'width_type' => '%',
70 'children' => array('main-row'),
71 'parent' => 'canvas',
72 ),
73 'main-row' => array(
74 'type' => 'row',
75 'contains' => 'region',
76 'children' => array('center'),
77 'parent' => 'main',
78 ),
79 'center' => array(
80 'type' => 'region',
81 'title' => t('Center'),
82 'width' => 100,
83 'width_type' => '%',
84 'parent' => 'main-row',
85 ),
86 ),
87 );
88 }
89 else if (!isset($settings['items'])) {
90 // Convert an old style flexible to a new style flexible.
91 $old = $settings;
92 $settings = array();
93 $settings['items']['canvas'] = array(
94 'type' => 'row',
95 'contains' => 'column',
96 'children' => array(),
97 'parent' => NULL,
98 );
99 // add the left sidebar column, row and region if it exists.
100 if (!empty($old['sidebars']['left'])) {
101 $settings['items']['canvas']['children'][] = 'sidebar-left';
102 $settings['items']['sidebar-left'] = array(
103 'type' => 'column',
104 'width' => $old['sidebars']['left_width'],
105 'width_type' => $old['sidebars']['width_type'],
106 'children' => array('sidebar-left-row'),
107 'parent' => 'canvas',
108 );
109 $settings['items']['sidebar-left-row'] = array(
110 'type' => 'row',
111 'contains' => 'region',
112 'children' => array('sidebar_left'),
113 'parent' => 'sidebar-left',
114 );
115 $settings['items']['sidebar_left'] = array(
116 'type' => 'region',
117 'title' => t('Left sidebar'),
118 'width' => 100,
119 'width_type' => '%',
120 'parent' => 'sidebar-left-row',
121 );
122 }
123
124 $settings['items']['canvas']['children'][] = 'main';
125
126 if (!empty($old['sidebars']['right'])) {
127 $settings['items']['canvas']['children'][] = 'sidebar-right';
128 $settings['items']['sidebar-right'] = array(
129 'type' => 'column',
130 'width' => $old['sidebars']['right_width'],
131 'width_type' => $old['sidebars']['width_type'],
132 'children' => array('sidebar-right-row'),
133 'parent' => 'canvas',
134 );
135 $settings['items']['sidebar-right-row'] = array(
136 'type' => 'row',
137 'contains' => 'region',
138 'children' => array('sidebar_right'),
139 'parent' => 'sidebar-right',
140 );
141 $settings['items']['sidebar_right'] = array(
142 'type' => 'region',
143 'title' => t('Right sidebar'),
144 'width' => 100,
145 'width_type' => '%',
146 'parent' => 'sidebar-right-row',
147 );
148 }
149
150 // Add the main column.
151 $settings['items']['main'] = array(
152 'type' => 'column',
153 'width' => 100,
154 'width_type' => '%',
155 'children' => array(),
156 'parent' => 'canvas',
157 );
158
159 // Add rows and regions.
160 for ($row = 1; $row <= intval($old['rows']); $row++) {
161 // Create entry for the row
162 $settings['items']["row_$row"] = array(
163 'type' => 'row',
164 'contains' => 'region',
165 'children' => array(),
166 'parent' => 'main',
167 );
168 // Add the row to the parent's children
169 $settings['items']['main']['children'][] = "row_$row";
170
171 for ($col = 1; $col <= intval($old["row_$row"]['columns']); $col++) {
172 // Create entry for the region
173 $settings['items']["row_${row}_$col"] = array(
174 'type' => 'region',
175 'width' => $old["row_$row"]["width_$col"],
176 'width_type' => '%',
177 'parent' => "row_$row",
178 );
179 // Add entry for the region to the row's children
180 $settings['items']["row_$row"]['children'][] = "row_${row}_$col";
181
182 // Apply the proper title to the region
183 if (!empty($old["row_$row"]['names'][$col - 1])) {
184 $settings['items']["row_${row}_$col"]['title'] = $old["row_$row"]['names'][$col - 1];
185 }
186 else {
187 $settings['items']["row_${row}_$col"]['title'] = t("Row @row, Column @col", array('@row' => $row, '@col' => $col));
188 }
189 }
190 }
191 }
192 else if (isset($settings['canvas'])) {
193 // Convert the old 'canvas' to the new canvas row.
194 $settings['items']['canvas'] = array(
195 'type' => 'row',
196 'contains' => 'column',
197 'children' => $settings['canvas'],
198 'parent' => NULL,
199 );
200 unset($settings['canvas']);
201 }
202 }
203
204 /**
205 * Define the actual list of columns and rows for this flexible panel.
206 */
207 function panels_flexible_panels($display, $settings) {
208 $items = array();
209 panels_flexible_convert_settings($settings);
210 foreach ($settings['items'] as $id => $item) {
211 if ($item['type'] == 'region') {
212 $items[$id] = $item['title'];
213 }
214 }
215
216 return $items;
217 }
218
219 /**
220 * Draw the flexible layout.
221 */
222 function theme_panels_flexible($id, $content, $settings, $display) {
223 panels_flexible_convert_settings($settings);
224
225 $renderer = new stdClass;
226 $renderer->settings = $settings;
227 $renderer->content = $content;
228 $renderer->css_id = $id;
229 $renderer->did = $display->did;
230 $renderer->id_str = $id ? 'id="' . $id . '"' : '';
231 $renderer->admin = FALSE;
232
233 // CSS must be generated because it reports back left/middle/right
234 // positions.
235 $css = panels_flexible_render_css($renderer);
236
237 if ($display->did && $display->did != 'new') {
238 ctools_include('css');
239 // Generate an id based upon rows + columns:
240 $css_id = 'flexible:' . $display->did;
241 $filename = ctools_css_retrieve($css_id);
242 if (!$filename) {
243 $filename = ctools_css_store($css_id, $css, FALSE);
244 }
245 drupal_add_css($filename, 'module', 'all', FALSE);
246 }
247 else {
248 // If the id is 'new' we can't reliably cache the CSS in the filesystem
249 // because the display does not truly exist, so we'll stick it in the
250 // head tag.
251 drupal_set_html_head("<style type=\"text/css\">\n$css</style>\n");
252 }
253
254 $output = "<div class=\"panel-flexible panel-flexible-$renderer->did clear-block\" $renderer->id_str>\n";
255 $output .= "<div class=\"panel-flexible-inside panel-flexible-$renderer->did-inside\">\n";
256
257 $output .= panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], 'panel-flexible-' . $renderer->did);
258
259 // Wrap the whole thing up nice and snug
260 $output .= "</div>\n</div>\n";
261
262 return $output;
263 }
264
265 /**
266 * Draw the flexible layout.
267 */
268 function theme_panels_flexible_admin($id, $content, $settings, $display) {
269 panels_flexible_convert_settings($settings);
270
271 $renderer = new stdClass;
272 $renderer->settings = $settings;
273 $renderer->content = $content;
274 $renderer->css_id = $id;
275 $renderer->did = $display->did;
276 $renderer->cache_key = $display->cache_key;
277 $renderer->id_str = $id ? 'id="' . $id . '"' : '';
278 $renderer->admin = TRUE;
279
280 if ($display->did && $display->did != 'new') {
281 // Automatically remove any cached CSS for this display since it's
282 // being edited. The next time it's viewed CSS will be auto generated
283 // if necessary.
284 $css_id = 'flexible:' . $display->did;
285 ctools_include('css');
286 ctools_css_clear($css_id);
287 }
288 $css = panels_flexible_render_css($renderer);
289
290 // For the administrative view, add CSS directly to head.
291 drupal_set_html_head("<style type=\"text/css\">\n$css</style>\n");
292
293 $output = '<input type="submit" id="panels-flexible-toggle-layout" value ="' .
294 t('Show layout designer') . '">';
295 $output .= "<div class=\"panel-flexible panel-flexible-$renderer->did clear-block panel-flexible-admin panel-flexible-no-edit-layout\" $renderer->id_str>\n";
296 $output .= "<div class=\"panel-flexible-inside panel-flexible-$renderer->did-inside \">\n";
297
298 $content = panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], 'panels-flexible-row-' . $renderer->did . '-canvas');
299 $output .= panels_flexible_render_item($renderer, $settings['items']['canvas'], 'panels-flexible-row', $content, 'canvas', 0, 0, TRUE);
300
301 // Wrap the whole thing up nice and snug
302 $output .= "</div>\n</div>\n";
303
304 drupal_add_js(panels_get_path('plugins/layouts/flexible/flexible-admin.js'));
305 drupal_add_js(array('flexible' => array('resize' => url('panels/ajax/flexible/resize/' . $display->cache_key, array('absolute' => TRUE)))), 'setting');
306 return $output;
307 }
308
309 /**
310 * Render a piece of a flexible layout.
311 */
312 function panels_flexible_render_items($renderer, $list, $owner_id) {
313 $output = '';
314 $groups = array('left' => '', 'middle' => '', 'right' => '');
315 $max = count($list) - 1;
316 $prev = NULL;
317
318 foreach ($list as $position => $id) {
319 $item = $renderer->settings['items'][$id];
320 $location = isset($renderer->positions[$id]) ? $renderer->positions[$id] : 'middle';
321
322 if ($renderer->admin && $item['type'] != 'row' && $prev ) {
323 $groups[$location] .= panels_flexible_render_splitter($renderer, $prev, $id);
324 }
325
326 switch ($item['type']) {
327 case 'column':
328 $content = panels_flexible_render_items($renderer, $item['children'], 'panels-flexible-column-' . $renderer->did . '-' . $id);
329 $groups[$location] .= panels_flexible_render_item($renderer, $item, 'panels-flexible-column', $content, $id, $position, $max);
330 break;
331 case 'row':
332 $content = panels_flexible_render_items($renderer, $item['children'], 'panels-flexible-row-' . $renderer->did . '-' . $id);
333 $groups[$location] .= panels_flexible_render_item($renderer, $item, 'panels-flexible-row', $content, $id, $position, $max, TRUE);
334 break;
335 case 'region':
336 $content = isset($renderer->content[$id]) ? $renderer->content[$id] : "&nbsp;";
337 $groups[$location] .= panels_flexible_render_item($renderer, $item, 'panels-flexible-region', $content, $id, $position, $max);
338 break;
339 }
340
341 // If all items are fixed then we have a special splitter on the right to
342 // control the overall width.
343 if (!empty($renderer->admin) && $max == $position && $location == 'left') {
344 $groups[$location] .= panels_flexible_render_splitter($renderer, $id, NULL);
345 }
346 $prev = $id;
347 }
348
349 foreach ($groups as $position => $content) {
350 if (!empty($content) || $renderer->admin) {
351 $output .= '<div class="' . $owner_id . '-' . $position . '">' . $content . '</div>';
352 }
353 }
354
355 return $output;
356 }
357
358 /**
359 * Render a column in the flexible layout.
360 */
361 function panels_flexible_render_item($renderer, $item, $base, $content, $id, $position, $max, $clear = FALSE) {
362 $output = '<div class="' . $base . ' ' . $base . '-' . $renderer->did . '-' . $id;
363 if ($position == 0) {
364 $output .= ' ' . $base . '-first';
365 }
366 if ($position == $max) {
367 $output .= ' ' . $base . '-last';
368 }
369 if ($clear) {
370 $output .= ' clear-block';
371 }
372
373 if (isset($item['class'])) {
374 $output .= ' ' . check_plain($item['class']);
375 }
376
377 $output .= '">' . "\n";
378
379 if (!empty($renderer->admin)) {
380 $output .= panels_flexible_render_item_links($renderer, $id, $item);
381 }
382
383 $output .= ' <div class="inside ' . $base . '-inside ' . $base . '-' . $renderer->did . '-' . $id . '-inside';
384 if ($position == 0) {
385 $output .= ' ' . $base . '-inside-first';
386 }
387 if ($position == $max) {
388 $output .= ' ' . $base . '-inside-last';
389 }
390 if ($clear) {
391 $output .= ' clear-block';
392 }
393
394 $output .= "\">\n";
395 $output .= $content;
396 $output .= ' </div>' . "\n";
397 $output .= '</div>' . "\n";
398
399 return $output;
400 }
401 /**
402 * Render a splitter div to place between the $left and $right items.
403 *
404 * If the right ID is NULL that means there isn't actually a box to the
405 * right, but we need a splitter anyway. We'll mostly use info about the
406 * left, but pretend it's 'fluid' so that the javascript won't actually
407 * modify the right item.
408 */
409 function panels_flexible_render_splitter($renderer, $left_id, $right_id) {
410 $left = $renderer->settings['items'][$left_id];
411
412 $left_class = 'panels-flexible-' . $left['type'] . '-' . $renderer->did . '-' . $left_id;
413 if ($right_id) {
414 $right = $renderer->settings['items'][$right_id];
415 $right_class = 'panels-flexible-' . $right['type'] . '-' . $renderer->did . '-' . $right_id;
416 }
417 else {
418 $right = $left;
419 $right_class = $left_class;
420 }
421
422 $output = '<div class="panels-flexible-splitter flexible-splitter-for-' . $left_class . '">';
423
424 // Name the left object
425 $output .= '<span class="panels-flexible-splitter-left">';
426 $output .= '.' . $left_class;
427 $output .= '</span>';
428
429 $output .= '<span class="panels-flexible-splitter-left-id">';
430 $output .= $left_id;
431 $output .= '</span>';
432
433 $output .= '<span class="panels-flexible-splitter-left-width ' . $left_class . '-width">';
434 $output .= $left['width'];
435 $output .= '</span>';
436
437 $output .= '<span class="panels-flexible-splitter-left-scale">';
438 $output .= isset($renderer->scale[$left_id]) ? $renderer->scale[$left_id] : 1;
439 $output .= '</span>';
440
441 $output .= '<span class="panels-flexible-splitter-left-width-type">';
442 $output .= $left['width_type'];
443 $output .= '</span>';
444
445 // Name the right object
446 $output .= '<span class="panels-flexible-splitter-right">';
447 $output .= '.' . $right_class;
448 $output .= '</span>';
449
450 $output .= '<span class="panels-flexible-splitter-right-id">';
451 $output .= $right_id;
452 $output .= '</span>';
453
454 $output .= '<span class="panels-flexible-splitter-right-width ' . $right_class . '-width">';
455 $output .= $right['width'];
456 $output .= '</span>';
457
458 $output .= '<span class="panels-flexible-splitter-right-scale">';
459 $output .= isset($renderer->scale[$right_id]) ? $renderer->scale[$right_id] : 1;
460 $output .= '</span>';
461
462 $output .= '<span class="panels-flexible-splitter-right-width-type">';
463 // If there is no right, make it fluid.
464 $output .= $right_id ? $right['width_type'] : '%';
465 $output .= '</span>';
466
467 $output .= '</div>';
468 return $output;
469 }
470
471 /**
472 * Render the dropdown links for an item.
473 */
474 function panels_flexible_render_item_links($renderer, $id, $item) {
475 $links = array();
476 $remove = '';
477 $add = '';
478 if ($item['type'] == 'column') {
479 $title = t('Column');
480 $settings = t('Column settings');
481 if (empty($item['children'])) {
482 $remove = t('Remove column');
483 $add = t('Add row');
484 }
485 else {
486 $add = t('Add row to top');
487 $add2 = t('Add row to bottom');
488 }
489 }
490 else if ($item['type'] == 'row') {
491 if ($id == 'canvas') {
492 $title = t('Canvas');
493 }
494 else {
495 $title = t('Row');
496 $settings = t('Row settings');
497 }
498 if (empty($item['children'])) {
499 if ($item != 'canvas') {
500 $remove = t('Remove row');
501 }
502 $add = $item['contains'] == 'region' ? t('Add region') : t('Add column');
503 }
504 else {
505 $add = $item['contains'] == 'region' ? t('Add region to left') : t('Add column to left');
506 $add2 = $item['contains'] == 'region' ? t('Add region to right') : t('Add column to right');
507 }
508 }
509 else if ($item['type'] == 'region') {
510 $title = t('Region');
511 $settings = t('Region settings');
512 $remove = t('Remove region');
513 }
514
515 if (!empty($settings)) {
516 $links[] = array(
517 'title' => $settings,
518 'href' => 'panels/ajax/flexible/settings/' . $renderer->cache_key . '/' . $id,
519 'attributes' => array('class' => 'ctools-use-modal'),
520 );
521 }
522 if ($add) {
523 $links[] = array(
524 'title' => $add,
525 'href' => 'panels/ajax/flexible/add/' . $renderer->cache_key . '/' . $id,
526 'attributes' => array('class' => 'ctools-use-modal'),
527 );
528 }
529 if (isset($add2)) {
530 $links[] = array(
531 'title' => $add2,
532 'href' => 'panels/ajax/flexible/add/' . $renderer->cache_key . '/' . $id . '/right',
533 'attributes' => array('class' => 'ctools-use-modal'),
534 );
535 }
536 if ($remove) {
537 $links[] = array(
538 'title' => $remove,
539 'href' => 'panels/ajax/flexible/remove/' . $renderer->cache_key . '/' . $id,
540 'attributes' => array('class' => 'ctools-use-ajax'),
541 );
542 }
543
544 return theme('ctools_dropdown', $title, $links, FALSE,
545 'flexible-layout-only flexible-links flexible-title flexible-links-' . $id);
546 }
547 /**
548 * Provide CSS for a flexible layout.
549 */
550 function panels_flexible_render_css($renderer) {
551 $parent_class = $renderer->admin ? '.panels-flexible-row-' . $renderer->did . '-canvas' : '.panel-flexible-' . $renderer->did;
552 return panels_flexible_render_css_group($renderer, $renderer->settings['items']['canvas']['children'], $parent_class, 'column');
553 }
554
555 /**
556 * Render the CSS for a group of items to be displayed together.
557 *
558 * Columns and regions, when displayed as a group, need to cooperate in
559 * order to share margins and make sure that percent widths add up
560 * to the right total.
561 */
562 function panels_flexible_render_css_group($renderer, $list, $owner_id, $type) {
563 $css = array();
564 panels_flexible_get_css_group($css, $renderer, $list, $owner_id, $type);
565
566 ctools_include('css');
567 return ctools_css_assemble($css);
568 }
569
570 /**
571 * Construct an array with all of the CSS properties for a group.
572 *
573 * This will parse down into children and produce all of the CSS needed if you
574 * start from the top.
575 */
576 function panels_flexible_get_css_group(&$css, $renderer, $list, $owner_id, $type) {
577 if ($type != 'row') {
578 // Go through our items and break up into right/center/right groups so we
579 // can figure out our offsets.
580
581 // right == any items on the right that are 'fixed'.
582 // middle == all fluid items.
583 // right == any items on the right that are 'fixed'.
584 $left = $middle = $right = array();
585 $left_total = $right_total = $middle_total = 0;
586 $current = 'left';
587 foreach ($list as $id) {
588 if ($renderer->settings['items'][$id]['width_type'] == 'px') {
589 // fixed
590 if ($current == 'left') {
591 $left[] = $id;
592 $renderer->positions[$id] = 'left';
593 $left_total += $renderer->settings['items'][$id]['width'];
594 }
595 else {
596 $current = 'right';
597 $right[] = $id;
598 $renderer->positions[$id] = 'right';
599 $right_total += $renderer->settings['items'][$id]['width'];
600 }
601 }
602 else {
603 // fluid
604 if ($current != 'right') {
605 $current = 'middle';
606 $middle[] = $id;
607 $renderer->positions[$id] = 'middle';
608 $middle_total += $renderer->settings['items'][$id]['width'];
609 }
610 // fall through: if current is 'right' and we ran into a 'fluid' then
611 // it gets *dropped* because that is invalid.
612 }
613 }
614
615 // Go through our right sides and create CSS.
616 foreach ($left as $id) {
617 $class = ".panels-flexible-$type-" . $renderer->did . "-$id";
618 $css[$class] = array(
619 'position' => 'relative',
620 'float' => 'left',
621 'background-color' => 'transparent',
622 'width' => $renderer->settings['items'][$id]['width'] . "px",
623 );
624 }
625
626 // Do the same for right.
627 $right_pixels = 0;
628
629 foreach ($right as $id) {
630 $class = ".panels-flexible-$type-" . $renderer->did . "-$id";
631 $css[$class] = array(
632 'float' => 'left',
633 'width' => $renderer->settings['items'][$id]['width'] . "px",
634 );
635 }
636
637 $max = count($middle) - 1;
638
639 if ($middle_total) {
640 // Because we love IE so much, auto scale everything to 99%. This
641 // means adding up the actual widths and then providing a multiplier
642 // to each so that the total is 99%.
643 $scale = 99.0 / $middle_total;
644 foreach ($middle as $position => $id) {
645 $class = ".panels-flexible-$type-" . $renderer->did . "-$id";
646 $css[$class] = array(
647 'float' => 'left',
648 'width' => ($renderer->settings['items'][$id]['width'] * $scale) . "%",
649 );
650
651 // Store this so we can use it later.
652 // @todo: Store the scale, not the new width, so .js can adjust
653 // bi-directionally.
654 $renderer->scale[$id] = $scale;
655 }
656 }
657
658 // If there is any total remaining, we need to offset the splitter
659 // by this much too.
660 if ($left_total) {
661 $css["$owner_id-left"]['margin-left'] = '-' . $left_total . 'px';
662 // IE hack
663 $css["* html $owner_id-left"]['left'] = $left_total . "px";
664 }
665 // Add this even if it's 0 so we can handle removals.
666 $css["$owner_id-inside"]['padding-left'] = $left_total . 'px';
667 if ($right_total) {
668 $css["$owner_id-right"]['margin-right'] = '-' . $right_total . 'px';
669 }
670 $css["$owner_id-inside"]['padding-right'] = $right_total . 'px';
671
672 }
673
674 // Go through each item and process children.
675 foreach ($list as $id) {
676 $item = $renderer->settings['items'][$id];
677 if (empty($item['children'])) {
678 continue;
679 }
680
681 if ($type == 'column') {
682 // Columns can only contain rows.
683 $child_type = 'row';
684 }
685 else {
686 $child_type = isset($item['contains']) ? $item['contains'] : 'region';
687 }
688
689 $class = ".panels-flexible-$type-" . $renderer->did . "-$id";
690 panels_flexible_get_css_group($css, $renderer, $item['children'], $class, $child_type);
691 }
692 }
693
694 /**
695 * AJAX responder to edit flexible settings for an item.
696 */
697 function panels_ajax_flexible_edit_settings($cache, $id) {
698 $settings = &$cache->display->layout_settings;
699 panels_flexible_convert_settings($settings);
700
701 if (empty($settings['items'][$id])) {
702 ctools_modal_render(t('Error'), t('Invalid item id.'));
703 }
704
705 $item = &$settings['items'][$id];
706 $siblings = $settings['items'][$item['parent']]['children'];
707
708 switch ($item['type']) {
709 case 'column':
710 $title = t('Configure column');
711 break;
712 case 'row':
713 $title = t('Configure row');
714 break;
715 case 'region':
716 $title = t('Configure region');
717 break;
718 }
719
720 $form_state = array(
721 'display' => &$cache->display,
722 'item' => &$item,
723 'id' => $id,
724 'siblings' => $siblings,
725 'settings' => &$settings,
726 'ajax' => TRUE,
727 'title' => $title,
728 'op' => 'edit',
729 );
730
731 $output = ctools_modal_form_wrapper('panels_flexible_config_item_form', $form_state);
732 if (empty($output)) {
733 // If the width type changed then other nearby items will have
734 // to have their widths adjusted.
735 panels_load_include('display-edit');
736 panels_edit_cache_set($cache);
737 $output = array();
738 // If the item is a region, replace the title.
739 $class = 'panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $id;
740 if ($item['type'] == 'region') {
741 $output[] = ctools_ajax_command_replace(".$class h2.label",
742 '<h2 class="label">' . check_plain($item['title']) . '</h2>');
743 }
744 // If not a row, reset the css width if necessary.
745
746 $output[] = ctools_modal_command_dismiss();
747 }
748
749 ctools_ajax_render($output);
750 }
751
752 /**
753 * Configure a row, column or region on the flexible page.
754 *
755 * @param <type> $form_state
756 * @return <type>
757 */
758 function panels_flexible_config_item_form(&$form_state) {
759 $display = &$form_state['display'];
760 $item = &$form_state['item'];
761 $siblings = &$form_state['siblings'];
762 $settings = &$form_state['settings'];
763 $id = &$form_state['id'];
764
765 // $form['#action'] = $form_state['url'];
766
767 if ($item['type'] == 'region') {
768 $form['title'] = array(
769 '#title' => t('Region title'),
770 '#type' => 'textfield',
771 '#default_value' => $item['title'],
772 '#required' => TRUE,
773 );
774
775 $form['class'] = array(
776 '#title' => t('Region class'),
777 '#type' => 'textfield',
778 '#default_value' => isset($item['class']) ? $item['class'] : '',
779 '#description' => t('Enter a CSS class that will be used for this region. This can be used to apply automatic styling from your theme, for example.'),
780 );
781 }
782
783 if ($item['type'] != 'row') {
784 // Test to see if there are fluid items to the left or the right. If there
785 // are fluid items on both sides, this item cannot be set to fixed.
786 $left = $right = FALSE;
787 $current = 'left';
788 foreach ($siblings as $sibling) {
789 if ($sibling == $id) {
790 $current = 'right';
791 }
792 else if ($settings['items'][$sibling]['width_type'] == '%') {
793 $$current = TRUE; // Indirection.
794 }
795 }
796
797 $form['width_type'] = array(
798 '#type' => 'select',
799 '#title' => t('Width'),
800 '#default_value' => $item['width_type'],
801 '#options' => array(
802 '%' => t('Fluid'),
803 'px' => t('Fixed'),
804 ),
805 '#disabled' => TRUE,
806 );
807 }
808 else {
809 $form['contains'] = array(
810 '#type' => 'select',
811 '#title' => t('Contains'),
812 '#default_value' => $item['contains'],
813 '#options' => array(
814 'region' => t('Regions'),
815 'column' => t('Columns'),
816 ),
817 );
818
819 if (!empty($item['children'])) {
820 $form['contains']['#disabled'] = TRUE;
821 $form['contains']['#value'] = $item['contains'];
822 $form['contains']['#description'] = t('You must remove contained items to change the row container type.');
823 }
824 }
825
826 $form['save'] = array(
827 '#type' => 'submit',
828 '#value' => t('Save'),
829 );
830
831 return $form;
832 }
833
834 /**
835 * Submit handler for editing a flexible item.
836 */
837 function panels_flexible_config_item_form_submit(&$form, &$form_state) {
838 $item = &$form_state['item'];
839 if ($item['type'] == 'region') {
840 $item['title'] = $form_state['values']['title'];
841 $item['class'] = $form_state['values']['class'];
842 }
843
844 if ($item['type'] != 'row') {
845 $item['width_type'] = $form_state['values']['width_type'];
846 }
847 else {
848 $item['contains'] = $form_state['values']['contains'];
849 }
850 }
851
852 /**
853 * AJAX responder to add a new row, column or region to a flexible layout.
854 */
855 function panels_ajax_flexible_edit_add($cache, $id, $location = 'left') {
856 ctools_include('modal');
857 ctools_include('ajax');
858 $settings = &$cache->display->layout_settings;
859 panels_flexible_convert_settings($settings);
860
861 if (empty($settings['items'][$id])) {
862 ctools_modal_render(t('Error'), t('Invalid item id.'));
863 }
864
865 $parent = &$settings['items'][$id];
866
867 switch ($parent['type']) {
868 case 'column':
869 $title = t('Add row');
870 // Create the new item with defaults.
871 $item = array(
872 'type' => 'row',
873 'contains' => 'region',
874 'children' => array(),
875 'parent' => $id,
876 );
877 break;
878 case 'row':
879 switch ($parent['contains']) {
880 case 'region':
881 $title = $location == 'left' ? t('Add region to left') : t('Add region to right');
882 $item = array(
883 'type' => 'region',
884 'title' => '',
885 'width' => 100,
886 'width_type' => '%',
887 'parent' => $id,
888 );
889 break;
890 case 'column':
891 $title = $location == 'left' ? t('Add column to left') : t('Add column to right');
892 $item = array(
893 'type' => 'column',
894 'width' => 100,
895 'width_type' => '%',
896 'parent' => $id,
897 'children' => array(),
898 );
899 break;
900 }
901 // Create the new item with defaults.
902 break;
903 case 'region':
904 // Cannot add items to regions.
905 break;
906 }
907
908 $form_state = array(
909 'display' => &$cache->display,
910 'parent' => &$parent,
911 'item' => &$item,
912 'id' => $id,
913 'settings' => &$settings,
914 'ajax' => TRUE,
915 'title' => $title,
916 'location' => $location,
917 );
918
919 $output = ctools_modal_form_wrapper('panels_flexible_add_item_form', $form_state);
920 if (empty($output)) {
921 // If the width type changed then other nearby items will have
922 // to have their widths adjusted.
923 panels_load_include('display-edit');
924 panels_edit_cache_set($cache);
925 $output = array();
926
927 $css_id = isset($cache->display->css_id) ? $cache->display->css_id : '';
928 // Create a renderer object so we can render our new stuff.
929 $renderer = new stdClass;
930 $renderer->settings = $settings;
931 $renderer->content = array();
932 $renderer->css_id = $css_id;
933 $renderer->did = $cache->display->did;
934 $renderer->cache_key = $cache->display->cache_key;
935 $renderer->id_str = $css_id ? 'id="' . $css_id . '"' : '';
936 $renderer->admin = TRUE;
937
938 $content = '';
939 if ($item['type'] == 'region') {
940 panels_load_include('display-edit');
941 $panel_buttons = panels_edit_panel_get_links($cache->display, $form_state['key']);
942
943 $content = panels_render_region_dnd('', $form_state['key'], $item['title'], $panel_buttons);
944 // Manually add the hidden field that our region uses to store pane info.
945 $content .= '<input type="hidden" name="panel[pane][' .
946 $form_state['key'] . ']" id="edit-panel-pane-' . $form_state['key'] . '" value="" />';
947
948 }
949 else {
950 // We need to make sure the left/middle/right divs exist inside this
951 // so that more stuff can be added inside it as needed.
952 foreach (array('left', 'middle', 'right') as $position) {
953 if (!empty($content) || $renderer->admin) {
954 $content .= '<div class="panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $form_state['key'] . '-' . $position . '"></div>';
955 }
956 }
957
958 }
959
960 // render the item
961 $parent_class = 'panels-flexible-' . $parent['type'] . '-' . $cache->display->did . '-' . $id;
962 $item_output = panels_flexible_render_item($renderer, $item, 'panels-flexible-' . $item['type'], $content, $form_state['key'], 0, 0, $item['type'] == 'row');
963
964 // Get all the CSS necessary for the entire row (as width adjustments may
965 // have cascaded).
966 $css = array();
967 panels_flexible_get_css_group($css, $renderer, $parent['children'], '.' . $parent_class, $item['type']);
968
969 $position = isset($renderer->positions[$form_state['key']]) ? $renderer->positions[$form_state['key']] : 'middle';
970 // If there's a nearby item, add the splitter and rewrite the width
971 // of the nearby item as it probably got adjusted.
972 // The blocks of code in this else look very similar but are not actually
973 // duplicated because the order changes based on left or right.
974 switch ($position) {
975 case 'left':
976 if ($location == 'left') {
977 $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
978 $output[] = ctools_ajax_command_prepend('.' . $parent_class . '-left', $item_output);
979 }
980 else if ($location == 'right') {
981 // If we are adding to the right side of the left box, there is
982 // a splitter that we have to remove; then we add our box normally,
983 // and then add a new splitter for just our guy.
984 $output[] = ctools_ajax_command_remove('panels-flexible-splitter-for-panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $form_state['key']);
985 $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
986 $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], NULL);
987 $output[] = ctools_ajax_command_append('.' . $parent_class . '-left', $item_output);
988 }
989 break;
990 case 'right':
991 if (!empty($form_state['sibling'])) {
992 $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
993 }
994 $output[] = ctools_ajax_command_append('.' . $parent_class . '-right', $item_output);
995 break;
996 case 'middle':
997 if ($location == 'left') {
998 if (!empty($form_state['sibling'])) {
999 $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
1000 }
1001 $output[] = ctools_ajax_command_prepend('.' . $parent_class . '-middle', $item_output);
1002 }
1003 else {
1004 if (!empty($form_state['sibling'])) {
1005 $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1006 }
1007 $output[] = ctools_ajax_command_append('.' . $parent_class . '-middle', $item_output);
1008 }
1009 break;
1010
1011 }
1012
1013 // Send our fix height command.
1014 $output[] = array('command' => 'flexible_fix_height');
1015
1016 if (!empty($form_state['sibling'])) {
1017 $sibling_width = '.panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $form_state['sibling'] . '-width';
1018 $output[] = ctools_ajax_command_html($sibling_width, $settings['items'][$form_state['sibling']]['width']);
1019 }
1020 foreach ($css as $selector => $data) {
1021 $output[] = ctools_ajax_command_css($selector, $data);
1022 }
1023
1024 // Rerender our parent item links:
1025 $output[] = ctools_ajax_command_replace('.flexible-links-' . $id,
1026 panels_flexible_render_item_links($renderer, $id, $parent));
1027
1028 $output[] = array(
1029 'command' => 'flexible_fix_firstlast',
1030 'selector' => '.' . $parent_class . '-inside',
1031 'base' => 'panels-flexible-' . $item['type'],
1032 );
1033
1034 $output[] = ctools_modal_command_dismiss();
1035 }
1036
1037 ctools_ajax_render($output);
1038 }
1039 /**
1040 * Form to add a row, column or region to a flexible layout.
1041 * @param <type> $form_state
1042 * @return <type>
1043 */
1044 function panels_flexible_add_item_form(&$form_state) {
1045 $display = &$form_state['display'];
1046 $item = &$form_state['item'];
1047 $parent = &$form_state['parent'];
1048 $settings = &$form_state['settings'];
1049 $location = &$form_state['location'];
1050 $id = &$form_state['id'];
1051
1052 // $form['#action'] = $form_state['url'];
1053
1054 if ($item['type'] == 'region') {
1055 $form['title'] = array(
1056 '#title' => t('Region title'),
1057 '#type' => 'textfield',
1058 '#default_value' => $item['title'],
1059 '#required' => TRUE,
1060 );
1061 }
1062
1063 if ($item['type'] != 'row') {
1064 // If there is a 'fixed' type on the side we're adding to, then this
1065 // must also be fixed. Otherwise it can be either and should default to
1066 // fluid.
1067 $restrict = FALSE;
1068
1069 if (!empty($parent['children'])) {
1070 if ($location == 'left') {
1071 $sibling = reset($parent['children']);
1072 }
1073 else {
1074 $sibling = end($parent['children']);
1075 }
1076 if ($settings['items'][$sibling]['width_type'] == 'px') {
1077 $restrict = TRUE;
1078 $item['width_type'] = 'px';
1079 }
1080 }
1081
1082 $form['width_type'] = array(
1083 '#type' => 'select',
1084 '#title' => t('Width'),
1085 '#default_value' => $item['width_type'],
1086 '#options' => array(
1087 '%' => t('Fluid'),
1088 'px' => t('Fixed'),
1089 ),
1090 '#disabled' => $restrict,
1091 );
1092 if ($restrict) {
1093 // This forces the value because disabled items don't always send
1094 // their data back.
1095 $form['width_type']['#value'] = $item['width_type'];
1096 $form['width_type']['#description'] = t('Items cannot be set to fluid if there are fixed items already on that side.');
1097 }
1098 }
1099 else {
1100 $form['contains'] = array(
1101 '#type' => 'select',
1102 '#title' => t('Contains'),
1103 '#default_value' => $item['contains'],
1104 '#options' => array(
1105 'region' => t('Regions'),
1106 'column' => t('Columns'),
1107 ),
1108 );
1109 }
1110
1111 $form['save'] = array(
1112 '#type' => 'submit',
1113 '#value' => t('Save'),
1114 );
1115
1116 return $form;
1117 }
1118
1119 /**
1120 * Submit handler for editing a flexible item.
1121 */
1122 function panels_flexible_add_item_form_submit(&$form, &$form_state) {
1123 $item = &$form_state['item'];
1124 $parent = &$form_state['parent'];
1125 $location = &$form_state['location'];
1126 $settings = &$form_state['settings'];
1127
1128 if ($item['type'] == 'region') {
1129 $item['title'] = $form_state['values']['title'];
1130 }
1131
1132 if ($item['type'] != 'row') {
1133 $item['width_type'] = $form_state['values']['width_type'];
1134 }
1135 else {
1136 $item['contains'] = $form_state['values']['contains'];
1137 }
1138
1139 if ($item['type'] == 'region') {
1140 // derive the region key from the title
1141 $key = preg_replace("/[^a-z0-9]/", '_', drupal_strtolower($item['title']));
1142 while (isset($settings['items'][$key])) {
1143 $key .= '_';
1144 }
1145 $form_state['key'] = $key;
1146 }
1147 else {
1148 $form_state['key'] = $key = max(array_keys($settings['items'])) + 1;
1149 }
1150
1151 $form_state['sibling'] = NULL;
1152 if ($item['type'] != 'row' && !empty($parent['children'])) {
1153 // Figure out what the width should be and adjust our sibling if
1154 // necessary.
1155 if ($location == 'left') {
1156 $form_state['sibling'] = reset($parent['children']);
1157 }
1158 else {
1159 $form_state['sibling'] = end($parent['children']);
1160
1161 }
1162
1163 // If there is no sibling, or the sibling is of a different type,
1164 // the default 100 will work for either fixed or fluid.
1165 if ($form_state['sibling'] && $settings['items'][$form_state['sibling']]['width_type'] == $item['width_type']) {
1166 // steal half of the sibling's space.
1167 $width = $settings['items'][$form_state['sibling']]['width'] / 2;
1168 $settings['items'][$form_state['sibling']]['width'] = $width;
1169 $item['width'] = $width;
1170 }
1171 }
1172
1173 // Place the item.
1174 $settings['items'][$key] = $item;
1175 if ($location == 'left') {
1176 array_unshift($parent['children'], $key);
1177 }
1178 else {
1179 $parent['children'][] = $key;
1180 }
1181 }
1182
1183 /**
1184 * AJAX responder to remove an existing row, column or region from a flexible
1185 * layout.
1186 */
1187 function panels_ajax_flexible_edit_remove($cache, $id) {
1188 $settings = &$cache->display->layout_settings;
1189 panels_flexible_convert_settings($settings);
1190
1191 if (empty($settings['items'][$id])) {
1192 ctools_ajax_render_error(print_r($settings['items'], 1));
1193 ctools_ajax_render_error(t('Invalid item id.'));
1194 }
1195
1196 $item = &$settings['items'][$id];
1197
1198 $siblings = &$settings['items'][$item['parent']]['children'];
1199 $parent_class = '.panels-flexible-' . $settings['items'][$item['parent']]['type'] .
1200 '-' . $cache->display->did . '-' . $item['parent'];
1201
1202 // Find the offset of our array. This will also be the key because
1203 // this is a simple array.
1204 $offset = array_search($id, $siblings);
1205
1206 // Only bother with this stuff if our item is fluid, since fixed is
1207 // as fixed does.
1208 if ($item['type'] != 'row') {
1209 if (isset($siblings[$offset + 1])) {
1210 $next = $siblings[$offset + 1];
1211 }
1212 if (isset($siblings[$offset - 1])) {
1213 $prev = $siblings[$offset - 1];
1214 }
1215
1216 if ($item['width_type'] == '%') {
1217 // First, try next.
1218 if (isset($next) && $settings['items'][$next]['width_type'] == '%') {
1219 $settings['items'][$next]['width'] += $item['width'];
1220 }
1221 // If that failed, try the previous one.
1222 else if (isset($prev) && $settings['items'][$prev]['width_type'] == '%') {
1223 $settings['items'][$prev]['width'] += $item['width'];
1224 }
1225 }
1226 // Not sure what happens if they both failed. Maybe nothing.
1227 }
1228
1229 // Remove the item.
1230 array_splice($siblings, $offset, 1);
1231
1232 unset($settings['items'][$id]);
1233
1234 // Save our new state.
1235 panels_load_include('display-edit');
1236 panels_edit_cache_set($cache);
1237 $class = 'panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $id;
1238 $output = array();
1239
1240 $output[] = ctools_ajax_command_remove('.' . $class);
1241
1242 $css_id = isset($cache->display->css_id) ? $cache->display->css_id : '';
1243 // Create a renderer object so we can render our new stuff.
1244 $renderer = new stdClass;
1245 $renderer->settings = $settings;
1246 $renderer->content = array();
1247 $renderer->css_id = $css_id;
1248 $renderer->did = $cache->display->did;
1249 $renderer->cache_key = $cache->display->cache_key;
1250 $renderer->id_str = $css_id ? 'id="' . $css_id . '"' : '';
1251 $renderer->admin = TRUE;
1252
1253 // Regenerate the CSS for siblings.
1254 if (!empty($siblings)) {
1255 // Get all the CSS necessary for the entire row (as width adjustments may
1256 // have cascaded).
1257 $css = array();
1258 panels_flexible_get_css_group($css, $renderer, $siblings, $parent_class, $item['type']);
1259 foreach ($css as $selector => $data) {
1260 $output[] = ctools_ajax_command_css($selector, $data);
1261 }
1262 }
1263
1264 // There are potentially two splitters linked to this item to be removed.
1265 if (!empty($prev)) {
1266 $output[] = ctools_ajax_command_remove('.flexible-splitter-for-panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $prev);
1267 }
1268
1269 // Try to remove the 'next' one even if there isn't a $next.
1270 $output[] = ctools_ajax_command_remove('.flexible-splitter-for-panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $id);
1271
1272 if (!empty($prev) && !empty($next)) {
1273 // Add a new splitter that links $prev and $next:
1274 $splitter = panels_flexible_render_splitter($renderer, $prev, $next);
1275 $prev_class = '.panels-flexible-' . $item['type'] . '-' . $cache->display->did . '-' . $prev;
1276 $output[] = ctools_ajax_command_after($prev_class, $splitter);
1277 // Send our fix height command.
1278 $output[] = array('command' => 'flexible_fix_height');
1279 }
1280 // Rerender our parent item links:
1281 $output[] = ctools_ajax_command_replace('.flexible-links-' . $item['parent'],
1282 panels_flexible_render_item_links($renderer, $item['parent'], $settings['items'][$item['parent']]));
1283
1284 $output[] = array(
1285 'command' => 'flexible_fix_firstlast',
1286 'selector' => $parent_class . '-inside',
1287 'base' => 'panels-flexible-' . $item['type'],
1288 );
1289
1290 ctools_ajax_render($output);
1291 }
1292
1293 /**
1294 * AJAX responder to store resize information when the user adjusts the
1295 * splitter.
1296 */
1297 function panels_ajax_flexible_edit_resize($cache) {
1298 ctools_include('ajax');
1299 $settings = &$cache->display->layout_settings;
1300 panels_flexible_convert_settings($settings);
1301
1302 $settings['items'][$_POST['left']]['width'] = $_POST['left_width'];
1303 if (!empty($_POST['right']) && $_POST['right'] != $_POST['left']) {
1304 $settings['items'][$_POST['right']]['width'] = $_POST['right_width'];
1305 }
1306
1307 // Save our new state.
1308 panels_load_include('display-edit');
1309 panels_edit_cache_set($cache);
1310
1311 ctools_ajax_render(array('ok'));
1312 }
1313