chromium/third_party/google_input_tools/src/chrome/os/inputview/handler/pointeractionbundle.js

// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
// limitations under the License.
// See the License for the specific language governing permissions and
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// distributed under the License is distributed on an "AS-IS" BASIS,
// Unless required by applicable law or agreed to in writing, software
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// You may obtain a copy of the License at
// you may not use this file except in compliance with the License.
// Licensed under the Apache License, Version 2.0 (the "License");
//
goog.provide('i18n.input.chrome.inputview.handler.PointerActionBundle');

goog.require('goog.Timer');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.math.Coordinate');
goog.require('i18n.input.chrome.inputview.SwipeDirection');
goog.require('i18n.input.chrome.inputview.events.DragEvent');
goog.require('i18n.input.chrome.inputview.events.EventType');
goog.require('i18n.input.chrome.inputview.events.PointerEvent');
goog.require('i18n.input.chrome.inputview.events.SwipeEvent');
goog.require('i18n.input.chrome.inputview.handler.SwipeState');
goog.require('i18n.input.chrome.inputview.handler.Util');



goog.scope(function() {



/**
 * The handler for long press.
 *
 * @param {!i18n.input.chrome.inputview.elements.Element} view The view for this
 *     pointer event.
 * @param {goog.events.EventTarget=} opt_parentEventTarget The parent event
 *     target.
 * @constructor
 * @extends {goog.events.EventTarget}
 */
i18n.input.chrome.inputview.handler.PointerActionBundle = function(view,
    opt_parentEventTarget) {
  goog.base(this);
  this.setParentEventTarget(opt_parentEventTarget || null);

  /**
   * The target.
   *
   * @type {!i18n.input.chrome.inputview.elements.Element}
   */
  this.view = view;

  /**
   * The swipe offset.
   *
   * @type {!i18n.input.chrome.inputview.handler.SwipeState}
   * @private
   */
  this.swipeState_ = new i18n.input.chrome.inputview.handler.SwipeState();
};
goog.inherits(i18n.input.chrome.inputview.handler.PointerActionBundle,
    goog.events.EventTarget);
var PointerActionBundle = i18n.input.chrome.inputview.handler.
    PointerActionBundle;
var Util = i18n.input.chrome.inputview.handler.Util;


/**
 * The current target after the touch point is moved.
 *
 * @type {!Node | Element}
 * @private
 */
PointerActionBundle.prototype.currentTarget_;


/**
 * How many milli-seconds to evaluate a double click event.
 *
 * @type {number}
 * @private
 */
PointerActionBundle.DOUBLE_CLICK_INTERVAL_ = 500;


/**
 * The timer ID.
 *
 * @type {number}
 * @private
 */
PointerActionBundle.prototype.longPressTimer_;


/**
 * The minimum swipe distance.
 *
 * @type {number}
 * @private
 */
PointerActionBundle.MINIMUM_SWIPE_DISTANCE_ = 20;


/**
 * The timestamp of the pointer down.
 *
 * @type {number}
 * @private
 */
PointerActionBundle.prototype.pointerDownTimeStamp_ = 0;


/**
 * The timestamp of the pointer up.
 *
 * @type {number}
 * @private
 */
PointerActionBundle.prototype.pointerUpTimeStamp_ = 0;


/**
 * True if it is double clicking.
 *
 * @type {boolean}
 * @private
 */
PointerActionBundle.prototype.isDBLClicking_ = false;


/**
 * True if it is long pressing.
 *
 * @type {boolean}
 * @private
 */
PointerActionBundle.prototype.isLongPressing_ = false;


/**
 * True if it is flickering.
 *
 * @type {boolean}
 * @private
 */
PointerActionBundle.prototype.isFlickering_ = false;


/**
 * Handles touchmove event for one target.
 *
 * @param {!Touch | !Event} e .
 */
PointerActionBundle.prototype.handlePointerMove = function(e) {
  var identifier = Util.getEventIdentifier(e);
  var direction = 0;
  var deltaX = this.swipeState_.previousX == 0 ? 0 : (e.pageX -
      this.swipeState_.previousX);
  var deltaY = this.swipeState_.previousY == 0 ? 0 :
      (e.pageY - this.swipeState_.previousY);
  this.swipeState_.offsetX += deltaX;
  this.swipeState_.offsetY += deltaY;
  this.dispatchEvent(new i18n.input.chrome.inputview.events.DragEvent(
      this.view, direction, /** @type {!Node} */ (e.target),
      e.pageX, e.pageY, deltaX, deltaY, identifier));

  var minimumSwipeDist = PointerActionBundle.
      MINIMUM_SWIPE_DISTANCE_;

  if (this.swipeState_.offsetX > minimumSwipeDist) {
    direction |= i18n.input.chrome.inputview.SwipeDirection.RIGHT;
    this.swipeState_.offsetX = 0;
  } else if (this.swipeState_.offsetX < -minimumSwipeDist) {
    direction |= i18n.input.chrome.inputview.SwipeDirection.LEFT;
    this.swipeState_.offsetX = 0;
  }

  if (Math.abs(deltaY) > Math.abs(deltaX)) {
    if (this.swipeState_.offsetY > minimumSwipeDist) {
      direction |= i18n.input.chrome.inputview.SwipeDirection.DOWN;
      this.swipeState_.offsetY = 0;
    } else if (this.swipeState_.offsetY < -minimumSwipeDist) {
      direction |= i18n.input.chrome.inputview.SwipeDirection.UP;
      this.swipeState_.offsetY = 0;
    }
  }

  this.swipeState_.previousX = e.pageX;
  this.swipeState_.previousY = e.pageY;

  if (direction > 0) {
    // If there is any movement, cancel the longpress timer.
    goog.Timer.clear(this.longPressTimer_);
    this.dispatchEvent(new i18n.input.chrome.inputview.events.SwipeEvent(
        this.view, direction, /** @type {!Node} */ (e.target),
        e.pageX, e.pageY, identifier));
    var currentTargetView = Util.getView(this.currentTarget_);
    this.isFlickering_ = !this.isLongPressing_ && !!(this.view.pointerConfig.
        flickerDirection & direction) && currentTargetView == this.view;
  }

  this.maybeSwitchTarget_(
      new goog.math.Coordinate(e.pageX, e.pageY), identifier);
};


/**
 * If the target is switched to a new one, sends out a pointer_over for the new
 * target and sends out a pointer_out for the old target.
 *
 * @param {!goog.math.Coordinate} pageOffset .
 * @param {number} identifier .
 * @private
 */
PointerActionBundle.prototype.maybeSwitchTarget_ = function(pageOffset,
    identifier) {
  if (!this.isFlickering_) {
    var actualTarget = document.elementFromPoint(pageOffset.x, pageOffset.y);
    var currentTargetView = Util.getView(this.currentTarget_);
    var actualTargetView = Util.getView(actualTarget);
    if (currentTargetView != actualTargetView) {
      if (currentTargetView) {
        this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
            currentTargetView,
            i18n.input.chrome.inputview.events.EventType.POINTER_OUT,
            this.currentTarget_, pageOffset.x, pageOffset.y, identifier));
      }
      if (actualTargetView) {
        this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
            actualTargetView,
            i18n.input.chrome.inputview.events.EventType.POINTER_OVER,
            actualTarget, pageOffset.x, pageOffset.y, identifier));
      }
      this.currentTarget_ = actualTarget;
    }
  }
};


