text: /**
   * The `lockedgrid` component manages one or more child `grid`s that independently scroll
   * in the horizontal axis but are vertically synchronized. The end-user can, using column
   * menus or drag-drop, control which of these {@link #cfg!regions regions} contain which
   * columns.
   *
   * ## Locked Regions
   *
   * The `lockedgrid` always has a `center` {@link Ext.grid.locked.Region region} and by
   * default a `left` and `right` region. These regions are derivatives of `Ext.panel.Panel`
   * (to allow them to be resized and collapsed) and contain normal `grid` with a subset of
   * the overall set of `columns`. All keys in the `regions` config object are valid values
   * for a {@link Ext.grid.column.Column column}'s `locked` config. The values of each of
   * the properties of the `regions` config are configurations for the locked region itself.
   *
   * The layout of the locked regions is a simple `hbox` with the `center` assigned `flex:1`
   * and the non-center regions assigned a width based on the columns contained in that
   * region. The order of items in the container is determined by the `weight` assigned to
   * each region. Regions to the left of center have negative `weight` values, while regions
   * to the right of center have positive `weight` values. This distinction is important
   * primarily to determine the side of the region on which to display the resizer as well
   * as setting the direction of collapse for the region.
   *
   * ## Config and Event Delegation
   *
   * The `lockedgrid` mimics the config properties and events fired by a normal `grid`. It
   * does this in some cases by delegating configs to each child grid. The `regions` config
   * should be used to listen to events or configure a child grid independently when this
   * isn't desired.
   */
  Ext.define('Ext.grid.locked.Grid', {
      extend: 'Ext.Panel',
      xtype: 'lockedgrid',
      alternateClassName: 'Ext.grid.LockedGrid',
      isLockedGrid: true,
  
      requires: [
          'Ext.layout.Box',
          'Ext.layout.Fit',
          'Ext.grid.Grid'
      ],
  
      classCls: Ext.baseCSSPrefix + 'lockedgrid',
  
      config: {
          /**
           * @cfg {Object} columnMenu
           * This is a config object which is used by columns in this grid to create their
           * header menus.
           *
           * The following column menu contains the following items.
           *
           * - Default column menu items {@link Ext.grid.Grid grid}
           * - "Region" menu item to provide locking sub-menu options
           *
           * This can be configured as `null` to prevent columns from showing a column menu.
           */
          columnMenu: {
              items: {
                  region: {
                      text: 'Locked',
                      iconCls: 'fi-lock',
                      menu: {}
                  }
              }
          },
  
          /**
           * @cfg {Ext.grid.column.Column[]} columns (required)
           * An array of column definition objects which define all columns that appear in this grid.
           * Each column definition provides the header text for the column, and a definition of where
           * the data for that column comes from.
           * Column definition can also define the locked property
           *
           * This can also be a configuration object for a {Ext.grid.header.Container HeaderContainer}
           * which may override certain default configurations if necessary. For example, the special
           * layout may be overridden to use a simpler layout, or one can set default values shared
           * by all columns:
           *
           *      columns: {
           *          items: [
           *              {
           *                  text: "Column A"
           *                  dataIndex: "field_A",
           *                  locked: true,
           *                  width: 200
           *              },{
           *                  text: "Column B",
           *                  dataIndex: "field_B",
           *                  width: 150
           *              },
           *              ...
           *          ]
           *      }
           *
           */
          columns: null,
  
          /**
           * @cfg {String} defaultLockedRegion
           * This config determines which region corresponds to `locked: true` on a column.
           */
          defaultLockedRegion: 'left',
  
          /**
           * @cfg {Object} gridDefaults
           * This config is applied to the child `grid` in all regions.
           */
          gridDefaults: null,
  
          /**
           * @cfg {Boolean} hideHeaders
           * @inheritdoc Ext.grid.Grid#cfg!hideHeaders
           */
          hideHeaders: false,
  
          /**
           * @cfg {Object} itemConfig
           * @inheritdoc Ext.grid.Grid#cfg!itemConfig
           */
          itemConfig: {},
  
          /**
           * @cfg {Object} leftGridDefaults
           *  This config is applied to all left-side regions (those of negative 'weight').
           *  To set defaults for all grids within left-side regions configure the `grid` 
           *  property within this config.
           */
          leftRegionDefaults: {
              locked: true,
  
              menuItem: {
                  iconCls: 'fi-chevron-left'
              }
          },
  
          /**
           * @cfg {Object} regions
           * This config determines the set of possible locked regions. Each key name in this
           * object denotes a named region (for example, "left", "right" and "center"). While
           * the set of names is not fixed, meaning a `lockedgrid` can have more than these
           * three regions, there must always be a "center" region. The center regions cannot
           * be hidden or collapsed or emptied of all columns.
           *
           * The values of each property in this object are configuration objects for the
           * {@link Ext.grid.locked.Region region}. The ordering of grids is determined by
           * the `weight` config. Negative values are "left" regions, while positive values
           * are "right" regions. The `menuLabel` is used in the column menu to allow the user
           * to place columns into the region.
           *
           * To add an additional left region:
           *
           *      xtype: 'lockedgrid',
           *      regions: {
           *          left2: {
           *              menuLabel: 'Locked (leftmost)',
           *              weight: -20   // to the left of the standard "left" region
           *          }
           *      }
           */
          regions: {
              left: {
                  menuItem: {
                      text: 'Locked (Left)'
                  },
                  weight: -10
              },
              center: {
                  flex: 1,
                  menuItem: {
                      text: 'Unlocked',
                      iconCls: 'fi-unlock'
                  },
                  weight: 0
              },
              right: {
                  menuItem: {
                      text: 'Locked (Right)'
                  },
                  weight: 10
              }
          },
  
          /**
           * @cfg {Object} rightGridDefaults
           *  This config is applied to all right-side regions (those of positive 'weight').
           *  To set defaults for all grids within right-side regions configure the `grid` 
           *  property within this config.
           */
          rightRegionDefaults: {
              locked: true,
  
              menuItem: {
                  iconCls: 'fi-chevron-right'
              }
          },
  
          /**
           * @cfg {Ext.data.Store/Object/String} store
           * @inheritdoc Ext.grid.Grid#cfg!store
           */
          store: null,
  
          /**
           * @cfg {Boolean} variableHeights
           * @inheritdoc Ext.grid.Grid#cfg!variableHeights
           */
          variableHeights: false,
  
          /**
           * @cfg {Boolean} enableColumnMove
           * Set to `false` to disable header reorder within this grid.
           */
          enableColumnMove: true,
  
          /**
           * @cfg {Boolean} grouped
           * @inheritdoc Ext.dataview.List#cfg!grouped
           */
          grouped: true
      },
  
      weighted: true,
  
      layout: {
          type: 'hbox',
          align: 'stretch'
      },
  
      onRender: function() {
          this.callParent();
  
          this.setupHeaderSync();
  
          this.reconfigure();
      },
  
      doDestroy: function() {
          Ext.undefer(this.partnerTimer);
          this.callParent();
      },
  
      addColumn: function(columns) {
          var me = this,
              map = me.processColumns(columns),
              isArray = Array.isArray(columns),
              ret = isArray ? [] : null,
              grids = me.gridItems,
              len = grids.length,
              v, i, grid, toAdd;
  
          // Instead of just iterating over the map, loop
          // over the grids in order so that we add items
          // in order
          for (i = 0; i < len; ++i) {
              grid = grids[i];
              toAdd = map[grid.regionKey];
  
              if (toAdd) {
                  v = grid.addColumn(toAdd);
  
                  if (isArray) {
                      Ext.Array.push(ret, v);
                  }
                  else {
                      // processColumns always returns an array
                      ret = v[0];
                  }
              }
          }
  
          if (me.getVariableHeights()) {
              me.doRefresh();
          }
  
          return ret;
      },
  
      getHorizontalOverflow: function() {
          var grids = this.visibleGrids,
              n = grids && grids.length,
              i;
  
          for (i = 0; i < n; ++i) {
              if (grids[i].getHorizontalOverflow()) {
                  return true;
              }
          }
  
          return false;
      },
  
      getRegion: function(key) {
          return this.regionMap[key] || null;
      },
  
      getVerticalOverflow: function() {
          var grids = this.visibleGrids,
              n = grids && grids.length;
  
          // Vertical overflow is always on the right-most grid (TODO RTL)
          return n && grids[n - 1].getVerticalOverflow();
      },
  
      insertColumnBefore: function(column, before) {
          var ret;
  
          if (before === null) {
              ret = this.gridMap.center.addColumn(column);
          }
          else {
              ret = before.getParent().insertBefore(column, before);
          }
  
          if (this.getVariableHeights()) {
              this.doRefresh();
          }
  
          return ret;
      },
  
      removeColumn: function(column) {
          var ret = column.getGrid().removeColumn(column);
  
          if (this.getVariableHeights()) {
              this.doRefresh();
          }
  
          return ret;
      },
  
      createLocation: function(location) {
          var grid;
  
          if (location.isGridLocation && location.column) {
              grid = location.column.getGrid();
  
              if (grid.getHidden()) {
                  grid = null;
                  location = location.record;
              }
          }
  
          if (!grid) {
              grid = this.regionMap.center.getGrid();
          }
  
          return grid.createLocation(location);
      },
  
      setLocation: function(location, options) {
          var grid;
  
          if (location.isGridLocation && location.column) {
              grid = location.column.getGrid();
  
              if (grid.getHidden()) {
                  grid = null;
                  location = location.record;
              }
          }
  
          if (!grid) {
              grid = this.regionMap.center.getGrid();
          }
  
          grid.setLocation(location, options);
      },
  
      updateColumns: function(columns) {
          var me = this,
              grids = me.gridItems,
              map, len, i, grid;
  
          if (me.isConfiguring) {
              return;
          }
  
          map = me.processColumns(columns);
  
          ++me.bulkColumnChange;
  
          for (i = 0, len = grids.length; i < len; ++i) {
              grid = grids[i];
              grid.setColumns(map[grid.regionKey] || []);
          }
  
          me.doRefresh();
  
          --me.bulkColumnChange;
      },
  
      updateGrouped: function(value) {
          this.relayConfig('grouped', value);
      },
  
      updateHideHeaders: function(hideHeaders) {
          var me = this;
  
          // destroy first since relay will trigger bogus height change...
          me.headerSync = Ext.destroy(me.headerSync);
  
          me.relayConfig('hideHeaders', hideHeaders);
  
          me.setupHeaderSync();
      },
  
      updateEnableColumnMove: function(enabled) {
          var me = this,
              gridItems, b;
  
          if (me.isConfiguring) {
              return;
          }
  
          gridItems = me.gridItems;
  
          // update grid header reorder 
          for (b = 0; b < gridItems.length; b++) {
              gridItems[b].setEnableColumnMove(enabled);
          }
      },
  
      updateItemConfig: function(itemConfig) {
          this.relayConfig('itemConfig', itemConfig);
      },
  
      updateMaxHeight: function(maxHeight) {
          this.relayConfig('maxHeight', maxHeight);
      },
  
      updateRegions: function(regions) {
          var me = this,
              regionMap = me.regionMap,
              gridDefaults = me.getGridDefaults(),
              variableHeights = me.getVariableHeights(),
              enableColumnMove = me.getEnableColumnMove(),
              key, region, colMap, grid, gridMap,
              prev, scroller, len, i,
              defaultPartner, regionItems, gridItems;
  
          if (regionMap) {
              for (key in regionMap) {
                  me.remove(regionMap[key]);
              }
          }
  
          me.regionMap = regionMap = {};
          me.gridMap = gridMap = {};
  
          colMap = me.processColumns(me.getColumns());
  
          for (key in regions) {
              region = regions[key];
  
              if (region) {
                  region = me.createRegion(key, regions[key], colMap[key], gridDefaults);
                  region = me.add(region);
  
                  grid = region.getGrid();
                  grid.isDefaultPartner = key === me.unlockedKey;
                  grid.setEnableColumnMove(enableColumnMove);
  
                  if (variableHeights) {
                      grid.partnerManager = me;
  
                      if (grid.isDefaultPartner) {
                          me.defaultPartner = defaultPartner = grid;
                      }
                  }
  
                  region.on({
                      scope: me,
                      collapse: 'onRegionCollapse',
                      expand: 'onRegionExpand',
                      hide: 'onRegionHide',
                      show: 'onRegionShow'
                  });
  
                  regionMap[key] = region;
                  gridMap[key] = grid;
  
                  scroller = grid.getScrollable();
  
                  if (scroller) {
                      if (prev) {
                          prev.addPartner(scroller, 'y');
                      }
  
                      prev = scroller;
                  }
  
                  me.setupGrid(grid);
              }
          }
  
          // We don't add to this in the loop above because we want
          // the items in weighted order, so wait til everything is
          // added and in order
          me.regionItems = regionItems = me.query('>[isLockedGridRegion]');
          me.gridItems = gridItems = [];
  
          for (i = 0, len = regionItems.length; i < len; ++i) {
              grid = regionItems[i].getGrid();
              gridItems.push(grid);
  
              if (defaultPartner && grid !== defaultPartner) {
                  grid.renderInfo = defaultPartner.renderInfo;
              }
          }
  
          me.setupHeaderSync();
      },
  
      applyStore: function(store) {
          return store ? Ext.data.StoreManager.lookup(store) : null;
      },
  
      updateStore: function(store) {
          this.store = store;
  
          this.relayConfig('store', store);
      },
  
      updateVariableHeights: function(variableHeights) {
          this.relayConfig('variableHeights', variableHeights);
      },
  
      registerActionable: function(actionable) {
          var me = this,
              actionables = me.actionables || (me.actionables = []),
              gridItems = me.gridItems,
              i;
  
          if (!Ext.Array.contains(actionables, actionable)) {
              actionables.push(actionable);
  
              if (gridItems) {
                  for (i = gridItems.length; i-- > 0; /* empty */) {
                      gridItems[i].registerActionable(actionable);
                  }
              }
          }
      },
  
      unregisterActionable: function(actionable) {
          var actionables = this.actionables,
              gridItems = this.gridItems,
              i;
  
          if (actionables) {
              Ext.Array.remove(actionables, actionable);
  
              if (gridItems) {
                  for (i = gridItems.length; i-- > 0; /* empty */) {
                      gridItems[i].registerActionable(actionable);
                  }
              }
          }
      },
  
      statics: {
          relayGridMethod: function(name, collection, key, defaultResult) {
              collection = collection || 'visibleGrids';
              key = key || 0;
  
              if (defaultResult == null) {
                  defaultResult = null;
              }
  
              this.prototype[name] = function() {
                  var grid = this[collection],
                      ret = defaultResult;
  
                  grid = grid && grid[key];
  
                  if (grid) {
                      if (grid.isLockedGridRegion) {
                          grid = grid.getGrid();
                      }
  
                      ret = grid[name].apply(grid, arguments);
                  }
  
                  return ret;
              };
          },
  
          relayGridMethods: function(descr) {
              var simple = [],
                  name, options;
  
              for (name in descr) {
                  options = descr[name];
  
                  if (options === true) {
                      options = simple;
                      simple[0] = name;
                  }
                  else {
                      options = options.slice();
                      options.unshift(name);
                  }
  
                  this.relayGridMethod.apply(this, options);
              }
          }
      },
  
      privates: {
          bulkColumnChange: 0,
          partnerOffset: 200,
          itemConfiguring: false,
          lastPartnerRequest: 0,
          unlockedKey: 'center',
  
          claimActivePartner: function(partner) {
              var me = this,
                  now = Date.now(),
                  active = me.activePartner;
  
              me.partnerTimer = Ext.undefer(me.partnerTimer);
  
              if (!active || (now - me.lastPartnerRequest > me.partnerOffset)) {
                  me.activePartner = partner;
                  me.lastPartnerRequest = now;
  
                  me.setActivePartner(partner);
              }
          },
  
          configureHeaderHeights: function() {
              var headerSync = this.headerSync;
  
              if (headerSync) {
                  headerSync.sync();
              }
          },
  
          configureItems: function() {
              var me = this,
                  gridItems = me.gridItems,
                  regionItems = me.regionItems,
                  i, found, grid, hide, region;
  
              me.itemConfiguring = true;
  
              for (i = gridItems.length - 1; i >= 0; --i) {
                  grid = gridItems[i];
                  region = regionItems[i];
                  me.setRegionVisibility(region);
                  hide = true;
  
                  if (!found || !grid.getVerticalOverflow()) {
                      // Don't hide the scrollbars on hidden items, the current
                      // logic assumes that anything after the current item has
                      // scrollers visible.
                      hide = false;
                      found = me.isRegionVisible(region);
                  }
  
                  grid.setHideScrollbar(hide);
              }
  
              me.itemConfiguring = false;
          },
  
          configurePartners: function() {
              var me = this,
                  gridItems = this.gridItems,
                  len = gridItems.length,
                  visibleGrids, i, grid;
  
              visibleGrids = gridItems.filter(function(item) {
                  return me.isRegionVisible(item.region);
              });
  
              me.visibleGrids = visibleGrids;
  
              for (i = 0; i < len; ++i) {
                  grid = gridItems[i];
                  grid.allPartners = visibleGrids;
                  grid.partners = visibleGrids.filter(function(item) {
                      return item !== grid;
                  });
              }
          },
  
          createRegion: function(key, cfg, columns, gridDefaults) {
              var me = this,
                  weight = cfg.weight,
                  defaults;
  
              me.fireEvent('createregion', me, columns);
  
              if (weight !== 0) {
                  defaults = weight < 0 ? me.getLeftRegionDefaults() : me.getRightRegionDefaults();
              }
  
              return Ext.merge({
                  xtype: 'lockedgridregion',
                  regionKey: key,
                  lockedGrid: me,
                  grid: Ext.apply({
                      regionKey: key,
                      columnMenu: me.getColumnMenu(),
                      columns: columns,
                      hideHeaders: me.getHideHeaders(),
                      grouped: me.getGrouped(),
                      itemConfig: me.getItemConfig(),
                      store: me.getStore(),
                      variableHeights: me.getVariableHeights()
                  }, gridDefaults)
              }, defaults, cfg);
          },
  
          doHorizontalScrollCheck: function() {
              var grids = this.gridItems,
                  len = grids.length,
                  grid,
                  scroller,
                  i;
  
              for (i = 0; i < len; ++i) {
                  grid = grids[i];
                  scroller = grid.getScrollable();
  
                  if (this.isRegionVisible(grid.region) && scroller) {
                      scroller.setX(grid.getHorizontalOverflow() ? 'scroll' : true);
                  }
              }
          },
  
          doRefresh: function() {
              this.reconfigure();
              this.refreshGrids();
              this.doHorizontalScrollCheck();
              this.doVerticalScrollCheck();
          },
  
          doReleaseActivePartner: function() {
              var me = this;
  
              if (!me.destroyed) {
                  me.lastPartnerRequest = 0;
                  me.activePartner = null;
  
                  me.setActivePartner(me.defaultPartner);
              }
          },
  
          doVerticalScrollCheck: function() {
              var grids = this.gridItems,
                  len = grids.length,
                  grid,
                  scroller,
                  region,
                  i;
  
              for (i = 0; i < len; ++i) {
                  grid = grids[i];
                  scroller = grid.getScrollable();
                  region = grid.region;
  
                  if (region && this.isRegionVisible(region) && scroller) {
                      if (grid.getVerticalOverflow()) {
                          this.setGridScrollers(region, region.isHidden());
                      }
                      else {
                          this.setGridScrollers(false);
                      }
                  }
              }
          },
  
          handleChangeRegion: function(region, column) {
              var me = this,
                  grid = region.getGrid(),
                  gridItems = me.gridItems,
                  newIdx = gridItems.indexOf(grid),
                  oldIdx = gridItems.indexOf(column.getGrid());
  
              // The idea here is to retain the closest position possible.
              // If moving backwards, add it to the end. If moving forwards,
              // add it to the front.
              ++me.bulkColumnChange;
  
              if (newIdx < oldIdx) {
                  grid.addColumn(column);
              }
              else {
                  grid.insertColumn(0, column);
              }
  
              // Refreshing grid on column add or insert.
              grid.syncRowsToHeight(true);
  
              --me.bulkColumnChange;
  
              me.doHorizontalScrollCheck();
              me.doVerticalScrollCheck();
          },
  
          handleRegionVisibilityChange: function(region, hiding) {
              var me = this;
  
              if (!me.itemConfiguring) {
                  me.configurePartners();
                  me.refreshGrids();
                  me.setGridScrollers(region, hiding);
                  me.configureHeaderHeights();
              }
          },
  
          isActivePartner: function(grid) {
              var active = this.activePartner;
  
              return active ? grid === active : grid.isDefaultPartner;
          },
  
          isHeaderVisible: function(header) {
              return this.isRegionVisible(header.getGrid().region);
          },
  
          isRegionVisible: function(region) {
              return !region.hasHiddenContent();
          },
  
          isLastVisibleRegion: function(region) {
              var regions = this.regionItems,
                  index = regions.indexOf(region),
                  other, i;
  
              for (i = regions.length - 1; i > index; --i) {
                  other = regions[i];
  
                  if (!other.hasHiddenContent()) {
                      return false;
                  }
              }
  
              return true;
          },
  
          onBeforeShowColumnMenu: function(grid, column, menu) {
              var regions = this.regionItems,
                  len = regions.length,
                  current = grid.region,
                  disabled = false,
                  items, region, i;
  
              menu = menu.getComponent('region');
  
              if (menu) {
                  menu = menu.getMenu();
                  menu.removeAll();
  
                  items = [];
  
                  disabled = !!(grid.isDefaultPartner && grid.getVisibleColumns().length === 1);
  
                  for (i = 0; i < len; ++i) {
                      region = regions[i];
                      items.push(Ext.applyIf({
                          disabled: disabled || region === current,
                          handler: this.handleChangeRegion.bind(this, region, column)
                      }, region.getMenuItem()));
                  }
  
                  menu.add(items);
              }
          },
  
          onColumnAdd: function(grid) {
              if (!this.setRegionVisibility(grid.region)) {
                  this.refreshGrids();
              }
  
              this.configureHeaderHeights();
          },
  
          onColumnHide: function(grid) {
              if (!this.setRegionVisibility(grid.region)) {
                  this.refreshGrids();
              }
  
              this.configureHeaderHeights();
          },
  
          onColumnRemove: function(grid, column) {
              var me = this;
  
              me.fireEvent('columnremove', grid, column);
  
              if (!me.setRegionVisibility(grid.region)) {
                  me.refreshGrids();
              }
  
              me.configureHeaderHeights();
          },
  
          onColumnShow: function(grid) {
              if (!this.setRegionVisibility(grid.region)) {
                  this.refreshGrids();
              }
  
              this.configureHeaderHeights();
          },
  
          onGridHorizontalOverflowChange: function() {
              if (!this.bulkColumnChange) {
                  this.doHorizontalScrollCheck();
              }
          },
  
          onGridResize: function(grid) {
              grid.syncRowsToHeight(true);
          },
  
          onGridVerticalOverflowChange: function(grid, value) {
              // We could call doVerticalScrollCheck here but that would cause
              // all grids to update every time this is called
              // seeing that we already know the grid that has changed we can target
              // just one grid per event
              var region = grid.region;
  
              if (value) {
                  this.setGridScrollers(region, region.isHidden());
              }
              else {
                  grid.setHideScrollbar(false);
              }
          },
  
          onRegionCollapse: function(region) {
              this.handleRegionVisibilityChange(region, true);
          },
  
          onRegionExpand: function(region) {
              this.handleRegionVisibilityChange(region, false);
          },
  
          onRegionHide: function(region) {
              this.handleRegionVisibilityChange(region, true);
          },
  
          onRegionShow: function(region) {
              this.handleRegionVisibilityChange(region, false);
          },
  
          getRegionKey: function(lockedValue) {
              var defaultLocked = this.getDefaultLockedRegion(),
                  key;
  
              if (lockedValue) {
                  key = lockedValue === true ? defaultLocked : lockedValue;
              }
              else {
                  key = this.unlockedKey;
              }
  
              return key;
          },
  
          processColumns: function(columns) {
              var me = this,
                  map = {},
                  len, i, col, locked, key, arr;
  
              if (columns) {
                  if (!Array.isArray(columns)) {
                      columns = [columns];
                  }
  
                  for (i = 0, len = columns.length; i < len; ++i) {
                      col = columns[i];
                      locked = col.locked || (col.getLocked && col.getLocked());
                      key = me.getRegionKey(locked);
                      arr = map[key];
  
                      if (!arr) {
                          map[key] = arr = [];
                      }
  
                      arr.push(col);
                  }
              }
  
              return map;
          },
  
          reconfigure: function() {
              this.configureItems();
              this.configurePartners();
              this.configureHeaderHeights();
          },
  
          refreshGrids: function() {
              var visibleGrids = this.visibleGrids,
                  len = visibleGrids.length,
                  i;
  
              if (!this.rendered) {
                  return;
              }
  
              for (i = 0; i < len; ++i) {
                  visibleGrids[i].syncRowsToHeight(true);
              }
          },
  
          relayConfig: function(name, value) {
              var grids = this.gridItems,
                  i, len, setter;
  
              if (grids && !this.isConfiguring) {
                  setter = Ext.Config.get(name).names.set;
  
                  for (i = 0, len = grids.length; i < len; ++i) {
                      grids[i][setter](value);
                  }
              }
          },
  
          releaseActivePartner: function(partner) {
              var me = this;
  
              if (me.activePartner === partner) {
                  Ext.undefer(me.partnerTimer);
                  me.partnerTimer = Ext.defer(me.doReleaseActivePartner, me.partnerOffset, me);
              }
          },
  
          setActivePartner: function(partner) {
              var visibleGrids = this.visibleGrids;
  
              Ext.Array.remove(visibleGrids, partner);
  
              visibleGrids.unshift(partner);
          },
  
          setGridScrollers: function(region, isHiding) {
              var gridItems = this.gridItems,
                  len = gridItems.length,
                  index, i, grid;
  
              if (this.isLastVisibleRegion(region)) {
                  grid = region.getGrid();
                  // If this is the last visible grid and we're hiding it, the
                  // previous grid needs to show the scroller. Otherwise, this
                  // grid does
                  index = gridItems.indexOf(grid) - (isHiding ? 1 : 0);
  
                  for (i = 0; i < len; ++i) {
                      gridItems[i].setHideScrollbar(grid.getVerticalOverflow() ? i < index : false);
                  }
              }
          },
  
          setRegionVisibility: function(region) {
              var grid = region.getGrid(),
                  hidden = !!region.getHidden();
  
              region.setHidden(grid.getVisibleColumns().length === 0);
  
              return hidden !== region.getHidden();
          },
  
          setupGrid: function(grid) {
              var actionables = this.actionables,
                  i;
  
              if (actionables) {
                  for (i = 0; i < actionables.length; ++i) {
                      grid.registerActionable(actionables[i]);
                  }
              }
  
              grid.on({
                  scope: this,
                  beforeshowcolumnmenu: 'onBeforeShowColumnMenu',
                  columnadd: 'onColumnAdd',
                  columnhide: 'onColumnHide',
                  columnremove: 'onColumnRemove',
                  columnshow: 'onColumnShow',
                  horizontaloverflowchange: 'onGridHorizontalOverflowChange',
                  resize: 'onGridResize',
                  verticaloverflowchange: 'onGridVerticalOverflowChange'
              });
          },
  
          setupHeaderSync: function() {
              var me = this,
                  grids = me.gridItems,
                  headers, i;
  
              if (!me.getHideHeaders() && !me.isConfiguring) {
                  headers = [];
  
                  for (i = 0; i < grids.length; ++i) {
                      headers.push(grids[i].getHeaderContainer());
                  }
  
                  Ext.destroy(me.headerSync);
  
                  me.headerSync = new Ext.util.HeightSynchronizer(
                      headers, me.isHeaderVisible.bind(me));
              }
          }
      }
  }, function(LockedGrid) {
      LockedGrid.relayGridMethods({
          ensureVisible: true,
          gatherData: true,
          getSelections: true,
          mapToItem: true,
          mapToRecord: true,
          mapToRecordIndex: true
      });
  });
  