diff --git a/web/source/kmwosk.ts b/web/source/kmwosk.ts index 8e5ca80b34..98dfd91935 100644 --- a/web/source/kmwosk.ts +++ b/web/source/kmwosk.ts @@ -446,6 +446,8 @@ if(!window['keyman']['initialized']) { (function() { // Declare KeymanWeb and member objects var keymanweb=window['keyman'], osk=keymanweb['osk'], util=keymanweb['util'], device=util.device, dbg=keymanweb.debug; + var Layouts = com.keyman.osk.Layouts; + var kbdInterface=keymanweb['interface']; // Define Keyman Developer modifier bit-flags (exposed for use by other modules) @@ -514,18 +516,6 @@ if(!window['keyman']['initialized']) { "K_TABBACK":50011,"K_TABFWD":50012 }; - // Cross-reference with the ids in osk.setButtonClass. - osk.buttonClasses = { - 'DEFAULT':'0', - 'SHIFT':'1', - 'SHIFT-ON':'2', - 'SPECIAL':'3', - 'SPECIAL-ON':'4', - 'DEADKEY':'8', - 'BLANK':'9', - 'HIDDEN':'10' - }; - // Defines the PUA code mapping for the various 'special' modifier/control keys on keyboards. osk.specialCharacters = { '*Shift*': 8, @@ -561,42 +551,9 @@ if(!window['keyman']['initialized']) { '*LCtrlShift*': 0x69, '*RCtrlShift*': 0x70 }; - - osk.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*' - }; var codesUS=[['0123456789',';=,-./`','[\\]\''],[')!@#$%^&*(',':+<_>?~','{|}"']]; - var 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"]; - - var dfltText='`1234567890-=\xA7~~qwertyuiop[]\\~~~asdfghjkl;\'~~~~~?zxcvbnm,./~~~~~ ' - +'~!@#$%^&*()_+\xA7~~QWERTYUIOP{}\\~~~ASDFGHJKL:"~~~~~?ZXCVBNM<>?~~~~~ '; - osk._Box = null; // Main DIV for OSK osk._DivVKbd = null; osk._DivVKbdHelp = null; @@ -1196,7 +1153,7 @@ if(!window['keyman']['initialized']) { var isChiral = keymanweb.keyboardManager.isChiral(); // Layer must be identified by name, not number (27/08/2015) - if(typeof nextLayer == 'number') nextLayer = osk.getLayerId(nextLayer * 0x10); + if(typeof nextLayer == 'number') nextLayer = Layouts.getLayerId(nextLayer * 0x10); // Identify next layer, if required by key if(!nextLayer) switch(keyName) @@ -1576,44 +1533,6 @@ if(!window['keyman']['initialized']) { return true; } - /** - * 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. - */ - osk.getLayerId = function(m) - { - var s=''; - if(m == 0) { - return 'default'; - } else { - if(m & osk.modifierCodes['LCTRL']) { - s = (s.length > 0 ? s + '-' : '') + 'leftctrl'; - } - if(m & osk.modifierCodes['RCTRL']) { - s = (s.length > 0 ? s + '-' : '') + 'rightctrl'; - } - if(m & osk.modifierCodes['LALT']) { - s = (s.length > 0 ? s + '-' : '') + 'leftalt'; - } - if(m & osk.modifierCodes['RALT']) { - s = (s.length > 0 ? s + '-' : '') + 'rightalt'; - } - if(m & osk.modifierCodes['SHIFT']) { - s = (s.length > 0 ? s + '-' : '') + 'shift'; - } - if(m & osk.modifierCodes['CTRL']) { - s = (s.length > 0 ? s + '-' : '') + 'ctrl'; - } - if(m & osk.modifierCodes['ALT']) { - s = (s.length > 0 ? s + '-' : '') + 'alt'; - } - return s; - } - } - /** * Get modifier key state from layer id * @@ -1730,9 +1649,9 @@ if(!window['keyman']['initialized']) { // Combine our base modifiable layer and attach the new modifier variation info to obtain our destination layer. if(s != 'default') { if(s == '') { - s = osk.getLayerId(modifier); + s = Layouts.getLayerId(modifier); } else { - s = osk.getLayerId(modifier) + '-' + s; + s = Layouts.getLayerId(modifier) + '-' + s; } } } @@ -2285,7 +2204,7 @@ if(!window['keyman']['initialized']) { } // Find and display the selected OSK layer - osk.layerId=osk.getLayerId(keyShiftState); + osk.layerId=Layouts.getLayerId(keyShiftState); // osk._UpdateVKShiftStyle will be called automatically upon the next _Show. if(osk._Visible) { @@ -2329,7 +2248,7 @@ if(!window['keyman']['initialized']) { continue; } - keys[i]['sp'] = osk._stateKeys[states[i]] ? osk.buttonClasses['SHIFT-ON'] : osk.buttonClasses['SHIFT']; + keys[i]['sp'] = osk._stateKeys[states[i]] ? Layouts.buttonClasses['SHIFT-ON'] : Layouts.buttonClasses['SHIFT']; var btn = document.getElementById(layerId+'-'+states[i]); osk.setButtonClass(keys[i], btn, osk.layout); @@ -3166,434 +3085,13 @@ if(!window['keyman']['initialized']) { } } - /** - * Converts the legacy BK property from pre 10.0 into the KLS keyboard layer spec format, - * sparsifying it as possible to pre-emptively check invalid layers. - * - * @param {Array} BK keyboard object (as loaded) - * @return {Object} - */ - osk.processLegacyDefinitions = function(BK) { - //['default','shift','ctrl','shiftctrl','alt','shiftalt','ctrlalt','shiftctrlalt']; - var idList=osk.generateLayerIds(false); // Non-chiral. - - var KLS = {}; - - // The old default: eight auto-managed layers... - for(var 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. - osk.formatDefaultLayer(layers[n], chiral != 0, 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]; - } - - // Fall back to US English keycap text as default for the base two layers if not otherwise defined. - // (Any 'ghost' keys must be explicitly defined in layout for these layers.) - if(isDefault) { - if((key['text'] == '' || typeof key['text'] == 'undefined') && key['id'] != 'K_SPACE' && kx+65 * isShift < dfltText.length) { - key['text']=dfltText[kx+65*isShift]; - } - } - } - - // Leave any unmarked key caps as null strings - if(typeof(key['text']) == 'undefined') { - 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']=osk.buttonClasses['SHIFT-ON']; - shiftKey['sk']=null; - shiftKey['text'] = osk.modifierSpecials[layers[n].id] ? osk.modifierSpecials[layers[n].id] : "*Shift*"; - } - } - } - - return layout; + return Layouts.emulatesAltGr(keyLabels); } /** @@ -3613,7 +3111,7 @@ if(!window['keyman']['initialized']) { // Build a layout using the default for the device if(typeof layout != 'object' || layout == null) - layout=osk.buildDefaultLayout(PVK,kbdBitmask,device.formFactor); + layout=Layouts.buildDefaultLayout(PVK,kbdBitmask,device.formFactor); // Create the collection of HTML elements from the device-dependent layout object osk.layout=layout; @@ -3699,7 +3197,7 @@ if(!window['keyman']['initialized']) { // Else get a default layout for the device for this keyboard if(layout == null && PVK != null) - layout=osk.buildDefaultLayout(PVK,keymanweb.keyboardManager.getKeyboardModifierBitmask(PKbd),formFactor); + layout=Layouts.buildDefaultLayout(PVK,keymanweb.keyboardManager.getKeyboardModifierBitmask(PKbd),formFactor); // Cannot create an OSK if no layout defined, just return empty DIV if(layout != null) @@ -4716,7 +4214,7 @@ if(!window['keyman']['initialized']) { if(Lviskbd != null || Lhelp == '' || device.touchable) // I3363 (Build 301) { // TODO: May want to define a default BK array here as well - if(Lviskbd == null) Lviskbd={'F':'Tahoma','BK':dfltText}; //DDOSK + if(Lviskbd == null) Lviskbd={'F':'Tahoma','BK': Layouts.dfltText}; //DDOSK osk._GenerateVisualKeyboard(Lviskbd, Lhelp, layout, keymanweb.keyboardManager.getKeyboardModifierBitmask()); } diff --git a/web/source/kmwutils.ts b/web/source/kmwutils.ts index f3389f0de9..ef2ca1967f 100644 --- a/web/source/kmwutils.ts +++ b/web/source/kmwutils.ts @@ -935,7 +935,7 @@ namespace com.keyman { * Description Makes an actual copy (not a reference) of an object, copying simple members, * arrays and member objects but not functions, so use with care! */ - deepCopy(p:T, c0): T { + deepCopy(p:T, c0?): T { var c = c0 || {}; for (var i in p) { if(typeof p[i] === 'object') { diff --git a/web/source/osk/defaultLayouts.ts b/web/source/osk/defaultLayouts.ts index a370eb8345..5c111fb6a5 100644 --- a/web/source/osk/defaultLayouts.ts +++ b/web/source/osk/defaultLayouts.ts @@ -4,339 +4,866 @@ ***/ namespace com.keyman.osk { - // The following types provide type definitions for the full JSON format we use for visual keyboard definitions. - export type LayoutKey = { - "id"?: string, - "text"?: string, - "sp"?: number|"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10", - "width"?: string, - "nextlayer"?: string, - "pad"?: string, - "sk"?: LayoutKey[] - } - - export type LayoutRow = { - "id": string, // represents a number, though... - "key": LayoutKey[] + 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, + "nextlayer"?: string, + "pad"?: string, + "sk"?: LayoutKey[] + } + + export type LayoutRow = { + "id": string, // represents a number, though... + "key": LayoutKey[] + }; + + export type LayoutLayer = { + "id": string, + "row": LayoutRow[] + } + + export type LayoutFormFactor = { + "font": string, + "layer": LayoutLayer[] + } + + 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' }; - export type LayoutLayer = { - "id": string, - "row": LayoutRow[] - } - - export type LayoutFormFactor = { - "font": string, - "layer": LayoutLayer[] - } - - export type LayoutSpec = { - "desktop"?: LayoutFormFactor, - "phone"?: LayoutFormFactor, - "tablet"?: LayoutFormFactor - } + 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*' + }; - // This class manages default layout construction for consumption by OSKs without a specified layout. - export class Layouts { - // Defines the default visual layout for a keyboard. - static dfltLayout: LayoutSpec = { - "desktop": - { - "font": "Tahoma,Helvetica", - "layer": [ - { - "id": "default", - "row": [ - { - "id": "1", - "key": [ - { "id": "K_BKQUOTE" }, - { "id": "K_1" }, - { "id": "K_2" }, - { "id": "K_3" }, - { "id": "K_4" }, - { "id": "K_5" }, - { "id": "K_6" }, - { "id": "K_7" }, - { "id": "K_8" }, - { "id": "K_9" }, - { "id": "K_0" }, - { "id": "K_HYPHEN" }, - { "id": "K_EQUAL" }, - { "id": "K_BKSP", "text": "*BkSp*", "sp": "1", "width": "130" } - ] - }, - { - "id": "2", - "key": [ - { "id": "K_TAB", "text": "*Tab*", "sp": "1", "width": "130" }, - { "id": "K_Q" }, - { "id": "K_W" }, - { "id": "K_E" }, - { "id": "K_R" }, - { "id": "K_T" }, - { "id": "K_Y" }, - { "id": "K_U" }, - { "id": "K_I" }, - { "id": "K_O" }, - { "id": "K_P" }, - { "id": "K_LBRKT" }, - { "id": "K_RBRKT" }, - { "id": "K_BKSLASH" } - ] - }, - { - "id": "3", - "key": [ - { "id": "K_CAPS", "text": "*Caps*", "sp": "1", "width": "165" }, - { "id": "K_A" }, - { "id": "K_S" }, - { "id": "K_D" }, - { "id": "K_F" }, - { "id": "K_G" }, - { "id": "K_H" }, - { "id": "K_J" }, - { "id": "K_K" }, - { "id": "K_L" }, - { "id": "K_COLON" }, - { "id": "K_QUOTE" }, - { "id": "K_ENTER", "text": "*Enter*", "sp": "1", "width": "165" } - ] - }, - { - "id": "4", - "key": [ - { "id": "K_SHIFT", "text": "*Shift*", "sp": "1", "width": "130" }, - { "id": "K_oE2" }, - { "id": "K_Z" }, - { "id": "K_X" }, - { "id": "K_C" }, - { "id": "K_V" }, - { "id": "K_B" }, - { "id": "K_N" }, - { "id": "K_M" }, - { "id": "K_COMMA" }, - { "id": "K_PERIOD" }, - { "id": "K_SLASH" }, - { "id": "K_RSHIFT", "text": "*Shift*", "sp": "1", "width": "130" } - ] - }, - { - "id": "5", - "key": [ - { "id": "K_LCONTROL", "text": "*Ctrl*", "sp": "1", "width": "170" }, - { "id": "K_LALT", "text": "*Alt*", "sp": "1", "width": "160" }, - { "id": "K_SPACE", "text": "", "width": "770" }, - { "id": "K_RALT", "text": "*Alt*", "sp": "1", "width": "160" }, - { "id": "K_RCONTROL", "text": "*Ctrl*", "sp": "1", "width": "170" } - ] - } - ] - } - ] - }, - "tablet": - { - "font": "Tahoma,Helvetica", - "layer": [ - { - "id": "default", - "row": [ - { - "id": "0", - "key": [ - { "id": "K_1" }, - { "id": "K_2" }, - { "id": "K_3" }, - { "id": "K_4" }, - { "id": "K_5" }, - { "id": "K_6" }, - { "id": "K_7" }, - { "id": "K_8" }, - { "id": "K_9" }, - { "id": "K_0" }, - { "id": "K_HYPHEN" }, - { "id": "K_EQUAL" }, - { "sp": "10", "width": "1" } - ] - }, - { - "id": "1", - "key": [ - { "id": "K_Q", "pad": "25" }, - { "id": "K_W" }, - { "id": "K_E" }, - { "id": "K_R" }, - { "id": "K_T" }, - { "id": "K_Y" }, - { "id": "K_U" }, - { "id": "K_I" }, - { "id": "K_O" }, - { "id": "K_P" }, - { "id": "K_LBRKT" }, - { "id": "K_RBRKT" }, - { "sp": "10", "width": "1" } - ] - }, - { - "id": "2", - "key": [ - { "id": "K_A", "pad": "50" }, - { "id": "K_S" }, - { "id": "K_D" }, - { "id": "K_F" }, - { "id": "K_G" }, - { "id": "K_H" }, - { "id": "K_J" }, - { "id": "K_K" }, - { "id": "K_L" }, - { "id": "K_COLON" }, - { "id": "K_QUOTE" }, - { "id": "K_BKSLASH", "width": "90" } - ] - }, - { - "id": "3", - "key": [ - { "id": "K_oE2", "width": "90" }, - { "id": "K_Z" }, - { "id": "K_X" }, - { "id": "K_C" }, - { "id": "K_V" }, - { "id": "K_B" }, - { "id": "K_N" }, - { "id": "K_M" }, - { "id": "K_COMMA" }, - { "id": "K_PERIOD" }, - { "id": "K_SLASH" }, - { "id": "K_BKQUOTE" }, - { "sp": "10", "width": "1" } - ] - }, - { - "id": "4", - "key": [ - { - "id": "K_SHIFT", "text": "*Shift*", "sp": "1", "width": "200", "sk": [ - { "id": "K_LCONTROL", "text": "*Ctrl*", "sp": "1", "width": "50", "nextlayer": "ctrl" }, - { "id": "K_LCONTROL", "text": "*LCtrl*", "sp": "1", "width": "50", "nextlayer": "leftctrl" }, - { "id": "K_RCONTROL", "text": "*RCtrl*", "sp": "1", "width": "50", "nextlayer": "rightctrl" }, - { "id": "K_LALT", "text": "*Alt*", "sp": "1", "width": "50", "nextlayer": "alt" }, - { "id": "K_LALT", "text": "*LAlt*", "sp": "1", "width": "50", "nextlayer": "leftalt" }, - { "id": "K_RALT", "text": "*RAlt*", "sp": "1", "width": "50", "nextlayer": "rightalt" }, - { "id": "K_ALTGR", "text": "*AltGr*", "sp": "1", "width": "50", "nextlayer": "ctrl-alt" }] - }, - { "id": "K_LOPT", "text": "*Menu*", "sp": "1", "width": "150" }, - { "id": "K_SPACE", "text": "", "width": "570" }, - { "id": "K_BKSP", "text": "*BkSp*", "sp": "1", "width": "150" }, - { "id": "K_ENTER", "text": "*Enter*", "sp": "1", "width": "200" } - ] - } - ] - } - ] - }, - "phone": - { - "font": "Tahoma,Helvetica", - "layer": [ - { - "id": "default", - "row": [ - { - "id": "0", - "key": [ - { "id": "K_1" }, - { "id": "K_2" }, - { "id": "K_3" }, - { "id": "K_4" }, - { "id": "K_5" }, - { "id": "K_6" }, - { "id": "K_7" }, - { "id": "K_8" }, - { "id": "K_9" }, - { "id": "K_0" }, - { "id": "K_HYPHEN" }, - { "id": "K_EQUAL" }, - { "sp": "10", "width": "1" } - ] - }, - { - "id": "1", - "key": [ - { "id": "K_Q", "pad": "25" }, - { "id": "K_W" }, - { "id": "K_E" }, - { "id": "K_R" }, - { "id": "K_T" }, - { "id": "K_Y" }, - { "id": "K_U" }, - { "id": "K_I" }, - { "id": "K_O" }, - { "id": "K_P" }, - { "id": "K_LBRKT" }, - { "id": "K_RBRKT" }, - { "sp": "10", "width": "1" } - ] - }, - { - "id": "2", - "key": [ - { "id": "K_A", "pad": "50" }, - { "id": "K_S" }, - { "id": "K_D" }, - { "id": "K_F" }, - { "id": "K_G" }, - { "id": "K_H" }, - { "id": "K_J" }, - { "id": "K_K" }, - { "id": "K_L" }, - { "id": "K_COLON" }, - { "id": "K_QUOTE" }, - { "id": "K_BKSLASH", "width": "90" } - ] - }, - { - "id": "3", - "key": [ - { "id": "K_oE2", "width": "90" }, - { "id": "K_Z" }, - { "id": "K_X" }, - { "id": "K_C" }, - { "id": "K_V" }, - { "id": "K_B" }, - { "id": "K_N" }, - { "id": "K_M" }, - { "id": "K_COMMA" }, - { "id": "K_PERIOD" }, - { "id": "K_SLASH" }, - { "id": "K_BKQUOTE" }, - { "sp": "10", "width": "1" } - ] - }, - { - "id": "4", - "key": [ - { - "id": "K_SHIFT", "text": "*Shift*", "sp": "1", "width": "200", "sk": [ - { "id": "K_LCONTROL", "text": "*Ctrl*", "sp": "1", "width": "50", "nextlayer": "ctrl" }, - { "id": "K_LCONTROL", "text": "*LCtrl*", "sp": "1", "width": "50", "nextlayer": "leftctrl" }, - { "id": "K_RCONTROL", "text": "*RCtrl*", "sp": "1", "width": "50", "nextlayer": "rightctrl" }, - { "id": "K_LALT", "text": "*Alt*", "sp": "1", "width": "50", "nextlayer": "alt" }, - { "id": "K_LALT", "text": "*LAlt*", "sp": "1", "width": "50", "nextlayer": "leftalt" }, - { "id": "K_RALT", "text": "*RAlt*", "sp": "1", "width": "50", "nextlayer": "rightalt" }, - { "id": "K_ALTGR", "text": "*AltGr*", "sp": "1", "width": "50", "nextlayer": "ctrl-alt" }] - }, - { "id": "K_LOPT", "text": "*Menu*", "width": "150", "sp": "1" }, - { "id": "K_SPACE", "width": "570", "text": "" }, - { "id": "K_BKSP", "text": "*BkSp*", "width": "150", "sp": "1" }, - { "id": "K_ENTER", "text": "*Enter*", "width": "200", "sp": "1" } - ] - } - ] - } - ] + /** + * Build a default layout for keyboards with no explicit layout + * + * @param {Object} PVK keyboard object (as loaded) + * @param {number} kbdBitmask keyboard modifier bitmask + * @param {string} formFactor + * @return {Object} + */ + static buildDefaultLayout(PVK, kbdBitmask: number, formFactor: string) { + let keyman = ( window['keyman']); + let osk = keyman.osk; + let util = keyman.util; + + // Build a layout using the default for the device + var layoutType=formFactor; + + if(typeof Layouts.dfltLayout[layoutType] != 'object') { + layoutType = 'desktop'; + } + + // Clone the default layout object for this device + var layout=util.deepCopy(Layouts.dfltLayout[layoutType]); + + var n,layers=layout['layer'], keyLabels: KLS=PVK['KLS'], key102=PVK['K102']; + var i, j, k, m, row, rows, key, keys; + var chiral: boolean = (kbdBitmask & osk.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(Layouts.emulatesAltGr(keyLabels)) { + // 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]; + } + + // Fall back to US English keycap text as default for the base two layers if not otherwise defined. + // (Any 'ghost' keys must be explicitly defined in layout for these layers.) + if(isDefault) { + if((key['text'] == '' || typeof key['text'] == 'undefined') && key['id'] != 'K_SPACE' && kx+65 * isShift < Layouts.dfltText.length) { + key['text']=Layouts.dfltText[kx+65*isShift]; + } + } + } + + // Leave any unmarked key caps as null strings + if(typeof(key['text']) == 'undefined') { + 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 = ( window['keyman']).osk.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 = keyman.osk.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