// Includes KMW string extension declarations. /// // Contains event management for mobile device rotation events. /// /*** KeymanWeb 11.0 Copyright 2019 SIL International ***/ namespace com.keyman.osk { /** * Touch hold key display management * * @param {Object} key base key object */ VisualKeyboard.prototype.touchHold = function(this: VisualKeyboard, key: KeyElement) { // Clear and restart the popup timer if(this.subkeyDelayTimer) { window.clearTimeout(this.subkeyDelayTimer); this.subkeyDelayTimer = null; } if(typeof key['subKeys'] != 'undefined' && key['subKeys'] != null) { this.subkeyDelayTimer = window.setTimeout( function(this: VisualKeyboard) { this.clearPopup(); this.showSubKeys(key); }.bind(this), this.popupDelay); } }; VisualKeyboard.prototype.optionKey = function(this: VisualKeyboard, e: KeyElement, keyName: string, keyDown: boolean) { let keyman = com.keyman.singleton; let oskManager = keyman.osk; if(keyDown) { if(keyName.indexOf('K_LOPT') >= 0) { oskManager.showLanguageMenu(); } else if(keyName.indexOf('K_ROPT') >= 0) { keyman.uiManager.setActivatingUI(false); oskManager._Hide(true); let active = keyman.domManager.getActiveElement(); if(dom.Utils.instanceof(active, "TouchAliasElement")) { (active as dom.TouchAliasElement).hideCaret(); } keyman.domManager.clearLastActiveElement(); } } }; // Manage popup key highlighting VisualKeyboard.prototype.highlightSubKeys=function(this: VisualKeyboard, k: KeyElement, x: number, y: number) { let util = com.keyman.singleton.util; // Test for subkey array, return if none // (JH 2/4/19) So, if a subkey is passed in, we return immediately? if(k == null || k['subKeys'] == null) { return; } // Highlight key at touch position (and clear other highlighting) var i,sk,x0,y0,x1,y1,onKey,skBox=document.getElementById('kmw-popup-keys'); //#region This section fills a different role than the method name would suggest. // Might correspond better to a 'checkInstantSubkeys' or something. // Show popup keys immediately if touch moved up towards key array (KMEW-100, Build 353) if((this.touchY-y > 5) && skBox == null) { if(this.subkeyDelayTimer) { window.clearTimeout(this.subkeyDelayTimer); } this.showSubKeys(k); skBox=document.getElementById('kmw-popup-keys'); } //#endregion /* (JH 2/4/19) Because of that earlier note, in KMW 12 alpha (and probably 11), * the following code is effectively impotent and could be deleted with no effect. * Note that this probably results from VisualKeyboard.keyTarget finding the * subkey first... which is necessary anyway to support subkey output. */ for(i=0; i < k['subKeys'].length; i++) { try { sk= skBox.childNodes[i].firstChild; x0=dom.Utils.getAbsoluteX(sk); y0=dom.Utils.getAbsoluteY(sk);//-document.body.scrollTop; x1=x0+sk.offsetWidth; y1=y0+sk.offsetHeight; onKey=(x > x0 && x < x1 && y > y0 && y < y1); this.highlightKey(sk, onKey); if(onKey) { this.highlightKey(k, false); } } catch(ex){} } }; /** * Add (or remove) the keytip preview (if KeymanWeb on a phone device) * * @param {Object} key HTML key element * @param {boolean} on show or hide */ VisualKeyboard.prototype.showKeyTip = function(this: VisualKeyboard, key: KeyElement, on: boolean) { let keyman = com.keyman.singleton; let util = keyman.util; let oskManager = keyman.osk; var tip=this.keytip; // Do not change the key preview unless key or state has changed if(tip == null || (key == tip.key && on == tip.state)) { return; } var sk=document.getElementById('kmw-popup-keys'), popup = (sk && sk.style.visibility == 'visible') // Create and display the preview if(on && !popup) { var y0 = dom.Utils.getAbsoluteY(oskManager._Box), h0 = oskManager._Box.offsetHeight, xLeft = dom.Utils.getAbsoluteX(key), xTop = dom.Utils.getAbsoluteY(key), xWidth = key.offsetWidth, xHeight = key.offsetHeight, kc = key.firstChild, kcs = kc.style, kts = tip.element.style, ktLabel = tip.element.childNodes[1], ktls = ktLabel.style, edge = 0, canvas = tip.element.firstChild, previewFontScale = 1.8; // Find key text element for(var i=0; i key.childNodes[i]; if(util.hasClass(kc,'kmw-key-text')) { break; } } // Canvas dimensions must be set explicitly to prevent clipping canvas.width = 1.6 * xWidth; canvas.height = 2.3 * xHeight; kts.top = 'auto'; kts.bottom = (y0 + h0 - xTop - xHeight)+'px'; kts.textAlign = 'center'; kts.overflow = 'visible'; kts.fontFamily = util.getStyleValue(kc,'font-family'); kts.width = canvas.width+'px'; kts.height = canvas.height+'px'; var px=util.getStyleInt(kc, 'font-size'); if(px != 0) { let popupFS = previewFontScale * px; kts.fontSize = popupFS + 'px'; let textWidth = com.keyman.osk.OSKKey.getTextWidth(this, ktLabel.textContent, kts); // We use a factor of 0.9 to serve as a buffer in case of mild measurement error. let proportion = canvas.width * 0.9 / (textWidth); // Prevent the preview from overrunning its display area. if(proportion < 1) { kts.fontSize = (popupFS * proportion) + 'px'; } } ktLabel.textContent = kc.textContent; ktls.display = 'block'; ktls.position = 'absolute'; ktls.textAlign = 'center'; ktls.width='100%'; ktls.top = '2%'; ktls.bottom = 'auto'; // Adjust canvas shape if at edges var xOverflow = (canvas.width - xWidth) / 2; if(xLeft < xOverflow) { edge = -1; xLeft += xOverflow; } else if(xLeft > window.innerWidth - xWidth - xOverflow) { edge = 1; xLeft -= xOverflow; } this.drawPreview(canvas, xWidth, xHeight, edge); kts.left=(xLeft - xOverflow) + 'px'; kts.display = 'block'; } else { // Hide the key preview tip.element.style.display = 'none'; } // Save the key preview state tip.key = key; tip.state = on; }; /** * Draw key preview in element using CANVAS * @param {Object} canvas CANVAS element * @param {number} w width of touched key, px * @param {number} h height of touched key, px * @param {number} edge -1 left edge, 1 right edge, else 0 */ VisualKeyboard.prototype.drawPreview = function(this: VisualKeyboard, canvas: HTMLCanvasElement, w: number, h: number, edge: number) { let device = com.keyman.singleton.util.device; var ctx = canvas.getContext('2d'), dx = (canvas.width - w)/2, hMax = canvas.height, w0 = 0, w1 = dx, w2 = w + dx, w3 = w + 2 * dx, h1 = 0.5 * hMax, h2 = 0.6 * hMax, h3 = hMax, r = 8; if(device.OS == 'Android') { r = 3; } // Adjust the preview shape at the edge of the keyboard switch(edge) { case -1: w1 -= dx; w2 -= dx; break; case 1: w1 += dx; w2 += dx; break; } // Clear the canvas ctx.clearRect(0,0,canvas.width,canvas.height); // Define appearance of preview (cannot be done directly in CSS) if(device.OS == 'Android') { var wx=(w1+w2)/2; w1 = w2 = wx; ctx.fillStyle = '#999'; } else { ctx.fillStyle = '#ffffff'; } ctx.lineWidth = 1; ctx.strokeStyle = '#cccccc'; // Draw outline ctx.save(); ctx.beginPath(); ctx.moveTo(w0+r,0); ctx.arcTo(w3,0,w3,r,r); if(device.OS == 'Android') { ctx.arcTo(w3,h1,w2,h2,r); ctx.arcTo(w2,h2,w1,h2,r); ctx.arcTo(w1,h2,w0,h1-r,r); } else { ctx.arcTo(w3,h1,w2,h2,r); ctx.arcTo(w2,h2,w2-r,h3,r); ctx.arcTo(w2,h3,w1,h3,r); ctx.arcTo(w1,h3,w1,h2-r,r); ctx.arcTo(w1,h2,w0,h1-r,r); } ctx.arcTo(w0,h1,w0,r,r); ctx.arcTo(w0,0,w0+r,0,r); ctx.fill(); ctx.stroke(); ctx.restore(); }; /** * Create a key preview element for phone devices */ VisualKeyboard.prototype.createKeyTip = function(this: VisualKeyboard) { let keyman = com.keyman.singleton; let util = keyman.util; if(keyman.util.device.formFactor == 'phone') { if(this.keytip == null) { this.keytip = { key: null, state: false } let tipElement = this.keytip.element=util._CreateElement('div'); tipElement.className='kmw-keytip'; tipElement.id = 'kmw-keytip'; // The following style is critical, so do not rely on external CSS tipElement.style.pointerEvents='none'; // Add CANVAS element for outline and SPAN for key label tipElement.appendChild(util._CreateElement('canvas')); tipElement.appendChild(util._CreateElement('span')); } // Always append to _Box (since cleared during OSK Load) keyman.osk._Box.appendChild(this.keytip.element); } }; /** * Add a callout for popup keys (if KeymanWeb on a phone device) * * @param {Object} key HTML key element * @return {Object} callout object */ VisualKeyboard.prototype.addCallout = function(this: VisualKeyboard, key: KeyElement): HTMLDivElement { let keyman = com.keyman.singleton; let util = keyman.util; if(util.device.formFactor != 'phone' || util.device.OS != 'iOS') { return null; } var cc = util._CreateElement('div'), ccs = cc.style; cc.id = 'kmw-popup-callout'; keyman.osk._Box.appendChild(cc); // Create the callout var xLeft = key.offsetLeft, xTop = key.offsetTop, xWidth = key.offsetWidth, xHeight = key.offsetHeight; // Set position and style ccs.top = (xTop-6)+'px'; ccs.left = xLeft+'px'; ccs.width = xWidth+'px'; ccs.height = (xHeight+6)+'px'; // Return callout element, to allow removal later return cc; } /** * Wait until font is loaded before applying stylesheet - test each 100 ms * @param {Object} kfd main font descriptor * @param {Object} ofd secondary font descriptor (OSK only) * @return {boolean} */ VisualKeyboard.prototype.waitForFonts = function(this: VisualKeyboard, kfd, ofd) { let keymanweb = com.keyman.singleton; let util = keymanweb.util; if(typeof(kfd) == 'undefined' && typeof(ofd) == 'undefined') { return true; } if(typeof(kfd['files']) == 'undefined' && typeof(ofd['files']) == 'undefined') { return true; } var kReady=util.checkFontDescriptor(kfd), oReady=util.checkFontDescriptor(ofd); if(kReady && oReady) { return true; } keymanweb.fontCheckTimer=window.setInterval(function() { if(util.checkFontDescriptor(kfd) && util.checkFontDescriptor(ofd)) { window.clearInterval(keymanweb.fontCheckTimer); keymanweb.fontCheckTimer=null; keymanweb.alignInputs(); } }, 100); // Align anyway as best as can if font appears to remain uninstalled after 5 seconds window.setTimeout(function() { if(keymanweb.fontCheckTimer) { window.clearInterval(keymanweb.fontCheckTimer); keymanweb.fontCheckTimer=null; keymanweb.alignInputs(); // Don't notify - this is a management issue, not anything the user needs to deal with // TODO: Consider having an icon in the OSK with a bubble that indicates missing font //util.alert('Unable to download the font normally used with '+ks['KN']+'.'); } }, 5000); return false; }; // /** // * Adjust the width of the last cell in each row for length differences // * due to rounding percentage widths to nearest pixel. // * // * @param {number} nLayer Index of currently visible layer // */ // osk.adjustRowLengths = function(nLayer) // { // if(nLayer >= 0) return; //TODO: TEST ONLY - remove code if not needed // var maxWidth,layers=osk._DivVKbd.childNodes[0].childNodes; // if(nLayer < 0 || nLayer >= layers.length || layers[nLayer].aligned) return; // // Do not try and align if not visible! // if(layers[nLayer].style.display != 'block') return; // // Set max width to be 6 px less than OSK layer width (allow for inter-key spacing) // // TODO: Adjustment needs to be device and orientation specific // maxWidth=osk._DivVKbd.childNodes[0].offsetWidth-6; // if(device.OS == 'Windows') // { // maxWidth -= util.landscapeView() ? 4: 40; // } // var i,rows=layers[nLayer].childNodes,keys,nKeys,lastKey,xMax; // for(i=0; i 0) { element.base.style.visibility = 'hidden'; } }) } } /** * Programatically hides all input fields with underlying elements. Restore with .alignInputs. * * @param {boolean} align align and make visible, else hide * **/ keymanweb.hideInputs = function() { var domManager = keymanweb.domManager; if(device.touchable) { for(var i=0; i