/**
 * Handles pointer up, e.g., mouseup/touchend.
 *
 * @param {!goog.events.BrowserEvent} e The event.
 */
PointerActionBundle.prototype.handlePointerUp = function(e) {
  goog.Timer.clear(this.longPressTimer_);
  var pageOffset = this.getPageOffset_(e);
  var identifier = Util.getEventIdentifier(e);
  this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
      this.view, i18n.input.chrome.inputview.events.EventType.LONG_PRESS_END,
      e.target, pageOffset.x, pageOffset.y, identifier));
  if (this.isDBLClicking_) {
    this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
        this.view, i18n.input.chrome.inputview.events.EventType.
        DOUBLE_CLICK_END, e.target, pageOffset.x, pageOffset.y, identifier));
  } else if (!(this.isLongPressing_ && this.view.pointerConfig.
      longPressWithoutPointerUp)) {
    this.maybeSwitchTarget_(pageOffset, identifier);
    var view = Util.getView(this.currentTarget_);
    var target = this.currentTarget_;
    if (this.isFlickering_) {
      view = this.view;
      target = e.target;
    }
    this.pointerUpTimeStamp_ = new Date().getTime();
    // Note |view| can be null if the finger moves outside of keyboard window
    // area. This is possible when user try to select an accent character
    // which is displayed outside of keyboard window. We need to dispatch a
    // POINTER_UP event to keyboard to commit the selected accent character.
    this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
        view, i18n.input.chrome.inputview.events.EventType.POINTER_UP,
        target, pageOffset.x, pageOffset.y, identifier,
        this.pointerUpTimeStamp_));
  }
  if (Util.getView(this.currentTarget_) == this.view) {
    this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
        this.view, i18n.input.chrome.inputview.events.EventType.CLICK,
        e.target, pageOffset.x, pageOffset.y, identifier));
  }
  this.isDBLClicking_ = false;
  this.isLongPressing_ = false;
  this.isFlickering_ = false;
  this.swipeState_.reset();
};


