Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(612)

Side by Side Diff: third_party/google_input_tools/src/chrome/os/keyboard/model.js

Issue 674153004: Add third_party/google-input-tools: Take 2 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@google_input_tools
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
2 // limitations under the License.
3 // See the License for the specific language governing permissions and
4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5 // distributed under the License is distributed on an "AS-IS" BASIS,
6 // Unless required by applicable law or agreed to in writing, software
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // You may obtain a copy of the License at
11 // you may not use this file except in compliance with the License.
12 // Licensed under the Apache License, Version 2.0 (the "License");
13 //
14 // Copyright 2013 The ChromeOS VK Authors. All Rights Reserved.
15 //
16 // Licensed under the Apache License, Version 2.0 (the "License");
17 // you may not use this file except in compliance with the License.
18 // You may obtain a copy of the License at
19 //
20 // http://www.apache.org/licenses/LICENSE-2.0
21 //
22 // Unless required by applicable law or agreed to in writing, software
23 // distributed under the License is distributed on an "AS-IS" BASIS,
24 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 // See the License for the specific language governing permissions and
26 // limitations under the License.
27
28 /**
29 * @fileoverview Definition of Model class.
30 * It is responsible for dynamically loading the layout JS files. It
31 * interprets the layout info and provides the function of getting
32 * transformed chars and recording history states to Model.
33 * It notifies View via events when layout info changes.
34 * This is the Model of MVC pattern.
35 */
36
37 goog.provide('i18n.input.chrome.vk.Model');
38
39 goog.require('goog.events.EventTarget');
40 goog.require('goog.net.jsloader');
41 goog.require('goog.object');
42 goog.require('goog.string');
43 goog.require('i18n.input.chrome.vk.EventType');
44 goog.require('i18n.input.chrome.vk.LayoutEvent');
45 goog.require('i18n.input.chrome.vk.ParsedLayout');
46
47
48
49 /**
50 * Creates the Model object.
51 *
52 * @constructor
53 * @extends {goog.events.EventTarget}
54 */
55 i18n.input.chrome.vk.Model = function() {
56 goog.base(this);
57
58 /**
59 * The registered layouts object.
60 * Its format is {<layout code>: <parsed layout obj>}.
61 *
62 * @type {!Object.<!i18n.input.chrome.vk.ParsedLayout|boolean>}
63 * @private
64 */
65 this.layouts_ = {};
66
67 /**
68 * The active layout code.
69 *
70 * @type {string}
71 * @private
72 */
73 this.activeLayout_ = '';
74
75 /**
76 * The layout code of which the layout is "being activated" when the layout
77 * hasn't been loaded yet.
78 *
79 * @type {string}
80 * @private
81 */
82 this.delayActiveLayout_ = '';
83
84 /**
85 * History state used for ambiguous transforms.
86 *
87 * @type {!Object}
88 * @private
89 */
90 this.historyState_ = {
91 previous: {text: '', transat: -1},
92 ambi: '',
93 current: {text: '', transat: -1}
94 };
95
96 // Exponses the onLayoutLoaded so that the layout JS can call it back.
97 goog.exportSymbol('cros_vk_loadme', goog.bind(this.onLayoutLoaded_, this));
98 };
99 goog.inherits(i18n.input.chrome.vk.Model, goog.events.EventTarget);
100
101
102 /**
103 * Loads the layout in the background.
104 *
105 * @param {string} layoutCode The layout will be loaded.
106 */
107 i18n.input.chrome.vk.Model.prototype.loadLayout = function(layoutCode) {
108 if (!layoutCode) return;
109
110 var parsedLayout = this.layouts_[layoutCode];
111 // The layout is undefined means not loaded, false means loading.
112 if (parsedLayout == undefined) {
113 this.layouts_[layoutCode] = false;
114 i18n.input.chrome.vk.Model.loadLayoutScript_(layoutCode);
115 } else if (parsedLayout) {
116 this.dispatchEvent(new i18n.input.chrome.vk.LayoutEvent(
117 i18n.input.chrome.vk.EventType.LAYOUT_LOADED,
118 /** @type {!Object} */ (parsedLayout)));
119 }
120 };
121
122
123 /**
124 * Activate layout by setting the current layout.
125 *
126 * @param {string} layoutCode The layout will be set as current layout.
127 */
128 i18n.input.chrome.vk.Model.prototype.activateLayout = function(
129 layoutCode) {
130 if (!layoutCode) return;
131
132 if (this.activeLayout_ != layoutCode) {
133 var parsedLayout = this.layouts_[layoutCode];
134 if (parsedLayout) {
135 this.activeLayout_ = layoutCode;
136 this.delayActiveLayout_ = '';
137 this.clearHistory();
138 } else if (parsedLayout == false) { // Layout being loaded?
139 this.delayActiveLayout_ = layoutCode;
140 }
141 }
142 };
143
144
145 /**
146 * Gets the current layout.
147 *
148 * @return {string} The current layout code.
149 */
150 i18n.input.chrome.vk.Model.prototype.getCurrentLayout = function() {
151 return this.activeLayout_;
152 };
153
154
155 /**
156 * Predicts whether there would be future transforms for the history text.
157 *
158 * @return {number} The matched position. Returns -1 for no match.
159 */
160 i18n.input.chrome.vk.Model.prototype.predictHistory = function() {
161 if (!this.activeLayout_ || !this.layouts_[this.activeLayout_]) {
162 return -1;
163 }
164 var parsedLayout = this.layouts_[this.activeLayout_];
165 var history = this.historyState_;
166 var text, transat;
167 if (history.ambi) {
168 text = history.previous.text;
169 transat = history.previous.transat;
170 // Tries to predict transform for previous history.
171 if (transat > 0) {
172 text = text.slice(0, transat) + '\u001d' + text.slice(transat) +
173 history.ambi;
174 } else {
175 text += history.ambi;
176 }
177 if (parsedLayout.predictTransform(text) >= 0) {
178 // If matched previous history, always return 0 because outside will use
179 // this to keep the composition text.
180 return 0;
181 }
182 }
183 // Tries to predict transform for current history.
184 text = history.current.text;
185 transat = history.current.transat;
186 if (transat >= 0) {
187 text = text.slice(0, transat) + '\u001d' + text.slice(transat);
188 }
189 var pos = parsedLayout.predictTransform(text);
190 if (transat >= 0 && pos > transat) {
191 // Adjusts the pos for removing the temporary \u001d character.
192 pos--;
193 }
194 return pos;
195 };
196
197
198 /**
199 * Translates the key code into the chars to put into the active input box.
200 *
201 * @param {string} chars The key commit chars.
202 * @param {string} charsBeforeCaret The chars before the caret in the active
203 * input box. This will be used to compare with the history states.
204 * @return {Object} The replace chars object whose 'back' means delete how many
205 * chars back from the caret, and 'chars' means the string insert after the
206 * deletion. Returns null if no result.
207 */
208 i18n.input.chrome.vk.Model.prototype.translate = function(
209 chars, charsBeforeCaret) {
210 if (!this.activeLayout_ || !chars) {
211 return null;
212 }
213 var parsedLayout = this.layouts_[this.activeLayout_];
214 if (!parsedLayout) {
215 return null;
216 }
217
218 this.matchHistory_(charsBeforeCaret);
219 var result, history = this.historyState_;
220 if (history.ambi) {
221 // If ambi is not empty, it means some ambi chars has been typed
222 // before. e.g. ka->k, kaa->K, typed 'ka', and now typing 'a':
223 // history.previous == 'k',1
224 // history.current == 'k',1
225 // history.ambi == 'a'
226 // So now we should get transform of 'k\u001d' + 'aa'.
227 result = parsedLayout.transform(
228 history.previous.text, history.previous.transat,
229 history.ambi + chars);
230 // Note: result.back could be negative number. In such case, we should give
231 // up the transform result. This is to be compatible the old vk behaviors.
232 if (result && result.back < 0) {
233 result = null;
234 }
235 }
236 if (result) {
237 // Because the result is related to previous history, adjust the result so
238 // that it is related to current history.
239 var prev = history.previous.text;
240 prev = prev.slice(0, prev.length - result.back);
241 prev += result.chars;
242 result.back = history.current.text.length;
243 result.chars = prev;
244 } else {
245 // If no ambi chars or no transforms for ambi chars, try to match the
246 // regular transforms. In above case, if now typing 'b', we should get
247 // transform of 'k\u001d' + 'b'.
248 result = parsedLayout.transform(
249 history.current.text, history.current.transat, chars);
250 }
251 // Updates the history state.
252 if (parsedLayout.isAmbiChars(history.ambi + chars)) {
253 if (!history.ambi) {
254 // Empty ambi means chars should be the first ambi chars.
255 // So now we should set the previous.
256 history.previous = goog.object.clone(history.current);
257 }
258 history.ambi += chars;
259 } else if (parsedLayout.isAmbiChars(chars)) {
260 // chars could match ambi regex when ambi+chars cannot.
261 // In this case, record the current history to previous, and set ambi as
262 // chars.
263 history.previous = goog.object.clone(history.current);
264 history.ambi = chars;
265 } else {
266 history.previous.text = '';
267 history.previous.transat = -1;
268 history.ambi = '';
269 }
270 // Updates the history text per transform result.
271 var text = history.current.text;
272 var transat = history.current.transat;
273 if (result) {
274 text = text.slice(0, text.length - result.back);
275 text += result.chars;
276 transat = text.length;
277 } else {
278 text += chars;
279 // This function doesn't return null. So if result is null, fill it.
280 result = {back: 0, chars: chars};
281 }
282 // The history text cannot cannot contain SPACE!
283 var spacePos = text.lastIndexOf(' ');
284 if (spacePos >= 0) {
285 text = text.slice(spacePos + 1);
286 if (transat > spacePos) {
287 transat -= spacePos + 1;
288 } else {
289 transat = -1;
290 }
291 }
292 history.current.text = text;
293 history.current.transat = transat;
294
295 return result;
296 };
297
298
299 /**
300 * Wether the active layout has transforms defined.
301 *
302 * @return {boolean} True if transforms defined, false otherwise.
303 */
304 i18n.input.chrome.vk.Model.prototype.hasTransforms = function() {
305 var parsedLayout = this.layouts_[this.activeLayout_];
306 return !!parsedLayout && !!parsedLayout.transforms;
307 };
308
309
310 /**
311 * Processes the backspace key. It affects the history state.
312 *
313 * @param {string} charsBeforeCaret The chars before the caret in the active
314 * input box. This will be used to compare with the history states.
315 */
316 i18n.input.chrome.vk.Model.prototype.processBackspace = function(
317 charsBeforeCaret) {
318 this.matchHistory_(charsBeforeCaret);
319
320 var history = this.historyState_;
321 // Reverts the current history. If the backspace across over the transat pos,
322 // clean it up.
323 var text = history.current.text;
324 if (text) {
325 text = text.slice(0, text.length - 1);
326 history.current.text = text;
327 if (history.current.transat > text.length) {
328 history.current.transat = text.length;
329 }
330
331 text = history.ambi;
332 if (text) { // If there is ambi text, remove the last char in ambi.
333 history.ambi = text.slice(0, text.length - 1);
334 }
335 // Prev history only exists when ambi is not empty.
336 if (!history.ambi) {
337 history.previous = {text: '', transat: -1};
338 }
339 } else {
340 // Cleans up the previous history.
341 history.previous = {text: '', transat: -1};
342 history.ambi = '';
343 // Cleans up the current history.
344 history.current = goog.object.clone(history.previous);
345 }
346 };
347
348
349 /**
350 * Callback when layout loaded.
351 *
352 * @param {!Object} layout The layout object passed from the layout JS's loadme
353 * callback.
354 * @private
355 */
356 i18n.input.chrome.vk.Model.prototype.onLayoutLoaded_ = function(layout) {
357 var parsedLayout = new i18n.input.chrome.vk.ParsedLayout(layout);
358 if (parsedLayout.id) {
359 this.layouts_[parsedLayout.id] = parsedLayout;
360 }
361 if (this.delayActiveLayout_ == layout.id) {
362 this.activateLayout(this.delayActiveLayout_);
363 this.delayActiveLayout_ = '';
364 }
365 this.dispatchEvent(new i18n.input.chrome.vk.LayoutEvent(
366 i18n.input.chrome.vk.EventType.LAYOUT_LOADED, parsedLayout));
367 };
368
369
370 /**
371 * Matches the given text to the last transformed text. Clears history if they
372 * are not matched.
373 *
374 * @param {string} text The text to be matched.
375 * @private
376 */
377 i18n.input.chrome.vk.Model.prototype.matchHistory_ = function(text) {
378 var hisText = this.historyState_.current.text;
379 if (!hisText || !text || !(goog.string.endsWith(text, hisText) ||
380 goog.string.endsWith(hisText, text))) {
381 this.clearHistory();
382 }
383 };
384
385
386 /**
387 * Clears the history state.
388 */
389 i18n.input.chrome.vk.Model.prototype.clearHistory = function() {
390 this.historyState_.ambi = '';
391 this.historyState_.previous = {text: '', transat: -1};
392 this.historyState_.current = goog.object.clone(this.historyState_.previous);
393 };
394
395
396 /**
397 * Prunes the history state to remove a number of chars at beginning.
398 *
399 * @param {number} count The count of chars to be removed.
400 */
401 i18n.input.chrome.vk.Model.prototype.pruneHistory = function(count) {
402 var pruneFunc = function(his) {
403 his.text = his.text.slice(count);
404 if (his.transat > 0) {
405 his.transat -= count;
406 if (his.transat <= 0) {
407 his.transat = -1;
408 }
409 }
410 };
411 pruneFunc(this.historyState_.previous);
412 pruneFunc(this.historyState_.current);
413 };
414
415
416 /**
417 * Loads the script for a layout.
418 *
419 * @param {string} layoutCode The layout code.
420 * @private
421 */
422 i18n.input.chrome.vk.Model.loadLayoutScript_ = function(layoutCode) {
423 goog.net.jsloader.load('layouts/' + layoutCode + '.js');
424 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698