text:
/**
 * Handsontable constructor
 * @param rootElement The jQuery element in which Handsontable DOM will be inserted
 * @param settings
 * @constructor
 */
Handsontable.Core = function (rootElement, settings) {
  this.rootElement = rootElement;

  var priv, datamap, grid, selection, editproxy, highlight, autofill, validate, self = this;

  priv = {
    settings: {},
    selStart: null,
    selEnd: null,
    editProxy: false,
    isPopulated: null,
    scrollable: null,
    undoRedo: null,
    extensions: {},
    colToProp: null,
    propToCol: null,
    dataSchema: null,
    dataType: 'array'
  };

  var hasMinWidthProblem = ($.browser.msie && (parseInt($.browser.version, 10) <= 7));
  /**
   * Used to get over IE7 not respecting CSS min-width (and also not showing border around empty cells)
   * @param {Element} td
   */
  this.minWidthFix = function (td) {
    if (hasMinWidthProblem) {
      if (td.className) {
        td.innerHTML = '
' + td.innerHTML + '
';
      }
      else {
        td.innerHTML = '
' + td.innerHTML + '
';
      }
    }
  };

  var hasPositionProblem = ($.browser.msie && (parseInt($.browser.version, 10) <= 7));
  /**
   * Used to get over IE7 returning negative position in demo/buttons.html
   * @param {Object} position
   */
  this.positionFix = function (position) {
    if (hasPositionProblem) {
      if (position.top < 0) {
        position.top = 0;
      }
      if (position.left < 0) {
        position.left = 0;
      }
    }
  };

  datamap = {
    recursiveDuckSchema: function (obj) {
      var schema;
      if ($.isPlainObject(obj)) {
        schema = {};
        for (var i in obj) {
          if (obj.hasOwnProperty(i)) {
            if ($.isPlainObject(obj[i])) {
              schema[i] = datamap.recursiveDuckSchema(obj[i]);
            }
            else {
              schema[i] = null;
            }
          }
        }
      }
      else {
        schema = [];
      }
      return schema;
    },

    recursiveDuckColumns: function (schema, lastCol, parent) {
      var prop, i;
      if (typeof lastCol === 'undefined') {
        lastCol = 0;
        parent = '';
      }
      if ($.isPlainObject(schema)) {
        for (i in schema) {
          if (schema.hasOwnProperty(i)) {
            if (schema[i] === null) {
              prop = parent + i;
              priv.colToProp.push(prop);
              priv.propToCol[prop] = lastCol;
              lastCol++;
            }
            else {
              lastCol = datamap.recursiveDuckColumns(schema[i], lastCol, i + '.');
            }
          }
        }
      }
      return lastCol;
    },

    createMap: function () {
      if (typeof datamap.getSchema() === "undefined") {
        throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`");
      }
      var i, ilen, schema = datamap.getSchema();
      priv.colToProp = [];
      priv.propToCol = {};
      if (priv.settings.columns) {
        for (i = 0, ilen = priv.settings.columns.length; i < ilen; i++) {
          priv.colToProp[i] = priv.settings.columns[i].data;
          priv.propToCol[priv.settings.columns[i].data] = i;
        }
      }
      else {
        datamap.recursiveDuckColumns(schema);
      }
    },

    colToProp: function (col) {
      if (typeof priv.colToProp[col] !== 'undefined') {
        return priv.colToProp[col];
      }
      else {
        return col;
      }
    },

    propToCol: function (prop) {
      if (typeof priv.propToCol[prop] !== 'undefined') {
        return priv.propToCol[prop];
      }
      else {
        return prop;
      }

    },

    getSchema: function () {
      return priv.settings.dataSchema || priv.duckDataSchema;
    },

    /**
     * Creates row at the bottom of the data array
     * @param {Object} [coords] Optional. Coords of the cell before which the new row will be inserted
     */
    createRow: function (coords) {
      var row;
      if (priv.dataType === 'array') {
        row = [];
        for (var c = 0; c < self.colCount; c++) {
          row.push(null);
        }
      }
      else {
        row = $.extend(true, {}, datamap.getSchema());
      }
      if (!coords || coords.row >= self.rowCount) {
        priv.settings.data.push(row);
      }
      else {
        priv.settings.data.splice(coords.row, 0, row);
      }
    },

    /**
     * Creates col at the right of the data array
     * @param {Object} [coords] Optional. Coords of the cell before which the new column will be inserted
     */
    createCol: function (coords) {
      if (priv.dataType === 'object' || priv.settings.columns) {
        throw new Error("Cannot create new column. When data source in an object, you can only have as much columns as defined in first data row, data schema or in the 'columns' setting");
      }
      var r = 0;
      if (!coords || coords.col >= self.colCount) {
        for (; r < self.rowCount; r++) {
          if (typeof priv.settings.data[r] === 'undefined') {
            priv.settings.data[r] = [];
          }
          priv.settings.data[r].push('');
        }
      }
      else {
        for (; r < self.rowCount; r++) {
          priv.settings.data[r].splice(coords.col, 0, '');
        }
      }
    },

    /**
     * Removes row at the bottom of the data array
     * @param {Object} [coords] Optional. Coords of the cell which row will be removed
     * @param {Object} [toCoords] Required if coords is defined. Coords of the cell until which all rows will be removed
     */
    removeRow: function (coords, toCoords) {
      if (!coords || coords.row === self.rowCount - 1) {
        priv.settings.data.pop();
      }
      else {
        priv.settings.data.splice(coords.row, toCoords.row - coords.row + 1);
      }
    },

    /**
     * Removes col at the right of the data array
     * @param {Object} [coords] Optional. Coords of the cell which col will be removed
     * @param {Object} [toCoords] Required if coords is defined. Coords of the cell until which all cols will be removed
     */
    removeCol: function (coords, toCoords) {
      if (priv.dataType === 'object' || priv.settings.columns) {
        throw new Error("cannot remove column with object data source or columns option specified");
      }
      var r = 0;
      if (!coords || coords.col === self.colCount - 1) {
        for (; r < self.rowCount; r++) {
          priv.settings.data[r].pop();
        }
      }
      else {
        var howMany = toCoords.col - coords.col + 1;
        for (; r < self.rowCount; r++) {
          priv.settings.data[r].splice(coords.col, howMany);
        }
      }
    },

    /**
     * Returns single value from the data array
     * @param {Number} row
     * @param {Number} prop
     */
    get: function (row, prop) {
      if (typeof prop === 'string' && prop.indexOf('.') > -1) {
        var sliced = prop.split(".");
        var out = priv.settings.data[row];
        if (!out) {
          return null;
        }
        for (var i = 0, ilen = sliced.length; i < ilen; i++) {
          out = out[sliced[i]];
          if (typeof out === 'undefined') {
            return null;
          }
        }
        return out;
      }
      else {
        return priv.settings.data[row] ? priv.settings.data[row][prop] : null;
      }
    },

    /**
     * Saves single value to the data array
     * @param {Number} row
     * @param {Number} prop
     * @param {String} value
     */
    set: function (row, prop, value) {
      if (typeof prop === 'string' && prop.indexOf('.') > -1) {
        var sliced = prop.split(".");
        var out = priv.settings.data[row];
        for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) {
          out = out[sliced[i]];
        }
        out[sliced[i]] = value;
      }
      else {
        priv.settings.data[row][prop] = value;
      }
    },

    /**
     * Clears the data array
     */
    clear: function () {
      for (var r = 0; r < self.rowCount; r++) {
        for (var c = 0; c < self.colCount; c++) {
          datamap.set(r, datamap.colToProp(c), '');
        }
      }
    },

    /**
     * Returns the data array
     * @return {Array}
     */
    getAll: function () {
      return priv.settings.data;
    },

    /**
     * Returns data range as array
     * @param {Object} start Start selection position
     * @param {Object} end End selection position
     * @return {Array}
     */
    getRange: function (start, end) {
      var r, rlen, c, clen, output = [], row;
      rlen = Math.max(start.row, end.row);
      clen = Math.max(start.col, end.col);
      for (r = Math.min(start.row, end.row); r <= rlen; r++) {
        row = [];
        for (c = Math.min(start.col, end.col); c <= clen; c++) {
          row.push(datamap.get(r, datamap.colToProp(c)));
        }
        output.push(row);
      }
      return output;
    },

    /**
     * Return data as text (tab separated columns)
     * @param {Object} start (Optional) Start selection position
     * @param {Object} end (Optional) End selection position
     * @return {String}
     */
    getText: function (start, end) {
      return SheetClip.stringify(datamap.getRange(start, end));
    }
  };

  grid = {
    /**
     * Alter grid
     * @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col"
     * @param {Object} coords
     * @param {Object} [toCoords] Required only for actions "remove_row" and "remove_col"
     */
    alter: function (action, coords, toCoords) {
      var oldData, newData, changes, r, rlen, c, clen, result;
      oldData = $.extend(true, [], datamap.getAll());

      switch (action) {
        case "insert_row":
          if (self.countRows() < priv.settings.maxRows) {
            datamap.createRow(coords);
            self.view.createRow(coords);
            self.view.renderRow(coords.row);
            self.blockedCols.refresh();
            if (priv.selStart && priv.selStart.row >= coords.row) {
              priv.selStart.row = priv.selStart.row + 1;
              selection.transformEnd(1, 0);
            }
            else {
              selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work
            }
          }
          break;

        case "insert_col":
          if (self.countCols() < priv.settings.maxCols) {
            datamap.createCol(coords);
            self.view.createCol(coords);
            self.view.renderCol(coords.col);
            self.blockedRows.refresh();
            if (priv.selStart && priv.selStart.col >= coords.col) {
              priv.selStart.col = priv.selStart.col + 1;
              selection.transformEnd(0, 1);
            }
            else {
              selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work
            }
          }
          break;

        case "remove_row":
          datamap.removeRow(coords, toCoords);
          self.view.removeRow(coords, toCoords);
          result = grid.keepEmptyRows();
          if (!result) {
            self.blockedCols.refresh();
          }
          selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work
          break;

        case "remove_col":
          datamap.removeCol(coords, toCoords);
          self.view.removeCol(coords, toCoords);
          result = grid.keepEmptyRows();
          if (!result) {
            self.blockedRows.refresh();
          }
          selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work
          break;
      }

      changes = [];
      newData = datamap.getAll();
      for (r = 0, rlen = newData.length; r < rlen; r++) {
        for (c = 0, clen = newData[r].length; c < clen; c++) {
          changes.push([r, c, oldData[r] ? oldData[r][c] : null, newData[r][c]]);
        }
      }
      self.rootElement.triggerHandler("datachange.handsontable", [changes, 'alter']);
    },

    /**
     * Makes sure there are empty rows at the bottom of the table
     * @return recreate {Boolean} TRUE if row or col was added or removed
     */
    keepEmptyRows: function () {
      var r, c, rlen, clen, emptyRows = 0, emptyCols = 0, recreateRows = false, recreateCols = false, val;

      var $tbody = $(priv.tableBody);

      //fix changes that may have come from loadData
      var dlen = priv.settings.data.length;
      while (self.rowCount < dlen) {
        self.view.createRow();
        recreateRows = true;
      }
      while (self.rowCount > dlen) {
        self.view.removeRow();
        recreateRows = true;
      }
      while (self.colCount < self.countCols()) {
        self.view.createCol();
        self.view.renderCol(self.colCount - 1);
        recreateRows = true;
      }
      while (self.colCount > self.countCols()) {
        self.view.removeCol();
        recreateRows = true;
      }

      //count currently empty rows
      rows : for (r = self.countRows() - 1; r >= 0; r--) {
        for (c = 0, clen = self.colCount; c < clen; c++) {
          val = datamap.get(r, datamap.colToProp(c));
          if (val !== '' && val !== null && typeof val !== 'undefined') {
            break rows;
          }
        }
        emptyRows++;
      }

      //should I add empty rows to data source to meet startRows?
      rlen = self.countRows();
      if (rlen < priv.settings.minRows) {
        for (r = 0; r < priv.settings.minRows - rlen; r++) {
          datamap.createRow();
        }
      }

      //should I add empty rows to table view to meet startRows?
      if (self.countRows() < priv.settings.minRows) {
        for (; self.countRows() < priv.settings.minRows; emptyRows++) {
          self.view.createRow();
          self.view.renderRow(self.countRows() - 1);
          recreateRows = true;
        }
      }

      //should I add empty rows to meet minSpareRows?
      if (emptyRows < priv.settings.minSpareRows) {
        for (; emptyRows < priv.settings.minSpareRows && self.countRows() < priv.settings.maxRows; emptyRows++) {
          datamap.createRow();
          self.view.createRow();
          self.view.renderRow(self.countRows() - 1);
          recreateRows = true;
        }
      }

      //should I add empty rows to meet minHeight
      //WARNING! jQuery returns 0 as height() for container which is not :visible. this will lead to a infinite loop
      if (priv.settings.minHeight) {
        if ($tbody.height() > 0 && $tbody.height() <= priv.settings.minHeight) {
          while ($tbody.height() <= priv.settings.minHeight && self.countRows() < priv.settings.maxRows) {
            datamap.createRow();
            self.view.createRow();
            self.view.renderRow(self.countRows() - 1);
            recreateRows = true;
          }
        }
      }

      //count currently empty cols
      if (self.countRows() - 1 > 0) {
        cols : for (c = self.countCols() - 1; c >= 0; c--) {
          for (r = 0; r < self.countRows(); r++) {
            val = datamap.get(r, datamap.colToProp(c));
            if (val !== '' && val !== null && typeof val !== 'undefined') {
              break cols;
            }
          }
          emptyCols++;
        }
      }

      //should I add empty cols to meet minCols?
      if (self.countCols() < priv.settings.minCols) {
        for (; self.countCols() < priv.settings.minCols; emptyCols++) {
          if (!priv.settings.columns) {
            datamap.createCol();
          }
          self.view.createCol();
          self.view.renderCol(self.countCols() - 1);
          recreateCols = true;
        }
      }

      //should I add empty cols to meet minSpareCols?
      if (priv.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
        for (; emptyCols < priv.settings.minSpareCols && self.countCols() < priv.settings.maxCols; emptyCols++) {
          if (!priv.settings.columns) {
            datamap.createCol();
          }
          self.view.createCol();
          self.view.renderCol(self.colCount - 1);
          recreateCols = true;
        }
      }

      //should I add empty rows to meet minWidth
      //WARNING! jQuery returns 0 as width() for container which is not :visible. this will lead to a infinite loop
      if (priv.settings.minWidth) {
        if ($tbody.width() > 0 && $tbody.width() <= priv.settings.minWidth) {
          while ($tbody.width() <= priv.settings.minWidth && self.countCols() < priv.settings.maxCols) {
            if (!priv.settings.columns) {
              datamap.createCol();
            }
            self.view.createCol();
            self.view.renderCol(self.colCount - 1);
            recreateCols = true;
          }
        }
      }

      if (!recreateRows && priv.settings.enterBeginsEditing) {
        for (; ((priv.settings.minRows && self.countRows() > priv.settings.minRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows) && (!priv.settings.minHeight || $tbody.height() - $tbody.find('tr:last').height() - 4 > priv.settings.minHeight)); emptyRows--) {
          self.view.removeRow();
          datamap.removeRow();
          recreateRows = true;
        }
      }

      if (!recreateCols && priv.settings.enterBeginsEditing) {
        for (; ((priv.settings.startCols && self.countCols() > priv.settings.startCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols) && (!priv.settings.minWidth || $tbody.width() - $tbody.find('tr:last').find('td:last').width() - 4 > priv.settings.minWidth)); emptyCols--) {
          if (!priv.settings.columns) {
            datamap.removeCol();
          }
          self.view.removeCol();
          recreateCols = true;
        }
      }

      var rowCount = self.countRows();
      var colCount = self.countCols();

      if (rowCount === 0 || colCount === 0) {
        selection.deselect();
      }

      if (recreateRows && priv.selStart) {
        //if selection is outside, move selection to last row
        if (priv.selStart.row > rowCount - 1) {
          priv.selStart.row = rowCount - 1;
          if (priv.selEnd.row > priv.selStart.row) {
            priv.selEnd.row = priv.selStart.row;
          }
        } else if (priv.selEnd.row > rowCount - 1) {
          priv.selEnd.row = rowCount - 1;
          if (priv.selStart.row > priv.selEnd.row) {
            priv.selStart.row = priv.selEnd.row;
          }
        }
      }

      if (recreateCols && priv.selStart) {
        //if selection is outside, move selection to last row
        if (priv.selStart.col > colCount - 1) {
          priv.selStart.col = colCount - 1;
          if (priv.selEnd.col > priv.selStart.col) {
            priv.selEnd.col = priv.selStart.col;
          }
        } else if (priv.selEnd.col > colCount - 1) {
          priv.selEnd.col = colCount - 1;
          if (priv.selStart.col > priv.selEnd.col) {
            priv.selStart.col = priv.selEnd.col;
          }
        }
      }

      if (recreateRows || recreateCols) {
        selection.refreshBorders();
        self.blockedCols.update();
        self.blockedRows.update();
      }

      return (recreateRows || recreateCols);
    },

    /**
     * Is cell writable
     */
    isCellWritable: function ($td, cellProperties) {
      if (priv.isPopulated) {
        var data = $td.data('readOnly');
        if (typeof data === 'undefined') {
          return !cellProperties.readOnly;
        }
        else {
          return !data;
        }
      }
      return true;
    },

    /**
     * Populate cells at position with 2d array
     * @param {Object} start Start selection position
     * @param {Array} input 2d array
     * @param {Object} [end] End selection position (only for drag-down mode)
     * @param {String} [source="populateFromArray"]
     * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
     */
    populateFromArray: function (start, input, end, source) {
      var r, rlen, c, clen, td, setData = [], current = {};
      rlen = input.length;
      if (rlen === 0) {
        return false;
      }
      current.row = start.row;
      current.col = start.col;
      for (r = 0; r < rlen; r++) {
        if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > self.countRows() - 1) || (current.row >= priv.settings.maxRows)) {
          break;
        }
        current.col = start.col;
        clen = input[r] ? input[r].length : 0;
        for (c = 0; c < clen; c++) {
          if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > self.colCount - 1) || (current.col >= priv.settings.maxCols)) {
            break;
          }
          td = self.view.getCellAtCoords(current);
          if (self.getCellMeta(current.row, current.col).isWritable) {
            var p = datamap.colToProp(current.col);
            setData.push([current.row, p, input[r][c]]);
          }
          current.col++;
          if (end && c === clen - 1) {
            c = -1;
          }
        }
        current.row++;
        if (end && r === rlen - 1) {
          r = -1;
        }
      }
      self.setDataAtCell(setData, null, null, source || 'populateFromArray');
    },

    /**
     * Clears all cells in the grid
     */
    clear: function () {
      var tds = self.view.getAllCells();
      for (var i = 0, ilen = tds.length; i < ilen; i++) {
        $(tds[i]).empty();
        self.minWidthFix(tds[i]);
      }
    },

    /**
     * Returns the top left (TL) and bottom right (BR) selection coordinates
     * @param {Object[]} coordsArr
     * @returns {Object}
     */
    getCornerCoords: function (coordsArr) {
      function mapProp(func, array, prop) {
        function getProp(el) {
          return el[prop];
        }

        if (Array.prototype.map) {
          return func.apply(Math, array.map(getProp));
        }
        return func.apply(Math, $.map(array, getProp));
      }

      return {
        TL: {
          row: mapProp(Math.min, coordsArr, "row"),
          col: mapProp(Math.min, coordsArr, "col")
        },
        BR: {
          row: mapProp(Math.max, coordsArr, "row"),
          col: mapProp(Math.max, coordsArr, "col")
        }
      };
    },

    /**
     * Returns array of td objects given start and end coordinates
     */
    getCellsAtCoords: function (start, end) {
      var corners = grid.getCornerCoords([start, end]);
      var r, c, output = [];
      for (r = corners.TL.row; r <= corners.BR.row; r++) {
        for (c = corners.TL.col; c <= corners.BR.col; c++) {
          output.push(self.view.getCellAtCoords({
            row: r,
            col: c
          }));
        }
      }
      return output;
    }
  };

  this.selection = selection = { //this public assignment is only temporary
    /**
     * Starts selection range on given td object
     * @param td element
     */
    setRangeStart: function (td) {
      selection.deselect();
      priv.selStart = self.view.getCellCoords(td);
      selection.setRangeEnd(td);
    },

    /**
     * Ends selection range on given td object
     * @param {Element} td
     * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to range end
     */
    setRangeEnd: function (td, scrollToCell) {
      var coords = self.view.getCellCoords(td);
      selection.end(coords);
      if (!priv.settings.multiSelect) {
        priv.selStart = coords;
      }
      self.rootElement.triggerHandler("selection.handsontable", [priv.selStart.row, priv.selStart.col, priv.selEnd.row, priv.selEnd.col]);
      self.rootElement.triggerHandler("selectionbyprop.handsontable", [priv.selStart.row, datamap.colToProp(priv.selStart.col), priv.selEnd.row, datamap.colToProp(priv.selEnd.col)]);
      selection.refreshBorders();
      if (scrollToCell !== false) {
        self.view.scrollViewport(td);
      }
    },

    /**
     * Destroys editor, redraws borders around cells, prepares editor
     * @param {Boolean} revertOriginal
     * @param {Boolean} keepEditor
     */
    refreshBorders: function (revertOriginal, keepEditor) {
      if (!keepEditor) {
        editproxy.destroy(revertOriginal);
      }
      if (!selection.isSelected()) {
        return;
      }
      selection.refreshBorderDimensions();
      if (!keepEditor) {
        editproxy.prepare();
      }
    },

    /**
     * Redraws borders around cells
     */
    refreshBorderDimensions: function () {
      if (!selection.isSelected()) {
        return;
      }
      if (autofill.handle) {
        autofill.showHandle();
      }
      priv.currentBorder.appear([priv.selStart]);
      highlight.on();
    },

    /**
     * Setter/getter for selection start
     */
    start: function (coords) {
      if (typeof coords !== 'undefined') {
        priv.selStart = coords;
      }
      return priv.selStart;
    },

    /**
     * Setter/getter for selection end
     */
    end: function (coords) {
      if (typeof coords !== 'undefined') {
        priv.selEnd = coords;
      }
      return priv.selEnd;
    },

    /**
     * Returns information if we have a multiselection
     * @return {Boolean}
     */
    isMultiple: function () {
      return !(priv.selEnd.col === priv.selStart.col && priv.selEnd.row === priv.selStart.row);
    },

    /**
     * Selects cell relative to current cell (if possible)
     */
    transformStart: function (rowDelta, colDelta, force) {
      if (priv.selStart.row + rowDelta > self.rowCount - 1) {
        if (force && priv.settings.minSpareRows > 0) {
          self.alter("insert_row", self.rowCount);
        }
        else if (priv.settings.autoWrapCol && priv.selStart.col + colDelta < self.colCount - 1) {
          rowDelta = 1 - self.rowCount;
          colDelta = 1;
        }
      }
      else if (priv.settings.autoWrapCol && priv.selStart.row + rowDelta < 0 && priv.selStart.col + colDelta >= 0) {
        rowDelta = self.rowCount - 1;
        colDelta = -1;
      }
      if (priv.selStart.col + colDelta > self.colCount - 1) {
        if (force && priv.settings.minSpareCols > 0) {
          self.alter("insert_col", self.colCount);
        }
        else if (priv.settings.autoWrapRow && priv.selStart.row + rowDelta < self.rowCount - 1) {
          rowDelta = 1;
          colDelta = 1 - self.colCount;
        }
      }
      else if (priv.settings.autoWrapRow && priv.selStart.col + colDelta < 0 && priv.selStart.row + rowDelta >= 0) {
        rowDelta = -1;
        colDelta = self.colCount - 1;
      }
      var td = self.view.getCellAtCoords({
        row: (priv.selStart.row + rowDelta),
        col: priv.selStart.col + colDelta
      });
      if (td) {
        selection.setRangeStart(td);
      }
      else {
        selection.setRangeStart(self.view.getCellAtCoords(priv.selStart)); //rerun some routines
      }
    },

    /**
     * Sets selection end cell relative to current selection end cell (if possible)
     */
    transformEnd: function (rowDelta, colDelta) {
      if (priv.selEnd) {
        var td = self.view.getCellAtCoords({
          row: (priv.selEnd.row + rowDelta),
          col: priv.selEnd.col + colDelta
        });
        if (td) {
          selection.setRangeEnd(td);
        }
      }
    },

    /**
     * Returns true if currently there is a selection on screen, false otherwise
     * @return {Boolean}
     */
    isSelected: function () {
      var selEnd = selection.end();
      if (!selEnd || typeof selEnd.row === "undefined") {
        return false;
      }
      return true;
    },

    /**
     * Returns true if coords is within current selection coords
     * @return {Boolean}
     */
    inInSelection: function (coords) {
      if (!selection.isSelected()) {
        return false;
      }
      var sel = grid.getCornerCoords([priv.selStart, priv.selEnd]);
      return (sel.TL.row <= coords.row && sel.BR.row >= coords.row && sel.TL.col <= coords.col && sel.BR.col >= coords.col);
    },

    /**
     * Deselects all selected cells
     */
    deselect: function () {
      if (!selection.isSelected()) {
        return;
      }
      highlight.off();
      priv.currentBorder.disappear();
      if (autofill.handle) {
        autofill.hideHandle();
      }
      selection.end(false);
      editproxy.destroy();
      self.rootElement.triggerHandler('deselect.handsontable');
    },

    /**
     * Select all cells
     */
    selectAll: function () {
      if (!priv.settings.multiSelect) {
        return;
      }
      var tds = self.view.getAllCells();
      if (tds.length) {
        selection.setRangeStart(tds[0]);
        selection.setRangeEnd(tds[tds.length - 1], false);
      }
    },

    /**
     * Deletes data from selected cells
     */
    empty: function () {
      if (!selection.isSelected()) {
        return;
      }
      var corners = grid.getCornerCoords([priv.selStart, selection.end()]);
      var r, c, changes = [];
      for (r = corners.TL.row; r <= corners.BR.row; r++) {
        for (c = corners.TL.col; c <= corners.BR.col; c++) {
          if (self.getCellMeta(r, c).isWritable) {
            changes.push([r, datamap.colToProp(c), '']);
          }
        }
      }
      self.setDataAtCell(changes);
    }
  };

  highlight = {
    /**
     * Create highlight border
     */
    init: function () {
      priv.selectionBorder = new Handsontable.Border(self, {
        className: 'selection',
        bg: true
      });
    },

    /**
     * Show border around selected cells
     */
    on: function () {
      if (!selection.isSelected()) {
        return false;
      }
      if (selection.isMultiple()) {
        priv.selectionBorder.appear([priv.selStart, selection.end()]);
      }
      else {
        priv.selectionBorder.disappear();
      }
    },

    /**
     * Hide border around selected cells
     */
    off: function () {
      if (!selection.isSelected()) {
        return false;
      }
      priv.selectionBorder.disappear();
    }
  };

  this.autofill = autofill = { //this public assignment is only temporary
    handle: null,
    fillBorder: null,

    /**
     * Create fill handle and fill border objects
     */
    init: function () {
      if (!autofill.handle) {
        autofill.handle = new Handsontable.FillHandle(self);
        autofill.fillBorder = new Handsontable.Border(self, {
          className: 'htFillBorder'
        });

        $(autofill.handle.handle).on('dblclick', autofill.selectAdjacent);
      }
      else {
        autofill.handle.disabled = false;
        autofill.fillBorder.disabled = false;
      }

      self.rootElement.on('beginediting.handsontable', function () {
        autofill.hideHandle();
      });

      self.rootElement.on('finishediting.handsontable', function () {
        if (selection.isSelected()) {
          autofill.showHandle();
        }
      });
    },

    /**
     * Hide fill handle and fill border permanently
     */
    disable: function () {
      autofill.handle.disabled = true;
      autofill.fillBorder.disabled = true;
    },

    /**
     * Selects cells down to the last row in the left column, then fills down to that cell
     */
    selectAdjacent: function () {
      var select, data, r, maxR, c;

      if (selection.isMultiple()) {
        select = priv.selectionBorder.corners;
      }
      else {
        select = priv.currentBorder.corners;
      }

      autofill.fillBorder.disappear();

      data = datamap.getAll();
      rows : for (r = select.BR.row + 1; r < self.rowCount; r++) {
        for (c = select.TL.col; c <= select.BR.col; c++) {
          if (data[r][c]) {
            break rows;
          }
        }
        if (!!data[r][select.TL.col - 1] || !!data[r][select.BR.col + 1]) {
          maxR = r;
        }
      }
      if (maxR) {
        autofill.showBorder(self.view.getCellAtCoords({row: maxR, col: select.BR.col}));
        autofill.apply();
      }
    },

    /**
     * Apply fill values to the area in fill border, omitting the selection border
     */
    apply: function () {
      var drag, select, start, end;

      autofill.handle.isDragged = 0;

      drag = autofill.fillBorder.corners;
      if (!drag) {
        return;
      }

      autofill.fillBorder.disappear();

      if (selection.isMultiple()) {
        select = priv.selectionBorder.corners;
      }
      else {
        select = priv.currentBorder.corners;
      }

      if (drag.TL.row === select.TL.row && drag.TL.col < select.TL.col) {
        start = drag.TL;
        end = {
          row: drag.BR.row,
          col: select.TL.col - 1
        };
      }
      else if (drag.TL.row === select.TL.row && drag.BR.col > select.BR.col) {
        start = {
          row: drag.TL.row,
          col: select.BR.col + 1
        };
        end = drag.BR;
      }
      else if (drag.TL.row < select.TL.row && drag.TL.col === select.TL.col) {
        start = drag.TL;
        end = {
          row: select.TL.row - 1,
          col: drag.BR.col
        };
      }
      else if (drag.BR.row > select.BR.row && drag.TL.col === select.TL.col) {
        start = {
          row: select.BR.row + 1,
          col: drag.TL.col
        };
        end = drag.BR;
      }

      if (start) {
        grid.populateFromArray(start, SheetClip.parse(priv.editProxy.val()), end, 'autofill');

        selection.setRangeStart(self.view.getCellAtCoords(drag.TL));
        selection.setRangeEnd(self.view.getCellAtCoords(drag.BR));
      }
      else {
        //reset to avoid some range bug
        selection.refreshBorders();
      }
    },

    /**
     * Show fill handle
     */
    showHandle: function () {
      autofill.handle.appear([priv.selStart, priv.selEnd]);
    },

    /**
     * Hide fill handle
     */
    hideHandle: function () {
      autofill.handle.disappear();
    },

    /**
     * Show fill border
     */
    showBorder: function (td) {
      var coords = self.view.getCellCoords(td);
      var corners = grid.getCornerCoords([priv.selStart, priv.selEnd]);
      if (priv.settings.fillHandle !== 'horizontal' && (corners.BR.row < coords.row || corners.TL.row > coords.row)) {
        coords = {row: coords.row, col: corners.BR.col};
      }
      else if (priv.settings.fillHandle !== 'vertical') {
        coords = {row: corners.BR.row, col: coords.col};
      }
      else {
        return; //wrong direction
      }
      autofill.fillBorder.appear([priv.selStart, priv.selEnd, coords]);
    }
  };

  this.editproxy = editproxy = { //this public assignment is only temporary
    /**
     * Create input field
     */
    init: function () {
      priv.editProxy = $('<textarea class="handsontableInput">');
      priv.editProxyHolder = $('
');
      priv.editProxyHolder.append(priv.editProxy);

      function onClick(event) {
        event.stopPropagation();
      }

      function onCut() {
        setTimeout(function () {
          selection.empty();
        }, 100);
      }

      function onPaste() {
        setTimeout(function () {
          self.rootElement.one("datachange.handsontable", function (event, changes, source) {
            if (changes.length) {
              var last = changes[changes.length - 1];
              var endTd = self.view.getCellAtCoords({row: last[0], col: self.propToCol(last[1])});
              selection.setRangeEnd(endTd);
            }
          });

          var input = priv.editProxy.val().replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, ''), //remove newline from the start and the end of the input
            inputArray = SheetClip.parse(input),
            coords = grid.getCornerCoords([priv.selStart, priv.selEnd]);

          grid.populateFromArray(coords.TL, inputArray, {
            row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row),
            col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col)
          }, 'paste');
        }, 100);
      }

      var $body = $(document.body);

      function onKeyDown(event) {
        if ($body.children('.context-menu-list:visible').length) {
          return;
        }

        priv.lastKeyCode = event.keyCode;
        if (selection.isSelected()) {
          var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
          if (Handsontable.helper.isPrintableChar(event.keyCode) && ctrlDown) {
            if (event.keyCode === 65) { //CTRL + A
              selection.selectAll(); //select all cells
            }
            else if (event.keyCode === 88 && $.browser.opera) { //CTRL + X
              priv.editProxyHolder.triggerHandler('cut'); //simulate oncut for Opera
            }
            else if (event.keyCode === 86 && $.browser.opera) { //CTRL + V
              priv.editProxyHolder.triggerHandler('paste'); //simulate onpaste for Opera
            }
            else if (event.keyCode === 89 || (event.shiftKey && event.keyCode === 90)) { //CTRL + Y or CTRL + SHIFT + Z
              priv.undoRedo && priv.undoRedo.redo();
            }
            else if (event.keyCode === 90) { //CTRL + Z
              priv.undoRedo && priv.undoRedo.undo();
            }
            return;
          }

          var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart;

          switch (event.keyCode) {
            case 38: /* arrow up */
              if (event.shiftKey) {
                selection.transformEnd(-1, 0);
              }
              else {
                selection.transformStart(-1, 0);
              }
              event.preventDefault();
              break;

            case 9: /* tab */
              var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves;
              if (event.shiftKey) {
                selection.transformStart(-tabMoves.row, -tabMoves.col);
              }
              else {
                selection.transformStart(tabMoves.row, tabMoves.col);
              }
              event.preventDefault();
              break;

            case 39: /* arrow right */
              if (event.shiftKey) {
                selection.transformEnd(0, 1);
              }
              else {
                selection.transformStart(0, 1);
              }
              event.preventDefault();
              break;

            case 37: /* arrow left */
              if (event.shiftKey) {
                selection.transformEnd(0, -1);
              }
              else {
                selection.transformStart(0, -1);
              }
              event.preventDefault();
              break;

            case 8: /* backspace */
            case 46: /* delete */
              selection.empty(event);
              event.preventDefault();
              break;

            case 40: /* arrow down */
              if (event.shiftKey) {
                selection.transformEnd(1, 0); //expanding selection down with shift
              }
              else {
                selection.transformStart(1, 0); //move selection down
              }
              event.preventDefault();
              break;

            case 113: /* F2 */
              event.preventDefault(); //prevent Opera from opening Go to Page dialog
              break;

            case 13: /* return/enter */
              var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves;
              if (event.shiftKey) {
                selection.transformStart(-enterMoves.row, -enterMoves.col); //move selection up
              }
              else {
                selection.transformStart(enterMoves.row, enterMoves.col); //move selection down
              }
              event.preventDefault(); //don't add newline to field
              break;

            case 36: /* home */
              if (event.ctrlKey || event.metaKey) {
                rangeModifier(self.view.getCellAtCoords({row: 0, col: priv.selStart.col}));
              }
              else {
                rangeModifier(self.view.getCellAtCoords({row: priv.selStart.row, col: 0}));
              }
              break;

            case 35: /* end */
              if (event.ctrlKey || event.metaKey) {
                rangeModifier(self.view.getCellAtCoords({row: self.rowCount - 1, col: priv.selStart.col}));
              }
              else {
                rangeModifier(self.view.getCellAtCoords({row: priv.selStart.row, col: self.colCount - 1}));
              }
              break;

            case 33: /* pg up */
              rangeModifier(self.view.getCellAtCoords({row: 0, col: priv.selStart.col}));
              break;

            case 34: /* pg dn */
              rangeModifier(self.view.getCellAtCoords({row: self.rowCount - 1, col: priv.selStart.col}));
              break;

            default:
              break;
          }
        }
      }

      priv.editProxy.on('click', onClick);
      priv.editProxyHolder.on('cut', onCut);
      priv.editProxyHolder.on('paste', onPaste);
      priv.editProxyHolder.on('keydown', onKeyDown);
      self.container.append(priv.editProxyHolder);
    },

    /**
     * Destroy current editor, if exists
     * @param {Boolean} revertOriginal
     */
    destroy: function (revertOriginal) {
      if (typeof priv.editorDestroyer === "function") {
        priv.editorDestroyer(revertOriginal);
        priv.editorDestroyer = null;
      }
    },

    /**
     * Prepare text input to be displayed at given grid cell
     */
    prepare: function () {
      priv.editProxy.height(priv.editProxy.parent().innerHeight() - 4);
      priv.editProxy.val(datamap.getText(priv.selStart, priv.selEnd));
      setTimeout(editproxy.focus, 1);
      priv.editorDestroyer = self.view.applyCellTypeMethod('editor', self.view.getCellAtCoords(priv.selStart), priv.selStart, priv.editProxy);
    },

    /**
     * Sets focus to textarea
     */
    focus: function () {
      priv.editProxy[0].select();
    }
  };

  this.init = function () {
    this.view = new Handsontable.TableView(this);

    self.rowCount = 0;

    highlight.init();
    priv.currentBorder = new Handsontable.Border(self, {
      className: 'current',
      bg: true
    });
    editproxy.init();

    bindEvents();
    this.updateSettings(settings);

    Handsontable.PluginHooks.run(self, 'afterInit');
  };

  validate = function (changes, source) {
    var validated = $.Deferred();
    var deferreds = [];

    if (source === 'paste') {
      //validate strict autocompletes
      var process = function (i) {
        var deferred = $.Deferred();
        deferreds.push(deferred);

        var originalVal = changes[i][3];
        var lowercaseVal = typeof originalVal === 'string' ? originalVal.toLowerCase() : null;

        return function (source) {
          var found = false;
          for (var s = 0, slen = source.length; s < slen; s++) {
            if (originalVal === source[s]) {
              found = true; //perfect match
              break;
            }
            else if (lowercaseVal === source[s].toLowerCase()) {
              changes[i][3] = source[s]; //good match, fix the case
              found = true;
              break;
            }
          }
          if (!found) {
            changes[i] = null;
          }
          deferred.resolve();
        }
      };

      for (var i = changes.length - 1; i >= 0; i--) {
        var cellProperties = self.getCellMeta(changes[i][0], changes[i][1]);
        if (cellProperties.strict && cellProperties.source) {
          var items = $.isFunction(cellProperties.source) ? cellProperties.source(changes[i][3], process(i)) : cellProperties.source;
          if (items) {
            process(i)(items)
          }
        }
      }
    }

    $.when(deferreds).then(function () {
      for (var i = changes.length - 1; i >= 0; i--) {
        if (changes[i] === null) {
          changes.splice(i, 1);
        }
      }

      if (priv.settings.onBeforeChange && changes.length) {
        var result = priv.settings.onBeforeChange.apply(self.rootElement[0], [changes, source]);
        if (typeof result === 'function') {
          $.when(result).then(function () {
            validated.resolve();
          });
        }
        else {
          if (result === false) {
            changes.splice(0, changes.length); //invalidate all changes (remove everything from array)
          }
          validated.resolve();
        }
      }
      else {
        validated.resolve();
      }
    });

    return $.when(validated);
  };

  var bindEvents = function () {
    self.rootElement.on("datachange.handsontable", function (event, changes, source) {
      if (priv.settings.onChange) {
        priv.settings.onChange.apply(self.rootElement[0], [changes, source]);
      }
    });
    self.rootElement.on("selection.handsontable", function (event, row, col, endRow, endCol) {
      if (priv.settings.onSelection) {
        priv.settings.onSelection.apply(self.rootElement[0], [row, col, endRow, endCol]);
      }
    });
    self.rootElement.on("selectionbyprop.handsontable", function (event, row, prop, endRow, endProp) {
      if (priv.settings.onSelectionByProp) {
        priv.settings.onSelectionByProp.apply(self.rootElement[0], [row, prop, endRow, endProp]);
      }
    });
  };

  /**
   * Set data at given cell
   * @public
   * @param {Number|Array} row or array of changes in format [[row, col, value], ...]
   * @param {Number} prop
   * @param {String} value
   * @param {String} [source='edit'] String that identifies how this change will be described in changes array (useful in onChange callback)
   */
  this.setDataAtCell = function (row, prop, value, source) {
    var refreshRows = false, refreshCols = false, changes, i, ilen;

    if (typeof row === "object") { //is it an array of changes
      changes = row;
    }
    else if ($.isPlainObject(value)) { //backwards compatibility
      changes = value;
    }
    else {
      changes = [
        [row, prop, value]
      ];
    }

    for (i = 0, ilen = changes.length; i < ilen; i++) {
      changes[i].splice(2, 0, datamap.get(changes[i][0], changes[i][1])); //add old value at index 2
    }

    validate(changes, source).then(function () { //when validate is resolved...
      for (i = 0, ilen = changes.length; i < ilen; i++) {
        row = changes[i][0];
        prop = changes[i][1];
        var col = datamap.propToCol(prop);
        value = changes[i][3];

        if (priv.settings.minSpareRows) {
          while (row > self.rowCount - 1) {
            datamap.createRow();
            self.view.createRow();
            self.view.renderRow(self.rowCount - 1);
            refreshRows = true;
          }
        }
        if (priv.dataType === 'array' && priv.settings.minSpareCols) {
          while (col > self.colCount - 1) {
            datamap.createCol();
            self.view.createCol();
            self.view.renderCol(self.colCount - 1);
            refreshCols = true;
          }
        }
        self.view.render(row, col, prop, value);
        datamap.set(row, prop, value);
      }
      if (refreshRows) {
        self.blockedCols.refresh();
      }
      if (refreshCols) {
        self.blockedRows.refresh();
      }
      var recreated = grid.keepEmptyRows();
      if (!recreated) {
        selection.refreshBorders();
      }
      self.rootElement.triggerHandler("datachange.handsontable", [changes, source || 'edit']);
      self.rootElement.triggerHandler("cellrender.handsontable", [changes, source || 'edit']);
    });
  };

  /**
   * Destroys current editor, renders and selects current cell. If revertOriginal != true, edited data is saved
   * @param {Boolean} revertOriginal
   */
  this.destroyEditor = function (revertOriginal) {
    selection.refreshBorders(revertOriginal);
  };

  /**
   * Populate cells at position with 2d array
   * @param {Object} start Start selection position
   * @param {Array} input 2d array
   * @param {Object} [end] End selection position (only for drag-down mode)
   * @param {String} [source="populateFromArray"]
   * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
   */
  this.populateFromArray = function (start, input, end, source) {
    return grid.populateFromArray(start, input, end, source);
  };

  /**
   * Returns the top left (TL) and bottom right (BR) selection coordinates
   * @param {Object[]} coordsArr
   * @returns {Object}
   */
  this.getCornerCoords = function (coordsArr) {
    return grid.getCornerCoords(coordsArr);
  };

  /**
   * Returns current selection. Returns undefined if there is no selection.
   * @public
   * @return {Array} [topLeftRow, topLeftCol, bottomRightRow, bottomRightCol]
   */
  this.getSelected = function () { //https://github.com/warpech/jquery-handsontable/issues/44  //cjl
    if (selection.isSelected()) {
      var coords = grid.getCornerCoords([priv.selStart, priv.selEnd]);
      return [coords.TL.row, coords.TL.col, coords.BR.row, coords.BR.col];
    }
  };

  /**
   * Render visible data
   * @public
   * @param {Array} changes (Optional) If not given, all visible grid will be rerendered
   * @param {String} source (Optional)
   */
  this.render = function (changes, source) {
    if (typeof changes === "undefined") {
      changes = [];
      var r
        , c
        , p
        , val
        , rlen = self.countRows()
        , clen = (priv.settings.columns && priv.settings.columns.length) || priv.settings.startCols;
      for (r = 0; r < rlen; r++) {
        for (c = 0; c < clen; c++) {
          p = datamap.colToProp(c);
          val = datamap.get(r, p);
          changes.push([r, p, val, val]);
        }
      }
    }
    for (var i = 0, ilen = changes.length; i < ilen; i++) {
      self.view.render(changes[i][0], datamap.propToCol(changes[i][1]), changes[i][1], changes[i][3]);
    }
    selection.refreshBorderDimensions();
    priv.editProxy.triggerHandler('refreshBorder');
    self.rootElement.triggerHandler('cellrender.handsontable', [changes, source || 'render']);
  };

  /**
   * Load data from array
   * @public
   * @param {Array} data
   */
  this.loadData = function (data) {
    priv.isPopulated = false;
    priv.settings.data = data;
    if ($.isPlainObject(priv.settings.dataSchema) || $.isPlainObject(data[0])) {
      priv.dataType = 'object';
    }
    else {
      priv.dataType = 'array';
    }
    if (data[0]) {
      priv.duckDataSchema = datamap.recursiveDuckSchema(data[0]);
    }
    else {
      priv.duckDataSchema = {};
    }
    datamap.createMap();

    var rlen = priv.settings.data.length;
    while (priv.settings.minRows > rlen) {
      datamap.createRow();
      rlen++;
    }

    if (self.colCount === void 0) {
      self.colCount = self.countCols();
    }

    grid.keepEmptyRows();
    grid.clear();
    var changes = [];
    rlen = priv.settings.data.length; //recount number of rows in case some row was removed by keepEmptyRows
    var clen = self.countCols();
    for (var r = 0; r < rlen; r++) {
      for (var c = 0; c < clen; c++) {
        var p = datamap.colToProp(c);
        changes.push([r, p, "", datamap.get(r, p)])
      }
    }
    self.rootElement.triggerHandler('datachange.handsontable', [changes, 'loadData']);
    self.render(changes, 'loadData');
    priv.isPopulated = true;
    self.clearUndo();
  };

  /**
   * Return the current data object (the same that was passed by `data` configuration option or `loadData` method). Optionally you can provide cell range `r`, `c`, `r2`, `c2` to get only a fragment of grid data
   * @public
   * @param {Number} r (Optional) From row
   * @param {Number} c (Optional) From col
   * @param {Number} r2 (Optional) To row
   * @param {Number} c2 (Optional) To col
   * @return {Array|Object}
   */
  this.getData = function (r, c, r2, c2) {
    if (typeof r === 'undefined') {
      return datamap.getAll();
    }
    else {
      return datamap.getRange({row: r, col: c}, {row: r2, col: c2});
    }
  };

  /**
   * Update settings
   * @public
   */
  this.updateSettings = function (settings) {
    var i, j, recreated;

    if (typeof settings.rows !== "undefined") {
      throw new Error("'rows' setting is no longer supported. do you mean startRows, minRows or maxRows?");
    }
    if (typeof settings.cols !== "undefined") {
      throw new Error("'cols' setting is no longer supported. do you mean startCols, minCols or maxCols?");
    }

    if (typeof settings.fillHandle !== "undefined") {
      if (autofill.handle && settings.fillHandle === false) {
        autofill.disable();
      }
      else if (!autofill.handle && settings.fillHandle !== false) {
        autofill.init();
      }
    }

    if (typeof settings.undo !== "undefined") {
      if (priv.undoRedo && settings.undo === false) {
        priv.undoRedo = null;
      }
      else if (!priv.undoRedo && settings.undo === true) {
        priv.undoRedo = new Handsontable.UndoRedo(self);
      }
    }

    if (!self.blockedCols) {
      self.blockedCols = new Handsontable.BlockedCols(self);
      self.blockedRows = new Handsontable.BlockedRows(self);
    }

    for (i in settings) {
      if (i === 'data') {
        continue; //loadData will be triggered later
      }
      else if (settings.hasOwnProperty(i)) {
        priv.settings[i] = settings[i];

        //launch extensions
        if (Handsontable.extension[i]) {
          priv.extensions[i] = new Handsontable.extension[i](self, settings[i]);
        }
      }
    }

    if (priv.settings.data === void 0) {
      if (settings.data === void 0) {
        settings.data = [];
        var row;
        for (var r = 0, rlen = priv.settings.startRows; r < rlen; r++) {
          row = [];
          for (var c = 0, clen = priv.settings.startCols; c < clen; c++) {
            row.push(null);
          }
          settings.data.push(row);
        }
      }
      else {
        if (settings.startRows !== void 0 && settings.minRows === void 0) {
          settings.minRows = settings.startRows;
        }
        if (settings.startCols !== void 0 && settings.minCols === void 0) {
          settings.minCols = settings.startCols;
        }
      }
    }

    if (settings.data !== void 0) {
      self.loadData(settings.data);
    }
    else if (settings.columns !== void 0) {
      datamap.createMap();
    }

    if (typeof settings.colHeaders !== "undefined") {
      if (settings.colHeaders === false && priv.extensions["ColHeader"]) {
        priv.extensions["ColHeader"].destroy();
      }
      else if (settings.colHeaders !== false) {
        priv.extensions["ColHeader"] = new Handsontable.ColHeader(self, settings.colHeaders);
      }
    }

    if (typeof settings.rowHeaders !== "undefined") {
      if (settings.rowHeaders === false && priv.extensions["RowHeader"]) {
        priv.extensions["RowHeader"].destroy();
      }
      else if (settings.rowHeaders !== false) {
        priv.extensions["RowHeader"] = new Handsontable.RowHeader(self, settings.rowHeaders);
      }
    }

    var blockedRowsCount = self.blockedRows.count();
    var blockedColsCount = self.blockedCols.count();
    if (blockedRowsCount && blockedColsCount && (typeof settings.rowHeaders !== "undefined" || typeof settings.colHeaders !== "undefined")) {
      if (self.blockedCorner) {
        self.blockedCorner.remove();
        self.blockedCorner = null;
      }

      var position = self.table.position();
      self.positionFix(position);

      var div = document.createElement('div');
      div.style.position = 'absolute';
      div.style.top = position.top + 'px';
      div.style.left = position.left + 'px';

      var table = document.createElement('table');
      table.cellPadding = 0;
      table.cellSpacing = 0;
      div.appendChild(table);

      var thead = document.createElement('thead');
      table.appendChild(thead);

      var tr, th;
      for (i = 0; i < blockedRowsCount; i++) {
        tr = document.createElement('tr');
        for (j = blockedColsCount - 1; j >= 0; j--) {
          th = document.createElement('th');
          th.className = self.blockedCols.headers[j].className;
          th.innerHTML = self.blockedCols.headerText(' ');
          self.minWidthFix(th);
          tr.appendChild(th);
        }
        thead.appendChild(tr);
      }
      self.blockedCorner = $(div);
      self.blockedCorner.on('click', function () {
        selection.selectAll();
      });
      self.container.append(self.blockedCorner);
    }
    else {
      if (self.blockedCorner) {
        self.blockedCorner.remove();
        self.blockedCorner = null;
      }
    }

    recreated = grid.keepEmptyRows();
    if (!recreated) {
      selection.refreshBorders(null, true);
    }

    self.blockedCols.update();
    self.blockedRows.update();
  };

  /**
   * Returns current settings object
   * @return {Object}
   */
  this.getSettings = function () {
    return priv.settings;
  };

  /**
   * Clears grid
   * @public
   */
  this.clear = function () {
    selection.selectAll();
    selection.empty();
  };

  /**
   * Return true if undo can be performed, false otherwise
   * @public
   */
  this.isUndoAvailable = function () {
    return priv.undoRedo && priv.undoRedo.isUndoAvailable();
  };

  /**
   * Return true if redo can be performed, false otherwise
   * @public
   */
  this.isRedoAvailable = function () {
    return priv.undoRedo && priv.undoRedo.isRedoAvailable();
  };

  /**
   * Undo last edit
   * @public
   */
  this.undo = function () {
    priv.undoRedo && priv.undoRedo.undo();
  };

  /**
   * Redo edit (used to reverse an undo)
   * @public
   */
  this.redo = function () {
    priv.undoRedo && priv.undoRedo.redo();
  };

  /**
   * Clears undo history
   * @public
   */
  this.clearUndo = function () {
    priv.undoRedo && priv.undoRedo.clear();
  };

  /**
   * Alters the grid
   * @param {String} action See grid.alter for possible values
   * @param {Number} from
   * @param {Number} [to] Optional. Used only for actions "remove_row" and "remove_col"
   * @public
   */
  this.alter = function (action, from, to) {
    if (typeof to === "undefined") {
      to = from;
    }
    switch (action) {
      case "insert_row":
      case "remove_row":
        grid.alter(action, {row: from, col: 0}, {row: to, col: 0});
        break;

      case "insert_col":
      case "remove_col":
        grid.alter(action, {row: 0, col: from}, {row: 0, col: to});
        break;

      default:
        throw Error('There is no such action "' + action + '"');
        break;
    }
  };

  /**
   * Returns  element corresponding to params row, col
   * @param {Number} row
   * @param {Number} col
   * @public
   * @return {Element}
   */
  this.getCell = function (row, col) {
    return self.view.getCellAtCoords({row: row, col: col});
  };

  /**
   * Returns property name associated with column number
   * @param {Number} col
   * @public
   * @return {String}
   */
  this.colToProp = function (col) {
    return datamap.colToProp(col);
  };

  /**
   * Returns column number associated with property name
   * @param {String} prop
   * @public
   * @return {Number}
   */
  this.propToCol = function (prop) {
    return datamap.propToCol(prop);
  };

  /**
   * Return cell value at `row`, `col`
   * @param {Number} row
   * @param {Number} col
   * @public
   * @return {string}
   */
  this.getDataAtCell = function (row, col) {
    return datamap.get(row, datamap.colToProp(col));
  };

  /**
   * Returns cell meta data object corresponding to params row, col
   * @param {Number} row
   * @param {Number} col
   * @public
   * @return {Object}
   */
  this.getCellMeta = function (row, col) {
    var cellProperites = {}
      , prop = datamap.colToProp(col);
    if (priv.settings.columns) {
      cellProperites = $.extend(true, cellProperites, priv.settings.columns[col] || {});
    }
    if (priv.settings.cells) {
      cellProperites = $.extend(true, cellProperites, priv.settings.cells(row, col, prop) || {});
    }
    cellProperites.isWritable = grid.isCellWritable($(self.view.getCellAtCoords({row: row, col: col})), cellProperites);
    Handsontable.PluginHooks.run(self, 'afterGetCellMeta', [row, col, cellProperites]);
    return cellProperites;
  };

  /**
   * Sets cell to be readonly
   * @param {Number} row
   * @param {Number} col
   * @public
   */
  this.setCellReadOnly = function (row, col) {
    $(self.view.getCellAtCoords({row: row, col: col})).data("readOnly", true);
  };

  /**
   * Sets cell to be editable (removes readonly)
   * @param {Number} row
   * @param {Number} col
   * @public
   */
  this.setCellEditable = function (row, col) {
    $(self.view.getCellAtCoords({row: row, col: col})).data("readOnly", false);
  };

  /**
   * Returns headers (if they are enabled)
   * @param {Object} obj Instance of rowHeader or colHeader
   * @param {Number} count Number of rows or cols
   * @param {Number} index (Optional) Will return only header at given index
   * @return {Array|String}
   */
  var getHeaderText = function (obj, count, index) {
    if (obj) {
      if (typeof index !== 'undefined') {
        return obj.columnLabel(index);
      }
      else {
        var headers = [];
        for (var i = 0; i < count; i++) {
          headers.push(obj.columnLabel(i));
        }
        return headers;
      }
    }
  };

  /**
   * Return array of row headers (if they are enabled). If param `row` given, return header at given row as string
   * @param {Number} row (Optional)
   * @return {Array|String}
   */
  this.getRowHeader = function (row) {
    return getHeaderText(self.rowHeader, self.rowCount, row);
  };

  /**
   * Return array of col headers (if they are enabled). If param `col` given, return header at given col as string
   * @param {Number} col (Optional)
   * @return {Array|String}
   */
  this.getColHeader = function (col) {
    return getHeaderText(self.colHeader, self.colCount, col);
  };

  /**
   * Return total number of rows in grid
   * @return {Number}
   */
  this.countRows = function () {
    return priv.settings.data.length;
  };

  /**
   * Return total number of columns in grid
   * @return {Number}
   */
  this.countCols = function () {
    if (priv.dataType === 'object') {
      if (priv.settings.columns && priv.settings.columns.length) {
        return priv.settings.columns.length;
      }
      else {
        return priv.colToProp.length;
      }
    }
    else if (priv.dataType === 'array') {
      return Math.max((priv.settings.columns && priv.settings.columns.length) || 0, (priv.settings.data && priv.settings.data[0] && priv.settings.data[0].length) || 0);
    }
  };

  /**
   * Selects cell on grid. Optionally selects range to another cell
   * @param {Number} row
   * @param {Number} col
   * @param {Number} [endRow]
   * @param {Number} [endCol]
   * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to the selection
   * @public
   */
  this.selectCell = function (row, col, endRow, endCol, scrollToCell) {
    if (typeof row !== 'number' || row < 0 || row >= self.rowCount) {
      return false;
    }
    if (typeof col !== 'number' || col < 0 || col >= self.colCount) {
      return false;
    }
    if (typeof endRow !== "undefined") {
      if (typeof endRow !== 'number' || endRow < 0 || endRow >= self.rowCount) {
        return false;
      }
      if (typeof endCol !== 'number' || endCol < 0 || endCol >= self.colCount) {
        return false;
      }
    }
    selection.start({row: row, col: col});
    if (typeof endRow === "undefined") {
      selection.setRangeEnd(self.getCell(row, col), scrollToCell);
    }
    else {
      selection.setRangeEnd(self.getCell(endRow, endCol), scrollToCell);
    }
  };

  this.selectCellByProp = function (row, prop, endRow, endProp, scrollToCell) {
    arguments[1] = datamap.propToCol(arguments[1]);
    if (typeof arguments[3] !== "undefined") {
      arguments[3] = datamap.propToCol(arguments[3]);
    }
    return self.selectCell.apply(self, arguments);
  };

  /**
   * Deselects current sell selection on grid
   * @public
   */
  this.deselectCell = function () {
    selection.deselect();
  };

  /**
   * Remove grid from DOM
   * @public
   */
  this.destroy = function () {
    self.rootElement.empty();
    self.rootElement.removeData('handsontable');
  };

  /**
   * Handsontable version
   */
  this.version = '@@version'; //inserted by grunt from package.json
};

var settings = {
  'data': void 0,
  'startRows': 5,
  'startCols': 5,
  'minRows': 0,
  'minCols': 0,
  'maxRows': Infinity,
  'maxCols': Infinity,
  'minSpareRows': 0,
  'minSpareCols': 0,
  'minHeight': 0,
  'minWidth': 0,
  'multiSelect': true,
  'fillHandle': true,
  'undo': true,
  'outsideClickDeselects': true,
  'enterBeginsEditing': true,
  'enterMoves': {row: 1, col: 0},
  'tabMoves': {row: 0, col: 1},
  'autoWrapRow': false,
  'autoWrapCol': false
};

$.fn.handsontable = function (action) {
  var i, ilen, args, output = [], userSettings;
  if (typeof action !== 'string') { //init
    userSettings = action || {};
    return this.each(function () {
      var $this = $(this);
      if ($this.data("handsontable")) {
        instance = $this.data("handsontable");
        instance.updateSettings(userSettings);
      }
      else {
        var currentSettings = $.extend(true, {}, settings), instance;
        for (i in userSettings) {
          if (userSettings.hasOwnProperty(i)) {
            currentSettings[i] = userSettings[i];
          }
        }
        instance = new Handsontable.Core($this, currentSettings);
        $this.data("handsontable", instance);
        instance.init();
      }
    });
  }
  else {
    args = [];
    if (arguments.length > 1) {
      for (i = 1, ilen = arguments.length; i < ilen; i++) {
        args.push(arguments[i]);
      }
    }
    this.each(function () {
      output = $(this).data("handsontable")[action].apply(this, args);
    });
    return output;
  }
};

redpajama_set_name: RedPajamaGithub


Are the rules set by people who grew up speaking the language or those who learnt it later?
WHAT IS THE DIFFERENCE BEWTEEN "LET'S EAT, GRANDMA" AND "LET'S EAT GRANDMA"
Sir, While agreeing with Michael Skapinker ("Mystery of acquiring another language", Comment, October 17) that some people do appear able to learn languages more easily than others, and that the reason remains something of a mystery, I would add two further complications to the story.
With many higher education institutions set in and around the city, London is certainly an attractive place to study, says undergrad James Connington.
Do you know what a plonker is? And how would you react if someone said they wanted to snog you? Improve your knowledge of British slang with this crossword. It's ace!
Guardian Style Guide author David Marsh set out to master perfect grammatical English - but discovered that 'correct' isn't always best. Here are the 10 grammar laws you no longer need to check.
How Do You Say 'Blog' in German?
WE Germans owe the English language a debt of gratitude. If English didn't lend us one or two little words every once in a while, we would probably call blogs "digitale Netztagebücher" and apps "Anwendungen für mobile Endgeräte." Even for German speakers, those don't exactly roll off the tongue.
Get up to date with today's news agenda.
Personal endorsements from leading figures in the arts, sport, media, business, and politics demonstrate how learning languages can open doors to an array of careers and life experiences.
How many Welsh speakers are there in the UK? Who invented Newspeak? Where is Blackfoot spoken? Take our quiz and test your language knowledge.


The Rheem RTEX 18 model is equipped with a rugged copper and brass heat exchanger, which can withstand harsh water conditions. This heater ensures longevity, compactness and usability because its installation process is simple and it is slim and light weight. Due to its space saving design, you can install it at any nook and corner. Since RTE 18 heats water only when demanded therefore, its energy consumption is very low. Additional convenience is offered by its on-unit control system that allows easy temperature adjustments. On average this tankless water heater provides up to 5.0 GPM water and this unit is appropriate for two bathrooms in hot weather and one bathroom during cold climate. It is an ideal choice as a point-of-use application and perfect choice for homes, condos, apartments, hospitals, offices, boats, hotels and restaurants.
This tankless water heater does its job well but finding its parts becomes a bit difficult after a while. Another drawback is that it requires two 40amp breakers. This makes it a bad choice if saving energy is your top priority as the unit will consume a lot of energy. Buy it if you require plenty of hot water or have a bigger family.
You can buy the Rheem RTEX 18 on Amazon.


Oil leaking into cylinder number three, burning the spark plug. Smoke coming out of exhaust (mostly on startup and accelerating), not losing coolant, not overheating, no oil in coolant, no coolant in oil.
Any idea what is could mean?
That usually means the o-rings sealing the spark plug tubes from the valve cover is leaking. Pretty common. I have attached a picture and circled in ted where the valve cover/spark plug tubes are leaking oil into the spark plugs. Would you like instructions how to fix yourself? Hope this helps and feel free to ask any questions if needed. Thanks for using CarPros.
Please send instructions on how to fix. Thank you.
Hi again. Danny indicated there may be a leak in the spark plug tube due to an o-ring. That will not cause oil to come out of the exhaust. It will, however, allow oil to enter the tube and get on the outside of the plug. Danny, is that correct or am I missing something?
Hey Joe. He is just noticing engine oil leaking into the spark plug tubes from the valve cover. It is a simple valve cover gasket replacement.
Okay, I misunderstood. He said he gets smoke from the exhaust so I figured it was getting into the combustion chamber.
Regardless, here are the directions for replacing the valve cover gaskets. There are three separate sets of directions. The first and second show how the valve cover and gaskets are removed. (Left and right sides are separate) The third show how to take apart the valve cover and replace the spark plug port seal.
Camshaft Cover Replacement - Left Side The first 7 pictures correlate to these directions.
1. Remove the fuel injector sight shield. Refer to Fuel Injector Sight Shield Replacement (See: Fuel Injector > Removal and Replacement).
2. Disconnect the positive crankcase ventilation (PCV) fresh air tube from the left camshaft cover.
3. Remove the bank 2 ignition coils. Refer to Ignition Coil Replacement - Bank 2 (See: Ignition Coil > Removal and Replacement).
4. Remove the bolt connecting the ground strap to the left camshaft cover.
5. Remove the bolt securing the oil level indicator tube to the left cylinder head and reposition the tube away from the camshaft cover.
6. Remove the camshaft cover bolts.
7. Lift the camshaft drive end of the camshaft cover up.
8. Remove the camshaft cover.
9. Discard the camshaft cover gasket and spark plug port seals if there is any evidence of damage or if the seal comes out of the groove in the cover during removal.
10. Clean the gasket mating surface on the cylinder head.
11. Clean and inspect the camshaft cover.
1. Install a new camshaft cover gasket to the camshaft cover if necessary.
2. Place a small amount of sealant at the split line (1, 2) of the left cylinder head and the left camshaft position actuator housing. Refer to Adhesives, Fluids, Lubricants, and Sealers (See: Engine > Mechanical).
Important: Be careful to prevent the exposed section of the camshaft cover gasket from being damaged by the edge of the cylinder head casting.
3. Work the camshaft cover into position by pivoting the cover down and aligning the bolt holes.
Notice: Refer to Fastener Notice (See: Vehicle > Vehicle Damage Warnings > Fastener Notice).
4. Install the camshaft cover bolts.
Tighten the bolts to 10 N.M (89 lb in).
5. Install the bolt connecting the left camshaft cover ground strap to the left camshaft cover.
Tighten the bolt to 10 N.M (89 lb in).
6. Rotate the oil level indicator tube back into its original position and install the bolt securing the tube to the cylinder head.
7. Install the bank 2 ignition coils. Refer to Ignition Coil Replacement - Bank 2 (See: Ignition Coil > Removal and Replacement).
8. Connect the PCV fresh air tube to the left camshaft cover.
2. Disconnect the positive crankcase ventilation (PCV) dirty air tube from the camshaft cover.
3. Remove the front air inlet housing panel. Refer to Air Inlet Housing Front Panel Replacement (See: Air Duct > Removal and Replacement > Air Inlet Housing Front Panel Replacement).
4. Remove the battery tray. Refer to Battery Tray Replacement (See: Battery Tray > Removal and Replacement > Battery Tray Replacement).
5. Raise the vehicle. Refer to Lifting and Jacking the Vehicle (See: Maintenance > Service Intervals).
6. Remove the front air deflector. Refer to Front Air Deflector Replacement (See: Air Dam > Removal and Replacement > Front Air Deflector Replacement).
7. Remove the bolts securing the wire harness bracket to the cylinder head.
9. Remove the bank 1 ignition coils. Refer to Ignition Coil Replacement - Bank 1 (See: Ignition Coil > Removal and Replacement).
10. Disconnect the cable harness clips at the front of the camshaft cover and position the cable harness aside.
11. Remove the bolt connecting the ground strap to the right camshaft cover.
12. Remove the camshaft cover bolts.
13. Remove the camshaft cover.
14. Discard the camshaft cover gasket and spark plug port seals if there is any evidence of damage or if the seal comes out of the groove in the cover during removal.
15. Clean and inspect the camshaft cover. Refer to Camshaft Cover Cleaning and Inspection ().
Important: Be careful to prevent the exposed section of the camshaft cover seal from being damaged by the edge of the cylinder head casting.
1. Install the camshaft cover gasket as required.
2. Place a small amount of sealant at the split line (1, 2) of the right cylinder head and the right camshaft position actuator housing. Refer to Adhesives, Fluids, Lubricants, and Sealers (See: Engine > Mechanical).
3. Install the camshaft cover.
4. Install the camshaft cover bolts installing the ground strap to the camshaft cover.
5. Install the bank 1 ignition coils. Refer to Ignition Coil Replacement - Bank 1 (See: Ignition Coil > Removal and Replacement).
6. Connect the cable harness clips to the front of the camshaft cover.
7. Raise the vehicle. Refer to Lifting and Jacking the Vehicle (See: Maintenance > Service Intervals).
8. Install the bolts securing the wire harness bracket to the cylinder head.
Tighten the bolts to 45 N.M (33 lb ft).
9. Install the front air deflector. Refer to Front Air Deflector Replacement (See: Air Dam > Removal and Replacement > Front Air Deflector Replacement).
11. Install the battery tray. Refer to Battery Tray Replacement (See: Battery Tray > Removal and Replacement > Battery Tray Replacement).
12. Install the front air inlet housing panel. Refer to Air Inlet Housing Front Panel Replacement (See: Air Duct > Removal and Replacement > Air Inlet Housing Front Panel 13. Connect the PCV dirty air tube to the camshaft cover.
14. Install the fuel injector sight shield. Refer to Fuel Injector Sight Shield Replacement (See: Fuel Injector > Removal and Replacement).
1. Remove the left camshaft cover gasket and discard.
2. Remove the left spark plug port seals and discard.
3. Remove the right camshaft cover gasket and discard.
5. Remove the camshaft cover bolts.
6. Remove the camshaft cover bolt grommets.
I hope this helps. Let us know if you have other questions.
Thank you so much for sending directions on how to replace valve cover gasket. However, after looking at them it may be too big of a job for me, but I appreciate you sending them. Thank you very much.
Happy to help. The directions are long but it really is not that hard of a job. If you do it and run into any trouble, let me know and I can try to help from here. By the way. I am sorry if I confused you at first. I thought you were burning the oil and the smoke was coming out the exhaust at start up and under acceleration.
Regardless, have a good weekend and take care.


text:
Stories 1 - 20 | Next >>
Peter Thiel: Google Made 'Seemingly Treasonous' Choice
He calls for the FBI, CIA to investigate the company
(Newser) - Silicon Valley bigwig Peter Thiel spoke at the nationalism-focused National Conservatism Conference on Sunday night, and he suggested the government place a big target on Google's back. Axios reports that the PayPal co-founder and Facebook board member called for the FBI and CIA to investigate the search giant over... More »
Startup Buys Young Blood, Injects It Into Older People
For $8K a pop
(Newser) - It sounds like something out of a sci-fi movie, but it's real: Ambrosia, a startup from entrepreneur Jesse Karmazin, pays for the blood of people under the age of 25, then sells anyone over age 35 a transfusion of the younger blood for $8,000—the only US company... More »
Peter Thiel Maybe Bought New Zealand Citizenship
At least according to one New Zealand politician
(Newser) - Trump adviser. Tech billionaire. Gawker killer. Lord of the Rings fan? The New York Times reports Peter Thiel was granted New Zealand citizenship back in 2011 despite not meeting any of the requirements for citizenship. Now at least one New Zealand politician is accusing Thiel of buying his citizenship. In... More »
Governor Peter Thiel?
Insiders say tech billionaire might make a longshot run in California
(Newser) - Perhaps inspired by Donald Trump's rise from business mogul to president-elect, Paypal founder, Gawker slayer, and ardent Trump fan Peter Thiel might be looking to add an unlikely title to his repertoire: That of governor of California, reports Politico , citing "three Republicans familiar with his thinking." While... More »
Peter Thiel: Lack of Corruption Can Be 'Too Boring'
Tech billionaire sits down with Maureen Dowd
(Newser) - Billionaire tech investor and PayPal founder Peter Thiel made big headlines for his once-secret funding of the lawsuit that destroyed Gawker, then for his strong backing of Donald Trump, the latter an anomaly in Silicon Valley. Now he "explains himself" in an interview with Maureen Dowd, as the headline... More »
Rand Paul Airs His Grievances
And they include Donald Trump, the media, and Peter Thiel
(Newser) - Rand Paul can be nothing if not persistent , and the Kentucky senator's annual Festivus airing of grievances proved no exception this year, reports the Guardian . In honor of the Seinfeld holiday, Paul took to Twitter on Friday to list his litany of light-hearted and not-so-light-hearted complaints. Highlights of his... More »
Tech Bigwigs Headed to NYC for Trump Tech Summit
It could be 'super awkward'
(Newser) - Donald Trump has invited some of the biggest names in tech to a summit at NYC's Trump Tower on Wednesday, and a good number of them have RSVP'd yes, per USA Today and Investor's Business Daily . Reported "ayes" are Facebook's Sheryl Sandberg, Apple's Tim... More »
Here's What Happened When Peter Thiel Tried to Kill College
A look inside the Thiel Fellowship
(Newser) - When Peter Thiel started the Thiel Fellowship in 2010, offering $100,000 to 20 teens to drop out of college and work on whatever they wanted, he envisioned he was funding technological breakthroughs on the level of the moon landing or home computers. What he actually funded is an exclusive... More »
Thiel's Costume for Posh Party? Hulk Hogan, per Sources
Trump, campaign manager Kellyanne Conway were also in attendance
(Newser) - Hulk Hogan, with funding from frequent Gawker target Peter Thiel, helped bring the media group to bankruptcy via his sex-tape lawsuit, so Thiel's choice of costume at a posh Long Island party might not be terribly surprising: He reportedly dressed as the Hulkster on Saturday night, when the PayPal... More »
Peter Thiel Having Hard Time Finding Tech Advisers for Trump
He's trying to find members for a 'brain trust'
(Newser) - The tech industry got used to "an administration that doted on us" under President Obama, one tech lobbyist tells the Mercury News . Now, with Donald Trump on his way to the White House, Silicon Valley needs to figure out how to have a voice in the government without angering... More »
Gawker, Hulk Hogan Settle Lawsuit for $31M
'Hulk Hogan's retirement will be comfortable'
(Newser) - Hulk Hogan has settled with the smoldering ashes of Gawker Media for $31 million, CNN reports. Gawker was appealing the $140 million a jury awarded Hogan earlier this year. "The saga is over," Gawker founder Nick Denton says. "Hulk Hogan's retirement will be comfortable." Hogan'... More »
Zuckerberg Defends Thiel's Trump Donation
'There are many reasons a person might support Trump,' says the Facebook CEO
(Newser) - Mark Zuckerberg broke his silence about billionaire Peter Thiel's $1.25 million donation to Donald Trump, saying the Facebook board member has a right to his opinion, CNNMoney reports. "We care deeply about diversity," Zuckerberg wrote in an internal post to employees. "That's easy to... More »
100 Business Leaders Explain Why They Back Trump
Including PayPal founder Peter Thiel
(Newser) - Some people might be backing off their support for Trump in the wake of the controversy surrounding his leaked 2005 comments about women, but not these 100 business leaders. In an open letter released by the Trump Organization , the business leaders—including PayPal founder Peter Thiel, who recently donated $1.... More »
Donald Trump Just Got a Prominent New Donor
Peter Thiel is giving candidate $1.25M in his first donation
(Newser) - Donald Trump may have long ago lost the Koch brothers , but Peter Thiel is turning his fortune away from taking down Gawker and using it to line Trump's pockets, reports CBS News . The tech billionaire—recently seen telling the Republican National Convention he was "proud to be gay"... More »
Univision Buys Gawker for $135M
The bankrupt media company was up for auction Tuesday
(Newser) - It appears the bankrupt Gawker has a new owner: Univision. The company best known for Spanish-language television bought Gawker Tuesday at auction for $135 million, the New York Times reports. The online media company was forced to put itself up for sale after Hulk Hogan and tech billionaire Peter Thiel... More »
Gawker Founder Files for Bankruptcy
Nick Denton owes Hulk Hogan $125M
(Newser) - Gawker founder Nick Denton filed for personal bankruptcy Monday amid the website's battle with Hulk Hogan. Denton listed $10 million to $50 million in assets and $100 million to $500 million in debts, the New York Post reports; Denton owes Hogan $125 million after a jury sided with the... More »
Peter Thiel to RNC: 'I Am Proud to Be Gay'
He says Trump will lead America 'back to that bright future'
(Newser) - Tech billionaire—and gay man—Peter Thiel was one of the more unexpected speakers lined up for Thursday's final night of the Republican National Convention . But he earned chants of "USA" from the crowd when he announced: "I am proud to be gay. I am proud to... More »
Thiel Lawyer Trying to Squash Story on Trump Hair
Gawker wonders how many other suits Peter Thiel is secretly funding
(Newser) - PayPal founder Peter Thiel admits wanting to take out Gawker , but he's been less than forthcoming about which other cases he's helped fund—and a recent legal letter that demands Gawker stop reporting on Donald Trump's hair gives one pause on that question. In what the Verge... More »
Gawker May File Its Own Suit Against Peter Thiel
He spent $10M funding lawsuits against them
(Newser) - Well, it looks like Gawker won't be going down without a fight. Forbes reports Gawker Media is looking into suing billionaire archenemy Peter Thiel. Thiel claims he spent about $10 million funding multiple lawsuits against Gawker—including Hulk Hogan's incredibly successful one—in an effort to punish the... More »
Gawker for Sale After Declaring Bankruptcy
And Gawker-hater Peter Thiel is 'closer to his goal'
(Newser) - Gawker Media—responsible for blogs like Deadspin, io9, and Jezebel—filed for Chapter 11 bankruptcy Friday as a result of Hulk Hogan's successful lawsuit against it, the Wall Street Journal reports. According to Recode , Gawker will be put up for auction, with the first bid coming in at less... More »

redpajama_set_name: RedPajamaCommonCrawl


text:
Noteworthy:
Alexa, Play Quick Country
Concerts Heard On Air
Quick Country Newsletter
The Judds
Wynonna Judd Doesn't Think The Judds Final Tour Will Extend Again: 'There Are No Plans'
Instead, the singer says, she's planning to focus on something new.
Country Artists Who Have Performed During the Super Bowl [PICTURES]
These country artists have all taken the stage during the NFL's biggest game of the year.
Carrie Horton
Top 10 Judds Songs — The Best of Naomi and Wynonna Judd
The Judds only released five albums, but they have a rich catalog of hit songs.
10 Essential Songs From The Judds
The Judds, comprised of Naomi Judd and her daughter, Wynonna Judd, are one of the most successful acts in country music history.
38 Years Ago: The Judds Hit No. 1 With 'Why Not Me'
Christmas came early for the Judds in 1984.
Gayle Thompson
Larry Strickland Reflects on Late Wife Naomi Judd's Depression Battle: 'The Tragedy, the Trauma — It Changes You'
Larry Strickland is opening up about Naomi Judd's battle with mental illness.
Grace Lenehan Vaughn
Ashley Judd Fractured Her Leg While Grieving Mom Naomi Judd's Death
Ashley Judd suffered the injury in a "freak accident," she says.
Wynonna Judd Opens Up About 'Most Emotional' Judds Final Tour: 'It's Almost Too Much to Handle'
"I feel like I'm just the messenger."
Wynonna Judd Honors Late Naomi Judd's Legacy in First Show of The Judds' Final Tour [Watch]
"Thank you, Mama, for writing this song."
How Wynonna Brought Mother Naomi Judd to the Stage During First Tour Stop
A performance of "Love Can Build a Bridge" was — as expected — among the more emotional moments of the 25-song concert.
2023 Quick Country 96.5, Townsquare Media, Inc. All rights reserved.


text:
Police: 2 busted after 7 pounds of marijuana, ammo found in CT home
Last updated September 5, 2020 by C.E. News on September 5, 2020
https://cannabisexaminers.com/wp-content/uploads/2020/09/1599294930_rawImage.jpg
Two men were arrested on Thursday, Sept. 3, 2020 after seven pounds of marijuana and firearms ammunition were seized in a Meriden house, police said. Among those arrested was Emilio Mercado, 27, who was charged with possession with intent to sell and operating a drug factory.
Two men were arrested on Thursday, Sept. 3, 2020 after seven pounds of marijuana and firearms ammunition were seized in a Meriden house, police said. Among those arrested was Emilio Mercado, 27, who was charged
Photo: Meriden Police Photo
MERIDEN — Two city men were arrested Thursday after 7 pounds of marijuana and ammunition were seized, police said.
Lt John Mennone said Meriden police, the department's SWAT team, and the Wallingford /Cheshire SWAT team executed a narcotics search warrant at a home in the 300-block of Colony Street, where the two men lived.
Emilio Mercado, 27, was charged with possession with intent to sell and operating a drug factory.
Caz Alicea, 31, was charged with possession with intent to sell and operating a drug factory, violation of a protective order, criminal possession of ammunition and interfering with police.
Caz was also taken into custody for an outstanding arrest warrant from the Connecticut State Police.
To report tips for violent crimes, call the Major Crimes Unit at 203-630-6253. To report tips for narcotics activity, call the Crime Suppression Unit at 203-630-6273
Cannabis shop should be welcomed by Sidney – Victoria News
Manufacturing, industry groups urge caution following governor's push for legalization of marijuana


The perfect time-killer for your kids! Come in and have lot of fun with witty, easy to paint characters.
This digital coloring book offers a huge variety of pictures and will keep your children entertained for a long time. This app releases creativity of each child by offering a multi-choice panel of possibilities.
It's a beautifully illustrated application with a touch of education flavor. Optimized for kids but also entertaining and relaxing for the grown-ups.


Whether your style is traditional, contemporary, or transitional, Furniture Galleries of Oshawa features Canadian Manufactures of fine furniture. Our 25000 ft showroom host collections from Décor-Rest, Palliser, SuperStyle, and Trendline. Featuring an array of styles with hundreds of choices in fabric and leather.
Whether you want seating for two or twelve Furniture Galleries of Oshawa features a large selection of sectionals offering a multitude of configuration options and style choices to fit any space.
Simmons Upholstery Canada manufactures quality sofa beds featuring the famous Simmons Beautyrest mattress. Come to Furniture Galleries Oshawa to see our selection of sofa beds.
Our vast section of Accent chairs offer a variety of stylish options in a large range of fabrics and colours.
Choose from a large variety of styles from Palliser Furniture, manufacture here in Canada, featuring quality leathers and a vast selection of fabrics. Find the one that's just perfect for you!
NEED COP HERE Choose from a large variety of styles from Palliser Furniture, manufacture here in Canada, featuring quality leathers and a vast selection of fabrics. Find the one that's just perfect for you!


There is definitely a type of person we all try to avoid, the HUF Type hoodie showcases our hesitation to them perfectly. Featured in black, this pullover sweatshirt features a black and yellow photo of a woman's face screen printed at the chest with "You Know The Type" in yellow text down each sleeve.
Type Black Hoodie from HUF.
Screen printed graphics at chest and down both sleeves.
HUF brand tag at bottom left side seam.


text:
This Party Bro Names His Favorite Part Of A Woman (Hint: It's Hilarious And Surprising) – VIDEO
By Kat George
Dude bros. The guys we love to hate (but mostly hate), are loud, obnoxious, and enjoy nothing more than jumping parties, terrible shirts and objectifying women. But not this Russian party bro: he loves a bit of tit, but when asked what the most important part of a woman is, he says her "soul", as he back pedals in a rare heartfelt dude bro moment. In the midst of a party, speaking to a female interviewer, he first tells her he wants "Tits, more tits!" But seconds later he reflects, and changes his mind. The most important part of a woman, he says, with a completely earnest expression, is her soul. God bless the hilariously radical shifting that happens in the course of this short video.
It helps that whoever edited the video added some reflective music to the video when the dude bro speaks about soul, but it's still jarring to see this party boy, in his natural environment, getting deep and feels-y. The way his whole expression changes when he talks about a woman's soul is weirdly endearing, and it's one of those teen movie moments where the misunderstood jocks shows that he actually has emotions too. But then at the end, he sort of ruins it for himself by bringing up tits again. We see you bro: we know you're only objectifying tits as a means to protect the soft heart that resides within. You don't need to hide any more.
Image: Georgia/Flickr


31st December 2014 from 9pm til Late!
End the year in style with the Coopers Arms. New Year's Eve Masquerade Ball.
Attire: Fabulous Frocks & Marvellous Masks!
Bubbly on Arrival, Live Music from Ryan Joseph, Masked Magician, Cocktail & Shot Bar, DJ til Late, midnight tipple and firework spectacular, Bacon Cobs in the New Year plus Prize for Best Mask & Much More!
Contact us to book your tickets now!


Zeke the Wonderdog does some warm up tricks with owner Jim Foley before the game against Maryland on Nov. 14, 2015 at Spartan Stadium. The Spartans defeated the Terrapins, 24-7.
MSU's Zeke the Wonderdog, the high-flying Frisbee catching Yellow Labrador died unexpectedly Friday night around 11:45 p.m.
His owners, Jim and Terri Foley, said Zeke was shaking and had trouble eating upon returning home from the MSU women's basketball game yesterday against Oakland University. When rushing Zeke to the veterinary hospital, he passed away. Jim Foley said the cause was likely a tumor in Zeke's abdomen that caused internal bleeding.
Zeke recently had blood and urine work done, however this tumor was hidden to these tests, Jim Foley said. Zeke went fast and never showed any signs of pain.
Zeke was soon to turn 12 years-old, his birthday a few weeks away on Dec. 19.
The Foley family said one of Zeke's fondest memories was the Spartan Marching Band at home football games. After each halftime performance, the band would receive apples and would feed some to Zeke. Terri said Zeke loved to interact with the band, while band members traded off feeding him.
Jim Foley said one of Zeke's happiest moments, was when MSU's former tight end Charlie Gantt streaked up-field to catch the game-winning 29-yard touchdown pass against Notre Dame in 2010 in Spartan Stadium. The play labeled, "Little Giants," was a fake field goal attempt in overtime to win 34-31. Jim Foley said Zeke was sprinting back-and-forth in the endzone in excitement and celebration.
Members of the MSU community have reached out to the Foley family including MSU Athletic Director Mark Hollis and Associate Vice President for Alumni Relations Scott Westerman.
"So sorry to hear this news. Take comfort in knowing how many lives, young and old, Zeke impacted during his life. Sad day!
The love and sympathy of the entire Spartan family is with you.
Zeke is truly an immortal soul. He manifests in the bodies of the amazing spirits you have nurtured over the years and miraculously reappears to amaze and delight generations of Spartan fans who appreciate all that he stands for. Our hearts are with you in this time of loss, but we take comfort in knowing that Zeke will live again. I can't think of a better legacy."
The Foley family plans on showcasing the new wonderdog Saturday at the Breslin Center during the men's basketball game against Oral Roberts.
Zeke performed for nine MSU football seasons.
Connor Clark is the basketball beat reporter for The State News.


Sydney Shop Girl: Lovin' Life 9/11/2017: Celia's Tinned Fruit Teacake.
Lovin' Life 9/11/2017: Celia's Tinned Fruit Teacake.
This week's love is a simple one. A tried and true cake recipe. Celia from 'Fig Jam and Lime Cordial' created this recipe for a Tinned Fruit Teacake a few years ago and it's become my favourite cake to bring to morning teas or to whip up at short notice.
The secret ingredient is almond meal in the batter and I think the 4 eggs it also requires helps give it a very rich taste for minimal effort. I just pop everything in the food processor and before you know it, you have your cake batter ready to go. It takes longer to disassemble and wash the parts of the food processor, to be honest. Though the batter might spread a bit thin at first glance, it does rise and spread rather nicely.
The fruit topping is also super easy. All you do is open your tins of pear and peach slices, drain and then stud the batter with them. Then drizzle over a can of passionfruit pulp.
If you look closely at the photo, you might also find a new addition to my kitchen benchtop. A set of 'proper' kitchen scales. The kind with a dial front. My set also features a nifty tare function. They're so much more reliable than those sleek looking digital scales that go haywire every once in a while before totally going nuts as they spew random flickering numbers at you.
Do you have that one cake recipe that's easy, never fails and looks great?
Packet mixes haha. I can't bake. I'm a good cook but baking involves following instructions and measuring things and I can't do that.
I'm trying this one SSG it looks easy and sounds delicious.
It looks great (and reasonably healthy too) I don't bake much any more but it would be a great dessert for the family.
This looks gorgeous. I want to make it.
I wish I had an easy go-to cake recipie! We are having a 'bring a plate' catered daycare Christmas party and I'm a bit stumped with what to bring!
SSG, that looks so good! I've never had nor seen that type of cake before. I really must tag this page and try it.


Special Notes: If there is an enemy of the target on the opposite side of the target while attacking it, this unit may backstab, inflicting double damage by creeping around behind that enemy. The victims of this unit's poison will continually take damage until they can be cured in town or by a unit which cures. This unit's skill at skirmishing allows it to ignore enemies' zones of control and thus move unhindered around them. This unit is able to hide at night, leaving no trace of its presence.