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