0)) {
(keySquare.childNodes[0] as HTMLElement).style.fontSize='6px';
}
}
}
// /**
// * Function _VKeyGetTarget
// * Scope Private
// * @param {Object} e OSK event
// * @return {Object} Target element for key in OSK
// * Description Identify the OSK key clicked
// */
// _VKeyGetTarget(e: Event) {
// var Ltarg;
// e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs
// Ltarg = util.eventTarget(e);
// if (Ltarg == null) {
// return null;
// }
// if (Ltarg.nodeType == 3) { // defeat Safari bug
// Ltarg = Ltarg.parentNode;
// }
// if (Ltarg.tagName == 'SPAN') {
// Ltarg = Ltarg.parentNode;
// }
// return Ltarg;
// }
/**
* Append a style sheet for the current keyboard if needed for specifying an embedded font
* or to re-apply the default element font
*
**/
appendStyleSheet() {
let keymanweb = com.keyman.singleton;
let util = keymanweb.util;
var activeKeyboard = keymanweb.core.activeKeyboard;
var activeStub: com.keyman.keyboards.KeyboardStub = keymanweb.keyboardManager.activeStub;
// Do not do anything if a null stub
if(activeStub == null) {
return;
}
// First remove any existing keyboard style sheet
if(this.styleSheet) {
util.removeStyleSheet(this.styleSheet);
}
var i, kfd=activeStub['KFont'], ofd=activeStub['KOskFont'];
// Add style sheets for embedded fonts if necessary (each font-face style will only be added once)
util.addFontFaceStyleSheet(kfd);
util.addFontFaceStyleSheet(ofd);
// Temporarily hide duplicated elements on non-desktop browsers
keymanweb.hideInputs();
// Build the style string and append (or replace) the font style sheet
// Note: Some browsers do not download the font-face font until it is applied,
// so must apply style before testing for font availability
// Extended to allow keyboard-specific custom styles for Build 360
var customStyle=this.addFontStyle(kfd,ofd);
if( activeKeyboard != null && typeof(activeKeyboard.oskStyling) == 'string') // KMEW-129
customStyle=customStyle+activeKeyboard.oskStyling;
this.styleSheet = util.addStyleSheet(customStyle); //Build 360
// Wait until font is loaded then align duplicated input elements with page elements
if(this.waitForFonts(kfd,ofd)) {
keymanweb.alignInputs();
}
}
/**
* Add or replace the style sheet used to set the font for input elements and OSK
*
* @param {Object} kfd KFont font descriptor
* @param {Object} ofd OSK font descriptor (if any)
* @return {string}
*
**/
addFontStyle(kfd, ofd): string {
let keymanweb = com.keyman.singleton;
// Get name of font to be applied
var fn=keymanweb.baseFont;
if(typeof(kfd) != 'undefined' && typeof(kfd['family']) != 'undefined') {
fn=kfd['family'];
}
// Unquote font name in base font (if quoted)
fn = fn.replace(/\u0022/g,'');
// Set font family chain for mapped elements and remove any double quotes
var rx=new RegExp('\\s?'+fn+',?'), ff=keymanweb.appliedFont.replace(/\u0022/g,'');
// Remove base font name from chain if present
ff = ff.replace(rx,'');
ff = ff.replace(/,$/,'');
// Then replace it at the head of the chain
if(ff == '') {
ff=fn;
} else {
ff=fn+','+ff;
}
// Re-insert quotes around individual font names
ff = '"' + ff.replace(/\,\s?/g,'","') + '"';
// Add to the stylesheet, quoted, and with !important to override any explicit style
var s='.keymanweb-font{\nfont-family:' + ff + ' !important;\n}\n';
// Set font family for OSK text
if(typeof(ofd) != 'undefined') {
s=s+'.kmw-key-text{\nfont-family:"'+ofd['family'].replace(/\u0022/g,'').replace(/,/g,'","')+'";\n}\n';
} else if(typeof(kfd) != 'undefined') {
s=s+'.kmw-key-text{\nfont-family:"'+kfd['family'].replace(/\u0022/g,'').replace(/,/g,'","')+'";\n}\n';
}
// Store the current font chain (with quote-delimited font names)
keymanweb.appliedFont = ff;
// Return the style string
return s;
}
/**
* Create copy of the OSK that can be used for embedding in documentation or help
* The currently active keyboard will be returned if PInternalName is null
*
* @param {Object} PKbd the keyboard object to be displayed
* @param {string=} argFormFactor layout form factor, defaulting to 'desktop'
* @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default'
* @param {Object} host KeymanWeb's active OSKManager instance
* (currently required for legacy reasons)
* @return {Object} DIV object with filled keyboard layer content
*/
static buildDocumentationKeyboard(PKbd: com.keyman.keyboards.Keyboard, argFormFactor,argLayerId, host: OSKManager): HTMLElement { // I777
if(!PKbd) {
return null;
}
var formFactor=(typeof(argFormFactor) == 'undefined' ? 'desktop' : argFormFactor),
layerId=(typeof(argLayerId) == 'undefined' ? 'default' : argLayerId),
device = new Device();
// Device emulation for target documentation.
device.formFactor = formFactor;
if(formFactor != 'desktop') {
device.OS = 'iOS';
}
let layout = PKbd.layout(formFactor);
let kbdObj = new VisualKeyboard(PKbd, device, true);
let kbd = kbdObj.kbdDiv.childNodes[0] as HTMLDivElement; // Gets the layer group.
// Select the layer to display, and adjust sizes
if(layout != null) {
kbdObj.layerId = layerId;
// This feels _really_ hacky. There are plans to address this through some
// of the later aspects of the Web OSK-Core design.
kbdObj.show(host);
kbdObj.adjustHeights(host); // Necessary for the row heights to be properly set!
// Relocates the font size definition from the main VisualKeyboard wrapper, since we don't return the whole thing.
kbd.style.fontSize = kbdObj.kbdDiv.style.fontSize;
} else {
kbd.innerHTML="No "+formFactor+" layout is defined for "+PKbd.name+".
";
}
// Add a faint border
kbd.style.border='1px solid #ccc';
return kbd;
}
onHide() {
// Remove highlighting from hide keyboard key, if applied
if(this.hkKey) {
this.highlightKey(this.hkKey,false);
}
}
/**
* Touch hold key display management
*
* @param {Object} key base key object
*/
touchHold(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);
}
};
optionKey(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
highlightSubKeys(k: KeyElement, x: number, y: number) {
// 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
*/
showKeyTip(key: KeyElement, on: boolean) {
let keyman = com.keyman.singleton;
let util = keyman.util;
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 xLeft = dom.Utils.getAbsoluteX(key),
xWidth = key.offsetWidth,
xHeight = key.offsetHeight,
kc = key.firstChild,
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(kc.classList.contains('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';
// Matches how the subkey positioning is set.
kts.bottom = (parseInt(key.style.bottom, 10))+'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;
let scaleStyle = {
fontFamily: kts.fontFamily,
fontSize: popupFS + 'px',
height: 1.6 * xHeight + 'px' // as opposed to the canvas height of 2.3 * xHeight.
};
kts.fontSize = key.key.getIdealFontSize(this, scaleStyle);
}
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;
}
// For now, should only be true (in production) when keyman.isEmbedded == true.
let constrainPopup = keyman.isEmbedded;
let cs = getComputedStyle(tip.element);
let oskHeight = keyman.osk.getHeight();
let bottomY = parseInt(cs.bottom, 10);
let tipHeight = parseInt(cs.height, 10);
let delta = 0;
if(tipHeight + bottomY > oskHeight && constrainPopup) {
delta = tipHeight + bottomY - oskHeight;
canvas.height = canvas.height - delta;
kts.height = canvas.height + 'px';
}
this.drawPreview(canvas, xWidth, xHeight, edge, delta);
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
*/
drawPreview(canvas: HTMLCanvasElement, w: number, h: number, edge: number, delta?: number) {
let device = this.device;
delta = delta || 0;
var ctx = canvas.getContext('2d'), dx = (canvas.width - w)/2, hMax = canvas.height + delta,
w0 = 0, w1 = dx, w2 = w + dx, w3 = w + 2 * dx,
h1 = 0.5 * hMax, h2 = 0.6 * hMax, h3 = hMax, r = 8;
let hBoundedMax = canvas.height;
h2 = h2 > hBoundedMax ? hBoundedMax : h2;
h3 = hMax > hBoundedMax ? hBoundedMax : h3;
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 = device.styles.popupCanvasBackgroundColor;
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);
} else {
let lowerR = 0;
if(h3 > h2) {
lowerR = h3-h2 > r ? r : h3-h2;
}
ctx.arcTo(w3,h1,w2,h2,r);
ctx.arcTo(w2,h2,w2-lowerR,h3,lowerR);
ctx.arcTo(w2,h3,w1,h3,lowerR);
ctx.arcTo(w1,h3,w1,h2-lowerR,lowerR);
}
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
*/
createKeyTip() {
let keyman = com.keyman.singleton;
if(this.device.formFactor == 'phone') {
if(this.keytip == null) {
this.keytip = {
key: null,
state: false
}
let tipElement = this.keytip.element=document.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(document.createElement('canvas'));
tipElement.appendChild(document.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
*/
addCallout(key: KeyElement, delta?: number): HTMLDivElement {
let keyman = com.keyman.singleton;
if(this.device.formFactor != 'phone' || this.device.OS != 'iOS') {
return null;
}
delta = delta || 0;
let calloutHeight = key.offsetHeight - delta;
if(calloutHeight > 0) {
var cc = document.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 + delta,
xWidth = key.offsetWidth,
xHeight = calloutHeight;
// 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;
} else {
return null;
}
}
/**
* 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}
*/
waitForFonts(kfd, ofd) {
let keymanweb = com.keyman.singleton;
let util = keymanweb.util;
let fontDefined = !!(kfd && kfd['files']);
kfd = fontDefined ? kfd : undefined;
let oskFontDefined = !!(ofd && ofd['files']);
ofd = oskFontDefined ? ofd : undefined;
// Automatically 'ready' if the descriptor is explicitly `undefined`.
// Thus, also covers the case where both are undefined.
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;
};
shutdown() {
let keyman = com.keyman.singleton;
// Prevents style-sheet pollution from multiple keyboard swaps.
if(this.styleSheet) {
keyman.util.removeStyleSheet(this.styleSheet);
}
}
}
}