b[n];
if (layerElement['layer'] == this.layerId) {
layerElement.style.display = 'block';
//b[n].style.visibility='visible';
// Most functions that call this one often indicate a change in modifier
// or state key state. Keep it updated!
this._UpdateVKShiftStyle();
} else {
layerElement.style.display = 'none';
//layerElement.style.visibility='hidden';
}
}
}
/**
* Used to refresh the VisualKeyboard's geometric layout and key sizes
* when needed.
*/
refreshLayout() {
let keyman = com.keyman.singleton;
let device = this.device;
var fs = 1.0;
// TODO: Logically, this should be needed for Android, too - may need to be changed for the next version!
if (device.OS == utils.OperatingSystem.iOS && !keyman.isEmbedded) {
fs = fs / keyman.util.getViewportScale();
}
let paddedHeight: number;
if (this.height) {
paddedHeight = this.computedAdjustedOskHeight(this.height);
}
let b = this.layerGroup.element as HTMLElement;
let gs = this.kbdDiv.style;
let bs = b.style;
if (this.usesFixedHeightScaling) {
// Sets the layer group to the correct height.
gs.height = gs.maxHeight = paddedHeight + 'px';
}
// The font-scaling applied on the layer group.
gs.fontSize = this.fontSize.styleString;
bs.fontSize = ParsedLengthStyle.forScalar(fs).styleString;
// NEW CODE ------
// Step 1: have the necessary conditions been met?
const fixedSize = this.width && this.height;
const computedStyle = getComputedStyle(this.kbdDiv);
const isInDOM = computedStyle.height != '' && computedStyle.height != 'auto';
// Step 2: determine basic layout geometry
if (fixedSize) {
this._computedWidth = this.width;
this._computedHeight = this.height;
} else if (isInDOM) {
this._computedWidth = parseInt(computedStyle.width, 10);
if (!this._computedWidth) {
// For touch keyboards, the width _was_ specified on the layer group,
// not the root element (`kbdDiv`).
const groupStyle = getComputedStyle(this.kbdDiv.firstElementChild);
this._computedWidth = parseInt(groupStyle.width, 10);
}
this._computedHeight = parseInt(computedStyle.height, 10);
} else {
// Cannot perform layout operations!
return;
}
// Step 3: perform layout operations. (Handled by 'old code' section below.)
// END NEW CODE -----------
// Needs the refreshed layout info to work correctly.
if(this.currentLayer) {
this.currentLayer.refreshLayout(this, paddedHeight, this._computedHeight);
}
}
/*private*/ computedAdjustedOskHeight(allottedHeight: number): number {
if (!this.layerGroup) {
return allottedHeight;
}
const layers = this.layerGroup.layers;
let oskHeight = 0;
// In case the keyboard's layers have differing row counts, we check them all for the maximum needed oskHeight.
for (const layerID in layers) {
const layer = layers[layerID];
let nRows = layer.rows.length;
let rowHeight = Math.floor(allottedHeight / (nRows == 0 ? 1 : nRows));
let layerHeight = nRows * rowHeight;
if (layerHeight > oskHeight) {
oskHeight = layerHeight;
}
}
// This isn't set anywhere else; it's a legacy part of the original methods.
const oskPad = 0;
let oskPaddedHeight = oskHeight + oskPad;
return oskPaddedHeight;
}
/**
* 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 {number} height Target height for the rendered keyboard
* (currently required for legacy reasons)
* @return {Object} DIV object with filled keyboard layer content
*/
static buildDocumentationKeyboard(PKbd: com.keyman.keyboards.Keyboard, argFormFactor, argLayerId, height: number): 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';
device.touchable = true;
} else {
device.OS = 'windows';
device.touchable = false;
}
let layout = PKbd.layout(formFactor);
let kbdObj = new VisualKeyboard(PKbd, device.coreSpec, device.coreSpec, true);
// The 'documentation' format uses the base element's child as the actual display base.
// Since there's no backing kmw-osk-frame, we do need the static-class kmw-osk-inner-frame
// to perform background styling on our behalf. We'll trust the actual, live keyboard rules
// for the other elements, which in turn needs the non-static variant of the CSS rules.
kbdObj.layerGroup.element.className = kbdObj.kbdDiv.className + ' ' + device.formFactor
+ '-static ' + device.OS.toLowerCase();
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;
kbdObj.updateState();
// This still feels fairly hacky... but something IS needed to constrain the height.
// There are plans to address related concerns through some of the later aspects of
// the Web OSK-Core design.
kbdObj.setSize(800, height); // Probably need something for width, too, rather than
// assuming 100%.
kbdObj.refreshLayout(); // 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;
kbd.style.height = kbdObj.kbdDiv.style.height;
kbd.style.maxHeight = kbdObj.kbdDiv.style.maxHeight;
} else {
kbd.innerHTML = "No " + formFactor + " layout is defined for " + PKbd.name + ".
";
}
// Add a faint border
kbd.style.border = '1px solid #ccc';
// Once the element is inserted into the DOM, refresh the layout so that proper text scaling may apply.
const refreshInterval = window.setInterval(function () {
let computedStyle = getComputedStyle(kbd);
if (computedStyle.fontSize) {
if (kbd.style.fontSize) {
// Preserve the new setting (provided by CSS)
kbdObj.fontSize = new ParsedLengthStyle(kbd.style.fontSize);
}
kbdObj.refreshLayout();
window.clearInterval(refreshInterval);
}
}, 10);
return kbd;
}
onHide() {
// Remove highlighting from hide keyboard key, if applied
if (this.hkKey) {
this.highlightKey(this.hkKey, false);
}
}
/**
* Starts an implementation-specific longpress gesture. Separately implemented for
* in-browser and embedded modes.
* @param key The base key of the longpress.
* @returns
*/
startLongpress(key: KeyElement): PendingGesture {
let _this = this;
// First-level object/Promise: will produce a subkey popup when the longpress gesture completes.
// 'Returns' a second-level object/Promise: resolves when a subkey is selected or is cancelled.
let pendingLongpress = new browser.PendingLongpress(this, key);
pendingLongpress.promise.then(function (subkeyPopup) {
// In-browser-specific handling.
if (subkeyPopup) {
// Append the touch-hold (subkey) array to the OSK
let keyman = com.keyman.singleton;
keyman.osk._Box.appendChild(subkeyPopup.element);
keyman.osk._Box.appendChild(subkeyPopup.shim);
// Must be placed after its `.element` has been inserted into the DOM.
subkeyPopup.reposition(_this);
}
});
return pendingLongpress;
}
/**
* Initializes all supported gestures given a base key and the triggering touch coordinates.
* @param key The gesture's base key
* @param touch The starting touch coordinates for the gesture
* @returns
*/
initGestures(key: KeyElement, input: InputEventCoordinate) {
if (this.pendingMultiTap) {
switch (this.pendingMultiTap.incrementTouch(key)) {
case PendingMultiTapState.Cancelled:
this.pendingMultiTap = null;
break;
case PendingMultiTapState.Realized:
// Don't initialize any other gestures if the
// multi tap is realized; we cleanup on touch
// release because we need to cancel the base
// key action
return;
}
}
if (!this.pendingMultiTap && PendingMultiTap.isValidTarget(this, key)) {
// We are only going to support double-tap on Shift
// in Keyman 15, so we pass in the constant count = 2
this.pendingMultiTap = new PendingMultiTap(this, key, 2);
this.pendingMultiTap.timeout.then(() => {
this.pendingMultiTap = null;
});
}
if (key['subKeys']) {
let _this = this;
let pendingLongpress = this.startLongpress(key);
if (pendingLongpress == null) {
return;
}
this.pendingSubkey = pendingLongpress;
pendingLongpress.promise.then(function (subkeyPopup) {
if (_this.pendingSubkey == pendingLongpress) {
_this.pendingSubkey = null;
}
if (subkeyPopup) {
// Clear key preview if any
_this.showKeyTip(null, false);
_this.subkeyGesture = subkeyPopup;
subkeyPopup.promise.then(function (keyEvent: text.KeyEvent) {
// Allow active cancellation, even if the source should allow passive.
// It's an easy and cheap null guard.
if (keyEvent) {
PreProcessor.raiseKeyEvent(keyEvent);
}
_this.clearPopup();
});
}
});
}
}
/**
* Updates all currently-pending and activated gestures.
*
* @param currentKey The key currently underneath the most recent touch coordinate
* @param previousKey The previously-selected key
* @param input The current mouse or touch coordinate for the gesture
* @returns true if should fully capture input, false if input should 'fall through'.
*/
updateGestures(currentKey: KeyElement, previousKey: KeyElement, input: InputEventCoordinate): boolean {
let key0 = previousKey;
let key1 = currentKey;
if(!currentKey && this.pendingMultiTap) {
this.pendingMultiTap.cancel();
this.pendingMultiTap = null;
}
// Clear previous key highlighting, allow subkey controller to highlight as appropriate.
if (this.subkeyGesture) {
if (key0) {
key0.key.highlight(false);
}
this.subkeyGesture.updateTouch(input);
this.keyPending = null;
this.touchPending = null;
return true;
}
this.currentTarget = null;
// If popup is visible, need to move over popup, not over main keyboard
// Could be turned into a browser-longpress specific implementation within browser.PendingLongpress?
if (key1 && key1['subKeys'] != null && this.initTouchCoord) {
// Show popup keys immediately if touch moved up towards key array (KMEW-100, Build 353)
if ((this.initTouchCoord.y - input.y > 5) && this.pendingSubkey && this.pendingSubkey instanceof browser.PendingLongpress) {
this.pendingSubkey.resolve();
}
}
// If there is an active popup menu (which can occur from the previous block),
// a subkey popup exists; do not allow base key output.
if (this.subkeyGesture || this.pendingSubkey) {
return true;
}
return false;
}
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.startHide(true);
let active = keyman.domManager.activeElement;
if (dom.Utils.instanceof(active, "TouchAliasElement")) {
(active as dom.TouchAliasElement).hideCaret();
}
keyman.domManager.lastActiveElement = null;
}
}
};
/**
* 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) {
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;
}
let sk = this.subkeyGesture;
let popup = (sk && sk.isVisible());
// If popup keys are active, do not show the key tip.
on = popup ? false : on;
tip.show(key, on, this);
};
/**
* Create a key preview element for phone devices
*/
createKeyTip() {
let keyman = com.keyman.singleton;
if (this.device.formFactor == 'phone') {
if (this.keytip == null) {
// For now, should only be true (in production) when keyman.isEmbedded == true.
let constrainPopup = keyman.isEmbedded;
this.keytip = new browser.KeyTip(constrainPopup);
}
// Always append to _Box (since cleared during OSK Load)
if (this.keytip && this.keytip.element) {
keyman.osk._Box.appendChild(this.keytip.element);
}
}
};
/**
* 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);
}
if(this.inputEngine) {
this.inputEngine.unregisterEventHandlers();
}
}
}
}