Home | History | Annotate | Download | only in elements
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 (function () {
      6 
      7   /**
      8    * The possible states of the shift key.
      9    * Unlocked is the default state. Locked for capslocked, pressed is a
     10    * key-down and tapped for a key-down followed by an immediate key-up.
     11    * @const
     12    * @type {Enum}
     13    */
     14   var KEY_STATES = {
     15     PRESSED: "pressed", // Key-down on shift key.
     16     LOCKED: "locked", // Key is capslocked.
     17     UNLOCKED: "unlocked", // Default state.
     18     TAPPED: "tapped", // Key-down followed by key-up.
     19     CHORDING: "chording" // Key-down followed by other keys.
     20   };
     21 
     22   /**
     23    * The pointerdown event on shiftkey that may eventually trigger chording
     24    * state. pointerId and eventTarget are the two fields that is used now.
     25    * @type {PointerEvent}
     26    */
     27   var enterChordingEvent = undefined;
     28 
     29   /**
     30    * Uses a closure to define one long press timer among all shift keys
     31    * regardless of the layout they are in.
     32    * @type {function}
     33    */
     34   var shiftLongPressTimer = undefined;
     35 
     36   /**
     37    * The current state of the shift key.
     38    * @type {Enum}
     39    */
     40   var state = KEY_STATES.UNLOCKED;
     41 
     42   Polymer('kb-shift-key', {
     43     /**
     44      * Defines how capslock effects keyset transition. We always transition
     45      * from the lowerCaseKeysetId to the upperCaseKeysetId if capslock is
     46      * on.
     47      * @type {string}
     48      */
     49     lowerCaseKeysetId: 'lower',
     50     upperCaseKeysetId: 'upper',
     51 
     52     up: function(event) {
     53       if (state == KEY_STATES.CHORDING &&
     54           event.pointerId != enterChordingEvent.pointerId) {
     55         // Disables all other pointer events on shift keys when chording.
     56         return;
     57       }
     58       switch (state) {
     59         case KEY_STATES.PRESSED:
     60           state = KEY_STATES.TAPPED;
     61           break;
     62         case KEY_STATES.CHORDING:
     63           // Leaves chording only if the pointer that triggered it is
     64           // released.
     65           state = KEY_STATES.UNLOCKED;
     66           break;
     67         default:
     68           break;
     69       }
     70       // When releasing the shift key, it is not the same shift key that was
     71       // pressed. Updates the pointerId of the releasing shift key to make
     72       // sure key-up event fires correctly in kb-key-base.
     73       this.pointerId = enterChordingEvent.pointerId;
     74       this.super([event]);
     75     },
     76 
     77     out: function(event) {
     78       // Sliding off the shift key while chording is treated as a key-up.
     79       // Note that we switch to a new keyset on shift keydown, and a finger
     80       // movement on the new shift key will trigger this function being
     81       // called on the old shift key. We should not end chording in that
     82       // case.
     83       if (state == KEY_STATES.CHORDING &&
     84           event.pointerId == enterChordingEvent.pointerId &&
     85           event.target != enterChordingEvent.target) {
     86         state = KEY_STATES.UNLOCKED;
     87         var detail = this.populateDetails('out');
     88         this.fire("key-out", detail);
     89       }
     90     },
     91 
     92     down: function(event) {
     93       // First transition state so that populateDetails generates
     94       // correct data.
     95       switch (state) {
     96         case KEY_STATES.UNLOCKED:
     97           state = KEY_STATES.PRESSED;
     98           break;
     99         case KEY_STATES.TAPPED:
    100         case KEY_STATES.LOCKED:
    101           state = KEY_STATES.UNLOCKED;
    102           break;
    103         case KEY_STATES.PRESSED:
    104         case KEY_STATES.CHORDING:
    105           // We pressed another shift key at the same time,
    106           // so ignore second press.
    107           return;
    108         default:
    109           console.error("Undefined shift key state: " + state);
    110           break;
    111       }
    112       enterChordingEvent = event;
    113       // Trigger parent behaviour.
    114       this.super([event]);
    115       this.fire('enable-sel');
    116       // Populate double click transition details.
    117       var detail = {};
    118       detail.char = this.char || this.textContent;
    119       detail.toKeyset = this.upperCaseKeysetId;
    120       detail.nextKeyset = undefined;
    121       detail.callback = this.onDoubleClick;
    122       this.fire('enable-dbl', detail);
    123     },
    124 
    125     generateLongPressTimer: function() {
    126       return this.async(function() {
    127         var detail = this.populateDetails();
    128         if (state == KEY_STATES.LOCKED) {
    129           // We don't care about the longpress if we are already
    130           // capitalized.
    131           return;
    132         } else {
    133           state = KEY_STATES.LOCKED;
    134           detail.toKeyset = this.upperCaseKeysetId;
    135           detail.nextKeyset = undefined;
    136         }
    137         this.fire('key-longpress', detail);
    138       }, null, LONGPRESS_DELAY_MSEC);
    139     },
    140 
    141     // @return Whether the shift modifier is currently active.
    142     isActive: function() {
    143       return state != KEY_STATES.UNLOCKED;
    144     },
    145 
    146     /**
    147      * Callback function for when a double click is triggered.
    148      */
    149     onDoubleClick: function() {
    150       state = KEY_STATES.LOCKED;
    151     },
    152 
    153     /**
    154      * Notifies shift key that a non-control key was pressed down.
    155      * A control key is defined as one of shift, control or alt.
    156      */
    157     onNonControlKeyDown: function() {
    158       switch (state) {
    159         case (KEY_STATES.PRESSED):
    160           state = KEY_STATES.CHORDING;
    161           // Disable longpress timer.
    162           clearTimeout(shiftLongPressTimer);
    163           break;
    164         default:
    165           break;
    166       }
    167     },
    168 
    169     /**
    170      * Notifies key that a non-control keyed was typed.
    171      * A control key is defined as one of shift, control or alt.
    172      */
    173     onNonControlKeyTyped: function() {
    174       if (state == KEY_STATES.TAPPED)
    175         state = KEY_STATES.UNLOCKED;
    176     },
    177 
    178     /**
    179      * Callback function for when a space is pressed after punctuation.
    180      * @return {Object} The keyset transitions the keyboard should make.
    181      */
    182     onSpaceAfterPunctuation: function() {
    183        var detail = {};
    184        detail.toKeyset = this.upperCaseKeysetId;
    185        detail.nextKeyset = this.lowerCaseKeysetId;
    186        state = KEY_STATES.TAPPED;
    187        return detail;
    188     },
    189 
    190     populateDetails: function(caller) {
    191       var detail = this.super([caller]);
    192       switch(state) {
    193         case(KEY_STATES.LOCKED):
    194           detail.toKeyset = this.upperCaseKeysetId;
    195           break;
    196         case(KEY_STATES.UNLOCKED):
    197           detail.toKeyset = this.lowerCaseKeysetId;
    198           break;
    199         case(KEY_STATES.PRESSED):
    200           detail.toKeyset = this.upperCaseKeysetId;
    201           break;
    202         case(KEY_STATES.TAPPED):
    203           detail.toKeyset = this.upperCaseKeysetId;
    204           detail.nextKeyset = this.lowerCaseKeysetId;
    205           break;
    206         case(KEY_STATES.CHORDING):
    207           detail.toKeyset = this.lowerCaseKeysetId;
    208           break;
    209         default:
    210           break;
    211       }
    212       return detail;
    213     },
    214 
    215     /**
    216      *  Resets the shift key state.
    217      */
    218     reset: function() {
    219       state = KEY_STATES.UNLOCKED;
    220     },
    221 
    222     /**
    223      * Overrides longPressTimer for the shift key.
    224      */
    225     get longPressTimer() {
    226       return shiftLongPressTimer;
    227     },
    228 
    229     set longPressTimer(timer) {
    230       shiftLongPressTimer = timer;
    231     },
    232 
    233     get state() {
    234       return state;
    235     },
    236 
    237     get textKeyset() {
    238       switch (state) {
    239         case KEY_STATES.UNLOCKED:
    240           return this.lowerCaseKeysetId;
    241         default:
    242           return this.upperCaseKeysetId;
    243       }
    244     },
    245   });
    246 })();
    247