mirror of
				https://github.com/SimpleMobileTools/Simple-Keyboard.git
				synced 2025-06-05 21:49:26 +02:00 
			
		
		
		
	adding an initial implementation of MyKeyboard
This commit is contained in:
		| @@ -0,0 +1,849 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (C) 2008-2009 Google Inc. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||||||
|  |  * use this file except in compliance with the License. You may obtain a copy of | ||||||
|  |  * the License at | ||||||
|  |  * | ||||||
|  |  * http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  |  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  |  * License for the specific language governing permissions and limitations under | ||||||
|  |  * the License. | ||||||
|  |  */ | ||||||
|  | package com.simplemobiletools.keyboard.helpers | ||||||
|  |  | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.res.Resources | ||||||
|  | import android.content.res.TypedArray | ||||||
|  | import android.content.res.XmlResourceParser | ||||||
|  | import android.graphics.drawable.Drawable | ||||||
|  | import android.inputmethodservice.Keyboard | ||||||
|  | import android.text.TextUtils | ||||||
|  | import android.util.TypedValue | ||||||
|  | import android.util.Xml | ||||||
|  | import androidx.annotation.XmlRes | ||||||
|  | import com.simplemobiletools.keyboard.R | ||||||
|  | import org.xmlpull.v1.XmlPullParserException | ||||||
|  | import java.io.IOException | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard | ||||||
|  |  * consists of rows of keys. | ||||||
|  |  * | ||||||
|  |  * The layout file for a keyboard contains XML that looks like the following snippet: | ||||||
|  |  * <pre> | ||||||
|  |  * <Keyboard | ||||||
|  |  * android:keyWidth="%10p" | ||||||
|  |  * android:keyHeight="50px" | ||||||
|  |  * android:horizontalGap="2px" | ||||||
|  |  * android:verticalGap="2px" > | ||||||
|  |  * <Row android:keyWidth="32px" > | ||||||
|  |  * <Key android:keyLabel="A" /> | ||||||
|  |  * ... | ||||||
|  |  * </Row> | ||||||
|  |  * ... | ||||||
|  |  * </Keyboard> | ||||||
|  | </pre> * | ||||||
|  |  * @attr ref android.R.styleable#Keyboard_keyWidth | ||||||
|  |  * @attr ref android.R.styleable#Keyboard_keyHeight | ||||||
|  |  * @attr ref android.R.styleable#Keyboard_horizontalGap | ||||||
|  |  * @attr ref android.R.styleable#Keyboard_verticalGap | ||||||
|  |  */ | ||||||
|  | class MyKeyboard { | ||||||
|  |     /** Keyboard label  */ | ||||||
|  |     private val mLabel: CharSequence? = null | ||||||
|  |  | ||||||
|  |     /** Horizontal gap default for all rows  */ | ||||||
|  |     protected var mDefaultHorizontalGap = 0 | ||||||
|  |  | ||||||
|  |     /** Default key width  */ | ||||||
|  |     protected var mDefaultWidth = 0 | ||||||
|  |  | ||||||
|  |     /** Default key height  */ | ||||||
|  |     protected var mDefaultHeight = 0 | ||||||
|  |  | ||||||
|  |     /** Default gap between rows  */ | ||||||
|  |     protected var mDefaultVerticalGap = 0 | ||||||
|  |  | ||||||
|  |     /** Is the keyboard in the shifted state  */ | ||||||
|  |     var isShifted = false | ||||||
|  |         private set | ||||||
|  |  | ||||||
|  |     /** Key instance for the shift key, if present  */ | ||||||
|  |     private val mShiftKeys = arrayOf<Key?>(null, null) | ||||||
|  |     /** | ||||||
|  |      * @hide | ||||||
|  |      */ | ||||||
|  |     /** Key index for the shift key, if present  */ | ||||||
|  |     val shiftKeyIndices = intArrayOf(-1, -1) | ||||||
|  |  | ||||||
|  |     /** Current key width, while loading the keyboard  */ | ||||||
|  |     private val mKeyWidth = 0 | ||||||
|  |  | ||||||
|  |     /** Current key height, while loading the keyboard  */ | ||||||
|  |     private val mKeyHeight = 0 | ||||||
|  |     /** | ||||||
|  |      * Returns the total height of the keyboard | ||||||
|  |      * @return the total height of the keyboard | ||||||
|  |      */ | ||||||
|  |     /** Total height of the keyboard, including the padding and keys  */ | ||||||
|  |     var height = 0 | ||||||
|  |         private set | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Total width of the keyboard, including left side gaps and keys, but not any gaps on the | ||||||
|  |      * right side. | ||||||
|  |      */ | ||||||
|  |     var minWidth = 0 | ||||||
|  |         private set | ||||||
|  |  | ||||||
|  |     /** List of keys in this keyboard  */ | ||||||
|  |     private var mKeys: MutableList<Key?>? = null | ||||||
|  |  | ||||||
|  |     /** List of modifier keys such as Shift & Alt, if any  */ | ||||||
|  |     private var mModifierKeys: MutableList<Key?>? = null | ||||||
|  |  | ||||||
|  |     /** Width of the screen available to fit the keyboard  */ | ||||||
|  |     private var mDisplayWidth = 0 | ||||||
|  |  | ||||||
|  |     /** Height of the screen  */ | ||||||
|  |     private var mDisplayHeight = 0 | ||||||
|  |  | ||||||
|  |     /** Keyboard mode, or zero, if none.   */ | ||||||
|  |     private var mKeyboardMode = 0 | ||||||
|  |     private var mCellWidth = 0 | ||||||
|  |     private var mCellHeight = 0 | ||||||
|  |     private var mGridNeighbors = ArrayList<IntArray?>() | ||||||
|  |     private var mProximityThreshold = 0 | ||||||
|  |     private val rows = ArrayList<Row?>() | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         const val TAG = "Keyboard" | ||||||
|  |  | ||||||
|  |         // Keyboard XML Tags | ||||||
|  |         private const val TAG_KEYBOARD = "Keyboard" | ||||||
|  |         private const val TAG_ROW = "Row" | ||||||
|  |         private const val TAG_KEY = "Key" | ||||||
|  |         const val EDGE_LEFT = 0x01 | ||||||
|  |         const val EDGE_RIGHT = 0x02 | ||||||
|  |         const val EDGE_TOP = 0x04 | ||||||
|  |         const val EDGE_BOTTOM = 0x08 | ||||||
|  |         const val KEYCODE_SHIFT = -1 | ||||||
|  |         const val KEYCODE_MODE_CHANGE = -2 | ||||||
|  |         const val KEYCODE_CANCEL = -3 | ||||||
|  |         const val KEYCODE_DONE = -4 | ||||||
|  |         const val KEYCODE_DELETE = -5 | ||||||
|  |         const val KEYCODE_ALT = -6 | ||||||
|  |  | ||||||
|  |         // Variables for pre-computing nearest keys. | ||||||
|  |         private const val GRID_WIDTH = 10 | ||||||
|  |         private const val GRID_HEIGHT = 5 | ||||||
|  |         private val GRID_SIZE: Int = GRID_WIDTH * GRID_HEIGHT | ||||||
|  |  | ||||||
|  |         /** Number of key widths from current touch point to search for nearest keys.  */ | ||||||
|  |         private const val SEARCH_DISTANCE = 1.8f | ||||||
|  |         fun getDimensionOrFraction(a: TypedArray, index: Int, base: Int, defValue: Int): Int { | ||||||
|  |             val value = a.peekValue(index) ?: return defValue | ||||||
|  |             if (value.type == TypedValue.TYPE_DIMENSION) { | ||||||
|  |                 return a.getDimensionPixelOffset(index, defValue) | ||||||
|  |             } else if (value.type == TypedValue.TYPE_FRACTION) { | ||||||
|  |                 // Round it to avoid values like 47.9999 from getting truncated | ||||||
|  |                 return Math.round(a.getFraction(index, base, base, defValue.toFloat())) | ||||||
|  |             } | ||||||
|  |             return defValue | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. | ||||||
|  |      * Some of the key size defaults can be overridden per row from what the [Keyboard] | ||||||
|  |      * defines. | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_keyWidth | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_keyHeight | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_horizontalGap | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_verticalGap | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Row_rowEdgeFlags | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Row_keyboardMode | ||||||
|  |      */ | ||||||
|  |     class Row { | ||||||
|  |         /** Default width of a key in this row.  */ | ||||||
|  |         var defaultWidth = 0 | ||||||
|  |  | ||||||
|  |         /** Default height of a key in this row.  */ | ||||||
|  |         var defaultHeight = 0 | ||||||
|  |  | ||||||
|  |         /** Default horizontal gap between keys in this row.  */ | ||||||
|  |         var defaultHorizontalGap = 0 | ||||||
|  |  | ||||||
|  |         /** Vertical gap following this row.  */ | ||||||
|  |         var verticalGap = 0 | ||||||
|  |         var mKeys = ArrayList<Key>() | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Edge flags for this row of keys. Possible values that can be assigned are | ||||||
|  |          * [EDGE_TOP][Keyboard.EDGE_TOP] and [EDGE_BOTTOM][Keyboard.EDGE_BOTTOM] | ||||||
|  |          */ | ||||||
|  |         var rowEdgeFlags = 0 | ||||||
|  |  | ||||||
|  |         /** The keyboard mode for this row  */ | ||||||
|  |         var mode = 0 | ||||||
|  |         var parent: MyKeyboard | ||||||
|  |  | ||||||
|  |         constructor(parent: MyKeyboard) { | ||||||
|  |             this.parent = parent | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         constructor(res: Resources, parent: MyKeyboard, parser: XmlResourceParser?) { | ||||||
|  |             this.parent = parent | ||||||
|  |             var a = res.obtainAttributes( | ||||||
|  |                 Xml.asAttributeSet(parser), | ||||||
|  |                 R.styleable.Keyboard | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             defaultWidth = getDimensionOrFraction(a, R.styleable.Keyboard_keyWidth, parent.mDisplayWidth, parent.mDefaultWidth) | ||||||
|  |             defaultHeight = getDimensionOrFraction(a, R.styleable.Keyboard_keyHeight, parent.mDisplayHeight, parent.mDefaultHeight) | ||||||
|  |             defaultHorizontalGap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap, parent.mDisplayWidth, parent.mDefaultHorizontalGap) | ||||||
|  |             verticalGap = getDimensionOrFraction(a, R.styleable.Keyboard_verticalGap, parent.mDisplayHeight, parent.mDefaultVerticalGap) | ||||||
|  |  | ||||||
|  |             a.recycle() | ||||||
|  |             a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Row) | ||||||
|  |             rowEdgeFlags = a.getInt(R.styleable.Keyboard_Row_rowEdgeFlags, 0) | ||||||
|  |             mode = a.getResourceId(R.styleable.Keyboard_Row_keyboardMode, 0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Class for describing the position and characteristics of a single key in the keyboard. | ||||||
|  |      * | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_keyWidth | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_keyHeight | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_horizontalGap | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_codes | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_keyIcon | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_keyLabel | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_iconPreview | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_isSticky | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_isRepeatable | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_isModifier | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_popupKeyboard | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_popupCharacters | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_keyOutputText | ||||||
|  |      * @attr ref android.R.styleable#Keyboard_Key_keyEdgeFlags | ||||||
|  |      */ | ||||||
|  |     class Key(parent: Row) { | ||||||
|  |         /** | ||||||
|  |          * All the key codes (unicode or custom code) that this key could generate, zero'th | ||||||
|  |          * being the most important. | ||||||
|  |          */ | ||||||
|  |         var codes = ArrayList<Int>() | ||||||
|  |  | ||||||
|  |         /** Label to display  */ | ||||||
|  |         var label: CharSequence? = null | ||||||
|  |  | ||||||
|  |         /** Icon to display instead of a label. Icon takes precedence over a label  */ | ||||||
|  |         var icon: Drawable? = null | ||||||
|  |  | ||||||
|  |         /** Preview version of the icon, for the preview popup  */ | ||||||
|  |         var iconPreview: Drawable? = null | ||||||
|  |  | ||||||
|  |         /** Width of the key, not including the gap  */ | ||||||
|  |         var width: Int | ||||||
|  |  | ||||||
|  |         /** Height of the key, not including the gap  */ | ||||||
|  |         var height: Int | ||||||
|  |  | ||||||
|  |         /** The horizontal gap before this key  */ | ||||||
|  |         var gap: Int | ||||||
|  |  | ||||||
|  |         /** Whether this key is sticky, i.e., a toggle key  */ | ||||||
|  |         var sticky = false | ||||||
|  |  | ||||||
|  |         /** X coordinate of the key in the keyboard layout  */ | ||||||
|  |         var x = 0 | ||||||
|  |  | ||||||
|  |         /** Y coordinate of the key in the keyboard layout  */ | ||||||
|  |         var y = 0 | ||||||
|  |  | ||||||
|  |         /** The current pressed state of this key  */ | ||||||
|  |         var pressed = false | ||||||
|  |  | ||||||
|  |         /** If this is a sticky key, is it on?  */ | ||||||
|  |         var on = false | ||||||
|  |  | ||||||
|  |         /** Text to output when pressed. This can be multiple characters, like ".com"  */ | ||||||
|  |         var text: CharSequence? = null | ||||||
|  |  | ||||||
|  |         /** Popup characters  */ | ||||||
|  |         var popupCharacters: CharSequence? = null | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Flags that specify the anchoring to edges of the keyboard for detecting touch events | ||||||
|  |          * that are just out of the boundary of the key. This is a bit mask of | ||||||
|  |          * [Keyboard.EDGE_LEFT], [Keyboard.EDGE_RIGHT], [Keyboard.EDGE_TOP] and | ||||||
|  |          * [Keyboard.EDGE_BOTTOM]. | ||||||
|  |          */ | ||||||
|  |         var edgeFlags: Int | ||||||
|  |  | ||||||
|  |         /** Whether this is a modifier key, such as Shift or Alt  */ | ||||||
|  |         var modifier = false | ||||||
|  |  | ||||||
|  |         /** The keyboard that this key belongs to  */ | ||||||
|  |         private val keyboard: MyKeyboard | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * If this key pops up a mini keyboard, this is the resource id for the XML layout for that | ||||||
|  |          * keyboard. | ||||||
|  |          */ | ||||||
|  |         var popupResId = 0 | ||||||
|  |  | ||||||
|  |         /** Whether this key repeats itself when held down  */ | ||||||
|  |         var repeatable = false | ||||||
|  |  | ||||||
|  |         /** Create a key with the given top-left coordinate and extract its attributes from | ||||||
|  |          * the XML parser. | ||||||
|  |          * @param res resources associated with the caller's context | ||||||
|  |          * @param parent the row that this key belongs to. The row must already be attached to | ||||||
|  |          * a [Keyboard]. | ||||||
|  |          * @param x the x coordinate of the top-left | ||||||
|  |          * @param y the y coordinate of the top-left | ||||||
|  |          * @param parser the XML parser containing the attributes for this key | ||||||
|  |          */ | ||||||
|  |         constructor(res: Resources, parent: Row, x: Int, y: Int, parser: XmlResourceParser?) : this(parent) { | ||||||
|  |             this.x = x | ||||||
|  |             this.y = y | ||||||
|  |             var a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard) | ||||||
|  |             width = getDimensionOrFraction(a, R.styleable.Keyboard_keyWidth, keyboard.mDisplayWidth, parent.defaultWidth) | ||||||
|  |             height = getDimensionOrFraction(a, R.styleable.Keyboard_keyHeight, keyboard.mDisplayHeight, parent.defaultHeight) | ||||||
|  |             gap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap, keyboard.mDisplayWidth, parent.defaultHorizontalGap) | ||||||
|  |             a.recycle() | ||||||
|  |             a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key) | ||||||
|  |             this.x += gap | ||||||
|  |             val codesValue = TypedValue() | ||||||
|  |  | ||||||
|  |             a.getValue(R.styleable.Keyboard_Key_codes, codesValue) | ||||||
|  |             if (codesValue.type == TypedValue.TYPE_INT_DEC || codesValue.type == TypedValue.TYPE_INT_HEX) { | ||||||
|  |                 codes = arrayListOf(codesValue.data) | ||||||
|  |             } else if (codesValue.type == TypedValue.TYPE_STRING) { | ||||||
|  |                 codes = parseCSV(codesValue.string.toString()) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             iconPreview = a.getDrawable(R.styleable.Keyboard_Key_iconPreview) | ||||||
|  |             if (iconPreview != null) { | ||||||
|  |                 iconPreview!!.setBounds( | ||||||
|  |                     0, 0, iconPreview!!.intrinsicWidth, | ||||||
|  |                     iconPreview!!.intrinsicHeight | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             popupCharacters = a.getText( | ||||||
|  |                 R.styleable.Keyboard_Key_popupCharacters | ||||||
|  |             ) | ||||||
|  |             popupResId = a.getResourceId( | ||||||
|  |                 R.styleable.Keyboard_Key_popupKeyboard, 0 | ||||||
|  |             ) | ||||||
|  |             repeatable = a.getBoolean( | ||||||
|  |                 R.styleable.Keyboard_Key_isRepeatable, false | ||||||
|  |             ) | ||||||
|  |             modifier = a.getBoolean( | ||||||
|  |                 R.styleable.Keyboard_Key_isModifier, false | ||||||
|  |             ) | ||||||
|  |             sticky = a.getBoolean( | ||||||
|  |                 R.styleable.Keyboard_Key_isSticky, false | ||||||
|  |             ) | ||||||
|  |             edgeFlags = a.getInt(R.styleable.Keyboard_Key_keyEdgeFlags, 0) | ||||||
|  |             edgeFlags = edgeFlags or parent.rowEdgeFlags | ||||||
|  |             icon = a.getDrawable( | ||||||
|  |                 R.styleable.Keyboard_Key_keyIcon | ||||||
|  |             ) | ||||||
|  |             if (icon != null) { | ||||||
|  |                 icon!!.setBounds(0, 0, icon!!.intrinsicWidth, icon!!.intrinsicHeight) | ||||||
|  |             } | ||||||
|  |             label = a.getText(R.styleable.Keyboard_Key_keyLabel) | ||||||
|  |             text = a.getText(R.styleable.Keyboard_Key_keyOutputText) | ||||||
|  |             if (!TextUtils.isEmpty(label)) { | ||||||
|  |                 codes = arrayListOf(label!![0].toInt()) | ||||||
|  |             } | ||||||
|  |             a.recycle() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** Create an empty key with no attributes.  */ | ||||||
|  |         init { | ||||||
|  |             keyboard = parent.parent | ||||||
|  |             height = parent.defaultHeight | ||||||
|  |             width = parent.defaultWidth | ||||||
|  |             gap = parent.defaultHorizontalGap | ||||||
|  |             edgeFlags = parent.rowEdgeFlags | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Informs the key that it has been pressed, in case it needs to change its appearance or | ||||||
|  |          * state. | ||||||
|  |          * @see .onReleased | ||||||
|  |          */ | ||||||
|  |         fun onPressed() { | ||||||
|  |             pressed = !pressed | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Changes the pressed state of the key. | ||||||
|  |          * | ||||||
|  |          * | ||||||
|  |          * Toggled state of the key will be flipped when all the following conditions are | ||||||
|  |          * fulfilled: | ||||||
|  |          * | ||||||
|  |          * | ||||||
|  |          *  * This is a sticky key, that is, [.sticky] is `true`. | ||||||
|  |          *  * The parameter `inside` is `true`. | ||||||
|  |          *  * [android.os.Build.VERSION.SDK_INT] is greater than | ||||||
|  |          * [android.os.Build.VERSION_CODES.LOLLIPOP_MR1]. | ||||||
|  |          * | ||||||
|  |          * | ||||||
|  |          * @param inside whether the finger was released inside the key. Works only on Android M and | ||||||
|  |          * later. See the method document for details. | ||||||
|  |          * @see .onPressed | ||||||
|  |          */ | ||||||
|  |         fun onReleased(inside: Boolean) { | ||||||
|  |             pressed = !pressed | ||||||
|  |             if (sticky && inside) { | ||||||
|  |                 on = !on | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun parseCSV(value: String): ArrayList<Int> { | ||||||
|  |             var count = 0 | ||||||
|  |             var lastIndex = 0 | ||||||
|  |             if (value.length > 0) { | ||||||
|  |                 count++ | ||||||
|  |                 while (value.indexOf(",", lastIndex + 1).also { lastIndex = it } > 0) { | ||||||
|  |                     count++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             val values = ArrayList<Int>(count) | ||||||
|  |             count = 0 | ||||||
|  |             val st = StringTokenizer(value, ",") | ||||||
|  |             while (st.hasMoreTokens()) { | ||||||
|  |                 try { | ||||||
|  |                     values[count++] = st.nextToken().toInt() | ||||||
|  |                 } catch (nfe: NumberFormatException) { | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return values | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Detects if a point falls inside this key. | ||||||
|  |          * @param x the x-coordinate of the point | ||||||
|  |          * @param y the y-coordinate of the point | ||||||
|  |          * @return whether or not the point falls inside the key. If the key is attached to an edge, | ||||||
|  |          * it will assume that all points between the key and the edge are considered to be inside | ||||||
|  |          * the key. | ||||||
|  |          */ | ||||||
|  |         fun isInside(x: Int, y: Int): Boolean { | ||||||
|  |             val leftEdge = edgeFlags and EDGE_LEFT > 0 | ||||||
|  |             val rightEdge = edgeFlags and EDGE_RIGHT > 0 | ||||||
|  |             val topEdge = edgeFlags and EDGE_TOP > 0 | ||||||
|  |             val bottomEdge = edgeFlags and EDGE_BOTTOM > 0 | ||||||
|  |             return ((x >= this.x || leftEdge && x <= this.x + width) | ||||||
|  |                 && (x < this.x + width || rightEdge && x >= this.x) | ||||||
|  |                 && (y >= this.y || topEdge && y <= this.y + height) | ||||||
|  |                 && (y < this.y + height || bottomEdge && y >= this.y)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Returns the square of the distance between the center of the key and the given point. | ||||||
|  |          * @param x the x-coordinate of the point | ||||||
|  |          * @param y the y-coordinate of the point | ||||||
|  |          * @return the square of the distance of the point from the center of the key | ||||||
|  |          */ | ||||||
|  |         fun squaredDistanceFrom(x: Int, y: Int): Int { | ||||||
|  |             val xDist = this.x + width / 2 - x | ||||||
|  |             val yDist = this.y + height / 2 - y | ||||||
|  |             return xDist * xDist + yDist * yDist | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Returns the drawable state for the key, based on the current state and type of the key. | ||||||
|  |          * @return the drawable state of the key. | ||||||
|  |          * @see android.graphics.drawable.StateListDrawable.setState | ||||||
|  |          */ | ||||||
|  |         val currentDrawableState: IntArray | ||||||
|  |             get() { | ||||||
|  |                 var states: IntArray = KEY_STATE_NORMAL | ||||||
|  |                 if (on) { | ||||||
|  |                     states = if (pressed) { | ||||||
|  |                         KEY_STATE_PRESSED_ON | ||||||
|  |                     } else { | ||||||
|  |                         KEY_STATE_NORMAL_ON | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (sticky) { | ||||||
|  |                         states = if (pressed) { | ||||||
|  |                             KEY_STATE_PRESSED_OFF | ||||||
|  |                         } else { | ||||||
|  |                             KEY_STATE_NORMAL_OFF | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         if (pressed) { | ||||||
|  |                             states = KEY_STATE_PRESSED | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return states | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         companion object { | ||||||
|  |             private val KEY_STATE_NORMAL_ON = intArrayOf( | ||||||
|  |                 android.R.attr.state_checkable, | ||||||
|  |                 android.R.attr.state_checked | ||||||
|  |             ) | ||||||
|  |             private val KEY_STATE_PRESSED_ON = intArrayOf( | ||||||
|  |                 android.R.attr.state_pressed, | ||||||
|  |                 android.R.attr.state_checkable, | ||||||
|  |                 android.R.attr.state_checked | ||||||
|  |             ) | ||||||
|  |             private val KEY_STATE_NORMAL_OFF = intArrayOf( | ||||||
|  |                 android.R.attr.state_checkable | ||||||
|  |             ) | ||||||
|  |             private val KEY_STATE_PRESSED_OFF = intArrayOf( | ||||||
|  |                 android.R.attr.state_pressed, | ||||||
|  |                 android.R.attr.state_checkable | ||||||
|  |             ) | ||||||
|  |             private val KEY_STATE_NORMAL = intArrayOf() | ||||||
|  |             private val KEY_STATE_PRESSED = intArrayOf( | ||||||
|  |                 android.R.attr.state_pressed | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a keyboard from the given xml key layout file. Weeds out rows | ||||||
|  |      * that have a keyboard mode defined but don't match the specified mode. | ||||||
|  |      * @param context the application or service context | ||||||
|  |      * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. | ||||||
|  |      * @param modeId keyboard mode identifier | ||||||
|  |      * @param width sets width of keyboard | ||||||
|  |      * @param height sets height of keyboard | ||||||
|  |      */ | ||||||
|  |     constructor( | ||||||
|  |         context: Context, @XmlRes xmlLayoutResId: Int, modeId: Int, width: Int, | ||||||
|  |         height: Int | ||||||
|  |     ) { | ||||||
|  |         mDisplayWidth = width | ||||||
|  |         mDisplayHeight = height | ||||||
|  |         mDefaultHorizontalGap = 0 | ||||||
|  |         mDefaultWidth = mDisplayWidth / 10 | ||||||
|  |         mDefaultVerticalGap = 0 | ||||||
|  |         mDefaultHeight = mDefaultWidth | ||||||
|  |         mKeys = ArrayList() | ||||||
|  |         mModifierKeys = ArrayList() | ||||||
|  |         mKeyboardMode = modeId | ||||||
|  |         loadKeyboard(context, context.resources.getXml(xmlLayoutResId)) | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * Creates a keyboard from the given xml key layout file. Weeds out rows | ||||||
|  |      * that have a keyboard mode defined but don't match the specified mode. | ||||||
|  |      * @param context the application or service context | ||||||
|  |      * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. | ||||||
|  |      * @param modeId keyboard mode identifier | ||||||
|  |      */ | ||||||
|  |     /** | ||||||
|  |      * Creates a keyboard from the given xml key layout file. | ||||||
|  |      * @param context the application or service context | ||||||
|  |      * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. | ||||||
|  |      */ | ||||||
|  |     @JvmOverloads | ||||||
|  |     constructor(context: Context, @XmlRes xmlLayoutResId: Int, modeId: Int = 0) { | ||||||
|  |         val dm = context.resources.displayMetrics | ||||||
|  |         mDisplayWidth = dm.widthPixels | ||||||
|  |         mDisplayHeight = dm.heightPixels | ||||||
|  |         //Log.v(TAG, "keyboard's display metrics:" + dm); | ||||||
|  |         mDefaultHorizontalGap = 0 | ||||||
|  |         mDefaultWidth = mDisplayWidth / 10 | ||||||
|  |         mDefaultVerticalGap = 0 | ||||||
|  |         mDefaultHeight = mDefaultWidth | ||||||
|  |         mKeys = ArrayList() | ||||||
|  |         mModifierKeys = ArrayList() | ||||||
|  |         mKeyboardMode = modeId | ||||||
|  |         loadKeyboard(context, context.resources.getXml(xmlLayoutResId)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * | ||||||
|  |      * Creates a blank keyboard from the given resource file and populates it with the specified | ||||||
|  |      * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. | ||||||
|  |      * | ||||||
|  |      * | ||||||
|  |      * If the specified number of columns is -1, then the keyboard will fit as many keys as | ||||||
|  |      * possible in each row. | ||||||
|  |      * @param context the application or service context | ||||||
|  |      * @param layoutTemplateResId the layout template file, containing no keys. | ||||||
|  |      * @param characters the list of characters to display on the keyboard. One key will be created | ||||||
|  |      * for each character. | ||||||
|  |      * @param columns the number of columns of keys to display. If this number is greater than the | ||||||
|  |      * number of keys that can fit in a row, it will be ignored. If this number is -1, the | ||||||
|  |      * keyboard will fit as many keys as possible in each row. | ||||||
|  |      */ | ||||||
|  |     constructor( | ||||||
|  |         context: Context, layoutTemplateResId: Int, | ||||||
|  |         characters: CharSequence, columns: Int, horizontalPadding: Int | ||||||
|  |     ) : this(context, layoutTemplateResId) { | ||||||
|  |         var x = 0 | ||||||
|  |         var y = 0 | ||||||
|  |         var column = 0 | ||||||
|  |         minWidth = 0 | ||||||
|  |         val row = Row(this) | ||||||
|  |         row.defaultHeight = mDefaultHeight | ||||||
|  |         row.defaultWidth = mDefaultWidth | ||||||
|  |         row.defaultHorizontalGap = mDefaultHorizontalGap | ||||||
|  |         row.verticalGap = mDefaultVerticalGap | ||||||
|  |         row.rowEdgeFlags = EDGE_TOP or EDGE_BOTTOM | ||||||
|  |         val maxColumns = if (columns == -1) Int.MAX_VALUE else columns | ||||||
|  |         for (i in 0 until characters.length) { | ||||||
|  |             val c = characters[i] | ||||||
|  |             if (column >= maxColumns | ||||||
|  |                 || x + mDefaultWidth + horizontalPadding > mDisplayWidth | ||||||
|  |             ) { | ||||||
|  |                 x = 0 | ||||||
|  |                 y += mDefaultVerticalGap + mDefaultHeight | ||||||
|  |                 column = 0 | ||||||
|  |             } | ||||||
|  |             val key = Key(row) | ||||||
|  |             key.x = x | ||||||
|  |             key.y = y | ||||||
|  |             key.label = c.toString() | ||||||
|  |             key.codes = arrayListOf(c.toInt()) | ||||||
|  |             column++ | ||||||
|  |             x += key.width + key.gap | ||||||
|  |             mKeys!!.add(key) | ||||||
|  |             row.mKeys.add(key) | ||||||
|  |             if (x > minWidth) { | ||||||
|  |                 minWidth = x | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         height = y + mDefaultHeight | ||||||
|  |         rows.add(row) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun resize(newWidth: Int, newHeight: Int) { | ||||||
|  |         val numRows = rows.size | ||||||
|  |         for (rowIndex in 0 until numRows) { | ||||||
|  |             val row = rows[rowIndex] ?: continue | ||||||
|  |             val numKeys: Int = row.mKeys.size | ||||||
|  |             var totalGap = 0 | ||||||
|  |             var totalWidth = 0 | ||||||
|  |             for (keyIndex in 0 until numKeys) { | ||||||
|  |                 val key: Key = row.mKeys.get(keyIndex) | ||||||
|  |                 if (keyIndex > 0) { | ||||||
|  |                     totalGap += key.gap | ||||||
|  |                 } | ||||||
|  |                 totalWidth += key.width | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (totalGap + totalWidth > newWidth) { | ||||||
|  |                 var x = 0 | ||||||
|  |                 val scaleFactor = (newWidth - totalGap).toFloat() / totalWidth | ||||||
|  |                 for (keyIndex in 0 until numKeys) { | ||||||
|  |                     val key = row.mKeys[keyIndex] | ||||||
|  |                     key.width *= scaleFactor.toInt() | ||||||
|  |                     key.x = x | ||||||
|  |                     x += key.width + key.gap | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         minWidth = newWidth | ||||||
|  |         // TODO: This does not adjust the vertical placement according to the new size. | ||||||
|  |         // The main problem in the previous code was horizontal placement/size, but we should | ||||||
|  |         // also recalculate the vertical sizes/positions when we get this resize call. | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val keys: List<Key?>? | ||||||
|  |         get() = mKeys | ||||||
|  |     val modifierKeys: List<Key?>? | ||||||
|  |         get() = mModifierKeys | ||||||
|  |  | ||||||
|  |     fun setShifted(shiftState: Boolean): Boolean { | ||||||
|  |         for (shiftKey in mShiftKeys) { | ||||||
|  |             if (shiftKey != null) { | ||||||
|  |                 shiftKey.on = shiftState | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (isShifted != shiftState) { | ||||||
|  |             isShifted = shiftState | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val shiftKeyIndex: Int | ||||||
|  |         get() = shiftKeyIndices[0] | ||||||
|  |  | ||||||
|  |     private fun computeNearestNeighbors() { | ||||||
|  |         // Round-up so we don't have any pixels outside the grid | ||||||
|  |         mCellWidth = (minWidth + GRID_WIDTH - 1) / GRID_WIDTH | ||||||
|  |         mCellHeight = (height + GRID_HEIGHT - 1) / GRID_HEIGHT | ||||||
|  |         mGridNeighbors = ArrayList<IntArray?>() | ||||||
|  |         val indices = IntArray(mKeys!!.size) | ||||||
|  |         val gridWidth: Int = GRID_WIDTH * mCellWidth | ||||||
|  |         val gridHeight: Int = GRID_HEIGHT * mCellHeight | ||||||
|  |         var x = 0 | ||||||
|  |         while (x < gridWidth) { | ||||||
|  |             var y = 0 | ||||||
|  |             while (y < gridHeight) { | ||||||
|  |                 var count = 0 | ||||||
|  |                 for (i in mKeys!!.indices) { | ||||||
|  |                     val key = mKeys!![i] | ||||||
|  |                     if (key!!.squaredDistanceFrom(x, y) < mProximityThreshold || key.squaredDistanceFrom( | ||||||
|  |                             x + mCellWidth - 1, y | ||||||
|  |                         ) < mProximityThreshold || (key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) | ||||||
|  |                             < mProximityThreshold) || key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold | ||||||
|  |                     ) { | ||||||
|  |                         indices[count++] = i | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 val cell = IntArray(count) | ||||||
|  |                 System.arraycopy(indices, 0, cell, 0, count) | ||||||
|  |                 mGridNeighbors[y / mCellHeight * GRID_WIDTH + x / mCellWidth] = cell | ||||||
|  |                 y += mCellHeight | ||||||
|  |             } | ||||||
|  |             x += mCellWidth | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the indices of the keys that are closest to the given point. | ||||||
|  |      * @param x the x-coordinate of the point | ||||||
|  |      * @param y the y-coordinate of the point | ||||||
|  |      * @return the array of integer indices for the nearest keys to the given point. If the given | ||||||
|  |      * point is out of range, then an array of size zero is returned. | ||||||
|  |      */ | ||||||
|  |     fun getNearestKeys(x: Int, y: Int): IntArray? { | ||||||
|  |         if (x >= 0 && x < minWidth && y >= 0 && y < height) { | ||||||
|  |             val index: Int = y / mCellHeight * GRID_WIDTH + x / mCellWidth | ||||||
|  |             if (index < GRID_SIZE) { | ||||||
|  |                 return mGridNeighbors[index] | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return IntArray(0) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected fun createRowFromXml(res: Resources, parser: XmlResourceParser?): Row { | ||||||
|  |         return Row(res, this, parser) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected fun createKeyFromXml(res: Resources, parent: Row, x: Int, y: Int, parser: XmlResourceParser?): Key { | ||||||
|  |         return Key(res, parent, x, y, parser) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun loadKeyboard(context: Context, parser: XmlResourceParser) { | ||||||
|  |         var inKey = false | ||||||
|  |         var inRow = false | ||||||
|  |         val leftMostKey = false | ||||||
|  |         var row = 0 | ||||||
|  |         var x = 0 | ||||||
|  |         var y = 0 | ||||||
|  |         var key: Key? = null | ||||||
|  |         var currentRow: Row? = null | ||||||
|  |         val res = context.resources | ||||||
|  |         var skipRow = false | ||||||
|  |         try { | ||||||
|  |             var event: Int | ||||||
|  |             while (parser.next().also { event = it } != XmlResourceParser.END_DOCUMENT) { | ||||||
|  |                 if (event == XmlResourceParser.START_TAG) { | ||||||
|  |                     val tag = parser.name | ||||||
|  |                     if (TAG_ROW == tag) { | ||||||
|  |                         inRow = true | ||||||
|  |                         x = 0 | ||||||
|  |                         currentRow = createRowFromXml(res, parser) | ||||||
|  |                         rows.add(currentRow) | ||||||
|  |                         skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode | ||||||
|  |                         if (skipRow) { | ||||||
|  |                             skipToEndOfRow(parser) | ||||||
|  |                             inRow = false | ||||||
|  |                         } | ||||||
|  |                     } else if (TAG_KEY == tag) { | ||||||
|  |                         inKey = true | ||||||
|  |                         key = createKeyFromXml(res, currentRow!!, x, y, parser) | ||||||
|  |                         mKeys!!.add(key) | ||||||
|  |                         if (key.codes[0] == KEYCODE_SHIFT) { | ||||||
|  |                             // Find available shift key slot and put this shift key in it | ||||||
|  |                             for (i in mShiftKeys.indices) { | ||||||
|  |                                 if (mShiftKeys[i] == null) { | ||||||
|  |                                     mShiftKeys[i] = key | ||||||
|  |                                     shiftKeyIndices[i] = mKeys!!.size - 1 | ||||||
|  |                                     break | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                             mModifierKeys!!.add(key) | ||||||
|  |                         } else if (key.codes[0] == KEYCODE_ALT) { | ||||||
|  |                             mModifierKeys!!.add(key) | ||||||
|  |                         } | ||||||
|  |                         currentRow.mKeys.add(key) | ||||||
|  |                     } else if (TAG_KEYBOARD == tag) { | ||||||
|  |                         parseKeyboardAttributes(res, parser) | ||||||
|  |                     } | ||||||
|  |                 } else if (event == XmlResourceParser.END_TAG) { | ||||||
|  |                     if (inKey) { | ||||||
|  |                         inKey = false | ||||||
|  |                         x += key!!.gap + key.width | ||||||
|  |                         if (x > minWidth) { | ||||||
|  |                             minWidth = x | ||||||
|  |                         } | ||||||
|  |                     } else if (inRow) { | ||||||
|  |                         inRow = false | ||||||
|  |                         y += currentRow!!.verticalGap | ||||||
|  |                         y += currentRow.defaultHeight | ||||||
|  |                         row++ | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |         } | ||||||
|  |         height = y - mDefaultVerticalGap | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(XmlPullParserException::class, IOException::class) | ||||||
|  |     private fun skipToEndOfRow(parser: XmlResourceParser) { | ||||||
|  |         var event: Int | ||||||
|  |         while (parser.next().also { event = it } != XmlResourceParser.END_DOCUMENT) { | ||||||
|  |             if (event == XmlResourceParser.END_TAG | ||||||
|  |                 && parser.name == TAG_ROW | ||||||
|  |             ) { | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun parseKeyboardAttributes(res: Resources, parser: XmlResourceParser) { | ||||||
|  |         val a = res.obtainAttributes( | ||||||
|  |             Xml.asAttributeSet(parser), | ||||||
|  |             R.styleable.Keyboard | ||||||
|  |         ) | ||||||
|  |         mDefaultWidth = getDimensionOrFraction( | ||||||
|  |             a, | ||||||
|  |             R.styleable.Keyboard_keyWidth, | ||||||
|  |             mDisplayWidth, mDisplayWidth / 10 | ||||||
|  |         ) | ||||||
|  |         mDefaultHeight = getDimensionOrFraction( | ||||||
|  |             a, | ||||||
|  |             R.styleable.Keyboard_keyHeight, | ||||||
|  |             mDisplayHeight, 50 | ||||||
|  |         ) | ||||||
|  |         mDefaultHorizontalGap = getDimensionOrFraction( | ||||||
|  |             a, | ||||||
|  |             R.styleable.Keyboard_horizontalGap, | ||||||
|  |             mDisplayWidth, 0 | ||||||
|  |         ) | ||||||
|  |         mDefaultVerticalGap = getDimensionOrFraction( | ||||||
|  |             a, | ||||||
|  |             R.styleable.Keyboard_verticalGap, | ||||||
|  |             mDisplayHeight, 0 | ||||||
|  |         ) | ||||||
|  |         mProximityThreshold = (mDefaultWidth * SEARCH_DISTANCE) as Int | ||||||
|  |         mProximityThreshold = mProximityThreshold * mProximityThreshold // Square it for comparison | ||||||
|  |         a.recycle() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -763,6 +763,7 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut | |||||||
|                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode |                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (mCurrentKeyIndex != NOT_A_KEY && keys.size > mCurrentKeyIndex) { |             if (mCurrentKeyIndex != NOT_A_KEY && keys.size > mCurrentKeyIndex) { | ||||||
|                 val newKey = keys[mCurrentKeyIndex] |                 val newKey = keys[mCurrentKeyIndex] | ||||||
|                 newKey.onPressed() |                 newKey.onPressed() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user