* Construct a single handler * Add a delay after a Keyevent key in a macro Add a delay before sending the next key to avoid race conditions causing keys to be handled in the wrong order. Notably, KeyEvent keys handling is scheduled differently than the other edit functions.
204 lines
5.3 KiB
Java
204 lines
5.3 KiB
Java
package juloo.keyboard2;
|
|
|
|
import android.os.Handler;
|
|
import android.text.InputType;
|
|
import android.text.TextUtils;
|
|
import android.view.inputmethod.EditorInfo;
|
|
import android.view.inputmethod.InputConnection;
|
|
import android.view.KeyEvent;
|
|
|
|
public final class Autocapitalisation
|
|
{
|
|
boolean _enabled = false;
|
|
boolean _should_enable_shift = false;
|
|
boolean _should_disable_shift = false;
|
|
boolean _should_update_caps_mode = false;
|
|
|
|
Handler _handler;
|
|
InputConnection _ic;
|
|
Callback _callback;
|
|
int _caps_mode;
|
|
|
|
/** Keep track of the cursor to recognize cursor movements from typing. */
|
|
int _cursor;
|
|
|
|
static int SUPPORTED_CAPS_MODES =
|
|
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES |
|
|
InputType.TYPE_TEXT_FLAG_CAP_WORDS;
|
|
|
|
public Autocapitalisation(Handler h, Callback cb)
|
|
{
|
|
_handler = h;
|
|
_callback = cb;
|
|
}
|
|
|
|
/**
|
|
* The events are: started, typed, event sent, selection updated
|
|
* [started] does initialisation work and must be called before any other
|
|
* event.
|
|
*/
|
|
public void started(EditorInfo info, InputConnection ic)
|
|
{
|
|
_ic = ic;
|
|
_caps_mode = info.inputType & TextUtils.CAP_MODE_SENTENCES;
|
|
if (!Config.globalConfig().autocapitalisation || _caps_mode == 0)
|
|
{
|
|
_enabled = false;
|
|
return;
|
|
}
|
|
_enabled = true;
|
|
_should_enable_shift = (info.initialCapsMode != 0);
|
|
_should_update_caps_mode = started_should_update_state(info.inputType);
|
|
callback_now(true);
|
|
}
|
|
|
|
public void typed(CharSequence c)
|
|
{
|
|
for (int i = 0; i < c.length(); i++)
|
|
type_one_char(c.charAt(i));
|
|
callback(false);
|
|
}
|
|
|
|
public void event_sent(int code, int meta)
|
|
{
|
|
if (meta != 0)
|
|
{
|
|
_should_enable_shift = false;
|
|
_should_update_caps_mode = false;
|
|
return;
|
|
}
|
|
switch (code)
|
|
{
|
|
case KeyEvent.KEYCODE_DEL:
|
|
if (_cursor > 0) _cursor--;
|
|
_should_update_caps_mode = true;
|
|
break;
|
|
case KeyEvent.KEYCODE_ENTER:
|
|
_should_update_caps_mode = true;
|
|
break;
|
|
}
|
|
callback(true);
|
|
}
|
|
|
|
public void stop()
|
|
{
|
|
_should_enable_shift = false;
|
|
_should_update_caps_mode = false;
|
|
callback_now(true);
|
|
}
|
|
|
|
/** Pause auto capitalisation until [unpause()] is called. */
|
|
public boolean pause()
|
|
{
|
|
boolean was_enabled = _enabled;
|
|
stop();
|
|
_enabled = false;
|
|
return was_enabled;
|
|
}
|
|
|
|
/** Continue auto capitalisation after [pause()] was called. Argument is the
|
|
output of [pause()]. */
|
|
public void unpause(boolean was_enabled)
|
|
{
|
|
_enabled = was_enabled;
|
|
_should_update_caps_mode = true;
|
|
callback_now(true);
|
|
}
|
|
|
|
public static interface Callback
|
|
{
|
|
public void update_shift_state(boolean should_enable, boolean should_disable);
|
|
}
|
|
|
|
/** Returns [true] if shift might be disabled. */
|
|
public void selection_updated(int old_cursor, int new_cursor)
|
|
{
|
|
if (new_cursor == _cursor) // Just typing
|
|
return;
|
|
if (new_cursor == 0 && _ic != null)
|
|
{
|
|
// Detect whether the input box has been cleared
|
|
CharSequence t = _ic.getTextAfterCursor(1, 0);
|
|
if (t != null && t.equals(""))
|
|
_should_update_caps_mode = true;
|
|
}
|
|
_cursor = new_cursor;
|
|
_should_enable_shift = false;
|
|
callback(true);
|
|
}
|
|
|
|
Runnable delayed_callback = new Runnable()
|
|
{
|
|
public void run()
|
|
{
|
|
if (_should_update_caps_mode && _ic != null)
|
|
{
|
|
_should_enable_shift = _enabled && (_ic.getCursorCapsMode(_caps_mode) != 0);
|
|
_should_update_caps_mode = false;
|
|
}
|
|
_callback.update_shift_state(_should_enable_shift, _should_disable_shift);
|
|
}
|
|
};
|
|
|
|
/** Update the shift state if [_should_update_caps_mode] is true, then call
|
|
[_callback.update_shift_state]. This is done after a short delay to wait
|
|
for the editor to handle the events, as this might be called before the
|
|
corresponding event is sent. */
|
|
void callback(boolean might_disable)
|
|
{
|
|
_should_disable_shift = might_disable;
|
|
// The callback must be delayed because [getCursorCapsMode] would sometimes
|
|
// be called before the editor finished handling the previous event.
|
|
_handler.postDelayed(delayed_callback, 1);
|
|
}
|
|
|
|
/** Like [callback] but runs immediately. */
|
|
void callback_now(boolean might_disable)
|
|
{
|
|
_should_disable_shift = might_disable;
|
|
delayed_callback.run();
|
|
}
|
|
|
|
void type_one_char(char c)
|
|
{
|
|
_cursor++;
|
|
if (is_trigger_character(c))
|
|
_should_update_caps_mode = true;
|
|
else
|
|
_should_enable_shift = false;
|
|
}
|
|
|
|
boolean is_trigger_character(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case ' ':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Whether the caps state should be updated when input starts. [inputType]
|
|
is the field from the editor info object. */
|
|
boolean started_should_update_state(int inputType)
|
|
{
|
|
int class_ = inputType & InputType.TYPE_MASK_CLASS;
|
|
int variation = inputType & InputType.TYPE_MASK_VARIATION;
|
|
if (class_ != InputType.TYPE_CLASS_TEXT)
|
|
return false;
|
|
switch (variation)
|
|
{
|
|
case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE:
|
|
case InputType.TYPE_TEXT_VARIATION_NORMAL:
|
|
case InputType.TYPE_TEXT_VARIATION_PERSON_NAME:
|
|
case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE:
|
|
case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT:
|
|
case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|