From ddd6a06df150c0b2fa183830e88bc83d4f4422c6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 23 Sep 2019 10:32:28 +1000 Subject: [PATCH 01/16] [Developer] Icon indices in kmdev.wxs were incorrect for tike.exe after removal of legacy .kct and .kpp icons. Fixes #1619. --- windows/src/developer/inst/kmdev.wxs | 32 ++++++++-------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/windows/src/developer/inst/kmdev.wxs b/windows/src/developer/inst/kmdev.wxs index 3cd529f1b3..0d04f65f3f 100644 --- a/windows/src/developer/inst/kmdev.wxs +++ b/windows/src/developer/inst/kmdev.wxs @@ -121,8 +121,6 @@ - - @@ -131,50 +129,38 @@ - + - + - - - - - - - - - - - - - + - + - + - - + + - - + + From 645f9fb9c60ef1fd10a30dadd9673c7252c7227e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 23 Sep 2019 10:55:26 +1000 Subject: [PATCH 02/16] [Developer] Shift layer in basic touch layout template has incorrect modifier for period. Fixes #2062. --- .../layoutbuilder/template-basic.keyman-touch-layout | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout b/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout index 7660d99e28..967ba6e179 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout +++ b/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout @@ -115,7 +115,7 @@ {"id": "K_B","text":"B"}, {"id": "K_N","text":"N"}, {"id": "K_M","text":"M"}, - {"id": "K_PERIOD","text": ".","sk": [ + {"id": "K_PERIOD","text": ".", "layer": "default", "sk": [ {"text": ",","id": "K_COMMA", "layer": "default"}, {"text": "!","id": "K_1", "layer": "shift"}, {"text": "?","id": "K_SLASH", "layer": "shift"}, @@ -131,7 +131,7 @@ }, { "id": 4, - "key": [ + "key": [ {"id": "K_NUMLOCK","text": "*123*","width":"140","sp": "1","nextlayer": "numeric"}, {"id": "K_LOPT","text": "*Menu*","width": "120","sp": "1"}, {"id": "K_SPACE","text": "","width": "630","sp": "0"}, @@ -244,7 +244,7 @@ {"id": "K_J","text":"j"}, {"id": "K_K","text":"k"}, {"id": "K_L","text":"l"}, - {"text": "","width": "10","sp": "10"} + {"text": "","width": "10","sp": "10"} ] }, { @@ -313,7 +313,7 @@ {"id": "K_J","text":"J"}, {"id": "K_K","text":"K"}, {"id": "K_L","text":"L"}, - {"text": "","width": "10","sp": "10"} + {"text": "","width": "10","sp": "10"} ] }, { @@ -327,7 +327,7 @@ {"id": "K_B","text":"B"}, {"id": "K_N","text":"N"}, {"id": "K_M","text":"M"}, - {"id": "K_PERIOD","text": ".","sk": [ + {"id": "K_PERIOD","text": ".", "layer": "default", "sk": [ {"text": ",","id": "K_COMMA", "layer": "default"}, {"text": "!","id": "K_1", "layer": "shift"}, {"text": "?","id": "K_SLASH", "layer": "shift"}, From c19a0490838f8070d0fbd85959eae70fd4826cbe Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 23 Sep 2019 11:20:57 +1000 Subject: [PATCH 03/16] [Developer] Adding or deleting a platform or layer and making no other changes does not stick in touch layout editor. Fixes #1898. --- .../TIKE/xml/layoutbuilder/builder.js | 171 +++++++++--------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js index 0c3f87333e..0bd14e7ab3 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/builder.js +++ b/windows/src/developer/TIKE/xml/layoutbuilder/builder.js @@ -1,6 +1,6 @@ $(function () { window.builder = this; - + (function(search) { var q = search.match(/Filename=(.+)(&|$)/); if(q) { @@ -351,7 +351,7 @@ $(function () { ['',''], // 'K_?3E', // &H3E ['',''], // 'K_?3F', // &H3F ['',''], // 'K_?40', // &H40 - + ['a','A'], // 'K_A', // &H41 ['b','B'], // 'K_B', // &H42 ['c','C'], // 'K_C', // &H43 @@ -555,8 +555,8 @@ $(function () { ['',''], // 'K_?FE', // &HFE ['',''], // 'K_?FF' // &HFF ]; - - + + this.lookupKeyNames = []; for (var i = 0; i < this.standardKeyNames.length; i++) { @@ -595,49 +595,49 @@ $(function () { "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.minimalModifierCombinations = [ 0, this.modifierCodes.RALT, this.modifierCodes.SHIFT, this.modifierCodes.RALT | this.modifierCodes.SHIFT ]; - + this.showAllModifierCombinations = false; - + // Add CAPS and NO_CAPS variants for all of the above (function(c, codes) { var i, n = c.length; @@ -646,7 +646,7 @@ $(function () { for(i = 0; i < n; i++) c.push(c[i] | codes.NO_CAPS); })(this.validModifierCombinations, this.modifierCodes); - + this.modifierNames = [ 'leftctrl', // 0x001 'rightctrl', // 0x002 @@ -659,7 +659,7 @@ $(function () { 'caps', // 0x100 'nocaps' // 0x200 ]; - + /** * Get kebab-cased full name of a modifier combination given a bitmask * @@ -688,7 +688,7 @@ $(function () { builder.prepareLayer(); builder.saveState(); }); - + $('#chkShowAllModifierOptions').click(function () { builder.showAllModifierCombinations = $('#chkShowAllModifierOptions')[0].checked; builder.fillModifierSelect(); @@ -731,8 +731,8 @@ $(function () { /** * Replace default key names by special font codes for modifier keys (copied from kmwosk.js) * - * @param {string} oldText - * @return {string} + * @param {string} oldText + * @return {string} **/ this.renameSpecialKey = function (oldText) { @@ -752,12 +752,12 @@ $(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); @@ -781,15 +781,15 @@ $(function () { $('#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') { @@ -800,7 +800,7 @@ $(function () { } e.append(opt); }; - + $('#selKeyLayerOverride option').remove(); $('#selSubKeyLayerOverride option').remove(); $('#addLayerList option').remove(); @@ -808,24 +808,24 @@ $(function () { 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++) { @@ -844,11 +844,11 @@ $(function () { } } } - + 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++) { @@ -869,15 +869,15 @@ $(function () { } 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 isLayerShifted = builder.isLayerIdShifted(layer.id); var width = 0; // calculate the widest and rescale for (var i = 0; i < layer.row.length; i++) { @@ -967,7 +967,7 @@ $(function () { $(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 }); @@ -994,7 +994,7 @@ $(function () { } $('#chkDisplayUnderlying')[0].checked = KVKL[builder.lastPlatform].displayUnderlying; - + builder.prepareLayers(); builder.selectLayer(0); } @@ -1050,7 +1050,7 @@ $(function () { if (!saveToRedo) { builder.redoStack = []; } - builder.generate(true); + builder.generate(true,false); var s = { KVKL: JSON.stringify(KVKL), platform: builder.lastPlatform, @@ -1070,7 +1070,7 @@ $(function () { builder.enableUndoControls(); builder.command('modified'); } - + this.commands = []; this.command = function (cmd) { @@ -1127,7 +1127,7 @@ $(function () { return row; }; - + this.addKey = function (position, isSubKey, sp) { var key = document.createElement('div'); var ktext = document.createElement('div'); @@ -1354,12 +1354,12 @@ $(function () { this.selectedKey = function () { return $('#kbd .selected'); } - + this.wrapChange = function(f, opt) { return function() { if(typeof opt == 'object' && opt.saveOnce) { - if (!builder.hasSavedKeyUndo) { - builder.saveUndo(); + if (!builder.hasSavedKeyUndo) { + builder.saveUndo(); builder.hasSavedKeyUndo = true; } else { builder.command('modified'); @@ -1385,7 +1385,7 @@ $(function () { builder.selectedKey().data('width', $(this).val()) .css('width', parseInt($(this).val(), 10) * this.xscale + 'px'); }, {rescale: true})); - + $('#inpKeyName').change(builder.wrapChange(function () { builder.selectedKey().data('id', $(this).val()); builder.updateKeyId(builder.selectedKey()); @@ -1727,14 +1727,14 @@ $(function () { builder.generateSubKeys(); })); - this.generate = function (display) { + 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 = []; @@ -1758,7 +1758,9 @@ $(function () { }); var newJson = JSON.stringify(KVKL, null, ' '); - if(newJson != json) { + 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': { @@ -1771,7 +1773,7 @@ $(function () { }; this.post = this.generate; - + $('#wedgeAddRowAbove').click(builder.wrapChange(function () { var row = builder.addRow('above'); builder.selectKey(builder.addKey(row, false)); }, {rescale: true})); @@ -1804,16 +1806,16 @@ $(function () { builder.enableKeyControls(); })); - $('#btnGenerate').click(function () { builder.generate(); }); + $('#btnGenerate').click(function () { builder.generate(false,false); }); $('#selPlatform').change(function () { - if (builder.lastPlatform) builder.generate(); + if (builder.lastPlatform) builder.generate(false,false); builder.selectPlatform(); builder.saveState(); }); $('#selLayer').change(function () { - if (builder.lastPlatform) builder.generate(); + if (builder.lastPlatform) builder.generate(false,false); builder.selectLayer(); builder.saveState(); }); @@ -1830,7 +1832,6 @@ $(function () { buttons: { "OK": function () { builder.saveUndo(); - builder.generate(); var platform = $('#selAddPlatform').val(); KVKL[platform] = $.extend(true, {}, KVKL[builder.lastPlatform]); // copy existing platform @@ -1839,6 +1840,8 @@ $(function () { $('#selPlatform').val(platform); builder.selectPlatform(); + builder.generate(false,true); + $(this).dialog('close'); }, "Cancel": function () { @@ -1870,9 +1873,9 @@ $(function () { delete KVKL[platform]; builder.lastPlatform = null; builder.preparePlatforms(); - builder.post(); + builder.generate(false,true); }); - + $('#chkDisplayUnderlying').click(function () { builder.saveUndo(); var platform = $('#selPlatform').val(); @@ -1959,8 +1962,8 @@ $(function () { } } }); - - + + $('#addLayerName').on('input', function() { var layerName = $(this).val(); $('#addLayerList').val(layerName); @@ -1971,7 +1974,7 @@ $(function () { $('#addLayerNote').text(layerName+' is a recognised modifier-aware layer name.'); } }); - + $('#addLayerList').change(function() { var v = $(this).val(); if(v == '(custom)') { @@ -2002,14 +2005,13 @@ $(function () { } } builder.saveUndo(); - builder.generate(); 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.post(); + builder.generate(false,true); $(this).dialog('close'); }, "Cancel": function () { @@ -2027,7 +2029,7 @@ $(function () { builder.saveUndo(); KVKL[builder.lastPlatform].layer.splice(builder.lastLayerIndex, 1); builder.selectPlatform(); - builder.post(); + builder.generate(false,true); }); $('#btnEditLayer').click(function () { @@ -2102,23 +2104,23 @@ $(function () { // // 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; + + 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) { @@ -2141,42 +2143,42 @@ $(function () { // 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); }; - + 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', + $.get('/app/source/toucheditor/state', { 'Filename': builder.filename }, @@ -2197,7 +2199,7 @@ $(function () { } 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()); } @@ -2205,10 +2207,9 @@ $(function () { } ); }; - + builder.preparePlatforms(); builder.enableUndoControls(); - + builder.loadState(); }); - \ No newline at end of file From 7f075942224852001df80775d54ce7ec78fffad1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 23 Sep 2019 11:39:50 +1000 Subject: [PATCH 04/16] [Developer] Modal dialogs could appear behind the main form on Windows 7. Fixes #2040. --- windows/src/developer/TIKE/Tike.dpr | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/windows/src/developer/TIKE/Tike.dpr b/windows/src/developer/TIKE/Tike.dpr index 9053596aa9..b5b93d8c84 100644 --- a/windows/src/developer/TIKE/Tike.dpr +++ b/windows/src/developer/TIKE/Tike.dpr @@ -306,6 +306,7 @@ begin begin InitThemeLibrary; SetThemeAppProperties(STAP_ALLOW_NONCLIENT or STAP_ALLOW_CONTROLS or STAP_ALLOW_WEBCONTENT); + Application.MainFormOnTaskBar := True; Application.Initialize; // TStyleManager.TrySetStyle(FKeymanDeveloperOptions.DisplayTheme); Application.Title := 'Keyman Developer'; @@ -313,8 +314,8 @@ begin if TikeActive then Exit; InitClasses; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); - Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper); - ShowStartup; + Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper); + ShowStartup; Application.Run; end; finally From c343baad070d8fb03a342b650bae6ebe530da34a Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 08:54:57 +0700 Subject: [PATCH 05/16] Fixes #1791. Better subtag handling in Toolbar UI --- web/source/kmwuitoolbar.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/web/source/kmwuitoolbar.ts b/web/source/kmwuitoolbar.ts index d24b742efe..a62f55cae3 100644 --- a/web/source/kmwuitoolbar.ts +++ b/web/source/kmwuitoolbar.ts @@ -641,21 +641,27 @@ if(!window['keyman']['ui']['name']) { * @param {Object} kbd * @return {boolean} **/ - ui.selectKeyboard = function(event,lang,kbd) - { - if(ui.selectedLanguage) - { + ui.selectKeyboard = function(event,lang,kbd) { + if(ui.selectedLanguage) { var found = ui.findListedKeyboard(ui.selectedLanguage); - if(found != null) ui.listedKeyboards[found].buttonNode.className = 'kmw_button'; + if(found != null) { + ui.listedKeyboards[found].buttonNode.className = 'kmw_button'; + } } + ui.offButtonNode.className = 'kmw_button'; ui.selectedLanguage = lang; ui.selectedKeyboard = kbd; - ui.SelectedLanguage = lang.id; + + // In 12.0, this UI class has only been partially converted to BCP-47. + // `lang` refers to the base language identifier and will NOT include any subtags. + // We want the FULL language identifier here. + ui.SelectedLanguage = kbd.LanguageCode; + // Return focus to input area and activate the selected keyboard ui.setLastFocus(); //*****this seems out of sequence??? - ui.addKeyboardToList(lang,kbd); - keymanweb['setActiveKeyboard'](kbd['InternalName'],lang.id); + ui.addKeyboardToList(lang, kbd); + keymanweb['setActiveKeyboard'](kbd['InternalName'], kbd['LanguageCode']); ui.listedKeyboards[ui.findListedKeyboard(lang)].buttonNode.className = 'kmw_button_selected'; // Always save current state when selecting a keyboard From 96bf0ad162421ed5e48977eebbaac00044067084 Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 08:57:31 +0700 Subject: [PATCH 06/16] Comment enhancement --- web/source/kmwuitoolbar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/source/kmwuitoolbar.ts b/web/source/kmwuitoolbar.ts index a62f55cae3..53f20dca14 100644 --- a/web/source/kmwuitoolbar.ts +++ b/web/source/kmwuitoolbar.ts @@ -654,8 +654,8 @@ if(!window['keyman']['ui']['name']) { ui.selectedKeyboard = kbd; // In 12.0, this UI class has only been partially converted to BCP-47. - // `lang` refers to the base language identifier and will NOT include any subtags. - // We want the FULL language identifier here. + // `lang.id` refers to the base language identifier and will NOT include + // any subtags. We want the FULL language identifier here, with subtags. ui.SelectedLanguage = kbd.LanguageCode; // Return focus to input area and activate the selected keyboard From b304b3b1206b1c4d0083b7d0f53bcec790389502 Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 09:25:42 +0700 Subject: [PATCH 07/16] Enables bitcode generation for KMEI --- ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index e22c333427..5b6b15ba92 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -1524,6 +1524,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -1577,6 +1578,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; From ce5d6e70ce7ece7e506d4be0a7f546d4319d3c69 Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 09:56:07 +0700 Subject: [PATCH 08/16] Updates Firefox-check for keymapping. Fixes #1694 --- web/source/text/processor.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web/source/text/processor.ts b/web/source/text/processor.ts index 02e6b8d0e1..6e1c938329 100644 --- a/web/source/text/processor.ts +++ b/web/source/text/processor.ts @@ -320,7 +320,7 @@ namespace com.keyman.text { let kbdInterface = keyman.interface; let keyMapManager = keyman.keyMapManager; - if(!keyman.isEmbedded && !fromOSK && !window.event) { + if(!keyman.isEmbedded && !fromOSK && keyman.util.device.browser == 'firefox') { // I1466 - Convert the - keycode on mnemonic as well as positional layouts // FireFox, Mozilla Suite if(keyMapManager.browserMap.FF['k'+keyEvent.Lcode]) { @@ -411,8 +411,7 @@ namespace com.keyman.text { return true; } - - if(!keyman.isEmbedded && !fromOSK && !window.event) { + if(!keyman.isEmbedded && !fromOSK && keyman.util.device.browser == 'firefox') { // I1466 - Convert the - keycode on mnemonic as well as positional layouts // FireFox, Mozilla Suite if(keyMapManager.browserMap.FF['k'+keyEvent.Lcode]) { From a09bc84b9e7e12607a84e8171c30769fd637fcb1 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 23 Sep 2019 10:29:12 +0700 Subject: [PATCH 09/16] Use predictions toggle to enable/disable corrections toggle --- .../kmea/LanguageSettingsActivity.java | 41 ++++++++++++++----- .../src/main/res/drawable/textview_item.xml | 5 +++ .../src/main/res/layout/list_row_layout4.xml | 2 +- 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 android/KMEA/app/src/main/res/drawable/textview_item.xml diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java index 40ad5fb93f..5c1a2e3857 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java @@ -43,6 +43,8 @@ public final class LanguageSettingsActivity extends AppCompatActivity { private static Toolbar toolbar = null; private static ListView listView = null; private static TextView lexicalModelTextView = null; + private static TextView correctionsTextView = null; + private static SwitchCompat correctionsToggle = null; private ImageButton addButton = null; private String associatedLexicalModel = ""; private String lgCode; @@ -66,12 +68,25 @@ public final class LanguageSettingsActivity extends AppCompatActivity { @Override public void onClick(View v) { - SwitchCompat correctToggle = (SwitchCompat) v; + // For predictions/corrections toggle + SwitchCompat toggle = (SwitchCompat) v; + + SharedPreferences.Editor prefEditor = prefs.edit(); + + // predictionToggle overrides whether correction toggle and text are enabled + if (prefsKey.endsWith(predictionPrefSuffix)) { + boolean override = toggle.isChecked(); + if (correctionsTextView != null) { + correctionsTextView.setEnabled(override); + } + if (correctionsToggle != null) { + correctionsToggle.setEnabled(override); + } + } // This will allow preemptively making settings for languages without models. // Seems more trouble than it's worth to block this. - SharedPreferences.Editor prefEditor = prefs.edit(); - prefEditor.putBoolean(prefsKey, correctToggle.isChecked()); + prefEditor.putBoolean(prefsKey, toggle.isChecked()); prefEditor.apply(); // Don't use/apply language modeling settings for languages without models. @@ -136,21 +151,25 @@ public final class LanguageSettingsActivity extends AppCompatActivity { RelativeLayout layout = (RelativeLayout)findViewById(R.id.corrections_toggle); - textView = (TextView) layout.findViewById(R.id.text1); - textView.setText(getString(R.string.enable_corrections)); - SwitchCompat toggle = layout.findViewById(R.id.toggle); - toggle.setChecked(mayCorrect); // Link to persistent option storage! Also needs handler. + correctionsTextView = (TextView) layout.findViewById(R.id.text1); + correctionsTextView.setText(getString(R.string.enable_corrections)); + correctionsToggle = layout.findViewById(R.id.toggle); + correctionsToggle.setChecked(mayCorrect); // Link to persistent option storage! Also needs handler. String prefsKey = getLanguageCorrectionPreferenceKey(lgCode); - toggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode)); + correctionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode)); layout = (RelativeLayout)findViewById(R.id.predictions_toggle); textView = (TextView) layout.findViewById(R.id.text1); textView.setText(getString(R.string.enable_predictions)); - toggle = layout.findViewById(R.id.toggle); - toggle.setChecked(mayPredict); // Link to persistent option storage! Also needs handler. + SwitchCompat predictionsToggle = layout.findViewById(R.id.toggle); + predictionsToggle.setChecked(mayPredict); // Link to persistent option storage! Also needs handler. prefsKey = getLanguagePredictionPreferenceKey(lgCode); - toggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode)); + predictionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode)); + + // Corrections toggle and text are enabled only when predictions toggle is also enabled + correctionsTextView.setEnabled(mayPredict); + correctionsToggle.setEnabled(mayPredict); layout = (RelativeLayout)findViewById(R.id.model_picker); textView = (TextView) layout.findViewById(R.id.text1); diff --git a/android/KMEA/app/src/main/res/drawable/textview_item.xml b/android/KMEA/app/src/main/res/drawable/textview_item.xml new file mode 100644 index 0000000000..00b8d97435 --- /dev/null +++ b/android/KMEA/app/src/main/res/drawable/textview_item.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/KMEA/app/src/main/res/layout/list_row_layout4.xml b/android/KMEA/app/src/main/res/layout/list_row_layout4.xml index d53fd27fc7..5ae4bb771c 100644 --- a/android/KMEA/app/src/main/res/layout/list_row_layout4.xml +++ b/android/KMEA/app/src/main/res/layout/list_row_layout4.xml @@ -14,7 +14,7 @@ Date: Mon, 23 Sep 2019 11:25:28 +0700 Subject: [PATCH 10/16] Address review comment --- .../kmea/LanguageSettingsActivity.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java index 5c1a2e3857..bb47a60501 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageSettingsActivity.java @@ -73,15 +73,10 @@ public final class LanguageSettingsActivity extends AppCompatActivity { SharedPreferences.Editor prefEditor = prefs.edit(); - // predictionToggle overrides whether correction toggle and text are enabled + // predictionsToggle overrides correctionToggle and correctionsTextView if (prefsKey.endsWith(predictionPrefSuffix)) { boolean override = toggle.isChecked(); - if (correctionsTextView != null) { - correctionsTextView.setEnabled(override); - } - if (correctionsToggle != null) { - correctionsToggle.setEnabled(override); - } + overrideCorrectionsToggle(override); } // This will allow preemptively making settings for languages without models. @@ -167,9 +162,7 @@ public final class LanguageSettingsActivity extends AppCompatActivity { prefsKey = getLanguagePredictionPreferenceKey(lgCode); predictionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode)); - // Corrections toggle and text are enabled only when predictions toggle is also enabled - correctionsTextView.setEnabled(mayPredict); - correctionsToggle.setEnabled(mayPredict); + overrideCorrectionsToggle(mayPredict); layout = (RelativeLayout)findViewById(R.id.model_picker); textView = (TextView) layout.findViewById(R.id.text1); @@ -324,6 +317,25 @@ public final class LanguageSettingsActivity extends AppCompatActivity { return langID + correctionPrefSuffix; } + /** + * Overrides the enable and visibility of corrections toggle, + * and overrides the enable of the corrections text view. + * Does not change the corrections toggle value. + * @param override boolean - Value from predictions toggle + * When true, enables corrections toggle and text field, and makes corrections toggle visible + * When false, disables corrections toggle and text field, and makes corrections toggle invisible + */ + private void overrideCorrectionsToggle(boolean override) { + if (correctionsTextView != null) { + correctionsTextView.setEnabled(override); + } + if (correctionsToggle != null) { + correctionsToggle.setEnabled(override); + int visibility = override ? View.VISIBLE : View.INVISIBLE; + correctionsToggle.setVisibility(visibility); + } + } + // Fully details the building of this Activity's list view items. static private class FilteredKeyboardsAdapter extends NestedAdapter { static final int RESOURCE = R.layout.list_row_layout1; From ef93b553cd79a3cb2f306ad746271aebdb18f67a Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 11:42:49 +0700 Subject: [PATCH 11/16] history.md --- web/history.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/history.md b/web/history.md index 01c05e6aba..ac6a733891 100644 --- a/web/history.md +++ b/web/history.md @@ -3,6 +3,10 @@ ## 13.0 alpha * Start version 13.0 +## 2019-09-23 12.0.86 beta +* Fixes issue in Toolbar UI for some keyboards with language ids that include subtags (#2116) +* Fixes issue with '=' key use on certain platforms in Firefox (#2118) + ## 2019-09-20 12.0.85 beta * Adds failsafe in case of sticky backspace scenarios, making it simpler to cancel. (#2107) From 7e9b0def232f53b2e321c8b7dc2d6ec1d46ab0ad Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 12:08:45 +0700 Subject: [PATCH 12/16] Hides correct toggle instead of disabling --- .../Classes/Settings/LanguageSettingsViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift index 158b9c0b74..1757fff24f 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift @@ -92,7 +92,7 @@ class LanguageSettingsViewController: UITableViewController { userDefaults.set(predictSetting: value, forLanguageID: self.language.id) // Reactively set the corrections switch interactivity state. - self.doCorrectionsSwitch?.isEnabled = value + self.doCorrectionsSwitch?.isHidden = !value self.doCorrectionsLabel?.isEnabled = value self.correctionsCell?.isUserInteractionEnabled = value @@ -163,7 +163,7 @@ class LanguageSettingsViewController: UITableViewController { } // Disable interactivity if the prediction toggle is set to 'off'. - doCorrectionsSwitch!.isEnabled = userDefaults.predictSettingForLanguage(languageID: self.language.id) + doCorrectionsSwitch!.isHidden = !userDefaults.predictSettingForLanguage(languageID: self.language.id) cell.isUserInteractionEnabled = userDefaults.predictSettingForLanguage(languageID: self.language.id) } else { // rows 3 and 4 cell.accessoryType = .disclosureIndicator @@ -229,7 +229,7 @@ class LanguageSettingsViewController: UITableViewController { case 1: doCorrectionsLabel = cell.textLabel cell.textLabel?.text = "Enable corrections" - cell.textLabel?.isEnabled = doCorrectionsSwitch?.isEnabled ?? false + cell.textLabel?.isEnabled = !(doCorrectionsSwitch?.isHidden ?? false) case 2: cell.textLabel?.text = "Dictionaries" cell.accessoryType = .disclosureIndicator From c5179e43864f6867337c88c6caf074326d33d460 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 23 Sep 2019 12:44:42 +0700 Subject: [PATCH 13/16] Update history.md --- android/history.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/history.md b/android/history.md index 28ab624c75..11bc012a34 100644 --- a/android/history.md +++ b/android/history.md @@ -3,6 +3,9 @@ ## 13.0 alpha * Start version 13.0 +## 2019-09-23 12.0.4092 beta +* Disable corrections toggle when predictions are disabled (#2119) + ## 2019-09-20 12.0.4091 beta * Update offline help content (#2104) From fa20d1699c27af01894efe57f2aed762324712ce Mon Sep 17 00:00:00 2001 From: jahorton Date: Mon, 23 Sep 2019 14:35:47 +0700 Subject: [PATCH 14/16] Hack: forces regionless kbds to 'me' region --- web/source/kmwuitoolbar.ts | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/web/source/kmwuitoolbar.ts b/web/source/kmwuitoolbar.ts index 53f20dca14..908b668f5c 100644 --- a/web/source/kmwuitoolbar.ts +++ b/web/source/kmwuitoolbar.ts @@ -49,6 +49,7 @@ if(!window['keyman']['ui']['name']) { langKeyboardListNodes: [], selectedRegion: 'as', listedKeyboards: [], + catchAllRegion: 'me', keyboardListPriority: 0, maxListedKeyboards: 1, lastActiveControl: null, @@ -332,9 +333,17 @@ if(!window['keyman']['ui']['name']) { var max = 0, count = 0, languageCode = ''; // Get number of languages for the region - for(var j=0; j Date: Mon, 23 Sep 2019 14:57:09 +0700 Subject: [PATCH 15/16] Hack - alters the toolbar map to match regions --- web/source/kmwuitoolbar.ts | 14 +++++++------- web/source/resources/ui/toolbar/world.png | Bin 49366 -> 277714 bytes 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/source/kmwuitoolbar.ts b/web/source/kmwuitoolbar.ts index 908b668f5c..1917eaaad1 100644 --- a/web/source/kmwuitoolbar.ts +++ b/web/source/kmwuitoolbar.ts @@ -49,7 +49,7 @@ if(!window['keyman']['ui']['name']) { langKeyboardListNodes: [], selectedRegion: 'as', listedKeyboards: [], - catchAllRegion: 'me', + catchAllRegion: 'un', keyboardListPriority: 0, maxListedKeyboards: 1, lastActiveControl: null, @@ -82,10 +82,10 @@ if(!window['keyman']['ui']['name']) { ui.ToolBar_Text['ca']='Central America'; ui.ToolBar_Text['sa']='South America'; - ui.ToolBar_Text['na']='North America'; + ui.ToolBar_Text['na']='Americas'; ui.ToolBar_Text['eu']='Europe'; ui.ToolBar_Text['af']='Africa'; - ui.ToolBar_Text['me']='Middle east'; + ui.ToolBar_Text['un']='Undetermined'; ui.ToolBar_Text['as']='Asia'; ui.ToolBar_Text['oc']='Oceania'; @@ -148,14 +148,14 @@ if(!window['keyman']['ui']['name']) { util['linkStyleSheet'](util['getOption']('resources')+'ui/toolbar/kmwuitoolbar.css'); ui.regions = {}; - ui.regions['ca'] = {t: ui.ToolBar_Text['ca'], m: '49,52,65,54,68,57,71,56,73,59,75,60,93,61,94,58,97,58,101,59,107,60,114,64,115,68,114,77,104,74,98,75,96,78,95,82,90,81,85,80,82,76,78,74,74,73,65,68,57,61' }, - ui.regions['sa'] = {t: ui.ToolBar_Text['sa'], m: '82,82,95,82,96,78,98,75,104,74,114,77,120,79,124,83,126,87,141,90,142,97,138,103,135,113,127,116,123,124,115,131,112,132,109,138,117,139,140,141,141,146,134,148,114,145,109,148,100,148,91,143,91,130,96,111,89,102,83,95,77,89' }, - ui.regions['na'] = {t: ui.ToolBar_Text['na'], m: '0,3,0,37,24,32,35,37,43,47,49,52,65,54,68,57,71,56,73,59,75,60,93,61,93,57,103,49,118,41,126,41,136,23,148,17,156,14,164,5,164,0,57,0,35,5,25,9,5,8' }, + //ui.regions['ca'] = {t: ui.ToolBar_Text['ca'], m: '49,52,65,54,68,57,71,56,73,59,75,60,93,61,94,58,97,58,101,59,107,60,114,64,115,68,114,77,104,74,98,75,96,78,95,82,90,81,85,80,82,76,78,74,74,73,65,68,57,61' }, + //ui.regions['sa'] = {t: ui.ToolBar_Text['sa'], m: '82,82,95,82,96,78,98,75,104,74,114,77,120,79,124,83,126,87,141,90,142,97,138,103,135,113,127,116,123,124,115,131,112,132,109,138,117,139,140,141,141,146,134,148,114,145,109,148,100,148,91,143,91,130,96,111,89,102,83,95,77,89' }, + ui.regions['na'] = {t: ui.ToolBar_Text['na'], m: '0,3,0,37,24,32,35,37,43,47,49,52,65,54,68,57,71,56,73,59,75,60,93,61,93,57,103,49,118,41,126,41,136,23,148,17,156,14,164,5,164,0,57,0,35,5,25,9,5,8,49,52,65,54,68,57,71,56,73,59,75,60,93,61,94,58,97,58,101,59,107,60,114,64,115,68,114,77,104,74,98,75,96,78,95,82,90,81,85,80,82,76,78,74,74,73,65,68,57,61,82,82,95,82,96,78,98,75,104,74,114,77,120,79,124,83,126,87,141,90,142,97,138,103,135,113,127,116,123,124,115,131,112,132,109,138,117,139,140,141,141,146,134,148,114,145,109,148,100,148,91,143,91,130,96,111,89,102,83,95,77,89' }, ui.regions['eu'] = {t: ui.ToolBar_Text['eu'], m: '145,29,146,19,158,14,171,6,187,2,206,1,217,4,227,11,231,16,231,33,227,34,225,35,225,37,227,39,228,44,228,47,227,48,223,46,218,44,215,43,208,43,203,45,202,48,205,52,201,52,195,49,189,50,187,48,177,48,175,49,166,50,147,33' }, ui.regions['af'] = {t: ui.ToolBar_Text['af'], m: '150,58,158,50,166,50,175,49,177,48,187,48,189,50,195,49,201,52,205,52,207,53,221,75,229,75,231,77,231,85,227,92,232,101,237,106,237,112,227,115,222,118,206,125,199,127,193,127,185,111,183,104,180,87,168,89,153,85,143,71,147,60' }, - ui.regions['me'] = {t: ui.ToolBar_Text['me'], m: '205,52,202,48,203,45,208,43,215,43,218,44,223,46,227,48,232,48,239,48,240,49,239,53,242,60,243,65,237,76,231,77,229,75,221,75,207,53' }, ui.regions['as'] = {t: ui.ToolBar_Text['as'], m: '219,1,221,6,228,12,231,16,231,33,227,34,225,35,225,37,227,39,229,45,232,48,239,48,240,49,239,53,242,60,243,65,249,70,252,81,259,87,271,87,278,95,289,100,303,101,311,98,320,98,323,98,323,84,311,81,308,73,307,65,317,57,330,50,334,44,348,36,364,38,375,34,375,8,355,8,336,5,292,1,285,0,219,0' }, ui.regions['oc'] = {t: ui.ToolBar_Text['oc'], m: '288,117,289,107,303,101,311,98,323,98,323,84,333,77,344,73,362,80,369,88,375,96,375,141,352,143,323,142,316,136,310,130,291,130' } + ui.regions['un'] = {t: ui.ToolBar_Text['un'], m: '205,52,202,48,203,45,208,43,215,43,218,44,223,46,227,48,232,48,239,48,240,49,239,53,242,60,243,65,237,76,231,77,229,75,221,75,207,53' }, ui.toolbarNode = ui.createNode('div', 'kmw_controls'); ui.toolbarNode.style.display='block'; diff --git a/web/source/resources/ui/toolbar/world.png b/web/source/resources/ui/toolbar/world.png index 2b380acfb474bc8c81cd9d5aea56d321b2505ffa..1e90c61c38da7aaf0e097b9d1e1895ef73984661 100644 GIT binary patch literal 277714 zcmV*cKvTboP)Hg1+lHrgWSWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6 zwD^Ni=!>T7nL9I?X}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8re zhoBb*p;u8ID_yBf0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J z`jH<$>RKN5V(7OqK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYv zwjAKwmYb0gKL(K8-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z> z!FI&AHCpoWI|RUqx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVT zrI(b06~u#xf1yS}_UGdMvD``!0~u->P=lA4?YN`hilQ z|3tHka)7T{2CGqwjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^ z7T9R1gAN8V6s;5)ieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2b zW$~+pTw@bIek?ZvKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L z_AC5qq~L$#SMj%U$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6= zb6>{xYV#Ue-+LB$7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re z4r3qYr~6#KE>;1F`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+ z5K}u-6REM(K@W$srgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5 zh^QEb$V`rCQ-|7ZS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX z2i^rZ^Mu;6+rb@?NPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV z0id6JRRdfL?*ITm07*naRCodG-Fwh|Re2})gJ7i7?X68h&_vT}6XS#iO*CRIB~Xfj z85^S$y8)*%1tDcwI92=+8N|v28SJVU$E3zV*+2Vd=^75a|M4IH@pw$vfAKH=#lGjh58@Yw zYsCNG{@Z^`!Nl|5{oUWq{^1|~VHWSD%l*Inm;W-o=h(X--#j0E^wB9Vk!I;%G`$f= zxfTu-4s_?hAqCvKGpo*J0Hkl9|NQ3@u(=1+z!BgDAbS4$zyJH$fBmojm6JjA&;R*9 zpFQIl&zL>)na@n14?F?#-~7$r?1cwt{f;{7sPs-6xV!15o6@uN0AJ|<;-C7|r_%c$ z`k^1%1M`3Sr+?ZDFTWAL@Sga@CnmS|-+%w!=Ky%=L&8QcdeMt!KlzhCnbPU`M}Fi- zTvF%aTEHI{x>0n@x$C-j`S)_an;abz4UgKpp9=`cDy8 z{_5fhC($_oe(BSRxcYtZi(lO94Dcl4;)i<@|1bXHFGf&1aRtgjF_Q9}cXt2fzxqS0|ngY@a?bv`mbjIC@Q|^FMQz(36%fyfBw(O_miLe