Overview

Packages

  • Auth
  • Core
  • Horde
    • Imsp
  • None
  • Notification

Classes

  • Horde
  • Horde_Config
  • Horde_Config_Form
  • Horde_Core_ActiveSync_Connector
  • Horde_Core_ActiveSync_Driver
  • Horde_Core_Ajax_Application
  • Horde_Core_Ajax_Imple
  • Horde_Core_Ajax_Imple_AutoCompleter
  • Horde_Core_Ajax_Imple_Geocoder_Geonames
  • Horde_Core_Ajax_Imple_SpellChecker
  • Horde_Core_Alarm_Handler_Notify
  • Horde_Core_Auth_Application
  • Horde_Core_Auth_Composite
  • Horde_Core_Auth_Ldap
  • Horde_Core_Auth_Msad
  • Horde_Core_Auth_Shibboleth
  • Horde_Core_Auth_Signup_Base
  • Horde_Core_Auth_Signup_Form
  • Horde_Core_Auth_Signup_Null
  • Horde_Core_Auth_Signup_Sql
  • Horde_Core_Auth_Signup_SqlObject
  • Horde_Core_Autoloader_Callback_Mime
  • Horde_Core_Autoloader_Callback_Nls
  • Horde_Core_Block
  • Horde_Core_Block_Collection
  • Horde_Core_Block_Layout
  • Horde_Core_Block_Layout_Manager
  • Horde_Core_Block_Layout_View
  • Horde_Core_Block_Upgrade
  • Horde_Core_Browser
  • Horde_Core_Bundle
  • Horde_Core_Cli
  • Horde_Core_Controller_NotFound
  • Horde_Core_Controller_RequestConfiguration
  • Horde_Core_Controller_RequestMapper
  • Horde_Core_Controller_SettingsFinder
  • Horde_Core_Db_Migration
  • Horde_Core_Factory_ActiveSyncBackend
  • Horde_Core_Factory_ActiveSyncServer
  • Horde_Core_Factory_ActiveSyncState
  • Horde_Core_Factory_Ajax
  • Horde_Core_Factory_Alarm
  • Horde_Core_Factory_Auth
  • Horde_Core_Factory_AuthSignup
  • Horde_Core_Factory_Base
  • Horde_Core_Factory_BlockCollection
  • Horde_Core_Factory_Browser
  • Horde_Core_Factory_Cache
  • Horde_Core_Factory_Crypt
  • Horde_Core_Factory_Data
  • Horde_Core_Factory_Db
  • Horde_Core_Factory_DbBase
  • Horde_Core_Factory_DbPear
  • Horde_Core_Factory_Dns
  • Horde_Core_Factory_Editor
  • Horde_Core_Factory_Facebook
  • Horde_Core_Factory_Group
  • Horde_Core_Factory_History
  • Horde_Core_Factory_HttpClient
  • Horde_Core_Factory_Identity
  • Horde_Core_Factory_Image
  • Horde_Core_Factory_Imple
  • Horde_Core_Factory_Imsp
  • Horde_Core_Factory_ImspAuth
  • Horde_Core_Factory_Injector
  • Horde_Core_Factory_KolabServer
  • Horde_Core_Factory_KolabSession
  • Horde_Core_Factory_KolabStorage
  • Horde_Core_Factory_Ldap
  • Horde_Core_Factory_Lock
  • Horde_Core_Factory_Logger
  • Horde_Core_Factory_LoginTasks
  • Horde_Core_Factory_Mail
  • Horde_Core_Factory_Mapper
  • Horde_Core_Factory_Matcher
  • Horde_Core_Factory_Memcache
  • Horde_Core_Factory_MimeViewer
  • Horde_Core_Factory_Notification
  • Horde_Core_Factory_Perms
  • Horde_Core_Factory_PermsCore
  • Horde_Core_Factory_Prefs
  • Horde_Core_Factory_Request
  • Horde_Core_Factory_Secret
  • Horde_Core_Factory_SessionHandler
  • Horde_Core_Factory_Share
  • Horde_Core_Factory_ShareBase
  • Horde_Core_Factory_Template
  • Horde_Core_Factory_TextFilter
  • Horde_Core_Factory_ThemesCache
  • Horde_Core_Factory_Token
  • Horde_Core_Factory_Tree
  • Horde_Core_Factory_Twitter
  • Horde_Core_Factory_UrlShortener
  • Horde_Core_Factory_Vfs
  • Horde_Core_Factory_View
  • Horde_Core_Factory_Weather
  • Horde_Core_Group_Ldap
  • Horde_Core_Log_Logger
  • Horde_Core_LoginTasks
  • Horde_Core_LoginTasks_Backend_Horde
  • Horde_Core_LoginTasks_SystemTask_Upgrade
  • Horde_Core_Mime_Viewer_Syntaxhighlighter
  • Horde_Core_Mime_Viewer_Vcard
  • Horde_Core_Notification_Event_Status
  • Horde_Core_Notification_Handler_Decorator_Hordelog
  • Horde_Core_Notification_Storage_Session
  • Horde_Core_Perms
  • Horde_Core_Perms_Ui
  • Horde_Core_Prefs_Cache_Session
  • Horde_Core_Prefs_Identity
  • Horde_Core_Prefs_Storage_Configuration
  • Horde_Core_Prefs_Storage_Hooks
  • Horde_Core_Prefs_Storage_Upgrade
  • Horde_Core_Prefs_Ui
  • Horde_Core_Prefs_Ui_Widgets
  • Horde_Core_Share_Driver
  • Horde_Core_Share_FactoryCallback
  • Horde_Core_Sidebar
  • Horde_Core_Text_Filter_Bbcode
  • Horde_Core_Text_Filter_Emails
  • Horde_Core_Text_Filter_Emoticons
  • Horde_Core_Text_Filter_Highlightquotes
  • Horde_Core_Translation
  • Horde_Core_Tree_Html
  • Horde_Core_Tree_Javascript
  • Horde_Core_Tree_Simplehtml
  • Horde_Core_Ui_FlagImage
  • Horde_Core_Ui_JsCalendar
  • Horde_Core_Ui_Language
  • Horde_Core_Ui_Layout
  • Horde_Core_Ui_ModalFormRenderer
  • Horde_Core_Ui_Pager
  • Horde_Core_Ui_Tabs
  • Horde_Core_Ui_TagCloud
  • Horde_Core_Ui_VarRenderer
  • Horde_Core_Ui_VarRenderer_Html
  • Horde_Core_Ui_VarRenderer_TablesetHtml
  • Horde_Core_Ui_Widget
  • Horde_ErrorHandler
  • Horde_Help
  • Horde_Menu
  • Horde_Registry
  • Horde_Registry_Api
  • Horde_Registry_Application
  • Horde_Registry_Caller
  • Horde_Registry_Nlsconfig
  • Horde_Script_Files
  • Horde_Session
  • Horde_Session_Null
  • Horde_Themes
  • Horde_Themes_Cache
  • Horde_Themes_Css
  • Horde_Themes_Element
  • Horde_Themes_Image
  • Horde_Themes_Sound

