Merge pull request #6274 from keymanapp/fix/android/5853-selection-management-android-1

fix(android): properly handle selection 🎢
This commit is contained in:
Marc Durdin 2022-03-01 08:08:19 +11:00 committed by GitHub
commit 7fd2f9d990
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 27 deletions

View file

@ -1,3 +1,10 @@
var _debug = 0;
// Android harness attachment
if(window.parent && window.parent.jsInterface && !window.jsInterface) {
window.jsInterface = window.parent.jsInterface;
}
var device = window.jsInterface.getDeviceType();
var oskHeight = Math.ceil(window.jsInterface.getKeyboardHeight() / window.devicePixelRatio);
var oskWidth = 0;
@ -19,7 +26,7 @@ function init() {
//window.console.log('Device type = '+device);
//window.console.log('Keyboard height = '+oskHeight);
var kmw=com.keyman.singleton;
kmw.init({'app':device,'fonts':'packages/'});
kmw.init({'app':device,'fonts':'packages/',root:'./'});
kmw['util']['setOption']('attachType','manual');
kmw['oninserttext'] = insertText;
kmw['showKeyboardList'] = showMenu;
@ -48,6 +55,7 @@ function init() {
}
function notifyHost(event, params) {
console_debug('notifyHost(event='+event+',params='+params+')');
// TODO: Update all other host notifications to use notifyHost instead of directly setting window.location.hash
window.setTimeout(function() {
// We use a timeout so that the navigation doesn't cause the calling function to abort after the call
@ -142,6 +150,7 @@ function setSpacebarText(mode) {
* @param dr Number of post-caret code points to delete. (optional)
*/
function insertText(dn, s, dr) {
console_debug('insertText(dn='+dn+',s='+s+',dr='+dr+')');
dr = dr || 0; // Sets a default value of zero when dr is undefined
//window.console.log('insertText('+ dn +', ' + s +', ' + dr + ');');
window.jsInterface.insertText(dn, s, dr);
@ -189,29 +198,43 @@ function setNumericLayer() {
}
function updateKMText(text) {
var ta = document.getElementById('ta');
console_debug('updateKMText(text='+text+') ta.value='+ta.value);
if(text == undefined) {
text = '';
}
var ta = document.getElementById('ta');
var kmw = window['keyman'];
var resetContext = ta.value != text;
ta.value = text;
kmw['setActiveElement'](ta);
if(resetContext) {
kmw.resetContext();
if(ta.value != text) {
ta.value = text;
window.resetContext();
}
}
function console_debug(s) {
if(_debug) {
console.debug(s);
}
}
function updateKMSelectionRange(start, end) {
var ta = document.getElementById('ta');
var kmw = window['keyman'];
var resetContext = (ta.selectionStart != start || ta.selectionEnd != end);
ta.selectionStart = ta._KeymanWebSelectionStart = start;
ta.selectionEnd = ta._KeymanWebSelectionEnd = end;
kmw['setActiveElement'](ta);
if(resetContext) {
kmw.resetContext();
console_debug('updateKMSelectionRange('+start+','+end+'): ta.selectionStart='+ta.selectionStart+' '+
'['+ta._KeymanWebSelectionStart+'] ta.selectionEnd='+ta.selectionEnd+' '+ta._KeymanWebSelectionEnd);
var selDirection = 'forward';
if(start > end) {
var e0 = end;
end = start;
start = e0;
selDirection = 'backward';
}
if(ta.selectionStart != start || ta.selectionEnd != end || ta.selectionDirection != selDirection) {
ta.selectionStart = ta._KeymanWebSelectionStart = start;
ta.selectionEnd = ta._KeymanWebSelectionEnd = end;
ta.selectionDirection = selDirection;
keyman.resetContext();
}
}
@ -301,6 +324,7 @@ function executePopupKey(keyID, keyText) {
}
function executeHardwareKeystroke(code, shift, lstates, eventModifiers) {
console_debug('executeHardwareKeystroke(code='+code+',shift='+shift+',lstates='+lstates+',eventModifiers='+eventModifiers+')');
var kmw=window['keyman'];
//window.console.log('executeHardwareKeystroke:('+code+', ' + shift + ', ' + lstates + ');');
try {
@ -342,7 +366,7 @@ function toHex(theString) {
* Reference: Issue #5376
*/
function checkTextArea() {
var uaRe = /Chrome\/([0-9]*)./g;
var uaRe = /Chrome\/([0-9]*)\./g;
var chromeMajorVersion = uaRe.exec(navigator.userAgent);
if (chromeMajorVersion && parseInt(chromeMajorVersion[1]) <= 37) {
var ta = document.getElementById('ta');

View file

@ -2683,17 +2683,24 @@ public final class KMManager {
start = temp;
}
if (dn <= 0) {
int deleteLeft = dn;
if(start != end && dn == 1 && s.length() == 0) {
/* Handle backspace with a selection: just delete selection */
deleteLeft = 0;
}
if (deleteLeft <= 0) {
if (start == end) {
if (s.length() > 0 && s.charAt(0) == '\n') {
textView.keyDownUp(KeyEvent.KEYCODE_ENTER);
} else {
// *** TO DO: Try to find a solution to the bug on API < 17, insert overwrites on next line
if (s.length() > 0) {
} else if (s.length() > 0) {
// *** TO DO: Try to find a solution to the bug on API < 17, insert overwrites on next line
InAppKeyboardShouldIgnoreTextChange = true;
InAppKeyboardShouldIgnoreSelectionChange = true;
textView.getText().insert(start, s);
}
} else {
textView.getText().delete(start, end);
}
} else {
if (s.length() > 0 && s.charAt(0) == '\n') {
@ -2712,7 +2719,16 @@ public final class KMManager {
}
}
} else {
for (int i = 0; i < dn; i++) {
if(start != end) {
// Delete the selection
InAppKeyboardShouldIgnoreTextChange = true;
InAppKeyboardShouldIgnoreSelectionChange = true;
textView.getText().delete(start, end);
textView.setSelection(start);
end = start;
deleteLeft = 0;
}
for (int i = 0; i < deleteLeft; i++) {
CharSequence chars = textView.getText().subSequence(0, start);
if (chars != null && chars.length() > 0) {
char c = chars.charAt(start - 1);
@ -2736,6 +2752,8 @@ public final class KMManager {
}
}
// Collapse the selection
textView.setSelection(start + s.length());
textView.endBatchEdit();
}
});
@ -2789,6 +2807,7 @@ public final class KMManager {
// This annotation is required in Jelly Bean and later:
@JavascriptInterface
public void insertText(final int dn, final String s, final int dr) {
// TODO: Unify in-app and system insertText
Handler mainLoop = new Handler(Looper.getMainLooper());
mainLoop.post(new Runnable() {
public void run() {
@ -2813,11 +2832,19 @@ public final class KMManager {
ic.beginBatchEdit();
int deleteLeft = dn;
// Delete any existing selected text.
ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0);
if (icText != null) { // This can be null if the input connection becomes invalid.
int start = icText.startOffset + icText.selectionStart;
int end = icText.startOffset + icText.selectionEnd;
if (end < start) {
// Swap start/end for backward selection
int temp = start;
start = end;
end = temp;
}
if (end > start) {
if (s.length() == 0) {
ic.setSelection(start, start);
@ -2829,6 +2856,10 @@ public final class KMManager {
ic.setSelection(start, start);
ic.deleteSurroundingText(0, end - start);
}
// KeymanWeb tells us how to delete the selection, but we don't
// want to do that twice
deleteLeft = 0;
}
}
@ -2839,8 +2870,8 @@ public final class KMManager {
}
// Perform left-deletions
if (dn > 0) {
performLeftDeletions(ic, dn);
if (deleteLeft > 0) {
performLeftDeletions(ic, deleteLeft);
}
// Perform right-deletions
@ -2875,8 +2906,8 @@ public final class KMManager {
}
/*
// TODO: Chromium has a bug where deleteSurroundingText deletes an entire grapheme cluster
// instead of one code-point. See Chromium issue #1024738
// Chromium up until version M81 had a bug where deleteSurroundingText deletes an entire
// grapheme cluster instead of one code-point. See Chromium issue #1024738
// https://bugs.chromium.org/p/chromium/issues/detail?id=1024738
//
// We'll retrieve up to (dn*2+16) characters before the cursor to collect enough characters
@ -2894,7 +2925,17 @@ public final class KMManager {
return;
}
int numPairs = CharSequenceUtil.countSurrogatePairs(charsBackup, dn);
// Count the number of characters which are surrogate pairs
int index = lastIndex, dnx = dn, numPairs = 0;
while(index > 0 && dnx > 0) {
if(Character.isLowSurrogate(charsBackup.charAt(index)) &&
Character.isHighSurrogate(charsBackup.charAt(index-1))) {
numPairs++;
index--;
}
index--;
dnx--;
}
// Chop dn+numPairs code points from the end of charsBackup
// subSequence indices are start(inclusive) to end(exclusive)