Javascript  |  247行  |  7.12 KB

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

(function () {

  /**
   * The possible states of the shift key.
   * Unlocked is the default state. Locked for capslocked, pressed is a
   * key-down and tapped for a key-down followed by an immediate key-up.
   * @const
   * @type {Enum}
   */
  var KEY_STATES = {
    PRESSED: "pressed", // Key-down on shift key.
    LOCKED: "locked", // Key is capslocked.
    UNLOCKED: "unlocked", // Default state.
    TAPPED: "tapped", // Key-down followed by key-up.
    CHORDING: "chording" // Key-down followed by other keys.
  };

  /**
   * The pointerdown event on shiftkey that may eventually trigger chording
   * state. pointerId and eventTarget are the two fields that is used now.
   * @type {PointerEvent}
   */
  var enterChordingEvent = undefined;

  /**
   * Uses a closure to define one long press timer among all shift keys
   * regardless of the layout they are in.
   * @type {function}
   */
  var shiftLongPressTimer = undefined;

  /**
   * The current state of the shift key.
   * @type {Enum}
   */
  var state = KEY_STATES.UNLOCKED;

  Polymer('kb-shift-key', {
    /**
     * Defines how capslock effects keyset transition. We always transition
     * from the lowerCaseKeysetId to the upperCaseKeysetId if capslock is
     * on.
     * @type {string}
     */
    lowerCaseKeysetId: 'lower',
    upperCaseKeysetId: 'upper',

    up: function(event) {
      if (state == KEY_STATES.CHORDING &&
          event.pointerId != enterChordingEvent.pointerId) {
        // Disables all other pointer events on shift keys when chording.
        return;
      }
      switch (state) {
        case KEY_STATES.PRESSED:
          state = KEY_STATES.TAPPED;
          break;
        case KEY_STATES.CHORDING:
          // Leaves chording only if the pointer that triggered it is
          // released.
          state = KEY_STATES.UNLOCKED;
          break;
        default:
          break;
      }
      // When releasing the shift key, it is not the same shift key that was
      // pressed. Updates the pointerId of the releasing shift key to make
      // sure key-up event fires correctly in kb-key-base.
      this.pointerId = enterChordingEvent.pointerId;
      this.super([event]);
    },

    out: function(event) {
      // Sliding off the shift key while chording is treated as a key-up.
      // Note that we switch to a new keyset on shift keydown, and a finger
      // movement on the new shift key will trigger this function being
      // called on the old shift key. We should not end chording in that
      // case.
      if (state == KEY_STATES.CHORDING &&
          event.pointerId == enterChordingEvent.pointerId &&
          event.target != enterChordingEvent.target) {
        state = KEY_STATES.UNLOCKED;
        var detail = this.populateDetails('out');
        this.fire("key-out", detail);
      }
    },

    down: function(event) {
      // First transition state so that populateDetails generates
      // correct data.
      switch (state) {
        case KEY_STATES.UNLOCKED:
          state = KEY_STATES.PRESSED;
          break;
        case KEY_STATES.TAPPED:
        case KEY_STATES.LOCKED:
          state = KEY_STATES.UNLOCKED;
          break;
        case KEY_STATES.PRESSED:
        case KEY_STATES.CHORDING:
          // We pressed another shift key at the same time,
          // so ignore second press.
          return;
        default:
          console.error("Undefined shift key state: " + state);
          break;
      }
      enterChordingEvent = event;
      // Trigger parent behaviour.
      this.super([event]);
      this.fire('enable-sel');
      // Populate double click transition details.
      var detail = {};
      detail.char = this.char || this.textContent;
      detail.toKeyset = this.upperCaseKeysetId;
      detail.nextKeyset = undefined;
      detail.callback = this.onDoubleClick;
      this.fire('enable-dbl', detail);
    },

    generateLongPressTimer: function() {
      return this.async(function() {
        var detail = this.populateDetails();
        if (state == KEY_STATES.LOCKED) {
          // We don't care about the longpress if we are already
          // capitalized.
          return;
        } else {
          state = KEY_STATES.LOCKED;
          detail.toKeyset = this.upperCaseKeysetId;
          detail.nextKeyset = undefined;
        }
        this.fire('key-longpress', detail);
      }, null, LONGPRESS_DELAY_MSEC);
    },

    // @return Whether the shift modifier is currently active.
    isActive: function() {
      return state != KEY_STATES.UNLOCKED;
    },

    /**
     * Callback function for when a double click is triggered.
     */
    onDoubleClick: function() {
      state = KEY_STATES.LOCKED;
    },

    /**
     * Notifies shift key that a non-control key was pressed down.
     * A control key is defined as one of shift, control or alt.
     */
    onNonControlKeyDown: function() {
      switch (state) {
        case (KEY_STATES.PRESSED):
          state = KEY_STATES.CHORDING;
          // Disable longpress timer.
          clearTimeout(shiftLongPressTimer);
          break;
        default:
          break;
      }
    },

    /**
     * Notifies key that a non-control keyed was typed.
     * A control key is defined as one of shift, control or alt.
     */
    onNonControlKeyTyped: function() {
      if (state == KEY_STATES.TAPPED)
        state = KEY_STATES.UNLOCKED;
    },

    /**
     * Callback function for when a space is pressed after punctuation.
     * @return {Object} The keyset transitions the keyboard should make.
     */
    onSpaceAfterPunctuation: function() {
       var detail = {};
       detail.toKeyset = this.upperCaseKeysetId;
       detail.nextKeyset = this.lowerCaseKeysetId;
       state = KEY_STATES.TAPPED;
       return detail;
    },

    populateDetails: function(caller) {
      var detail = this.super([caller]);
      switch(state) {
        case(KEY_STATES.LOCKED):
          detail.toKeyset = this.upperCaseKeysetId;
          break;
        case(KEY_STATES.UNLOCKED):
          detail.toKeyset = this.lowerCaseKeysetId;
          break;
        case(KEY_STATES.PRESSED):
          detail.toKeyset = this.upperCaseKeysetId;
          break;
        case(KEY_STATES.TAPPED):
          detail.toKeyset = this.upperCaseKeysetId;
          detail.nextKeyset = this.lowerCaseKeysetId;
          break;
        case(KEY_STATES.CHORDING):
          detail.toKeyset = this.lowerCaseKeysetId;
          break;
        default:
          break;
      }
      return detail;
    },

    /**
     *  Resets the shift key state.
     */
    reset: function() {
      state = KEY_STATES.UNLOCKED;
    },

    /**
     * Overrides longPressTimer for the shift key.
     */
    get longPressTimer() {
      return shiftLongPressTimer;
    },

    set longPressTimer(timer) {
      shiftLongPressTimer = timer;
    },

    get state() {
      return state;
    },

    get textKeyset() {
      switch (state) {
        case KEY_STATES.UNLOCKED:
          return this.lowerCaseKeysetId;
        default:
          return this.upperCaseKeysetId;
      }
    },
  });
})();