/*** KeymanWeb 10.0 Copyright 2017 SIL International ***/ // If KMW is already initialized, the KMW script has been loaded more than once. We wish to prevent resetting the // KMW system, so we use the fact that 'initialized' is only 1 / true after all scripts are loaded for the initial // load of KMW. if(!window['keyman']['initialized']) { (function() { // Declare KeymanWeb and util objects var keymanweb=window['keyman'], kbdInterface=window['KeymanWeb']=keymanweb['interface'], KeymanWeb=kbdInterface, util=keymanweb['util'], osk=keymanweb['osk'],device=util.device,dbg=keymanweb.debug; //osk defined here, build 350 /** * Function KSF * Scope Public * Description Save keyboard focus */ KeymanWeb['KSF'] = kbdInterface['saveFocus'] = kbdInterface.saveFocus = function() { keymanweb._IgnoreNextSelChange = 1; } /** * Function KT * Scope Public * @param {string} Ptext Text to insert * @param {?number} PdeadKey Dead key number, if any (???) * @return {boolean} true if inserted * Description Insert text into active control */ KeymanWeb['KT'] = kbdInterface['insertText'] = kbdInterface.insertText = function(Ptext,PdeadKey) { kbdInterface.resetContextCache(); //_DebugEnter('InsertText'); var Lelem = keymanweb._LastActiveElement, Ls, Le, Lkc, Lsel, Lv=false; if(Lelem != null) { Ls=Lelem._KeymanWebSelectionStart; Le=Lelem._KeymanWebSelectionEnd; Lsel=keymanweb._Selection; keymanweb._IsActivatingKeymanWebUI = 1; keymanweb._IgnoreNextSelChange = 100; keymanweb._FocusLastActiveElement(); if(keymanweb._IsMozillaEditableIframe(Lelem,0)) { Lelem = Lelem.documentElement; // I3363 (Build 301) } if(document.selection && Lsel != null) { Lsel.select(); } Lelem._KeymanWebSelectionStart=Ls; Lelem._KeymanWebSelectionEnd=Le; keymanweb._IgnoreNextSelChange = 0; if(Ptext!=null) { kbdInterface.output(0, Lelem, Ptext); } if((typeof(PdeadKey)!=='undefined') && (PdeadKey !== null)) { kbdInterface.deadkeyOutput(0, Lelem, PdeadKey); } Lelem._KeymanWebSelectionStart=null; Lelem._KeymanWebSelectionEnd=null; Lv=true; } //_DebugExit('InsertText'); return Lv; } /** * Function registerKeyboard KR * Scope Public * @param {Object} Pk Keyboard object * Description Register and load the keyboard */ KeymanWeb['KR'] = kbdInterface['registerKeyboard'] = kbdInterface.registerKeyboard = function(Pk) { // If initialization not yet complete, list the keyboard to be registered on completion of initialization if(!keymanweb['initialized']) { keymanweb.deferredKR.push(Pk); return; } var Li,Lstub; // Check if the active stub refers to this keyboard, else find applicable stub var Ps=keymanweb._ActiveStub, savedActiveStub = keymanweb._ActiveStub; if(!Ps || !('KI' in Ps) || (Ps['KI'] != Pk['KI'])) { // Find the first stub for this keyboard for(Lstub=0;Lstub < keymanweb._KeyboardStubs.length; Lstub++) { // I1511 - array prototype extended Ps=keymanweb._KeyboardStubs[Lstub]; if(Pk['KI'] == Ps['KI'])break; Ps=null; } } // Build 369: ensure active stub defined when loading local keyboards if(keymanweb._ActiveStub == null && Ps != null) { keymanweb._ActiveStub = Ps; } // Register the stub for this language (unless it is already registered) // keymanweb.KRS(Ps?Ps:Pk); // Test if keyboard already loaded for(Li=0; Li 255) { keyCode = e.vkCode; // added to support extended (touch-hold) keys for mnemonic layouts } if(e.LisVirtualKey || keyCode > 255) { if((Lruleshift & 0x4000) == 0x4000 || (keyCode > 255)) { // added keyCode test to support extended keys retVal = ((Lrulekey == keyCode) && ((Lruleshift & bitmask) == e.Lmodifiers)); //I3318, I3555 } } else if((Lruleshift & 0x4000) == 0) { retVal = (keyCode == Lrulekey); // I3318, I3555 } if(!retVal) { kbdInterface._DeadkeyResetMatched(); // I3318 } return retVal != 0; // I3318 }; /** * Function stateMatch KSM * Scope Public * @param {Object} e keystroke event * @param {number} Lstate * Description Test keystroke against state key rules */ KeymanWeb['KSM'] = kbdInterface['stateMatch'] = kbdInterface.stateMatch = function(e, Lstate) { return ((Lstate & e.Lstates) == Lstate); } /** * Function keyInformation KKI * Scope Public * @param {Object} e * @return {Object} Object with event's virtual key flag, key code, and modifiers * Description Get object with extended key event information */ KeymanWeb['KKI'] = kbdInterface['keyInformation'] = kbdInterface.keyInformation = function(e) { var ei = {}; ei['vk'] = e.LisVirtualKey; ei['code'] = e.Lcode; ei['modifiers'] = e.Lmodifiers; return ei; }; /** * Function deadkeyMatch KDM * Scope Public * @param {number} n current cursor position * @param {Object} Ptarg target element * @param {number} d deadkey * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position */ KeymanWeb['KDM'] = kbdInterface['deadkeyMatch'] = kbdInterface.deadkeyMatch = function(n, Ptarg, d) { if(kbdInterface._DeadKeys.length == 0) { return false; // I3318 } var sp=keymanweb._SelPos(Ptarg); n = sp - n; for(var i = 0; i < kbdInterface._DeadKeys.length; i++) { if(kbdInterface._DeadKeys[i].p == n && kbdInterface._DeadKeys[i].d == d) { kbdInterface._DeadKeys[i].matched = 1; return true; // I3318 } } kbdInterface._DeadkeyResetMatched(); // I3318 return false; } /** * Function beepReset KBR * Scope Public * Description Reset/terminate beep or flash (not currently used: Aug 2011) */ KeymanWeb['KBR'] = kbdInterface['beepReset'] = kbdInterface.beepReset = function() { kbdInterface.resetContextCache(); var Lbo; kbdInterface._BeepTimeout = 0; for(Lbo=0;Lbo= 0; } /** * Function output KO * Scope Public * @param {number} dn number of characters to overwrite * @param {Object} Pelem element to output to * @param {string} s string to output * Description Keyboard output */ KeymanWeb['KO'] = kbdInterface['output'] = kbdInterface.output = function(dn, Pelem, s) { kbdInterface.resetContextCache(); // KeymanTouch for Android uses direct insertion of the character string if('oninserttext' in keymanweb) { keymanweb['oninserttext'](dn,s); } if(Pelem.body) { var Ldoc=Pelem; } else { var Ldoc=Pelem.ownerDocument; // I1481 - integration with rich editors not working 100% } var Li, Ldv; if(Pelem.className.indexOf('keymanweb-input') >= 0) { var t=keymanweb.getTextBeforeCaret(Pelem); if(dn > 0) { t=t._kmwSubstr(0,t._kmwLength()-dn)+s; } else { t=t+s; } keymanweb.setTextBeforeCaret(Pelem,t); return; } if (keymanweb.legacy) { if(dn>0) { Pelem.value=Pelem.value._kmwSubstr(0,Pelem.value._kmwLength()-dn)+s; //I3319 } else { Pelem.value=Pelem.value+s; } } else if (Ldoc && (Ldv=Ldoc.defaultView) && Ldv.getSelection && (Ldoc.designMode.toLowerCase() == 'on' || Pelem.contentEditable == 'true' || Pelem.contentEditable == 'plaintext-only' || Pelem.contentEditable === '') ) { // I2457 - support contentEditable elements in mozilla, webkit /* Editable iframe and contentEditable elements for mozilla */ var _IsEditableIframe = Ldoc.designMode.toLowerCase() == 'on'; if(_IsEditableIframe) { var _CacheableCommands = kbdInterface._CacheCommands(Ldoc); } var Lsel = Ldv.getSelection(); var LselectionStart = Lsel.focusNode.nodeValue ? Lsel.focusNode.substringData(0,Lsel.focusOffset)._kmwLength() : 0; // I3319 if(!Lsel.isCollapsed) { Lsel.deleteFromDocument(); // I2134, I2192 } //KeymanWeb._Debug('KO: focusOffset='+Lsel.focusOffset+', dn='+dn+', s='+s+' focusNode.type='+Lsel.focusNode.nodeType+', focusNode.parentNode.tagName='+(Lsel.focusNode.parentNode?Lsel.focusNode.parentNode.tagName:'NULL') ); var Lrange = Lsel.getRangeAt(0); if(dn > 0) { Lrange.setStart(Lsel.focusNode, Lsel.focusOffset - Lsel.focusNode.nodeValue.substr(0,Lsel.focusOffset)._kmwSubstr(-dn).length); // I3319 Lrange.deleteContents(); } //KeymanWeb._Debug('KO: focusOffset='+Lsel.focusOffset+', dn='+dn+', s='+s+' focusNode.type='+Lsel.focusNode.nodeType+', focusNode.parentNode.tagName='+(Lsel.focusNode.parentNode?Lsel.focusNode.parentNode.tagName:'NULL') ); if(s._kmwLength() > 0) { // I2132 - exception if s.length > 0, I3319 if(Lsel.focusNode.nodeType == 3) { // I2134, I2192 // Already in a text node //KeymanWeb._Debug('KO: Already in a text node, adding "'+s+'": '+Lsel.focusOffset + '-> '+Lsel.toString()); var LfocusOffset = Lsel.focusOffset; //KeymanWeb._Debug('KO: node.text="'+Lsel.focusNode.data+'", node.length='+Lsel.focusNode.length); Lsel.focusNode.insertData(Lsel.focusOffset, s); try { Lsel.extend(Lsel.focusNode, LfocusOffset + s.length); } catch(e) { // Chrome (through 4.0 at least) throws an exception because it has not synchronised its content with the selection. scrollIntoView synchronises the content for selection Lsel.focusNode.parentNode.scrollIntoView(); Lsel.extend(Lsel.focusNode, LfocusOffset + s.length); } } else { // Create a new text node - empty control //KeymanWeb._Debug('KO: Creating a new text node for "'+s+'"'); var n = Ldoc.createTextNode(s); Lrange.insertNode(n); Lsel.extend(n,s.length); } } if(_IsEditableIframe) { kbdInterface._CacheCommandsReset(Ldoc, _CacheableCommands, null);// I2457 - support contentEditable elements in mozilla, webkit } Lsel.collapseToEnd(); // Adjust deadkey positions if(dn >= 0) { kbdInterface._DeadkeyDeleteMatched(); // I3318 kbdInterface._DeadkeyAdjustPos(LselectionStart, -dn + s._kmwLength()); // I3318 } // Internet Explorer (including IE9) } else if(Ldoc && (Ldv=Ldoc.selection)) { // build 77 - use elem.ownerDocument.selection if(Ldoc.body.isContentEditable || Ldoc.designMode.toLowerCase()=='on') { // I1295 - isContentEditable var _CacheableCommands = kbdInterface._CacheCommands(Ldoc); } var Lrange = Ldv.createRange(), Ls1; if(Lrange.text != '') { Ldv.clear(); dn = 0; } else { Lrange.collapse(true); } if(dn > 0) { Lrange.moveStart('character',-2*dn); // I3319 (next four lines var s0=Lrange.text,s1=s0._kmwSubstr(-dn); Lrange.collapse(false); //move start back to end Lrange.moveStart('character',-s1.length); } else { dn = 0; } Lrange.text = s; if(Ldoc.body.isContentEditable || Ldoc.designMode.toLowerCase()=='on') { // I1295 - isContentEditable Lrange.moveStart('character',-s.length); kbdInterface._CacheCommandsReset(Ldoc, _CacheableCommands,Lrange.select); Lrange.moveStart('character',s.length); Lrange.select(); } // Adjust deadkey positions if(dn >= 0) { // Pelem.selectionStart seems to exist here in IE 9 and is valid. This provides a possible approach, but may be wrong. // It appears safe to model the deadkey adjustment based on the non-IE9 code path's calculations. if(Pelem._KeymanWebSelectionStart != null) {// changed to allow a value of 0 LselectionStart = Pelem._KeymanWebSelectionStart; } else { LselectionStart = Pelem.value._kmwCodeUnitToCodePoint(Pelem.selectionStart); // I3319 } kbdInterface._DeadkeyDeleteMatched(); // I3318 kbdInterface._DeadkeyAdjustPos(LselectionStart, -dn + s._kmwLength()); // I3318 } keymanweb._Selection = Ldv.createRange(); keymanweb._Selection.select(); keymanweb._Selection.scrollIntoView(); // Mozilla et al; IE9+ also recognizes setSelectionRange, but does not seem to work in exactly the same way as Mozilla } else if (Pelem.setSelectionRange) { var LselectionStart, LselectionEnd; if(Pelem._KeymanWebSelectionStart != null) {// changed to allow a value of 0 LselectionStart = Pelem._KeymanWebSelectionStart; LselectionEnd = Pelem._KeymanWebSelectionEnd; } else { LselectionStart = Pelem.value._kmwCodeUnitToCodePoint(Pelem.selectionStart); // I3319 LselectionEnd = Pelem.value._kmwCodeUnitToCodePoint(Pelem.selectionEnd); // I3319 } var LscrollTop, LscrollLeft; if(Pelem.type.toLowerCase() == 'textarea' && typeof(Pelem.scrollTop) != 'undefined') { LscrollTop = Pelem.scrollTop; LscrollLeft = Pelem.scrollLeft; } if(dn < 0) {// Don't delete, leave context alone (dn = -1) Pelem.value = Pelem.value._kmwSubstring(0,LselectionStart) + s + Pelem.value._kmwSubstring(LselectionEnd); //I3319 dn = 0; } else if(LselectionStart < dn) { Pelem.value = s + Pelem.value._kmwSubstring(LselectionEnd); //I3319 } else { Pelem.value = Pelem.value._kmwSubstring(0,LselectionStart-dn) + s + Pelem.value._kmwSubstring(LselectionEnd); //I3319 } // Adjust deadkey positions if(dn >= 0) { kbdInterface._DeadkeyDeleteMatched(); // I3318 kbdInterface._DeadkeyAdjustPos(LselectionStart, -dn + s._kmwLength()); // I3318,I3319 } if (typeof(LscrollTop) != 'undefined') { Pelem.scrollTop = LscrollTop; Pelem.scrollLeft = LscrollLeft; } var caretPos=LselectionStart-dn+s._kmwLength(); // I3319 var caretPosUnits=Pelem.value._kmwCodePointToCodeUnit(caretPos); // I3319 Pelem.setSelectionRange(caretPosUnits,caretPosUnits); // I3319 Pelem._KeymanWebSelectionStart = null; Pelem._KeymanWebSelectionEnd = null; } // Refresh element content after change (if needed) if(typeof(keymanweb.refreshElementContent) == 'function') { keymanweb.refreshElementContent(Pelem); } } /** * Function deadkeyOutput KDO * Scope Public * @param {number} Pdn no of character to overwrite (delete) * @param {Object} Pelem element to output to * @param {number} Pd deadkey id * Description Record a deadkey at current cursor position, deleting Pdn characters first */ KeymanWeb['KDO'] = kbdInterface['deadkeyOutput'] = kbdInterface.deadkeyOutput = function(Pdn,Pelem,Pd) { kbdInterface.resetContextCache(); var Lc = new Object(); if(Pdn >= 0) { kbdInterface.output(Pdn,Pelem,""); //I3318 corrected to >= } Lc.p=keymanweb._SelPos(Pelem); Lc.d=Pd; kbdInterface._DeadKeys=keymanweb._push(kbdInterface._DeadKeys,Lc); // _DebugDeadKeys(Pelem, 'KDeadKeyOutput: dn='+Pdn+'; deadKey='+Pd); } /** * Function indexOutput KIO * Scope Public * @param {number} Pdn no of character to overwrite (delete) * @param {string} Ps string * @param {number} Pn index * @param {Object} Pelem element to output to * Description Output a character selected from the string according to the offset in the index array */ KeymanWeb['KIO'] = kbdInterface['indexOutput'] = kbdInterface.indexOutput = function(Pdn,Ps,Pn,Pelem) { kbdInterface.resetContextCache(); if(kbdInterface._AnyIndices[Pn-1] < Ps._kmwLength()) { //I3319 kbdInterface.output(Pdn,Pelem,Ps._kmwCharAt(kbdInterface._AnyIndices[Pn-1])); //I3319 } } /** * Function _CacheCommands * Scope Private * @param {Object} _Document * @return {Array.} List of style commands that are cacheable * Description Build reate list of styles that can be applied in iframes */ kbdInterface._CacheCommands = function(_Document) { // I1204 - style application in IFRAMEs, I2192, I2134, I2192 //var _CacheableBackColor=(_Document.selection?'hilitecolor':'backcolor'); var _CacheableCommands=[ ['backcolor',1],['fontname',1],['fontsize',1],['forecolor',1],['bold',0], ['italic',0],['strikethrough',0],['subscript',0],['superscript',0],['underline',0] ]; if(_Document.defaultView) { keymanweb._push(_CacheableCommands,['hilitecolor',1]); } for(var n=0;n < _CacheableCommands.length; n++) { // I1511 - array prototype extended //KeymanWeb._Debug('Command:'+_CacheableCommands[n][0]); keymanweb._push(_CacheableCommands[n],_CacheableCommands[n][1] ? _Document.queryCommandValue(_CacheableCommands[n][0]) : _Document.queryCommandState(_CacheableCommands[n][0])); } return _CacheableCommands; } /** * Function _CacheCommandReset * Scope Private * @param _Document * _CacheableCommands * _func * @return Nothing * Description Restore styles in IFRAMEs (??) */ kbdInterface._CacheCommandsReset = function(_Document, _CacheableCommands, _func) { for(var n=0;n < _CacheableCommands.length; n++) { // I1511 - array prototype extended //KeymanWeb._Debug('ResetCacheCommand:'+_CacheableCommands[n][0]+'='+_CacheableCommands[n][2]); if(_CacheableCommands[n][1]) { if(_Document.queryCommandValue(_CacheableCommands[n][0]) != _CacheableCommands[n][2]) { if(_func) { _func(); } _Document.execCommand(_CacheableCommands[n][0], false, _CacheableCommands[n][2]); } } else if(_Document.queryCommandState(_CacheableCommands[n][0]) != _CacheableCommands[n][2]) { if(_func) { _func(); } //KeymanWeb._Debug('executing command '+_CacheableCommand[n][0]); _Document.execCommand(_CacheableCommands[n][0], false, null); } } } /** * KIFS compares the content of a system store with a string value * * @param {number} systemId ID of the system store to test (only TSS_LAYER currently supported) * @param {string} strValue String value to compare to * @param {Object} Pelem Currently active element (may be needed by future tests) * @return {boolean} True if the test succeeds */ KeymanWeb['KIFS'] = kbdInterface['ifStore'] = kbdInterface.ifStore = function(systemId,strValue,Pelem) { var result=true; if(systemId == kbdInterface.TSS_LAYER) { result = (osk.layerId === strValue); } else if(systemId == kbdInterface.TSS_PLATFORM) { var i,constraint,constraints=strValue.split(' '); for(i=0; i Lstart) { _Dk[Li].p += Ldelta; } } } kbdInterface.clearDeadkeys = function() { kbdInterface._DeadKeys = []; } // I3318 - deadkey changes END /** * Function processKeystroke * Scope Private * @param {Object} device The device object properties to be utilized for this keystroke. * @param {Object} element The page element receiving input * @param {Object} keystroke The input keystroke (with its properties) to be mapped by the keyboard. * Description Encapsulates calls to keyboard input processing. * @returns {number} 0 if no match is made, otherwise 1. */ kbdInterface.processKeystroke = function(device, element, keystroke) { // Clear internal state tracking data from prior keystrokes. keymanweb._CachedSelectionStart = null; // I3319 kbdInterface._DeadkeyResetMatched(); // I3318 kbdInterface.resetContextCache(); // Ensure the settings are in place so that KIFS/ifState activates and deactivates // the appropriate rule(s) for the modeled device. util.activeDevice = device; // Calls the start-group of the active keyboard. return keymanweb._ActiveKeyboard['gs'](element, keystroke); } })(); }