309 lines
9.6 KiB
Kotlin
309 lines
9.6 KiB
Kotlin
package net.mezimmah.wkt9
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.Intent
|
|
import android.inputmethodservice.InputMethodService
|
|
import android.provider.Settings
|
|
import android.util.Log
|
|
import android.view.KeyEvent
|
|
import android.view.View
|
|
import android.view.inputmethod.CursorAnchorInfo
|
|
import android.view.inputmethod.EditorInfo
|
|
import android.view.inputmethod.InputConnection
|
|
import android.view.inputmethod.InputMethodManager
|
|
import android.view.inputmethod.InputMethodSubtype
|
|
import net.mezimmah.wkt9.entity.Word
|
|
import net.mezimmah.wkt9.inputhandler.IdleInputHandler
|
|
import net.mezimmah.wkt9.inputhandler.InputHandler
|
|
import net.mezimmah.wkt9.inputhandler.LetterInputHandler
|
|
import net.mezimmah.wkt9.inputhandler.NumberInputHandler
|
|
import net.mezimmah.wkt9.inputhandler.WordInputHandler
|
|
import net.mezimmah.wkt9.inputmode.InputModeManager
|
|
import net.mezimmah.wkt9.inputmode.InputMode
|
|
import net.mezimmah.wkt9.keypad.Event
|
|
import net.mezimmah.wkt9.keypad.Key
|
|
import net.mezimmah.wkt9.keypad.KeyEventStat
|
|
import net.mezimmah.wkt9.layout.Words
|
|
import net.mezimmah.wkt9.t9.T9
|
|
import java.util.Locale
|
|
|
|
|
|
class WKT9IME: IME, InputMethodService() {
|
|
private val tag = "WKT9"
|
|
|
|
private val inputModeManager = InputModeManager(this)
|
|
|
|
private lateinit var locale: Locale
|
|
private var inputHandler: InputHandler? = null
|
|
|
|
private var wordsView: Words? = null
|
|
|
|
private val keyDownStats = KeyEventStat(0, 0)
|
|
private val keyUpStats = KeyEventStat(0, 0)
|
|
|
|
private var composing: Boolean = false
|
|
private var selectionStart: Int = 0
|
|
private var selectionEnd: Int = 0
|
|
|
|
override fun onCandidates(candidates: ArrayList<CharSequence>, current: Int?) {
|
|
// this.candidates?.load(candidates, current)
|
|
}
|
|
|
|
override fun onCommit(text: CharSequence, beforeCursor: Int, afterCursor: Int) {
|
|
currentInputConnection?.run {
|
|
beginBatchEdit()
|
|
setComposingRegion(selectionEnd - beforeCursor, selectionEnd + afterCursor)
|
|
setComposingText(text, 1)
|
|
finishComposingText()
|
|
endBatchEdit()
|
|
}
|
|
}
|
|
|
|
override fun onCompose(text: CharSequence) {
|
|
currentInputConnection?.run {
|
|
if (!composing) setComposingRegion(selectionStart, selectionEnd)
|
|
|
|
setComposingText(text, 1)
|
|
}
|
|
}
|
|
|
|
@SuppressLint("InflateParams")
|
|
override fun onCreate() {
|
|
Log.d(tag, "Starting WKT9")
|
|
|
|
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
|
val languageTag = inputMethodManager.currentInputMethodSubtype?.languageTag ?: "en-US"
|
|
|
|
locale = Locale.forLanguageTag(languageTag)
|
|
|
|
initializeDictionary(locale)
|
|
|
|
super.onCreate()
|
|
}
|
|
|
|
@SuppressLint("InflateParams")
|
|
override fun onCreateInputView(): View? {
|
|
wordsView = layoutInflater.inflate(R.layout.words, null) as Words
|
|
|
|
return wordsView
|
|
}
|
|
|
|
override fun onCurrentInputMethodSubtypeChanged(newSubtype: InputMethodSubtype?) {
|
|
super.onCurrentInputMethodSubtypeChanged(newSubtype)
|
|
|
|
newSubtype?.let {
|
|
locale = Locale.forLanguageTag(it.languageTag)
|
|
|
|
initializeDictionary(locale)
|
|
inputHandler?.onSwitchLocale(locale)
|
|
}
|
|
}
|
|
|
|
override fun onDeleteText(beforeCursor: Int, afterCursor: Int, finishComposing: Boolean) {
|
|
deleteText(beforeCursor, afterCursor)
|
|
}
|
|
|
|
override fun onGetTextBeforeCursor(n: Int): CharSequence? {
|
|
return this.currentInputConnection?.getTextBeforeCursor(n, 0)
|
|
}
|
|
|
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
|
if (keyDownStats.keyCode != keyCode) {
|
|
keyDownStats.keyCode = keyCode
|
|
keyDownStats.repeats = 0
|
|
} else keyDownStats.repeats++
|
|
|
|
val key = Key.fromKeyCode(keyCode)
|
|
|
|
if (event == null || key == null) return super.onKeyDown(keyCode, event)
|
|
|
|
var consume = key.consume
|
|
val hasLongDownMapping = key.mappings.hasLongDownMapping(InputMode.Word)
|
|
val mappings = key.mappings.match(
|
|
event = if (event.repeatCount > 0) Event.keyDownRepeat else Event.keyDown,
|
|
inputMode = inputModeManager.currentMode,
|
|
packageName = currentInputEditorInfo.packageName,
|
|
fn = event.isFunctionPressed,
|
|
ctrl = event.isCtrlPressed,
|
|
repeatCount = event.repeatCount
|
|
)
|
|
|
|
mappings?.map { mapping ->
|
|
if (mapping.command != null) {
|
|
inputHandler?.onRunCommand(mapping.command, key, event, keyDownStats)
|
|
}
|
|
|
|
if (mapping.overrideConsume) consume = mapping.consume
|
|
}
|
|
|
|
if (hasLongDownMapping && event.repeatCount == 0) event.startTracking()
|
|
|
|
return when (consume) {
|
|
true -> true
|
|
false -> false
|
|
else -> super.onKeyDown(keyCode, event)
|
|
}
|
|
}
|
|
|
|
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
|
if (keyUpStats.keyCode != keyCode) {
|
|
keyUpStats.keyCode = keyCode
|
|
keyUpStats.repeats = 0
|
|
} else keyUpStats.repeats++
|
|
|
|
val key = Key.fromKeyCode(keyCode)
|
|
|
|
if (event == null || key == null) return super.onKeyUp(keyCode, event)
|
|
|
|
var consume = key.consume
|
|
val keyDownMS = event.eventTime - event.downTime
|
|
val mappings = key.mappings.match(
|
|
event = if (keyDownMS >= 400L) Event.afterLongDown else Event.afterShortDown,
|
|
inputMode = inputModeManager.currentMode,
|
|
packageName = currentInputEditorInfo.packageName,
|
|
fn = event.isFunctionPressed,
|
|
ctrl = event.isCtrlPressed,
|
|
repeatCount = event.repeatCount
|
|
)
|
|
|
|
mappings?.map { mapping ->
|
|
if (mapping.command != null) {
|
|
inputHandler?.onRunCommand(mapping.command, key, event, keyUpStats)
|
|
}
|
|
|
|
if (mapping.overrideConsume) consume = mapping.consume
|
|
}
|
|
|
|
return when (consume) {
|
|
true -> true
|
|
false -> false
|
|
else -> super.onKeyUp(keyCode, event)
|
|
}
|
|
}
|
|
|
|
override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
|
|
val key = Key.fromKeyCode(keyCode)
|
|
|
|
if (event == null || key == null) return super.onKeyLongPress(keyCode, event)
|
|
|
|
var consume = key.consume
|
|
val mappings = key.mappings.match(
|
|
event = Event.keyLongDown,
|
|
inputMode = inputModeManager.currentMode,
|
|
packageName = currentInputEditorInfo.packageName,
|
|
fn = event.isFunctionPressed,
|
|
ctrl = event.isCtrlPressed
|
|
)
|
|
|
|
mappings?.map { mapping ->
|
|
if (mapping.command != null) {
|
|
inputHandler?.onRunCommand(mapping.command, key, event, keyDownStats)
|
|
}
|
|
|
|
if (mapping.overrideConsume) consume = mapping.consume
|
|
}
|
|
|
|
return when (consume) {
|
|
true -> true
|
|
false -> false
|
|
else -> super.onKeyLongPress(keyCode, event)
|
|
}
|
|
}
|
|
|
|
override fun onShowInputRequested(flags: Int, configChange: Boolean): Boolean {
|
|
return (
|
|
inputModeManager.currentMode != InputMode.Number &&
|
|
inputModeManager.currentMode != InputMode.Idle
|
|
)
|
|
}
|
|
|
|
override fun onStartInput(editorInfo: EditorInfo?, restarting: Boolean) {
|
|
val mode = inputModeManager.selectModeByEditor(editorInfo)
|
|
|
|
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
|
|
|
|
switchInputMode(mode)
|
|
}
|
|
|
|
override fun onStartIntent(intent: Intent) {
|
|
if (Settings.canDrawOverlays(this)) {
|
|
startActivity(intent)
|
|
}
|
|
}
|
|
|
|
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
|
cursorAnchorInfo?.let {
|
|
selectionStart = it.selectionStart
|
|
selectionEnd = it.selectionEnd
|
|
composing = if (it.composingTextStart == -1) {
|
|
if (composing) finishComposing()
|
|
|
|
false
|
|
} else if (it.selectionEnd != (it.composingTextStart + it.composingText.length)) {
|
|
onCommit()
|
|
|
|
true
|
|
} else true
|
|
}
|
|
|
|
super.onUpdateCursorAnchorInfo(cursorAnchorInfo)
|
|
}
|
|
|
|
override fun onSwitchInputHandler(inputMode: InputMode) {
|
|
val mode = inputModeManager.switchToMode(inputMode)
|
|
|
|
switchInputMode(mode)
|
|
}
|
|
|
|
override fun onTriggerKeyEvent(event: KeyEvent) {
|
|
currentInputConnection?.sendKeyEvent(event)
|
|
}
|
|
|
|
override fun onUpdateStatusIcon(icon: Int?) {
|
|
if (icon == null) hideStatusIcon()
|
|
else showStatusIcon(icon)
|
|
}
|
|
|
|
override fun onWords(words: List<Word>) {
|
|
wordsView?.words = words
|
|
}
|
|
|
|
override fun onWordSelected(word: Word) {
|
|
this.onCompose(word.word)
|
|
this.inputHandler?.onWordSelected(word)
|
|
}
|
|
|
|
override fun onNextWord() {
|
|
wordsView?.next()
|
|
}
|
|
|
|
override fun onPreviousWord() {
|
|
wordsView?.previous()
|
|
}
|
|
|
|
private fun deleteText(beforeCursor: Int, afterCursor: Int) {
|
|
currentInputConnection?.run {
|
|
deleteSurroundingText(beforeCursor, afterCursor)
|
|
}
|
|
}
|
|
|
|
private fun finishComposing() {
|
|
wordsView?.clear()
|
|
inputHandler?.onFinishComposing()
|
|
}
|
|
|
|
private fun initializeDictionary(locale: Locale) {
|
|
val t9 = T9(this, locale)
|
|
|
|
t9.initializeWords()
|
|
}
|
|
|
|
private fun switchInputMode(mode: InputMode) {
|
|
inputHandler = when(mode) {
|
|
InputMode.Word -> WordInputHandler(this, this, locale)
|
|
InputMode.Letter -> LetterInputHandler(this, this, locale)
|
|
InputMode.Number -> NumberInputHandler(this, this)
|
|
else -> IdleInputHandler(this, this)
|
|
}
|
|
}
|
|
} |