text: /**
   * This {@link Ext.grid.Grid grid} plugin adds clipboard support to a grid.
   *
   * This class supports the following `{@link Ext.plugin.AbstractClipboard#formats formats}`
   * for grid data:
   *
   *  * `cell` - Complete field data that can be matched to other grids using the same
   *    {@link Ext.data.Model model} regardless of column order.
   *  * `text` - Cell content stripped of HTML tags.
   *  * `html` - Complete cell content, including any rendered HTML tags.
   *  * `raw` - Underlying field values based on `dataIndex`.
   *
   * The `cell` format is not valid for the `{@link Ext.plugin.AbstractClipboard#system system}`
   * clipboard format.
   */
  Ext.define('Ext.grid.plugin.Clipboard', {
      extend: 'Ext.plugin.AbstractClipboard',
  
      alias: 'plugin.clipboard',
      requires: [
          'Ext.util.Format',
          'Ext.util.TSV'
      ],
  
      formats: {
          cell: {
              get: 'getCells'
          },
          html: {
              get: 'getCellData'
          },
          raw: {
              get: 'getCellData',
              put: 'putCellData'
          }
      },
  
      gridListeners: {
          initialize: 'onCmpReady'
      },
  
      getCellData: function(format, erase) {
          var cmp = this.getCmp(),
              selectable = cmp.getSelectable(),
              selection = selectable && selectable.getSelection(),
              ret = [],
              isRaw = format === 'raw',
              isText = format === 'text',
              data, dataIndex, lastRecord, column, record, row;
  
          if (selection) {
              selection.eachCell(function(location, colIdx, rowIdx) {
                  column = location.column;
                  record = location.record;
  
                  // Do not copy data from ignored columns
                  if (column.getIgnoreExport()) {
                      return;
                  }
  
                  if (lastRecord !== record) {
                      lastRecord = record;
                      ret.push(row = []);
                  }
  
                  dataIndex = column.getDataIndex();
                  data = record.data[dataIndex];
  
                  if (!isRaw) {
                      // printValue takes care of not yet rendered cells
                      data = column.printValue(data);
  
                      if (isText) {
                          data = Ext.util.Format.stripTags(data);
                      }
                  }
  
                  row.push(data);
  
                  if (erase && dataIndex) {
                      record.set(dataIndex, null);
                  }
              });
          }
  
          // See decode() comment below
          return Ext.util.TSV.encode(ret);
      },
  
      getCells: function(format, erase) {
          var cmp = this.getCmp(),
              selectable = cmp.getSelectable(),
              selection = selectable && selectable.getSelection(),
              ret = [],
              dataIndex, lastRecord, record, row;
  
          if (selection) {
              selection.eachCell(function(location) {
                  record = location.record;
  
                  if (lastRecord !== record) {
                      lastRecord = record;
                      ret.push(row = {
                          model: record.self,
                          fields: []
                      });
                  }
  
                  dataIndex = location.column.getDataIndex();
  
                  row.fields.push({
                      name: dataIndex,
                      value: record.data[dataIndex]
                  });
  
                  if (erase && dataIndex) {
                      record.set(dataIndex, null);
                  }
              });
          }
  
          return ret;
      },
  
      getTextData: function(format, erase) {
          return this.getCellData(format, erase);
      },
  
      putCellData: function(data, format) {
          var cmp = this.getCmp(),
              // We pass null as field quote here to override default TSV decoding behavior
              // that will try to unquote fields and break if double quote character is
              // encountered in the data. TSV format does not support any kind of field quoting
              // but Ext.util.TSV mistakenly assumed otherwise pre-6.5.3
              values = Ext.util.TSV.decode(data, undefined, null),
              recCount = values.length,
              colCount = recCount ? values[0].length : 0,
              columns = cmp.getHeaderContainer().getVisibleColumns(),
              store = cmp.getStore(),
              maxRowIdx = store ? store.getCount() - 1 : 0,
              maxColIdx = columns.length - 1,
              selectable = cmp.getSelectable(),
              selection = selectable && selectable.getSelection(),
              row, sourceRowIdx, sourceColIdx, column, record, columnIndex, recordIndex,
              dataObject, destination, dataIndex, startColumnIndex, startRecordIndex;
  
          if (maxRowIdx <= 0 || maxColIdx <= 0) {
              return;
          }
  
          if (selection) {
              selection.eachCell(function(c) {
                  destination = c;
  
                  return false;
              });
          }
  
          startColumnIndex = destination ? destination.columnIndex : 0;
          startRecordIndex = destination ? destination.recordIndex : 0;
  
          for (sourceRowIdx = 0; sourceRowIdx < recCount; sourceRowIdx++) {
              row = values[sourceRowIdx];
              recordIndex = startRecordIndex + sourceRowIdx;
  
              // If we are at the end of the destination store, break the row loop.
              if (recordIndex > maxRowIdx) {
                  break;
              }
  
              record = store.getAt(recordIndex);
  
              dataObject = {};
              columnIndex = startColumnIndex;
              sourceColIdx = 0;
  
              // Collect new values in dataObject
              while (sourceColIdx < colCount && columnIndex <= maxColIdx) {
                  column = columns[columnIndex];
                  dataIndex = column.getDataIndex();
  
                  // we skip ignored columns
                  if (!column.getIgnoreExport()) {
                      // paste the content if the column allows us to do that, otherwise we ignore it
                      if (dataIndex && (format === 'raw' || format === 'text')) {
                          dataObject[dataIndex] = row[sourceColIdx];
                      }
  
                      sourceColIdx++;
                  }
  
                  columnIndex++;
              }
  
              // Update the record in one go.
              record.set(dataObject);
          }
      },
  
      putTextData: function(data, format) {
          this.putCellData(data, format);
      },
  
      getTarget: function(comp) {
          return comp.element;
      },
  
      privates: {
          validateAction: function(event) {
              var cmp = this.getCmp(),
                  viewLocation = cmp.getNavigationModel().getLocation(),
                  selectable = cmp.getSelectable(),
                  checkColumn = selectable && selectable.getCheckbox();
  
              // if current location's column is not the checkbox selection column then allow copying
              if (viewLocation && viewLocation.actionable && checkColumn !== viewLocation.column) {
                  return false;
              }
          }
      }
  });
  