spiegel-keyman/windows/src/developer/TIKE/xml/layoutbuilder/builder.js
Marc Durdin 0764305211 fix(developer): detect changes in dropdowns in builder
Fixes #6386.

The touch layout editor would not reliably detect changes if using the
mouse to select an item in a dropdown 'autocomplete'.
2022-03-21 17:06:38 +11:00

2324 lines
70 KiB
JavaScript

$(function () {
window.builder = this;
(function(search) {
var q = search.match(/Filename=(.+)(&|$)/);
if(q) {
builder.filename = decodeURIComponent(q[1]);
}
})(location.search);
this.undoStack = [];
this.redoStack = [];
this.xscale = 1;
this.yscale = 1;
this.uniqId = 1;
this.standardKeyNames = [
'K_?00', // &H0
'K_LBUTTON', // &H1
'K_RBUTTON', // &H2
'K_CANCEL', // &H3
'K_MBUTTON', // &H4
'K_?05', // &H5
'K_?06', // &H6
'K_?07', // &H7
'K_BKSP', // &H8
'K_TAB', // &H9
'K_?0A', // &HA
'K_?0B', // &HB
'K_KP5', // &HC
'K_ENTER', // &HD
'K_?0E', // &HE
'K_?0F', // &HF
'K_SHIFT', // &H10
'K_CONTROL', // &H11
'K_ALT', // &H12
'K_PAUSE', // &H13
'K_CAPS', // &H14
'K_KANJI?15', // &H15
'K_KANJI?16', // &H16
'K_KANJI?17', // &H17
'K_KANJI?18', // &H18
'K_KANJI?19', // &H19
'K_?1A', // &H1A
'K_ESC', // &H1B
'K_KANJI?1C', // &H1C
'K_KANJI?1D', // &H1D
'K_KANJI?1E', // &H1E
'K_KANJI?1F', // &H1F
'K_SPACE', // &H20
'K_PGUP', // &H21
'K_PGDN', // &H22
'K_END', // &H23
'K_HOME', // &H24
'K_LEFT', // &H25
'K_UP', // &H26
'K_RIGHT', // &H27
'K_DOWN', // &H28
'K_SEL', // &H29
'K_PRINT', // &H2A
'K_EXEC', // &H2B
'K_PRTSCN', // &H2C
'K_INS', // &H2D
'K_DEL', // &H2E
'K_HELP', // &H2F
'K_0', // &H30
'K_1', // &H31
'K_2', // &H32
'K_3', // &H33
'K_4', // &H34
'K_5', // &H35
'K_6', // &H36
'K_7', // &H37
'K_8', // &H38
'K_9', // &H39
'K_?3A', // &H3A
'K_?3B', // &H3B
'K_?3C', // &H3C
'K_?3D', // &H3D
'K_?3E', // &H3E
'K_?3F', // &H3F
'K_?40', // &H40
'K_A', // &H41
'K_B', // &H42
'K_C', // &H43
'K_D', // &H44
'K_E', // &H45
'K_F', // &H46
'K_G', // &H47
'K_H', // &H48
'K_I', // &H49
'K_J', // &H4A
'K_K', // &H4B
'K_L', // &H4C
'K_M', // &H4D
'K_N', // &H4E
'K_O', // &H4F
'K_P', // &H50
'K_Q', // &H51
'K_R', // &H52
'K_S', // &H53
'K_T', // &H54
'K_U', // &H55
'K_V', // &H56
'K_W', // &H57
'K_X', // &H58
'K_Y', // &H59
'K_Z', // &H5A
'K_?5B', // &H5B
'K_?5C', // &H5C
'K_?5D', // &H5D
'K_?5E', // &H5E
'K_?5F', // &H5F
'K_NP0', // &H60
'K_NP1', // &H61
'K_NP2', // &H62
'K_NP3', // &H63
'K_NP4', // &H64
'K_NP5', // &H65
'K_NP6', // &H66
'K_NP7', // &H67
'K_NP8', // &H68
'K_NP9', // &H69
'K_NPSTAR', // &H6A
'K_NPPLUS', // &H6B
'K_SEPARATOR', // &H6C
'K_NPMINUS', // &H6D
'K_NPDOT', // &H6E
'K_NPSLASH', // &H6F
'K_F1', // &H70
'K_F2', // &H71
'K_F3', // &H72
'K_F4', // &H73
'K_F5', // &H74
'K_F6', // &H75
'K_F7', // &H76
'K_F8', // &H77
'K_F9', // &H78
'K_F10', // &H79
'K_F11', // &H7A
'K_F12', // &H7B
'K_F13', // &H7C
'K_F14', // &H7D
'K_F15', // &H7E
'K_F16', // &H7F
'K_F17', // &H80
'K_F18', // &H81
'K_F19', // &H82
'K_F20', // &H83
'K_F21', // &H84
'K_F22', // &H85
'K_F23', // &H86
'K_F24', // &H87
'K_?88', // &H88
'K_?89', // &H89
'K_?8A', // &H8A
'K_?8B', // &H8B
'K_?8C', // &H8C
'K_?8D', // &H8D
'K_?8E', // &H8E
'K_?8F', // &H8F
'K_NUMLOCK', // &H90
'K_SCROLL', // &H91
'K_?92', // &H92
'K_?93', // &H93
'K_?94', // &H94
'K_?95', // &H95
'K_?96', // &H96
'K_?97', // &H97
'K_?98', // &H98
'K_?99', // &H99
'K_?9A', // &H9A
'K_?9B', // &H9B
'K_?9C', // &H9C
'K_?9D', // &H9D
'K_?9E', // &H9E
'K_?9F', // &H9F
'K_?A0', // &HA0
'K_?A1', // &HA1
'K_?A2', // &HA2
'K_?A3', // &HA3
'K_?A4', // &HA4
'K_?A5', // &HA5
'K_?A6', // &HA6
'K_?A7', // &HA7
'K_?A8', // &HA8
'K_?A9', // &HA9
'K_?AA', // &HAA
'K_?AB', // &HAB
'K_?AC', // &HAC
'K_?AD', // &HAD
'K_?AE', // &HAE
'K_?AF', // &HAF
'K_?B0', // &HB0
'K_?B1', // &HB1
'K_?B2', // &HB2
'K_?B3', // &HB3
'K_?B4', // &HB4
'K_?B5', // &HB5
'K_?B6', // &HB6
'K_?B7', // &HB7
'K_?B8', // &HB8
'K_?B9', // &HB9
'K_COLON', // &HBA
'K_EQUAL', // &HBB
'K_COMMA', // &HBC
'K_HYPHEN', // &HBD
'K_PERIOD', // &HBE
'K_SLASH', // &HBF
'K_BKQUOTE', // &HC0
'K_?C1', // &HC1
'K_?C2', // &HC2
'K_?C3', // &HC3
'K_?C4', // &HC4
'K_?C5', // &HC5
'K_?C6', // &HC6
'K_?C7', // &HC7
'K_?C8', // &HC8
'K_?C9', // &HC9
'K_?CA', // &HCA
'K_?CB', // &HCB
'K_?CC', // &HCC
'K_?CD', // &HCD
'K_?CE', // &HCE
'K_?CF', // &HCF
'K_?D0', // &HD0
'K_?D1', // &HD1
'K_?D2', // &HD2
'K_?D3', // &HD3
'K_?D4', // &HD4
'K_?D5', // &HD5
'K_?D6', // &HD6
'K_?D7', // &HD7
'K_?D8', // &HD8
'K_?D9', // &HD9
'K_?DA', // &HDA
'K_LBRKT', // &HDB
'K_BKSLASH', // &HDC
'K_RBRKT', // &HDD
'K_QUOTE', // &HDE
'K_oDF', // &HDF
'K_oE0', // &HE0
'K_oE1', // &HE1
'K_oE2', // &HE2
'K_oE3', // &HE3
'K_oE4', // &HE4
'K_?E5', // &HE5
'K_oE6', // &HE6
'K_?E7', // &HE7
'K_?E8', // &HE8
'K_oE9', // &HE9
'K_oEA', // &HEA
'K_oEB', // &HEB
'K_oEC', // &HEC
'K_oED', // &HED
'K_oEE', // &HEE
'K_oEF', // &HEF
'K_oF0', // &HF0
'K_oF1', // &HF1
'K_oF2', // &HF2
'K_oF3', // &HF3
'K_oF4', // &HF4
'K_oF5', // &HF5
'K_?F6', // &HF6
'K_?F7', // &HF7
'K_?F8', // &HF8
'K_?F9', // &HF9
'K_?FA', // &HFA
'K_?FB', // &HFB
'K_?FC', // &HFC
'K_?FD', // &HFD
'K_?FE', // &HFE
'K_?FF' // &HFF
];
this.standardKeyCaps = [
['',''], // 'K_?00', // &H0
['',''], // 'K_LBUTTON', // &H1
['',''], // 'K_RBUTTON', // &H2
['',''], // 'K_CANCEL', // &H3
['',''], // 'K_MBUTTON', // &H4
['',''], // 'K_?05', // &H5
['',''], // 'K_?06', // &H6
['',''], // 'K_?07', // &H7
['',''], // 'K_BKSP', // &H8
['',''], // 'K_TAB', // &H9
['',''], // 'K_?0A', // &HA
['',''], // 'K_?0B', // &HB
['',''], // 'K_KP5', // &HC
['',''], // 'K_ENTER', // &HD
['',''], // 'K_?0E', // &HE
['',''], // 'K_?0F', // &HF
['',''], // 'K_SHIFT', // &H10
['',''], // 'K_CONTROL', // &H11
['',''], // 'K_ALT', // &H12
['',''], // 'K_PAUSE', // &H13
['',''], // 'K_CAPS', // &H14
['',''], // 'K_KANJI?15', // &H15
['',''], // 'K_KANJI?16', // &H16
['',''], // 'K_KANJI?17', // &H17
['',''], // 'K_KANJI?18', // &H18
['',''], // 'K_KANJI?19', // &H19
['',''], // 'K_?1A', // &H1A
['',''], // 'K_ESC', // &H1B
['',''], // 'K_KANJI?1C', // &H1C
['',''], // 'K_KANJI?1D', // &H1D
['',''], // 'K_KANJI?1E', // &H1E
['',''], // 'K_KANJI?1F', // &H1F
[' ',' '], // 'K_SPACE', // &H20
['',''], // 'K_PGUP', // &H21
['',''], // 'K_PGDN', // &H22
['',''], // 'K_END', // &H23
['',''], // 'K_HOME', // &H24
['',''], // 'K_LEFT', // &H25
['',''], // 'K_UP', // &H26
['',''], // 'K_RIGHT', // &H27
['',''], // 'K_DOWN', // &H28
['',''], // 'K_SEL', // &H29
['',''], // 'K_PRINT', // &H2A
['',''], // 'K_EXEC', // &H2B
['',''], // 'K_PRTSCN', // &H2C
['',''], // 'K_INS', // &H2D
['',''], // 'K_DEL', // &H2E
['',''], // 'K_HELP', // &H2F
['0',')'], // 'K_0', // &H30
['1','!'], // 'K_1', // &H31
['2','@'], // 'K_2', // &H32
['3','#'], // 'K_3', // &H33
['4','$'], // 'K_4', // &H34
['5','%'], // 'K_5', // &H35
['6','^'], // 'K_6', // &H36
['7','&'], // 'K_7', // &H37
['8','*'], // 'K_8', // &H38
['9','('], // 'K_9', // &H39
['',''], // 'K_?3A', // &H3A
['',''], // 'K_?3B', // &H3B
['',''], // 'K_?3C', // &H3C
['',''], // 'K_?3D', // &H3D
['',''], // 'K_?3E', // &H3E
['',''], // 'K_?3F', // &H3F
['',''], // 'K_?40', // &H40
['a','A'], // 'K_A', // &H41
['b','B'], // 'K_B', // &H42
['c','C'], // 'K_C', // &H43
['d','D'], // 'K_D', // &H44
['e','E'], // 'K_E', // &H45
['f','F'], // 'K_F', // &H46
['g','G'], // 'K_G', // &H47
['h','H'], // 'K_H', // &H48
['i','I'], // 'K_I', // &H49
['j','J'], // 'K_J', // &H4A
['k','K'], // 'K_K', // &H4B
['l','L'], // 'K_L', // &H4C
['m','M'], // 'K_M', // &H4D
['n','N'], // 'K_N', // &H4E
['o','O'], // 'K_O', // &H4F
['p','P'], // 'K_P', // &H50
['q','Q'], // 'K_Q', // &H51
['r','R'], // 'K_R', // &H52
['s','S'], // 'K_S', // &H53
['t','T'], // 'K_T', // &H54
['u','U'], // 'K_U', // &H55
['v','V'], // 'K_V', // &H56
['w','W'], // 'K_W', // &H57
['x','X'], // 'K_X', // &H58
['y','Y'], // 'K_Y', // &H59
['z','Z'], // 'K_Z', // &H5A
['',''], // 'K_?5B', // &H5B
['',''], // 'K_?5C', // &H5C
['',''], // 'K_?5D', // &H5D
['',''], // 'K_?5E', // &H5E
['',''], // 'K_?5F', // &H5F
['',''], // 'K_NP0', // &H60
['',''], // 'K_NP1', // &H61
['',''], // 'K_NP2', // &H62
['',''], // 'K_NP3', // &H63
['',''], // 'K_NP4', // &H64
['',''], // 'K_NP5', // &H65
['',''], // 'K_NP6', // &H66
['',''], // 'K_NP7', // &H67
['',''], // 'K_NP8', // &H68
['',''], // 'K_NP9', // &H69
['',''], // 'K_NPSTAR', // &H6A
['',''], // 'K_NPPLUS', // &H6B
['',''], // 'K_SEPARATOR', // &H6C
['',''], // 'K_NPMINUS', // &H6D
['',''], // 'K_NPDOT', // &H6E
['',''], // 'K_NPSLASH', // &H6F
['',''], // 'K_F1', // &H70
['',''], // 'K_F2', // &H71
['',''], // 'K_F3', // &H72
['',''], // 'K_F4', // &H73
['',''], // 'K_F5', // &H74
['',''], // 'K_F6', // &H75
['',''], // 'K_F7', // &H76
['',''], // 'K_F8', // &H77
['',''], // 'K_F9', // &H78
['',''], // 'K_F10', // &H79
['',''], // 'K_F11', // &H7A
['',''], // 'K_F12', // &H7B
['',''], // 'K_F13', // &H7C
['',''], // 'K_F14', // &H7D
['',''], // 'K_F15', // &H7E
['',''], // 'K_F16', // &H7F
['',''], // 'K_F17', // &H80
['',''], // 'K_F18', // &H81
['',''], // 'K_F19', // &H82
['',''], // 'K_F20', // &H83
['',''], // 'K_F21', // &H84
['',''], // 'K_F22', // &H85
['',''], // 'K_F23', // &H86
['',''], // 'K_F24', // &H87
['',''], // 'K_?88', // &H88
['',''], // 'K_?89', // &H89
['',''], // 'K_?8A', // &H8A
['',''], // 'K_?8B', // &H8B
['',''], // 'K_?8C', // &H8C
['',''], // 'K_?8D', // &H8D
['',''], // 'K_?8E', // &H8E
['',''], // 'K_?8F', // &H8F
['',''], // 'K_NUMLOCK', // &H90
['',''], // 'K_SCROLL', // &H91
['',''], // 'K_?92', // &H92
['',''], // 'K_?93', // &H93
['',''], // 'K_?94', // &H94
['',''], // 'K_?95', // &H95
['',''], // 'K_?96', // &H96
['',''], // 'K_?97', // &H97
['',''], // 'K_?98', // &H98
['',''], // 'K_?99', // &H99
['',''], // 'K_?9A', // &H9A
['',''], // 'K_?9B', // &H9B
['',''], // 'K_?9C', // &H9C
['',''], // 'K_?9D', // &H9D
['',''], // 'K_?9E', // &H9E
['',''], // 'K_?9F', // &H9F
['',''], // 'K_?A0', // &HA0
['',''], // 'K_?A1', // &HA1
['',''], // 'K_?A2', // &HA2
['',''], // 'K_?A3', // &HA3
['',''], // 'K_?A4', // &HA4
['',''], // 'K_?A5', // &HA5
['',''], // 'K_?A6', // &HA6
['',''], // 'K_?A7', // &HA7
['',''], // 'K_?A8', // &HA8
['',''], // 'K_?A9', // &HA9
['',''], // 'K_?AA', // &HAA
['',''], // 'K_?AB', // &HAB
['',''], // 'K_?AC', // &HAC
['',''], // 'K_?AD', // &HAD
['',''], // 'K_?AE', // &HAE
['',''], // 'K_?AF', // &HAF
['',''], // 'K_?B0', // &HB0
['',''], // 'K_?B1', // &HB1
['',''], // 'K_?B2', // &HB2
['',''], // 'K_?B3', // &HB3
['',''], // 'K_?B4', // &HB4
['',''], // 'K_?B5', // &HB5
['',''], // 'K_?B6', // &HB6
['',''], // 'K_?B7', // &HB7
['',''], // 'K_?B8', // &HB8
['',''], // 'K_?B9', // &HB9
[';',':'], // 'K_COLON', // &HBA
['=','+'], // 'K_EQUAL', // &HBB
[',','<'], // 'K_COMMA', // &HBC
['-','_'], // 'K_HYPHEN', // &HBD
['.','>'], // 'K_PERIOD', // &HBE
['/','?'], // 'K_SLASH', // &HBF
['`','~'], // 'K_BKQUOTE', // &HC0
['',''], // 'K_?C1', // &HC1
['',''], // 'K_?C2', // &HC2
['',''], // 'K_?C3', // &HC3
['',''], // 'K_?C4', // &HC4
['',''], // 'K_?C5', // &HC5
['',''], // 'K_?C6', // &HC6
['',''], // 'K_?C7', // &HC7
['',''], // 'K_?C8', // &HC8
['',''], // 'K_?C9', // &HC9
['',''], // 'K_?CA', // &HCA
['',''], // 'K_?CB', // &HCB
['',''], // 'K_?CC', // &HCC
['',''], // 'K_?CD', // &HCD
['',''], // 'K_?CE', // &HCE
['',''], // 'K_?CF', // &HCF
['',''], // 'K_?D0', // &HD0
['',''], // 'K_?D1', // &HD1
['',''], // 'K_?D2', // &HD2
['',''], // 'K_?D3', // &HD3
['',''], // 'K_?D4', // &HD4
['',''], // 'K_?D5', // &HD5
['',''], // 'K_?D6', // &HD6
['',''], // 'K_?D7', // &HD7
['',''], // 'K_?D8', // &HD8
['',''], // 'K_?D9', // &HD9
['',''], // 'K_?DA', // &HDA
['[','{'], // 'K_LBRKT', // &HDB
['\\','|'], // 'K_BKSLASH', // &HDC
[']','}'], // 'K_RBRKT', // &HDD
['\'','"'], // 'K_QUOTE', // &HDE
['',''], // 'K_oDF', // &HDF
['',''], // 'K_oE0', // &HE0
['',''], // 'K_oE1', // &HE1
['\\','|'], // 'K_oE2', // &HE2
['',''], // 'K_oE3', // &HE3
['',''], // 'K_oE4', // &HE4
['',''], // 'K_?E5', // &HE5
['',''], // 'K_oE6', // &HE6
['',''], // 'K_?E7', // &HE7
['',''], // 'K_?E8', // &HE8
['',''], // 'K_oE9', // &HE9
['',''], // 'K_oEA', // &HEA
['',''], // 'K_oEB', // &HEB
['',''], // 'K_oEC', // &HEC
['',''], // 'K_oED', // &HED
['',''], // 'K_oEE', // &HEE
['',''], // 'K_oEF', // &HEF
['',''], // 'K_oF0', // &HF0
['',''], // 'K_oF1', // &HF1
['',''], // 'K_oF2', // &HF2
['',''], // 'K_oF3', // &HF3
['',''], // 'K_oF4', // &HF4
['',''], // 'K_oF5', // &HF5
['',''], // 'K_?F6', // &HF6
['',''], // 'K_?F7', // &HF7
['',''], // 'K_?F8', // &HF8
['',''], // 'K_?F9', // &HF9
['',''], // 'K_?FA', // &HFA
['',''], // 'K_?FB', // &HFB
['',''], // 'K_?FC', // &HFC
['',''], // 'K_?FD', // &HFD
['',''], // 'K_?FE', // &HFE
['',''], // 'K_?FF' // &HFF
];
this.lookupKeyNames = [];
for (var i = 0; i < this.standardKeyNames.length; i++) {
if (this.standardKeyNames[i].indexOf('?') < 0) {
this.lookupKeyNames.push(this.standardKeyNames[i]);
}
}
this.lookupKeyNames.sort();
// Defines the PUA code mapping for the various 'special' modifier/control keys on keyboards.
// This is lifted directly from visualKeyboard.ts and must be kept in sync. See also CompileKeymanWeb.pas: CSpecialText10
this.specialCharacters = {
'*Shift*': 8,
'*Enter*': 5,
'*Tab*': 6,
'*BkSp*': 4,
'*Menu*': 11,
'*Hide*': 10,
'*Alt*': 25,
'*Ctrl*': 1,
'*Caps*': 3,
'*ABC*': 16,
'*abc*': 17,
'*123*': 19,
'*Symbol*': 21,
'*Currency*': 20,
'*Shifted*': 8, // set SHIFTED->9 for filled arrow icon
'*AltGr*': 2,
'*TabLeft*': 7,
'*LAlt*': 0x56,
'*RAlt*': 0x57,
'*LCtrl*': 0x58,
'*RCtrl*': 0x59,
'*LAltCtrl*': 0x60,
'*RAltCtrl*': 0x61,
'*LAltCtrlShift*': 0x62,
'*RAltCtrlShift*': 0x63,
'*AltShift*': 0x64,
'*CtrlShift*': 0x65,
'*AltCtrlShift*': 0x66,
'*LAltShift*': 0x67,
'*RAltShift*': 0x68,
'*LCtrlShift*': 0x69,
'*RCtrlShift*': 0x70,
// Following codes introduced in Keyman 14.0
'*RTLEnter*': 0x71,
'*RTLBkSp*': 0x72,
'*ShiftLock*': 0x73,
'*ShiftedLock*': 0x74,
'*ZWNJ*': 0x75, // * If this one is specified, auto-detection will kick in.
'*ZWNJiOS*': 0x75, // The iOS version will be used by default, but the
'*ZWNJAndroid*': 0x76, // Android platform has its own default glyph.
'*LTREnter*': 5, // * Forces LTR shaping for the Enter/BkSp keys regardless
'*LTRBkSp*': 4, // of layout's directionality.
};
this.specialKeyNames = Object.entries(this.specialCharacters).map(ch => ch[0]);
this.presentations = {
"tablet-ipad-landscape": { "x": 829, "y": 299, "name": "iPad (landscape)" }, // 829x622 = iPad tablet box size; (97,101)-(926,723)
"tablet-ipad-portrait": { "x": 605, "y": 300, "name": "iPad (portrait)" }, // 605x806 = iPad tablet box size; (98,94)-(703,900)
"phone-iphone5-landscape": { "x": 731, "y": 196, "name": "iPhone 5 (landscape)" }, // 731x412 = iPhone box size; (144,39)-(875,451)
"phone-iphone5-portrait": { "x": 526, "y": 266, "name": "iPhone 5 (portrait)"} // 528x936 = iPhone box size; (90,204)-(618,1040)
};
this.keyMargin = 15;
// from kmwosk.js:
this.modifierCodes = {
"LCTRL":0x0001,
"RCTRL":0x0002,
"LALT":0x0004,
"RALT":0x0008,
"SHIFT":0x0010,
"CTRL":0x0020,
"ALT":0x0040,
"CAPS":0x0100,
"NO_CAPS":0x0200
/*"NUM_LOCK":0x0400, We don't support these in the designer
"NO_NUM_LOCK":0x0800,
"SCROLL_LOCK":0x1000,
"NO_SCROLL_LOCK":0x2000,
"VIRTUAL_KEY":0x4000*/
};
// Lists the combinations that we allow users to use. Some are mutually exclusive,
// such as Left+Right modifiers, or chiral and non-chiral modifiers (excl. Shift)
this.validModifierCombinations = [
0,
this.modifierCodes.LCTRL,
this.modifierCodes.RCTRL,
this.modifierCodes.LALT,
this.modifierCodes.LCTRL | this.modifierCodes.LALT,
this.modifierCodes.RALT,
this.modifierCodes.RCTRL | this.modifierCodes.RALT,
this.modifierCodes.SHIFT,
this.modifierCodes.LCTRL | this.modifierCodes.SHIFT,
this.modifierCodes.LALT | this.modifierCodes.SHIFT,
this.modifierCodes.LCTRL | this.modifierCodes.LALT | this.modifierCodes.SHIFT,
this.modifierCodes.RCTRL | this.modifierCodes.SHIFT,
this.modifierCodes.RALT | this.modifierCodes.SHIFT,
this.modifierCodes.RCTRL | this.modifierCodes.RALT | this.modifierCodes.SHIFT,
this.modifierCodes.CTRL,
this.modifierCodes.CTRL | this.modifierCodes.SHIFT,
this.modifierCodes.ALT,
this.modifierCodes.CTRL | this.modifierCodes.ALT,
this.modifierCodes.SHIFT | this.modifierCodes.ALT,
this.modifierCodes.SHIFT | this.modifierCodes.CTRL | this.modifierCodes.ALT,
this.modifierCodes.CAPS
];
this.minimalModifierCombinations = [
0,
this.modifierCodes.RALT,
this.modifierCodes.SHIFT,
this.modifierCodes.RALT | this.modifierCodes.SHIFT,
this.modifierCodes.CAPS
];
this.showAllModifierCombinations = false;
// Add CAPS and NO_CAPS variants for all of the above
(function(c, codes) {
var i, n = c.length;
for(i = 0; i < n; i++)
c.push(c[i] | codes.CAPS);
for(i = 0; i < n; i++)
c.push(c[i] | codes.NO_CAPS);
})(this.validModifierCombinations, this.modifierCodes);
this.modifierNames = [
'leftctrl', // 0x001
'rightctrl', // 0x002
'leftalt', // 0x004
'rightalt', // 0x008
'shift', // 0x010
'ctrl', // 0x020
'alt', // 0x040
'', // 0x080 - reserved
'caps', // 0x100
'nocaps' // 0x200
];
/**
* Get kebab-cased full name of a modifier combination given a bitmask
*
* @param {Number} c
* @return {string}
**/
this.getModifierCombinationName = function(c) {
var r = '';
if(c == 0) return 'default';
for(var i = 0; i < this.modifierNames.length; i++) {
if(c & (1<<i)) r += (r == '' ? '' : '-') + this.modifierNames[i];
}
return r;
};
this.getPresentation = function () {
var platform = $('#selPlatformPresentation').val();
//if(platform == 'tablet') return 'tablet-ipad';
//if(platform == 'phone') return 'phone-iphone5';
return platform;
}
$('#selPlatformPresentation').change(function () {
builder.selectKey(null, false);
builder.selectSubKey(null);
builder.prepareLayer();
builder.saveState();
});
$('#chkShowAllModifierOptions').click(function () {
builder.showAllModifierCombinations = $('#chkShowAllModifierOptions')[0].checked;
builder.fillModifierSelect();
builder.prepareKey();
});
this.preparePlatforms = function () {
var firstPlatform = null;
$('#platforms').empty();
for (var platform in KVKL) {
firstPlatform = firstPlatform || platform;
var ul = document.createElement('ul');
var li = document.createElement('li');
var a = document.createElement('a');
$(a).data('platform', platform).text(platform).click(function () {
builder.selectPlatform($(this).data('platform'));
}).attr('href', 'javascript:void(0)');
$(li).append(a).append(ul);
$('#platforms').append(li);
for (var layer in KVKL[platform].layer) {
li = document.createElement('li');
$(li).data('platform', platform).data('layer', layer).text(KVKL[platform].layer[layer].id).click(function () {
builder.selectPlatform($(this).data('platform'));
builder.selectLayer($(this).data('layer'));
});
$(ul).append(li);
}
}
$('#selPlatform option').remove();
for (var platform in KVKL) {
var opt = document.createElement('option');
$(opt).append(platform);
$('#selPlatform').append(opt);
}
builder.selectPlatform(firstPlatform);
}
/**
* Replace default key names by special font codes for modifier keys (copied from kmwosk.js)
*
* @param {string} oldText
* @return {string}
**/
this.renameSpecialKey = function (oldText) {
//Note: U+E000 *is* PUA but was not accepted by IE as a character in the EOT font, so Alt recoded as U+E019
return this.specialCharacters[oldText] ?
String.fromCharCode(0xE000 + this.specialCharacters[oldText]) :
oldText;
}
this.prepareLayers = function () {
this.fillLayerSelect();
this.fillModifierSelect();
};
this.fillLayerSelect = function() {
$('#selLayer option').remove();
$('#selKeyNextLayer option').remove();
$('#selSubKeyNextLayer option').remove();
opt = document.createElement('option');
$(opt).attr('value', '').text('(none)');
$('#selKeyNextLayer').append(opt);
opt = document.createElement('option');
$(opt).attr('value', '').text('(none)');
$('#selSubKeyNextLayer').append(opt);
for (var layer in KVKL[builder.lastPlatform].layer) {
var opt = document.createElement('option');
$(opt).attr('value', layer)
.append(KVKL[builder.lastPlatform].layer[layer].id);
$('#selLayer').append(opt);
opt = document.createElement('option');
$(opt).append(KVKL[builder.lastPlatform].layer[layer].id);
$('#selKeyNextLayer').append(opt);
opt = document.createElement('option');
$(opt).append(KVKL[builder.lastPlatform].layer[layer].id);
$('#selSubKeyNextLayer').append(opt);
}
};
this.fillModifierSelect = function() {
var modifiers = this.showAllModifierCombinations ? this.validModifierCombinations : this.minimalModifierCombinations;
var
$selKeyLayerOverride = $('#selKeyLayerOverride'),
$selSubKeyLayerOverride = $('#selSubKeyLayerOverride'),
$addLayerList = $('#addLayerList');
var add = function(e, val, text) {
var opt = document.createElement('option');
if(typeof text != 'undefined') {
$(opt).attr('value', val);
$(opt).text(text);
} else {
$(opt).text(val);
}
e.append(opt);
};
$('#selKeyLayerOverride option').remove();
$('#selSubKeyLayerOverride option').remove();
$('#addLayerList option').remove();
add($selKeyLayerOverride, '', '(current layer)');
add($selSubKeyLayerOverride, '', '(current layer)');
add($addLayerList, '', '(custom)');
for (var modifier = 0; modifier < modifiers.length; modifier++) {
var name = this.getModifierCombinationName(modifiers[modifier]);
add($selKeyLayerOverride, name);
add($selSubKeyLayerOverride, name);
add($addLayerList, name);
}
var alreadyAdded = function(modifier) {
return builder.minimalModifierCombinations.indexOf(modifier) >= 0;
};
// check all keys for modifier usage
var modifierNames = [];
for(var i = 0; i < KVKL[builder.lastPlatform].layer.length; i++) {
var layer = KVKL[builder.lastPlatform].layer[i];
for(var j = 0; j < layer.row.length; j++) {
var row = layer.row[j];
for(var k = 0; k < row.key.length; k++) {
if(typeof row.key[k].layer != 'undefined' && modifierNames.indexOf(row.key[k].layer) < 0) {
modifierNames.push(row.key[k].layer);
}
if(typeof row.key[k].sk != 'undefined') {
for(var l = 0; l < row.key[k].sk.length; l++) {
if(typeof row.key[k].sk[l].layer != 'undefined' && modifierNames.indexOf(row.key[k].sk[l].layer) < 0) {
modifierNames.push(row.key[k].sk[l].layer);
}
}
}
}
}
}
var isUsed = function(modifierName) {
return modifierNames.indexOf(modifierName) >= 0;
};
if(!this.showAllModifierCombinations) {
// Add any layer names that are already referenced
for(modifier = 0; modifier < this.validModifierCombinations.length; modifier++) {
var name = this.getModifierCombinationName(this.validModifierCombinations[modifier]);
if(!alreadyAdded(this.validModifierCombinations[modifier]) && isUsed(name)) {
add($selKeyLayerOverride, name);
add($selSubKeyLayerOverride, name);
}
}
}
};
this.getModifierCombinationFromLayerId = function(id) {
for(var i = 0; i < this.validModifierCombinations.length; i++) {
if(this.getModifierCombinationName(this.validModifierCombinations[i]) == id) {
return this.validModifierCombinations[i];
}
}
return 0;
};
this.isLayerIdShifted = function(id) {
return (builder.getModifierCombinationFromLayerId(id) & this.modifierCodes.SHIFT) != 0;
};
this.prepareLayer = function () {
var layer = KVKL[builder.lastPlatform].layer[builder.lastLayerIndex];
var isLayerShifted = builder.isLayerIdShifted(layer.id);
var width = 0; // calculate the widest and rescale
for (var i = 0; i < layer.row.length; i++) {
var row = builder.addRow('bottom'), rowWidth = 0;
for (var j = 0; j < layer.row[i].key.length; j++) {
var key = layer.row[i].key[j];
rowWidth += (key.width ? parseInt(key.width, 10) : 100) + (key.pad ? parseInt(key.pad, 10) : builder.keyMargin);
}
width = Math.max(width, rowWidth);
}
width += builder.keyMargin; // add right hand margin
var height = layer.row.length * (100 + builder.keyMargin); // 50% of tablet height, 100 px per row, 5 px margin
//
// Scaling for different platform images
//
var pres = this.presentations[builder.getPresentation()];
if (pres) {
this.xscale = pres.x / width;
this.yscale = pres.y / height;
} else {
this.xscale = 0.5;
this.yscale = 0.5;
}
$('#kbd,#sk')
.css('font-family', KVKL[builder.lastPlatform].font)
.css('font-size', KVKL[builder.lastPlatform].fontsize || "1em");
$('#inpKeyCap,#inpSubKeyCap')
.css('font-family', KVKL[builder.lastPlatform].font)
.css('font-size', '18pt');
$('#kbd div').remove();
for (var i = 0; i < layer.row.length; i++) {
var row = builder.addRow('bottom');
var calcKeyWidth = 0, calcGapWidth = 0;
for (var j = 0; j < layer.row[i].key.length; j++) {
var key = layer.row[i].key[j];
var nkey = builder.addKey(row, false, key.sp);
var w = key.width ? key.width : 100;
var p = (key.pad ? key.pad : builder.keyMargin) * this.xscale;
if (key.sk) $(nkey).addClass('hasSubKeyArray');
var code = key.id ? String.fromCharCode(parseInt(key.id.substring(2), 16)) : 0;
var text = typeof key.text == 'string' ? key.text : (code == '' || code.charCodeAt(0) < 32 ? '' : code);
calcKeyWidth += parseInt(w, 10);
calcGapWidth += parseInt(key.pad ? key.pad : builder.keyMargin, 10);
$(nkey)
.data('id', key.id)
.data('pad', key.pad)
.data('width', key.width)
.data('sp', key.sp)
.data('font', key.font)
.data('fontsize', key.fontsize)
.data('nextlayer', key.nextlayer)
.data('layer', key.layer)
.data('text', text)
.data('sk', key.sk)
.css('width', (w * this.xscale) + 'px')
.css('height', (100 * this.yscale) + 'px')
.css('margin-top', (builder.keyMargin * this.yscale) + 'px')
.css('margin-left', p + 'px')
.css('font-family', key.font)
.css('font-size', key.fontsize);
if(this.specialCharacters[text])
$(nkey).addClass('key-special-text');
$('.text', nkey).text(this.renameSpecialKey(text));
if(KVKL[builder.lastPlatform].displayUnderlying) $('.underlying', nkey).text(this.getStandardKeyCap(key.id, key.layer ? builder.isLayerIdShifted(key.layer) : isLayerShifted));
builder.updateKeyId(nkey);
}
var calcWidth = calcKeyWidth + calcGapWidth;
$('.key-size', row).html(layer.row[i].key.length + ' keys<br>' +
calcKeyWidth + ' key width<br>' +
calcGapWidth + ' padding<br>' +
calcWidth + ' total');
$('#kbd').attr('class', builder.getPresentation()); //builder.lastPlatform); //css('background-position-y', '-200px');
}
var div = document.createElement('div');
$(div).css('clear', 'both');
$('#kbd').append(div);
};
this.getStandardKeyCap = function (id, shifted) {
id = id ? id.toUpperCase() : '';
var i = this.standardKeyNames.findIndex(function(x) { return x.toUpperCase() == id });
return i >= 0 ? this.standardKeyCaps[i][shifted ? 1 : 0] : '';
};
this.updateKeyId = function (nkey) {
$('.id', nkey).text(($(nkey).data('layer') || '') + ' ' + $(nkey).data('id'));
};
this.selectPlatform = function (val) {
builder.lastPlatform = val || $('#selPlatform').val();
var listContainer = $('#selPlatformPresentation');
$('option', listContainer).remove();
for (var i in this.presentations) {
if (i.substring(0, builder.lastPlatform.length) != builder.lastPlatform) {
continue;
}
var option = $(document.createElement('option'));
option.attr('value', i).text(this.presentations[i].name);
listContainer.append(option);
}
$('#chkDisplayUnderlying')[0].checked = KVKL[builder.lastPlatform].displayUnderlying;
builder.prepareLayers();
builder.selectLayer(0);
}
this.selectLayer = function (val) {
if(val) $('#selLayer').val(val);
builder.lastLayerIndex = $('#selLayer').val();
builder.prepareLayer();
builder.selectKey($('#kbd > div.row > div.key')[0]);
}
this.selectLayerByName = function (name) {
var layerOption = $('#selLayer option').filter(function (index) { return $(this).text() === name; });
if (layerOption.length == 0) {
alert('Layer ' + name + ' not found.');
} else {
builder.selectLayer(layerOption.attr('value'));
}
}
this.undo = function () {
if (builder.undoStack.length == 0) {
return;
}
builder.saveUndo(1);
var s = builder.undoStack.pop();
this.loadUndo(s);
}
this.redo = function () {
if (builder.redoStack.length == 0) {
return;
}
builder.saveUndo(2);
var s = builder.redoStack.pop();
this.loadUndo(s);
}
this.loadUndo = function (s) {
KVKL = JSON.parse(s.KVKL);
builder.preparePlatforms();
builder.enableUndoControls();
$('#selPlatform').val(s.platform);
builder.selectPlatform();
$('#selLayer').val(s.layer);
builder.selectLayer();
builder.selectKey($('#kbd .key').filter(function (index) { return $(this).data('id') === s.id; }).first());
if (s.subkey) builder.selectSubKey($('#sk .key').filter(function (index) { return $(this).data('id') === s.subkey; }).first());
}
this.saveUndo = function (saveToRedo) {
if (!saveToRedo) {
builder.redoStack = [];
}
builder.generate(true,false);
var s = {
KVKL: JSON.stringify(KVKL),
platform: builder.lastPlatform,
layer: builder.lastLayerIndex,
key: builder.selectedKey().data('id')
};
var key = builder.selectedSubKey();
if (key.length > 0) {
s.subkey = $(key).data('id');
}
var stack = (saveToRedo == 1 ? builder.redoStack : builder.undoStack);
stack.push(s);
if (stack.length > 100) {
stack.shift();
}
builder.enableUndoControls();
builder.command('modified');
}
this.commands = [];
this.command = function (cmd) {
if (navigator.userAgent.indexOf('Keyman') < 0) {
return false;
}
// queue commands to build a single portmanteau command when we return to idle
if (builder.commands.length == 0) {
window.setTimeout(function() {
try {
location.href = 'keyman:command?' + builder.commands.reduce(function(a,v) { return a + '&' + v; });
builder.commands = [];
} catch (e) {
// ignore errors - saves weirdness happening when testing outside TIKE.exe
}
}, 10);
}
builder.commands.push(cmd);
}
this.enableUndoControls = function () {
if (builder.undoStack.length == 0) {
builder.command('undo-disable');
$('#btnUndo').attr('disabled', 'disabled');
} else {
builder.command('undo-enable');
$('#btnUndo').removeAttr('disabled');
}
if (builder.redoStack.length == 0) {
builder.command('redo-disable');
$('#btnRedo').attr('disabled', 'disabled');
} else {
builder.command('redo-enable');
$('#btnRedo').removeAttr('disabled');
}
}
this.addRow = function (position) {
var row = document.createElement('div');
$(row).addClass('row');
var parentRow = builder.selectedKey().parent();
if (position == 'below') {
$(parentRow).after(row);
} else if (position == 'above') {
$(parentRow).before(row);
} else {
$('#kbd').append(row);
}
var rowKeySize = document.createElement('div');
$(rowKeySize).addClass('key-size');
$(rowKeySize).text('15');
$(row).append(rowKeySize);
return row;
};
this.addKey = function (position, isSubKey, sp) {
var key = document.createElement('div');
var ktext = document.createElement('div');
var kid = document.createElement('div');
var kunderlying = document.createElement('div');
$(kid).addClass('id');
$(ktext).addClass('text');
$(kunderlying).addClass('underlying');
$(key).append(kid);
$(key).append(ktext);
$(key).append(kunderlying);
$(key).addClass('key');
$(key).data('id', 'T_new_' + this.uniqId);
builder.uniqId++;
builder.updateKeyId(key);
if (isSubKey) {
$(key).click(function (event) {
event.stopPropagation();
builder.selectSubKey(this);
if (builder.lastFocus) {
$(builder.lastFocus).focus().select();
}
}).dblclick(function (event) {
event.stopPropagation();
var layer = $(this).data('nextlayer');
if (layer) builder.selectLayerByName(layer);
});
$(kid).click(function (event) {
event.stopPropagation();
builder.selectSubKey(key);
$('#inpSubKeyName').focus().select();
});
} else {
$(key).click(function (event) {
event.stopPropagation();
builder.selectKey(this);
if (builder.lastFocus) {
$(builder.lastFocus).focus().select();
}
}).dblclick(function (event) {
event.stopPropagation();
var layer = $(this).data('nextlayer');
if (layer) builder.selectLayerByName(layer);
});
$(kid).click(function (event) {
event.stopPropagation();
builder.selectKey(key);
$('#inpKeyName').focus().select();
});
}
if (position == 'before') {
builder.selectedKey().before(key);
} else if (position == 'after') {
builder.selectedKey().after(key);
} else if (position == 'before-subkey') {
builder.selectedSubKey().before(key);
} else if (position == 'after-subkey') {
builder.selectedSubKey().after(key);
} else {
$(position).append(key);
}
$(key).draggable({
revert: "invalid",
stack: ".key",
zIndex: 100,
helper: "clone",
//appendTo: "body",
start: function (event, ui) {
var isSubKey = ($(this).parent().attr('id') == 'sk');
if (isSubKey)
builder.selectSubKey(null);
else
builder.selectKey(null, false);
var parent = isSubKey ? '#sk' : '#kbd';
var drag = this;
drag.overList = [];
var pos = $(this).position();
$(this).addClass('key-dragging');
$(parent + ' .key').before(function (index) {
if (this == drag) return '';
if ($(this).prev()[0] == drag) return '<div class="key-droppable key-current"></div>';
return '<div class="key-droppable"></div>';
});
$(isSubKey ? '#sk' : '.row').append('<div class="key-droppable"></div>');
$('.key-droppable').css('margin-top', $(this).css('margin-top')).css('height', $(this).css('height')).droppable({
accept: ".key",
tolerance: "touch",
over: function (event, ui) {
drag.overList.push(this);
$(drag.overList[0]).addClass('key-droppable-hover');
},
out: function (event, ui) {
var n = drag.overList.indexOf(this);
if (n >= 0)
drag.overList.splice(n, 1);
$(this).removeClass('key-droppable-hover');
if (drag.overList.length > 0) $(drag.overList[0]).addClass('key-droppable-hover');
},
drop: function (event, ui) {
//
// Drop the selected key into its new position
//
builder.saveUndo();
$(drag).detach().removeClass('key-dragging');
$(drag.overList[0]).after(drag);
//builder.selectKey(drag);
if (!isSubKey) builder.rescale(); else builder.generateSubKeys();
builder.post();
}
});
$('.key-current').css('width', $(drag).width() + 'px').css('margin-left', -($(drag).width()) + 'px');
},
stop: function (event, ui) {
$('.key-droppable').remove();
$(this).removeClass('key-dragging');
//builder.rescale();
}
});
builder.formatKey(key, sp);
return key;
};
this.formatKey = function (key, sp) {
$(key)
.removeClass('key-modifier')
.removeClass('key-modifier-selected')
.removeClass('key-deadkey')
.removeClass('key-blank')
.removeClass('key-spacer');
if (sp) {
switch (parseInt(sp, 10)) {
case 1: $(key).addClass('key-modifier'); break;
case 2: $(key).addClass('key-modifier-selected'); break;
case 8: $(key).addClass('key-deadkey'); break;
case 9: $(key).addClass('key-blank'); break;
case 10: $(key).addClass('key-spacer'); break;
}
}
}
this.delSelectedKey = function () {
var key = builder.selectedKey();
var nextKey = $(key).next('.key');
if (nextKey.length == 0) nextKey = $(key).prev('.key');
if (nextKey.length == 0) {
return this.delSelectedRow();
}
$(key).remove();
builder.selectKey(nextKey);
}
this.delSelectedRow = function () {
var parentRow = builder.selectedKey().parent();
var nextKey = $(parentRow).next().children('.key').first();
if (nextKey.length == 0) {
nextKey = $(parentRow).prev().children('.key').first();
}
if (nextKey.length == 0) {
return;
}
builder.selectKey(nextKey);
$(parentRow).remove();
}
this.selectSubKey = function (key) {
builder.selectedSubKey().removeClass('selected');
if (key) {
$(key).addClass('selected');
$('.skcontrol.wedge-horz,.skcontrol.wedge-vert,input#inpSubKeyCap').css('display', 'block');
$('div#btnDelSubKey').css('display', ($('#sk > div').length > 1) ? 'block' : '');
var offset = $(key).offset();
$('#wedgeAddSubKeyLeft').offset({ left: offset.left - 9, top: offset.top + $(key).outerHeight() + 2 });
$('#wedgeAddSubKeyRight').offset({ left: offset.left + $(key).outerWidth() - 7, top: offset.top + $(key).outerHeight() + 2 });
$('div#btnDelSubKey').offset({ left: offset.left + $(key).outerWidth() - 14, top: offset.top + 3 });
$('input#inpSubKeyCap').offset({ left: offset.left + 16, top: offset.top + 4 }).width($(key).width() - 32);
builder.lastFocus = $('input#inpSubKeyCap');
this.prepareSubKey();
builder.enableSubKeyControls();
} else {
$('.skcontrol.wedge-horz,.skcontrol.wedge-vert,div#btnDelSubKey,input#inpSubKeyCap').css('display', '');
builder.enableSubKeyControls();
}
builder.saveState();
}
this.selectKey = function (key, adjustResizable) {
if (arguments.length == 1 || adjustResizable)
builder.selectedKey().resizable('destroy');
$('.skcontrol.wedge-horz,.skcontrol.wedge-vert,div#btnDelSubKey,input#inpSubKeyCap').css('display', '');
builder.selectedKey().removeClass('selected');
if (key && $(key).length) {
$(key).addClass('selected');
$('.kcontrol.wedge-horz,.kcontrol.wedge-vert,div#btnDelKey,input#inpKeyCap').css('display', 'block');
var rowOffset = $(key).parent().offset();
var offset = $(key).offset();
$('#wedgeAddRowAbove').offset({ left: rowOffset.left - 18, top: rowOffset.top - 7 });
$('#wedgeAddRowBelow').offset({ left: rowOffset.left - 18, top: rowOffset.top + $(key).parent().outerHeight() - 7 });
$('#wedgeAddKeyLeft').offset({ left: offset.left - 9, top: offset.top + $(key).outerHeight() + 2 });
$('#wedgeAddKeyRight').offset({ left: offset.left + $(key).outerWidth() - 7, top: offset.top + $(key).outerHeight() + 2 });
$('div#btnDelKey').offset({ left: offset.left + $(key).outerWidth() - 14, top: offset.top + 3 });
$('input#inpKeyCap').offset({ left: offset.left + 16, top: offset.top + 4 }).width($(key).width() - 32);
this.prepareKey();
builder.lastFocus = $('input#inpKeyCap');
} else {
$('.kcontrol.wedge-horz,.kcontrol.wedge-vert,div#btnDelKey,input#inpKeyCap').css('display', '');
$('#sk div').remove();
builder.enableKeyControls();
builder.enableSubKeyControls();
}
builder.saveState();
};
this.selectedKey = function () {
return $('#kbd .selected');
}
this.wrapChange = function(f, opt) {
return function() {
if(typeof opt == 'object' && opt.saveOnce) {
if (!builder.hasSavedKeyUndo) {
builder.saveUndo();
builder.hasSavedKeyUndo = true;
} else {
builder.command('modified');
}
} else {
builder.saveUndo();
}
var r = f.apply(this);
if(typeof opt == 'object' && opt.rescale) {
builder.rescale();
}
builder.post();
return r;
}
};
const inpKeyPaddingChange = builder.wrapChange(function () {
builder.selectedKey().data('pad', $(this).val())
.css('margin-left', $(this).val() + 'px');
}, {rescale: true});
$('#inpKeyPadding')
.change(inpKeyPaddingChange)
.on('input', inpKeyPaddingChange);
const inpKeyWidthChange = builder.wrapChange(function () {
builder.selectedKey().data('width', $(this).val())
.css('width', parseInt($(this).val(), 10) * this.xscale + 'px');
}, {rescale: true});
$('#inpKeyWidth')
.change(inpKeyWidthChange)
.on('input', inpKeyWidthChange);
const inpKeyNameChange = builder.wrapChange(function () {
builder.selectedKey().data('id', $(this).val());
builder.updateKeyId(builder.selectedKey());
}, {saveOnce: true});
const wrapInstant = function(f) {
return function() {
window.setTimeout(f.bind(this), 0);
}
}
$('#inpKeyName')
.change(inpKeyNameChange)
.autocomplete({
source: builder.lookupKeyNames,
change: inpKeyNameChange,
select: wrapInstant(inpKeyNameChange)
})
.on('input', inpKeyNameChange)
.blur(function () {
builder.hasSavedKeyUndo = false;
});
this.updateCharacterMap = function (val, fromSubKey) {
// Update character map
var src = $(fromSubKey ? '#inpSubKeyCap' : '#inpKeyCap')[0];
var x1 = src.selectionStart;
var x2 = src.selectionEnd;
if (x2 != x1 || x1 > 0) {
var ch = val.charCodeAt(x2 - 1);
if (x2 > 1 && ch >= 0xDC00 && ch < 0xE000) {
// yep, surrogate pairs again!
var chHigh = val.charCodeAt(x2 - 2), chLow = ch;
ch = ((chHigh - 0xD800) << 10) + (chLow - 0xDC00) + 0x10000;
}
builder.command('selected-char,' + ch.toString());
}
}
const inpKeyCapChange = builder.wrapChange(function (e) {
const val = $(this).val();
var k = builder.selectedKey();
$('.text', k).text(builder.renameSpecialKey(val));
k.data('text', val);
if(builder.specialCharacters[val]) {
k.addClass('key-special-text');
} else {
k.removeClass('key-special-text');
}
builder.updateCharacterMap(val, false);
}, {saveOnce: true});
$('#inpKeyCap')
.on('input', inpKeyCapChange)
.change(inpKeyCapChange)
.autocomplete({
source: builder.specialKeyNames,
change: inpKeyCapChange,
select: wrapInstant(inpKeyCapChange)
})
.mouseup(function () {
builder.updateCharacterMap($(this).val(), false);
}).focus(function () {
builder.updateCharacterMap($(this).val(), false);
}).blur(function () {
builder.hasSavedKeyUndo = false;
});
const selKeyTypeChange = builder.wrapChange(function () {
var sp = $(this).val();
if (sp == 0) {
builder.selectedKey().removeData('sp');
} else {
builder.selectedKey().data('sp', $(this).val());
}
builder.formatKey(builder.selectedKey(), $(this).val());
});
$('#selKeyType').change(selKeyTypeChange);
const selKeyNextLayerChange = builder.wrapChange(function () {
$(this).val() === '' ?
builder.selectedKey().removeData('nextlayer') :
builder.selectedKey().data('nextlayer', $(this).val());
});
$('#selKeyNextLayer').change(selKeyNextLayerChange);
const selKeyLayerOverrideChange = builder.wrapChange(function () {
$(this).val() === '' ?
builder.selectedKey().removeData('layer') :
builder.selectedKey().data('layer', $(this).val());
builder.updateKeyId(builder.selectedKey());
});
$('#selKeyLayerOverride').change(selKeyLayerOverrideChange);
this.prepareKey = function () {
builder.enableKeyControls();
var key = builder.selectedKey();
builder.hasSavedKeyUndo = false;
$('#inpKeyCap').val($(key).data('text'));
$('#inpKeyName').val($(key).data('id'));
$('#inpKeyWidth').val($(key).data('width'));
$('#inpKeyPadding').val($(key).data('pad'));
$('#selKeyType').val($(key).data('sp') ? $(key).data('sp') : 0);
$('#selKeyNextLayer').val($(key).data('nextlayer'));
$('#selKeyLayerOverride').val($(key).data('layer'));
$(key).resizable({
minWidth: 10 * this.xscale,
minHeight: 100 * this.yscale,
maxWidth: 500 * this.xscale,
maxHeight: 100 * this.yscale,
ghost: false,
handles: "e",
resize: function (event, ui) {
builder.selectedKey().data('width', Math.round(ui.size.width / builder.xscale, 0));
$('#inpKeyWidth').val(Math.round(ui.size.width / builder.xscale, 0));
},
start: function (event, ui) {
builder.saveUndo();
$(ui.originalElement).css('position', 'relative');
$(ui.originalElement).css('left', '');
},
stop: function (event, ui) {
builder.rescale();
builder.post();
}
});
//
// Prepare sub-key array
//
$('#sk div').remove();
var skContainer = $('#sk');
var sk = $(key).data('sk');
if (typeof sk != 'object') {
$(key).removeClass('hasSubKeyArray');
builder.enableSubKeyControls();
return;
}
$(key).addClass('hasSubKeyArray');
for (var i = 0; i < sk.length; i++) {
var nkey = builder.addKey(skContainer, true);
var key = sk[i];
var code = key.id ? String.fromCharCode(parseInt(key.id.substring(2), 16)) : 0;
var text = typeof key.text == 'string' ? key.text : (code.charCodeAt(0) < 32 ? '' : code);
$(nkey)
.data('text', text)
.data('id', key.id)
.data('sp', key.sp)
.data('font', key.font)
.data('fontsize', key.fontsize)
.data('nextlayer', key.nextlayer)
.data('layer', key.layer)
.css('width', (100 * 0.7) + 'px')
.css('font-family', KVKL[builder.lastPlatform].font);
if(this.specialCharacters[text])
$(nkey).addClass('key-special-text');
$('.text', nkey).text(this.renameSpecialKey(text));
builder.updateKeyId(nkey);
}
//var div = document.createElement('div'); $(div).css('clear', 'both'); $('#sk').append(div);
builder.selectSubKey($('#sk > div')[0]);
builder.prepareSubKey();
builder.enableSubKeyControls();
}
this.enableKeyControls = function () {
var key = builder.selectedKey();
if (key.length == 0) {
$('#keyToolbar *').attr('disabled', 'disabled');
$('#btnAddSubKeyArray').css('display', '');
$('#btnDelSubKeyArray').css('display', 'none');
} else {
$('#keyToolbar *').removeAttr('disabled');
if (builder.selectedKey().hasClass('hasSubKeyArray')) {
$('#btnAddSubKeyArray').css('display', 'none');
$('#btnDelSubKeyArray').css('display', '');
} else {
$('#btnAddSubKeyArray').css('display', '');
$('#btnDelSubKeyArray').css('display', 'none');
}
}
}
this.enableSubKeyControls = function () {
var key = builder.selectedSubKey();
if (key.length == 0) {
$('#subKeyToolbar').css('visibility', 'hidden'); // attr('disabled', 'disabled');
} else {
$('#subKeyToolbar').css('visibility', ''); // removeAttr('disabled');
}
}
this.rescale = function () {
builder.saveUndo();
var keyId = builder.selectedKey().data('id'); //$('#kbd .key')
builder.prepareLayer();
if (keyId !== null)
builder.selectKey($('#kbd .key').filter(function (index) { return $(this).data('id') === keyId; }).first());
};
//
// Popup keys
//
this.addSubKeyArray = function () {
var key = builder.selectedKey();
if ($(key).data('sk')) return;
var sk = [{ id: 'T_new_' + builder.uniqId}];
builder.uniqId++;
$(key).data('sk', sk);
builder.prepareKey();
}
this.delSubKeyArray = function () {
var key = builder.selectedKey();
$(key).removeData('sk');
builder.prepareKey();
builder.selectSubKey(null);
builder.enableSubKeyControls();
}
this.delSelectedSubKey = function () {
var key = builder.selectedSubKey();
if (key.length == 0) return;
var nextKey = $(key).next('.key');
if (nextKey.length == 0) nextKey = $(key).prev('.key');
if (nextKey.length == 0) return;
$(key).remove();
builder.selectSubKey(nextKey);
};
this.selectedSubKey = function () {
return $('#sk .selected');
};
this.generateSubKeys = function () {
var key = builder.selectedKey();
var sk = [];
var keys = $('#sk > div.key').filter(':not(.ui-draggable-dragging)');
for (var i = 0; i < keys.length; i++) {
sk.push({
text: $(keys[i]).data('text'),
id: $(keys[i]).data('id'),
sp: $(keys[i]).data('sp'),
font: $(keys[i]).data('font'),
fontsize: $(keys[i]).data('fontsize'),
nextlayer: $(keys[i]).data('nextlayer'),
layer: $(keys[i]).data('layer')
});
}
key.data('sk', sk);
}
this.prepareSubKey = function () {
var key = builder.selectedSubKey();
$('#inpSubKeyCap').val($(key).data('text'));
$('#inpSubKeyName').val($(key).data('id'));
$('#selSubKeyType').val($(key).data('sp') ? $(key).data('sp') : 0);
$('#selSubKeyNextLayer').val($(key).data('nextlayer'));
$('#selSubKeyLayerOverride').val($(key).data('layer'));
}
var subKeyNameChange = builder.wrapChange(function () {
var nkey = builder.selectedSubKey();
if (nkey.length == 0) return;
nkey.data('id', $(this).val());
builder.generateSubKeys();
builder.updateKeyId(nkey);
}, {saveOnce: true});
$('#inpSubKeyName')
.change(subKeyNameChange)
.on('input', subKeyNameChange)
.autocomplete({
source: builder.lookupKeyNames,
change: subKeyNameChange,
select: wrapInstant(subKeyNameChange)
}).blur(function () {
builder.hasSavedKeyUndo = false;
});
builder.updateSubKeyCap = function (val) {
var k = builder.selectedSubKey();
if (k.length == 0) return;
$('.text', k).text(builder.renameSpecialKey(val));
k.data('text', val);
builder.updateCharacterMap(val, true);
builder.generateSubKeys();
};
const inpSubKeyCapChange = builder.wrapChange(function () {
builder.updateSubKeyCap($(this).val());
}, {saveOnce: true});
$('#inpSubKeyCap')
.change(inpSubKeyCapChange)
.autocomplete({
source: builder.specialKeyNames,
change: inpSubKeyCapChange,
select: wrapInstant(inpSubKeyCapChange)
})
.on('input', inpSubKeyCapChange)
.mouseup(function () {
builder.updateCharacterMap($(this).val(), false);
}).focus(function () {
builder.updateCharacterMap($(this).val(), false);
}).blur(function () {
builder.hasSavedSubKeyUndo = false;
});
const selSubKeyNextLayerChange = builder.wrapChange(function () {
$(this).val() === '' ?
builder.selectedSubKey().removeData('nextlayer') :
builder.selectedSubKey().data('nextlayer', $(this).val());
builder.generateSubKeys();
});
$('#selSubKeyNextLayer').change(selSubKeyNextLayerChange);
const selSubKeyLayerOverrideChange = builder.wrapChange(function () {
$(this).val() === '' ?
builder.selectedSubKey().removeData('layer') :
builder.selectedSubKey().data('layer', $(this).val());
builder.updateKeyId(builder.selectedSubKey());
builder.generateSubKeys();
});
$('#selSubKeyLayerOverride').change(selSubKeyLayerOverrideChange);
$('#wedgeAddSubKeyLeft').click(builder.wrapChange(function () {
builder.selectSubKey(builder.addKey('before-subkey', true));
builder.generateSubKeys();
}));
$('#wedgeAddSubKeyRight').click(builder.wrapChange(function () {
builder.selectSubKey(builder.addKey('after-subkey', true));
builder.generateSubKeys();
}));
$('#btnDelSubKey').click(builder.wrapChange(function () {
builder.delSelectedSubKey();
builder.generateSubKeys();
}));
const selSubKeyTypeChange = builder.wrapChange(function () {
var sp = $(this).val(), key = builder.selectedSubKey();
if (key.length == 0) return;
if (sp == 0) {
key.removeData('sp');
} else {
key.data('sp', $(this).val());
}
builder.formatKey(key, $(this).val());
builder.generateSubKeys();
});
$('#selSubKeyType').change(selSubKeyTypeChange);
this.generate = function (display, force) {
var json = JSON.stringify(KVKL, null, ' ');
if(!display) {
// Save changed settings -- only when not saving undo
KVKL[builder.lastPlatform].displayUnderlying = $('#chkDisplayUnderlying')[0].checked;
}
var layer = KVKL[builder.lastPlatform].layer[builder.lastLayerIndex];
layer.row = [];
var rows = $('#kbd > div.row'), n = 1;
$(rows).each(function () {
var row = { id: n++, key: [] };
$(this).children(".key").each(function () {
if ($(this).hasClass('ui-draggable-dragging')) return;
var key = { "id": $(this).data('id'), "text": $(this).data('text') };
key.pad = $(this).data('pad');
key.width = $(this).data('width');
key.sp = $(this).data('sp');
key.font = $(this).data('font');
key.fontsize = $(this).data('fontsize');
key.nextlayer = $(this).data('nextlayer');
key.layer = $(this).data('layer');
key.sk = $(this).data('sk');
row.key.push(key);
});
layer.row.push(row);
});
builder.saveJSON(json, force);
};
this.saveJSON = function(force) {
builder.removeEmptyRows();
var newJson = JSON.stringify(KVKL, null, ' ');
if(force || newJson != json) {
// When adding or deleting layers and platforms, we need to force because
// that will not result in changes to the rows.
$.ajax('/app/source/file', {
'type': 'POST',
'data': {
'Filename': builder.filename,
'Data': newJson
}
});
$('#data').val(newJson);
}
};
this.post = this.generate;
$('#wedgeAddRowAbove').click(builder.wrapChange(function () {
var row = builder.addRow('above'); builder.selectKey(builder.addKey(row, false));
}, {rescale: true}));
$('#wedgeAddRowBelow').click(builder.wrapChange(function () {
var row = builder.addRow('below'); builder.selectKey(builder.addKey(row, false));
}, {rescale: true}));
$('#wedgeAddKeyLeft').click(builder.wrapChange(function () {
builder.selectKey(builder.addKey('before', false));
}, {rescale: true}));
$('#wedgeAddKeyRight').click(builder.wrapChange(function () {
builder.selectKey(builder.addKey('after', false));
}, {rescale: true}));
$('#btnDelKey').click(builder.wrapChange(function () {
builder.delSelectedKey();
}, {rescale: true}));
$('#btnAddSubKeyArray').click(builder.wrapChange(function () {
builder.addSubKeyArray();
builder.selectSubKey($('#sk > div')[0]);
if (builder.lastFocus) builder.lastFocus.focus();
builder.enableKeyControls();
}));
$('#btnDelSubKeyArray').click(builder.wrapChange(function () {
builder.delSubKeyArray();
builder.enableKeyControls();
}));
$('#btnGenerate').click(function () { builder.generate(false,false); });
$('#selPlatform').change(function () {
if (builder.lastPlatform) builder.generate(false,false);
builder.selectPlatform();
builder.saveState();
});
$('#selLayer').change(function () {
if (builder.lastPlatform) builder.generate(false,false);
builder.selectLayer();
builder.saveState();
});
//
// Platform dialogs
//
$('#addPlatformDialog').dialog({
autoOpen: false,
height: 300,
width: 350,
modal: true,
buttons: {
"OK": function () {
builder.saveUndo();
var platform = $('#selAddPlatform').val();
KVKL[platform] = $.extend(true, {}, KVKL[builder.lastPlatform]); // copy existing platform
builder.preparePlatforms();
$('#selPlatform').val(platform);
builder.selectPlatform();
builder.generate(false,true);
$(this).dialog('close');
},
"Cancel": function () {
$(this).dialog('close');
}
}
});
$('#btnAddPlatform').click(function () {
$('#selAddPlatform option').remove();
var platforms = { 'tablet': 1, 'desktop': 1, 'phone': 1 };
for (var platform in KVKL) {
platforms[platform] = 0;
}
for (platform in platforms) {
if (platforms[platform]) {
var opt = document.createElement('option');
$(opt).text(platform);
$('#selAddPlatform').append(opt);
}
}
$('#addPlatformDialog').dialog('open')
});
$('#btnDelPlatform').click(function () {
if ($('#selPlatform option').length == 1) return;
builder.saveUndo();
var platform = $('#selPlatform').val();
delete KVKL[platform];
builder.lastPlatform = null;
builder.preparePlatforms();
builder.generate(false,true);
});
$('#chkDisplayUnderlying').click(function () {
builder.saveUndo();
var platform = $('#selPlatform').val();
builder.post();
builder.prepareLayer();
});
//
// Layer dialogs
//
$('#layerPropertiesDialog').dialog({
autoOpen: false,
height: 300,
width: 350,
modal: true,
buttons: {
"OK": function () {
var newLayerName = $('#layerName').val();
if (!newLayerName.match(/^[a-zA-Z0-9_-]+$/)) {
alert('Layer name must contain only alphanumerics, underscore and hyphen.');
return;
}
builder.saveUndo();
builder.generate();
var platform = KVKL[builder.lastPlatform];
var oldLayerName = platform.layer[builder.lastLayerIndex].id;
for (var i = 0; i < platform.layer.length; i++) {
var layer = platform.layer[i];
if (layer.row) {
for (var j = 0; j < layer.row.length; j++) {
var row = layer.row[j];
if (row.key) {
for (var k = 0; k < row.key.length; k++) {
var key = row.key[k];
if (key.layer == oldLayerName) {
key.layer = newLayerName;
}
if (key.nextlayer == oldLayerName) {
key.nextlayer = newLayerName;
}
if (key.sk) {
for (var l = 0; l < key.sk.length; l++) {
if (key.sk[l].layer == oldLayerName) {
key.sk[l].layer = newLayerName;
}
if (key.sk[l].nextlayer == oldLayerName) {
key.sk[l].nextlayer = newLayerName;
}
}
}
}
}
}
}
}
platform.layer[builder.lastLayerIndex].id = newLayerName;
builder.prepareLayers();
$('#selLayer').val(builder.lastLayerIndex);
builder.selectLayer();
builder.post();
$(this).dialog('close');
},
"Cancel": function () {
$(this).dialog('close');
}
}
});
$('#selectKeyDialog').dialog({
autoOpen: false,
height: 140,
width: 240,
modal: true,
buttons: {
"Cancel": function () {
builder.nextKeySelects = false;
$(this).dialog('close');
}
}
});
$('#addLayerName').on('input', function() {
var layerName = $(this).val();
$('#addLayerList').val(layerName);
if($('#addLayerList')[0].selectedIndex < 0) {
$('#addLayerList').val('(custom)');
$('#addLayerNote').text('');
} else {
$('#addLayerNote').text(layerName+' is a recognised modifier-aware layer name.');
}
});
$('#addLayerList').change(function() {
var v = $(this).val();
if(v == '(custom)') {
$('#addLayerName').val('');
$('#addLayerNote').text('');
} else {
$('#addLayerName').val(v);
$('#addLayerNote').text(v+' is a recognised modifier-aware layer name.');
}
});
$('#addLayerDialog').dialog({
autoOpen: false,
height: 300,
width: 350,
modal: true,
buttons: {
"OK": function () {
var id = $('#addLayerName').val();
if (!id.match(/^[a-zA-Z0-9_-]+$/)) {
alert('Layer name must contain only alphanumerics, underscore and hyphen.');
return;
}
for(var i = 0; i < KVKL[builder.lastPlatform].layer.length; i++) {
if(KVKL[builder.lastPlatform].layer[i].id == id) {
alert('Layer name must not already be in use for the current platform.');
return;
}
}
builder.saveUndo();
var layer = $.extend(true, {}, KVKL[builder.lastPlatform].layer[builder.lastLayerIndex]);
layer.id = id;
var n = KVKL[builder.lastPlatform].layer.push(layer) - 1;
builder.selectPlatform();
$('#selLayer').val(n);
builder.selectLayer();
builder.generate(false,true);
$(this).dialog('close');
},
"Cancel": function () {
$(this).dialog('close');
}
}
});
$('#btnAddLayer').click(function () {
$('#addLayerDialog').dialog('open');
});
$('#btnDelLayer').click(function () {
if ($('#selLayer option').length == 1) return;
builder.saveUndo();
KVKL[builder.lastPlatform].layer.splice(builder.lastLayerIndex, 1);
builder.selectPlatform();
builder.generate(false,true);
});
$('#btnEditLayer').click(function () {
$('#layerName').val(KVKL[builder.lastPlatform].layer[builder.lastLayerIndex].id);
$('#layerPropertiesDialog').dialog('open');
});
$('input').blur(function () {
builder.lastFocus = this;
});
$('input').focus(function () {
builder.lastFocus = this;
});
$('#btnUndo').click(function () {
builder.undo();
});
$('#btnRedo').click(function () {
builder.redo();
});
$('#btnTemplate').click(function () {
builder.command('template');
});
$('#btnImport').click(function () {
builder.command('import');
});
$('#kbd').click(function () {
builder.selectKey(null);
});
$('#sk').click(function () {
builder.selectSubKey(null);
});
builder.ctrlDown = false;
builder.nextKeySelects = false;
builder.selectKeyByCode = function (code) {
if (code >= 0 && code < 256) {
var keyName = builder.standardKeyNames[code];
var key = $('.key').filter(function (index) { return $(this).data('id') === keyName; });
if (key.length > 0) builder.selectKey(key[0]);
}
}
$(document).keydown(function (event) {
builder.ctrlDown = (event.which == 17);
if (builder.nextKeySelects) {
$('#selectKeyDialog').dialog('close');
event.preventDefault();
event.stopImmediatePropagation();
builder.nextKeySelects = false;
builder.selectKeyByCode(event.which);
}
});
$(document).keyup(function (event) {
if (builder.ctrlDown && event.which == 17) {
event.preventDefault();
event.stopImmediatePropagation();
builder.nextKeySelects = true;
$('#selectKeyDialog').dialog('open');
}
builder.ctrlDown = false;
});
//
// Character map drag+drop and double-click insertion
//
builder.charmapDragOver = function(o) {
// Convert X, Y to document coordinates
let target = document.elementFromPoint(o.x, o.y);
if(target === null || (target.nodeName != 'INPUT' && target.className != 'text')) {
return false;
}
return true;
};
builder.charmapDragDrop = function(o) {
// Convert X, Y to document coordinates
if(o.x >= 0 && o.y >= 0) {
var target = document.elementFromPoint(o.x, o.y);
if(target === null) {
return false;
}
if(target.nodeName == 'INPUT') {
target = $(target);
} else if(target.className == 'text') {
if(target.parentElement.parentElement.id == 'sk') {
builder.selectSubKey(target.parentElement);
target = document.lastFocus;
} else {
builder.selectKey(target.parentElement);
target = document.lastFocus;
}
} else {
return false;
}
} else {
// Double-click insertion, so use last focused control
var target = $(document.lastFocus);
}
// Focus the control and add the text
target.focus();
target.val(target.val() + o.text);
target.change();
};
builder.loadingState = true;
builder.saveState = function() {
if(builder.loadingState) return;
var state = {
platform: builder.lastPlatform,
layer: builder.lastLayerIndex,
presentation: $('#selPlatformPresentation').val()
};
var key = builder.selectedKey();
if(key.length > 0) {
state.key = key.data('id');
}
var subkey = builder.selectedSubKey();
if (subkey.length > 0) {
state.subkey = $(subkey).data('id');
}
$.post('/app/source/toucheditor/state', {
'Filename': builder.filename,
'State': JSON.stringify(state)
});
};
builder.loadState = function() {
$.get('/app/source/toucheditor/state',
{
'Filename': builder.filename
},
function(data) {
builder.loadingState = false;
if(typeof data === 'object') {
if(data.platform && KVKL[data.platform]) {
$('#selPlatform').val(data.platform);
}
builder.selectPlatform();
if(data.presentation && builder.presentations[data.presentation]) {
$('#selPlatformPresentation').val(data.presentation);
builder.prepareLayer();
}
if(data.layer && KVKL[builder.lastPlatform][data.layer]) {
$('#selLayer').val(data.layer);
builder.selectLayer();
}
if(data.key) {
builder.selectKey($('#kbd .key').filter(function (index) { return $(this).data('id') === data.key; }).first());
}
if(data.subkey) {
builder.selectSubKey($('#sk .key').filter(function (index) { return $(this).data('id') === data.subkey; }).first());
}
}
}
);
};
builder.removeEmptyRows = function() {
var json = JSON.stringify(KVKL, null, ' ');
for (var platform in KVKL) {
for (let layer of KVKL[platform].layer) {
let newRow = [], n = 1;
for(let row of layer.row) {
if(row.key.length > 0) {
row.id = n;
n++;
newRow.push(row);
}
}
layer.row = newRow;
}
}
};
builder.preparePlatforms();
builder.enableUndoControls();
builder.loadState();
});