• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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