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