mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-07 22:47:29 +00:00
Merge branch 'master' into chore/linux/cherry-pick/changelog
This commit is contained in:
commit
8d5fe8a66a
252 changed files with 3084 additions and 1325 deletions
100
HISTORY.md
100
HISTORY.md
|
|
@ -1,5 +1,9 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.10 alpha 2025-03-01
|
||||
|
||||
* chore: merge beta into master B18S2 (#13391)
|
||||
|
||||
## 19.0.9 alpha 2025-02-27
|
||||
|
||||
* docs: move node.js before Emscripten (#13366)
|
||||
|
|
@ -42,6 +46,102 @@
|
|||
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
|
||||
* chore: increment to alpha 19.0 (#13187)
|
||||
|
||||
## 18.0.203 beta 2025-02-28
|
||||
|
||||
* feat(windows): keyman configuration icon refresh (#13294)
|
||||
* fix(windows): if kmshell is "silent" don't start a Keyman update (#13330)
|
||||
* chore(windows): Update crowdin strings for Khmer (#13364)
|
||||
* docs: clean up links in builder.md (#13328)
|
||||
* chore: add build.sh edit action for Delphi projects (#13327)
|
||||
* fix(developer): handle line breaks in debugger correctly (#13334)
|
||||
* fix(developer): use U+0001 for deadkey markers in the debugger (#13336)
|
||||
* feat(developer): use .kps folder as initial dir for Add Files (#13324)
|
||||
* fix(developer): verify `context(n)` offsets are valid in kmcmplib (#13308)
|
||||
* chore(core): add unit tests to validate `nul` with `index` and `context` (#13311)
|
||||
* fix(core): include `nul` in context offset calculations in kmx processor (#13314)
|
||||
* fix(developer): include `nul` in offset calculations for kmw v10 compiler (#13326)
|
||||
|
||||
## 18.0.202 beta 2025-02-27
|
||||
|
||||
* fix(android): Direct user to switch system IME when WebView fails (#13352)
|
||||
* fix(android): Filter out SocketTimeoutExceptions (#13353)
|
||||
* refactor(linux): extract method for setting up mocks in tests (#13355)
|
||||
* chore(linux): update to current Debian policy version (#13362)
|
||||
* fix(developer): prevent double action of editor hotkeys in debugger (#13345)
|
||||
* fix(linux): ignore DBusException during install and uninstall (#13356)
|
||||
* fix(linux): ignore exception when fcitx5-configtool is not found (#13363)
|
||||
* fix(developer): prevent simultaneous opening of the same project by two processes (#13341)
|
||||
* chore: add a workflow to automatically close linked issues (#13369)
|
||||
|
||||
## 18.0.201 beta 2025-02-26
|
||||
|
||||
* fix(web): prevent cookie-default behavior from mangling engine init (#13331)
|
||||
* chore(linux): adjust to renamed xdg module (#13354)
|
||||
|
||||
## 18.0.200 beta 2025-02-25
|
||||
|
||||
* fix(web): properly support workerless use of Web engine (#13290)
|
||||
* fix(web): reconnect, patch up engine/main headless unit tests (#13291)
|
||||
* change(ios): re-order host page script load so Sentry can report script load failures (#13332)
|
||||
* chore(linux): Update to new Debian policy version (#13325)
|
||||
|
||||
## 18.0.199 beta 2025-02-24
|
||||
|
||||
* fix(windows): Update Greek Polytonic for keyboard help (#13323)
|
||||
|
||||
## 18.0.198 beta 2025-02-21
|
||||
|
||||
* fix(android): Update system keyboard height consistently (#13273)
|
||||
|
||||
## 18.0.197 beta 2025-02-20
|
||||
|
||||
* fix(linux): address compiling with ICU 76 (#13285)
|
||||
|
||||
## 18.0.196 beta 2025-02-19
|
||||
|
||||
* fix(windows): remove sentry report on firstrun in idle state (#13266)
|
||||
* fix(android): Correct the scope of `tier` in build.gradle (#13268)
|
||||
* chore(common): Update crowdin strings for Polytonic Greek (#13277)
|
||||
* fix(web): provide informative error + message when loading UI without the engine (#13269)
|
||||
* fix(developer): Use 'desktop' for target in OSK fill temp keyboard (#13271)
|
||||
* fix(linux): add missing dependency for uploading to llso (#13279)
|
||||
* refactor(linux): re-enable integration tests (#13234)
|
||||
|
||||
## 18.0.195 beta 2025-02-18
|
||||
|
||||
* docs(web): use promise-chaining in manual-control example (#13272)
|
||||
|
||||
## 18.0.194 beta 2025-02-17
|
||||
|
||||
* fix(android): Use TIER.md to determine Play Store tier (#13259)
|
||||
* fix(android): prevent update-checks from crashing system keyboard and app if `DownloadManager` is disabled (#13218)
|
||||
* feat(linux): add `--remote-debug` flag to `run-tests.sh` script (#13233)
|
||||
* chore(linux): improve output when running tests (#13247)
|
||||
* refactor(linux): cleanup code of test fixture (#13248)
|
||||
|
||||
## 18.0.193 beta 2025-02-14
|
||||
|
||||
* chore(developer): add INFO messages for `kmc copy` and `kmc generate` (#13200)
|
||||
* fix(android): Remove sendOptionsToKeyboard function from Engine API (#13219)
|
||||
* docs(android): Document removing sendOptionsToKeyboard API (#13220)
|
||||
* chore(ios): increase minimum ios version to 15 (#13201)
|
||||
* fix(android): Catch WebView exceptions and prompt user to install WebView (#13140)
|
||||
* chore(common): Update crowdin strings for Mon (#13241)
|
||||
* chore(ios,windows): Update Crowdin strings for Czech (#13242)
|
||||
* change(web): drop 'dom' unit test concurrency when run in CI mode (#13141)
|
||||
* chore(web): ensure /web/build.sh clean triggers all predictive-text child actions (#13158)
|
||||
* chore(web): fix coverage-reporting artifact (#13196)
|
||||
|
||||
## 18.0.192 beta 2025-02-14
|
||||
|
||||
* chore(common): Fix 17.0.335 tier in HISTORY.md (#13213)
|
||||
* docs(linux): update what's new for 18.0 (#13207)
|
||||
* chore(linux): additional code cleanup after Focal removal (#13206)
|
||||
* docs(windows): update keyman for windows 18.0 whats-new (#13211)
|
||||
* chore: undo master merge (#13238)
|
||||
* change(ios): prevent memory leaks from keyboard-related notification observers (#13129)
|
||||
* change(ios): mitigate keyboard input-view memory leak by force-discarding WebView (#13169)
|
||||
|
||||
## 18.0.191 beta 2025-02-12
|
||||
|
||||
* feat(windows): handle a hard windows reset occurring while downloading updated keyman files (#13128)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.10
|
||||
19.0.11
|
||||
|
|
@ -9,6 +9,8 @@ plugins {
|
|||
ext.rootPath = '../../'
|
||||
apply from: "$rootPath/version.gradle"
|
||||
|
||||
String tier = new File('../../TIER.md').getText('UTF-8').trim();
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
namespace="com.tavultesoft.kmapro"
|
||||
|
|
@ -75,7 +77,7 @@ android {
|
|||
// - PR builds also have "-test-1234" suffix (where 1234 is PR#)
|
||||
// Gradle sync will nullify these values, so have a fallback
|
||||
String appSuffix = ''
|
||||
String tag = VERSION_TAG ? VERSION_TAG : '-' + new File('../../TIER.md').getText('UTF-8');
|
||||
String tag = VERSION_TAG ? VERSION_TAG : '-' + tier;
|
||||
if (tag.startsWithAny("-beta", "beta")) {
|
||||
appSuffix = ' Beta';
|
||||
} else if (tag.startsWithAny("-alpha", "alpha")) {
|
||||
|
|
@ -127,7 +129,7 @@ if (env_keys_json_file != null && file(env_keys_json_file).exists()) {
|
|||
|
||||
// Deactivate lower conflicting version APKs
|
||||
resolutionStrategy.set(com.github.triplet.gradle.androidpublisher.ResolutionStrategy.IGNORE)
|
||||
switch (System.env.TIER) {
|
||||
switch (tier) {
|
||||
case 'beta':
|
||||
track.set("beta")
|
||||
println "TIER set to beta"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package com.keyman.android;
|
||||
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
import com.tavultesoft.kmapro.AdjustLongpressDelayActivity;
|
||||
import com.tavultesoft.kmapro.BuildConfig;
|
||||
import com.tavultesoft.kmapro.DefaultLanguageResource;
|
||||
|
|
@ -84,7 +85,12 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false);
|
||||
KMManager.setHapticFeedback(mayHaveHapticFeedback);
|
||||
|
||||
KMManager.executeResourceUpdate(this);
|
||||
// Checking for updates should never be allowed to crash the keyboard.
|
||||
// Just silently fail if this occurs.
|
||||
if(DownloadFileUtils.getDownloadManager(this) != null) {
|
||||
// Will try to emit a toast if it fails - i.e., is not silent.
|
||||
KMManager.executeResourceUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -149,16 +155,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
KMManager.onStartInput(attribute, restarting);
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
|
||||
// This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes,
|
||||
// making it an important time to detect orientation changes.
|
||||
Context appContext = getApplicationContext();
|
||||
int newOrientation = KMManager.getOrientation(appContext);
|
||||
if(newOrientation != lastOrientation) {
|
||||
lastOrientation = newOrientation;
|
||||
Configuration newConfig = this.getResources().getConfiguration();
|
||||
KMManager.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
// Temporarily disable predictions on certain fields (e.g. hidden password field or numeric)
|
||||
int inputType = attribute.inputType;
|
||||
KMManager.setMayPredictOverride(inputType);
|
||||
|
|
@ -228,6 +225,16 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
public void onComputeInsets(InputMethodService.Insets outInsets) {
|
||||
super.onComputeInsets(outInsets);
|
||||
|
||||
// This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes,
|
||||
// making it an important time to detect orientation changes.
|
||||
Context appContext = getApplicationContext();
|
||||
int newOrientation = KMManager.getOrientation(appContext);
|
||||
if(newOrientation != lastOrientation) {
|
||||
lastOrientation = newOrientation;
|
||||
Configuration newConfig = this.getResources().getConfiguration();
|
||||
KMManager.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
// We should extend the touchable region so that Keyman sub keys menu can receive touch events outside the keyboard frame
|
||||
Point size = KMManager.getWindowSize(getApplicationContext());
|
||||
|
||||
|
|
@ -250,8 +257,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
if (exText != null)
|
||||
exText = null;
|
||||
}
|
||||
// Initialize keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ public class AdjustLongpressDelayActivity extends BaseActivity {
|
|||
// Store the longpress delay as a reference
|
||||
// and then update KeymanWeb with the longpress delay
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.graphics.Bitmap;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -70,7 +71,13 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_kmp_browser);
|
||||
try {
|
||||
setContentView(R.layout.activity_kmp_browser);
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// WebView not enabled
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
webView = (WebView) findViewById(R.id.kmpBrowserWebView);
|
||||
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ import com.keyman.android.DownloadIntentService;
|
|||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.KMPLink;
|
||||
import com.keyman.engine.util.KMString;
|
||||
import com.keyman.engine.util.WebViewUtils;
|
||||
import com.keyman.engine.util.WebViewUtils.EngineWebViewVersionStatus;
|
||||
import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ProgressDialog;
|
||||
|
|
@ -153,7 +155,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
KMManager.setDebugMode(true);
|
||||
}
|
||||
|
||||
// Verify WebView installed and enabled before attempting to initialize KMManager
|
||||
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
|
||||
KMManager.executeResourceUpdate(this);
|
||||
|
||||
DefaultLanguageResource.install(context);
|
||||
|
|
@ -181,8 +185,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
textView.setTextSize((float) textSize);
|
||||
textView.setSelection(textView.getText().length());
|
||||
|
||||
// Use in-app keyboard WebView to check the Chrome version
|
||||
checkChromeVersion(KMManager.getKMKeyboard(KeyboardType.KEYBOARD_TYPE_INAPP));
|
||||
// Check WebView enabled and Chrome version
|
||||
SystemWebViewStatus webViewStatus = WebViewUtils.getSystemWebViewStatus(context);
|
||||
EngineWebViewVersionStatus chromeStatus = KMManager.getEngineWebViewVersionStatus();
|
||||
updateWebViewIfNeeded(chromeStatus, webViewStatus);
|
||||
|
||||
CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context);
|
||||
checkGetStarted();
|
||||
}
|
||||
|
|
@ -475,8 +482,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
|
||||
@Override
|
||||
public void onKeyboardLoaded(KeyboardType keyboardType) {
|
||||
// Initialize keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -770,39 +776,114 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
KMManager.setHapticFeedback(mayHaveHapticFeedback);
|
||||
}
|
||||
|
||||
private void checkChromeVersion(WebView webView) {
|
||||
if (KMManager.getEngineWebViewVersionStatus() != EngineWebViewVersionStatus.FULL) {
|
||||
LinearLayout updateChromeLayout = (LinearLayout) findViewById(R.id.updateChromeLayout);
|
||||
updateChromeLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
Button updateChromeButton = (Button)findViewById(R.id.updateChromeButton);
|
||||
updateChromeButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Launch PlayStore to update Chrome
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.android.chrome"));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
// Link to Chrome if user is not signed in to Play Store
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Based on whether Chrome and WebView are installed & enabled,
|
||||
* display the corresponding update panel
|
||||
* @param chromeStatus
|
||||
* @param webViewStatus
|
||||
*/
|
||||
private void updateWebViewIfNeeded(EngineWebViewVersionStatus chromeStatus, SystemWebViewStatus webViewStatus) {
|
||||
if (chromeStatus == EngineWebViewVersionStatus.FULL && webViewStatus == SystemWebViewStatus.FULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (webViewStatus == SystemWebViewStatus.NOT_INSTALLED) {
|
||||
displayInstallWebView();
|
||||
} else if (webViewStatus == SystemWebViewStatus.DISABLED) {
|
||||
displayEnableWebView();
|
||||
} else {
|
||||
// Otherwise, display update Chrome
|
||||
displayUpdateChrome();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayInstallWebView() {
|
||||
TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
|
||||
textView.setText(getString(R.string.body_install_webview));
|
||||
|
||||
Button button = (Button)findViewById(R.id.webViewChromeButton);
|
||||
button.setText(getString(R.string.label_install_webview));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Play Store link to install WebView
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.webview"));
|
||||
|
||||
// Launch intent
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
|
||||
webViewChromeLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void displayEnableWebView() {
|
||||
TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
|
||||
textView.setText(getString(R.string.body_enable_webview));
|
||||
|
||||
Button button = (Button)findViewById(R.id.webViewChromeButton);
|
||||
button.setText(getString(R.string.label_enable_webview));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Application settings to enable WebView
|
||||
Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS",
|
||||
Uri.parse("package:com.google.android.webview"));
|
||||
|
||||
// Launch intent
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
|
||||
webViewChromeLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void displayUpdateChrome() {
|
||||
// TextView's default string is to update Chrome
|
||||
|
||||
Button button = (Button)findViewById(R.id.webViewChromeButton);
|
||||
button.setText(getString(R.string.button_update_chrome));
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// Primary Play Store link to install/update Chrome
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=com.android.chrome"));
|
||||
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
// (alternate Play Store link to install/update Chrome)
|
||||
intent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
|
||||
|
||||
intent.resolveActivity(packageManager);
|
||||
startActivity(intent);
|
||||
}
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
|
||||
webViewChromeLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private Uri requestPermissionIntentUri;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<include layout="@layout/titlebar" />
|
||||
|
||||
<include layout="@layout/update_chrome_layout"
|
||||
<include layout="@layout/check_chrome_webview_layout"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
android:background="@drawable/textview_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/updateChromeLayout"
|
||||
android:layout_below="@id/checkWebViewChromeLayout"
|
||||
android:ems="10"
|
||||
android:inputType="textMultiLine|textNoSuggestions"
|
||||
android:hint="@string/textview_hint"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/updateChromeLayout"
|
||||
android:id="@+id/checkWebViewChromeLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/white"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Banner to display if old version of Chrome installed -->
|
||||
<!-- Banner to display if WebView is not installed/enabled or old version of Chrome installed -->
|
||||
<TextView
|
||||
android:id="@+id/kmUpgradeChromeTextView"
|
||||
android:id="@+id/kmWebViewChromeTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
android:textSize="@dimen/update_chrome_label_textsize" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/updateChromeButton"
|
||||
android:id="@+id/webViewChromeButton"
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<!-- Context: Menu Action -->
|
||||
<string name="action_settings" comment="Menu action to open Settings menu">Ρυθμίσεις</string>
|
||||
<!-- Context: Menu Action -->
|
||||
<string name="action_install_updates" comment="Menu notification that keyboard or dictionary updates available">Εγκατάσταση Ενημερώσεων</string>
|
||||
<string name="action_install_updates" comment="Menu notification that keyboard or dictionary updates available">Ἐγκατάσταση Ἐνημερώσεων</string>
|
||||
<!-- Context: Title -->
|
||||
<string name="title_version" comment="Title of Keyman for Android version">Ἔκδοση %1$s</string>
|
||||
<!-- Context: Text label when old Chrome version installed -->
|
||||
|
|
@ -38,11 +38,11 @@
|
|||
<!-- Context: Clear Text dialog -->
|
||||
<string name="all_text_will_be_cleared" comment="Erase all the text">\nΤὸ κείμενο θὰ ἐκκαθαρισθεῖ πλήρως\n</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="get_started" comment="Menu for getting started">Μικρύνετε τὸ κείμενο</string>
|
||||
<string name="get_started" comment="Menu for getting started">Ξεκινῆστε</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="add_a_keyboard" comment="Menu item to add a keyboard">Προσθέστε πληκτρολόγιο γιὰ τὴν γλῶσσα σας</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="enable_system_keyboard" comment="Menu item to enable Keyman as system keyboard">Ὁρίστε τὸ Κῆμαν ὡς παν-συστημικὸ πληκτρολόγιο</string>
|
||||
<string name="enable_system_keyboard" comment="Menu item to enable Keyman as system keyboard">Ὁρίστε τὸ Keyman ὡς πληκτρολόγιο συστήματος</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
<string name="set_keyman_as_default" comment="Menu item to set Keyman as default system keyboard">Ὁρίστε τὸ Κῆμαν ὡς προεπιλεγμένο πληκτρολόγιο</string>
|
||||
<!-- Context: Get Started menu -->
|
||||
|
|
@ -68,6 +68,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Μεταβολὴ ὕψους πληκτρολογίου</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Καθυστέρηση παρατεταμένου πατήματος</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Λεζάντα πλήκτρου διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">Πληκτρολόγιο</string>
|
||||
|
|
@ -87,11 +89,11 @@
|
|||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">Καμμία λεζάντα στὸ πλῆκτρο διαστήματος</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">Δὸνηση κατὰ τὴν πληκτρολόγηση</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner" comment="text suggestions banner">Νὰ ἐμφανίζεται πάντα banner</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">Πρὸς ὑλοποίησιν</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">OFF: ἐμφάνιση μόνον ὅταν ἔχει ἐνεργοποιηθεῖ ἡ πρόβλεψη κειμένου</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">Ἐπιτρέψτε τὴν ἀποστολὴ ἀναφορῶν κατάρρευσης μέσῳ δικτύου</string>
|
||||
|
|
@ -123,6 +125,14 @@
|
|||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">Περιστρέψτε τὴν συσκευὴ γιὰ προσαρμογὴ τοῦ ὕψους σὲ λειτουργία πορτραίτου καὶ τοπίου</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">Ἐπαναφέρετε τὶς προεπιλεγμένες ρυθμίσεις</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Χρόνος καθυστέρησης: %1$.1f δευτ</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">Μεγαλύτερος χρόνος καθυστέρησης</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Μικρότερος χρόνος καθυστέρησης</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Ρυθμιστής καθυστέρησης πατήματος</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">Ἀναζητῆστε ἢ πληκτρολογῆστε URL</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">ချိၚ်ဆသမၠုၚ်ကဳဗုဒ်ညိ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">ချိၚ်အခိၚ် ဍဵုလအ်အ်</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">က္တိုပ်လိက် Spacebar</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">ကဳဗုဒ်</string>
|
||||
|
|
@ -87,11 +89,11 @@
|
|||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">လ္ပဓမံက်ထ္ၜးမလိက်ပ္ဍဲspacebarညိ</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">ပ္ဍဲကာလမတက်လိက် ချဳဒတဝ်ညိ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner" comment="text suggestions banner">ဓမံက်ထ္ၜးbannerလၟိုန်ညိ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">သွက်ဂွံဓမံက်ရုပ်ရဴ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">ကၟာတ်လဝ်မ္ဂး မလိက်ချိၚ်ဆလဝ်တံဂှ် ထ္ၜးအဃောပံက်လဝ်ဟေၚ်</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">ကဵုအခေါၚ် ပၠံၚ်လိက်စဳရေၚ်လ္တူ အေန်တာနက်ညိ</string>
|
||||
|
|
@ -123,6 +125,14 @@
|
|||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">ဂွံချိၚ် လံက်သတိက် လံက်ဇမၠိၚ်ဂှ် ဗဂေတ်ကဵုစက်ပိုဲညိ။</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">ကလေၚ်သ္ပနူပကတိ</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">အခိၚ်ဖအိၚ်: %1$.1f စက္က</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">ဖအိၚ်အခိၚ်လအ်အ်</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">ဖအိၚ်အခိၚ်အောန်န်</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">ဍဵုလအ်အ်သွက် မသရေတ််ဖအိၚ်အခိၚ်</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">ဂၠာဲ ဟွံသေၚ် တက် URL</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
<!-- Context: Menu Action -->
|
||||
<string name="action_install_updates" comment="Menu notification that keyboard or dictionary updates available">Install Updates</string>
|
||||
|
||||
|
||||
<!-- Context: Title -->
|
||||
<string name="title_version" comment="Title of Keyman for Android version">Version: %1$s</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@
|
|||
</intent>
|
||||
</queries>
|
||||
|
||||
<queries>
|
||||
<package android:name="com.android.webkit" />
|
||||
<package android:name="com.google.android.ims" />
|
||||
</queries>
|
||||
|
||||
<!-- https://developer.android.com/develop/ui/views/notifications/notification-permission#declare -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ var bannerImagePath = '';
|
|||
var bannerHTMLContents = '';
|
||||
var fragmentToggle = 0;
|
||||
var deferredBannerCall;
|
||||
var longpressDelay = null;
|
||||
|
||||
var sentryManager = new KeymanSentryManager({
|
||||
hostPlatform: "android"
|
||||
|
|
@ -71,6 +72,10 @@ function init() {
|
|||
|
||||
keyman.refreshOskLayout();
|
||||
}
|
||||
// Initialize the longpress delay
|
||||
if(longpressDelay !== null) {
|
||||
setLongpressDelay(longpressDelay);
|
||||
}
|
||||
});
|
||||
|
||||
keyman.addEventListener('keyboardloaded', setIsChiral);
|
||||
|
|
@ -128,11 +133,10 @@ function notifyHost(event, params) {
|
|||
// Update the KeymanWeb longpress delay
|
||||
// delay is in milliseconds
|
||||
function setLongpressDelay(delay) {
|
||||
if (keyman.osk) {
|
||||
longpressDelay = delay;
|
||||
if (keyman && keyman.osk) {
|
||||
keyman.osk.gestureParams.longpress.waitLength = delay;
|
||||
console_debug('setLongpressDelay('+delay+')');
|
||||
} else {
|
||||
window.console.log('setLongpressDelay error: keyman.osk undefined');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import android.content.res.Configuration;
|
|||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
|
|
@ -110,14 +111,29 @@ final class KMKeyboard extends WebView {
|
|||
|
||||
public String specialOskFont = "";
|
||||
|
||||
public KMKeyboard(Context context) {
|
||||
/**
|
||||
* Constructor for KMKeyboard.
|
||||
* If System WebView not installed/enabled, will throw AndroidRuntimeException.
|
||||
* Can't catch the exception here because super(context) must be the first line.
|
||||
* @param context
|
||||
* @throws AndroidRuntimeException
|
||||
*/
|
||||
public KMKeyboard(Context context) throws AndroidRuntimeException {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.keyboardType = KeyboardType.KEYBOARD_TYPE_INAPP;
|
||||
initKMKeyboard(context);
|
||||
}
|
||||
|
||||
public KMKeyboard(Context context, KeyboardType keyboardType) {
|
||||
/**
|
||||
* Constructor for KMKeyboard.
|
||||
* If System WebView not installed/enabled, will throw AndroidRuntimeException.
|
||||
* Can't catch the exception here because super(context) must be the first line.
|
||||
* @param context {Context}
|
||||
* @param keyboardType {KeyboardType}
|
||||
* @throws AndroidRuntimeException
|
||||
*/
|
||||
public KMKeyboard(Context context, KeyboardType keyboardType) throws AndroidRuntimeException {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.keyboardType = keyboardType;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class KMKeyboardDownloaderActivity extends BaseActivity {
|
|||
|
||||
//TODO: move to keyboard manager class
|
||||
private static ArrayList<KeyboardEventHandler.OnKeyboardDownloadEventListener> kbDownloadEventListeners = null;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import android.net.NetworkInfo;
|
|||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.text.InputType;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
|
@ -73,6 +74,7 @@ import com.keyman.engine.util.KMLog;
|
|||
import com.keyman.engine.util.KMString;
|
||||
import com.keyman.engine.util.MapCompat;
|
||||
import com.keyman.engine.util.WebViewUtils;
|
||||
import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
|
@ -687,12 +689,33 @@ public final class KMManager {
|
|||
KMKeyboardWebViewClient webViewClient = null;
|
||||
|
||||
if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard == null) {
|
||||
InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
try {
|
||||
InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// Catch fatal error when WebView not installed/enabled
|
||||
// Only log exceptions unrelated to WebView
|
||||
if (e != null && !e.getMessage().contains("MissingWebViewPackageException")) {
|
||||
KMLog.LogException(TAG, "initKeyboard for INAPP" , e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
InAppKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType);
|
||||
keyboard = InAppKeyboard;
|
||||
webViewClient = InAppKeyboardWebViewClient;
|
||||
} else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard == null) {
|
||||
SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
try {
|
||||
SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// Catch fatal error when WebView not installed/enabled
|
||||
// Only log exceptions unrelated to WebView
|
||||
if (e != null && !e.getMessage().contains("MissingWebViewPackageException")) {
|
||||
KMLog.LogException(TAG, "initKeyboard for SYSTEM", e);
|
||||
}
|
||||
// Direct user to pick another system keyboard
|
||||
InputMethodManager imManager = (InputMethodManager) appContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imManager.showInputMethodPicker();
|
||||
return;
|
||||
}
|
||||
SystemKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType);
|
||||
keyboard = SystemKeyboard;
|
||||
webViewClient = SystemKeyboardWebViewClient;
|
||||
|
|
@ -722,6 +745,7 @@ public final class KMManager {
|
|||
}
|
||||
keyboard.setBanner(KMManager.BannerType.HTML);
|
||||
keyboard.showBanner(true);
|
||||
sendLongpressDelay();
|
||||
}
|
||||
setEngineWebViewVersionStatus(appContext, keyboard);
|
||||
}
|
||||
|
|
@ -776,6 +800,10 @@ public final class KMManager {
|
|||
final FrameLayout mainLayout = new FrameLayout(appContext);
|
||||
mainLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
|
||||
if (SystemKeyboard == null) {
|
||||
return mainLayout;
|
||||
}
|
||||
|
||||
RelativeLayout keyboardLayout = new RelativeLayout(appContext);
|
||||
keyboardLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
ViewGroup parent = (ViewGroup) SystemKeyboard.getParent();
|
||||
|
|
@ -921,7 +949,7 @@ public final class KMManager {
|
|||
copyAsset(context, KMFilename_KmwCss, "", true);
|
||||
copyAsset(context, KMFilename_KmwGlobeHintCss, "", true);
|
||||
copyAsset(context, KMFilename_Osk_Ttf_Font, "", true);
|
||||
|
||||
|
||||
// Needed until our minimum version of Chrome is 61.0+.
|
||||
copyAsset(context, KMFilename_JSPolyfill2, "", true);
|
||||
|
||||
|
|
@ -2124,7 +2152,7 @@ public final class KMManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the longpress delay (in milliseconds) as a stored preference.
|
||||
* Set the longpress delay (in milliseconds) as a stored preference and sends to KeymanWeb.
|
||||
* Valid range is 300 ms to 1500 ms. Returns true if the preference is successfully stored.
|
||||
* @param longpressDelay - int longpress delay in milliseconds
|
||||
* @return boolean
|
||||
|
|
@ -2140,17 +2168,19 @@ public final class KMManager {
|
|||
editor.putInt(KMKey_LongpressDelay, longpressDelay);
|
||||
editor.commit();
|
||||
|
||||
// Send longpress delay to KeymanWeb
|
||||
sendLongpressDelay();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends options to the KeymanWeb keyboard.
|
||||
* Sends longpress delay to the KeymanWeb keyboard.
|
||||
* 1. number of milliseconds to trigger a longpress gesture.
|
||||
* This method requires a keyboard to be loaded for the value to take effect.
|
||||
*/
|
||||
public static void sendOptionsToKeyboard() {
|
||||
private static void sendLongpressDelay() {
|
||||
int longpressDelay = getLongpressDelay();
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
if (InAppKeyboard != null) {
|
||||
InAppKeyboard.loadJavascript(KMString.format("setLongpressDelay(%d)", longpressDelay));
|
||||
}
|
||||
|
||||
|
|
@ -2301,9 +2331,9 @@ public final class KMManager {
|
|||
public static boolean updateText(KeyboardType kbType, String text) {
|
||||
boolean result = false;
|
||||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) {
|
||||
return InAppKeyboard.updateText(text);
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) {
|
||||
return SystemKeyboard.updateText(text);
|
||||
}
|
||||
|
||||
|
|
@ -2335,13 +2365,13 @@ public final class KMManager {
|
|||
public static boolean updateSelectionRange(KeyboardType kbType) {
|
||||
boolean result = false;
|
||||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreSelectionChange()) {
|
||||
result = InAppKeyboard.updateSelectionRange();
|
||||
}
|
||||
|
||||
InAppKeyboard.setShouldIgnoreSelectionChange(false);
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreSelectionChange()) {
|
||||
result = SystemKeyboard.updateSelectionRange();
|
||||
}
|
||||
|
|
@ -2354,11 +2384,11 @@ public final class KMManager {
|
|||
|
||||
public static void resetContext(KeyboardType kbType) {
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && InAppKeyboard != null) {
|
||||
InAppKeyboard.loadJavascript("resetContext()");
|
||||
}
|
||||
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) {
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && SystemKeyboard != null) {
|
||||
SystemKeyboard.loadJavascript("resetContext()");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus) {
|
||||
activeView = v;
|
||||
if (KMManager.InAppKeyboardWebViewClient.getKeyboardLoaded()) {
|
||||
if (KMManager.InAppKeyboard != null && KMManager.InAppKeyboardWebViewClient.getKeyboardLoaded()) {
|
||||
updateTextContext();
|
||||
}
|
||||
showKeyboard();
|
||||
|
|
@ -165,7 +165,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
if (activeView != null && activeView.equals(this)) {
|
||||
if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
if (KMManager.InAppKeyboard != null && KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP)) {
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
}
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ public final class KMTextView extends AppCompatEditText {
|
|||
AppCompatActivity activity = (AppCompatActivity)context;
|
||||
|
||||
Window mainWindow = activity.getWindow();
|
||||
if (hasWindowFocus) {
|
||||
if (hasWindowFocus && KMManager.InAppKeyboard != null) {
|
||||
KMManager.InAppKeyboardWebViewClient.setContext(context);
|
||||
activeView = mainWindow.getCurrentFocus();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keyman.engine.R;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -206,12 +210,34 @@ public class CloudDownloadMgr{
|
|||
* @param params the cloud api params for download
|
||||
* @param <ModelType> the target models type
|
||||
* @param <ResultType> the cloud requests result type
|
||||
* @return `false` if the download request cannot be executed; `true` otherwise.
|
||||
*/
|
||||
public <ModelType,ResultType> void executeAsDownload(Context aContext, String aDownloadIdentifier,
|
||||
public <ModelType,ResultType> boolean executeAsDownload(Context aContext, String aDownloadIdentifier,
|
||||
ModelType aTargetModel,
|
||||
ICloudDownloadCallback<ModelType,ResultType> aCallback,
|
||||
CloudApiTypes.CloudApiParam... params)
|
||||
{
|
||||
CloudApiTypes.CloudApiParam... params) {
|
||||
try {
|
||||
executeAsDownloadInternal(aContext, aDownloadIdentifier, aTargetModel, aCallback, params);
|
||||
} catch (DownloadManagerDisabledException e) {
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.update_check_download_manager_disabled),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(aContext,
|
||||
aContext.getString(R.string.update_check_unavailable),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
KMLog.LogException(TAG, "Unexpected exception occurred during download/query attempt", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private <ModelType,ResultType> void executeAsDownloadInternal(Context aContext, String aDownloadIdentifier,
|
||||
ModelType aTargetModel,
|
||||
ICloudDownloadCallback<ModelType,ResultType> aCallback,
|
||||
CloudApiTypes.CloudApiParam... params) throws DownloadManagerDisabledException {
|
||||
if(!isInitialized) {
|
||||
Log.w(TAG, "DownloadManager not initialized. Initializing CloudDownloadMgr.");
|
||||
initialize(aContext);
|
||||
|
|
@ -219,16 +245,22 @@ public class CloudDownloadMgr{
|
|||
KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.executeAsDownload() called; already initialized", true);
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = DownloadFileUtils.getDownloadManager(aContext);
|
||||
if(downloadManager == null) {
|
||||
// The callback object provided to us provides no way to directly signal a
|
||||
// failure. That said, we can also immediately detect that we WILL fail and
|
||||
// corresponding error _now_, rather than later.
|
||||
//
|
||||
// Unique custom error so it's easy to explicitly filter.
|
||||
throw new DownloadManagerDisabledException();
|
||||
}
|
||||
|
||||
synchronized (downloadSetByDownloadIdentifier) {
|
||||
|
||||
if (alreadyDownloadingData(aDownloadIdentifier) || params == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
if(downloadManager==null)
|
||||
throw new IllegalStateException("DownloadManager is not available");
|
||||
|
||||
aCallback.initializeContext(aContext);
|
||||
|
||||
CloudApiTypes.CloudDownloadSet<ModelType,ResultType> _downloadSet =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.keyman.engine.cloud;
|
||||
|
||||
public class DownloadManagerDisabledException extends RuntimeException {
|
||||
DownloadManagerDisabledException() {
|
||||
super("System service DownloadManager is not available and cannot facilitate downloads or queries.");
|
||||
}
|
||||
}
|
||||
|
|
@ -111,8 +111,8 @@ public class CloudLexicalModelMetaDataDownloadCallback implements ICloudDownload
|
|||
|
||||
BaseActivity.makeToast(aContext, R.string.dictionary_download_start_in_background, Toast.LENGTH_SHORT);
|
||||
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(aContext,
|
||||
_r.additionalDownloadid, null, _callback,
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
aContext, _r.additionalDownloadid, null, _callback,
|
||||
_r.additionalDownloads.toArray(new CloudApiTypes.CloudApiParam[0]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ import com.keyman.engine.KMManager;
|
|||
import com.keyman.engine.KeyboardPickerActivity;
|
||||
import com.keyman.engine.R;
|
||||
import com.keyman.engine.cloud.CloudApiTypes;
|
||||
import com.keyman.engine.cloud.DownloadManagerDisabledException;
|
||||
import com.keyman.engine.cloud.impl.CloudCatalogDownloadCallback;
|
||||
import com.keyman.engine.cloud.impl.CloudCatalogDownloadReturns;
|
||||
import com.keyman.engine.cloud.CloudDataJsonUtil;
|
||||
import com.keyman.engine.cloud.CloudDownloadMgr;
|
||||
import com.keyman.engine.packages.JSONUtils;
|
||||
import com.keyman.engine.util.BCP47;
|
||||
import com.keyman.engine.util.DownloadFileUtils;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.VersionUtils;
|
||||
|
||||
|
|
@ -366,6 +368,10 @@ public class CloudRepository {
|
|||
* @param onFailure A callback to be triggered upon failure of a query.
|
||||
*/
|
||||
private void downloadMetaDataFromServer(@NonNull Context context, UpdateHandler updateHandler, Runnable onSuccess, Runnable onFailure) {
|
||||
if(DownloadFileUtils.getDownloadManager(context) == null) {
|
||||
onFailure.run();
|
||||
return;
|
||||
}
|
||||
boolean cacheValid = getCacheValidity(context);
|
||||
|
||||
// For local and PR test builds, force download of metadata
|
||||
|
|
@ -405,8 +411,13 @@ public class CloudRepository {
|
|||
BaseActivity.makeToast(context, R.string.catalog_download_is_running_in_background, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
updateIsRunning = true;
|
||||
CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
boolean executionStarted = CloudDownloadMgr.getInstance().executeAsDownload(
|
||||
context, DOWNLOAD_IDENTIFIER_CATALOGUE, memCachedDataset, _download_callback, params);
|
||||
if(!executionStarted) {
|
||||
// Since we couldn't initiate the execution's async components,
|
||||
// we need to immediately clear the update-running flag.
|
||||
updateIsRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
return;
|
||||
}
|
||||
|
||||
// Warning: can be run by our system keyboard, which will attempt to
|
||||
// display the toast if it or the app is visible!
|
||||
BaseActivity.makeToast(currentContext, R.string.update_check_unavailable, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
updateCheckFailed = true;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.keyman.engine.util;
|
|||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
|
||||
public final class Connection {
|
||||
|
|
@ -99,6 +100,9 @@ public final class Connection {
|
|||
attempt++;
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
KMLog.LogBreadcrumb(TAG, "Connection.initialize timeout for: " + originalUrl, true);
|
||||
KMLog.LogInfo(TAG, "SocketTimeoutException");
|
||||
} catch (Exception e) {
|
||||
KMLog.LogException(TAG, "Initialization failed:", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
*/
|
||||
package com.keyman.engine.util;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
|
|
@ -21,6 +23,37 @@ import java.io.InputStream;
|
|||
public final class DownloadFileUtils {
|
||||
private static final String TAG = "DownloadFileUtils";
|
||||
|
||||
private static final String DOWNLOAD_MANAGER_PACKAGE_NAME = "com.android.providers.downloads";
|
||||
|
||||
/**
|
||||
* Determines whether or not Android's `DownloadManager` service is active, only
|
||||
* returning an instance of DownloadManager when it is currently accessible and enabled.
|
||||
* Downloads and cloud queries are impossible when it's disabled.
|
||||
*
|
||||
* This solution is based heavily on
|
||||
* https://gist.github.com/Folyd/b9412bb6e2b06eb511f7.
|
||||
* @return A valid and enabled reference to the system's DownloadManager service. May be null
|
||||
* if it is not accessible or is disabled.
|
||||
*/
|
||||
public static DownloadManager getDownloadManager(Context context) {
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
if(downloadManager==null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int state = context.getPackageManager().getApplicationEnabledSetting(DOWNLOAD_MANAGER_PACKAGE_NAME);
|
||||
|
||||
if(
|
||||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
|
||||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER ||
|
||||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|
||||
) {
|
||||
return null;
|
||||
};
|
||||
|
||||
return downloadManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small class for returning information about a file downloaded via DownloadManager.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
package com.keyman.engine.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebView;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
@ -22,6 +24,14 @@ public final class WebViewUtils {
|
|||
FULL; // WebView supports touch keyboards and LDML keyboards
|
||||
}
|
||||
|
||||
// System WebView status
|
||||
public enum SystemWebViewStatus {
|
||||
UNDETERMINED, // Functionality not determined yet
|
||||
NOT_INSTALLED, // WebView not installed
|
||||
DISABLED, // WebView installed, but disabled
|
||||
FULL; // WebView installed and enabled
|
||||
}
|
||||
|
||||
private static final String CHROME_INSTALL_PATTERN_FORMATSTR = "^.*Chrome/([\\d.]+).*$";
|
||||
private static final Pattern installPattern = Pattern.compile(CHROME_INSTALL_PATTERN_FORMATSTR);
|
||||
|
||||
|
|
@ -51,6 +61,34 @@ public final class WebViewUtils {
|
|||
return EngineWebViewVersionStatus.FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the state of the system WebView (required for Keyman)
|
||||
* @param context
|
||||
* @return {SystemWebViewStatus}
|
||||
*/
|
||||
public static SystemWebViewStatus getSystemWebViewStatus(
|
||||
Context context) {
|
||||
// Check whether WebView is installed
|
||||
// If the package is installed but disabled, hasSystemFeature() still returns true
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (!packageManager.hasSystemFeature("android.software.webview")) {
|
||||
return SystemWebViewStatus.NOT_INSTALLED;
|
||||
}
|
||||
|
||||
// Use CookieManager to check if WebView is really enabled
|
||||
// https://stackoverflow.com/a/70354583
|
||||
try {
|
||||
CookieManager.getInstance();
|
||||
} catch (AndroidRuntimeException e) {
|
||||
// WebView installed but not enabled
|
||||
return SystemWebViewStatus.DISABLED;
|
||||
} catch (Exception e) {
|
||||
return SystemWebViewStatus.DISABLED;
|
||||
}
|
||||
|
||||
return SystemWebViewStatus.FULL;
|
||||
}
|
||||
|
||||
// Inject a meta viewport tag into the head of the file if it doesn't exist
|
||||
public static void injectViewport(WebView webView) {
|
||||
if (webView != null) {
|
||||
|
|
|
|||
|
|
@ -133,10 +133,15 @@
|
|||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">Τὸ πληκτρολόγιο %1$s ἐγκατεστάθη</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">Τὸ πληκτρολόγιο διεγράφη</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">Ἐνεργοποιῆστε τὶς διορθώσεις</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">Ἐνεργοποιῆστε προβλέψεις κειμένου</string>
|
||||
<!-- Context: Language Settings menu strings - radio buttons -->
|
||||
<string name="suggestions_radio_0" comment="Predictions and corrections disabled">Ἀπενεργοποίηση προτάσεων</string>
|
||||
<string name="suggestions_radio_1" comment="Suggestions Enabled: Predictions with no corrections">Μόνο προβλέψεις</string>
|
||||
<string name="suggestions_radio_2" comment="Suggestions Enabled: Predictions with corrections">Προβλέψεις καὶ διορθώσεις</string>
|
||||
<string name="suggestions_radio_3" comment="Suggestions Enabled: Predictions with auto-corrections">Προβλέψεις μὲ αὐτόματες διορθώσεις</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Λεξικό</string>
|
||||
<plurals name="model_count">
|
||||
|
|
|
|||
|
|
@ -131,10 +131,15 @@
|
|||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">ကဳဗုဒ် %1$s စုတ်လဝ်တုဲ</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">ဇိုတ် ကဳဗုဒ်တုဲ</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">ပံက် ပလေဝ်ဒါန်</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">ပံက် တၚ်ချိၚ်ခယျဂမၠိုၚ်</string>
|
||||
<!-- Context: Language Settings menu strings - radio buttons -->
|
||||
<string name="suggestions_radio_0" comment="Predictions and corrections disabled">Disable suggestions</string>
|
||||
<string name="suggestions_radio_1" comment="Suggestions Enabled: Predictions with no corrections">တၚ်ချိၚ်ခယျဂမၠိုၚ်ဟေၚ်</string>
|
||||
<string name="suggestions_radio_2" comment="Suggestions Enabled: Predictions with corrections">တၚ်ချၚ်ခယျ ကေုာံ တၚ်ပလေဝ်ဂမၠိုၚ်</string>
|
||||
<string name="suggestions_radio_3" comment="Suggestions Enabled: Predictions with auto-corrections">တၚ်ချိၚ်ခယျနကဵု တၚ်ပလေဝ်အဝ်တဵု</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">အဘိဓာန်</string>
|
||||
<plurals name="model_count">
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
<!-- Context: Title for list -->
|
||||
<plurals name="title_keyboards">
|
||||
<item quantity="one">Keyboard</item>
|
||||
<item quantity="other">Keyboards</item>
|
||||
<item quantity="one">Keyboard</item>
|
||||
<item quantity="other">Keyboards</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Context: Title for list -->
|
||||
<plurals name="title_other_input_methods">
|
||||
<item quantity="one">Other Input Method</item>
|
||||
<item quantity="other">Other Input Methods</item>
|
||||
<item quantity="one">Other Input Method</item>
|
||||
<item quantity="other">Other Input Methods</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Context: Title for list -->
|
||||
|
|
@ -127,10 +127,24 @@
|
|||
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard">
|
||||
Fatal keyboard error with %1$s:%2$s for %3$s language. Loading default keyboard.</string>
|
||||
|
||||
<!-- Context: Fatal keyboard error. -->
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language).">
|
||||
<!-- Context: Fatal keyboard error. -->
|
||||
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language).">
|
||||
Error in keyboard %1$s:%2$s for %3$s language.</string>
|
||||
|
||||
<!-- Context: Alert Dialog body: System WebView not installed -->
|
||||
<string name="body_install_webview" comment="Notification when WebView not installed">
|
||||
Keyman requires WebView to be installed."</string>
|
||||
|
||||
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
|
||||
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled">
|
||||
Keyman requires WebView to be enabled."</string>
|
||||
|
||||
<!-- Context Button label: Install WebView -->
|
||||
<string name="label_install_webview" comment="Button label to install webview">Install WebView</string>
|
||||
|
||||
<!-- Context Button label: Enable WebView -->
|
||||
<string name="label_enable_webview" comment="Button label to enable webview">Enable WebView</string>
|
||||
|
||||
<!-- Context: Query for associated dictionary -->
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Checking for associated dictionary to download</string>
|
||||
|
||||
|
|
@ -184,6 +198,9 @@
|
|||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Failed to access server!</string>
|
||||
|
||||
<!-- Context: Update-check failure notification -->
|
||||
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager disabled - cannot check for updates</string>
|
||||
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_current" comment="Notification that all resources are up to date">"All resources are up to date!"</string>
|
||||
|
||||
|
|
@ -229,8 +246,8 @@
|
|||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Dictionary</string>
|
||||
<plurals name="model_count">
|
||||
<item quantity="one">Dictionary</item>
|
||||
<item quantity="other">Dictionaries</item>
|
||||
<item quantity="one">Dictionary</item>
|
||||
<item quantity="other">Dictionaries</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Context: Language Settings menu substring - Check for available dictionary -->
|
||||
|
|
@ -251,8 +268,8 @@
|
|||
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<plurals name="keyboard_count">
|
||||
<item quantity="one">(%1$d keyboard)</item>
|
||||
<item quantity="other">(%1$d keyboards)</item>
|
||||
<item quantity="one">(%1$d keyboard)</item>
|
||||
<item quantity="other">(%1$d keyboards)</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
|
|
@ -278,4 +295,5 @@
|
|||
|
||||
<!-- Context: anywhere -->
|
||||
<string name="unable_to_open_browser" comment="Notification when a browser activity cannot be launched">Unable to launch web browser</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="AppTheme.NoTitleDialog" parent="AppTheme.Light.Dialog">
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
|
|
|
|||
|
|
@ -34,5 +34,4 @@ The following code illustrates the use of `getLongpressDelay()`:
|
|||
Keyman Engine for Android 18.0: New function.
|
||||
|
||||
## See also
|
||||
* [sendOptionsToKeyboard](sendOptionsToKeyboard)
|
||||
* [setLongpressDelay](setLongpressDelay)
|
||||
|
|
|
|||
|
|
@ -212,9 +212,6 @@ The KMManager is the core class which provides most of the methods and constants
|
|||
[`removeKeyboardEventListener()`](removeKeyboardEventListener)
|
||||
: removes the specified listener from the list of keyboard event listeners
|
||||
|
||||
[`sendOptionsToKeyboard()`](sendOptionsToKeyboard)
|
||||
: sends options like longpress delay to the KeymanWeb keyboard
|
||||
|
||||
[`setCanAddNewKeyboard()`](setCanAddNewKeyboard)
|
||||
: sets whether adding a new keyboard is allowed
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
---
|
||||
title: KMManager.sendOptionsToKeyboard()
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The `sendOptionsToKeyboard()` method sends options like longpress delay to the KeymanWeb keyboard.
|
||||
|
||||
## Syntax
|
||||
|
||||
```java
|
||||
KMManager.sendOptionsToKeyboard()
|
||||
```
|
||||
|
||||
## Description
|
||||
Use this method to update options in the KeymanWeb keyboard.
|
||||
* Number of milliseconds to trigger a longpress gesture
|
||||
|
||||
This method requires a keyboard to be loaded for the values to take effect.
|
||||
|
||||
## Examples
|
||||
|
||||
### Example: Using `sendOptionsToKeyboard()`
|
||||
|
||||
The following code illustrates the use of `sendOptionsToKeyboard()`:
|
||||
```java
|
||||
int currentDelayTimeMS = 250;
|
||||
|
||||
// Store currentDelayTimeMS
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
|
||||
// Apply the keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
```
|
||||
|
||||
## History
|
||||
Keyman Engine for Android 18.0: New function.
|
||||
|
||||
## See also
|
||||
* [getLongpressDelay](getLongpressDelay)
|
||||
* [setLongpressDelay](setLongpressDelay)
|
||||
|
|
@ -4,7 +4,7 @@ title: KMManager.setLongpressDelay()
|
|||
|
||||
## Summary
|
||||
|
||||
The `setLongpressDelay()` method sets the number of milliseconds to trigger a longpress gesture as a stored preference.
|
||||
The `setLongpressDelay()` method sets the number of milliseconds to trigger a longpress gesture, applies to the keyboard, and stores preference.
|
||||
|
||||
## Syntax
|
||||
|
||||
|
|
@ -27,11 +27,8 @@ The following code illustrates the use of `setLongpressDelay()`:
|
|||
```java
|
||||
int currentDelayTimeMS = 300;
|
||||
|
||||
// Store currentDelayTimeMS
|
||||
// Store currentDelayTimeMS and applies
|
||||
KMManager.setLongpressDelay(currentDelayTimeMS);
|
||||
|
||||
// Apply the keyboard options
|
||||
KMManager.sendOptionsToKeyboard();
|
||||
```
|
||||
|
||||
## History
|
||||
|
|
@ -39,4 +36,3 @@ Keyman Engine for Android 18.0: New function.
|
|||
|
||||
## See also
|
||||
* [getLongpressDelay](getLongpressDelay)
|
||||
* [sendOptionsToKeyboard](sendOptionsToKeyboard)
|
||||
|
|
|
|||
BIN
common/resources/bitmaps/windows/kmshell/FullSizeCtrlK.png
Normal file
BIN
common/resources/bitmaps/windows/kmshell/FullSizeCtrlK.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
common/resources/bitmaps/windows/kmshell/FullSizeKeyboard.png
Normal file
BIN
common/resources/bitmaps/windows/kmshell/FullSizeKeyboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
BIN
common/resources/bitmaps/windows/kmshell/FullSizeUpdate.png
Normal file
BIN
common/resources/bitmaps/windows/kmshell/FullSizeUpdate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
BIN
common/resources/bitmaps/windows/kmshell/FullSizeWrench.png
Normal file
BIN
common/resources/bitmaps/windows/kmshell/FullSizeWrench.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
Binary file not shown.
1
common/test/keyboards/baseline/.gitignore
vendored
Normal file
1
common/test/keyboards/baseline/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
kmcomp/
|
||||
36
common/test/keyboards/baseline/build.sh
Executable file
36
common/test/keyboards/baseline/build.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
builder_describe "Baseline keyboards tests -- built with 16.0 compiler" \
|
||||
clean \
|
||||
configure \
|
||||
build
|
||||
|
||||
builder_describe_platform \
|
||||
:project win
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
builder_describe_outputs \
|
||||
configure kmcomp/kmcomp.exe \
|
||||
build k_000___null_keyboard.kmx
|
||||
|
||||
do_configure() {
|
||||
mkdir -p kmcomp
|
||||
unzip kmcomp-16.0.138.zip -d kmcomp/
|
||||
}
|
||||
|
||||
do_build() {
|
||||
local name
|
||||
for name in *.kmn; do
|
||||
./kmcomp/kmcomp.exe -no-compiler-version -d "$name"
|
||||
done
|
||||
}
|
||||
|
||||
builder_run_action clean rm -rf *.kmx ./kmcomp/
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action build do_build
|
||||
15
common/test/keyboards/baseline/k_050___nul_and_context.kmn
Normal file
15
common/test/keyboards/baseline/k_050___nul_and_context.kmn
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
store(&NAME) '050 - nul_and_context'
|
||||
c Description: Tests that context has the right offset when used with nul
|
||||
c keys: [K_T][K_B]
|
||||
c expected: 2t3
|
||||
c context:
|
||||
|
||||
store(&version) '10.0'
|
||||
|
||||
begin Unicode > use(Main)
|
||||
|
||||
store(cons) 'mnpqrstv'
|
||||
|
||||
group(Main) using keys
|
||||
|
||||
nul any(cons) + 'b' > '2' context(2) '3'
|
||||
BIN
common/test/keyboards/baseline/k_050___nul_and_context.kmx
Normal file
BIN
common/test/keyboards/baseline/k_050___nul_and_context.kmx
Normal file
Binary file not shown.
16
common/test/keyboards/baseline/k_051___if_and_context.kmn
Normal file
16
common/test/keyboards/baseline/k_051___if_and_context.kmn
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
store(&NAME) '051 - if_and_context'
|
||||
c Description: Tests that context has the right offset when used with if
|
||||
c keys: [K_T][K_D]
|
||||
c expected: 4t5
|
||||
c context:
|
||||
|
||||
store(&version) '10.0'
|
||||
|
||||
begin Unicode > use(Main)
|
||||
|
||||
store(cons) 'mnpqrstv'
|
||||
store(ifx) '1'
|
||||
|
||||
group(Main) using keys
|
||||
|
||||
if(ifx = '1') any(cons) + 'd' > '4' context(2) '5'
|
||||
BIN
common/test/keyboards/baseline/k_051___if_and_context.kmx
Normal file
BIN
common/test/keyboards/baseline/k_051___if_and_context.kmx
Normal file
Binary file not shown.
16
common/test/keyboards/baseline/k_052___nul_and_index.kmn
Normal file
16
common/test/keyboards/baseline/k_052___nul_and_index.kmn
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
store(&NAME) '052 - nul_and_index'
|
||||
c Description: Tests that context has the right offset when used with nul
|
||||
c keys: [K_T][K_B]
|
||||
c expected: 2T3
|
||||
c context:
|
||||
|
||||
store(&version) '10.0'
|
||||
|
||||
begin Unicode > use(Main)
|
||||
|
||||
store(cons) 'mnpqrstv'
|
||||
store(outs) 'MNPQRSTV'
|
||||
|
||||
group(Main) using keys
|
||||
|
||||
nul any(cons) + 'b' > '2' index(outs, 2) '3'
|
||||
BIN
common/test/keyboards/baseline/k_052___nul_and_index.kmx
Normal file
BIN
common/test/keyboards/baseline/k_052___nul_and_index.kmx
Normal file
Binary file not shown.
18
common/test/keyboards/baseline/k_053___if_and_index.kmn
Normal file
18
common/test/keyboards/baseline/k_053___if_and_index.kmn
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
store(&NAME) '053 - if_and_index'
|
||||
c Description: Tests that index() has the right offset when used with if
|
||||
c keys: [K_T][K_D]
|
||||
c expected: 4T5
|
||||
c context:
|
||||
|
||||
store(&version) '10.0'
|
||||
|
||||
begin Unicode > use(Main)
|
||||
|
||||
store(cons) 'mnpqrstv'
|
||||
store(outs) 'MNPQRSTV'
|
||||
store(ifx) '1'
|
||||
|
||||
group(Main) using keys
|
||||
|
||||
if(ifx = '1') any(cons) + 'd' > '4' index(outs, 2) '5'
|
||||
|
||||
BIN
common/test/keyboards/baseline/k_053___if_and_index.kmx
Normal file
BIN
common/test/keyboards/baseline/k_053___if_and_index.kmx
Normal file
Binary file not shown.
BIN
common/test/keyboards/baseline/kmcomp-16.0.138.zip
Normal file
BIN
common/test/keyboards/baseline/kmcomp-16.0.138.zip
Normal file
Binary file not shown.
|
|
@ -5,7 +5,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
builder_describe "Common Delphi components" clean configure build test
|
||||
builder_describe "Common Delphi components" clean configure build test edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -28,3 +28,4 @@ builder_run_action clean:project clean_windows_project_files
|
|||
builder_run_action configure:project configure_windows_build_environment
|
||||
builder_run_action build:project do_build
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start common_components.dproj
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ type
|
|||
const S_CustomisationFilename = 'desktop_pro.pxx';
|
||||
|
||||
const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\';
|
||||
|
||||
class var FHaveCachedKeymanRoot: Boolean;
|
||||
class var FCachedKeymanRoot: string;
|
||||
public
|
||||
const S_KMShell = 'kmshell.exe';
|
||||
const S_TSysInfoExe = 'tsysinfo.exe';
|
||||
|
|
@ -434,12 +437,21 @@ end;
|
|||
|
||||
class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean;
|
||||
begin
|
||||
if FHaveCachedKeymanRoot then
|
||||
begin
|
||||
keyman_root := FCachedKeymanRoot;
|
||||
Exit(keyman_root <> '');
|
||||
end;
|
||||
// On developer machines, if we are running within the source repo, then use
|
||||
// those paths
|
||||
keyman_root := GetEnvironmentVariable('KEYMAN_ROOT');
|
||||
keyman_root := GetEnvironmentVariable('KEYMAN_ROOT').Replace('/', '\', [rfReplaceAll]);
|
||||
if keyman_root <> '' then
|
||||
keyman_root := IncludeTrailingPathDelimiter(keyman_root);
|
||||
Result := (keyman_root <> '') and SameText(keyman_root, ParamStr(0).Substring(0, keyman_root.Length));
|
||||
|
||||
FHaveCachedKeymanRoot := True;
|
||||
if Result then
|
||||
FCachedKeymanRoot := keyman_root;
|
||||
end;
|
||||
|
||||
class function TKeymanPaths.KeymanCoreLibraryPath(const Filename: string): string;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
builder_describe "Build build_standards_data tool" clean configure build test
|
||||
builder_describe "Build build_standards_data tool" clean configure build test edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -49,3 +49,4 @@ builder_run_action clean:project clean_windows_project_files
|
|||
builder_run_action configure:project configure_windows_build_environment
|
||||
builder_run_action build:project do_build
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start build_standards_data.dproj
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
|
||||
builder_describe "Unicode character database build tool" \
|
||||
@/common/windows/delphi:keymanversion \
|
||||
clean configure build test
|
||||
clean configure build test edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -30,3 +30,4 @@ builder_run_action clean:project clean_windows_project_files
|
|||
builder_run_action configure:project configure_windows_build_environment
|
||||
builder_run_action build:project do_build
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start buildunidata.dproj
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
builder_describe \
|
||||
"Development and build utilities for Delphi" \
|
||||
@/common/windows/delphi:keymanversion \
|
||||
clean configure build test prepublish
|
||||
clean configure build test prepublish edit
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -30,3 +30,4 @@ builder_run_action configure:project configure_windows_build_environment
|
|||
builder_run_action build:project delphi_msbuild devtools.dproj "//p:Platform=Win32"
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action prepublish:project "$DEVTOOLS" -rt
|
||||
builder_run_action edit:project start devtools.dproj
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
builder_describe "Sentrytool for converting Delphi symbols into sentry-readable format" clean configure build test
|
||||
builder_describe "Sentrytool for converting Delphi symbols into sentry-readable format" \
|
||||
clean configure build test edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -30,3 +31,4 @@ builder_run_action clean:project clean_windows_project_files
|
|||
builder_run_action configure:project configure_windows_build_environment
|
||||
builder_run_action build:project delphi_msbuild sentrytool.dproj "//p:Platform=Win32"
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start sentrytool.dproj
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
builder_describe "Tool for validating klog is disabled for release builds" clean configure build test prepublish
|
||||
builder_describe "Tool for validating klog is disabled for release builds" \
|
||||
clean configure build test prepublish edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -33,3 +34,4 @@ builder_run_action configure:project configure_windows_build_environment
|
|||
builder_run_action build:project do_build
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action prepublish:project "$WIN32_TARGET_PATH/test_klog.exe"
|
||||
builder_run_action edit:project start test_klog.dproj
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
builder_describe "Tool to verify all Windows executable signatures and manifests" clean configure build test verify
|
||||
builder_describe "Tool to verify all Windows executable signatures and manifests" \
|
||||
clean configure build test verify edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -27,3 +28,4 @@ builder_run_action clean:project clean_windows_project_files
|
|||
builder_run_action configure:project configure_windows_build_environment
|
||||
builder_run_action build:project do_build
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start verify_signatures.dproj
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ archtargets=(":wasm WASM build")
|
|||
case $BUILDER_OS in
|
||||
win)
|
||||
archtargets+=(
|
||||
":win Both x86 and x64"
|
||||
":x86 32-bit Windows (x86) build"
|
||||
":x64 64-bit Windows (x64) build"
|
||||
)
|
||||
|
|
@ -92,9 +93,12 @@ fi
|
|||
# generates a 'fat' library from them.
|
||||
builder_describe_internal_dependency \
|
||||
build:mac build:mac-x86_64 \
|
||||
build:mac build:mac-arm64
|
||||
build:mac build:mac-arm64 \
|
||||
build:win build:x86 \
|
||||
build:win build:x64
|
||||
|
||||
builder_describe_outputs \
|
||||
configure:win /core/build/win/$BUILDER_CONFIGURATION/ \
|
||||
configure:x86 /core/build/x86/$BUILDER_CONFIGURATION/build.ninja \
|
||||
configure:x64 /core/build/x64/$BUILDER_CONFIGURATION/build.ninja \
|
||||
configure:mac /core/build/mac/$BUILDER_CONFIGURATION/ \
|
||||
|
|
@ -102,6 +106,7 @@ builder_describe_outputs \
|
|||
configure:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/build.ninja \
|
||||
configure:arch /core/build/arch/$BUILDER_CONFIGURATION/build.ninja \
|
||||
configure:wasm /core/build/wasm/$BUILDER_CONFIGURATION/build.ninja \
|
||||
build:win /core/build/win/$BUILDER_CONFIGURATION/BUILT \
|
||||
build:x86 /core/build/x86/$BUILDER_CONFIGURATION/src/libkeymancore.a \
|
||||
build:x64 /core/build/x64/$BUILDER_CONFIGURATION/src/libkeymancore.a \
|
||||
build:mac /core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a \
|
||||
|
|
@ -139,24 +144,21 @@ do_action clean
|
|||
|
||||
do_action configure
|
||||
|
||||
# After we have built the necessary internal dependencies, then we can go
|
||||
# ahead and build a fat library for external consumption
|
||||
if builder_start_action configure:mac; then
|
||||
mkdir -p "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION"
|
||||
builder_finish_action success configure:mac
|
||||
fi
|
||||
# After we have built the necessary internal dependencies, then we can go ahead
|
||||
# and build a fat library for external consumption for mac, and no-op for win
|
||||
builder_run_action configure:mac mkdir -p "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION"
|
||||
builder_run_action configure:win mkdir -p "$KEYMAN_ROOT/core/build/win/$BUILDER_CONFIGURATION"
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
do_action build
|
||||
|
||||
if builder_start_action build:mac; then
|
||||
lipo -create \
|
||||
"$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \
|
||||
"$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \
|
||||
-output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a"
|
||||
builder_finish_action success build:mac
|
||||
fi
|
||||
builder_run_action build:mac lipo -create \
|
||||
"$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \
|
||||
"$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \
|
||||
-output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a"
|
||||
|
||||
builder_run_action build:win touch "$KEYMAN_ROOT/core/build/win/$BUILDER_CONFIGURATION/BUILT"
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -177,6 +179,15 @@ if builder_start_action test:mac; then
|
|||
builder_finish_action success test:mac
|
||||
fi
|
||||
|
||||
if builder_start_action test:win; then
|
||||
# We can assume that build:win has run so both architectures will be available
|
||||
MESON_PATH="$KEYMAN_ROOT/core/build/x86/$BUILDER_CONFIGURATION"
|
||||
meson test -C "$MESON_PATH" $testparams
|
||||
MESON_PATH="$KEYMAN_ROOT/core/build/x64/$BUILDER_CONFIGURATION"
|
||||
meson test -C "$MESON_PATH" $testparams
|
||||
builder_finish_action success test:win
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
do_action install
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ KMX_ProcessEvent::KMX_ProcessEvent() : m_actions(&m_context), m_options(&m_keybo
|
|||
m_indexStack = new KMX_WORD[GLOBAL_ContextStackSize];
|
||||
m_miniContext = new KMX_WCHAR[GLOBAL_ContextStackSize];
|
||||
m_miniContextIfLen = 0;
|
||||
m_miniContextStartsWithNul = FALSE;
|
||||
m_debug_items = nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -338,10 +339,14 @@ KMX_BOOL KMX_ProcessEvent::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke)
|
|||
m_miniContextIfLen = xstrlen(kkp->dpContext) - xstrlen_ignoreifopt(kkp->dpContext);
|
||||
|
||||
// 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support
|
||||
if(*kkp->dpContext == UC_SENTINEL && *(kkp->dpContext+1) == CODE_NUL)
|
||||
if(*kkp->dpContext == UC_SENTINEL && *(kkp->dpContext+1) == CODE_NUL) {
|
||||
m_miniContextStartsWithNul = TRUE;
|
||||
u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)-1) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536
|
||||
else
|
||||
}
|
||||
else {
|
||||
m_miniContextStartsWithNul = FALSE;
|
||||
u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536
|
||||
}
|
||||
|
||||
m_miniContext[GLOBAL_ContextStackSize-1] = 0;
|
||||
|
||||
|
|
@ -458,7 +463,12 @@ int KMX_ProcessEvent::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR end
|
|||
}
|
||||
case CODE_CONTEXTEX:
|
||||
p++;
|
||||
for (q = m_miniContext, i = m_miniContextIfLen; *q && i < *p - 1; i++, q = incxstr(q));
|
||||
// We have to exclude nul and if() statements from our offset calculation
|
||||
i = m_miniContextIfLen;
|
||||
if(m_miniContextStartsWithNul) {
|
||||
i++;
|
||||
}
|
||||
for (q = m_miniContext; *q && i < *p - 1; i++, q = incxstr(q));
|
||||
if (*q) {
|
||||
KMX_BOOL ignoreOutputKeystroke;
|
||||
temp = incxstr(q);
|
||||
|
|
@ -600,6 +610,7 @@ KMX_BOOL KMX_ProcessEvent::ContextMatch(LPKEY kkp)
|
|||
memset(m_indexStack, 0, GLOBAL_ContextStackSize*sizeof(KMX_WORD));
|
||||
|
||||
p = kkp->dpContext;
|
||||
indexp = m_indexStack;
|
||||
|
||||
if(*p == 0)
|
||||
{
|
||||
|
|
@ -611,6 +622,7 @@ KMX_BOOL KMX_ProcessEvent::ContextMatch(LPKEY kkp)
|
|||
// If context buf is longer than the context, then obviously not start of doc.
|
||||
if(m_context.Buf(xstrlen_ignoreifopt(p))) return FALSE;
|
||||
p = incxstr(p);
|
||||
indexp++;
|
||||
if(*p == 0) return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -660,7 +672,6 @@ KMX_BOOL KMX_ProcessEvent::ContextMatch(LPKEY kkp)
|
|||
{
|
||||
return FALSE; // context buf is too short!
|
||||
}
|
||||
indexp = m_indexStack;
|
||||
|
||||
for(; *p && *q; p = incxstr(p))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ class KMX_ProcessEvent {
|
|||
private:
|
||||
PKMX_WORD m_indexStack;
|
||||
PKMX_WCHAR m_miniContext;
|
||||
int m_miniContextIfLen; // number of if() statements excluded from start of m_miniContext
|
||||
/** number of `if()` statements excluded from start of `m_miniContext` for offset calculations */
|
||||
int m_miniContextIfLen;
|
||||
/** flag if we need to account for `nul` in offset calculations */
|
||||
bool m_miniContextStartsWithNul;
|
||||
KMSTATE m_state;
|
||||
km_core_state *m_core_state;
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ tests = [
|
|||
'k_046___deadkey_and_contextex',
|
||||
'k_047___caps_always_off_initially_on',
|
||||
'k_048___modifier_keys_keep_context',
|
||||
'k_049___enter_invalidates_context'
|
||||
'k_049___enter_invalidates_context',
|
||||
'k_050___nul_and_context',
|
||||
'k_051___if_and_context',
|
||||
'k_052___nul_and_index',
|
||||
'k_053___if_and_index',
|
||||
]
|
||||
|
||||
node = find_program('node', required: true)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
builder_describe \
|
||||
"Keyman Developer Delphi components" \
|
||||
@/common/windows/delphi \
|
||||
clean configure build test
|
||||
clean configure build test edit
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -33,3 +33,4 @@ builder_run_action clean:project clean_windows_project_files
|
|||
builder_run_action configure:project configure_windows_build_environment
|
||||
builder_run_action build:project do_build
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start delphi_components.dproj
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ type
|
|||
procedure WndProc(var Message: TMessage); override;
|
||||
public
|
||||
constructor Create(AOwner: TComponent); override;
|
||||
function GetTextCR: string;
|
||||
function GetSelTextCR: string;
|
||||
published
|
||||
property AllowUnicode: Boolean read FAllowUnicodeInput write SetAllowUnicode default True;
|
||||
property OnMessage: TKeymanDeveloperDebuggerMessageEvent read FOnMessage write FOnMessage;
|
||||
|
|
@ -65,6 +67,9 @@ procedure Register;
|
|||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils;
|
||||
|
||||
{ TKeymanDeveloperDebuggerMemo }
|
||||
|
||||
procedure TKeymanDeveloperDebuggerMemo.CNKeyDown(var Message: TWMKeyDown);
|
||||
|
|
@ -123,6 +128,16 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
function TKeymanDeveloperDebuggerMemo.GetSelTextCR: string;
|
||||
begin
|
||||
Result := SelText.Replace(#$0D#$0A, #$0D, [rfReplaceAll]);
|
||||
end;
|
||||
|
||||
function TKeymanDeveloperDebuggerMemo.GetTextCR: string;
|
||||
begin
|
||||
Result := string(Text).Replace(#$0D#$0A, #$0D, [rfReplaceAll]);
|
||||
end;
|
||||
|
||||
procedure TKeymanDeveloperDebuggerMemo.SetAllowUnicode(const Value: Boolean);
|
||||
begin
|
||||
if Value <> fAllowUnicodeInput then
|
||||
|
|
|
|||
|
|
@ -272,6 +272,9 @@ namespace KmnCompilerMessages {
|
|||
|
||||
HINT_IndexStoreLong = SevHint | 0x0B0,
|
||||
|
||||
ERROR_ContextExCannotReferenceIf = SevError | 0x0B1,
|
||||
ERROR_ContextExCannotReferenceNul = SevError | 0x0B2,
|
||||
|
||||
FATAL_BufferOverflow = SevFatal | 0x0C0
|
||||
// FATAL_Break = SevFatal | 0x0C1, unused
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,11 +25,7 @@ export class CopierMessages {
|
|||
o.e ?? 'unknown error'
|
||||
);
|
||||
|
||||
static INFO_CopyingProject = SevInfo | 0x0002;
|
||||
static Info_CopyingProject = (o:{type: string, id: string}) => m(
|
||||
this.INFO_CopyingProject,
|
||||
`Copying project of type ${def(o.type)} with id ${def(o.id)}`
|
||||
);
|
||||
// unused 0x0002 (was INFO_CopyingProject, now in InfrastructureMessages)
|
||||
|
||||
static ERROR_CannotCreateFolder = SevError | 0x0003;
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.Generator;
|
||||
const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
|
||||
const SevWarn = CompilerErrorSeverity.Warn | Namespace;
|
||||
const SevError = CompilerErrorSeverity.Error | Namespace;
|
||||
|
|
@ -23,11 +23,7 @@ export class GeneratorMessages {
|
|||
o.e ?? 'unknown error',
|
||||
);
|
||||
|
||||
static INFO_GeneratingProject = SevInfo | 0x0002;
|
||||
static Info_GeneratingProject = (o:{type: string, id: string}) => m(
|
||||
this.INFO_GeneratingProject,
|
||||
`Generating project of type ${def(o.type)} with id ${def(o.id)}`
|
||||
);
|
||||
// unused 0x0002 (was INFO_GeneratingProject, now in InfrastructureMessages)
|
||||
|
||||
static ERROR_CannotCreateFolder = SevError | 0x0003;
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
|
||||
|
|
|
|||
|
|
@ -737,7 +737,19 @@ export class KmnCompilerMessages {
|
|||
static HINT_IndexStoreLong = SevHint | 0x0B0;
|
||||
static Hint_IndexStoreLong = () => m(
|
||||
this.HINT_IndexStoreLong,
|
||||
`The store referenced in index() is longer than the store referenced in any()`
|
||||
`The store referenced in index() is longer than the store referenced in any()`,
|
||||
);
|
||||
|
||||
static ERROR_ContextExCannotReferenceIf = SevError | 0x0B1;
|
||||
static Error_ContextExCannotReferenceIf = () => m(
|
||||
this.ERROR_ContextExCannotReferenceIf,
|
||||
`The offset in context() points to a non-character if() statement in the context`,
|
||||
);
|
||||
|
||||
static ERROR_ContextExCannotReferenceNul = SevError | 0x0B2;
|
||||
static Error_ContextExCannotReferenceNul = () => m(
|
||||
this.ERROR_ContextExCannotReferenceNul,
|
||||
`The offset in context() points to a non-character nul statement in the context`,
|
||||
);
|
||||
|
||||
static FATAL_BufferOverflow = SevFatal | 0x0C0;
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
|
|||
let recContext = ExpandSentinel(fk, pwszContext, x);
|
||||
|
||||
if(isKeyboardVersion10OrLater()) {
|
||||
if(recContext.IsSentinel && [KMX.KMXFile.CODE_NUL, KMX.KMXFile.CODE_IFOPT, KMX.KMXFile.CODE_IFSYSTEMSTORE].includes(recContext.Code)) {
|
||||
if(recContext.IsSentinel && [KMX.KMXFile.CODE_IFOPT, KMX.KMXFile.CODE_IFSYSTEMSTORE].includes(recContext.Code)) {
|
||||
Result--;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_cannot_reference_if.kmn
vendored
Normal file
11
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_cannot_reference_if.kmn
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
store(&TARGETS) 'any'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
store(cons) 'bcd'
|
||||
store(ifx) '1'
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c ERROR_ContextExCannotReferenceIf:
|
||||
if(ifx = '1') any(cons) + 'd' > context(1)
|
||||
10
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_cannot_reference_nul.kmn
vendored
Normal file
10
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_cannot_reference_nul.kmn
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
store(&TARGETS) 'any'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
store(cons) 'bcd'
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c ERROR_ContextExCannotReferenceNul:
|
||||
nul any(cons) + 'a' > 'x' context(1)
|
||||
12
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_has_invalid_offset_if.kmn
vendored
Normal file
12
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_has_invalid_offset_if.kmn
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
store(&TARGETS) 'any'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
store(cons) 'bcd'
|
||||
store(ifx) '1'
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c ERROR_ContextExHasInvalidOffset: `context(3)` should be `context(2)`
|
||||
if(ifx='1') any(cons) + 'a' > 'x' context(3) 'z'
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
store(&TARGETS) 'any'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
store(cons) 'bcd'
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c ERROR_ContextExHasInvalidOffset: `context(2)` is longer than range
|
||||
any(cons) + 'a' > 'x' context(2) 'z'
|
||||
|
||||
12
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_has_invalid_offset_nul.kmn
vendored
Normal file
12
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_contextex_has_invalid_offset_nul.kmn
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
store(&TARGETS) 'any'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
store(cons) 'bcd'
|
||||
store(ifx) '1'
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c ERROR_ContextExHasInvalidOffset: `context(3)` should be `context(2)`
|
||||
nul any(cons) + 'a' > 'x' context(3) 'z'
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
store(&TARGETS) 'any'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
store(cons) 'bcd'
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c ERROR_ContextExHasInvalidOffset: `context(0)` is lower than 1
|
||||
any(cons) + 'a' > 'x' context(0) 'z'
|
||||
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
c test_index_12980 generated from template at 2025-01-22 13:25:35
|
||||
c Validates index() offsets when used with if() - see #12980
|
||||
c Test with v10+ kmw compiler
|
||||
|
||||
store(&VERSION) '14.0'
|
||||
store(&NAME) 'test_index_12980'
|
||||
store(&NAME) 'test_index_12980_v10'
|
||||
store(&TARGETS) 'web'
|
||||
|
||||
|
||||
22
developer/src/kmc-kmn/test/fixtures/kmw/test_index_12980_v9.kmn
vendored
Normal file
22
developer/src/kmc-kmn/test/fixtures/kmw/test_index_12980_v9.kmn
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
c test_index_12980 generated from template at 2025-01-22 13:25:35
|
||||
c Validates index() offsets when used with if() - see #12980
|
||||
c Test with v9 KMW compiler (does not support notany)
|
||||
|
||||
store(&VERSION) '9.0'
|
||||
store(&NAME) 'test_index_12980_v9'
|
||||
store(&TARGETS) 'web'
|
||||
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
|
||||
store(a) '1'
|
||||
store(b) '2'
|
||||
store(e) '5'
|
||||
store(f) '6'
|
||||
|
||||
if(&platform = 'web') any(a) + 'a' > context 'A'
|
||||
if(&platform = 'web') any(b) + 'b' > context(2) 'B'
|
||||
nul any(e) + 'e' > context 'E'
|
||||
nul any(f) + 'f' > context(2) 'F'
|
||||
|
|
@ -218,29 +218,56 @@ describe('KeymanWeb Compiler', function() {
|
|||
assert.lengthOf(callbacks.messages, 2);
|
||||
});
|
||||
|
||||
it('should generate correct index offsets (#12980) for context and context(n) when building a KMW keyboard', async function() {
|
||||
const filenames = generateTestFilenames('test_index_12980');
|
||||
|
||||
function doMatchKIO(data: string, s: string, offset: number) {
|
||||
const regex = new RegExp('k\\.KIO\\(-1,this\\.'+s+',(.+?),t\\);');
|
||||
const m = regex.exec(data);
|
||||
assert.isNotNull(m, `Could not find match for ${s}`);
|
||||
assert.equal(m[1], offset.toString(), `Expected '${s}' usage to have an index parameter == 1`);
|
||||
}
|
||||
|
||||
it('should generate correct index offsets (#12980) for context and context(n) when building a KMW keyboard with v9 target', async function() {
|
||||
const filenames = generateTestFilenames('test_index_12980_v9');
|
||||
|
||||
let result = await kmnCompiler.run(filenames.source, null);
|
||||
assert.isNotNull(result);
|
||||
const data = new TextDecoder('utf-8').decode(result.artifacts.js.data);
|
||||
|
||||
function doMatchKIO(s: string) {
|
||||
const regex = new RegExp('k\\.KIO\\(-1,this\\.'+s+',(.+?),t\\);');
|
||||
const m = regex.exec(data);
|
||||
assert.isNotNull(m, `Could not find match for ${s}`);
|
||||
assert.equal(m[1], '1', `Expected '${s}' usage to have an index parameter == 1`);
|
||||
if(debug) {
|
||||
console.log(result.artifacts.js.filename);
|
||||
await kmnCompiler.write(result.artifacts);
|
||||
}
|
||||
|
||||
doMatchKIO('s_a_6');
|
||||
doMatchKIO('s_b_7');
|
||||
// v9 targets should match
|
||||
assert.match(data, /k\.KO\(-1,t,"A"\);/);
|
||||
assert.match(data, /k\.KIO\(1,this.s_b_7,1,t\);/);
|
||||
assert.match(data, /k\.KO\(-1,t,"E"\);/);
|
||||
assert.match(data, /k\.KIO\(1,this.s_f_9,1,t\);/);
|
||||
});
|
||||
|
||||
// matches the 4 notany() rules
|
||||
const matches = [...data.matchAll(/k\.KCXO\(-1,t,1,1\);/g)];
|
||||
assert.lengthOf(matches, 4);
|
||||
it('should generate correct index offsets (#12980) for context and context(n) when building a KMW keyboard with v10+ target', async function() {
|
||||
const filenames = generateTestFilenames('test_index_12980_v10');
|
||||
|
||||
doMatchKIO('s_e_10');
|
||||
doMatchKIO('s_f_11');
|
||||
let result = await kmnCompiler.run(filenames.source, null);
|
||||
assert.isNotNull(result);
|
||||
const data = new TextDecoder('utf-8').decode(result.artifacts.js.data);
|
||||
|
||||
if(debug) {
|
||||
console.log(result.artifacts.js.filename);
|
||||
await kmnCompiler.write(result.artifacts);
|
||||
}
|
||||
|
||||
doMatchKIO(data, 's_a_6', 1); //if() has index 1
|
||||
doMatchKIO(data, 's_b_7', 1); //if() has index 1
|
||||
|
||||
// matches the 2 notany() rules for if() and 2 for nul
|
||||
const matches_if = [...data.matchAll(/k\.KCXO\(-1,t,1,1\);/g)];
|
||||
assert.lengthOf(matches_if, 2);
|
||||
const matches_nul = [...data.matchAll(/k\.KCXO\(-1,t,2,2\);/g)];
|
||||
assert.lengthOf(matches_nul, 2);
|
||||
|
||||
doMatchKIO(data, 's_e_10', 2);
|
||||
doMatchKIO(data, 's_f_11', 2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -185,7 +185,6 @@ describe('KmnCompilerMessages', function () {
|
|||
|
||||
it('should generate WARN_IndexStoreShort if a store referenced in index() is shorter than the corresponding any() store', async function() {
|
||||
await testForMessage(this, ['keyboards', 'warn_index_store_short.kmn'], KmnCompilerMessages.WARN_IndexStoreShort);
|
||||
callbacks.clear();
|
||||
await testForMessage(this, ['keyboards', 'warn_index_store_short_key.kmn'], KmnCompilerMessages.WARN_IndexStoreShort);
|
||||
});
|
||||
|
||||
|
|
@ -193,8 +192,28 @@ describe('KmnCompilerMessages', function () {
|
|||
|
||||
it('should generate HINT_IndexStoreLong if a store referenced in index() is longer than the corresponding any() store', async function() {
|
||||
await testForMessage(this, ['keyboards', 'hint_index_store_long.kmn'], KmnCompilerMessages.HINT_IndexStoreLong);
|
||||
callbacks.clear();
|
||||
await testForMessage(this, ['keyboards', 'hint_index_store_long_key.kmn'], KmnCompilerMessages.HINT_IndexStoreLong);
|
||||
});
|
||||
|
||||
// ERROR_ContextExHasInvalidOffset
|
||||
|
||||
it('should generate ERROR_ContextExHasInvalidOffset if the offset is lower than 1 or greater than the context length', async function() {
|
||||
await testForMessage(this, ['invalid-keyboards', 'error_contextex_has_invalid_offset_if.kmn'], KmnCompilerMessages.ERROR_ContextExHasInvalidOffset);
|
||||
await testForMessage(this, ['invalid-keyboards', 'error_contextex_has_invalid_offset_long.kmn'], KmnCompilerMessages.ERROR_ContextExHasInvalidOffset);
|
||||
await testForMessage(this, ['invalid-keyboards', 'error_contextex_has_invalid_offset_nul.kmn'], KmnCompilerMessages.ERROR_ContextExHasInvalidOffset);
|
||||
await testForMessage(this, ['invalid-keyboards', 'error_contextex_has_invalid_offset_zero.kmn'], KmnCompilerMessages.ERROR_ContextExHasInvalidOffset);
|
||||
});
|
||||
|
||||
// ERROR_ContextExCannotReferenceIf
|
||||
|
||||
it('should generate ERROR_ContextExCannotReferenceIf if the offset points to an if() statement', async function() {
|
||||
await testForMessage(this, ['invalid-keyboards', 'error_contextex_cannot_reference_if.kmn'], KmnCompilerMessages.ERROR_ContextExCannotReferenceIf);
|
||||
});
|
||||
|
||||
// ERROR_ContextExCannotReferenceNul
|
||||
|
||||
it('should generate ERROR_ContextExCannotReferenceNul if the offset points to a nul statement', async function() {
|
||||
await testForMessage(this, ['invalid-keyboards', 'error_contextex_cannot_reference_nul.kmn'], KmnCompilerMessages.ERROR_ContextExCannotReferenceNul);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -79,6 +79,18 @@ async function doCopy(callbacks: NodeCompilerCallbacks, sources: string | string
|
|||
return false;
|
||||
}
|
||||
|
||||
const dest = options.outPath;
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_CopyingProject({source, dest}));
|
||||
const result = runCopier(callbacks, source, options);
|
||||
if(result) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectCopiedSuccessfully({source, dest}));
|
||||
} else {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotCopiedSuccessfully({source, dest}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function runCopier(callbacks: NodeCompilerCallbacks, source: string, options: CopierOptions) {
|
||||
const copier = new KeymanProjectCopier();
|
||||
try {
|
||||
if(!await copier.init(callbacks, options)) {
|
||||
|
|
|
|||
|
|
@ -113,9 +113,16 @@ async function generate(generator: KeymanCompiler, ids: string | string[], comma
|
|||
}
|
||||
|
||||
const options = commanderOptionsToGeneratorOptions(id, commanderOptions);
|
||||
|
||||
const outPath = options.outPath;
|
||||
const callbacks = new NodeCompilerCallbacks(options);
|
||||
if(!await doGenerate(callbacks, generator, options)) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_GeneratingProject({id, outPath}));
|
||||
|
||||
const result = await doGenerate(callbacks, generator, options);
|
||||
|
||||
if(result) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectGeneratedSuccessfully({id}));
|
||||
} else {
|
||||
callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotGeneratedSuccessfully({id}));
|
||||
return await exitProcess(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,5 +158,48 @@ export class InfrastructureMessages {
|
|||
this.ERROR_CopyRequiresOutPath,
|
||||
`The copy command requires the --out-path, -o parameter`
|
||||
);
|
||||
}
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_CopyingProject = SevInfo | 0x0023;
|
||||
static Info_CopyingProject = (o:{source:string,dest:string}) => ({filename:o.source, ...m(
|
||||
this.INFO_CopyingProject,
|
||||
`Copying project '${def(o.source)}' to '${def(o.dest)}'`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectCopiedSuccessfully = SevInfo | 0x0024;
|
||||
static Info_ProjectCopiedSuccessfully = (o:{source:string, dest:string}) => ({filename:o.source, ...m(
|
||||
this.INFO_ProjectCopiedSuccessfully,
|
||||
`'${def(o.source)}' copied to '${def(o.dest)}' successfully.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectNotCopiedSuccessfully = SevInfo | 0x0025;
|
||||
static Info_ProjectNotCopiedSuccessfully = (o:{source:string, dest:string}) => ({filename:o.source, ...m(
|
||||
this.INFO_ProjectNotCopiedSuccessfully,
|
||||
`Failed to copy '${def(o.source)}' to '${def(o.dest)}'.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_GeneratingProject = SevInfo | 0x0026;
|
||||
static Info_GeneratingProject = (o:{id:string, outPath: string}) => ({filename:o.id, ...m(
|
||||
this.INFO_GeneratingProject,
|
||||
`Generating new project '${def(o.id)}' in '${def(o.outPath)}'.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectGeneratedSuccessfully = SevInfo | 0x0027;
|
||||
static Info_ProjectGeneratedSuccessfully = (o:{id:string}) => ({filename:o.id, ...m(
|
||||
this.INFO_ProjectGeneratedSuccessfully,
|
||||
`New project '${def(o.id)}' generated successfully.`,
|
||||
)});
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
static INFO_ProjectNotGeneratedSuccessfully = SevInfo | 0x0028;
|
||||
static Info_ProjectNotGeneratedSuccessfully = (o:{id:string}) => ({filename:o.id, ...m(
|
||||
this.INFO_ProjectNotGeneratedSuccessfully,
|
||||
`Failed to generate new project '${def(o.id)}'.`,
|
||||
)});
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -242,12 +242,18 @@ export class NodeCompilerCallbacks implements CompilerCallbacks {
|
|||
messageSpecialColor(event: CompilerEvent) {
|
||||
switch(event.code) {
|
||||
case InfrastructureMessages.INFO_BuildingFile:
|
||||
case InfrastructureMessages.INFO_CopyingProject:
|
||||
case InfrastructureMessages.INFO_GeneratingProject:
|
||||
return color.whiteBright;
|
||||
case InfrastructureMessages.INFO_FileNotBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectNotBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectNotCopiedSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectNotGeneratedSuccessfully:
|
||||
return color.red;
|
||||
case InfrastructureMessages.INFO_FileBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectBuiltSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectCopiedSuccessfully:
|
||||
case InfrastructureMessages.INFO_ProjectGeneratedSuccessfully:
|
||||
return color.green;
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1458,13 +1458,23 @@ KMX_DWORD CheckStatementOffsets(PFILE_KEYBOARD fk, PFILE_GROUP gp, PKMX_WCHAR co
|
|||
}
|
||||
} else if (*(p + 1) == CODE_CONTEXTEX) {
|
||||
int contextOffset = *(p + 2);
|
||||
if (contextOffset > xstrlen(context))
|
||||
if (contextOffset > xstrlen(context)) {
|
||||
return KmnCompilerMessages::ERROR_ContextExHasInvalidOffset;
|
||||
}
|
||||
|
||||
// contextOffset <= 0 is tested in parse stage
|
||||
|
||||
for (q = context, i = 1; *q && i < contextOffset; q = incxstr(q), i++);
|
||||
if(*q == UC_SENTINEL && (*(q + 1) == CODE_IFOPT || *(q + 1) == CODE_IFSYSTEMSTORE)) {
|
||||
return KmnCompilerMessages::ERROR_ContextExCannotReferenceIf;
|
||||
}
|
||||
if(*q == UC_SENTINEL && *(q + 1) == CODE_NUL) {
|
||||
return KmnCompilerMessages::ERROR_ContextExCannotReferenceNul;
|
||||
}
|
||||
|
||||
// Due to a limitation in earlier versions of KeymanWeb, the minimum version
|
||||
// for context() referring to notany() is 14.0. See #917 for details.
|
||||
if (kmcmp::CompileTarget == CKF_KEYMANWEB) {
|
||||
for (q = context, i = 1; *q && i < contextOffset; q = incxstr(q), i++);
|
||||
if (*q == UC_SENTINEL && *(q + 1) == CODE_NOTANY) {
|
||||
if(!VerifyKeyboardVersion(fk, VERSION_140)) {
|
||||
return KmnCompilerMessages::ERROR_140FeatureOnlyContextAndNotAnyWeb;
|
||||
|
|
@ -2267,7 +2277,9 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
|
|||
}
|
||||
int n1b;
|
||||
n1b = atoiW(q);
|
||||
if (n1b < 1 || n1b >= 0xF000) return KmnCompilerMessages::ERROR_InvalidToken;
|
||||
if (n1b < 1 || n1b >= 0xF000) {
|
||||
return KmnCompilerMessages::ERROR_ContextExHasInvalidOffset;
|
||||
}
|
||||
tstr[mx++] = UC_SENTINEL;
|
||||
tstr[mx++] = CODE_CONTEXTEX;
|
||||
tstr[mx++] = n1b;
|
||||
|
|
|
|||
|
|
@ -1281,10 +1281,10 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) {
|
|||
const KMX_WCHAR tstr_context_offset_valid[] = { UC_SENTINEL, CODE_CONTEXTEX, 1, 0 };
|
||||
EXPECT_EQ(0, u16cmp(tstr_context_offset_valid, tstr));
|
||||
|
||||
// context, CERR_InvalidToke, offset < 1
|
||||
// context, ERROR_ContextExHasInvalidOffset, offset < 1
|
||||
fileKeyboard.version = VERSION_60;
|
||||
u16cpy(str, u"context(0)");
|
||||
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
|
||||
EXPECT_EQ(KmnCompilerMessages::ERROR_ContextExHasInvalidOffset, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
|
||||
|
||||
// context, large offset < 0xF000, valid
|
||||
fileKeyboard.version = VERSION_60;
|
||||
|
|
@ -1293,10 +1293,10 @@ TEST_F(CompilerTest, GetXStringImpl_type_c_test) {
|
|||
const KMX_WCHAR tstr_context_large_offset_valid[] = { UC_SENTINEL, CODE_CONTEXTEX, 61439, 0 };
|
||||
EXPECT_EQ(0, u16cmp(tstr_context_large_offset_valid, tstr));
|
||||
|
||||
// context, KmnCompilerMessages::ERROR_InvalidToken, too large offset == 0xF000
|
||||
// context, KmnCompilerMessages::ERROR_ContextExHasInvalidOffset, too large offset == 0xF000
|
||||
fileKeyboard.version = VERSION_60;
|
||||
u16cpy(str, u"context(61440)"); //0xF000
|
||||
EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidToken, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
|
||||
EXPECT_EQ(KmnCompilerMessages::ERROR_ContextExHasInvalidOffset, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE));
|
||||
|
||||
// context, KmnCompilerMessages::ERROR_60FeatureOnly_Contextn
|
||||
fileKeyboard.version = VERSION_50;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,12 @@ tests = [
|
|||
'k_045___deadkey_and_context',
|
||||
'k_046___deadkey_and_contextex',
|
||||
'k_047___caps_always_off_initially_on',
|
||||
'k_048___modifier_keys_keep_context'
|
||||
'k_048___modifier_keys_keep_context',
|
||||
'k_049___enter_invalidates_context',
|
||||
'k_050___nul_and_context',
|
||||
'k_051___if_and_context',
|
||||
'k_052___nul_and_index',
|
||||
'k_053___if_and_index',
|
||||
]
|
||||
|
||||
foreach kbd : tests
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
builder_describe "Keyboard project generation and conversion tool" \
|
||||
@/common/include \
|
||||
@/common/windows/delphi \
|
||||
clean configure build test publish install
|
||||
clean configure build test publish install edit
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -62,3 +62,4 @@ builder_run_action build:project do_build
|
|||
builder_run_action test:project do_test
|
||||
builder_run_action publish:project do_publish
|
||||
builder_run_action install:project cp "$DEVELOPER_PROGRAM/kmconvert.exe" "$INSTALLPATH_KEYMANDEVELOPER/kmconvert.exe"
|
||||
builder_run_action edit:project start kmconvert.dproj
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
builder_describe "Chromium browser host process for Keyman Developer" \
|
||||
@/common/include \
|
||||
@/common/windows/delphi \
|
||||
clean configure build test publish install
|
||||
clean configure build test publish install edit
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -52,3 +52,5 @@ builder_run_action build:project do_build
|
|||
# builder_run_action test:project do_test
|
||||
builder_run_action publish:project do_publish
|
||||
builder_run_action install:project cp "$DEVELOPER_PROGRAM/kmdbrowserhost.exe" "$INSTALLPATH_KEYMANDEVELOPER/kmdbrowserhost.exe"
|
||||
builder_run_action edit:project start kmdbrowserhost.dproj
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ builder_describe \
|
|||
"Keyman Developer Setup bootstrap executable" \
|
||||
@/common/include \
|
||||
@/common/windows/delphi \
|
||||
clean configure build test publish
|
||||
clean configure build test publish edit
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -55,3 +55,4 @@ builder_run_action configure:project configure_windows_build_environment
|
|||
builder_run_action build:project do_build
|
||||
builder_run_action publish:project do_publish
|
||||
# builder_run_action test:project do_test
|
||||
builder_run_action edit:project start setup.dproj
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ builder_describe "Build Keyman Developer IDE" \
|
|||
@/common/include \
|
||||
@/core:x86 \
|
||||
@/common/windows/delphi \
|
||||
clean configure build test publish install
|
||||
clean configure build test publish install edit
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -113,6 +113,7 @@ builder_run_action build:project do_build
|
|||
# builder_run_action test:project do_test
|
||||
builder_run_action publish:project do_publish
|
||||
builder_run_action install:project do_install
|
||||
builder_run_action edit:project start tike.dproj
|
||||
|
||||
# Note: generating monaco installer:
|
||||
# @echo *******************************************************************************************
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ inherited frmLdmlKeyboardDebug: TfrmLdmlKeyboardDebug
|
|||
Font.Name = 'Arial'
|
||||
Font.Style = []
|
||||
ParentFont = False
|
||||
PlainText = True
|
||||
PopupMenu = mnuPopup
|
||||
ScrollBars = ssVertical
|
||||
TabOrder = 0
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ procedure TfrmLdmlKeyboardDebug.Run(vkey: Word);
|
|||
if dk.SavedPosition < 0 then
|
||||
begin
|
||||
dk.Position := L + dk.SavedPosition;
|
||||
if (dk.Position < 0) or (dk.Position >= L) or (s[dk.Position + 1] <> #$FFFC) then
|
||||
if (dk.Position < 0) or (dk.Position >= L) or (s[dk.Position + 1] <> DeadKey_Marker) then
|
||||
// This can happen if the deadkey was in a replaced selection
|
||||
dk.Delete;
|
||||
end;
|
||||
|
|
@ -481,7 +481,7 @@ procedure TfrmLdmlKeyboardDebug.Run(vkey: Word);
|
|||
|
||||
|
||||
// Returns the length of the context items in UTF16, treating markers as a
|
||||
// single UTF16 code unit (U+FFFC)
|
||||
// single UTF16 code unit (DeadKey_Marker)
|
||||
function ContextItemsLengthUTF16(pc: pkm_core_context_item): Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
|
|
@ -519,7 +519,7 @@ procedure TfrmLdmlKeyboardDebug.Run(vkey: Word);
|
|||
dk.Position := Result.Length + offset;
|
||||
dk.SavedPosition := Result.Length + offset;
|
||||
FDeadkeys.Add(dk);
|
||||
Result := Result + #$FFFC;
|
||||
Result := Result + DeadKey_Marker;
|
||||
end;
|
||||
Inc(pc);
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ inherited frmDebug: TfrmDebug
|
|||
Font.Name = 'Arial'
|
||||
Font.Style = []
|
||||
ParentFont = False
|
||||
PlainText = True
|
||||
PopupMenu = mnuPopup
|
||||
ScrollBars = ssVertical
|
||||
TabOrder = 0
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
dk: TDeadKeyInfo;
|
||||
begin
|
||||
Result.Selection := memo.Selection;
|
||||
Result.Length := Length(memo.Text);
|
||||
Result.Length := Length(memo.GetTextCR);
|
||||
for dk in FDeadkeys do
|
||||
begin
|
||||
if dk.Position >= Result.Selection.Start
|
||||
|
|
@ -694,13 +694,13 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
L: Integer;
|
||||
s: string;
|
||||
begin
|
||||
s := memo.Text;
|
||||
s := memo.GetTextCR;
|
||||
L := Length(s);
|
||||
for dk in FDeadkeys do
|
||||
if dk.SavedPosition < 0 then
|
||||
begin
|
||||
dk.Position := L + dk.SavedPosition;
|
||||
if (dk.Position < 0) or (dk.Position >= L) or (s[dk.Position + 1] <> #$FFFC) then
|
||||
if (dk.Position < 0) or (dk.Position >= L) or (s[dk.Position + 1] <> DeadKey_Marker) then
|
||||
// This can happen if the deadkey was in a replaced selection
|
||||
dk.Delete;
|
||||
end;
|
||||
|
|
@ -710,6 +710,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
|
||||
procedure DoBackspace(BackspaceType: km_core_backspace_type; ExpectedValue: NativeUInt);
|
||||
var
|
||||
t: string;
|
||||
m, n: Integer;
|
||||
dk: TDeadKeyInfo;
|
||||
state: TMemoSelectionState;
|
||||
|
|
@ -721,7 +722,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
'ExpectedValue=U+'+IntToHex(ExpectedValue, 4)+'; '+
|
||||
'memo.SelStart='+IntToStr(memo.SelStart)+'; '+
|
||||
'memo.SelLength='+IntToStr(memo.SelLength)+'; '+
|
||||
'memo.Text='+Copy(memo.Text, 1, 256);
|
||||
'memo.Text='+Copy(memo.GetTextCR, 1, 256);
|
||||
end;
|
||||
begin
|
||||
// Offset is zero-based, but string is 1-based. Beware!
|
||||
|
|
@ -740,11 +741,14 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
Exit;
|
||||
end;
|
||||
|
||||
t := memo.GetTextCR;
|
||||
|
||||
case BackspaceType of
|
||||
KM_CORE_BT_MARKER:
|
||||
begin
|
||||
Assert(m >= 1, AssertionMessage);
|
||||
Assert(memo.Text[m] = #$FFFC, AssertionMessage);
|
||||
Assert(m <= t.Length, AssertionMessage);
|
||||
Assert(t[m] = DeadKey_Marker, AssertionMessage);
|
||||
dk := FDeadkeys.GetFromPosition(m-1);
|
||||
Assert(Assigned(dk));
|
||||
dk.Delete;
|
||||
|
|
@ -753,23 +757,19 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
KM_CORE_BT_CHAR:
|
||||
begin
|
||||
Assert(m >= 1, AssertionMessage);
|
||||
Assert(memo.Text[m] <> #$FFFC, AssertionMessage);
|
||||
Assert(m <= t.Length, AssertionMessage);
|
||||
Assert(t[m] <> DeadKey_Marker, AssertionMessage);
|
||||
// Delete surrogate pairs
|
||||
if (m > 1) and
|
||||
Uni_IsSurrogate2(memo.Text[m]) and
|
||||
Uni_IsSurrogate1(memo.Text[m-1]) then
|
||||
Dec(m, 2)
|
||||
// Delete \r\n line breaks
|
||||
else if (m > 1) and
|
||||
(memo.Text[m] = #$0A) and
|
||||
(memo.Text[m-1] = #$0D) then
|
||||
Uni_IsSurrogate2(t[m]) and
|
||||
Uni_IsSurrogate1(t[m-1]) then
|
||||
Dec(m, 2)
|
||||
else
|
||||
Dec(m);
|
||||
end;
|
||||
KM_CORE_BT_UNKNOWN:
|
||||
begin
|
||||
while (m >= 1) and (memo.Text[m] = #$FFFC) do
|
||||
while (m >= 1) and (t[m] = DeadKey_Marker) do
|
||||
begin
|
||||
dk := FDeadkeys.GetFromPosition(m-1);
|
||||
Assert(Assigned(dk), AssertionMessage);
|
||||
|
|
@ -779,14 +779,14 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
|
||||
// Delete character
|
||||
if (m > 1) and
|
||||
Uni_IsSurrogate2(memo.Text[m]) and
|
||||
Uni_IsSurrogate1(memo.Text[m-1]) then
|
||||
Uni_IsSurrogate2(t[m]) and
|
||||
Uni_IsSurrogate1(t[m-1]) then
|
||||
Dec(m, 2)
|
||||
else
|
||||
Dec(m);
|
||||
|
||||
// Also delete deadkeys to left of current character
|
||||
while (m >= 1) and (memo.Text[m] = #$FFFC) do
|
||||
while (m >= 1) and (t[m] = DeadKey_Marker) do
|
||||
begin
|
||||
dk := FDeadkeys.GetFromPosition(m-1);
|
||||
Assert(Assigned(dk), AssertionMessage);
|
||||
|
|
@ -800,7 +800,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
|
||||
memo.Lines.BeginUpdate;
|
||||
try
|
||||
memo.Text := Copy(memo.Text, 1, m) + Copy(memo.Text, n+1, MaxInt);
|
||||
memo.Text := Copy(t, 1, m) + Copy(t, n+1, MaxInt);
|
||||
memo.SelStart := m;
|
||||
finally
|
||||
memo.Lines.EndUpdate;
|
||||
|
|
@ -830,7 +830,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
begin
|
||||
state := SaveMemoSelectionState;
|
||||
|
||||
memo.SelText := WideChar($FFFC);
|
||||
memo.SelText := DeadKey_Marker;
|
||||
memo.SelStart := memo.SelStart + memo.SelLength; // I1603
|
||||
memo.SelLength := 0;
|
||||
dk.Position := memo.SelStart - 1;
|
||||
|
|
@ -843,27 +843,6 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure DoHandleShortcut(vk: UINT);
|
||||
begin
|
||||
// Because we disable shortcuts in the debug memo, there are a small set of
|
||||
// editor Ctrl+Key shortcuts we need to rehandle here. Note that Ctrl+Ins,
|
||||
// Shift+Ins, Shift+Del appear to be handled natively by the debug memo
|
||||
// control (Windows code?), as are all cursor movement / selection keys,
|
||||
// apart from Ctrl+A
|
||||
|
||||
if GetKeyState(VK_CONTROL) >= 0 then
|
||||
Exit;
|
||||
|
||||
case vk of
|
||||
Ord('A'): memo.SelectAll;
|
||||
Ord('C'): memo.CopyToClipboard;
|
||||
Ord('V'): memo.PasteFromClipboard;
|
||||
Ord('X'): memo.CutToClipboard;
|
||||
Ord('Y'): ; // redo not supported in edit
|
||||
Ord('Z'): memo.Undo;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure DoEmitKeystroke(dwData: DWord);
|
||||
var
|
||||
flag: Integer;
|
||||
|
|
@ -889,8 +868,6 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
else msg.Msg := WM_KEYUP;
|
||||
memo.Dispatch(msg);
|
||||
|
||||
DoHandleShortcut(LOBYTE(dwData));
|
||||
|
||||
RealignMemoSelectionState(state);
|
||||
end;
|
||||
end;
|
||||
|
|
@ -898,11 +875,13 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer);
|
|||
procedure DoChar(const text: string);
|
||||
var
|
||||
state: TMemoSelectionState;
|
||||
selStart: Integer;
|
||||
begin
|
||||
state := SaveMemoSelectionState;
|
||||
// Line breaks: replace \r (0x0D) with \r\n (0x0D 0x0A) so line breaks work
|
||||
memo.SelText := ReplaceStr(Text, #$0D, #$0D#$0A);
|
||||
memo.SelStart := memo.SelStart + memo.SelLength; // I1603
|
||||
selStart := memo.SelStart;
|
||||
memo.SelText := text;
|
||||
memo.SelStart := selStart + text.Length;
|
||||
memo.SelLength := 0;
|
||||
RealignMemoSelectionState(state);
|
||||
end;
|
||||
|
|
@ -1357,20 +1336,22 @@ end;
|
|||
procedure TfrmDebug.UpdateCharacterGrid; // I4808
|
||||
var
|
||||
start, len: Integer;
|
||||
t: string;
|
||||
begin
|
||||
if csDestroying in ComponentState then
|
||||
Exit;
|
||||
|
||||
start := memo.SelStart;
|
||||
len := memo.SelLength;
|
||||
if start + len > Length(memo.Text) then
|
||||
t := memo.GetTextCR;
|
||||
if start + len > t.Length then
|
||||
begin
|
||||
// RichEdit has a virtual final character, which is selected when
|
||||
// pressing Ctrl+A, etc.
|
||||
len := Length(memo.Text) - start;
|
||||
len := t.Length - start;
|
||||
end;
|
||||
|
||||
TCharacterGridRenderer.Fill(sgChars, memo.Text, FDeadkeys, start, len, memo.Selection.Anchor);
|
||||
TCharacterGridRenderer.Fill(sgChars, t, FDeadkeys, start, len, memo.Selection.Anchor);
|
||||
TCharacterGridRenderer.Size(sgChars, memo.Font);
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -3000,7 +3000,7 @@ begin
|
|||
else kbdparser.KeyboardText := frameSource.EditorText;
|
||||
|
||||
kbdparser.AddRequiredLines;
|
||||
kbdparser.SetSystemStoreValue(ssTargets, 'windows native');
|
||||
kbdparser.SetSystemStoreValue(ssTargets, 'desktop');
|
||||
kbdparser.SetSystemStoreValue(ssVersion, SKeymanVersion90);
|
||||
kbdparser.DeleteSystemStore(ssVisualKeyboard);
|
||||
kbdparser.DeleteSystemStore(ssBitmap);
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ inherited frmPackageEditor: TfrmPackageEditor
|
|||
Width = 546
|
||||
Height = 32
|
||||
AutoSize = False
|
||||
Caption =
|
||||
Caption =
|
||||
'A typical package will need keyboards, fonts, and documentation.' +
|
||||
' You shouldn'#39't typically add source files. Also, don'#39't add any s' +
|
||||
'tandard Keyman files (such as keyman.exe) here.'
|
||||
|
|
@ -1342,7 +1342,7 @@ inherited frmPackageEditor: TfrmPackageEditor
|
|||
Top = 48
|
||||
Width = 551
|
||||
Height = 13
|
||||
Caption =
|
||||
Caption =
|
||||
'Compiling the package takes all the files you have selected and ' +
|
||||
'compresses them into a single package file.'
|
||||
end
|
||||
|
|
@ -1617,11 +1617,4 @@ inherited frmPackageEditor: TfrmPackageEditor
|
|||
Left = 32
|
||||
Top = 492
|
||||
end
|
||||
object dlgNewCustomisation: TSaveDialog
|
||||
DefaultExt = 'kct'
|
||||
Filter = 'Customisation file (*.kct)|*.kct|All files (*.*)|*.*'
|
||||
Options = [ofOverwritePrompt, ofHideReadOnly, ofPathMustExist, ofEnableSizing]
|
||||
Left = 32
|
||||
Top = 532
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ uses
|
|||
type
|
||||
TfrmPackageEditor = class(TfrmTikeEditor) // I4689
|
||||
dlgFiles: TOpenDialog;
|
||||
dlgNewCustomisation: TSaveDialog;
|
||||
pages: TLeftTabbedPageControl;
|
||||
pageFiles: TTabSheet;
|
||||
pageDetails: TTabSheet;
|
||||
|
|
@ -628,6 +627,7 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
dlgFiles.InitialDir := ExtractFileDir(FileName);
|
||||
UpdateData;
|
||||
Result := True;
|
||||
end;
|
||||
|
|
@ -806,8 +806,10 @@ begin
|
|||
if Found then Continue; // Don't add the file again
|
||||
|
||||
AddFile(Files[i]);
|
||||
dlgFiles.InitialDir := ExtractFileDir(Files[i]);
|
||||
end;
|
||||
if lbFiles.CanFocus then lbFiles.SetFocus; // I3100 // I3506
|
||||
dlgFiles.FileName := '';
|
||||
end;
|
||||
|
||||
procedure TfrmPackageEditor.cmdRemoveFileClick(Sender: TObject);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ function GetContextFromMemo(Memo: TKeymanDeveloperDebuggerMemo; DeadKeys: TDebug
|
|||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
|
||||
Unicode;
|
||||
|
||||
function GetContextFromMemo(Memo: TKeymanDeveloperDebuggerMemo; DeadKeys: TDebugDeadkeyInfoList; IncludeMarkers: Boolean): TArray<km_core_context_item>;
|
||||
|
|
@ -19,21 +21,23 @@ var
|
|||
n, i: Integer;
|
||||
ch: Char;
|
||||
dk: TDeadKeyInfo;
|
||||
t: string;
|
||||
begin
|
||||
n := 0;
|
||||
SetLength(Result, Length(Memo.Text)+1);
|
||||
t := Memo.GetTextCR;
|
||||
SetLength(Result, t.Length+1);
|
||||
i := 1;
|
||||
while i <= Memo.SelStart + Memo.SelLength do
|
||||
begin
|
||||
ch := Memo.Text[i];
|
||||
if Uni_IsSurrogate1(ch) and (i < Length(Memo.Text)) and
|
||||
Uni_IsSurrogate2(Memo.Text[i+1]) then
|
||||
ch := t[i];
|
||||
if Uni_IsSurrogate1(ch) and (i < t.Length) and
|
||||
Uni_IsSurrogate2(t[i+1]) then
|
||||
begin
|
||||
Result[n]._type := KM_CORE_CT_CHAR;
|
||||
Result[n].character := Uni_SurrogateToUTF32(ch, Memo.Text[i+1]);
|
||||
Result[n].character := Uni_SurrogateToUTF32(ch, t[i+1]);
|
||||
Inc(i);
|
||||
end
|
||||
else if Ord(ch) = $FFFC then
|
||||
else if ch = DeadKey_Marker then
|
||||
begin
|
||||
if IncludeMarkers then
|
||||
begin
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue