/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.android.inputmethod.keyboard;
import android.util.SparseArray;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.common.CoordinateUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* 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:
*
* <Keyboard
* latin:keyWidth="10%p"
* latin:rowHeight="50px"
* latin:horizontalGap="2%p"
* latin:verticalGap="2%p" >
* <Row latin:keyWidth="10%p" >
* <Key latin:keyLabel="A" />
* ...
* </Row>
* ...
* </Keyboard>
*
*/
public class Keyboard {
@Nonnull
public final KeyboardId mId;
public final int mThemeId;
/** Total height of the keyboard, including the padding and keys */
public final int mOccupiedHeight;
/** Total width of the keyboard, including the padding and keys */
public final int mOccupiedWidth;
/** Base height of the keyboard, used to calculate rows' height */
public final int mBaseHeight;
/** Base width of the keyboard, used to calculate keys' width */
public final int mBaseWidth;
/** The padding above the keyboard */
public final int mTopPadding;
/** Default gap between rows */
public final int mVerticalGap;
/** Per keyboard key visual parameters */
public final KeyVisualAttributes mKeyVisualAttributes;
public final int mMostCommonKeyHeight;
public final int mMostCommonKeyWidth;
/** More keys keyboard template */
public final int mMoreKeysTemplate;
/** Maximum column for more keys keyboard */
public final int mMaxMoreKeysKeyboardColumn;
/** List of keys in this keyboard */
@Nonnull
private final List mSortedKeys;
@Nonnull
public final List mShiftKeys;
@Nonnull
public final List mAltCodeKeysWhileTyping;
@Nonnull
public final KeyboardIconsSet mIconsSet;
private final SparseArray mKeyCache = new SparseArray<>();
@Nonnull
private final ProximityInfo mProximityInfo;
@Nonnull
private final KeyboardLayout mKeyboardLayout;
private final boolean mProximityCharsCorrectionEnabled;
public Keyboard(@Nonnull final KeyboardParams params) {
mId = params.mId;
mThemeId = params.mThemeId;
mOccupiedHeight = params.mOccupiedHeight;
mOccupiedWidth = params.mOccupiedWidth;
mBaseHeight = params.mBaseHeight;
mBaseWidth = params.mBaseWidth;
mMostCommonKeyHeight = params.mMostCommonKeyHeight;
mMostCommonKeyWidth = params.mMostCommonKeyWidth;
mMoreKeysTemplate = params.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
mKeyVisualAttributes = params.mKeyVisualAttributes;
mTopPadding = params.mTopPadding;
mVerticalGap = params.mVerticalGap;
mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys));
mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping);
mIconsSet = params.mIconsSet;
mProximityInfo = new ProximityInfo(params.GRID_WIDTH, params.GRID_HEIGHT,
mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
mSortedKeys, params.mTouchPositionCorrection);
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
mKeyboardLayout = KeyboardLayout.newKeyboardLayout(mSortedKeys, mMostCommonKeyWidth,
mMostCommonKeyHeight, mOccupiedWidth, mOccupiedHeight);
}
protected Keyboard(@Nonnull final Keyboard keyboard) {
mId = keyboard.mId;
mThemeId = keyboard.mThemeId;
mOccupiedHeight = keyboard.mOccupiedHeight;
mOccupiedWidth = keyboard.mOccupiedWidth;
mBaseHeight = keyboard.mBaseHeight;
mBaseWidth = keyboard.mBaseWidth;
mMostCommonKeyHeight = keyboard.mMostCommonKeyHeight;
mMostCommonKeyWidth = keyboard.mMostCommonKeyWidth;
mMoreKeysTemplate = keyboard.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = keyboard.mMaxMoreKeysKeyboardColumn;
mKeyVisualAttributes = keyboard.mKeyVisualAttributes;
mTopPadding = keyboard.mTopPadding;
mVerticalGap = keyboard.mVerticalGap;
mSortedKeys = keyboard.mSortedKeys;
mShiftKeys = keyboard.mShiftKeys;
mAltCodeKeysWhileTyping = keyboard.mAltCodeKeysWhileTyping;
mIconsSet = keyboard.mIconsSet;
mProximityInfo = keyboard.mProximityInfo;
mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled;
mKeyboardLayout = keyboard.mKeyboardLayout;
}
public boolean hasProximityCharsCorrection(final int code) {
if (!mProximityCharsCorrectionEnabled) {
return false;
}
// Note: The native code has the main keyboard layout only at this moment.
// TODO: Figure out how to handle proximity characters information of all layouts.
final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = (
mId.mElementId == KeyboardId.ELEMENT_ALPHABET
|| mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);
return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);
}
@Nonnull
public ProximityInfo getProximityInfo() {
return mProximityInfo;
}
@Nonnull
public KeyboardLayout getKeyboardLayout() {
return mKeyboardLayout;
}
/**
* Return the sorted list of keys of this keyboard.
* The keys are sorted from top-left to bottom-right order.
* The list may contain {@link Key.Spacer} object as well.
* @return the sorted unmodifiable list of {@link Key}s of this keyboard.
*/
@Nonnull
public List getSortedKeys() {
return mSortedKeys;
}
@Nullable
public Key getKey(final int code) {
if (code == Constants.CODE_UNSPECIFIED) {
return null;
}
synchronized (mKeyCache) {
final int index = mKeyCache.indexOfKey(code);
if (index >= 0) {
return mKeyCache.valueAt(index);
}
for (final Key key : getSortedKeys()) {
if (key.getCode() == code) {
mKeyCache.put(code, key);
return key;
}
}
mKeyCache.put(code, null);
return null;
}
}
public boolean hasKey(@Nonnull final Key aKey) {
if (mKeyCache.indexOfValue(aKey) >= 0) {
return true;
}
for (final Key key : getSortedKeys()) {
if (key == aKey) {
mKeyCache.put(key.getCode(), key);
return true;
}
}
return false;
}
@Override
public String toString() {
return mId.toString();
}
/**
* Returns the array 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 list of the nearest keys to the given point. If the given
* point is out of range, then an array of size zero is returned.
*/
@Nonnull
public List getNearestKeys(final int x, final int y) {
// Avoid dead pixels at edges of the keyboard
final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
@Nonnull
public int[] getCoordinates(@Nonnull final int[] codePoints) {
final int length = codePoints.length;
final int[] coordinates = CoordinateUtils.newCoordinateArray(length);
for (int i = 0; i < length; ++i) {
final Key key = getKey(codePoints[i]);
if (null != key) {
CoordinateUtils.setXYInArray(coordinates, i,
key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2);
} else {
CoordinateUtils.setXYInArray(coordinates, i,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
}
return coordinates;
}
}