1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16: class Horde_Core_Block_Layout_Manager extends Horde_Core_Block_Layout implements Countable
17: {
18: 19: 20: 21: 22:
23: protected $_collection;
24:
25: 26: 27: 28: 29:
30: protected $_blocks = array();
31:
32: 33: 34: 35: 36:
37: protected $_columns = 0;
38:
39: 40: 41: 42: 43:
44: protected $_layout = array();
45:
46: 47: 48: 49: 50:
51: protected $_updated = false;
52:
53: 54: 55: 56: 57:
58: protected $_currentBlock = array(null, null);
59:
60: 61: 62: 63: 64:
65: protected $_changedRow = null;
66:
67: 68: 69: 70: 71:
72: protected $_changedCol = null;
73:
74: 75: 76: 77: 78: 79:
80: public function __construct(Horde_Core_Block_Collection $collection)
81: {
82: $this->_collection = $collection;
83: $this->_editUrl = Horde::selfUrl();
84: $this->_layout = $collection->getLayout();
85:
86:
87: $rows = count($this->_layout);
88: $emptyrows = array();
89:
90: for ($row = 0; $row < $rows; $row++) {
91: $cols = count($this->_layout[$row]);
92: if (!isset($emptyrows[$row])) {
93: $emptyrows[$row] = true;
94: }
95:
96: for ($col = 0; $col < $cols; ++$col) {
97: if (isset($this->_layout[$row][$col]) &&
98: is_array($this->_layout[$row][$col])) {
99: $field = $this->_layout[$row][$col];
100:
101: $emptyrows[$row] = false;
102: if (isset($field['width'])) {
103: for ($i = 1; $i < $field['width']; ++$i) {
104: $this->_layout[$row][$col + $i] = 'covered';
105: }
106: }
107: if (isset($field['height'])) {
108: if (!isset($field['width'])) {
109: $field['width'] = 1;
110: }
111:
112: for ($i = 1; $i < $field['height']; ++$i) {
113: $this->_layout[$row + $i][$col] = 'covered';
114: for ($j = 1; $j < $field['width']; $j++) {
115: $this->_layout[$row + $i][$col + $j] = 'covered';
116: }
117: $emptyrows[$row + $i] = false;
118: }
119: }
120: }
121: }
122:
123:
124: for ($col = $cols - 1; $col >= 0; --$col) {
125: if (isset($this->_layout[$row][$col]) &&
126: $this->_layout[$row][$col] != 'empty') {
127: break;
128: }
129: unset($this->_layout[$row][$col]);
130: }
131:
132: $this->_columns = max($this->_columns, count($this->_layout[$row]));
133: }
134:
135:
136: $layout = array();
137: for ($row = 0; $row < $rows; ++$row) {
138: $cols = count($this->_layout[$row]);
139: if ($cols < $this->_columns) {
140: for ($col = $cols; $col < $this->_columns; ++$col) {
141: $this->_layout[$row][$col] = 'empty';
142: }
143: }
144: $layout[] = $this->_layout[$row];
145: }
146:
147: $this->_layout = $layout;
148: }
149:
150: 151: 152: 153: 154:
155: public function serialize()
156: {
157: return serialize($this->_layout);
158: }
159:
160: 161: 162:
163: public function unserialize($data)
164: {
165: $this->_layout = @unserialize($data);
166: }
167:
168: 169: 170: 171: 172: 173: 174: 175: 176: 177:
178: public function handle($action, $row, $col, $url = null)
179: {
180: switch ($action) {
181: case 'moveUp':
182: case 'moveDown':
183: case 'moveLeft':
184: case 'moveRight':
185: case 'expandUp':
186: case 'expandDown':
187: case 'expandLeft':
188: case 'expandRight':
189: case 'shrinkLeft':
190: case 'shrinkRight':
191: case 'shrinkUp':
192: case 'shrinkDown':
193: case 'removeBlock':
194: try {
195: call_user_func(array($this, $action), $row, $col);
196: $this->_updated = true;
197: } catch (Horde_Exception $e) {
198: $GLOBALS['notification']->push($e);
199: }
200: break;
201:
202:
203: case 'save':
204:
205: case 'save-resume':
206:
207: list($newapp, $newtype) = explode(':', Horde_Util::getFormData('app'));
208:
209:
210: $new = false;
211: if ($this->isEmpty($row, $col) ||
212: !$this->rowExists($row) ||
213: !$this->colExists($col)) {
214:
215: $max_blocks = $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('max_blocks');
216: if (($max_blocks !== true) &&
217: ($max_blocks <= count($this))) {
218: Horde::permissionDeniedError(
219: 'horde',
220: 'max_blocks',
221: sprintf(Horde_Core_Translation::ngettext("You are not allowed to create more than %d block.", "You are not allowed to create more than %d blocks.", $max_blocks), $max_blocks)
222: );
223: break;
224: }
225:
226: $new = true;
227:
228: $this->addBlock($row, $col);
229: }
230:
231:
232: $exists = false;
233: $changed = false;
234: if (!$new) {
235:
236: $info = $this->getBlockInfo($row, $col);
237: $exists = $this->isBlock($row, $col);
238:
239: if ($exists &&
240: ($info['app'] != $newapp ||
241: $info['block'] != $newtype)) {
242: $changed = true;
243: }
244: }
245:
246: if ($new || $changed) {
247:
248: $info = array('app' => $newapp,
249: 'block' => $newtype);
250: $params = $this->_collection->getParams($newapp, $newtype);
251: foreach ($params as $newparam) {
252: $info['params'][$newparam] = $this->_collection->getDefaultValue($newapp, $newtype, $newparam);
253: }
254: $this->setBlockInfo($row, $col, $info);
255: } elseif ($exists) {
256:
257: $this->setBlockInfo($row, $col, array('params' => Horde_Util::getFormData('params', array())));
258: }
259: $this->_updated = true;
260: if ($action == 'save') {
261: break;
262: }
263:
264:
265: case 'edit':
266: $this->_currentBlock = array($row, $col);
267: $url = null;
268: break;
269: }
270:
271: if (!empty($url)) {
272: $url = new Horde_Url($url);
273: $url->unique()->redirect();
274: }
275: }
276:
277: 278: 279: 280: 281:
282: public function updated()
283: {
284: return $this->_updated;
285: }
286:
287: 288: 289: 290: 291:
292: public function getCurrentBlock()
293: {
294: return $this->_currentBlock;
295: }
296:
297: 298: 299: 300: 301: 302: 303: 304:
305: public function getBlock($row, $col)
306: {
307: if (!isset($this->_blocks[$row][$col])) {
308: $field = $this->_layout[$row][$col];
309: $this->_blocks[$row][$col] = $GLOBALS['injector']
310: ->getInstance('Horde_Core_Factory_BlockCollection')
311: ->create()
312: ->getBlock($field['app'],
313: $field['params']['type2'],
314: $field['params']['params']);
315: }
316:
317: return $this->_blocks[$row][$col];
318: }
319:
320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330:
331: public function getBlockAt($row, $col)
332: {
333:
334: if ($this->isEmpty($row, $col)) {
335: return null;
336: } elseif (!$this->isCovered($row, $col)) {
337: return array($row, $col);
338: }
339:
340:
341: for ($test = $row - 1; $test >= 0; $test--) {
342: if (!$this->isCovered($test, $col) &&
343: !$this->isEmpty($test, $col) &&
344: $test + $this->getHeight($test, $col) - 1 == $row) {
345: return array($test, $col);
346: }
347: }
348: for ($test = $col - 1; $test >= 0; $test--) {
349: if (!$this->isCovered($row, $test) &&
350: !$this->isEmpty($test, $col) &&
351: $test + $this->getWidth($row, $test) - 1 == $col) {
352: return array($row, $test);
353: }
354: }
355: }
356:
357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371:
372: public function getBlockInfo($row, $col)
373: {
374: if (!isset($this->_layout[$row][$col]) ||
375: $this->isEmpty($row, $col) ||
376: $this->isCovered($row, $col)) {
377: throw new Horde_Exception('No block exists at the requested position');
378: }
379:
380: return array(
381: 'app' => $this->_layout[$row][$col]['app'],
382: 'block' => $this->_layout[$row][$col]['params']['type2'],
383: 'params' => $this->_layout[$row][$col]['params']['params']
384: );
385: }
386:
387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399:
400: public function setBlockInfo($row, $col, $info = array())
401: {
402: if (!isset($this->_layout[$row][$col])) {
403: throw new Horde_Exception('No block exists at the requested position');
404: }
405:
406: if (isset($info['app'])) {
407: $this->_layout[$row][$col]['app'] = $info['app'];
408: }
409: if (isset($info['block'])) {
410: $this->_layout[$row][$col]['params']['type2'] = $info['block'];
411: }
412: if (isset($info['params'])) {
413: $this->_layout[$row][$col]['params']['params'] = $info['params'];
414: }
415:
416: $this->_changedRow = $row;
417: $this->_changedCol = $col;
418: }
419:
420: 421: 422: 423: 424:
425: public function rows()
426: {
427: return count($this->_layout);
428: }
429:
430: 431: 432: 433: 434: 435: 436: 437: 438:
439: public function columns($row)
440: {
441: if (isset($this->_layout[$row])) {
442: return count($this->_layout[$row]);
443: }
444:
445: throw new Horde_Exception(sprintf('The specified row (%d) does not exist.', $row));
446: }
447:
448: 449: 450: 451: 452: 453: 454: 455: 456:
457: public function isEmpty($row, $col)
458: {
459: return !isset($this->_layout[$row][$col]) || $this->_layout[$row][$col] == 'empty';
460: }
461:
462: 463: 464: 465: 466: 467: 468: 469: 470:
471: public function isCovered($row, $col)
472: {
473: return isset($this->_layout[$row][$col])
474: ? $this->_layout[$row][$col] == 'covered'
475: : false;
476: }
477:
478: 479: 480: 481: 482: 483: 484: 485: 486: 487:
488: public function isBlock($row, $col)
489: {
490: return ($this->rowExists($row) &&
491: $this->colExists($col) &&
492: !$this->isEmpty($row, $col) &&
493: !$this->isCovered($row, $col));
494: }
495:
496: 497: 498: 499: 500: 501: 502: 503:
504: public function isChanged($row, $col)
505: {
506: return (($this->_changedRow === $row) &&
507: ($this->_changedCol === $col));
508: }
509:
510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523:
524: public function getControl($type, $row, $col)
525: {
526: $type = explode('/', $type);
527: $action = $type[0] . ucfirst($type[1]);
528: $url = $this->getActionUrl($action, $row, $col);
529:
530: switch ($type[0]) {
531: case 'expand':
532: $title = Horde_Core_Translation::t("Expand");
533: $img = 'large_' . $type[1];
534: break;
535:
536: case 'shrink':
537: $title = Horde_Core_Translation::t("Shrink");
538: $img = 'large_';
539:
540: switch ($type[1]) {
541: case 'up':
542: $img .= 'down';
543: break;
544:
545: case 'down':
546: $img .= 'up';
547: break;
548:
549: case 'left':
550: $img .= 'right';
551: break;
552:
553: case 'right':
554: $img .= 'left';
555: break;
556: }
557: break;
558:
559: case 'move':
560: switch ($type[1]) {
561: case 'up':
562: $title = Horde_Core_Translation::t("Move Up");
563: break;
564:
565: case 'down':
566: $title = Horde_Core_Translation::t("Move Down");
567: break;
568:
569: case 'left':
570: $title = Horde_Core_Translation::t("Move Left");
571: break;
572:
573: case 'right':
574: $title = Horde_Core_Translation::t("Move Right");
575: break;
576: }
577:
578: $img = $type[1];
579: break;
580: }
581:
582: return Horde::link($url, $title) .
583: Horde::img('block/' . $img . '.png', $title) . '</a>';
584: }
585:
586: 587: 588: 589: 590: 591: 592:
593: public function rowExists($row)
594: {
595: return $row < count($this->_layout);
596: }
597:
598: 599: 600: 601: 602: 603: 604:
605: public function colExists($col)
606: {
607: return $col < $this->_columns;
608: }
609:
610: 611: 612: 613: 614: 615: 616: 617: 618: 619:
620: public function getWidth($row, $col)
621: {
622: if (!isset($this->_layout[$row][$col]) ||
623: !is_array($this->_layout[$row][$col])) {
624: return 1;
625: }
626: if (!isset($this->_layout[$row][$col]['width'])) {
627: $this->_layout[$row][$col]['width'] = 1;
628: }
629: return $this->_layout[$row][$col]['width'];
630: }
631:
632: 633: 634: 635: 636: 637: 638: 639: 640: 641:
642: public function getHeight($row, $col)
643: {
644: if (!isset($this->_layout[$row][$col]) ||
645: !is_array($this->_layout[$row][$col])) {
646: return 1;
647: }
648: if (!isset($this->_layout[$row][$col]['height'])) {
649: $this->_layout[$row][$col]['height'] = 1;
650: }
651: return $this->_layout[$row][$col]['height'];
652: }
653:
654: 655: 656: 657: 658: 659:
660: public function addBlock($row, $col)
661: {
662: if (!$this->rowExists($row)) {
663: $this->addRow($row);
664: }
665: if (!$this->colExists($col)) {
666: $this->addCol($col);
667: }
668:
669: $this->_layout[$row][$col] = array('app' => null,
670: 'height' => 1,
671: 'width' => 1,
672: 'params' => array('type2' => null,
673: 'params' => array()));
674: }
675:
676: 677: 678: 679: 680:
681: public function addRow($row)
682: {
683: if ($this->_columns > 0) {
684: $this->_layout[$row] = array_fill(0, $this->_columns, 'empty');
685: }
686: }
687:
688: 689: 690: 691: 692:
693: public function addCol($col)
694: {
695: foreach ($this->_layout as $id => $val) {
696: $this->_layout[$id][$col] = 'empty';
697: }
698: ++$this->_columns;
699: }
700:
701: 702: 703: 704: 705: 706:
707: public function removeBlock($row, $col)
708: {
709: $width = $this->getWidth($row, $col);
710: $height = $this->getHeight($row, $col);
711: for ($i = $height - 1; $i >= 0; $i--) {
712: for ($j = $width - 1; $j >= 0; $j--) {
713: $this->_layout[$row + $i][$col + $j] = 'empty';
714: if (!$this->colExists($col + $j + 1)) {
715: $this->removeColIfEmpty($col + $j);
716: }
717: }
718: if (!$this->rowExists($row + $i + 1) && $this->rowExists($row + $i)) {
719: $this->removeRowIfEmpty($row + $i);
720: }
721: }
722:
723: $this->_changedRow = $row;
724: $this->_changedCol = $col;
725:
726: if (!$this->rowExists($row)) {
727: do {
728: --$row;
729: } while ($row >= 0 && $this->removeRowIfEmpty($row));
730: }
731: if (!$this->colExists($col)) {
732: do {
733: $col--;
734: } while ($col >= 0 && $this->removeColIfEmpty($col));
735: }
736: }
737:
738: 739: 740: 741: 742: 743: 744: 745:
746: public function removeRowIfEmpty($row)
747: {
748: if (!$this->rowExists($row)) {
749: return true;
750: }
751:
752: $rows = count($this->_layout[$row]);
753: for ($i = 0; $i < $rows; $i++) {
754: if (isset($this->_layout[$row][$i]) && $this->_layout[$row][$i] != 'empty') {
755: return false;
756: }
757: }
758: unset($this->_layout[$row]);
759:
760: return true;
761: }
762:
763: 764: 765: 766: 767: 768: 769: 770:
771: public function removeColIfEmpty($col)
772: {
773: if (!$this->colExists($col)) {
774: return true;
775: }
776:
777: $cols = count($this->_layout);
778: for ($i = 0; $i < $cols; $i++) {
779: if (isset($this->_layout[$i][$col]) && $this->_layout[$i][$col] != 'empty') {
780: return false;
781: }
782: }
783:
784: for ($i = 0; $i < $cols; $i++) {
785: unset($this->_layout[$i][$col]);
786: }
787:
788: return true;
789: }
790:
791: 792: 793: 794: 795: 796: 797: 798:
799: public function moveUp($row, $col)
800: {
801: if ($this->rowExists($row - 1)) {
802: $width = $this->getWidth($row, $col);
803:
804: for ($i = 0; $i < $width; $i++) {
805: if (!$this->isEmpty($row - 1, $col + $i)) {
806: $in_way = $this->getBlockAt($row - 1, $col + $i);
807: if (!is_null($in_way) &&
808: $in_way[1] == $col &&
809: $this->getWidth($in_way[0], $in_way[1]) == $width) {
810:
811: $rec1 = Horde_Array::getRectangle($this->_layout, $row, $col,
812: $this->getHeight($row, $col), $this->getWidth($row, $col));
813: $rec2 = Horde_Array::getRectangle($this->_layout, $in_way[0], $in_way[1],
814: $this->getHeight($in_way[0], $in_way[1]), $this->getWidth($in_way[0], $in_way[1]));
815: for ($j = 0; $j < count($rec1); $j++) {
816: for ($k = 0; $k < count($rec1[$j]); $k++) {
817: $this->_layout[$in_way[0] + $j][$in_way[1] + $k] = $rec1[$j][$k];
818: }
819: }
820: for ($j = 0; $j < count($rec2); $j++) {
821: for ($k = 0; $k < count($rec2[$j]); $k++) {
822: $this->_layout[$in_way[0] + count($rec1) + $j][$in_way[1] + $k] = $rec2[$j][$k];
823: }
824: }
825: $this->_changedRow = $in_way[0];
826: $this->_changedCol = $in_way[1];
827: return;
828: }
829:
830: throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
831: }
832: }
833:
834: $lastrow = $row + $this->getHeight($row, $col) - 1;
835: for ($i = 0; $i < $width; $i++) {
836: $prev = $this->_layout[$row][$col + $i];
837:
838: $this->_layout[$row - 1][$col + $i] = $prev;
839: $this->_layout[$row][$col + $i] = 'covered';
840:
841: $this->_layout[$lastrow][$col + $i] = 'empty';
842: }
843:
844: if (!$this->rowExists($lastrow + 1)) {
845:
846: $this->removeRowIfEmpty($lastrow);
847: }
848: }
849:
850: $this->_changedRow = $row - 1;
851: $this->_changedCol = $col;
852: }
853:
854: 855: 856: 857: 858: 859: 860: 861:
862: public function moveDown($row, $col)
863: {
864: $width = $this->getWidth($row, $col);
865: $lastrow = $row + $this->getHeight($row, $col);
866: if ($this->rowExists($lastrow)) {
867:
868: for ($i = 0; $i < $width; $i++) {
869: if (!$this->isEmpty($lastrow, $col + $i)) {
870: $in_way = $this->getBlockAt($lastrow, $col + $i);
871: if (!is_null($in_way) &&
872: $in_way[1] == $col &&
873: $this->getWidth($in_way[0], $in_way[1]) == $width) {
874:
875: $rec1 = Horde_Array::getRectangle($this->_layout, $row, $col,
876: $this->getHeight($row, $col), $this->getWidth($row, $col));
877: $rec2 = Horde_Array::getRectangle($this->_layout, $in_way[0], $in_way[1],
878: $this->getHeight($in_way[0], $in_way[1]), $this->getWidth($in_way[0], $in_way[1]));
879: for ($j = 0; $j < count($rec2); $j++) {
880: for ($k = 0; $k < count($rec2[$j]); $k++) {
881: $this->_layout[$row + $j][$col + $k] = $rec2[$j][$k];
882: }
883: }
884: for ($j = 0; $j < count($rec1); $j++) {
885: for ($k = 0; $k < count($rec1[$j]); $k++) {
886: $this->_layout[$row + count($rec2) + $j][$col + $k] = $rec1[$j][$k];
887: }
888: }
889: $this->_changedRow = $in_way[0];
890: $this->_changedCol = $in_way[1];
891: return;
892: }
893:
894: throw new Horde_Exception('Shrink or move neighbouring block(s) out of the way first');
895: }
896: }
897: } else {
898:
899: $this->addRow($lastrow);
900: }
901:
902: for ($i = 0; $i < $width; $i++) {
903: if (!isset($this->_layout[$row][$col + $i])) {
904: continue;
905: }
906: $prev = $this->_layout[$row][$col + $i];
907:
908: $this->_layout[$lastrow][$col + $i] = 'covered';
909:
910: $this->_layout[$row + 1][$col + $i] = $prev;
911: $this->_layout[$row][$col + $i] = 'empty';
912: }
913:
914: $this->_changedRow = $row + 1;
915: $this->_changedCol = $col;
916: }
917:
918: 919: 920: 921: 922: 923: 924:
925: function moveDownBelow($row)
926: {
927: $moved = array();
928: for ($y = count($this->_layout) - 1; $y > $row; $y--) {
929: for ($x = 0; $x < $this->_columns; $x++) {
930: $block = $this->getBlockAt($y, $x);
931: if (empty($block)) {
932: continue;
933: }
934: if (empty($moved[$block[1] . ':' . $block[0]])) {
935: try {
936: $result = $this->moveDown($block[0], $block[1]);
937: } catch (Horde_Exception $e) {
938: return false;
939: }
940: $moved[$block[1] . ':' . ($block[0] + 1)] = true;
941: }
942: }
943: }
944:
945: return true;
946: }
947:
948: 949: 950: 951: 952: 953: 954: 955:
956: public function moveLeft($row, $col)
957: {
958: if ($this->colExists($col - 1)) {
959: $height = $this->getHeight($row, $col);
960:
961: for ($i = 0; $i < $height; $i++) {
962: if (!$this->isEmpty($row + $i, $col - 1)) {
963: $in_way = $this->getBlockAt($row + $i, $col - 1);
964: if (!is_null($in_way) &&
965: $in_way[0] == $row &&
966: $this->getHeight($in_way[0], $in_way[1]) == $height) {
967:
968: $rec1 = Horde_Array::getRectangle($this->_layout, $row, $col,
969: $this->getHeight($row, $col), $this->getWidth($row, $col));
970: $rec2 = Horde_Array::getRectangle($this->_layout, $in_way[0], $in_way[1],
971: $this->getHeight($in_way[0], $in_way[1]), $this->getWidth($in_way[0], $in_way[1]));
972: for ($j = 0; $j < count($rec1); $j++) {
973: for ($k = 0; $k < count($rec1[$j]); $k++) {
974: $this->_layout[$in_way[0] + $j][$in_way[1] + $k] = $rec1[$j][$k];
975: }
976: }
977: for ($j = 0; $j < count($rec2); $j++) {
978: for ($k = 0; $k < count($rec2[$j]); $k++) {
979: $this->_layout[$in_way[0] + $j][$in_way[1] + count($rec1[$j]) + $k] = $rec2[$j][$k];
980: }
981: }
982: $this->_changedRow = $in_way[0];
983: $this->_changedCol = $in_way[1];
984: return;
985: }
986:
987: throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
988: }
989: }
990:
991: $lastcol = $col + $this->getWidth($row, $col) - 1;
992: for ($i = 0; $i < $height; $i++) {
993: if (!isset($this->_layout[$row + $i][$col])) {
994: continue;
995: }
996: $prev = $this->_layout[$row + $i][$col];
997:
998: $this->_layout[$row + $i][$col - 1] = $prev;
999: $this->_layout[$row + $i][$col] = 'covered';
1000:
1001: $this->_layout[$row + $i][$lastcol] = 'empty';
1002: }
1003:
1004: if (!$this->colExists($lastcol + 1)) {
1005:
1006: $this->removeColIfEmpty($lastcol);
1007: }
1008:
1009: $this->_changedRow = $row;
1010: $this->_changedCol = $col - 1;
1011: }
1012: }
1013:
1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021:
1022: public function moveRight($row, $col)
1023: {
1024: $height = $this->getHeight($row, $col);
1025: $lastcol = $col + $this->getWidth($row, $col);
1026: if ($this->colExists($lastcol)) {
1027:
1028: for ($i = 0; $i < $height; $i++) {
1029: if (!$this->isEmpty($row + $i, $lastcol)) {
1030: $in_way = $this->getBlockAt($row + $i, $lastcol);
1031: if (!is_null($in_way) &&
1032: $in_way[0] == $row &&
1033: $this->getHeight($in_way[0], $in_way[1]) == $height) {
1034:
1035: $rec1 = Horde_Array::getRectangle($this->_layout, $row, $col,
1036: $this->getHeight($row, $col), $this->getWidth($row, $col));
1037: $rec2 = Horde_Array::getRectangle($this->_layout, $in_way[0], $in_way[1],
1038: $this->getHeight($in_way[0], $in_way[1]), $this->getWidth($in_way[0], $in_way[1]));
1039: for ($j = 0; $j < count($rec2); $j++) {
1040: for ($k = 0; $k < count($rec2[$j]); $k++) {
1041: $this->_layout[$row + $j][$col + $k] = $rec2[$j][$k];
1042: }
1043: }
1044: for ($j = 0; $j < count($rec1); $j++) {
1045: for ($k = 0; $k < count($rec1[$j]); $k++) {
1046: $this->_layout[$row + $j][$col + count($rec2[$j]) + $k] = $rec1[$j][$k];
1047: }
1048: }
1049: $this->_changedRow = $in_way[0];
1050: $this->_changedCol = $in_way[1];
1051: return;
1052: }
1053:
1054: throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
1055: }
1056: }
1057: } else {
1058:
1059: $this->addCol($lastcol);
1060: }
1061:
1062: for ($i = 0; $i < $height; $i++) {
1063: if (!isset($this->_layout[$row + $i][$col])) {
1064: continue;
1065: }
1066: $prev = $this->_layout[$row + $i][$col];
1067:
1068: $this->_layout[$row + $i][$lastcol] = 'covered';
1069:
1070: $this->_layout[$row + $i][$col + 1] = $prev;
1071: $this->_layout[$row + $i][$col] = 'empty';
1072: }
1073:
1074: $this->_changedRow = $row;
1075: $this->_changedCol = $col + 1;
1076: }
1077:
1078: 1079: 1080: 1081: 1082: 1083: 1084:
1085: public function moveRightAfter($col)
1086: {
1087: $moved = array();
1088: for ($x = $this->_columns - 1; $x > $col; $x--) {
1089: for ($y = 0; $y < count($this->_layout); $y++) {
1090: $block = $this->getBlockAt($y, $x);
1091: if (empty($block)) {
1092: continue;
1093: }
1094: if (empty($moved[$block[1] . ':' . $block[0]])) {
1095: try {
1096: $result = $this->moveRight($block[0], $block[1]);
1097: } catch (Horde_Exception $e) {
1098: return false;
1099: }
1100: $moved[($block[1] + 1) . ':' . $block[0]] = true;
1101: }
1102: }
1103: }
1104: return true;
1105: }
1106:
1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114:
1115: public function expandUp($row, $col)
1116: {
1117: if ($this->rowExists($row - 1)) {
1118: $width = $this->getWidth($row, $col);
1119:
1120: for ($i = 0; $i < $width; $i++) {
1121: if (!$this->isEmpty($row - 1, $col + $i)) {
1122: if (!$this->moveDownBelow($row - 1)) {
1123: throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
1124: } else {
1125: $row++;
1126: }
1127: }
1128: }
1129:
1130: for ($i = 0; $i < $width; $i++) {
1131: $this->_layout[$row - 1][$col + $i] = $this->_layout[$row][$col + $i];
1132: $this->_layout[$row][$col + $i] = 'covered';
1133: }
1134: $this->_layout[$row - 1][$col]['height'] = $this->getHeight($row - 1, $col) + 1;
1135:
1136: $this->_changedRow = $row - 1;
1137: $this->_changedCol = $col;
1138: }
1139: }
1140:
1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148:
1149: public function expandDown($row, $col)
1150: {
1151: $width = $this->getWidth($row, $col);
1152: $lastrow = $row + $this->getHeight($row, $col) - 1;
1153: if (!$this->rowExists($lastrow + 1)) {
1154:
1155: $this->addRow($lastrow + 1);
1156: for ($i = 0; $i < $width; $i++) {
1157: $this->_layout[$lastrow + 1][$col + $i] = 'covered';
1158: }
1159: $this->_layout[$row][$col]['height'] = $this->getHeight($row, $col) + 1;
1160: } else {
1161:
1162: for ($i = 0; $i < $width; $i++) {
1163: if (!$this->isEmpty($lastrow + 1, $col + $i)) {
1164: if (!$this->moveDownBelow($lastrow)) {
1165: throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
1166: }
1167: }
1168: }
1169:
1170: for ($i = 0; $i < $width; $i++) {
1171: $this->_layout[$lastrow + 1][$col + $i] = 'covered';
1172: }
1173: $this->_layout[$row][$col]['height'] = $this->getHeight($row, $col) + 1;
1174: }
1175:
1176: $this->_changedRow = $row;
1177: $this->_changedCol = $col;
1178: }
1179:
1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187:
1188: public function expandLeft($row, $col)
1189: {
1190: if ($this->colExists($col - 1)) {
1191: $height = $this->getHeight($row, $col);
1192:
1193: for ($i = 0; $i < $height; $i++) {
1194: if (!$this->isEmpty($row + $i, $col - 1)) {
1195: if (!$this->moveRightAfter($col - 1)) {
1196: throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
1197: } else {
1198: $col++;
1199: }
1200: }
1201: }
1202:
1203: for ($i = 0; $i < $height; $i++) {
1204: $this->_layout[$row + $i][$col - 1] = $this->_layout[$row + $i][$col];
1205: $this->_layout[$row + $i][$col] = 'covered';
1206: }
1207: $this->_layout[$row][$col - 1]['width'] = $this->getWidth($row, $col - 1) + 1;
1208:
1209: $this->_changedRow = $row;
1210: $this->_changedCol = $col - 1;
1211: }
1212: }
1213:
1214: 1215: 1216: 1217: 1218: 1219: 1220: 1221:
1222: public function expandRight($row, $col)
1223: {
1224: $height = $this->getHeight($row, $col);
1225: $lastcol = $col + $this->getWidth($row, $col) - 1;
1226: if ($this->colExists($lastcol + 1)) {
1227:
1228: for ($i = 0; $i < $height; $i++) {
1229: if (!$this->isEmpty($row + $i, $lastcol + 1)) {
1230: if (!$this->moveRightAfter($lastcol)) {
1231: throw new Horde_Exception('Shrink or move neighbouring block(s) out of the way first');
1232: }
1233: }
1234: }
1235:
1236: for ($i = 0; $i < $height; $i++) {
1237: $this->_layout[$row + $i][$lastcol + 1] = 'covered';
1238: }
1239: $this->_layout[$row][$col]['width'] = $this->getWidth($row, $col) + 1;
1240: } else {
1241:
1242: $this->addCol($lastcol + 1);
1243: for ($i = 0; $i < $height; $i++) {
1244: $this->_layout[$row + $i][$lastcol + 1] = 'covered';
1245: }
1246: $this->_layout[$row][$col]['width'] = $this->getWidth($row, $col) + 1;
1247: }
1248:
1249: $this->_changedRow = $row;
1250: $this->_changedCol = $col;
1251: }
1252:
1253: 1254: 1255: 1256: 1257: 1258:
1259: public function shrinkUp($row, $col)
1260: {
1261: if ($this->getHeight($row, $col) > 1) {
1262: $width = $this->getWidth($row, $col);
1263: for ($i = 0; $i < $width; $i++) {
1264: $this->_layout[$row + 1][$col + $i] = $this->_layout[$row][$col + $i];
1265: $this->_layout[$row][$col + $i] = 'empty';
1266: }
1267: $this->_layout[$row + 1][$col]['height'] = $this->getHeight($row + 1, $col) - 1;
1268:
1269: $this->_changedRow = $row + 1;
1270: $this->_changedCol = $col;
1271: }
1272: }
1273:
1274: 1275: 1276: 1277: 1278: 1279:
1280: public function shrinkDown($row, $col)
1281: {
1282: if ($this->getHeight($row, $col) > 1) {
1283: $lastrow = $row + $this->getHeight($row, $col) - 1;
1284: $width = $this->getWidth($row, $col);
1285: for ($i = 0; $i < $width; $i++) {
1286: $this->_layout[$lastrow][$col + $i] = 'empty';
1287: }
1288: $this->_layout[$row][$col]['height'] = $this->getHeight($row, $col) - 1;
1289: if (!$this->rowExists($lastrow + 1)) {
1290:
1291: $this->removeRowIfEmpty($lastrow);
1292: }
1293:
1294: $this->_changedRow = $row;
1295: $this->_changedCol = $col;
1296: }
1297: }
1298:
1299: 1300: 1301: 1302: 1303: 1304:
1305: public function shrinkLeft($row, $col)
1306: {
1307: if ($this->getWidth($row, $col) > 1) {
1308: $height = $this->getHeight($row, $col);
1309: for ($i = 0; $i < $height; $i++) {
1310: $this->_layout[$row + $i][$col + 1] = $this->_layout[$row + $i][$col];
1311: $this->_layout[$row + $i][$col] = 'empty';
1312: }
1313: $this->_layout[$row][$col + 1]['width'] = $this->getWidth($row, $col + 1) - 1;
1314:
1315: $this->_changedRow = $row;
1316: $this->_changedCol = $col + 1;
1317: }
1318: }
1319:
1320: 1321: 1322: 1323: 1324: 1325:
1326: public function shrinkRight($row, $col)
1327: {
1328: if ($this->getWidth($row, $col) > 1) {
1329: $lastcol = $col + $this->getWidth($row, $col) - 1;
1330: $height = $this->getHeight($row, $col);
1331: for ($i = 0; $i < $height; $i++) {
1332: $this->_layout[$row + $i][$lastcol] = 'empty';
1333: }
1334: $this->_layout[$row][$col]['width'] = $this->getWidth($row, $col) - 1;
1335: $this->removeColIfEmpty($lastcol);
1336:
1337: $this->_changedRow = $row;
1338: $this->_changedCol = $col;
1339: }
1340: }
1341:
1342:
1343:
1344: 1345: 1346: 1347: 1348:
1349: public function count()
1350: {
1351: $rows = $this->rows();
1352: $count = 0;
1353:
1354: for ($row = 0; $row < $rows; $row++) {
1355: $cols = $this->columns($row);
1356: for ($col = 0; $col < $cols; $col++) {
1357: if (!$this->isEmpty($row, $col) &&
1358: !$this->isCovered($row, $col)) {
1359: ++$count;
1360: }
1361: }
1362: }
1363:
1364: return $count;
1365: }
1366:
1367: }
1368: