/*** KeymanWeb 10.0 Copyright 2017 SIL International ***/ /// namespace com.keyman.keyboards { 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, isDefault?: 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<>?~~~~~ '; static readonly DEFAULT_RAW_SPEC = {'F':'Tahoma', 'BK': Layouts.dfltText}; // 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*' }; /** * Build a default layout for keyboards with no explicit layout * * @param {Object} PVK keyboard object (as loaded) * @param {Object} kbdDevVersion object representing the version of Developer that compiled the keyboard * @param {number} kbdBitmask keyboard modifier bitmask * @param {string} formFactor * @return {Object} */ static buildDefaultLayout(PVK, keyboard: Keyboard, formFactor: string): LayoutFormFactor { // TODO: Only here because of util.deepCopy. Start a web-core utils so that it's accessible in headless. let util = com.keyman.singleton.util; // Build a layout using the default for the device var layoutType=formFactor; if(typeof Layouts.dfltLayout[layoutType] != 'object') { layoutType = 'desktop'; } let kbdBitmask = Codes.modifierBitmasks['NON_CHIRAL']; // An unfortunate dependency there. Should probably also set a version within web-core for use. let kbdDevVersion = new utils.Version(com.keyman.singleton['version']); if(keyboard) { kbdBitmask = keyboard.modifierBitmask; kbdDevVersion = keyboard.compilerVersion; } if(!PVK) { PVK = this.DEFAULT_RAW_SPEC; } // Clone the default layout object for this device var layout: LayoutFormFactor = util.deepCopy(Layouts.dfltLayout[layoutType]); var n,layers=layout['layer'], keyLabels: KLS=PVK['KLS'], key102=PVK['K102']; var i, j, k, m, row, rows: LayoutRow[], key: LayoutKey, keys: LayoutKey[]; var chiral: boolean = (kbdBitmask & Codes.modifierBitmasks.IS_CHIRAL) != 0; var kmw10Plus = !(typeof keyLabels == 'undefined' || !keyLabels); if(!kmw10Plus) { // Save the processed key label information to the keyboard's general data. // Makes things more efficient elsewhere and for reloading after keyboard swaps. keyLabels = PVK['KLS'] = Layouts.processLegacyDefinitions(PVK['BK']); } // Identify key labels (e.g. *Shift*) that require the special OSK font var specialLabel=/\*\w+\*/; // *** Step 1: instantiate the layer objects. *** // Get the list of valid layers, enforcing that the 'default' layer must be the first one processed. var validIdList = Object.getOwnPropertyNames(keyLabels), invalidIdList = []; validIdList.splice(validIdList.indexOf('default'), 1); validIdList = [ 'default' ].concat(validIdList); // Automatic AltGr emulation if the 'leftctrl-leftalt' layer is otherwise undefined. if(keyboard && keyboard.emulatesAltGr) { // We insert only the layers that need to be emulated. if((validIdList.indexOf('leftctrl-leftalt') == -1) && validIdList.indexOf('rightalt') != -1) { validIdList.push('leftctrl-leftalt'); keyLabels['leftctrl-leftalt'] = keyLabels['rightalt']; } if((validIdList.indexOf('leftctrl-leftalt-shift') == -1) && validIdList.indexOf('rightalt-shift') != -1) { validIdList.push('leftctrl-leftalt-shift'); keyLabels['leftctrl-leftalt-shift'] = keyLabels['rightalt-shift']; } } // For desktop devices, we must create all layers, even if invalid. if(formFactor == 'desktop') { invalidIdList = Layouts.generateLayerIds(chiral); // Filter out all ids considered valid. (We also don't want duplicates in the following list...) for(n=0; n 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; } } /** * 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