Creates fat-finger 'alternates' for use in LMLayer

This commit is contained in:
jahorton 2019-05-24 15:20:16 +07:00
parent fb76feae69
commit 2f2ba2de00
5 changed files with 113 additions and 64 deletions

View file

@ -311,6 +311,10 @@ namespace com.keyman.osk {
// generating a probability distribution.
this.row.forEach(function(row: ActiveRow): void {
row.key.forEach(function(key: ActiveKey): void {
// If the key lacks an ID, just skip it. Sometimes used for padding.
if(!key.id) {
return;
}
// These represent the within-key distance of the touch from the key's center.
// Both should be on the interval [0, 0.5].
let dx = Math.abs(touchCoords.x - key.proportionalX);
@ -361,9 +365,9 @@ namespace com.keyman.osk {
// Keys usually are specified in a "long form" prefixed with their layer's ID.
if(keyId.indexOf(this.id + '-') == 0) {
keyId = keyId.replace(this.id + '-', '');
return this.keyMap[keyId];
}
return this.keyMap[keyId];
}
}
@ -371,10 +375,19 @@ namespace com.keyman.osk {
layer: ActiveLayer[];
font: string;
/**
* Facilitates mapping layer id strings to their specification objects.
*/
layerMap: {[layerId: string]: ActiveLayer};
private constructor() {
}
getLayer(layerId: string): ActiveLayer {
return this.layerMap[layerId];
}
/**
*
* @param layout
@ -388,6 +401,7 @@ namespace com.keyman.osk {
// Create a separate OSK div for each OSK layer, only one of which will ever be visible
var n: number, i: number;
var layers: LayoutLayer[], layer: LayoutLayer;
let layerMap: {[layerId: string]: ActiveLayer} = {};
var rows: LayoutRow[];
layers=layout['layer'];
@ -409,6 +423,7 @@ namespace com.keyman.osk {
for(n=0; n<layers.length; n++) {
ActiveLayer.polyfill(layers[n], formFactor);
layerMap[layers[n].id] = layers[n] as ActiveLayer;
}
// Add class functions to the existing layout object, allowing it to act as an ActiveLayout.
@ -419,7 +434,10 @@ namespace com.keyman.osk {
}
}
return layout as ActiveLayout;
let aLayout = layout as ActiveLayout;
aLayout.layerMap = layerMap;
return aLayout;
}
}
}

View file

@ -26,28 +26,35 @@ namespace com.keyman.text {
readonly token: number;
readonly keystroke: KeyEvent;
readonly transform: Transform;
readonly alternates: Alternate[];
readonly preInput: Mock;
// While not presently needed, there's a chance we'll want to have this information tracked
// for developments later down the line. May as well resolve the logic now.
readonly removedDks: Deadkey[];
readonly insertedDks: Deadkey[];
// // While not presently needed, there's a chance we'll want to have this information tracked
// // for developments later down the line. May as well resolve the logic now.
// readonly removedDks: Deadkey[];
// readonly insertedDks: Deadkey[];
private static tokenSeed: number = 0;
constructor(keystroke: KeyEvent, transform: Transform, preInput: Mock, removedDks: Deadkey[], insertedDks: Deadkey[]) {
constructor(keystroke: KeyEvent, transform: Transform, preInput: Mock, alternates: Alternate[]/*, removedDks: Deadkey[], insertedDks: Deadkey[]*/) {
this.token = Transcription.tokenSeed++;
this.keystroke = keystroke;
this.transform = transform;
this.alternates = alternates;
this.preInput = preInput;
this.removedDks = removedDks;
this.insertedDks = insertedDks;
// this.removedDks = removedDks;
// this.insertedDks = insertedDks;
this.transform.id = this.token;
}
}
export interface Alternate {
t: Transform;
p: number;
}
export abstract class OutputTarget {
private _dks: text.DeadkeyTracker;
@ -122,46 +129,46 @@ namespace com.keyman.text {
return new TextTransform(delta, deletedLeft, originalRight - undeletedRight);
}
buildTranscriptionFrom(original: OutputTarget, keyEvent: KeyEvent): Transcription {
buildTranscriptionFrom(original: OutputTarget, keyEvent: KeyEvent, alternates?: Alternate[]): Transcription {
let transform = this.buildTransformFrom(original);
// While not presently needed, there's a chance we'll want to have Deadkey mutation tracked
// for developments later down the line. May as well resolve the logic now.
// // While not presently needed, there's a chance we'll want to have Deadkey mutation tracked
// // for developments later down the line. May as well resolve the logic now.
// Determine what deadkeys were removed and added.
let fromDks = original.deadkeys().toSortedArray();
let toDks = this.deadkeys().toSortedArray();
// // Determine what deadkeys were removed and added.
// let fromDks = original.deadkeys().toSortedArray();
// let toDks = this.deadkeys().toSortedArray();
let commonDks: Deadkey[] = [];
let removedDks: Deadkey[] = [];
let insertedDks: Deadkey[] = [];
// let commonDks: Deadkey[] = [];
// let removedDks: Deadkey[] = [];
// let insertedDks: Deadkey[] = [];
// Since the ordinals of any original deadkey are preserved, we can check for the (id, ordinal).
fromDks.forEach(function(dk: Deadkey){
for(var i=0; i < toDks.length; i++) {
if(toDks[i].d == dk.d && toDks[i].o == dk.o) {
commonDks.push(dk);
return;
}
}
// // Since the ordinals of any original deadkey are preserved, we can check for the (id, ordinal).
// fromDks.forEach(function(dk: Deadkey){
// for(var i=0; i < toDks.length; i++) {
// if(toDks[i].d == dk.d && toDks[i].o == dk.o) {
// commonDks.push(dk);
// return;
// }
// }
// Wasn't found to be in common? Must have been removed.
removedDks.push(dk);
});
// // Wasn't found to be in common? Must have been removed.
// removedDks.push(dk);
// });
toDks.forEach(function(dk: Deadkey) {
for(var i=0; i < commonDks.length; i++) {
if(commonDks[i].d == dk.d && commonDks[i].o == dk.o) {
// It's a common one.
return;
}
}
// toDks.forEach(function(dk: Deadkey) {
// for(var i=0; i < commonDks.length; i++) {
// if(commonDks[i].d == dk.d && commonDks[i].o == dk.o) {
// // It's a common one.
// return;
// }
// }
// Wasn't found to be in common? Must have been inserted.
insertedDks.push(dk);
});
// // Wasn't found to be in common? Must have been inserted.
// insertedDks.push(dk);
// });
return new Transcription(keyEvent, transform, Mock.from(original), removedDks, insertedDks);
return new Transcription(keyEvent, transform, Mock.from(original), alternates/*, removedDks, insertedDks*/);
}
/**

View file

@ -313,6 +313,8 @@ namespace com.keyman.text.prediction {
this.recordTranscription(transcription);
let transform = transcription.transform;
// TODO: Use this instead of the single Transform; requires LMLayer work.
let alternates = transcription.alternates;
var promise = this.currentPromise = this.lmEngine.predict(transform, context);
let mm = this;

View file

@ -38,7 +38,7 @@ namespace com.keyman.text {
* @param {boolean} usingOSK
* @return {string}
*/
defaultKeyOutput(Lkc: KeyEvent, keyShiftState: number, usingOSK: boolean): string {
defaultKeyOutput(Lkc: KeyEvent, keyShiftState: number, usingOSK: boolean, disableDOM: boolean): string {
var Lkc: KeyEvent;
let keyName = Lkc.kName;
@ -73,13 +73,19 @@ namespace com.keyman.text {
keyman.interface.defaultBackspace();
return '';
case Codes.keyCodes['K_TAB']:
domManager.moveToNext(keyShiftState);
if(!disableDOM) {
domManager.moveToNext(keyShiftState);
}
break;
case Codes.keyCodes['K_TABBACK']:
domManager.moveToNext(true);
if(!disableDOM) {
domManager.moveToNext(true);
}
break;
case Codes.keyCodes['K_TABFWD']:
domManager.moveToNext(false);
if(!disableDOM) {
domManager.moveToNext(false);
}
break;
case Codes.keyCodes['K_ENTER']:
// Insert new line in text area fields
@ -95,12 +101,13 @@ namespace com.keyman.text {
} else if(typeof(Lelem.base) != 'undefined' && dom.Utils.instanceof(Lelem.base, "HTMLInputElement")) {
inputEle = <HTMLInputElement> Lelem.base;
}
if (inputEle && (inputEle.type == 'search' || inputEle.type == 'submit')) {
inputEle.disabled=false;
inputEle.form.submit();
} else {
domManager.moveToNext(false);
if(!disableDOM) {
if (inputEle && (inputEle.type == 'search' || inputEle.type == 'submit')) {
inputEle.disabled=false;
inputEle.form.submit();
} else {
domManager.moveToNext(false);
}
}
}
break;
@ -302,8 +309,7 @@ namespace com.keyman.text {
}
}
processKeystroke(keyEvent: KeyEvent, outputTarget: OutputTarget, fromOSK: boolean): boolean {
// Create `Processor.processKeystroke` for this section.
processKeystroke(keyEvent: KeyEvent, outputTarget: OutputTarget, fromOSK: boolean, disableDOM: boolean): boolean {
let keyman = com.keyman.singleton;
var activeKeyboard = keyman.keyboardManager.activeKeyboard;
@ -333,7 +339,7 @@ namespace com.keyman.text {
// Handle unmapped keys, including special keys
// The following is physical layout dependent, so should be avoided if possible. All keys should be mapped.
var ch = this.defaultKeyOutput(keyEvent, keyEvent.Lmodifiers, true);
var ch = this.defaultKeyOutput(keyEvent, keyEvent.Lmodifiers, true, disableDOM);
if(ch) {
kbdInterface.output(0, outputTarget, ch);
LeventMatched = 1;
@ -342,7 +348,6 @@ namespace com.keyman.text {
}
}
/// End serious keystroke processing.
return LeventMatched == 1;
}
@ -355,7 +360,6 @@ namespace com.keyman.text {
*/
processKeyEvent(keyEvent: KeyEvent, e?: osk.KeyElement | boolean): boolean {
let keyman = com.keyman.singleton;
//var Lelem = keyman.domManager.getLastActiveElement();
let fromOSK = !!e; // If specified, it's from the OSK.
@ -364,8 +368,6 @@ namespace com.keyman.text {
e = null as osk.KeyElement; // Cast is necessary for TS type-checking later in the method.
}
var activeKeyboard = keyman.keyboardManager.activeKeyboard;
let kbdInterface = keyman.interface;
let formFactor = keyman.util.device.formFactor;
let keyMapManager = keyman.keyMapManager;
@ -442,9 +444,7 @@ namespace com.keyman.text {
let outputTarget = Processor.getOutputTarget(keyEvent.Ltarg);
let preInputMock = Mock.from(outputTarget);
// For fat-finger adjustments, we should iterate across the most likely members of the key distribution,
// not just the selected key.
let LeventMatched = this.processKeystroke(keyEvent, outputTarget, fromOSK);
let LeventMatched = this.processKeystroke(keyEvent, outputTarget, fromOSK, false);
// End of fat-finger loop section; the rest is post-processing maintenance.
@ -455,7 +455,28 @@ namespace com.keyman.text {
// Should we swallow any further processing of keystroke events for this keydown-keypress sequence?
if(LeventMatched) {
let transcription = outputTarget.buildTranscriptionFrom(preInputMock, keyEvent);
let alternates: Alternate[];
if(keyEvent.keyDistribution) {
let activeLayout = keyman['osk'].vkbd.layout as osk.ActiveLayout;
alternates = [];
for(let pair of keyEvent.keyDistribution) {
let mock = Mock.from(preInputMock);
let altKey = activeLayout.getLayer(keyEvent.kbdLayer).getKey(pair.keyId);
if(!altKey) {
console.warn("Potential fat-finger key could not be found in layer!");
continue;
}
let altEvent = this._GetClickEventProperties(altKey, keyEvent.Ltarg);
if(this.processKeystroke(altEvent, mock, fromOSK, true)) {
alternates.push({t: mock.buildTransformFrom(preInputMock), 'p': pair.p});
}
}
}
let transcription = outputTarget.buildTranscriptionFrom(preInputMock, keyEvent, alternates);
// Notify the ModelManager of new input
keyman.modelManager.predict(transcription);
@ -535,7 +556,7 @@ namespace com.keyman.text {
mappingEvent.kName = 'K_xxxx';
mappingEvent.Ltarg = null;
var mappedChar: string = this.defaultKeyOutput(Lkc, (shifted ? 0x10 : 0), false);
var mappedChar: string = this.defaultKeyOutput(Lkc, (shifted ? 0x10 : 0), false, true);
if(mappedChar) {
// FIXME; Warning - will return 96 for 'a', which is a keycode corresponding to Codes.keyCodes('K_NP1') - a numpad key.
Lkc.Lcode = mappedChar.charCodeAt(0);

View file

@ -150,7 +150,7 @@ describe("Transcriptions and Transforms", function() {
});
});
describe("Operations with deadkeys", function() {
/*describe("Operations with deadkeys", function() {
// Just one, less nuanced/subdivided; it's not a present priority for our work, but it should provide a decent basis if/when it's needed.
it("Correctly recognizes deadkey set mutations", function() {
var Mock = com.keyman.text.Mock;
@ -193,4 +193,5 @@ describe("Transcriptions and Transforms", function() {
assert.deepEqual({d: ins[0].d, p: ins[0].p}, {d: 3, p:2}, "Selected wrong deadkey as inserted");
});
});
*/
});