2023-12-01 16:30:59 -05:00

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)
}
}
}