/*** 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['tavultesoft']['keymanweb']['initialized']) { /*****************************************/ /* */ /* On-Screen (Visual) Keyboard Code */ /* */ /*****************************************/ (function() { // Declare KeymanWeb and member objects var keymanweb=window['tavultesoft']['keymanweb'], osk=keymanweb['osk'], util=keymanweb['util'],device=util.device,dbg=keymanweb.debug; // Define Keyman Developer modifier bit-flags (exposed for use by other modules) osk.modifierCodes = { "LCTRL":0x0001, "RCTRL":0x0002, "LALT":0x0004, "RALT":0x0008, "SHIFT":0x0010, "CTRL":0x0020, "ALT":0x0040, "CAPS":0x0100, "NO_CAPS":0x0200, "NUM_LOCK":0x0400, "NO_NUM_LOCK":0x0800, "SCROLL_LOCK":0x1000, "NO_SCROLL_LOCK":0x2000, "VIRTUAL_KEY":0x4000 }; osk.modifierBitmasks = { "ALL":0x007F, "ALT_GR_SIM": (osk.modifierCodes['LCTRL'] | osk.modifierCodes['LALT']), "CHIRAL":0x001F, // The base bitmask for chiral keyboards. Includes SHIFT, which is non-chiral. "IS_CHIRAL":0x000F, // Used to test if a bitmask uses a chiral modifier. "NON_CHIRAL":0x0070 // The default bitmask, for non-chiral keyboards }; osk.stateBitmasks = { "ALL":0x3F00, "CAPS":0x0300, "NUM_LOCK":0x0C00, "SCROLL_LOCK":0x3000 }; // Define standard keycode numbers (exposed for use by other modules) osk.keyCodes={ "K_BKSP":8,"K_TAB":9,"K_ENTER":13, "K_SHIFT":16,"K_CONTROL":17,"K_ALT":18,"K_PAUSE":19,"K_CAPS":20, "K_ESC":27,"K_SPACE":32,"K_PGUP":33, "K_PGDN":34,"K_END":35,"K_HOME":36,"K_LEFT":37,"K_UP":38, "K_RIGHT":39,"K_DOWN":40,"K_SEL":41,"K_PRINT":42,"K_EXEC":43, "K_INS":45,"K_DEL":46,"K_HELP":47,"K_0":48, "K_1":49,"K_2":50,"K_3":51,"K_4":52,"K_5":53,"K_6":54,"K_7":55, "K_8":56,"K_9":57,"K_A":65,"K_B":66,"K_C":67,"K_D":68,"K_E":69, "K_F":70,"K_G":71,"K_H":72,"K_I":73,"K_J":74,"K_K":75,"K_L":76, "K_M":77,"K_N":78,"K_O":79,"K_P":80,"K_Q":81,"K_R":82,"K_S":83, "K_T":84,"K_U":85,"K_V":86,"K_W":87,"K_X":88,"K_Y":89,"K_Z":90, "K_NP0":96,"K_NP1":97,"K_NP2":98, "K_NP3":99,"K_NP4":100,"K_NP5":101,"K_NP6":102, "K_NP7":103,"K_NP8":104,"K_NP9":105,"K_NPSTAR":106, "K_NPPLUS":107,"K_SEPARATOR":108,"K_NPMINUS":109,"K_NPDOT":110, "K_NPSLASH":111,"K_F1":112,"K_F2":113,"K_F3":114,"K_F4":115, "K_F5":116,"K_F6":117,"K_F7":118,"K_F8":119,"K_F9":120, "K_F10":121,"K_F11":122,"K_F12":123,"K_NUMLOCK":144,"K_SCROLL":145, "K_LSHIFT":160,"K_RSHIFT":161,"K_LCONTROL":162,"K_RCONTROL":163, "K_LALT":164,"K_RALT":165, "K_COLON":186,"K_EQUAL":187,"K_COMMA":188,"K_HYPHEN":189, "K_PERIOD":190,"K_SLASH":191,"K_BKQUOTE":192, "K_LBRKT":219,"K_BKSLASH":220,"K_RBRKT":221, "K_QUOTE":222,"K_oE2":226, "K_LOPT":50001,"K_ROPT":50002, "K_NUMERALS":50003,"K_SYMBOLS":50004,"K_CURRENCIES":50005, "K_UPPER":50006,"K_LOWER":50007,"K_ALPHA":50008, "K_SHIFTED":50009,"K_ALTGR":50010, "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' }; 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; osk._Visible = 0; // Whether or not actually visible osk._Enabled = 1; // Whether or not enabled by UI osk._VShift = []; osk._VKeySpans = []; osk._VKeyDown = null; osk._VKbdContainer = null; osk._VOriginalWidth = 1; // Non-zero default value needed osk._BaseLayout = 'us'; // default BaseLayout osk._BaseLayoutEuro = {}; // I1299 (not currently exposed, but may need to be e.g. for external users) osk._BaseLayoutEuro['se'] = '\u00a71234567890+ยด~~~QWERTYUIOP\u00c5\u00a8\'~~~ASDFGHJKL\u00d6\u00c4~~~~~0) if dragged by user osk.dfltX = ''; // Left position set by page code osk.dfltY = ''; // Top position set by page code osk.noDrag = false; // allow page to override user OSK dragging osk.keytip = null; // Key preview (phones) osk.touchY = 0; // First y position of touched key // Placeholder functions osk.addCallout = function(e){}; /** * Function addEventListener * Scope Public * @param {string} event event name * @param {function(Object)} func event handler * @return {boolean} * Description Wrapper function to add and identify OSK-specific event handlers */ osk['addEventListener'] = function(event, func) { return util.addEventListener('osk.'+event, func); } /** * Function _TitleBarInterior * Scope Private * Description Title bar interior formatting and element event handling */ osk._TitleBarInterior = function() { var Ldiv = util._CreateElement('DIV'); var Ls = Ldiv.style; Ls.paddingLeft='2px'; Ls.cursor='move'; Ls.background='#ad4a28'; Ls.font='8pt Tahoma,Arial,sans-serif'; //I2186 // Add container for buttons, handle mousedown event var LdivButtons = util._CreateElement('DIV'); LdivButtons.className = 'kmw-title-bar-actions'; LdivButtons.onmousedown=osk._CancelMouse; // Add close button, handle click and mousedown events var Limg = util._CreateElement('DIV'); Limg.className='kmw-close-button'; Limg.onmousedown=osk._CancelMouse; Limg.onclick=function () {osk._Hide(true);} osk.closeButton = Limg; LdivButtons.appendChild(Limg); /** * Move OSK back to default position */ osk.restorePosition = function() { if(osk._Visible) { keymanweb._FocusLastActiveElement(); // I2036 - OSK does not unpin to correct location osk.loadCookie(); osk.userPositioned=false; osk.saveCookie(); osk._Show(); osk.doResizeMove(); //allow the UI to respond to OSK movements } if(osk.pinImg) osk.pinImg.style.display='none'; if(window.event) window.event.returnValue=false; } // Add 'Unpin' button for restoring OSK to default location, handle mousedown and click events Limg=osk.pinImg = util._CreateElement('DIV'); //I2186 Limg.className='kmw-pin-image'; Limg.title='Pin the On Screen Keyboard to its default location on the active text box'; Limg.onclick=osk.restorePosition; Limg.onmousedown=osk._CancelMouse; Limg.style.display='none'; // Do not use Unpin button on touch screens (OSK location fixed) if(!device.touchable) LdivButtons.appendChild(Limg); // I3363 (Build 301) // Attach button container to title bar Ldiv.appendChild(LdivButtons); // Add title bar caption Limg=keymanweb._TitleElement=util._CreateElement('SPAN'); // I1972 Limg.className='kmw-title-bar-caption'; Limg.innerHTML='KeymanWeb'; Ldiv.appendChild(Limg); return Ldiv; } // End of TitleBarInterior /** * Function enabled * Scope Public * @return {boolean|number} True if KMW OSK enabled * Description Test if KMW OSK is enabled */ osk['isEnabled'] = osk.isEnabled = function() { return osk._Enabled; } /** * Function isVisible * Scope Public * @return {boolean|number} True if KMW OSK visible * Description Test if KMW OSK is actually visible * Note that this will usually return false after any UI event that results in (temporary) loss of input focus */ osk['isVisible'] = osk.isVisible = function() { return osk._Visible; } /** * Function getRect //TODO: This is probably not correct, anyway!!!!! * Scope Public * @return {Object.} Array object with position and size of OSK container * Description Get rectangle containing KMW Virtual Keyboard */ osk['getRect'] = osk.getRect = function() // I2405 { var p={}; if(osk._DivVKbd) { p['left'] = p.left = util._GetAbsoluteX(osk._DivVKbd); p['top'] = p.top = util._GetAbsoluteY(osk._DivVKbd); p['width'] = p.width = util._GetAbsoluteX(osk._DivVKbdHelp) - util._GetAbsoluteX(osk._DivVKbd) + osk._DivVKbdHelp.offsetWidth; p['height'] = p.height = util._GetAbsoluteY(osk._DivVKbdHelp) - util._GetAbsoluteY(osk._DivVKbd) + osk._DivVKbdHelp.offsetHeight; } else { p['left'] = p.left = util._GetAbsoluteX(osk._Box); p['top'] = p.top = util._GetAbsoluteY(osk._Box); p['width'] = p.width = util._GetAbsoluteX(osk._Box) + osk._Box.offsetWidth; p['height'] = p.height = util._GetAbsoluteY(osk._Box) + osk._Box.offsetHeight; } return p; } /** * Allow the UI or page to set the position and size of the OSK * and (optionally) override user repositioning or sizing * * @param {Object.} p Array object with position and size of OSK container **/ osk['setRect'] = osk.setRect = function(p) { var q={}; if(osk._Box == null || device.formFactor != 'desktop') return; var b=osk._Box,bs=b.style; if('left' in p) { bs.left=(p['left']-util._GetAbsoluteX(b)+b.offsetLeft)+'px'; osk.dfltX=bs.left; } if('top' in p) { bs.top=(p['top']-util._GetAbsoluteY(b)+b.offsetTop)+'px'; osk.dfltY=bs.top; } //Do not allow user resizing for non-standard keyboards (e.g. EuroLatin) if(osk._DivVKbd != null) { var d=osk._DivVKbd,ds=d.style; // Set width, but limit to reasonable value if('width' in p) { var w=(p['width']-(b.offsetWidth-d.offsetWidth)); if(w < 0.2*screen.width) w=0.2*screen.width; if(w > 0.9*screen.width) w=0.9*screen.width; ds.width=w+'px'; osk.width=w; } // Set height, but limit to reasonable value // This sets the default font size for the OSK in px, but that // can be modified at the key text level by setting // the font size in em in the kmw-key-text class if('height' in p) { var h=(p['height']-(b.offsetHeight-d.offsetHeight)); if(h < 0.1*screen.height) h=0.1*screen.height; if(h > 0.5*screen.height) h=0.5*screen.height; ds.height=h+'px'; ds.fontSize=(h/8)+'px'; osk.height=h; } // Fix or release user resizing if('nosize' in p) if(osk.resizeIcon) osk.resizeIcon.style.display=(p['nosize'] ? 'none' : 'block'); } // Fix or release user dragging if('nomove' in p) { osk.noDrag=p['nomove']; if(osk.pinImg) osk.pinImg.style.display=(p['nomove'] || !osk.userPositioned) ? 'none' : 'block'; } // Save the user-defined OSK size osk.saveCookie(); } /** * Get position of OSK window * * @return {Object.} Array object with OSK window position **/ osk.getPos = function() { var Lkbd=osk._Box, p={}; p.left = osk._Visible ? Lkbd.offsetLeft : osk.x; p.top = osk._Visible ? Lkbd.offsetTop : osk.y; return p; } /** * Function setPos * Scope Private * @param {Object.} p Array object with OSK left, top * Description Set position of OSK window, but limit to screen, and ignore if a touch input device */ osk['setPos'] = osk.setPos = function(p) { if(typeof(osk._Box) == 'undefined' || device.touchable) return; // I3363 (Build 301) if(osk.userPositioned) { var Px=p['left'],Py=p['top']; if(typeof(Px) != 'undefined') { if(Px < -0.8*osk._Box.offsetWidth) Px = -0.8*osk._Box.offsetWidth; if(osk.userPositioned) {osk._Box.style.left=Px+'px'; osk.x = Px;} } // May not be needed - vertical positioning is handled differently and defaults to input field if off screen if(typeof(Py) != 'undefined') { if(Py < 0) Py = 0; if(osk.userPositioned) {osk._Box.style.top=Py+'px'; osk.y = Py;} } } if(osk.pinImg) osk.pinImg.style.display=(osk.userPositioned ? 'block' : 'none'); } /** * Function _VKeyGetTarget * Scope Private * @param {Object} e OSK event * @return {Object} Target element for key in OSK * Description Identify the OSK key clicked */ osk._VKeyGetTarget = function(e) { var Ltarg; e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(!e) return null; if (e.target) Ltarg = e.target; else if (e.srcElement) Ltarg = e.srcElement; else return null; if (Ltarg.nodeType == 3) // defeat Safari bug Ltarg = Ltarg.parentNode; if (Ltarg.tagName == 'SPAN') Ltarg = Ltarg.parentNode; return Ltarg; } /** * Add or remove a class from a keyboard key (when touched or clicked) * or add a key preview for phone devices * * @param {Object} key key affected * @param {boolean} on add or remove highlighting **/ osk.highlightKey = function(key,on) { // Do not change element class unless a key if(!key || (key.className == '') || (key.className.indexOf('kmw-key-row') >= 0)) return; var classes=key.className, cs = ' kmw-key-touched'; // For phones, use key preview rather than highlighting the key, // except for space, bksp, enter, shift and popup keys var usePreview = ((osk.keytip != null) && (classes.indexOf('kmw-key-shift') < 0) && (classes.indexOf('kmw-spacebar') < 0) && (key.id.indexOf('popup') < 0 )); if(usePreview) { osk.showKeyTip(key,on); } else { if(on && classes.indexOf(cs) < 0) { key.className=classes+cs; osk.showKeyTip(null,false); // Moved here by Serkan } else { key.className=classes.replace(cs,''); } } } /** * Display touch-hold array of 'sub-keys' above the currently touched key * @param {Object} e primary key element */ osk.showSubKeys = function(e) { // Do not show subkeys if key already released if(osk.keyPending == null) return; // Create holder DIV for subkey array, and set styles. // A subkey array for Shift will only appear if extra layers exist // The holder is position:fixed, but the keys do not need to be, as no scrolling // is possible while the array is visible. So it is simplest to let the keys have // position:static and display:inline-block var subKeys=document.createElement('DIV'),i,sk, t,ts,t1,ts1,kDiv,ks,btn,bs; var tKey = osk.getDefaultKeyObject(); subKeys.id='kmw-popup-keys'; osk.popupBaseKey = e; // Does the popup array include the base key? *** condition for phone only *** if(device.formFactor == 'phone') osk.prependBaseKey(e); var idx = e.id.split('-'), baseId = idx[idx.length-1]; // If not, insert at start if(device.formFactor == 'phone' && e.subKeys[0].id != baseId) { var eCopy={'id':baseId,'layer':''}; if(idx.length > 1) eCopy['layer'] = idx[0]; for(i=0; i 1) ss.width=(nCols*e.offsetWidth+nCols*5)+'px'; // Add nested button elements for each sub-key for(i=0; i 1 && nRow > 0) ks.marginTop='5px'; if(typeof sk['width'] != 'undefined') kDiv.width=ks.width=(parseInt(sk['width'],10)*e.offsetWidth/100)+'px'; else kDiv.width=ks.width=e.offsetWidth+'px'; ks.height=e.offsetHeight+'px'; btn=document.createElement('DIV'); osk.setButtonClass(sk,btn); // Create (temporarily) unique ID by prefixing 'popup-' to actual key ID if(typeof(sk['layer']) == 'string' && sk['layer'] != '') btn.id='popup-'+sk['layer']+'-'+sk['id']; else btn.id='popup-default-'+sk['id']; btn.key = sk; // Must set button size (in px) dynamically, not from CSS bs=btn.style; bs.height=ks.height; bs.width=ks.width; // Must set position explicitly, at least for Android bs.position='absolute'; t=util._CreateElement('SPAN'); t.className='kmw-key-text'; if(sk['text'] == null || sk['text'] == '') { t.innerHTML='\xa0'; if(typeof sk['id'] == 'string') { if(/^U_[0-9A-F]{4}$/i.test(sk['id'])) t.innerHTML=String.fromCharCode(parseInt(sk['id'].substr(2),16)); } } else t.innerHTML=sk['text']; // Override the font name and size if set in the layout ts=t.style; ts.fontSize=osk.fontSize; //Build 344, KMEW-90 if(typeof sk['font'] == 'string' && sk['font'] != '') ts.fontFamily=sk['font']; if(typeof sk['fontsize'] == 'string' && sk['fontsize'] != 0) ts.fontSize=sk['fontsize']; btn.appendChild(t); kDiv.appendChild(btn); subKeys.appendChild(kDiv); } // Clear key preview if any osk.showKeyTip(null,false); // Otherwise append the touch-hold (subkey) array to the OSK osk._Box.appendChild(subKeys); // And correct its position with respect to that element ss=subKeys.style; var x=util._GetAbsoluteX(e)+0.5*(e.offsetWidth-subKeys.offsetWidth), y, xMax=(util.landscapeView()?screen.height:screen.width)-subKeys.offsetWidth; if(x > xMax) x=xMax; if(x < 0) x=0; ss.left=x+'px'; // Add the callout osk.popupCallout = osk.addCallout(e); // Make the popup keys visible ss.visibility='visible'; // And add a filter to fade main keyboard subKeys.shim = document.createElement('DIV'); subKeys.shim.id = 'kmw-popup-shim'; osk._Box.appendChild(subKeys.shim); // Highlight the duplicated base key (if a phone) if(device.formFactor == 'phone') { var bk = subKeys.childNodes[0].firstChild; osk.keyPending = bk; osk.highlightKey(bk,true);//bk.className = bk.className+' kmw-key-touched'; } } /** * Prepend the base key to the touch-hold key array (for phones) * * @param {Object} e base key object */ osk.prependBaseKey = function(e) { if(e && typeof(e.id) != 'undefined') { var i, idx = e.id.split('-'), baseId = idx[idx.length-1], layer = (idx.length > 1 ? idx[0] : ''); if(typeof e.subKeys != 'undefined' && e.subKeys.length > 0 && (e.subKeys[0].id != baseId || e.subKeys[0].layer != layer)) { var eCopy={'id':baseId,'layer':''}; if(idx.length > 1) eCopy['layer'] = idx[0]; for(i = 0; i < e.childNodes.length; i++) { if(osk.hasClass(e.childNodes[i],'kmw-key-text')) break; } if(i < e.childNodes.length) eCopy['text'] = e.childNodes[i].textContent; e.subKeys.splice(0,0,eCopy); } } } /** * @summary Look up a custom virtual key code in the virtual key code dictionary KVKD. On first run, will build the dictionary. * * `VKDictionary` is constructed from the keyboard's `KVKD` member. This list is constructed * at compile-time and is a list of 'additional' virtual key codes, starting at 256 (i.e. * outside the range of standard virtual key codes). These additional codes are both * `[T_xxx]` and `[U_xxxx]` custom key codes from the Keyman keyboard language. However, * `[U_xxxx]` keys only generate an entry in `KVKD` if there is a corresponding rule that * is associated with them in the keyboard rules. If the `[U_xxxx]` key code is only * referenced as the id of a key in the touch layout, then it does not get an entry in * the `KVKD` property. * * @private * @param {string} keyName custom virtual key code to lookup in the dictionary * @return {number} key code > 255 on success, or 0 if not found */ osk.getVKDictionaryCode = function(keyName) { if(!keymanweb._ActiveKeyboard['VKDictionary']) { var a=[]; if(typeof keymanweb._ActiveKeyboard['KVKD'] == 'string') { // Build the VK dictionary // TODO: Move the dictionary build into the compiler -- so compiler generates code such as following. Makes the VKDictionary member unnecessary // this.KVKD={"K_ABC":256,"K_DEF":257,...}; var s=keymanweb._ActiveKeyboard['KVKD'].split(' '); for(var i=0; i 127 && ch < 160)) { // 127 - 160 refer to Unicode control codes. // Do not allow output of these codes via U_xxxx shortcuts. ch = 0; } // Hereafter, we refer to keycodes. } else if(checkCodes) { if(n >= osk.keyCodes['K_0'] && n <= osk.keyCodes['K_9']) { // The number keys. ch = codesUS[keyShiftState][0][n-osk.keyCodes['K_0']]; } else if(n >=osk.keyCodes['K_A'] && n <= osk.keyCodes['K_Z']) { // The base letter keys ch = String.fromCharCode(n+(keyShiftState?0:32)); // 32 is the offset from uppercase to lowercase. } else if(n >= osk.keyCodes['K_COLON'] && n <= osk.keyCodes['K_BKQUOTE']) { ch = codesUS[keyShiftState][1][n-osk.keyCodes['K_COLON']]; } else if(n >= osk.keyCodes['K_LBRKT'] && n <= osk.keyCodes['K_QUOTE']) { ch = codesUS[keyShiftState][2][n-osk.keyCodes['K_LBRKT']]; } } return ch; } /** * Simulate a keystroke according to the touched keyboard button element * * @param {Object} e element touched (or clicked) */ osk.clickKey = function(e) { var Lelem = keymanweb._LastActiveElement, Ls, Le, Lkc, Lsel; // Each button id is of the form -, e.g. 'shift-ctrl-K_Q' or 'popup-shift-K_501', etc. var t=e.id.split('-'); if(t.length < 2) return true; //shouldn't happen, but... // Remove popup prefix before processing keystroke (KMEW-93) if(t[0] == 'popup') t.splice(0,1); if(Lelem != null) { // Get key name and keyboard shift state (needed only for default layouts and physical keyboard handling) var layer=t[0], keyName=t[t.length-1], keyShiftState=osk.getModifierState(osk.layerId), nextLayer = keyShiftState; // Make sure to get the full current layer, since layers are now kebab-case. for(var i=1; i < t.length-1; i++) { layer = layer + "-" + t[i]; } if(typeof(e.key) != 'undefined') nextLayer=e.key['nextlayer']; if(keymanweb._ActiveElement == null) keymanweb._ActiveElement=Lelem; // Exclude menu and OSK hide keys from normal click processing if(keyName == 'K_LOPT' || keyName == 'K_ROPT') { osk.optionKey(e,keyName,true); return true; } // Turn off key highlighting (or preview) osk.highlightKey(e,false); // The default OSK layout for desktop devices does not include nextlayer info, relying on modifier detection here. if(device.formFactor == 'desktop') { if(osk.selectLayer(keyName, nextLayer)){ return true; } } // Prevent any output from 'ghost' (unmapped) keys if(keyName != 'K_SPACE') { var keyText=e.childNodes[0].innerHTML; //// if(keyText == '' || keyText == ' ') return true; --> why? } 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; if(document.selection && Lsel != null) Lsel.select(); Lelem._KeymanWebSelectionStart=Ls; Lelem._KeymanWebSelectionEnd=Le; keymanweb._IgnoreNextSelChange = 0; // ...end I3363 (Build 301) keymanweb._CachedSelectionStart = null; // I3319 // Deadkey matching continues to be troublesome. // Deleting matched deadkeys here seems to correct some of the issues. (JD 6/6/14) keymanweb._DeadkeyDeleteMatched(); // Delete any matched deadkeys before continuing //keymanweb._DeadkeyResetMatched(); // I3318 (Not needed if deleted first?) // First check the virtual key, and process shift, control, alt or function keys Lkc = {Ltarg:Lelem,Lmodifiers:0,Lstates:0,Lcode:osk.keyCodes[keyName],LisVirtualKey:true}; // Set the flags for the state keys. Lkc.Lstates |= osk._stateKeys['K_CAPS'] ? osk.modifierCodes['CAPS'] : osk.modifierCodes['NO_CAPS']; Lkc.Lstates |= osk._stateKeys['K_NUMLOCK'] ? osk.modifierCodes['NUM_LOCK'] : osk.modifierCodes['NO_NUM_LOCK']; Lkc.Lstates |= osk._stateKeys['K_SCROLL'] ? osk.modifierCodes['SCROLL_LOCK'] : osk.modifierCodes['NO_SCROLL_LOCK']; // Set LisVirtualKey to false to ensure that nomatch rule does fire for U_xxxx keys if(keyName.substr(0,2) == 'U_') Lkc.LisVirtualKey=false; // Get code for non-physical keys (T_KOKAI, U_05AB etc) if(typeof Lkc.Lcode == 'undefined') { Lkc.Lcode = osk.getVKDictionaryCode(keyName);// Updated for Build 347 if(!Lkc.Lcode) { // Special case for U_xxxx keys. This vk code will never be used // in a keyboard, so we use this to ensure that keystroke processing // occurs for the key. Lkc.Lcode = 1; } } // Override key shift state if specified for key in layout (corrected for popup keys KMEW-93) var lx=(typeof e.key == 'undefined' ? null : e.key['layer']); if(lx == null) keyShiftState=osk.getModifierState(layer); else keyShiftState=osk.getModifierState(lx); // Define modifiers value for sending to keyboard mapping function Lkc.Lmodifiers = keyShiftState; // Handles modifier states when the OSK is emulating rightalt through the leftctrl-leftalt layer. if((Lkc.Lmodifiers & osk.modifierBitmasks['ALT_GR_SIM']) == osk.modifierBitmasks['ALT_GR_SIM'] && osk.emulatesAltGr()) { Lkc.Lmodifiers &= ~osk.modifierBitmasks['ALT_GR_SIM']; Lkc.Lmodifiers |= osk.modifierCodes['RALT']; } // Include *limited* support for mnemonic keyboards (Sept 2012) if(keymanweb._ActiveKeyboard && (keymanweb._ActiveKeyboard['KM'])) { var keyText=e.firstChild.firstChild.wholeText; Lkc.LisVirtualKey=false; Lkc.LisVirtualKeyCode=false; Lkc.vkCode=Lkc.Lcode; if(Lkc.Lcode != osk.keyCodes['K_SPACE']) // exception required, March 2013 { if(typeof keyText == 'string' && keyText != '') Lkc.Lcode=keyText.charCodeAt(0); else Lkc.Lcode=0; if(Lkc.Lcode == 160) Lkc.Lcode = 0; } Lkc.Lmodifiers=0; } else Lkc.vkCode=Lkc.Lcode; // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic if(typeof keymanweb._ActiveKeyboard['KM'] == 'undefined') { Lkc.Lcode=keymanweb._USKeyCodeToCharCode(Lkc); Lkc.LisVirtualKey=false; } // Pass this key code and state to the keyboard program if(!keymanweb._ActiveKeyboard || (Lkc.Lcode != 0 && !keymanweb._ActiveKeyboard['gs'](Lelem, Lkc))) { // Restore the virtual key code if a mnemonic keyboard is being used Lkc.Lcode=Lkc.vkCode; // Handle unmapped keys, including special keys switch(keyName) { case 'K_BKSP': //Only desktop UI, not touch devices. TODO: add repeat while mouse down for desktop UI keymanweb.KO(1,keymanweb._LastActiveElement,""); break; case 'K_TAB': var bBack=(osk.layerId == 'shift'); keymanweb.moveToNext(bBack); break; case 'K_TABBACK': keymanweb.moveToNext(true); break; case 'K_TABFWD': keymanweb.moveToNext(false); break; case 'K_ENTER': // Insert new line in text area fields if(Lelem.nodeName == 'TEXTAREA' || (typeof Lelem.base != 'undefined' && Lelem.base.nodeName == 'TEXTAREA')) keymanweb.KO(0, Lelem, '\n'); // Or move to next field from TEXT fields else { if(Lelem.nodeName == 'INPUT' && (Lelem.type == 'search' || Lelem.type == 'submit')) Lelem.form.submit(); else if(typeof(Lelem.base) != 'undefined' && (Lelem.base.type == 'search' || Lelem.base.type == 'submit')) { Lelem.base.disabled=false; Lelem.base.form.submit(); } else keymanweb.moveToNext(false); } break; case 'K_SPACE': keymanweb.KO(0, Lelem, ' '); break; case 'K_CAPS': case 'K_NUMLOCK': case 'K_SCROLL': osk._stateKeys[keyName] = ! osk._stateKeys[keyName]; osk._Show(); break; default: // The following is physical layout dependent, so should be avoided if possible. All keys should be mapped. var ch = osk.defaultKeyOutput(keyName,Lkc.Lcode,keyShiftState); if(ch)keymanweb.KO(0, Lelem, ch); } } // Test if this key has a non-default next layer if(typeof e.key != 'undefined' && e.key['nextlayer'] !== null) osk.nextLayer=e.key['nextlayer']; // TODO: Verify the layer name is valid? // Refresh the OSK if a different layer must be displayed if(osk.nextLayer != osk.layerId) { osk.layerId=osk.nextLayer; osk._Show(); } /* I732 END - 13/03/2007 MCD: End Positional Layout support in OSK */ Lelem._KeymanWebSelectionStart=null; Lelem._KeymanWebSelectionEnd=null; } keymanweb._IsActivatingKeymanWebUI = 0; // I2498 - KeymanWeb OSK does not accept clicks in FF when using automatic UI 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 * * @param {string} layerId layer id (e.g. ctrlshift) * @return {number} modifier key state (desktop keyboards) */ osk.getModifierState = function(layerId) { var modifier=0; if(layerId.indexOf('shift') >= 0) { modifier |= osk.modifierCodes['SHIFT']; } // The chiral checks must not be directly exclusive due each other to visual OSK feedback. var ctrlMatched=false; if(layerId.indexOf('leftctrl') >= 0) { modifier |= osk.modifierCodes['LCTRL']; ctrlMatched=true; } if(layerId.indexOf('rightctrl') >= 0) { modifier |= osk.modifierCodes['RCTRL']; ctrlMatched=true; } if(layerId.indexOf('ctrl') >= 0 && !ctrlMatched) { modifier |= osk.modifierCodes['CTRL']; } var altMatched=false; if(layerId.indexOf('leftalt') >= 0) { modifier |= osk.modifierCodes['LALT']; altMatched=true; } if(layerId.indexOf('rightalt') >= 0) { modifier |= osk.modifierCodes['RALT']; altMatched=true; } if(layerId.indexOf('alt') >= 0 && !altMatched) { modifier |= osk.modifierCodes['ALT']; } return modifier; } /** * Sets the new layer id, allowing for toggling shift/ctrl/alt while preserving the remainder * of the modifiers represented by the current layer id (where applicable) * * @param {string} id layer id (e.g. ctrlshift) */ osk.updateLayer = function(id) { var s=osk.layerId,idx=id; var i; // Need to test if target layer is a standard layer (based on the plain 'default') var replacements= ['leftctrl', 'rightctrl', 'ctrl', 'leftalt', 'rightalt', 'alt', 'shift']; for(i=0; i < replacements.length; i++) { // Don't forget to remove the kebab-case hyphens! idx=idx.replace(replacements[i] + '-', ''); idx=idx.replace(replacements[i],''); } // If we are presently on the default layer, drop the 'default' and go straight to the shifted mode. // If on a common symbolic layer, drop out of symbolic mode and go straight to the shifted mode. if(osk.layerId == 'default' || osk.layerId == 'numeric' || osk.layerId == 'symbol' || osk.layerId == 'currency' || idx != '') { s = id; } // Otherwise, we are based upon the a layer that accepts modifier variations. // Modify the layer according to the current state and key pressed. // // TODO: Consider: should this ever be allowed for a base layer other than 'default'? If not, // if(idx == '') with accompanying if-else structural shift would be a far better test here. else { // Save our current modifier state. var modifier=osk.getModifierState(s); // Strip down to the base modifiable layer. for(i=0; i < replacements.length; i++) { // Don't forget to remove the kebab-case hyphens! s=s.replace(replacements[i] + '-', ''); s=s.replace(replacements[i],''); } // Toggle the modifier represented by our input argument. switch(id) { case 'shift': modifier ^= osk.modifierCodes['SHIFT']; break; case 'leftctrl': modifier ^= osk.modifierCodes['LCTRL']; break; case 'rightctrl': modifier ^= osk.modifierCodes['RCTRL']; break; case 'ctrl': modifier ^= osk.modifierCodes['CTRL']; break; case 'leftalt': modifier ^= osk.modifierCodes['LALT']; break; case 'rightalt': modifier ^= osk.modifierCodes['RALT']; break; case 'alt': modifier ^= osk.modifierCodes['ALT']; break; default: s = id; } // 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); } else { s = osk.getLayerId(modifier) + '-' + s; } } } if(s == '') { s = 'default'; } // Actually set the new layer id. osk.layerId = s; // Check that requested layer is defined (KMEA-1, but does not resolve issue) for(i=0; i 2) { var sX=e.touches[1].pageX,sY=e.touches[1].pageY; if(sX > osk.spaceBar.offsetLeft && sX < osk.spaceBar.offsetLeft+osk.spaceBar.offsetWidth && sY > osk.spaceBar.offsetTop && sY < osk.spaceBar.offsetTop+osk.spaceBar.offsetHeight) osk.showBuild(); } },false); document.body.appendChild(menu.shim); // Add two nested DIVs to properly support iOS scrolling with momentum // c.f. https://github.com/joelambert/ScrollFix/issues/2 var m2=util._CreateElement('DIV'),s2=m2.style, m3=util._CreateElement('DIV'),s3=m3.style; m2.id='kmw-menu-scroll-container'; m3.id='kmw-menu-scroller'; // Support momentum scrolling on iOS if('WebkitOverflowScrolling' in s2) s2.WebkitOverflowScrolling='touch'; m2.appendChild(m3); menu.appendChild(m2); // Add menu index strip var i,x,mx=util._CreateElement('DIV'); mx.id='kmw-menu-index'; for(i=1; i<=26; i++) { x=util._CreateElement('P'); x.innerHTML=String.fromCharCode(i+64); mx.appendChild(x); } // Add index selection (for a large menu) mx.addEventListener('touchstart',function(e){osk.scrollToLanguage(e,m2,m3);},false); mx.addEventListener('touchend',function(e){e.stopPropagation();e.preventDefault();},false); menu.appendChild(mx); //TODO: not sure if either of these two handlers ar actually needed. touchmove handler may be doing all that is necessary. // Add scroll end event handling to override body scroll menu.addEventListener('scroll',function(e){ osk.lgList.scrolling=true; },false); m2.addEventListener('scroll',function(e){ //osk.lgList.scrolling=true; if(m2.scrollTop < 1)m2.scrollTop=1; if(m2.scrollTop > m2.scrollHeight-m2.offsetHeight-1)m2.scrollTop=m2.scrollHeight-m2.offsetHeight-1; },false); // Add a list of keyboards to the innermost DIV osk.lgList.activeLgNo=osk.addLanguagesToMenu(m3,kbdList); // Get number of visible (language) selectors var nLgs=m3.childNodes.length-1; // Do not display until sizes have been calculated osk.lgList.visibility='hidden'; // Append menu to document body, not to OSK document.body.appendChild(osk.lgList); // Adjust size for viewport scaling (probably not needed for iOS, but check!) if(device.OS == 'Android' && 'devicePixelRatio' in window) osk.lgList.style.fontSize=(2/window.devicePixelRatio)+'em'; // Adjust width for pixel scaling on Android tablets if(device.OS == 'Android' && device.formFactor == 'tablet' && 'devicePixelRatio' in window) { var w=parseInt(util.getStyleValue(menu,'width'),10),ms=menu.style; if(!isNaN(w)) ms.width=ms.maxWidth=(2*w/window.devicePixelRatio)+'px'; w=parseInt(util.getStyleValue(m2,'width'),10); ms=m2.style; if(!isNaN(w)) ms.width=ms.maxWidth=(2*w/window.devicePixelRatio)+'px'; w=parseInt(util.getStyleValue(m3,'width'),10); ms=m3.style; if(!isNaN(w)) ms.width=ms.maxWidth=(2*w/window.devicePixelRatio)+'px'; } // Adjust initial top and height of menu osk.adjustLanguageMenu(0); // Adjust the index font size and line height var dy=mx.childNodes[1].offsetTop-mx.childNodes[0].offsetTop, lineHeight=Math.floor(menu.offsetHeight/26.0), scale=Math.round(100.0*lineHeight/dy)/100.0, factor=(scale > 0.6 ? 1 : 2); if(scale > 1.25) scale=1.25; for(i=0;i<26;i++) { var qs=mx.childNodes[i].style; if(factor == 2 && (i%2) == 1) { qs.display='none'; } else { qs.fontSize=(scale*factor)+'em'; qs.lineHeight=(lineHeight*factor)+'px'; } } // Increase width of outer menu DIV by index, else hide index var menuWidth=m2.offsetWidth; if(m2.scrollHeight > m2.offsetHeight+3) menuWidth = menuWidth+mx.offsetWidth; else mx.style.display='none'; menu.style.width=menuWidth+'px'; // Now display the menu osk.lgList.visibility=''; // Set initial scroll to show current language (but never less than 1, to avoid dragging body) var top=m3.firstChild.offsetHeight*osk.lgList.activeLgNo+1; m2.scrollTop=top; // The scrollTop value is limited by the device, and must be limited to avoid dragging the document body if(m2.scrollTop < top) m2.scrollTop=m2.scrollHeight-m2.offsetHeight; if(m2.scrollTop > m2.scrollHeight-m2.offsetHeight-1)m2.scrollTop=m2.scrollHeight-m2.offsetHeight-1; } /** * Adjust top and height of language menu * * @param {number} nKbds number of displayed keyboards to add to number of languages **/ osk.adjustLanguageMenu = function(nKbds) { var menu=osk.lgList,m2=menu.firstChild,m3=m2.firstChild, barWidth=0,s=menu.style,mx=menu.childNodes[1], maxHeight=window.innerHeight-osk.lgKey.offsetHeight-16, nItems=m3.childNodes.length+nKbds-1, // Number of (visible) keyboard selectors itemHeight=m3.firstChild.firstChild.offsetHeight, menuHeight=nItems*itemHeight; // Correct maxheight for viewport scaling (iPhone/iPod only) and internal position corrections if(device.OS == 'iOS') { if(device.formFactor == 'phone') { barWidth=(util.landscapeView() ? 36 : 0); maxHeight=(window.innerHeight-barWidth-16)*util.getViewportScale(); } else if(device.formFactor == 'tablet') { barWidth=(util.landscapeView() ? 16 : 0); maxHeight=(maxHeight-barWidth); } } // Explicitly set position and height s.left=util._GetAbsoluteX(osk.lgKey)+'px'; if(menuHeight > maxHeight) menuHeight=maxHeight; s.height=menuHeight+'px'; // Position menu at bottom of screen, but referred to top (works for both iOS and Firefox) s.top=(util._GetAbsoluteY(osk._Box)+osk._Box.offsetHeight-menuHeight+window.pageYOffset-6)+'px'; s.bottom='auto'; // Explicitly set the scroller and index heights to the container height mx.style.height=m2.style.height=s.height; } /** * Add an index to the language menu * * @param {Object} e touch start event from index * @param {Object} m2 menu scroller DIV * @param {Object} menu DIV with list of languages */ osk.scrollToLanguage = function(e,m2,menu) { e.stopImmediatePropagation();e.stopPropagation();e.preventDefault(); if(e.touches[0].target.nodeName != 'P') return; var i,t,top=0,initial=e.touches[0].target.innerHTML.charCodeAt(0),nn=menu.childNodes; try { for(i=0; i= initial) break; } } catch(ex){} try { top=menu.firstChild.offsetHeight*i+1; m2.scrollTop=top; } catch(ex){top=0;} try { if(m2.scrollTop < top) m2.scrollTop=m2.scrollHeight-m2.offsetHeight; if(m2.scrollTop > m2.scrollHeight-m2.offsetHeight-1) m2.scrollTop=m2.scrollHeight-m2.offsetHeight-1; } catch(ex){} } /** * Display all languages for installed keyboards in scrollable list * * @param {Object} menu DIV to which language selectors will be added * @param {Object} kbdList array of keyboard stub objects * @return {number} index of currently active language **/ osk.addLanguagesToMenu = function(menu,kbdList) { var nStubs=kbdList.length; // Create and sort a list of languages var k,n,lg,langs=[]; for(n=0; n 1) { lgBar.className='kbd-list'; lgBar.innerHTML=langs[k]+'...'; lgBar.scrolled=false; lgBar.ontouchend=function(e) { e.preventDefault();e.stopPropagation(); if(e.target.scrolled) e.target.scrolled=false; else this.parentNode.className=(this.parentNode.className=='kbd-list-closed'?'kbd-list-open':'kbd-list-closed'); // Adjust top of menu to allow for expanded list osk.adjustLanguageMenu(this.parentNode.className=='kbd-list-closed'?0:this.kList.length); } lgBar.addEventListener('touchstart',function(e){e.stopPropagation();},false); lgBar.addEventListener('touchmove',function(e){e.target.scrolled=true;e.stopPropagation();},false); for(i=0; i= yMax-1) { e.preventDefault(); osk.lgList.y0=y; } } // Scroll down (show earlier listed languages) else if(dy > 0) { if(scroller.scrollTop < 2) { e.preventDefault(); osk.lgList.y0=y; } } // Dont' scroll - can happen if changing scroll direction else return; // Disable selected language if drag more than 5px if(dy < -5 || dy > 5) { osk.lgList.scrolling=true; this.className=this.className.replace(/\s*selected/,''); osk.lgList.y0=y; } return true; }, // Touch release (click) event selects touched list item touchEnd=function(e) { e.preventDefault(); if(typeof(e.stopImmediatePropagation) != 'undefined') e.stopImmediatePropagation();else e.stopPropagation(); if(osk.lgList.scrolling) { this.className=this.className.replace(/\s*selected/,''); } else { keymanweb.focusing=true; keymanweb.focusTimer=window.setTimeout(function(){keymanweb.focusing=false;},1000); osk.lgList.style.display='none'; //still allows blank menu momentarily on selection keymanweb._SetActiveKeyboard(this.kn,this.kc,true); keymanweb.doKeyboardChange(this.kn,this.kc); keymanweb._FocusLastActiveElement(); osk.hideLanguageList(); // Update the OSK with the new keyboard osk._Show(); } return true; }; kb.onmspointerdown=touchStart; kb.addEventListener('touchstart',touchStart,false); kb.onmspointermove=touchMove; kb.addEventListener('touchmove',touchMove,false); kb.onmspointerout=touchEnd; kb.addEventListener('touchend',touchEnd,false); } /** * Remove the language menu again **/ osk.hideLanguageList = function() { if(osk.lgList) { osk.highlightKey(osk.lgKey.firstChild,false); osk.lgList.style.visibility='hidden'; window.setTimeout(function(){ if(osk.lgList != null && typeof osk.lgList != 'undefined') { document.body.removeChild(osk.lgList.shim); document.body.removeChild(osk.lgList); } osk.lgList=null; },500); } } /** * Function _UpdateVKShift * Scope Private * @param {Object} e OSK event * @param {number} v keyboard shift state * @param {(boolean|number)} d set (1) or clear(0) shift state bits * @return Always true * Description Update the current shift state within KMW */ osk._UpdateVKShift = function(e, v, d) { var keyShiftState=0, lockStates=0, i; var lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK']; var lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL']; if(e) { // read shift states from Pevent keyShiftState = e.Lmodifiers; lockStates = e.Lstates; // Are we simulating AltGr? If it's a simulation and not real, time to un-simulate for the OSK. if(keymanweb.isChiral() && osk.emulatesAltGr() && (keymanweb.modStateFlags & osk.modifierBitmasks.ALT_GR_SIM) == osk.modifierBitmasks.ALT_GR_SIM) { keyShiftState |= osk.modifierBitmasks.ALT_GR_SIM; keyShiftState &= ~osk.modifierCodes.RALT; } for(i=0; i < lockNames.length; i++) { if(lockStates & osk.stateBitmasks[lockNames[i]]) { osk._stateKeys[lockKeys[i]] = lockStates & osk.modifierCodes[lockNames[i]]; } } } else if(d) { keyShiftState |= v; for(i=0; i < lockNames.length; i++) { if(v & osk.stateBitmasks[lockNames[i]]) { osk._stateKeys[lockKeys[i]] = true; } } } else { keyShiftState &= ~v; for(i=0; i < lockNames.length; i++) { if(v & osk.stateBitmasks[lockNames[i]]) { osk._stateKeys[lockKeys[i]] = false; } } } // Find and display the selected OSK layer osk.layerId=osk.getLayerId(keyShiftState); // osk._UpdateVKShiftStyle will be called automatically upon the next _Show. if(osk._Visible) { osk._Show(); } return true; } /** * Function _UpdateVKShiftStyle * Scope Private * @param {string=} layerId * Description Updates the OSK's visual style for any toggled state keys */ osk._UpdateVKShiftStyle = function(layerId) { var i, n, layer=null, layerElement=null; if(layerId) { for(n=0; n0; i--) { if(rows[i-1]['key'].length > 0) break; } if(i < rows.length) rows.splice(i-rows.length,rows.length-i); } // ...remove to here when compiler bug fixed *** // Set the OSK row height, **assuming all layers have the same number of rows** // Calculate default row height rowHeight=100/rows.length; // For desktop OSK, use a percentage of the OSK height if(formFactor == 'desktop') { rowHeight=100/rows.length; } // Get the actual available document width and scale factor according to device type var objectUnits, objectWidth; if(formFactor == 'desktop') { objectUnits = function(v) { return v + '%' }; objectWidth = 100; } else { objectUnits = function(v) { return Math.round(v)+'px' }; objectWidth = osk.getWidth(); } if(device.touchable && ('ontouchstart' in window)) { lDiv.addEventListener('touchstart', osk.touch,true); lDiv.addEventListener('touchend', osk.release,false); lDiv.addEventListener('touchmove', osk.moveOver,false); //lDiv.addEventListener('touchcancel', osk.cancel,false); //event never generated by iOS } for(n=0; n totalWidth) totalWidth = width; } // Add default right margin if(formFactor == 'desktop') { totalWidth += 5; // KMEW-117 } else { // TODO: Not entirely clear why this needs to be 15 instead of 5 on touch layouts. We probably have // a miscalculation somewhere totalWidth += 15; } for(i=0; i 0) { j=keys.length-1; padPercent=Math.round(parseInt(keys[j]['pad'],10)*objectWidth/totalWidth); keys[j]['padpc']=padPercent; totalPercent += padPercent; keys[j]['widthpc']=(objectWidth-totalPercent); } //Create the key square (an outer DIV) for each key element with padding, and an inner DIV for the button (btn) totalPercent=0; for(j=0; j 90) x=0; } if(x > 0) { q=util._CreateElement('DIV'); q.className='kmw-key-label'; q.innerHTML=String.fromCharCode(x); //kDiv.appendChild(q); btn.appendChild(q); } } // Define each key element id by layer id and key id (duplicate possible for SHIFT - does it matter?) btn.id=layer['id']+'-'+key.id; btn.key=key; //attach reference to key layout spec to element // Add reference to subkey array if defined if(typeof key['sk'] != 'undefined' && key['sk'] != null) { var bsn,bsk=btn.subKeys=key['sk']; for(bsn=0; bsn9 for filled arrow icon //Note: U+E000 *is* PUA but was not accepted by IE as a character in the EOT font, so Alt recoded as U+E019 for(var i=0; i