/***
KeymanWeb 10.0
Copyright 2017 SIL International
***/
///
namespace com.keyman.osk {
let Codes = com.keyman.text.Codes;
export type KLS = {[layerName: string]: string[]};
// The following types provide type definitions for the full JSON format we use for visual keyboard definitions.
export type ButtonClass = number|"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10"
export type LayoutKey = {
"id"?: string,
"text"?: string,
"sp"?: ButtonClass,
"width"?: string,
"layer"?: string, // Key derives any modifiers from the value set here if specified, not the actual display layer.
"nextlayer"?: string,
"pad"?: string,
"sk"?: LayoutKey[]
}
export type LayoutRow = {
"id": string, // represents a number, though...
"key": LayoutKey[]
};
export type LayoutLayer = {
"id": string,
"row": LayoutRow[],
// Post-processing elements.
shiftKey?: LayoutKey,
capsKey?: LayoutKey,
numKey?: LayoutKey,
scrollKey?: LayoutKey
aligned?: boolean
}
export type LayoutFormFactor = {
"font": string,
"layer": LayoutLayer[],
keyLabels?: boolean
}
export type LayoutSpec = {
"desktop"?: LayoutFormFactor,
"phone"?: LayoutFormFactor,
"tablet"?: LayoutFormFactor
}
// This class manages default layout construction for consumption by OSKs without a specified layout.
export class Layouts {
static dfltCodes=[
"K_BKQUOTE","K_1","K_2","K_3","K_4","K_5","K_6","K_7","K_8","K_9","K_0",
"K_HYPHEN","K_EQUAL","K_*","K_*","K_*","K_Q","K_W","K_E","K_R","K_T",
"K_Y","K_U","K_I","K_O","K_P","K_LBRKT","K_RBRKT","K_BKSLASH","K_*",
"K_*","K_*","K_A","K_S","K_D","K_F","K_G","K_H","K_J","K_K","K_L",
"K_COLON","K_QUOTE","K_*","K_*","K_*","K_*","K_*","K_oE2",
"K_Z","K_X","K_C","K_V","K_B","K_N","K_M","K_COMMA","K_PERIOD",
"K_SLASH","K_*","K_*","K_*","K_*","K_*","K_SPACE"
];
static dfltText='`1234567890-=\xA7~~qwertyuiop[]\\~~~asdfghjkl;\'~~~~~?zxcvbnm,./~~~~~ '
+'~!@#$%^&*()_+\xA7~~QWERTYUIOP{}\\~~~ASDFGHJKL:"~~~~~?ZXCVBNM<>?~~~~~ ';
// Cross-reference with the ids in osk.setButtonClass.
static buttonClasses: {[name: string]: ButtonClass} = {
'DEFAULT':'0',
'SHIFT':'1',
'SHIFT-ON':'2',
'SPECIAL':'3',
'SPECIAL-ON':'4',
'DEADKEY':'8',
'BLANK':'9',
'HIDDEN':'10'
};
static modifierSpecials = {
'leftalt': '*LAlt*',
'rightalt': '*RAlt*',
'alt': '*Alt*',
'leftctrl': '*LCtrl*',
'rightctrl': '*RCtrl*',
'ctrl': '*Ctrl*',
'ctrl-alt': '*AltGr*',
'leftctrl-leftalt': '*LAltCtrl*',
'rightctrl-rightalt': '*RAltCtrl*',
'leftctrl-leftalt-shift': '*LAltCtrlShift*',
'rightctrl-rightalt-shift': '*RAltCtrlShift*',
'shift': '*Shift*',
'shift-alt': '*AltShift*',
'shift-ctrl': '*CtrlShift*',
'shift-ctrl-alt': '*AltCtrlShift*',
'leftalt-shift': '*LAltShift*',
'rightalt-shift': '*RAltShift*',
'leftctrl-shift': '*LCtrlShift*',
'rightctrl-shift': '*RCtrlShift*'
};
static _BaseLayout: string = 'us'; // default BaseLayout
static _BaseLayoutEuro: {[code: string]: string} = {
'se': '\u00a71234567890+ยด~~~QWERTYUIOP\u00c5\u00a8\'~~~ASDFGHJKL\u00d6\u00c4~~~~~ 0) {
layers[n]=util.deepCopy(layers[0]);
}
layers[n]['id']=idList[n];
layers[n]['nextlayer']=idList[n]; // This would only be different for a dynamic keyboard
// Extraced into a helper method to improve readability.
Layouts.formatDefaultLayer(layers[n], chiral, formFactor, !!key102);
}
// *** Step 2: Layer objects now exist; time to fill them with the appropriate key labels and key styles ***
for(n=0; n= 0 && kx < layerSpec.length) key['text']=layerSpec[kx];
}
// Legacy (pre 12.0) behavior: fall back to US English keycap text as default for the base two layers
// if a key cap is not otherwise defined. (Any intentional 'ghost' keys must be explicitly defined.)
if(isDefault && kbdDevVersion.precedes(utils.Version.NO_DEFAULT_KEYCAPS)) {
if(key['id'] != 'K_SPACE' && kx+65 * isShift < Layouts.dfltText.length && key['text'] !== null) {
key['text'] = key['text'] || Layouts.dfltText[kx+65*isShift];
}
}
}
// Leave any unmarked key caps as null strings
if(key['text'] !== null) {
key['text'] = key['text'] || '';
}
// Detect important tracking keys.
switch(key['id']) {
case "K_SHIFT":
shiftKey=key;
break;
case "K_TAB":
nextKey=key;
break;
case "K_CAPS":
capsKey=key;
break;
case "K_NUMLOCK":
numKey=key;
break;
case "K_SCROLL":
scrollKey=key;
break;
}
// Remove pop-up shift keys referencing invalid layers (Build 349)
if(key['sk'] != null) {
for(k=0; k 0 && shiftKey != null) {
shiftKey['sp']=Layouts.buttonClasses['SHIFT-ON'];
shiftKey['sk']=null;
shiftKey['text'] = Layouts.modifierSpecials[layers[n].id] ? Layouts.modifierSpecials[layers[n].id] : "*Shift*";
}
}
}
return layout;
}
/**
* Function getLayerId
* Scope Private
* @param {number} m shift modifier code
* @return {string} layer string from shift modifier code (desktop keyboards)
* Description Get name of layer from code, where the modifer order is determined by ascending bit-flag value.
*/
static getLayerId(m: number): string {
let modifierCodes = Codes.modifierCodes;
var s='';
if(m == 0) {
return 'default';
} else {
if(m & modifierCodes['LCTRL']) {
s = (s.length > 0 ? s + '-' : '') + 'leftctrl';
}
if(m & modifierCodes['RCTRL']) {
s = (s.length > 0 ? s + '-' : '') + 'rightctrl';
}
if(m & modifierCodes['LALT']) {
s = (s.length > 0 ? s + '-' : '') + 'leftalt';
}
if(m & modifierCodes['RALT']) {
s = (s.length > 0 ? s + '-' : '') + 'rightalt';
}
if(m & modifierCodes['SHIFT']) {
s = (s.length > 0 ? s + '-' : '') + 'shift';
}
if(m & modifierCodes['CTRL']) {
s = (s.length > 0 ? s + '-' : '') + 'ctrl';
}
if(m & modifierCodes['ALT']) {
s = (s.length > 0 ? s + '-' : '') + 'alt';
}
return s;
}
}
/**
* Signifies whether or not the OSK facilitates AltGr / Right-alt emulation for this keyboard.
* @param {Object=} keyLabels
* @return {boolean}
*/
static emulatesAltGr(keyLabels?: KLS): boolean { // TODO: typing for keyLabels (corresponds to KLS)
var layers;
let keyman = window['keyman'];
let modifierCodes = Codes.modifierCodes;
// If we're not chiral, we're not emulating.
if(!keyman.keyboardManager.isChiral()) {
return false;
}
if(!keyLabels) {
var activeKeyboard = keyman.keyboardManager.activeKeyboard;
if(activeKeyboard == null || activeKeyboard['KV'] == null) {
return false;
}
layers = activeKeyboard['KV']['KLS'];
} else {
layers = keyLabels;
}
var emulationMask = modifierCodes['LCTRL'] | modifierCodes['LALT'];
var unshiftedEmulationLayer = layers[Layouts.getLayerId(emulationMask)];
var shiftedEmulationLayer = layers[Layouts.getLayerId(modifierCodes['SHIFT'] | emulationMask)];
// buildDefaultLayout ensures that these are aliased to the original modifier set being emulated.
// As a result, we can directly test for reference equality.
if(unshiftedEmulationLayer != null &&
unshiftedEmulationLayer != layers[Layouts.getLayerId(modifierCodes['RALT'])]) {
return false;
}
if(shiftedEmulationLayer != null &&
shiftedEmulationLayer != layers[Layouts.getLayerId(modifierCodes['RALT'] | modifierCodes['SHIFT'])]) {
return false;
}
// It's technically possible for the OSK to not specify anything while allowing chiral input. A last-ditch catch:
var bitmask = keyman.keyboardManager.getKeyboardModifierBitmask();
if((bitmask & emulationMask) != emulationMask) {
// At least one of the emulation modifiers is never used by the keyboard! We can confirm everything's safe.
return true;
}
if(unshiftedEmulationLayer == null && shiftedEmulationLayer == null) {
// We've run out of things to go on; we can't detect if chiral AltGr emulation is intended or not.
// TODO: handle this again!
// if(!osk.altGrWarning) {
// console.warn("Could not detect if AltGr emulation is safe, but defaulting to active emulation!")
// // Avoid spamming the console with warnings on every call of the method.
// osk.altGrWarning = true;
// }
return true;
}
return true;
}
/**
* Generates a list of potential layer ids for the specified chirality mode.
*
* @param {boolean} chiral // Does the keyboard use chiral modifiers or not?
*/
static generateLayerIds(chiral: boolean): string[] {
var layerCnt, offset;
if(chiral) {
layerCnt=32;
offset=0x01;
} else {
layerCnt=8;
offset=0x10;
}
var layerIds = [];
for(var i=0; i < layerCnt; i++) {
layerIds.push(Layouts.getLayerId(i * offset));
}
return layerIds;
}
/**
* Sets a formatting property for the modifier keys when constructing a default layout for a keyboard.
*
* @param {Object} layer // One layer specification
* @param {boolean} chiral // Whether or not the keyboard uses chiral modifier information.
* @param {string} formFactor // The form factor of the device the layout is being constructed for.
* @param {boolean} key102 // Whether or not the extended key 102 should be hidden.
*/
static formatDefaultLayer(layer: LayoutLayer, chiral: boolean, formFactor: string, key102: boolean) {
var layerId = layer['id'];
let buttonClasses = Layouts.buttonClasses;
// Correct appearance of state-dependent modifier keys according to group
for(var i=0; i