Exceptions

  • Horde_Exception_HookNotSet
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Provides manipulation of block layouts.
   4:  *
   5:  * Copyright 2003-2012 Horde LLC (http://www.horde.org/)
   6:  *
   7:  * See the enclosed file COPYING for license information (LGPL). If you
   8:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
   9:  *
  10:  * @author   Mike Cochrane <mike@graftonhall.co.nz>
  11:  * @author   Jan Schneider <jan@horde.org>
  12:  * @category Horde
  13:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
  14:  * @package  Core
  15:  */
  16: class Horde_Core_Block_Layout_Manager extends Horde_Core_Block_Layout implements Countable
  17: {
  18:     /**
  19:      * Our Horde_Core_Block_Collection instance.
  20:      *
  21:      * @var Horde_Core_Block_Collection
  22:      */
  23:     protected $_collection;
  24: 
  25:     /**
  26:      * A cache for the block objects.
  27:      *
  28:      * @var array
  29:      */
  30:     protected $_blocks = array();
  31: 
  32:     /**
  33:      * The maximum number of columns.
  34:      *
  35:      * @var integer
  36:      */
  37:     protected $_columns = 0;
  38: 
  39:     /**
  40:      * The current block layout.
  41:      *
  42:      * @var array
  43:      */
  44:     protected $_layout = array();
  45: 
  46:     /**
  47:      * Has the layout been updated since it was instantiated.
  48:      *
  49:      * @var boolean
  50:      */
  51:     protected $_updated = false;
  52: 
  53:     /**
  54:      * The current block (array: [row, col]).
  55:      *
  56:      * @var array
  57:      */
  58:     protected $_currentBlock = array(null, null);
  59: 
  60:     /**
  61:      * The new row of the last changed block.
  62:      *
  63:      * @var integer
  64:      */
  65:     protected $_changedRow = null;
  66: 
  67:     /**
  68:      * The new column of the last changed block.
  69:      *
  70:      * @var integer
  71:      */
  72:     protected $_changedCol = null;
  73: 
  74:     /**
  75:      * Constructor.
  76:      *
  77:      * @param Horde_Core_Block_Collection $collection  TODO
  78:      * @param array $layout                            TODO
  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:         // Fill the _covered caches and empty rows.
  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:             // Strip empty blocks from the end of the rows.
 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:         // Fill all rows up to the same length.
 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:      * Serialize and return the current block layout.
 152:      *
 153:      * @return TODO
 154:      */
 155:     public function serialize()
 156:     {
 157:         return serialize($this->_layout);
 158:     }
 159: 
 160:     /**
 161:      * Resets the current layout to the value stored in the preferences.
 162:      */
 163:     public function unserialize($data)
 164:     {
 165:         $this->_layout = @unserialize($data);
 166:     }
 167: 
 168:     /**
 169:      * Process a modification to the current layout.
 170:      *
 171:      * @param string $action  TODO
 172:      * @param integer $row    TODO
 173:      * @param integer $col    TODO
 174:      * @param string $url     TODO
 175:      *
 176:      * @throws Horde_Exception
 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:         // Save the changes made to a block.
 203:         case 'save':
 204:         // Save the changes made to a block and continue editing.
 205:         case 'save-resume':
 206:             // Get requested block type.
 207:             list($newapp, $newtype) = explode(':', Horde_Util::getFormData('app'));
 208: 
 209:             // Is this a new block?
 210:             $new = false;
 211:             if ($this->isEmpty($row, $col) ||
 212:                 !$this->rowExists($row) ||
 213:                 !$this->colExists($col)) {
 214:                 // Check permissions.
 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:                 // Make sure there is somewhere to put it.
 228:                 $this->addBlock($row, $col);
 229:             }
 230: 
 231:             // Or an existing one?
 232:             $exists = false;
 233:             $changed = false;
 234:             if (!$new) {
 235:                 // Get target block info.
 236:                 $info = $this->getBlockInfo($row, $col);
 237:                 $exists = $this->isBlock($row, $col);
 238:                 // Has a different block been selected?
 239:                 if ($exists &&
 240:                     ($info['app'] != $newapp ||
 241:                      $info['block'] != $newtype)) {
 242:                     $changed = true;
 243:                 }
 244:             }
 245: 
 246:             if ($new || $changed) {
 247:                 // Change app or type.
 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:                 // Change values.
 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:         // Make a block the current block for editing.
 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:      * Has the layout been changed since it was instantiated?
 279:      *
 280:      * @return boolean
 281:      */
 282:     public function updated()
 283:     {
 284:         return $this->_updated;
 285:     }
 286: 
 287:     /**
 288:      * Get the current block row and column.
 289:      *
 290:      * @return array  [row, col]
 291:      */
 292:     public function getCurrentBlock()
 293:     {
 294:         return $this->_currentBlock;
 295:     }
 296: 
 297:     /**
 298:      * Returns the block object at the specified position.
 299:      *
 300:      * @param integer $row  A layout row.
 301:      * @param integer $col  A layout column.
 302:      *
 303:      * @return Horde_Core_Block  The block from that position.
 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:      * Returns the coordinates of the block covering the specified
 322:      * field.
 323:      *
 324:      * @param integer $row  A layout row.
 325:      * @param integer $col  A layout column.
 326:      *
 327:      * @return array  The top-left row-column-coordinate of the block
 328:      *                covering the specified field or null if the field
 329:      *                is empty.
 330:      */
 331:     public function getBlockAt($row, $col)
 332:     {
 333:         /* Trivial cases first. */
 334:         if ($this->isEmpty($row, $col)) {
 335:             return null;
 336:         } elseif (!$this->isCovered($row, $col)) {
 337:             return array($row, $col);
 338:         }
 339: 
 340:         /* This is a covered field. */
 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:      * Returns a hash with some useful information about the specified
 359:      * block.
 360:      *
 361:      * Returned hash values:
 362:      * 'app': application name
 363:      * 'block': block name
 364:      * 'params': parameter hash
 365:      *
 366:      * @param integer $row  A layout row.
 367:      * @param integer $col  A layout column.
 368:      *
 369:      * @return array  The information hash.
 370:      * @throws Horde_Exception
 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:      * Sets a batch of information about the specified block.
 389:      *
 390:      * @param integer $row  A layout row.
 391:      * @param integer $col  A layout column.
 392:      * @param array $info   A hash with information values.
 393:      *                      Possible elements are:
 394:      *                      'app': application name
 395:      *                      'block': block name
 396:      *                      'params': parameter hash
 397:      *
 398:      * @throws Horde_Exception
 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:      * Returns the number of rows in the current layout.
 422:      *
 423:      * @return integer  The number of rows.
 424:      */
 425:     public function rows()
 426:     {
 427:         return count($this->_layout);
 428:     }
 429: 
 430:     /**
 431:      * Returns the number of columns in the specified row of the
 432:      * current layout.
 433:      *
 434:      * @param integer $row  The row to return the number of columns from.
 435:      *
 436:      * @return integer  The number of columns.
 437:      * @throws Horde_Exception
 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:      * Checks to see if a given location if being used by a block.
 450:      *
 451:      * @param integer $row  A layout row.
 452:      * @param integer $col  A layout column.
 453:      *
 454:      * @return boolean  True if the location is empty
 455:      *                  False is the location is being used.
 456:      */
 457:     public function isEmpty($row, $col)
 458:     {
 459:         return !isset($this->_layout[$row][$col]) || $this->_layout[$row][$col] == 'empty';
 460:     }
 461: 
 462:     /**
 463:      * Returns if the field at the specified position is covered by
 464:      * another block.
 465:      *
 466:      * @param integer $row  A layout row.
 467:      * @param integer $col  A layout column.
 468:      *
 469:      * @return boolean  True if the specified field is covered.
 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:      * Returns if the specified location is the top left field of
 480:      * a block.
 481:      *
 482:      * @param integer $row  A layout row.
 483:      * @param integer $col  A layout column.
 484:      *
 485:      * @return boolean  True if the specified position is a block, false if
 486:      *                  the field doesn't exist, is empty or covered.
 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:      * Returns if the specified block has been changed last.
 498:      *
 499:      * @param integer $row  A layout row.
 500:      * @param integer $col  A layout column.
 501:      *
 502:      * @return boolean  True if this block is the last one that was changed.
 503:      */
 504:     public function isChanged($row, $col)
 505:     {
 506:         return (($this->_changedRow === $row) &&
 507:                 ($this->_changedCol === $col));
 508:     }
 509: 
 510:     /**
 511:      * Returns a control (linked arrow) for a certain action on the
 512:      * specified block.
 513:      *
 514:      * @param string $type  A control type in the form
 515:      *                      "modification/direction". Possible values for
 516:      *                      modification: expand, shrink, move. Possible values
 517:      *                      for direction: up, down, left, right.
 518:      * @param integer $row  A layout row.
 519:      * @param integer $col  A layout column.
 520:      *
 521:      * @return string  A link containing an arrow representing the requested
 522:      *                 control.
 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:      * Does a row exist?
 588:      *
 589:      * @param integer $row  The row to look for.
 590:      *
 591:      * @return boolean  True if the row exists.
 592:      */
 593:     public function rowExists($row)
 594:     {
 595:         return $row < count($this->_layout);
 596:     }
 597: 
 598:     /**
 599:      * Does a column exist?
 600:      *
 601:      * @param integer $col  The column to look for.
 602:      *
 603:      * @return boolean  True if the column exists.
 604:      */
 605:     public function colExists($col)
 606:     {
 607:         return $col < $this->_columns;
 608:     }
 609: 
 610:     /**
 611:      * Get the width of the block at a given location.
 612:      * This returns the width if there is a block at this location, otherwise
 613:      * returns 1.
 614:      *
 615:      * @param integer $row  A layout row.
 616:      * @param integer $col  A layout column.
 617:      *
 618:      * @return integer  The number of columns this block spans.
 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:      * Get the height of the block at a given location.
 634:      * This returns the height if there is a block at this location, otherwise
 635:      * returns 1.
 636:      *
 637:      * @param integer $row  A layout row.
 638:      * @param integer $col  A layout column.
 639:      *
 640:      * @return integer  The number of rows this block spans.
 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:      * Adds an empty block at the specified position.
 656:      *
 657:      * @param integer $row  A layout row.
 658:      * @param integer $col  A layout column.
 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:      * Adds a new row to the layout.
 678:      *
 679:      * @param integer $row  The number of the row to add
 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:      * Adds a new column to the layout.
 690:      *
 691:      * @param integer $col  The number of the column to add
 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:      * Removes a block.
 703:      *
 704:      * @param integer $row  A layout row.
 705:      * @param integer $col  A layout column.
 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:      * Removes a row if it's empty.
 740:      *
 741:      * @param integer $row  The number of the row to to check
 742:      *
 743:      * @return boolean  True if the row is now removed.
 744:      *                  False if the row still exists.
 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:      * Removes a column if it's empty.
 765:      *
 766:      * @param integer $col  The number of the column to to check
 767:      *
 768:      * @return boolean  True if the column is now removed.
 769:      *                  False if the column still exists.
 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:      * Moves a block one row up.
 793:      *
 794:      * @param integer $row  A layout row.
 795:      * @param integer $col  A layout column.
 796:      *
 797:      * @throws Horde_Exception
 798:      */
 799:     public function moveUp($row, $col)
 800:     {
 801:         if ($this->rowExists($row - 1)) {
 802:             $width = $this->getWidth($row, $col);
 803:             // See if there's room to move into
 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:                         // We need to swap the blocks.
 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:                     // Nowhere to go.
 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:                 // Move top edge
 838:                 $this->_layout[$row - 1][$col + $i] = $prev;
 839:                 $this->_layout[$row][$col + $i] = 'covered';
 840:                 // Move bottom edge
 841:                 $this->_layout[$lastrow][$col + $i] = 'empty';
 842:             }
 843: 
 844:             if (!$this->rowExists($lastrow + 1)) {
 845:                 // Was on the bottom row
 846:                 $this->removeRowIfEmpty($lastrow);
 847:             }
 848:         }
 849: 
 850:         $this->_changedRow = $row - 1;
 851:         $this->_changedCol = $col;
 852:     }
 853: 
 854:     /**
 855:      * Moves a block one row down.
 856:      *
 857:      * @param integer $row  A layout row.
 858:      * @param integer $col  A layout column.
 859:      *
 860:      * @throws Horde_Exception
 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:             // See if there's room to move into
 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:                         // We need to swap the blocks.
 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:                     // No where to go
 894:                     throw new Horde_Exception('Shrink or move neighbouring block(s) out of the way first');
 895:                 }
 896:             }
 897:         } else {
 898:             // Make room to move into
 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:             // Move bottom edge
 908:             $this->_layout[$lastrow][$col + $i] = 'covered';
 909:             // Move top edge
 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:      * Moves all blocks below a certain row one row down.
 920:      *
 921:      * @param integer $row  A layout row.
 922:      *
 923:      * @return boolean  True if all rows could be moved down.
 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:      * Moves a block one column left.
 950:      *
 951:      * @param integer $row  A layout row.
 952:      * @param integer $col  A layout column.
 953:      *
 954:      * @throws Horde_Exception
 955:      */
 956:     public function moveLeft($row, $col)
 957:     {
 958:         if ($this->colExists($col - 1)) {
 959:             $height = $this->getHeight($row, $col);
 960:             // See if there's room to move into.
 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:                         // We need to swap the blocks.
 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:                     // No where to go
 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:                 // Move left hand edge
 998:                 $this->_layout[$row + $i][$col - 1] = $prev;
 999:                 $this->_layout[$row + $i][$col] = 'covered';
1000:                 // Move right hand edge
1001:                 $this->_layout[$row + $i][$lastcol] = 'empty';
1002:             }
1003: 
1004:             if (!$this->colExists($lastcol + 1)) {
1005:                 // Was on the right-most column
1006:                 $this->removeColIfEmpty($lastcol);
1007:             }
1008: 
1009:             $this->_changedRow = $row;
1010:             $this->_changedCol = $col - 1;
1011:         }
1012:     }
1013: 
1014:     /**
1015:      * Moves a block one column right.
1016:      *
1017:      * @param integer $row  A layout row.
1018:      * @param integer $col  A layout column.
1019:      *
1020:      * @throws Horde_Exception
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:             // See if there's room to move into.
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:                         // We need to swap the blocks.
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:                     // No where to go
1054:                     throw new Horde_Exception('Shrink or move neighboring block(s) out of the way first');
1055:                 }
1056:             }
1057:         } else {
1058:             // Make room to move into.
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:             // Move right hand edge
1068:             $this->_layout[$row + $i][$lastcol] = 'covered';
1069:             // Move left hand edge
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:      * Moves all blocks after a certain column one column right.
1080:      *
1081:      * @param integer $col  A layout column.
1082:      *
1083:      * @return boolean  True if all columns could be moved right.
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:      * Makes a block one row taller by moving the top up.
1109:      *
1110:      * @param integer $row  A layout row.
1111:      * @param integer $col  A layout column.
1112:      *
1113:      * @throws Horde_Exception
1114:      */
1115:     public function expandUp($row, $col)
1116:     {
1117:         if ($this->rowExists($row - 1)) {
1118:             $width = $this->getWidth($row, $col);
1119:             // See if there's room to expand into
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:      * Makes a block one row taller by moving the bottom down.
1143:      *
1144:      * @param integer $row  A layout row.
1145:      * @param integer $col  A layout column.
1146:      *
1147:      * @throws Horde_Exception
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:             // Add a new row.
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:             // See if there's room to expand into
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:      * Makes a block one column wider by moving the left side out.
1182:      *
1183:      * @param integer $row  A layout row.
1184:      * @param integer $col  A layout column.
1185:      *
1186:      * @throws Horde_Exception
1187:      */
1188:     public function expandLeft($row, $col)
1189:     {
1190:         if ($this->colExists($col - 1)) {
1191:             $height = $this->getHeight($row, $col);
1192:             // See if there's room to expand into
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:      * Makes a block one column wider by moving the right side out.
1216:      *
1217:      * @param integer $row  A layout row.
1218:      * @param integer $col  A layout column.
1219:      *
1220:      * @throws Horde_Exception
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:             // See if there's room to expand into
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:             // Add new column
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:      * Makes a block one row lower by moving the top down.
1255:      *
1256:      * @param integer $row  A layout row.
1257:      * @param integer $col  A layout column.
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:      * Makes a block one row lower by moving the bottom up.
1276:      *
1277:      * @param integer $row  A layout row.
1278:      * @param integer $col  A layout column.
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:                 // Was on the bottom row
1291:                 $this->removeRowIfEmpty($lastrow);
1292:             }
1293: 
1294:             $this->_changedRow = $row;
1295:             $this->_changedCol = $col;
1296:         }
1297:     }
1298: 
1299:     /**
1300:      * Makes a block one column narrower by moving the left side in.
1301:      *
1302:      * @param integer $row  A layout row.
1303:      * @param integer $col  A layout column.
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:      * Makes a block one column narrower by moving the right side in.
1322:      *
1323:      * @param integer $row  A layout row.
1324:      * @param integer $col  A layout column.
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:     /* Countable methods. */
1343: 
1344:     /**
1345:      * Returns the number of blocks in the current layout.
1346:      *
1347:      * @return integer  The number of blocks.
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: 
API documentation generated by ApiGen