/**
 * Cancel double click recognition on this target.
 */
PointerActionBundle.prototype.cancelDoubleClick = function() {
  this.pointerDownTimeStamp_ = 0;
};


/**
 * Handles pointer down, e.g., mousedown/touchstart.
 *
 * @param {!goog.events.BrowserEvent} e The event.
 */
PointerActionBundle.prototype.handlePointerDown = function(e) {
  this.currentTarget_ = e.target;
  goog.Timer.clear(this.longPressTimer_);
  var identifier = Util.getEventIdentifier(e);
  if (e.type == goog.events.EventType.TOUCHSTART) {
    this.maybeTriggerKeyDownLongPress_(e, identifier);
  }
  this.maybeHandleDBLClick_(e, identifier);
  if (!this.isDBLClicking_) {
    var pageOffset = this.getPageOffset_(e);
    this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
        this.view, i18n.input.chrome.inputview.events.EventType.POINTER_DOWN,
        e.target, pageOffset.x, pageOffset.y, identifier,
        this.pointerDownTimeStamp_));
  }
};


/**
 * Gets the page offset from the event which may be mouse event or touch event.
 *
 * @param {!goog.events.BrowserEvent} e .
 * @return {!goog.math.Coordinate} .
 * @private
 */
PointerActionBundle.prototype.getPageOffset_ = function(e) {
  var nativeEvt = e.getBrowserEvent();
  if (nativeEvt.pageX && nativeEvt.pageY) {
    return new goog.math.Coordinate(nativeEvt.pageX, nativeEvt.pageY);
  }


  var touchEventList = nativeEvt['changedTouches'];
  if (!touchEventList || touchEventList.length == 0) {
    touchEventList = nativeEvt['touches'];
  }
  if (touchEventList && touchEventList.length > 0) {
    var touchEvent = touchEventList[0];
    return new goog.math.Coordinate(touchEvent.pageX, touchEvent.pageY);
  }

  return new goog.math.Coordinate(0, 0);
};


/**
 * Maybe triggers the long press timer when pointer down.
 *
 * @param {!goog.events.BrowserEvent} e The event.
 * @param {number} identifier .
 * @private
 */
PointerActionBundle.prototype.maybeTriggerKeyDownLongPress_ = function(e,
    identifier) {
  if (this.view && (this.view.pointerConfig.longPressWithPointerUp ||
      this.view.pointerConfig.longPressWithoutPointerUp)) {
    this.longPressTimer_ = goog.Timer.callOnce(
        goog.bind(this.triggerLongPress_, this, e, identifier),
        this.view.pointerConfig.longPressDelay, this);
  }
};


/**
 * Maybe handle the double click.
 *
 * @param {!goog.events.BrowserEvent} e .
 * @param {number} identifier .
 * @private
 */
PointerActionBundle.prototype.maybeHandleDBLClick_ = function(e, identifier) {
  if (this.view && this.view.pointerConfig.dblClick) {
    var timeInMs = new Date().getTime();
    var interval = this.view.pointerConfig.dblClickDelay ||
        PointerActionBundle.DOUBLE_CLICK_INTERVAL_;
    var nativeEvt = e.getBrowserEvent();
    if ((timeInMs - this.pointerDownTimeStamp_) < interval) {
      this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
          this.view, i18n.input.chrome.inputview.events.EventType.DOUBLE_CLICK,
          e.target, nativeEvt.pageX, nativeEvt.pageY, identifier));
      this.isDBLClicking_ = true;
    }
    this.pointerDownTimeStamp_ = timeInMs;
  }
};


/**
 * Triggers long press event.
 *
 * @param {!goog.events.BrowserEvent} e The event.
 * @param {number} identifier .
 * @private
 */
PointerActionBundle.prototype.triggerLongPress_ = function(e, identifier) {
  var nativeEvt = e.getBrowserEvent();
  if (nativeEvt.touches.length > 1) {
    return;
  }
  this.dispatchEvent(new i18n.input.chrome.inputview.events.PointerEvent(
      this.view, i18n.input.chrome.inputview.events.EventType.LONG_PRESS,
      e.target, nativeEvt.pageX, nativeEvt.pageY, identifier));
  this.isLongPressing_ = true;
};


/** @override */
PointerActionBundle.prototype.disposeInternal = function() {
  goog.dispose(this.longPressTimer_);

  goog.base(this, 'disposeInternal');
};

});  // goog.scope