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