forked from extern/Unexpected-Keyboard
Automatic capitalisation at beginning of sentences
Keep track of end-of-sentence characters while typing and automatically enable shift when appropriate. The last few characters just before the cursor need to be queried in some cases: Begin of input, cursor has moved or text is deleted. This might have a performance cost. This normally only enable shift but it also needs to disable shift when the cursor moves.
This commit is contained in:
parent
2d8ed2d858
commit
324756535e
119
srcs/juloo.keyboard2/Autocapitalisation.java
Normal file
119
srcs/juloo.keyboard2/Autocapitalisation.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package juloo.keyboard2;
|
||||||
|
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputConnection;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
final class Autocapitalisation
|
||||||
|
{
|
||||||
|
private boolean _enabled = false;
|
||||||
|
private boolean _beginning_of_sentence = false;
|
||||||
|
|
||||||
|
/** Keep track of the cursor to differentiate 'selection_updated' events
|
||||||
|
corresponding to typing from cursor movement. */
|
||||||
|
private int _cursor = 0;
|
||||||
|
|
||||||
|
public boolean should_enable_shift()
|
||||||
|
{
|
||||||
|
return _enabled && _beginning_of_sentence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns [true] if shift should be on initially. The input connection
|
||||||
|
isn't stored. */
|
||||||
|
public void started(EditorInfo info, InputConnection ic)
|
||||||
|
{
|
||||||
|
if ((info.inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) == 0)
|
||||||
|
{
|
||||||
|
_enabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_enabled = true;
|
||||||
|
_beginning_of_sentence = ((info.initialCapsMode & TextUtils.CAP_MODE_SENTENCES) != 0);
|
||||||
|
_cursor = 0; // Just a guess
|
||||||
|
scan_text_before_cursor(10, ic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void typed(CharSequence c)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < c.length(); i++)
|
||||||
|
typed(c.charAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void typed(char c)
|
||||||
|
{
|
||||||
|
_cursor++;
|
||||||
|
if (is_beginning_of_sentence(c))
|
||||||
|
_beginning_of_sentence = true;
|
||||||
|
else if (!ignore_at_beginning_of_sentence(c))
|
||||||
|
_beginning_of_sentence = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selection_updated(int old_cursor, int new_cursor, InputConnection ic)
|
||||||
|
{
|
||||||
|
if (new_cursor == _cursor)
|
||||||
|
return;
|
||||||
|
// Text has been inserted
|
||||||
|
if (old_cursor == _cursor && new_cursor > old_cursor)
|
||||||
|
{
|
||||||
|
scan_text_before_cursor(Math.min(new_cursor - old_cursor, 10), ic);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Cursor has moved or [_cursor] wasn't uptodate
|
||||||
|
_beginning_of_sentence = false;
|
||||||
|
scan_text_before_cursor(10, ic);
|
||||||
|
}
|
||||||
|
_cursor = new_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates [_cursor]. */
|
||||||
|
private void scan_text_before_cursor(int range, InputConnection ic)
|
||||||
|
{
|
||||||
|
if (!_enabled) // Don't query characters if disabled
|
||||||
|
return;
|
||||||
|
CharSequence text_before = ic.getTextBeforeCursor(range, 0);
|
||||||
|
if (text_before == null)
|
||||||
|
{
|
||||||
|
_beginning_of_sentence = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_beginning_of_sentence = true;
|
||||||
|
typed(text_before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean ignore_at_beginning_of_sentence(char c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case ' ':
|
||||||
|
case '"':
|
||||||
|
case '\'':
|
||||||
|
case '(':
|
||||||
|
case '«':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean is_beginning_of_sentence(char c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '.':
|
||||||
|
case ';':
|
||||||
|
case '\n':
|
||||||
|
case '!':
|
||||||
|
case '?':
|
||||||
|
case '¿':
|
||||||
|
case '¡':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,7 @@ public class Keyboard2 extends InputMethodService
|
|||||||
private ViewGroup _emojiPane = null;
|
private ViewGroup _emojiPane = null;
|
||||||
|
|
||||||
private Config _config;
|
private Config _config;
|
||||||
|
private Autocapitalisation _autocap = new Autocapitalisation();
|
||||||
|
|
||||||
private boolean _debug_logs = false;
|
private boolean _debug_logs = false;
|
||||||
|
|
||||||
@ -57,6 +58,14 @@ public class Keyboard2 extends InputMethodService
|
|||||||
_debug_logs = getResources().getBoolean(R.bool.debug_logs);
|
_debug_logs = getResources().getBoolean(R.bool.debug_logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void update_shift_state(boolean might_disable)
|
||||||
|
{
|
||||||
|
if (_autocap.should_enable_shift())
|
||||||
|
_keyboardView.set_shift_state(true);
|
||||||
|
else if (might_disable)
|
||||||
|
_keyboardView.set_shift_state(false);
|
||||||
|
}
|
||||||
|
|
||||||
private List<InputMethodSubtype> getEnabledSubtypes(InputMethodManager imm)
|
private List<InputMethodSubtype> getEnabledSubtypes(InputMethodManager imm)
|
||||||
{
|
{
|
||||||
String pkg = getPackageName();
|
String pkg = getPackageName();
|
||||||
@ -163,7 +172,7 @@ public class Keyboard2 extends InputMethodService
|
|||||||
return getResources().getString(res);
|
return getResources().getString(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshEditorInfo(EditorInfo info)
|
private void refresh_action_label(EditorInfo info)
|
||||||
{
|
{
|
||||||
// First try to look at 'info.actionLabel', if it isn't set, look at
|
// First try to look at 'info.actionLabel', if it isn't set, look at
|
||||||
// 'imeOptions'.
|
// 'imeOptions'.
|
||||||
@ -210,11 +219,13 @@ public class Keyboard2 extends InputMethodService
|
|||||||
public void onStartInputView(EditorInfo info, boolean restarting)
|
public void onStartInputView(EditorInfo info, boolean restarting)
|
||||||
{
|
{
|
||||||
refreshConfig();
|
refreshConfig();
|
||||||
refreshEditorInfo(info);
|
refresh_action_label(info);
|
||||||
if ((info.inputType & InputType.TYPE_CLASS_NUMBER) != 0)
|
if ((info.inputType & InputType.TYPE_CLASS_NUMBER) != 0)
|
||||||
_keyboardView.setKeyboard(getLayout(R.xml.numeric));
|
_keyboardView.setKeyboard(getLayout(R.xml.numeric));
|
||||||
else
|
else
|
||||||
_keyboardView.setKeyboard(getLayout(_currentTextLayout));
|
_keyboardView.setKeyboard(getLayout(_currentTextLayout));
|
||||||
|
_autocap.started(info, getCurrentInputConnection());
|
||||||
|
update_shift_state(false);
|
||||||
setInputView(_keyboardView);
|
setInputView(_keyboardView);
|
||||||
if (_debug_logs)
|
if (_debug_logs)
|
||||||
log_editor_info(info);
|
log_editor_info(info);
|
||||||
@ -236,6 +247,14 @@ public class Keyboard2 extends InputMethodService
|
|||||||
_keyboardView.setKeyboard(getLayout(_currentTextLayout));
|
_keyboardView.setKeyboard(getLayout(_currentTextLayout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)
|
||||||
|
{
|
||||||
|
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd);
|
||||||
|
_autocap.selection_updated(oldSelStart, newSelStart, getCurrentInputConnection());
|
||||||
|
update_shift_state(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinishInputView(boolean finishingInput)
|
public void onFinishInputView(boolean finishingInput)
|
||||||
{
|
{
|
||||||
@ -330,11 +349,15 @@ public class Keyboard2 extends InputMethodService
|
|||||||
public void commitText(String text)
|
public void commitText(String text)
|
||||||
{
|
{
|
||||||
getCurrentInputConnection().commitText(text, 1);
|
getCurrentInputConnection().commitText(text, 1);
|
||||||
|
_autocap.typed(text);
|
||||||
|
update_shift_state(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commitChar(char c)
|
public void commitChar(char c)
|
||||||
{
|
{
|
||||||
sendKeyChar(c);
|
sendKeyChar(c);
|
||||||
|
_autocap.typed(c);
|
||||||
|
update_shift_state(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +90,27 @@ public class Keyboard2View extends View
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Called by auto-capitalisation. */
|
||||||
|
public void set_shift_state(boolean state)
|
||||||
|
{
|
||||||
|
KeyValue shift = KeyValue.getKeyByName("shift");
|
||||||
|
KeyboardData.Key key = _keyboard.findKeyWithValue(shift);
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
// Lookup again for the lockable shift key, which is a different value.
|
||||||
|
shift = shift.withFlags(shift.getFlags() | KeyValue.FLAG_LOCK);
|
||||||
|
key = _keyboard.findKeyWithValue(shift);
|
||||||
|
}
|
||||||
|
if (key != null)
|
||||||
|
{
|
||||||
|
if (state)
|
||||||
|
_pointers.add_fake_pointer(shift, key);
|
||||||
|
else
|
||||||
|
_pointers.remove_fake_pointer(shift, key);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods)
|
public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods)
|
||||||
{
|
{
|
||||||
return KeyModifier.modify(k, mods);
|
return KeyModifier.modify(k, mods);
|
||||||
|
@ -43,6 +43,17 @@ class KeyboardData
|
|||||||
return new KeyboardData(rows, keysWidth, extra_keys);
|
return new KeyboardData(rows, keysWidth, extra_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Key findKeyWithValue(KeyValue kv)
|
||||||
|
{
|
||||||
|
for (Row r : rows)
|
||||||
|
{
|
||||||
|
Key k = r.findKeyWithValue(kv);
|
||||||
|
if (k != null)
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static void addExtraKeys_to_row(ArrayList<Row> rows, final Iterator<KeyValue> extra_keys, int row_i, final int d)
|
private static void addExtraKeys_to_row(ArrayList<Row> rows, final Iterator<KeyValue> extra_keys, int row_i, final int d)
|
||||||
{
|
{
|
||||||
if (!extra_keys.hasNext())
|
if (!extra_keys.hasNext())
|
||||||
@ -168,6 +179,14 @@ class KeyboardData
|
|||||||
public Key apply(Key k) { return k.scaleWidth(s); }
|
public Key apply(Key k) { return k.scaleWidth(s); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Key findKeyWithValue(KeyValue kv)
|
||||||
|
{
|
||||||
|
for (Key k : keys)
|
||||||
|
if (k.hasValue(kv))
|
||||||
|
return k;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Key
|
public static class Key
|
||||||
@ -291,6 +310,17 @@ class KeyboardData
|
|||||||
}
|
}
|
||||||
return (c == null) ? null : c.kv;
|
return (c == null) ? null : c.kv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasValue(KeyValue kv)
|
||||||
|
{
|
||||||
|
return (hasValue(key0, kv) || hasValue(key1, kv) || hasValue(key2, kv) ||
|
||||||
|
hasValue(key3, kv) || hasValue(key4, kv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasValue(Corner c, KeyValue kv)
|
||||||
|
{
|
||||||
|
return (c != null && c.kv.equals(kv));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Corner
|
public static final class Corner
|
||||||
|
@ -74,6 +74,24 @@ public final class Pointers implements Handler.Callback
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fake pointers are latched and not lockable. */
|
||||||
|
public void add_fake_pointer(KeyValue kv, KeyboardData.Key key)
|
||||||
|
{
|
||||||
|
// Avoid adding a fake pointer to a key that is already down.
|
||||||
|
if (isKeyDown(key))
|
||||||
|
return;
|
||||||
|
Pointer ptr = new Pointer(-1, key, kv, 0.f, 0.f, Modifiers.EMPTY);
|
||||||
|
ptr.flags = ptr.flags & ~(KeyValue.FLAG_LATCH | KeyValue.FLAG_LOCK);
|
||||||
|
_ptrs.add(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove_fake_pointer(KeyValue kv, KeyboardData.Key key)
|
||||||
|
{
|
||||||
|
Pointer ptr = getLatched(key, kv);
|
||||||
|
if (ptr != null)
|
||||||
|
removePtr(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
// Receiving events
|
// Receiving events
|
||||||
|
|
||||||
public void onTouchUp(int pointerId)
|
public void onTouchUp(int pointerId)
|
||||||
@ -252,8 +270,11 @@ public final class Pointers implements Handler.Callback
|
|||||||
|
|
||||||
private Pointer getLatched(Pointer target)
|
private Pointer getLatched(Pointer target)
|
||||||
{
|
{
|
||||||
KeyboardData.Key k = target.key;
|
return getLatched(target.key, target.value);
|
||||||
KeyValue v = target.value;
|
}
|
||||||
|
|
||||||
|
private Pointer getLatched(KeyboardData.Key k, KeyValue v)
|
||||||
|
{
|
||||||
if (v == null)
|
if (v == null)
|
||||||
return null;
|
return null;
|
||||||
for (Pointer p : _ptrs)
|
for (Pointer p : _ptrs)
|
||||||
|
Loading…
Reference in New Issue
Block a user