blob: 8a98fdf9c44e21b084969a41e6f94c11f45648f5 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* @fileoverview Sample IME for ChromeOS with IME Extension API.
*/
'use strict';
/**
* Namespace for this extension.
*/
var sampleImeForImeExtensionApi = window.sampleImeForImeExtensionApi || {};
/**
* Sample IME with IME extension API.
* @constructor
*/
sampleImeForImeExtensionApi.SampleIme = function() {
/**
* Context information which is provided by Chrome.
* @type {Object}
* @private
*/
this.context_ = null;
/**
* Engine id which is specified on manifest.js.
* @type {string}
* @private
*/
this.engineID_ = '';
// Some properties are initialized on
// {@code sampleImeForImeExtensionApi.SampleIme.clear_} and
// {@code sampleImeForImeExtensionApi.SampleIme.initializeMenuItems_}.
this.clear_();
this.initializeMenuItems_();
var ime = this;
chrome.input.ime.onActivate.addListener(
function(engineID) { ime.onActivate(engineID); });
chrome.input.ime.onDeactivated.addListener(
function(engineID) { ime.onDeactivated(engineID); });
chrome.input.ime.onFocus.addListener(
function(context) { ime.onFocus(context); });
chrome.input.ime.onBlur.addListener(
function(contextID) { ime.onBlur(contextID); });
chrome.input.ime.onInputContextUpdate.addListener(
function(context) { ime.onInputContextUpdate(context); });
chrome.input.ime.onKeyEvent.addListener(
function(engineID, keyData) {
return ime.onKeyEvent(engineID, keyData);
});
chrome.input.ime.onCandidateClicked.addListener(
function(engineID, candidateID, button) {
ime.onCandidateClicked(engineID, candidateID, button);
});
chrome.input.ime.onMenuItemActivated.addListener(
function(engineID, name) {
ime.onMenuItemActivated(engineID, name);
});
};
/**
* Stringifies key event data.
* @param {!Object} keyData Key event data.
* @return {string} Stringified key event data.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.stringifyKeyAndModifiers_ =
function(keyData) {
var keys = [keyData.key];
if (keyData.altKey) { keys.push('alt'); }
if (keyData.ctrlKey) { keys.push('ctrl'); }
if (keyData.shiftKey) { keys.push('shift'); }
return keys.join(' ');
};
/**
* Ignorable key set to determine we handle the key event or not.
* @type {!Object.<boolean>}
* @private
*/
sampleImeForImeExtensionApi.SampleIme.IGNORABLE_KEY_SET_ = (function() {
var IGNORABLE_KEYS = [
{ // PrintScreen shortcut.
key: 'ChromeOSSwitchWindow',
ctrlKey: true
}, { // PrintScreen shortcut.
key: 'ChromeOSSwitchWindow',
ctrlKey: true,
shiftKey: true
}
];
var ignorableKeySet = [];
for (var i = 0; i < IGNORABLE_KEYS.length; ++i) {
var key = sampleImeForImeExtensionApi.SampleIme.prototype.
stringifyKeyAndModifiers_(IGNORABLE_KEYS[i]);
ignorableKeySet[key] = true;
}
return ignorableKeySet;
})();
/**
* Immutable conversion table. It is used to suggest special candidates.
* @type {!Object.<string>}
* @private
*/
sampleImeForImeExtensionApi.SampleIme.CONVERSION_TABLE_ = {
star: '\u2606', // '☆'
heart: '\u2661' // '♡'
};
/**
* Page size of a candidate list.
* This value should not be greater than 12 since we use Roman number to
* indicates the candidate number on the list.
* @type {number}
* @private
*/
sampleImeForImeExtensionApi.SampleIme.PAGE_SIZE_ = 5;
/**
* Enum of IME state.
* @enum {number}
*/
sampleImeForImeExtensionApi.SampleIme.State = {
/** IME doesn't have any input text. */
PRECOMPOSITION: 0,
/**
* IME has a input text, but no candidate are expressly selected or input text
* is not segmented.
*/
COMPOSITION: 1,
/**
* IME has a input text, and one of the candidate is selected or input text is
* segmentated.
*/
CONVERSION: 2
};
/**
* Segment information of a composition text.
* @constructor
*/
sampleImeForImeExtensionApi.SampleIme.Segment = function() {
/**
* Start position of the segment.
* @type {number}
*/
this.start = 0;
/**
* Candidates list.
* @type {!Array.<string>}
*/
this.candidates = [];
/**
* Focused candidate index.
* @type {number}
*/
this.focusedIndex = 0;
};
/**
* Initializes menu items and some member variables.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.initializeMenuItems_ =
function() {
var menuItems = [];
var callbacks = {};
var that = this;
/**
* Indicates IME suggests dummy candidates or not.
* @type {boolean}
* @private
*/
this.useDummyCandidates_ = true;
var radioItems = [{
id: 'enable_dummy_candidates',
label: 'Enable dummy candidates',
style: 'radio',
checked: true,
enabled: true,
visible: true
}, {
id: 'disable_dummy_candidates',
label: 'Disable dummy candidates',
style: 'radio',
checked: false,
enabled: true,
visible: true
}];
for (var i = 0; i < radioItems.length; ++i) {
callbacks[radioItems[i].id] = (function(index) {
return function() {
for (var j = 0; j < radioItems.length; ++j) {
var isChecked = (index == j);
radioItems[j].checked = isChecked;
}
var isEnabled = index == 0;
that.useDummyCandidates_ = isEnabled;
};
})(i);
menuItems.push(radioItems[i]);
}
var enableRadioMenuItem = {
id: 'enable_radio_menu',
label: 'Enable radio menu',
style: 'check',
checked: true
};
menuItems.push(enableRadioMenuItem);
callbacks[enableRadioMenuItem.id] = function() {
enableRadioMenuItem.checked = !enableRadioMenuItem.checked;
for (var i = 0; i < radioItems.length; ++i) {
radioItems[i].enabled = enableRadioMenuItem.checked;
}
};
var displayRadioMenuItem = {
id: 'display_radio_menu',
label: 'Display radio menu',
style: 'check',
checked: true
};
menuItems.push(displayRadioMenuItem);
callbacks[displayRadioMenuItem.id] = function() {
displayRadioMenuItem.checked = !displayRadioMenuItem.checked;
for (var i = 0; i < radioItems.length; ++i) {
radioItems[i].visible = displayRadioMenuItem.checked;
}
};
/**
* Menu items of this IME.
*/
this.menuItems_ = menuItems;
/**
* Callback function table which is called when menu item is clicked.
*/
this.menuItemCallbackTable_ = callbacks;
};
/**
* Clears properties of IME.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.clear_ = function() {
/**
* Raw input text.
* @type {string}
* @private
*/
this.inputText_ = '';
/**
* Commit text.
* This is a volatile property, and will be cleared by
* {@code sampleImeForImeExtensionApi.SampleIme.updateCommitText_}.
* @type {?string}
* @private
*/
this.commitText_ = null;
/**
* Segments information.
* @type {!Array.<!sampleImeForImeExtensionApi.SampleIme.Segment>}
* @private
*/
this.segments_ = [];
/**
* Cursor position.
* @type {number}
* @private
*/
this.cursor_ = 0;
/**
* Focused segment index.
* @type {number}
* @private
*/
this.focusedSegmentIndex_ = 0;
/**
* The state of the IME.
* @type {sampleImeForImeExtensionApi.SampleIme.State}
* @private
*/
this.state_ = sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION;
};
/**
* Determines that IME is enabled or not using a context information.
* @return {boolean} IME is enabled or not.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.isImeEnabled_ = function() {
return this.context_ != null;
};
/**
* Appends a new empty segment on
* {@code sampleImeForImeExtensionApi.SampleIme.segments}.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.appendNewSegment_ = function() {
var startPosition = this.inputText_.length;
if (this.segments_.length == 0) {
startPosition = 0;
}
var newSegment = new sampleImeForImeExtensionApi.SampleIme.Segment();
newSegment.start = startPosition;
this.segments_.push(newSegment);
};
/**
* Gets input text on the segment.
* @param {number=} opt_segmentIndex Index of the segment you want to get
* a text. this.focusedSegmentIndex_ is used as a default value.
* @return {string} Input text of the segment.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.getInputTextOnSegment_ =
function(opt_segmentIndex) {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return '';
}
var segmentIndex = (opt_segmentIndex == undefined) ?
this.focusedSegmentIndex_ : opt_segmentIndex;
if (segmentIndex < 0 || this.segments_.length <= segmentIndex) {
return '';
}
var start = this.segments_[segmentIndex].start;
var end = (segmentIndex + 1 == this.segments_.length) ?
this.inputText_.length : this.segments_[segmentIndex + 1].start;
var length = end - start;
return this.inputText_.substr(start, length);
};
/**
* Generates and sets candidates of the segment.
* @param {number=} opt_segmentIndex Index of the segment you want to get a
* text. {@code sampleImeForImeExtensionApi.SampleIme.focusedSegmentIndex_}
* is used as a default value.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.generateCandidates_ =
function(opt_segmentIndex) {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return;
}
var segmentIndex = (opt_segmentIndex == undefined) ?
this.focusedSegmentIndex_ : opt_segmentIndex;
if (segmentIndex < 0 || this.segments_.length <= segmentIndex) {
return;
}
var segment = this.segments_[segmentIndex];
var text = this.getInputTextOnSegment_(segmentIndex);
segment.focusedIndex = 0;
if (text == '') {
segment.candidates = [];
return;
}
segment.candidates = [
text.replace(/(\w)/g, function(match) {
// Converts ASCII alphabet characters to its fullwidth versions.
var offset = 65248;
return String.fromCharCode(match.charCodeAt(0) + offset);
}),
text,
text.toUpperCase(),
text.substr(0, 1).toUpperCase() + text.substr(1).toLowerCase()
];
var table = sampleImeForImeExtensionApi.SampleIme.CONVERSION_TABLE_;
if (text in table) {
segment.candidates.push(table[text]);
}
if (this.useDummyCandidates_) {
segment.candidates.push('DummyCandidate1');
segment.candidates.push('DummyCandidate2');
}
};
/**
* Gets preedit text.
* @return {string} Preedit text.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.getPreeditText_ = function() {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return '';
}
var texts = [];
for (var i = 0; i < this.segments_.length; ++i) {
var segment = this.segments_[i];
texts.push(segment.candidates[segment.focusedIndex]);
}
return texts.join('');
};
/**
* Updates preedit text.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.updatePreedit_ = function() {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
chrome.input.ime.clearComposition({
contextID: this.context_.contextID
}, function(success) {
console.log('Composition is cleared. result=' + success);
});
return;
}
var segmentsData = [];
for (var i = 0; i < this.segments_.length; ++i) {
var text = this.segments_[i].candidates[this.segments_[i].focusedIndex];
var start = i == 0 ? 0 : segmentsData[i - 1].end;
var end = start + text.length;
segmentsData.push({
start: start,
end: end,
style: 'underline'
});
}
if (this.state_ == sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
segmentsData[this.focusedSegmentIndex_].style = 'doubleUnderline';
}
var cursorPos = 0;
if (this.state_ == sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
for (var i = 0; i < this.focusedSegmentIndex_; ++i) {
var segment = this.segments_[i];
cursorPos += segment.candidates[segment.focusedIndex].length;
}
} else {
cursorPos = this.cursor_;
}
var composition = {
contextID: this.context_.contextID,
text: this.getPreeditText_(),
segments: segmentsData,
cursor: cursorPos
};
if (this.state_ == sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
composition.selectionStart = segmentsData[this.focusedSegmentIndex_].start;
composition.selectionEnd = segmentsData[this.focusedSegmentIndex_].end;
}
chrome.input.ime.setComposition(composition, function(success) {
console.log('Composition is set. result=' + success);
});
};
/**
* Updates candidates.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.updateCandidates_ = function() {
var candidateWindowPropertiesCallback = function(success) {
console.log('Candidate window properties are updated. result=' + success);
};
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
chrome.input.ime.setCandidateWindowProperties({
engineID: this.engineID_,
properties: {
visible: false,
auxiliaryTextVisible: false
}
}, candidateWindowPropertiesCallback);
} else {
var PAGE_SIZE = sampleImeForImeExtensionApi.SampleIme.PAGE_SIZE_;
var segment = this.segments_[this.focusedSegmentIndex_];
var labels = [];
for (var i = 0; i < PAGE_SIZE; ++i) {
// Roman number.
labels.push(String.fromCharCode(0x2160 + i)); // 'Ⅰ' + i
}
chrome.input.ime.setCandidates({
contextID: this.context_.contextID,
candidates: segment.candidates.map(function(value, index) {
var candidate = {
candidate: value,
id: index,
label: labels[index % PAGE_SIZE]
};
if (index == 0) {
candidate.annotation = '1st candidate';
}
return candidate;
})
}, function(success) {
console.log('Candidates are set. result=' + success);
});
chrome.input.ime.setCursorPosition({
contextID: this.context_.contextID,
candidateID: segment.focusedIndex
}, function(success) {
console.log('Cursor position is set. result=' + success);
});
chrome.input.ime.setCandidateWindowProperties({
engineID: this.engineID_,
properties: {
visible: true,
cursorVisible: true,
vertical: true,
pageSize: PAGE_SIZE,
auxiliaryTextVisible: true,
auxiliaryText: 'Sample IME'
}
}, candidateWindowPropertiesCallback);
}
};
/**
* Updates commit text if {@code commitText_} isn't null.
* This function clears {@code commitText_} since it is a volatile property.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.updateCommitText_ = function() {
if (this.commitText_ === null) {
return;
}
chrome.input.ime.commitText({
contextID: this.context_.contextID,
text: this.commitText_
}, function(success) {
console.log('Commited. result=' + success);
});
this.commitText_ = null;
};
/**
* Updates output using IME Extension API.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.update_ = function() {
this.updatePreedit_();
this.updateCandidates_();
this.updateCommitText_();
};
/**
* Commits a preedit text and clears a context.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.commit_ = function() {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return;
}
var commitText = this.getPreeditText_();
this.clear_();
this.commitText_ = commitText;
};
/**
* Inserts characters into the cursor position.
* @param {string} value Text we want to insert into.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.insertCharacters_ =
function(value) {
if (this.state_ == sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
return;
}
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
this.appendNewSegment_();
this.focusedSegmentIndex_ = 0;
}
var text = this.inputText_;
this.inputText_ =
text.substr(0, this.cursor_) + value + text.substr(this.cursor_);
this.state_ = sampleImeForImeExtensionApi.SampleIme.State.COMPOSITION;
this.moveCursor_(this.cursor_ + value.length);
this.generateCandidates_();
};
/**
* Removes a character.
* @param {number} index index of the character you want to remove.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.removeCharacter_ =
function(index) {
if (this.state_ != sampleImeForImeExtensionApi.SampleIme.State.COMPOSITION) {
return;
}
if (index < 0 || this.inputText_.length <= index) {
return;
}
this.inputText_ =
this.inputText_.substr(0, index) + this.inputText_.substr(index + 1);
if (this.inputText_.length == 0) {
this.clear_();
return;
}
if (index < this.cursor_) {
this.moveCursor_(this.cursor_ - 1);
}
this.generateCandidates_();
};
/**
* Moves a cursor position.
* @param {number} index Cursor position.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.moveCursor_ = function(index) {
if (this.state_ != sampleImeForImeExtensionApi.SampleIme.State.COMPOSITION) {
return;
}
if (index < 0 || this.inputText_.length < index) {
return;
}
this.cursor_ = index;
};
/**
* Expands a focused segment.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.expandSegment_ = function() {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return;
}
this.state_ = sampleImeForImeExtensionApi.SampleIme.State.CONVERSION;
var index = this.focusedSegmentIndex_;
var segments = this.segments_;
if (index + 1 >= segments.length) {
return;
}
if ((index + 2 == segments.length &&
segments[index + 1].start + 1 == this.inputText_.length) ||
(index + 2 < segments.length &&
segments[index + 1].start + 1 == segments[index + 2].start)) {
segments.splice(index + 1, 1);
} else {
++segments[index + 1].start;
this.generateCandidates_(index + 1);
}
this.generateCandidates_();
};
/**
* Shrinks a focused segment.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.shrinkSegment_ = function() {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return;
}
this.state_ = sampleImeForImeExtensionApi.SampleIme.State.CONVERSION;
var segments = this.segments_;
var index = this.focusedSegmentIndex_;
if (index + 1 == segments.length) {
if (this.inputText_.length - segments[index].start > 1) {
this.appendNewSegment_();
segments[index + 1].start = this.inputText_.length - 1;
this.generateCandidates_();
this.generateCandidates_(index + 1);
}
} else {
if (segments[index + 1].start - segments[index].start > 1) {
--segments[index + 1].start;
this.generateCandidates_();
this.generateCandidates_(index + 1);
}
}
};
/**
* Resets a segmentation data of the preedit text.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.resetSegments_ = function() {
if (this.state_ != sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
return;
}
this.segments_ = [];
this.appendNewSegment_();
this.focusedSegmentIndex_ = 0;
this.generateCandidates_();
this.state_ = sampleImeForImeExtensionApi.SampleIme.State.COMPOSITION;
};
/**
* Selects a candidate.
* @param {number} candidateID index of the candidate.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.focusCandidate_ =
function(candidateID) {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return;
}
var segment = this.segments_[this.focusedSegmentIndex_];
if (candidateID < 0 || segment.candidates.length <= candidateID) {
return;
}
segment.focusedIndex = candidateID;
this.state_ = sampleImeForImeExtensionApi.SampleIme.State.CONVERSION;
};
/**
* Focuses a segment.
* @param {number} segmentID index of the segment.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.focusSegment_ =
function(segmentID) {
if (this.state_ != sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
return;
}
if (segmentID < 0 || this.segments_.length <= segmentID) {
return;
}
this.focusedSegmentIndex_ = segmentID;
};
/**
* Handles a alphabet key.
* @param {!Object} keyData key event data.
* @return {boolean} true if key event is consumed.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.handleKey_ = function(keyData) {
var keyValue = keyData.key;
if (keyData.altKey || keyData.ctrlKey || keyData.shiftKey) {
return false;
}
if (!keyValue.match(/^[a-z]$/i)) {
return false;
}
if (this.state_ == sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
this.commit_();
}
this.insertCharacters_(keyValue);
this.update_();
return true;
};
/**
* Handles a non-alphabet key.
* @param {!Object} keyData key event data.
* @return {boolean} true if key event is consumed.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.handleSpecialKey_ =
function(keyData) {
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
return false;
}
var segment = this.segments_[this.focusedSegmentIndex_];
if (!keyData.altKey && !keyData.ctrlKey && !keyData.shiftKey) {
switch (keyData.key) {
case 'Backspace':
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
this.resetSegments_();
} else if (this.cursor_ != 0) {
this.removeCharacter_(this.cursor_ - 1);
}
break;
case 'Delete':
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
this.resetSegments_();
} else if (this.cursor_ != this.inputText_.length) {
this.removeCharacter_(this.cursor_);
}
break;
case 'Up':
var previous_index = segment.focusedIndex - 1;
if (previous_index == -1) {
previous_index = segment.candidates.length - 1;
}
this.focusCandidate_(previous_index);
break;
case 'Down':
case ' ':
var next_index = segment.focusedIndex + 1;
if (next_index == segment.candidates.length) {
next_index = 0;
}
this.focusCandidate_(next_index);
break;
case 'Left':
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
if (this.focusedSegmentIndex_ != 0) {
this.focusSegment_(this.focusedSegmentIndex_ - 1);
}
} else {
this.moveCursor_(this.cursor_ - 1);
}
break;
case 'Right':
if (this.state_ ==
sampleImeForImeExtensionApi.SampleIme.State.CONVERSION) {
if (this.focusedSegmentIndex_ + 1 != this.segments_.length) {
this.focusSegment_(this.focusedSegmentIndex_ + 1);
}
} else {
this.moveCursor_(this.cursor_ + 1);
}
break;
case 'Enter':
this.commit_();
break;
default:
return true;
}
} else if (!keyData.altKey && !keyData.ctrlKey && keyData.shiftKey) {
switch (keyData.key) {
case 'Left':
if (this.state_ !=
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
this.shrinkSegment_();
}
break;
case 'Right':
if (this.state_ !=
sampleImeForImeExtensionApi.SampleIme.State.PRECOMPOSITION) {
this.expandSegment_();
}
break;
default:
return true;
}
} else {
return true;
}
this.update_();
return true;
};
/**
* Sets up a menu on a uber tray.
* @private
*/
sampleImeForImeExtensionApi.SampleIme.prototype.setUpMenu_ = function() {
chrome.input.ime.setMenuItems({
engineID: this.engineID_,
items: this.menuItems_
}, function() {
console.log('Menu items are set.');
});
};
/**
* Callback method. It is called when IME is activated.
* @param {string} engineID engine ID.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onActivate =
function(engineID) {
this.engineID_ = engineID;
this.clear_();
this.setUpMenu_();
};
/**
* Callback method. It is called when IME is deactivated.
* @param {string} engineID engine ID.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onDeactivated =
function(engineID) {
this.clear_();
this.engineID_ = '';
};
/**
* Callback method. It is called when a context acquires a focus.
* @param {!Object} context context information.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onFocus = function(context) {
this.context_ = context;
this.clear_();
};
/**
* Callback method. It is called when a context lost a focus.
* @param {number} contextID ID of the context.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onBlur = function(contextID) {
this.clear_();
this.context_ = null;
};
/**
* Callback method. It is called when properties of the context is changed.
* @param {!Object} context context information.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onInputContextUpdate =
function(context) {
this.context_ = context;
if (!this.isImeEnabled_()) {
this.clear_();
}
this.update_();
};
/**
* Callback method. It is called when IME catches a new key event.
* @param {string} engineID ID of the engine.
* @param {!Object} keyData key event data.
* @return {boolean} true if the key event is consumed.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onKeyEvent =
function(engineID, keyData) {
if (keyData.type != 'keydown' || !this.isImeEnabled_()) {
return false;
}
var key = this.stringifyKeyAndModifiers_(keyData);
if (sampleImeForImeExtensionApi.SampleIme.IGNORABLE_KEY_SET_[key]) {
return false;
}
return this.handleKey_(keyData) || this.handleSpecialKey_(keyData);
};
/**
* Callback method. It is called when candidates on candidate window is clicked.
* @param {string} engineID ID of the engine.
* @param {number} candidateID Index of the candidate.
* @param {string} button Which mouse button was clicked.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onCandidateClicked =
function(engineID, candidateID, button) {
if (button == 'left') {
this.focusCandidate_(candidateID);
this.update_();
}
};
/**
* Callback method. It is called when menu item on uber tray is activated.
* @param {string} engineID ID of the engine.
* @param {string} name name of the menu item.
*/
sampleImeForImeExtensionApi.SampleIme.prototype.onMenuItemActivated =
function(engineID, name) {
var callback = this.menuItemCallbackTable_[name];
if (typeof(callback) != 'function') {
return;
}
callback();
chrome.input.ime.updateMenuItems({
engineID: engineID,
items: this.menuItems_
}, function() {
console.log('Menu items are updated.');
});
};