Separate inner classes of keyboard package out under internal package

Change-Id: Ia3969bd5ddec5aa5d81d05ad4cf676d818587922
This commit is contained in:
Tadashi G. Takaoka 2012-08-30 14:22:40 +09:00
parent ded498d4a4
commit 35ff94547c
18 changed files with 1875 additions and 1678 deletions

View file

@ -32,9 +32,11 @@ import android.util.Log;
import android.util.Xml; import android.util.Xml;
import com.android.inputmethod.keyboard.internal.KeySpecParser; import com.android.inputmethod.keyboard.internal.KeySpecParser;
import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec; import com.android.inputmethod.keyboard.internal.KeyStyle;
import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeyboardRow;
import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.StringUtils;
@ -166,8 +168,8 @@ public class Key {
/** /**
* This constructor is being used only for keys in more keys keyboard. * This constructor is being used only for keys in more keys keyboard.
*/ */
public Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height, public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
int labelFlags) { final int width, final int height, final int labelFlags) {
this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode, this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
moreKeySpec.mOutputText, x, y, width, height, labelFlags); moreKeySpec.mOutputText, x, y, width, height, labelFlags);
} }
@ -175,8 +177,9 @@ public class Key {
/** /**
* This constructor is being used only for key in popup suggestions pane. * This constructor is being used only for key in popup suggestions pane.
*/ */
public Key(Keyboard.Params params, String label, String hintLabel, int iconId, public Key(final KeyboardParams params, final String label, final String hintLabel,
int code, String outputText, int x, int y, int width, int height, int labelFlags) { final int iconId, final int code, final String outputText, final int x, final int y,
final int width, final int height, final int labelFlags) {
mHeight = height - params.mVerticalGap; mHeight = height - params.mVerticalGap;
mWidth = width - params.mHorizontalGap; mWidth = width - params.mHorizontalGap;
mHintLabel = hintLabel; mHintLabel = hintLabel;
@ -213,8 +216,8 @@ public class Key {
* @param parser the XML parser containing the attributes for this key * @param parser the XML parser containing the attributes for this key
* @throws XmlPullParserException * @throws XmlPullParserException
*/ */
public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row, public Key(final Resources res, final KeyboardParams params, final KeyboardRow row,
XmlPullParser parser) throws XmlPullParserException { final XmlPullParser parser) throws XmlPullParserException {
final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
final int keyHeight = row.mRowHeight; final int keyHeight = row.mRowHeight;
mHeight = keyHeight - params.mVerticalGap; mHeight = keyHeight - params.mVerticalGap;
@ -364,7 +367,7 @@ public class Key {
} }
} }
private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) { private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
switch (keyboardElementId) { switch (keyboardElementId) {
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
@ -377,7 +380,7 @@ public class Key {
} }
} }
private static int computeHashCode(Key key) { private static int computeHashCode(final Key key) {
return Arrays.hashCode(new Object[] { return Arrays.hashCode(new Object[] {
key.mX, key.mX,
key.mY, key.mY,
@ -404,7 +407,7 @@ public class Key {
}); });
} }
private boolean equals(Key o) { private boolean equals(final Key o) {
if (this == o) return true; if (this == o) return true;
return o.mX == mX return o.mX == mX
&& o.mY == mY && o.mY == mY
@ -427,7 +430,7 @@ public class Key {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(final Object o) {
return o instanceof Key && equals((Key)o); return o instanceof Key && equals((Key)o);
} }
@ -444,7 +447,7 @@ public class Key {
KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
} }
private static String backgroundName(int backgroundType) { private static String backgroundName(final int backgroundType) {
switch (backgroundType) { switch (backgroundType) {
case BACKGROUND_TYPE_NORMAL: return "normal"; case BACKGROUND_TYPE_NORMAL: return "normal";
case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
@ -455,19 +458,19 @@ public class Key {
} }
} }
public void markAsLeftEdge(Keyboard.Params params) { public void markAsLeftEdge(final KeyboardParams params) {
mHitBox.left = params.mHorizontalEdgesPadding; mHitBox.left = params.mHorizontalEdgesPadding;
} }
public void markAsRightEdge(Keyboard.Params params) { public void markAsRightEdge(final KeyboardParams params) {
mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding; mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
} }
public void markAsTopEdge(Keyboard.Params params) { public void markAsTopEdge(final KeyboardParams params) {
mHitBox.top = params.mTopPadding; mHitBox.top = params.mTopPadding;
} }
public void markAsBottomEdge(Keyboard.Params params) { public void markAsBottomEdge(final KeyboardParams params) {
mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
} }
@ -501,7 +504,7 @@ public class Key {
&& (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
} }
public Typeface selectTypeface(Typeface defaultTypeface) { public Typeface selectTypeface(final Typeface defaultTypeface) {
// TODO: Handle "bold" here too? // TODO: Handle "bold" here too?
if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) { if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
return Typeface.DEFAULT; return Typeface.DEFAULT;
@ -512,8 +515,8 @@ public class Key {
} }
} }
public int selectTextSize(int letterSize, int largeLetterSize, int labelSize, public int selectTextSize(final int letterSize, final int largeLetterSize, final int labelSize,
int largeLabelSize, int hintLabelSize) { final int largeLabelSize, final int hintLabelSize) {
switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
return letterSize; return letterSize;
@ -606,7 +609,7 @@ public class Key {
return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
} }
public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) { public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
final OptionalAttributes attrs = mOptionalAttributes; final OptionalAttributes attrs = mOptionalAttributes;
final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
final int iconId = mEnabled ? mIconId : disabledIconId; final int iconId = mEnabled ? mIconId : disabledIconId;
@ -617,7 +620,7 @@ public class Key {
return icon; return icon;
} }
public Drawable getPreviewIcon(KeyboardIconsSet iconSet) { public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
final OptionalAttributes attrs = mOptionalAttributes; final OptionalAttributes attrs = mOptionalAttributes;
final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
return previewIconId != ICON_UNDEFINED return previewIconId != ICON_UNDEFINED
@ -657,7 +660,7 @@ public class Key {
return mEnabled; return mEnabled;
} }
public void setEnabled(boolean enabled) { public void setEnabled(final boolean enabled) {
mEnabled = enabled; mEnabled = enabled;
} }
@ -667,9 +670,9 @@ public class Key {
* @param y the y-coordinate of the point * @param y the y-coordinate of the point
* @return whether or not the point falls on the key. If the key is attached to an edge, it * @return whether or not the point falls on 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 on the key. * will assume that all points between the key and the edge are considered to be on the key.
* @see #markAsLeftEdge(Keyboard.Params) etc. * @see #markAsLeftEdge(KeyboardParams) etc.
*/ */
public boolean isOnKey(int x, int y) { public boolean isOnKey(final int x, final int y) {
return mHitBox.contains(x, y); return mHitBox.contains(x, y);
} }
@ -679,7 +682,7 @@ public class Key {
* @param y the y-coordinate of the point * @param y the y-coordinate of the point
* @return the square of the distance of the point from the nearest edge of the key * @return the square of the distance of the point from the nearest edge of the key
*/ */
public int squaredDistanceToEdge(int x, int y) { public int squaredDistanceToEdge(final int x, final int y) {
final int left = mX; final int left = mX;
final int right = left + mWidth; final int right = left + mWidth;
final int top = mY; final int top = mY;
@ -761,15 +764,16 @@ public class Key {
} }
public static class Spacer extends Key { public static class Spacer extends Key {
public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row, public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row,
XmlPullParser parser) throws XmlPullParserException { final XmlPullParser parser) throws XmlPullParserException {
super(res, params, row, parser); super(res, params, row, parser);
} }
/** /**
* This constructor is being used only for divider in more keys keyboard. * This constructor is being used only for divider in more keys keyboard.
*/ */
protected Spacer(Keyboard.Params params, int x, int y, int width, int height) { protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
final int height) {
super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED, super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
null, x, y, width, height, 0); null, x, y, width, height, 0);
} }

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,9 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams; import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeysCache;
import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.InputTypeUtils;
@ -78,31 +80,19 @@ public class KeyboardLayoutSet {
public static class KeyboardLayoutSetException extends RuntimeException { public static class KeyboardLayoutSetException extends RuntimeException {
public final KeyboardId mKeyboardId; public final KeyboardId mKeyboardId;
public KeyboardLayoutSetException(Throwable cause, KeyboardId keyboardId) { public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
super(cause); super(cause);
mKeyboardId = keyboardId; mKeyboardId = keyboardId;
} }
} }
public static class KeysCache { private static class ElementParams {
private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap(); int mKeyboardXmlId;
boolean mProximityCharsCorrectionEnabled;
public void clear() { public ElementParams() {}
mMap.clear();
}
public Key get(Key key) {
final Key existingKey = mMap.get(key);
if (existingKey != null) {
// Reuse the existing element that equals to "key" without adding "key" to the map.
return existingKey;
}
mMap.put(key, key);
return key;
}
} }
static class Params { private static class Params {
String mKeyboardLayoutSetName; String mKeyboardLayoutSetName;
int mMode; int mMode;
EditorInfo mEditorInfo; EditorInfo mEditorInfo;
@ -118,11 +108,7 @@ public class KeyboardLayoutSet {
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id. // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap = final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
CollectionUtils.newSparseArray(); CollectionUtils.newSparseArray();
public Params() {}
static class ElementParams {
int mKeyboardXmlId;
boolean mProximityCharsCorrectionEnabled;
}
} }
public static void clearKeyboardCache() { public static void clearKeyboardCache() {
@ -130,12 +116,12 @@ public class KeyboardLayoutSet {
sKeysCache.clear(); sKeysCache.clear();
} }
private KeyboardLayoutSet(Context context, Params params) { KeyboardLayoutSet(final Context context, final Params params) {
mContext = context; mContext = context;
mParams = params; mParams = params;
} }
public Keyboard getKeyboard(int baseKeyboardLayoutSetElementId) { public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
final int keyboardLayoutSetElementId; final int keyboardLayoutSetElementId;
switch (mParams.mMode) { switch (mParams.mMode) {
case KeyboardId.MODE_PHONE: case KeyboardId.MODE_PHONE:
@ -170,12 +156,12 @@ public class KeyboardLayoutSet {
} }
} }
private Keyboard getKeyboard(ElementParams elementParams, final KeyboardId id) { private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
final SoftReference<Keyboard> ref = sKeyboardCache.get(id); final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
Keyboard keyboard = (ref == null) ? null : ref.get(); Keyboard keyboard = (ref == null) ? null : ref.get();
if (keyboard == null) { if (keyboard == null) {
final Keyboard.Builder<Keyboard.Params> builder = final KeyboardBuilder<KeyboardParams> builder =
new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params()); new KeyboardBuilder<KeyboardParams>(mContext, new KeyboardParams());
if (id.isAlphabetKeyboard()) { if (id.isAlphabetKeyboard()) {
builder.setAutoGenerate(sKeysCache); builder.setAutoGenerate(sKeysCache);
} }
@ -202,7 +188,7 @@ public class KeyboardLayoutSet {
// KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies // KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies
// which XML layout should be used for each keyboard. The KeyboardId is an internal key for // which XML layout should be used for each keyboard. The KeyboardId is an internal key for
// Keyboard object. // Keyboard object.
private KeyboardId getKeyboardId(int keyboardLayoutSetElementId) { private KeyboardId getKeyboardId(final int keyboardLayoutSetElementId) {
final Params params = mParams; final Params params = mParams;
final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS
|| keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED); || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
@ -225,7 +211,7 @@ public class KeyboardLayoutSet {
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo(); private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
public Builder(Context context, EditorInfo editorInfo) { public Builder(final Context context, final EditorInfo editorInfo) {
mContext = context; mContext = context;
mPackageName = context.getPackageName(); mPackageName = context.getPackageName();
mResources = context.getResources(); mResources = context.getResources();
@ -238,7 +224,8 @@ public class KeyboardLayoutSet {
mPackageName, NO_SETTINGS_KEY, mEditorInfo); mPackageName, NO_SETTINGS_KEY, mEditorInfo);
} }
public Builder setScreenGeometry(int deviceFormFactor, int orientation, int widthPixels) { public Builder setScreenGeometry(final int deviceFormFactor, final int orientation,
final int widthPixels) {
final Params params = mParams; final Params params = mParams;
params.mDeviceFormFactor = deviceFormFactor; params.mDeviceFormFactor = deviceFormFactor;
params.mOrientation = orientation; params.mOrientation = orientation;
@ -246,7 +233,7 @@ public class KeyboardLayoutSet {
return this; return this;
} }
public Builder setSubtype(InputMethodSubtype subtype) { public Builder setSubtype(final InputMethodSubtype subtype) {
final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE); final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE);
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions( final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
@ -263,8 +250,8 @@ public class KeyboardLayoutSet {
return this; return this;
} }
public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain, public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
boolean languageSwitchKeyEnabled) { final boolean languageSwitchKeyEnabled) {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions( final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
null, NO_MICROPHONE_COMPAT, mEditorInfo); null, NO_MICROPHONE_COMPAT, mEditorInfo);
@ -277,7 +264,7 @@ public class KeyboardLayoutSet {
return this; return this;
} }
public void setTouchPositionCorrectionEnabled(boolean enabled) { public void setTouchPositionCorrectionEnabled(final boolean enabled) {
mParams.mTouchPositionCorrectionEnabled = enabled; mParams.mTouchPositionCorrectionEnabled = enabled;
} }
@ -298,7 +285,7 @@ public class KeyboardLayoutSet {
return new KeyboardLayoutSet(mContext, mParams); return new KeyboardLayoutSet(mContext, mParams);
} }
private void parseKeyboardLayoutSet(Resources res, int resId) private void parseKeyboardLayoutSet(final Resources res, final int resId)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
final XmlResourceParser parser = res.getXml(resId); final XmlResourceParser parser = res.getXml(resId);
try { try {
@ -318,7 +305,7 @@ public class KeyboardLayoutSet {
} }
} }
private void parseKeyboardLayoutSetContent(XmlPullParser parser) private void parseKeyboardLayoutSetContent(final XmlPullParser parser)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
int event; int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@ -340,7 +327,7 @@ public class KeyboardLayoutSet {
} }
} }
private void parseKeyboardLayoutSetElement(XmlPullParser parser) private void parseKeyboardLayoutSetElement(final XmlPullParser parser)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.KeyboardLayoutSet_Element); R.styleable.KeyboardLayoutSet_Element);
@ -367,7 +354,7 @@ public class KeyboardLayoutSet {
} }
} }
private static int getKeyboardMode(EditorInfo editorInfo) { private static int getKeyboardMode(final EditorInfo editorInfo) {
if (editorInfo == null) if (editorInfo == null)
return KeyboardId.MODE_TEXT; return KeyboardId.MODE_TEXT;

View file

@ -20,15 +20,17 @@ import android.graphics.Paint;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.view.View; import android.view.View;
import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec; import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.StringUtils;
public class MoreKeysKeyboard extends Keyboard { public class MoreKeysKeyboard extends Keyboard {
private final int mDefaultKeyCoordX; private final int mDefaultKeyCoordX;
MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) { MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
super(params); super(params);
mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
} }
@ -37,220 +39,222 @@ public class MoreKeysKeyboard extends Keyboard {
return mDefaultKeyCoordX; return mDefaultKeyCoordX;
} }
public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> { /* package for test */
static class MoreKeysKeyboardParams extends KeyboardParams {
public boolean mIsFixedOrder;
/* package */int mTopRowAdjustment;
public int mNumRows;
public int mNumColumns;
public int mTopKeys;
public int mLeftKeys;
public int mRightKeys; // includes default key.
public int mDividerWidth;
public int mColumnWidth;
public MoreKeysKeyboardParams() {
super();
}
/**
* Set keyboard parameters of more keys keyboard.
*
* @param numKeys number of keys in this more keys keyboard.
* @param maxColumns number of maximum columns of this more keys keyboard.
* @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
* @param rowHeight more keys keyboard row height in pixel, including vertical gap.
* @param coordXInParent coordinate x of the key preview in parent keyboard.
* @param parentKeyboardWidth parent keyboard width in pixel.
* @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
* @param dividerWidth width of divider, zero for no dividers.
*/
public void setParameters(final int numKeys, final int maxColumns, final int keyWidth,
final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
final boolean isFixedColumnOrder, final int dividerWidth) {
mIsFixedOrder = isFixedColumnOrder;
if (parentKeyboardWidth / keyWidth < maxColumns) {
throw new IllegalArgumentException(
"Keyboard is too small to hold more keys keyboard: "
+ parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
}
mDefaultKeyWidth = keyWidth;
mDefaultRowHeight = rowHeight;
final int numRows = (numKeys + maxColumns - 1) / maxColumns;
mNumRows = numRows;
final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
: getOptimizedColumns(numKeys, maxColumns);
mNumColumns = numColumns;
final int topKeys = numKeys % numColumns;
mTopKeys = topKeys == 0 ? numColumns : topKeys;
final int numLeftKeys = (numColumns - 1) / 2;
final int numRightKeys = numColumns - numLeftKeys; // including default key.
// Maximum number of keys we can layout both side of the parent key
final int maxLeftKeys = coordXInParent / keyWidth;
final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
int leftKeys, rightKeys;
if (numLeftKeys > maxLeftKeys) {
leftKeys = maxLeftKeys;
rightKeys = numColumns - leftKeys;
} else if (numRightKeys > maxRightKeys + 1) {
rightKeys = maxRightKeys + 1; // include default key
leftKeys = numColumns - rightKeys;
} else {
leftKeys = numLeftKeys;
rightKeys = numRightKeys;
}
// If the left keys fill the left side of the parent key, entire more keys keyboard
// should be shifted to the right unless the parent key is on the left edge.
if (maxLeftKeys == leftKeys && leftKeys > 0) {
leftKeys--;
rightKeys++;
}
// If the right keys fill the right side of the parent key, entire more keys
// should be shifted to the left unless the parent key is on the right edge.
if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
leftKeys++;
rightKeys--;
}
mLeftKeys = leftKeys;
mRightKeys = rightKeys;
// Adjustment of the top row.
mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
: getAutoOrderTopRowAdjustment();
mDividerWidth = dividerWidth;
mColumnWidth = mDefaultKeyWidth + mDividerWidth;
mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
// Need to subtract the bottom row's gutter only.
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+ mTopPadding + mBottomPadding;
}
private int getFixedOrderTopRowAdjustment() {
if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
|| mLeftKeys == 0 || mRightKeys == 1) {
return 0;
}
return -1;
}
private int getAutoOrderTopRowAdjustment() {
if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
|| mLeftKeys == 0 || mRightKeys == 1) {
return 0;
}
return -1;
}
// Return key position according to column count (0 is default).
/* package */int getColumnPos(final int n) {
return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
}
private int getFixedOrderColumnPos(final int n) {
final int col = n % mNumColumns;
final int row = n / mNumColumns;
if (!isTopRow(row)) {
return col - mLeftKeys;
}
final int rightSideKeys = mTopKeys / 2;
final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
final int pos = col - leftSideKeys;
final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
final int numRightKeys = mRightKeys - 1;
if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
return pos;
} else if (numRightKeys < rightSideKeys) {
return pos - (rightSideKeys - numRightKeys);
} else { // numLeftKeys < leftSideKeys
return pos + (leftSideKeys - numLeftKeys);
}
}
private int getAutomaticColumnPos(final int n) {
final int col = n % mNumColumns;
final int row = n / mNumColumns;
int leftKeys = mLeftKeys;
if (isTopRow(row)) {
leftKeys += mTopRowAdjustment;
}
if (col == 0) {
// default position.
return 0;
}
int pos = 0;
int right = 1; // include default position key.
int left = 0;
int i = 0;
while (true) {
// Assign right key if available.
if (right < mRightKeys) {
pos = right;
right++;
i++;
}
if (i >= col)
break;
// Assign left key if available.
if (left < leftKeys) {
left++;
pos = -left;
i++;
}
if (i >= col)
break;
}
return pos;
}
private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
final int remainings = numKeys % numColumns;
return remainings == 0 ? 0 : numColumns - remainings;
}
private int getOptimizedColumns(final int numKeys, final int maxColumns) {
int numColumns = Math.min(numKeys, maxColumns);
while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
numColumns--;
}
return numColumns;
}
public int getDefaultKeyCoordX() {
return mLeftKeys * mColumnWidth;
}
public int getX(final int n, final int row) {
final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
if (isTopRow(row)) {
return x + mTopRowAdjustment * (mColumnWidth / 2);
}
return x;
}
public int getY(final int row) {
return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
}
public void markAsEdgeKey(final Key key, final int row) {
if (row == 0)
key.markAsTopEdge(this);
if (isTopRow(row))
key.markAsBottomEdge(this);
}
private boolean isTopRow(final int rowCount) {
return mNumRows > 1 && rowCount == mNumRows - 1;
}
}
public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
private final Key mParentKey; private final Key mParentKey;
private final Drawable mDivider; private final Drawable mDivider;
private static final float LABEL_PADDING_RATIO = 0.2f; private static final float LABEL_PADDING_RATIO = 0.2f;
private static final float DIVIDER_RATIO = 0.2f; private static final float DIVIDER_RATIO = 0.2f;
public static class MoreKeysKeyboardParams extends Keyboard.Params {
public boolean mIsFixedOrder;
/* package */int mTopRowAdjustment;
public int mNumRows;
public int mNumColumns;
public int mTopKeys;
public int mLeftKeys;
public int mRightKeys; // includes default key.
public int mDividerWidth;
public int mColumnWidth;
public MoreKeysKeyboardParams() {
super();
}
/**
* Set keyboard parameters of more keys keyboard.
*
* @param numKeys number of keys in this more keys keyboard.
* @param maxColumns number of maximum columns of this more keys keyboard.
* @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
* @param rowHeight more keys keyboard row height in pixel, including vertical gap.
* @param coordXInParent coordinate x of the key preview in parent keyboard.
* @param parentKeyboardWidth parent keyboard width in pixel.
* @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
* @param dividerWidth width of divider, zero for no dividers.
*/
public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder,
int dividerWidth) {
mIsFixedOrder = isFixedColumnOrder;
if (parentKeyboardWidth / keyWidth < maxColumns) {
throw new IllegalArgumentException(
"Keyboard is too small to hold more keys keyboard: "
+ parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
}
mDefaultKeyWidth = keyWidth;
mDefaultRowHeight = rowHeight;
final int numRows = (numKeys + maxColumns - 1) / maxColumns;
mNumRows = numRows;
final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
: getOptimizedColumns(numKeys, maxColumns);
mNumColumns = numColumns;
final int topKeys = numKeys % numColumns;
mTopKeys = topKeys == 0 ? numColumns : topKeys;
final int numLeftKeys = (numColumns - 1) / 2;
final int numRightKeys = numColumns - numLeftKeys; // including default key.
// Maximum number of keys we can layout both side of the parent key
final int maxLeftKeys = coordXInParent / keyWidth;
final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
int leftKeys, rightKeys;
if (numLeftKeys > maxLeftKeys) {
leftKeys = maxLeftKeys;
rightKeys = numColumns - leftKeys;
} else if (numRightKeys > maxRightKeys + 1) {
rightKeys = maxRightKeys + 1; // include default key
leftKeys = numColumns - rightKeys;
} else {
leftKeys = numLeftKeys;
rightKeys = numRightKeys;
}
// If the left keys fill the left side of the parent key, entire more keys keyboard
// should be shifted to the right unless the parent key is on the left edge.
if (maxLeftKeys == leftKeys && leftKeys > 0) {
leftKeys--;
rightKeys++;
}
// If the right keys fill the right side of the parent key, entire more keys
// should be shifted to the left unless the parent key is on the right edge.
if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
leftKeys++;
rightKeys--;
}
mLeftKeys = leftKeys;
mRightKeys = rightKeys;
// Adjustment of the top row.
mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
: getAutoOrderTopRowAdjustment();
mDividerWidth = dividerWidth;
mColumnWidth = mDefaultKeyWidth + mDividerWidth;
mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
// Need to subtract the bottom row's gutter only.
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+ mTopPadding + mBottomPadding;
}
private int getFixedOrderTopRowAdjustment() {
if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
|| mLeftKeys == 0 || mRightKeys == 1) {
return 0;
}
return -1;
}
private int getAutoOrderTopRowAdjustment() {
if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
|| mLeftKeys == 0 || mRightKeys == 1) {
return 0;
}
return -1;
}
// Return key position according to column count (0 is default).
/* package */int getColumnPos(int n) {
return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
}
private int getFixedOrderColumnPos(int n) {
final int col = n % mNumColumns;
final int row = n / mNumColumns;
if (!isTopRow(row)) {
return col - mLeftKeys;
}
final int rightSideKeys = mTopKeys / 2;
final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
final int pos = col - leftSideKeys;
final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
final int numRightKeys = mRightKeys - 1;
if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
return pos;
} else if (numRightKeys < rightSideKeys) {
return pos - (rightSideKeys - numRightKeys);
} else { // numLeftKeys < leftSideKeys
return pos + (leftSideKeys - numLeftKeys);
}
}
private int getAutomaticColumnPos(int n) {
final int col = n % mNumColumns;
final int row = n / mNumColumns;
int leftKeys = mLeftKeys;
if (isTopRow(row)) {
leftKeys += mTopRowAdjustment;
}
if (col == 0) {
// default position.
return 0;
}
int pos = 0;
int right = 1; // include default position key.
int left = 0;
int i = 0;
while (true) {
// Assign right key if available.
if (right < mRightKeys) {
pos = right;
right++;
i++;
}
if (i >= col)
break;
// Assign left key if available.
if (left < leftKeys) {
left++;
pos = -left;
i++;
}
if (i >= col)
break;
}
return pos;
}
private static int getTopRowEmptySlots(int numKeys, int numColumns) {
final int remainings = numKeys % numColumns;
return remainings == 0 ? 0 : numColumns - remainings;
}
private int getOptimizedColumns(int numKeys, int maxColumns) {
int numColumns = Math.min(numKeys, maxColumns);
while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
numColumns--;
}
return numColumns;
}
public int getDefaultKeyCoordX() {
return mLeftKeys * mColumnWidth;
}
public int getX(int n, int row) {
final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
if (isTopRow(row)) {
return x + mTopRowAdjustment * (mColumnWidth / 2);
}
return x;
}
public int getY(int row) {
return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
}
public void markAsEdgeKey(Key key, int row) {
if (row == 0)
key.markAsTopEdge(this);
if (isTopRow(row))
key.markAsBottomEdge(this);
}
private boolean isTopRow(int rowCount) {
return mNumRows > 1 && rowCount == mNumRows - 1;
}
}
/** /**
* The builder of MoreKeysKeyboard. * The builder of MoreKeysKeyboard.
@ -258,7 +262,8 @@ public class MoreKeysKeyboard extends Keyboard {
* @param parentKey the {@link Key} that invokes more keys keyboard. * @param parentKey the {@link Key} that invokes more keys keyboard.
* @param parentKeyboardView the {@link KeyboardView} that contains the parentKey. * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
*/ */
public Builder(View containerView, Key parentKey, KeyboardView parentKeyboardView) { public Builder(final View containerView, final Key parentKey,
final KeyboardView parentKeyboardView) {
super(containerView.getContext(), new MoreKeysKeyboardParams()); super(containerView.getContext(), new MoreKeysKeyboardParams());
final Keyboard parentKeyboard = parentKeyboardView.getKeyboard(); final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId); load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
@ -300,7 +305,8 @@ public class MoreKeysKeyboard extends Keyboard {
dividerWidth); dividerWidth);
} }
private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) { private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
final int minKeyWidth) {
final int padding = (int)(view.getResources() final int padding = (int)(view.getResources()
.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding) .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
+ (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0)); + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
@ -322,24 +328,6 @@ public class MoreKeysKeyboard extends Keyboard {
return maxWidth; return maxWidth;
} }
private static class MoreKeyDivider extends Key.Spacer {
private final Drawable mIcon;
public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) {
super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
mIcon = icon;
}
@Override
public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
// KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
// constructor.
// TODO: Drawable itself should have an alpha value.
mIcon.setAlpha(128);
return mIcon;
}
}
@Override @Override
public MoreKeysKeyboard build() { public MoreKeysKeyboard build() {
final MoreKeysKeyboardParams params = mParams; final MoreKeysKeyboardParams params = mParams;
@ -368,4 +356,23 @@ public class MoreKeysKeyboard extends Keyboard {
return new MoreKeysKeyboard(params); return new MoreKeysKeyboard(params);
} }
} }
private static class MoreKeyDivider extends Key.Spacer {
private final Drawable mIcon;
public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon,
final int x, final int y) {
super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
mIcon = icon;
}
@Override
public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
// KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
// constructor.
// TODO: Drawable itself should have an alpha value.
mIcon.setAlpha(128);
return mIcon;
}
}
} }

View file

@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard;
import android.graphics.Rect; import android.graphics.Rect;
import android.text.TextUtils; import android.text.TextUtils;
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection; import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.JniUtils; import com.android.inputmethod.latin.JniUtils;
@ -48,9 +48,10 @@ public class ProximityInfo {
private final Key[][] mGridNeighbors; private final Key[][] mGridNeighbors;
private final String mLocaleStr; private final String mLocaleStr;
ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height, ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys, final int minWidth, final int height, final int mostCommonKeyWidth,
TouchPositionCorrection touchPositionCorrection) { final int mostCommonKeyHeight, final Key[] keys,
final TouchPositionCorrection touchPositionCorrection) {
if (TextUtils.isEmpty(localeStr)) { if (TextUtils.isEmpty(localeStr)) {
mLocaleStr = ""; mLocaleStr = "";
} else { } else {
@ -81,7 +82,7 @@ public class ProximityInfo {
} }
public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity, public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
int rowSize, int gridWidth, int gridHeight) { final int rowSize, final int gridWidth, final int gridHeight) {
final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
spellCheckerProximityInfo.mNativeProximityInfo = spellCheckerProximityInfo.mNativeProximityInfo =
spellCheckerProximityInfo.setProximityInfoNative("", spellCheckerProximityInfo.setProximityInfoNative("",

View file

@ -56,59 +56,20 @@ public class KeySpecParser {
private static final char ESCAPE_CHAR = '\\'; private static final char ESCAPE_CHAR = '\\';
private static final char LABEL_END = '|'; private static final char LABEL_END = '|';
private static final String PREFIX_TEXT = "!text/"; private static final String PREFIX_TEXT = "!text/";
private static final String PREFIX_ICON = "!icon/"; static final String PREFIX_ICON = "!icon/";
private static final String PREFIX_CODE = "!code/"; private static final String PREFIX_CODE = "!code/";
private static final String PREFIX_HEX = "0x"; private static final String PREFIX_HEX = "0x";
private static final String ADDITIONAL_MORE_KEY_MARKER = "%"; private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
public static class MoreKeySpec {
public final int mCode;
public final String mLabel;
public final String mOutputText;
public final int mIconId;
public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, Locale locale,
final KeyboardCodesSet codesSet) {
mLabel = toUpperCaseOfStringForLocale(getLabel(moreKeySpec),
needsToUpperCase, locale);
final int code = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet),
needsToUpperCase, locale);
if (code == Keyboard.CODE_UNSPECIFIED) {
// Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
// upper case representation ("SS").
mCode = Keyboard.CODE_OUTPUT_TEXT;
mOutputText = mLabel;
} else {
mCode = code;
mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec),
needsToUpperCase, locale);
}
mIconId = getIconId(moreKeySpec);
}
@Override
public String toString() {
final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
: PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
: Keyboard.printableCode(mCode));
if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
return output;
} else {
return label + "|" + output;
}
}
}
private KeySpecParser() { private KeySpecParser() {
// Intentional empty constructor for utility class. // Intentional empty constructor for utility class.
} }
private static boolean hasIcon(String moreKeySpec) { private static boolean hasIcon(final String moreKeySpec) {
return moreKeySpec.startsWith(PREFIX_ICON); return moreKeySpec.startsWith(PREFIX_ICON);
} }
private static boolean hasCode(String moreKeySpec) { private static boolean hasCode(final String moreKeySpec) {
final int end = indexOfLabelEnd(moreKeySpec, 0); final int end = indexOfLabelEnd(moreKeySpec, 0);
if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith( if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
PREFIX_CODE, end + 1)) { PREFIX_CODE, end + 1)) {
@ -117,7 +78,7 @@ public class KeySpecParser {
return false; return false;
} }
private static String parseEscape(String text) { private static String parseEscape(final String text) {
if (text.indexOf(ESCAPE_CHAR) < 0) { if (text.indexOf(ESCAPE_CHAR) < 0) {
return text; return text;
} }
@ -136,7 +97,7 @@ public class KeySpecParser {
return sb.toString(); return sb.toString();
} }
private static int indexOfLabelEnd(String moreKeySpec, int start) { private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) { if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
final int end = moreKeySpec.indexOf(LABEL_END, start); final int end = moreKeySpec.indexOf(LABEL_END, start);
if (end == 0) { if (end == 0) {
@ -157,7 +118,7 @@ public class KeySpecParser {
return -1; return -1;
} }
public static String getLabel(String moreKeySpec) { public static String getLabel(final String moreKeySpec) {
if (hasIcon(moreKeySpec)) { if (hasIcon(moreKeySpec)) {
return null; return null;
} }
@ -170,7 +131,7 @@ public class KeySpecParser {
return label; return label;
} }
private static String getOutputTextInternal(String moreKeySpec) { private static String getOutputTextInternal(final String moreKeySpec) {
final int end = indexOfLabelEnd(moreKeySpec, 0); final int end = indexOfLabelEnd(moreKeySpec, 0);
if (end <= 0) { if (end <= 0) {
return null; return null;
@ -181,7 +142,7 @@ public class KeySpecParser {
return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1)); return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
} }
static String getOutputText(String moreKeySpec) { static String getOutputText(final String moreKeySpec) {
if (hasCode(moreKeySpec)) { if (hasCode(moreKeySpec)) {
return null; return null;
} }
@ -205,7 +166,7 @@ public class KeySpecParser {
return (StringUtils.codePointCount(label) == 1) ? null : label; return (StringUtils.codePointCount(label) == 1) ? null : label;
} }
static int getCode(String moreKeySpec, KeyboardCodesSet codesSet) { static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
if (hasCode(moreKeySpec)) { if (hasCode(moreKeySpec)) {
final int end = indexOfLabelEnd(moreKeySpec, 0); final int end = indexOfLabelEnd(moreKeySpec, 0);
if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
@ -230,7 +191,8 @@ public class KeySpecParser {
return Keyboard.CODE_OUTPUT_TEXT; return Keyboard.CODE_OUTPUT_TEXT;
} }
public static int parseCode(String text, KeyboardCodesSet codesSet, int defCode) { public static int parseCode(final String text, final KeyboardCodesSet codesSet,
final int defCode) {
if (text == null) return defCode; if (text == null) return defCode;
if (text.startsWith(PREFIX_CODE)) { if (text.startsWith(PREFIX_CODE)) {
return codesSet.getCode(text.substring(PREFIX_CODE.length())); return codesSet.getCode(text.substring(PREFIX_CODE.length()));
@ -241,7 +203,7 @@ public class KeySpecParser {
} }
} }
public static int getIconId(String moreKeySpec) { public static int getIconId(final String moreKeySpec) {
if (moreKeySpec != null && hasIcon(moreKeySpec)) { if (moreKeySpec != null && hasIcon(moreKeySpec)) {
final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length()); final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length()) final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
@ -251,7 +213,7 @@ public class KeySpecParser {
return KeyboardIconsSet.ICON_UNDEFINED; return KeyboardIconsSet.ICON_UNDEFINED;
} }
private static <T> ArrayList<T> arrayAsList(T[] array, int start, int end) { private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
if (array == null) { if (array == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
@ -268,7 +230,7 @@ public class KeySpecParser {
private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static String[] filterOutEmptyString(String[] array) { private static String[] filterOutEmptyString(final String[] array) {
if (array == null) { if (array == null) {
return EMPTY_STRING_ARRAY; return EMPTY_STRING_ARRAY;
} }
@ -289,8 +251,8 @@ public class KeySpecParser {
return out.toArray(new String[out.size()]); return out.toArray(new String[out.size()]);
} }
public static String[] insertAdditionalMoreKeys(String[] moreKeySpecs, public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
String[] additionalMoreKeySpecs) { final String[] additionalMoreKeySpecs) {
final String[] moreKeys = filterOutEmptyString(moreKeySpecs); final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs); final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
final int moreKeysCount = moreKeys.length; final int moreKeysCount = moreKeys.length;
@ -357,12 +319,13 @@ public class KeySpecParser {
@SuppressWarnings("serial") @SuppressWarnings("serial")
public static class KeySpecParserError extends RuntimeException { public static class KeySpecParserError extends RuntimeException {
public KeySpecParserError(String message) { public KeySpecParserError(final String message) {
super(message); super(message);
} }
} }
public static String resolveTextReference(String rawText, KeyboardTextsSet textsSet) { public static String resolveTextReference(final String rawText,
final KeyboardTextsSet textsSet) {
int level = 0; int level = 0;
String text = rawText; String text = rawText;
StringBuilder sb; StringBuilder sb;
@ -408,7 +371,7 @@ public class KeySpecParser {
return text; return text;
} }
private static int searchTextNameEnd(String text, int start) { private static int searchTextNameEnd(final String text, final int start) {
final int size = text.length(); final int size = text.length();
for (int pos = start; pos < size; pos++) { for (int pos = start; pos < size; pos++) {
final char c = text.charAt(pos); final char c = text.charAt(pos);
@ -421,7 +384,7 @@ public class KeySpecParser {
return size; return size;
} }
public static String[] parseCsvString(String rawText, KeyboardTextsSet textsSet) { public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) {
final String text = resolveTextReference(rawText, textsSet); final String text = resolveTextReference(rawText, textsSet);
final int size = text.length(); final int size = text.length();
if (size == 0) { if (size == 0) {
@ -460,7 +423,8 @@ public class KeySpecParser {
return list.toArray(new String[list.size()]); return list.toArray(new String[list.size()]);
} }
public static int getIntValue(String[] moreKeys, String key, int defaultValue) { public static int getIntValue(final String[] moreKeys, final String key,
final int defaultValue) {
if (moreKeys == null) { if (moreKeys == null) {
return defaultValue; return defaultValue;
} }
@ -486,7 +450,7 @@ public class KeySpecParser {
return value; return value;
} }
public static boolean getBooleanValue(String[] moreKeys, String key) { public static boolean getBooleanValue(final String[] moreKeys, final String key) {
if (moreKeys == null) { if (moreKeys == null) {
return false; return false;
} }
@ -502,8 +466,8 @@ public class KeySpecParser {
return value; return value;
} }
public static int toUpperCaseOfCodeForLocale(int code, boolean needsToUpperCase, public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
Locale locale) { final Locale locale) {
if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code; if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code;
final String text = new String(new int[] { code } , 0, 1); final String text = new String(new int[] { code } , 0, 1);
final String casedText = KeySpecParser.toUpperCaseOfStringForLocale( final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
@ -512,8 +476,8 @@ public class KeySpecParser {
? casedText.codePointAt(0) : CODE_UNSPECIFIED; ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
} }
public static String toUpperCaseOfStringForLocale(String text, boolean needsToUpperCase, public static String toUpperCaseOfStringForLocale(final String text,
Locale locale) { final boolean needsToUpperCase, final Locale locale) {
if (text == null || !needsToUpperCase) return text; if (text == null || !needsToUpperCase) return text;
return text.toUpperCase(locale); return text.toUpperCase(locale);
} }

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2012 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.internal;
import android.content.res.TypedArray;
public abstract class KeyStyle {
private final KeyboardTextsSet mTextsSet;
public abstract String[] getStringArray(TypedArray a, int index);
public abstract String getString(TypedArray a, int index);
public abstract int getInt(TypedArray a, int index, int defaultValue);
public abstract int getFlag(TypedArray a, int index);
protected KeyStyle(final KeyboardTextsSet textsSet) {
mTextsSet = textsSet;
}
protected String parseString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
}
return null;
}
protected String[] parseStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
}
return null;
}
}

View file

@ -20,7 +20,6 @@ import android.content.res.TypedArray;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.XmlParseUtils; import com.android.inputmethod.latin.XmlParseUtils;
@ -30,75 +29,62 @@ import org.xmlpull.v1.XmlPullParserException;
import java.util.HashMap; import java.util.HashMap;
public class KeyStyles { public class KeyStylesSet {
private static final String TAG = KeyStyles.class.getSimpleName(); private static final String TAG = KeyStylesSet.class.getSimpleName();
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap(); private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
final KeyboardTextsSet mTextsSet; private final KeyboardTextsSet mTextsSet;
private final KeyStyle mEmptyKeyStyle; private final KeyStyle mEmptyKeyStyle;
private static final String EMPTY_STYLE_NAME = "<empty>"; private static final String EMPTY_STYLE_NAME = "<empty>";
public KeyStyles(KeyboardTextsSet textsSet) { public KeyStylesSet(final KeyboardTextsSet textsSet) {
mTextsSet = textsSet; mTextsSet = textsSet;
mEmptyKeyStyle = new EmptyKeyStyle(); mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle); mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
} }
public abstract class KeyStyle { private static class EmptyKeyStyle extends KeyStyle {
public abstract String[] getStringArray(TypedArray a, int index); EmptyKeyStyle(final KeyboardTextsSet textsSet) {
public abstract String getString(TypedArray a, int index); super(textsSet);
public abstract int getInt(TypedArray a, int index, int defaultValue);
public abstract int getFlag(TypedArray a, int index);
protected String parseString(TypedArray a, int index) {
if (a.hasValue(index)) {
return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
}
return null;
} }
protected String[] parseStringArray(TypedArray a, int index) {
if (a.hasValue(index)) {
return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
}
return null;
}
}
class EmptyKeyStyle extends KeyStyle {
@Override @Override
public String[] getStringArray(TypedArray a, int index) { public String[] getStringArray(final TypedArray a, final int index) {
return parseStringArray(a, index); return parseStringArray(a, index);
} }
@Override @Override
public String getString(TypedArray a, int index) { public String getString(final TypedArray a, final int index) {
return parseString(a, index); return parseString(a, index);
} }
@Override @Override
public int getInt(TypedArray a, int index, int defaultValue) { public int getInt(final TypedArray a, final int index, final int defaultValue) {
return a.getInt(index, defaultValue); return a.getInt(index, defaultValue);
} }
@Override @Override
public int getFlag(TypedArray a, int index) { public int getFlag(final TypedArray a, final int index) {
return a.getInt(index, 0); return a.getInt(index, 0);
} }
} }
private class DeclaredKeyStyle extends KeyStyle { private static class DeclaredKeyStyle extends KeyStyle {
private final HashMap<String, KeyStyle> mStyles;
private final String mParentStyleName; private final String mParentStyleName;
private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray(); private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
public DeclaredKeyStyle(String parentStyleName) { public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet,
final HashMap<String, KeyStyle> styles) {
super(textsSet);
mParentStyleName = parentStyleName; mParentStyleName = parentStyleName;
mStyles = styles;
} }
@Override @Override
public String[] getStringArray(TypedArray a, int index) { public String[] getStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
return parseStringArray(a, index); return parseStringArray(a, index);
} }
@ -111,7 +97,7 @@ public class KeyStyles {
} }
@Override @Override
public String getString(TypedArray a, int index) { public String getString(final TypedArray a, final int index) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
return parseString(a, index); return parseString(a, index);
} }
@ -124,7 +110,7 @@ public class KeyStyles {
} }
@Override @Override
public int getInt(TypedArray a, int index, int defaultValue) { public int getInt(final TypedArray a, final int index, final int defaultValue) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
return a.getInt(index, defaultValue); return a.getInt(index, defaultValue);
} }
@ -137,7 +123,7 @@ public class KeyStyles {
} }
@Override @Override
public int getFlag(TypedArray a, int index) { public int getFlag(final TypedArray a, final int index) {
int flags = a.getInt(index, 0); int flags = a.getInt(index, 0);
final Object value = mStyleAttributes.get(index); final Object value = mStyleAttributes.get(index);
if (value != null) { if (value != null) {
@ -147,7 +133,7 @@ public class KeyStyles {
return flags | parentStyle.getFlag(a, index); return flags | parentStyle.getFlag(a, index);
} }
void readKeyAttributes(TypedArray keyAttr) { public void readKeyAttributes(final TypedArray keyAttr) {
// TODO: Currently not all Key attributes can be declared as style. // TODO: Currently not all Key attributes can be declared as style.
readString(keyAttr, R.styleable.Keyboard_Key_code); readString(keyAttr, R.styleable.Keyboard_Key_code);
readString(keyAttr, R.styleable.Keyboard_Key_altCode); readString(keyAttr, R.styleable.Keyboard_Key_altCode);
@ -165,38 +151,38 @@ public class KeyStyles {
readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
} }
private void readString(TypedArray a, int index) { private void readString(final TypedArray a, final int index) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
mStyleAttributes.put(index, parseString(a, index)); mStyleAttributes.put(index, parseString(a, index));
} }
} }
private void readInt(TypedArray a, int index) { private void readInt(final TypedArray a, final int index) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
mStyleAttributes.put(index, a.getInt(index, 0)); mStyleAttributes.put(index, a.getInt(index, 0));
} }
} }
private void readFlag(TypedArray a, int index) { private void readFlag(final TypedArray a, final int index) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
final Integer value = (Integer)mStyleAttributes.get(index); final Integer value = (Integer)mStyleAttributes.get(index);
mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0)); mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
} }
} }
private void readStringArray(TypedArray a, int index) { private void readStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) { if (a.hasValue(index)) {
mStyleAttributes.put(index, parseStringArray(a, index)); mStyleAttributes.put(index, parseStringArray(a, index));
} }
} }
} }
public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs, public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
XmlPullParser parser) throws XmlPullParserException { final XmlPullParser parser) throws XmlPullParserException {
final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName); final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, String.format("<%s styleName=%s />", Log.d(TAG, String.format("<%s styleName=%s />",
Keyboard.Builder.TAG_KEY_STYLE, styleName)); KeyboardBuilder.TAG_KEY_STYLE, styleName));
if (mStyles.containsKey(styleName)) { if (mStyles.containsKey(styleName)) {
Log.d(TAG, "key-style " + styleName + " is overridden at " Log.d(TAG, "key-style " + styleName + " is overridden at "
+ parser.getPositionDescription()); + parser.getPositionDescription());
@ -211,12 +197,12 @@ public class KeyStyles {
"Unknown parentStyle " + parentStyleName, parser); "Unknown parentStyle " + parentStyleName, parser);
} }
} }
final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName); final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
style.readKeyAttributes(keyAttrs); style.readKeyAttributes(keyAttrs);
mStyles.put(styleName, style); mStyles.put(styleName, style);
} }
public KeyStyle getKeyStyle(TypedArray keyAttr, XmlPullParser parser) public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
throws XmlParseUtils.ParseException { throws XmlParseUtils.ParseException {
if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) { if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
return mEmptyKeyStyle; return mEmptyKeyStyle;

View file

@ -0,0 +1,829 @@
/*
* Copyright (C) 2012 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.internal;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.util.Xml;
import android.view.InflateException;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SubtypeLocale;
import com.android.inputmethod.latin.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
/**
* Keyboard Building helper.
*
* This class parses Keyboard XML file and eventually build a Keyboard.
* The Keyboard XML file looks like:
* <pre>
* &lt;!-- xml/keyboard.xml --&gt;
* &lt;Keyboard keyboard_attributes*&gt;
* &lt;!-- Keyboard Content --&gt;
* &lt;Row row_attributes*&gt;
* &lt;!-- Row Content --&gt;
* &lt;Key key_attributes* /&gt;
* &lt;Spacer horizontalGap="32.0dp" /&gt;
* &lt;include keyboardLayout="@xml/other_keys"&gt;
* ...
* &lt;/Row&gt;
* &lt;include keyboardLayout="@xml/other_rows"&gt;
* ...
* &lt;/Keyboard&gt;
* </pre>
* The XML file which is included in other file must have &lt;merge&gt; as root element,
* such as:
* <pre>
* &lt;!-- xml/other_keys.xml --&gt;
* &lt;merge&gt;
* &lt;Key key_attributes* /&gt;
* ...
* &lt;/merge&gt;
* </pre>
* and
* <pre>
* &lt;!-- xml/other_rows.xml --&gt;
* &lt;merge&gt;
* &lt;Row row_attributes*&gt;
* &lt;Key key_attributes* /&gt;
* &lt;/Row&gt;
* ...
* &lt;/merge&gt;
* </pre>
* You can also use switch-case-default tags to select Rows and Keys.
* <pre>
* &lt;switch&gt;
* &lt;case case_attribute*&gt;
* &lt;!-- Any valid tags at switch position --&gt;
* &lt;/case&gt;
* ...
* &lt;default&gt;
* &lt;!-- Any valid tags at switch position --&gt;
* &lt;/default&gt;
* &lt;/switch&gt;
* </pre>
* You can declare Key style and specify styles within Key tags.
* <pre>
* &lt;switch&gt;
* &lt;case mode="email"&gt;
* &lt;key-style styleName="f1-key" parentStyle="modifier-key"
* keyLabel=".com"
* /&gt;
* &lt;/case&gt;
* &lt;case mode="url"&gt;
* &lt;key-style styleName="f1-key" parentStyle="modifier-key"
* keyLabel="http://"
* /&gt;
* &lt;/case&gt;
* &lt;/switch&gt;
* ...
* &lt;Key keyStyle="shift-key" ... /&gt;
* </pre>
*/
public class KeyboardBuilder<KP extends KeyboardParams> {
private static final String BUILDER_TAG = "Keyboard.Builder";
private static final boolean DEBUG = false;
// Keyboard XML Tags
private static final String TAG_KEYBOARD = "Keyboard";
private static final String TAG_ROW = "Row";
private static final String TAG_KEY = "Key";
private static final String TAG_SPACER = "Spacer";
private static final String TAG_INCLUDE = "include";
private static final String TAG_MERGE = "merge";
private static final String TAG_SWITCH = "switch";
private static final String TAG_CASE = "case";
private static final String TAG_DEFAULT = "default";
public static final String TAG_KEY_STYLE = "key-style";
private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
private static final int DEFAULT_KEYBOARD_ROWS = 4;
protected final KP mParams;
protected final Context mContext;
protected final Resources mResources;
private final DisplayMetrics mDisplayMetrics;
private int mCurrentY = 0;
private KeyboardRow mCurrentRow = null;
private boolean mLeftEdge;
private boolean mTopEdge;
private Key mRightEdgeKey = null;
public KeyboardBuilder(final Context context, final KP params) {
mContext = context;
final Resources res = context.getResources();
mResources = res;
mDisplayMetrics = res.getDisplayMetrics();
mParams = params;
params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
}
public void setAutoGenerate(final KeysCache keysCache) {
mParams.mKeysCache = keysCache;
}
public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
mParams.mId = id;
final XmlResourceParser parser = mResources.getXml(xmlId);
try {
parseKeyboard(parser);
} catch (XmlPullParserException e) {
Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
throw new IllegalArgumentException(e);
} catch (IOException e) {
Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
throw new RuntimeException(e);
} finally {
parser.close();
}
return this;
}
// TODO: Remove this method.
public void setTouchPositionCorrectionEnabled(final boolean enabled) {
mParams.mTouchPositionCorrection.setEnabled(enabled);
}
public void setProximityCharsCorrectionEnabled(final boolean enabled) {
mParams.mProximityCharsCorrectionEnabled = enabled;
}
public Keyboard build() {
return new Keyboard(mParams);
}
private int mIndent;
private static final String SPACES = " ";
private static String spaces(final int count) {
return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
}
private void startTag(final String format, final Object ... args) {
Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
}
private void endTag(final String format, final Object ... args) {
Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
}
private void startEndTag(final String format, final Object ... args) {
Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
mIndent--;
}
private void parseKeyboard(final XmlPullParser parser)
throws XmlPullParserException, IOException {
if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_KEYBOARD.equals(tag)) {
parseKeyboardAttributes(parser);
startKeyboard();
parseKeyboardContent(parser, false);
break;
} else {
throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
}
}
}
}
private void parseKeyboardAttributes(final XmlPullParser parser) {
final int displayWidth = mDisplayMetrics.widthPixels;
final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
R.style.Keyboard);
final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
final TypedArray keyboardViewAttr = mResources.obtainAttributes(
Xml.asAttributeSet(parser), R.styleable.KeyboardView);
try {
final int displayHeight = mDisplayMetrics.heightPixels;
final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue(
mResources, R.array.keyboard_heights, null);
final float keyboardHeight;
if (keyboardHeightString != null) {
keyboardHeight = Float.parseFloat(keyboardHeightString)
* mDisplayMetrics.density;
} else {
keyboardHeight = keyboardAttr.getDimension(
R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
}
final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
float minKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
if (minKeyboardHeight < 0) {
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
}
final KeyboardParams params = mParams;
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
params.mOccupiedHeight = (int)Math.max(
Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
params.mOccupiedWidth = params.mId.mWidth;
params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
params.mHorizontalEdgesPadding = (int)ResourceUtils.getDimensionOrFraction(
keyboardAttr,
R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
mParams.mOccupiedWidth, 0);
params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
- params.mHorizontalCenterPadding;
params.mDefaultKeyWidth = (int)ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
params.mVerticalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
- params.mBottomPadding + params.mVerticalGap;
params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_rowHeight, params.mBaseHeight,
params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
if (keyboardViewAttr.hasValue(R.styleable.KeyboardView_keyTypeface)) {
params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt(
R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL));
}
params.mKeyLetterRatio = ResourceUtils.getFraction(keyboardViewAttr,
R.styleable.KeyboardView_keyLetterSize);
params.mKeyLetterSize = ResourceUtils.getDimensionPixelSize(keyboardViewAttr,
R.styleable.KeyboardView_keyLetterSize);
params.mKeyHintLetterRatio = ResourceUtils.getFraction(keyboardViewAttr,
R.styleable.KeyboardView_keyHintLetterRatio);
params.mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(keyboardViewAttr,
R.styleable.KeyboardView_keyShiftedLetterHintRatio);
params.mMoreKeysTemplate = keyboardAttr.getResourceId(
R.styleable.Keyboard_moreKeysTemplate, 0);
params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
params.mIconsSet.loadIcons(keyboardAttr);
final String language = params.mId.mLocale.getLanguage();
params.mCodesSet.setLanguage(language);
params.mTextsSet.setLanguage(language);
final RunInLocale<Void> job = new RunInLocale<Void>() {
@Override
protected Void job(Resources res) {
params.mTextsSet.loadStringResources(mContext);
return null;
}
};
// Null means the current system locale.
final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
? null : params.mId.mLocale;
job.runInLocale(mResources, locale);
final int resourceId = keyboardAttr.getResourceId(
R.styleable.Keyboard_touchPositionCorrectionData, 0);
params.mTouchPositionCorrection.setEnabled(resourceId != 0);
if (resourceId != 0) {
final String[] data = mResources.getStringArray(resourceId);
params.mTouchPositionCorrection.load(data);
}
} finally {
keyboardViewAttr.recycle();
keyAttr.recycle();
keyboardAttr.recycle();
}
}
private void parseKeyboardContent(final XmlPullParser parser, final boolean skip)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_ROW.equals(tag)) {
final KeyboardRow row = parseRowAttributes(parser);
if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
if (!skip) {
startRow(row);
}
parseRowContent(parser, row, skip);
} else if (TAG_INCLUDE.equals(tag)) {
parseIncludeKeyboardContent(parser, skip);
} else if (TAG_SWITCH.equals(tag)) {
parseSwitchKeyboardContent(parser, skip);
} else if (TAG_KEY_STYLE.equals(tag)) {
parseKeyStyle(parser, skip);
} else {
throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
if (DEBUG) endTag("</%s>", tag);
if (TAG_KEYBOARD.equals(tag)) {
endKeyboard();
break;
} else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
|| TAG_MERGE.equals(tag)) {
break;
} else {
throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
}
}
}
}
private KeyboardRow parseRowAttributes(final XmlPullParser parser)
throws XmlPullParserException {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
try {
if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
}
if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
}
return new KeyboardRow(mResources, mParams, parser, mCurrentY);
} finally {
a.recycle();
}
}
private void parseRowContent(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_KEY.equals(tag)) {
parseKey(parser, row, skip);
} else if (TAG_SPACER.equals(tag)) {
parseSpacer(parser, row, skip);
} else if (TAG_INCLUDE.equals(tag)) {
parseIncludeRowContent(parser, row, skip);
} else if (TAG_SWITCH.equals(tag)) {
parseSwitchRowContent(parser, row, skip);
} else if (TAG_KEY_STYLE.equals(tag)) {
parseKeyStyle(parser, skip);
} else {
throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
if (DEBUG) endTag("</%s>", tag);
if (TAG_ROW.equals(tag)) {
if (!skip) {
endRow(row);
}
break;
} else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
|| TAG_MERGE.equals(tag)) {
break;
} else {
throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
}
}
}
}
private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
throws XmlPullParserException, IOException {
if (skip) {
XmlParseUtils.checkEndTag(TAG_KEY, parser);
if (DEBUG) {
startEndTag("<%s /> skipped", TAG_KEY);
}
} else {
final Key key = new Key(mResources, mParams, row, parser);
if (DEBUG) {
startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
(key.isEnabled() ? "" : " disabled"), key,
Arrays.toString(key.mMoreKeys));
}
XmlParseUtils.checkEndTag(TAG_KEY, parser);
endKey(key);
}
}
private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
throws XmlPullParserException, IOException {
if (skip) {
XmlParseUtils.checkEndTag(TAG_SPACER, parser);
if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
} else {
final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
if (DEBUG) startEndTag("<%s />", TAG_SPACER);
XmlParseUtils.checkEndTag(TAG_SPACER, parser);
endKey(spacer);
}
}
private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip)
throws XmlPullParserException, IOException {
parseIncludeInternal(parser, null, skip);
}
private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
parseIncludeInternal(parser, row, skip);
}
private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
if (skip) {
XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
} else {
final AttributeSet attr = Xml.asAttributeSet(parser);
final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
R.styleable.Keyboard_Include);
final TypedArray keyAttr = mResources.obtainAttributes(attr,
R.styleable.Keyboard_Key);
int keyboardLayout = 0;
float savedDefaultKeyWidth = 0;
int savedDefaultKeyLabelFlags = 0;
int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
try {
XmlParseUtils.checkAttributeExists(keyboardAttr,
R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
TAG_INCLUDE, parser);
keyboardLayout = keyboardAttr.getResourceId(
R.styleable.Keyboard_Include_keyboardLayout, 0);
if (row != null) {
if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
// Override current x coordinate.
row.setXPos(row.getKeyX(keyAttr));
}
// TODO: Remove this if-clause and do the same as backgroundType below.
savedDefaultKeyWidth = row.getDefaultKeyWidth();
if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
// Override default key width.
row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
}
savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
// Bitwise-or default keyLabelFlag if exists.
row.setDefaultKeyLabelFlags(keyAttr.getInt(
R.styleable.Keyboard_Key_keyLabelFlags, 0)
| savedDefaultKeyLabelFlags);
savedDefaultBackgroundType = row.getDefaultBackgroundType();
// Override default backgroundType if exists.
row.setDefaultBackgroundType(keyAttr.getInt(
R.styleable.Keyboard_Key_backgroundType,
savedDefaultBackgroundType));
}
} finally {
keyboardAttr.recycle();
keyAttr.recycle();
}
XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
if (DEBUG) {
startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
mResources.getResourceEntryName(keyboardLayout));
}
final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
try {
parseMerge(parserForInclude, row, skip);
} finally {
if (row != null) {
// Restore default keyWidth, keyLabelFlags, and backgroundType.
row.setDefaultKeyWidth(savedDefaultKeyWidth);
row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
row.setDefaultBackgroundType(savedDefaultBackgroundType);
}
parserForInclude.close();
}
}
}
private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
throws XmlPullParserException, IOException {
if (DEBUG) startTag("<%s>", TAG_MERGE);
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_MERGE.equals(tag)) {
if (row == null) {
parseKeyboardContent(parser, skip);
} else {
parseRowContent(parser, row, skip);
}
break;
} else {
throw new XmlParseUtils.ParseException(
"Included keyboard layout must have <merge> root element", parser);
}
}
}
}
private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip)
throws XmlPullParserException, IOException {
parseSwitchInternal(parser, null, skip);
}
private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
parseSwitchInternal(parser, row, skip);
}
private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
boolean selected = false;
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_CASE.equals(tag)) {
selected |= parseCase(parser, row, selected ? true : skip);
} else if (TAG_DEFAULT.equals(tag)) {
selected |= parseDefault(parser, row, selected ? true : skip);
} else {
throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
if (TAG_SWITCH.equals(tag)) {
if (DEBUG) endTag("</%s>", TAG_SWITCH);
break;
} else {
throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
}
}
}
}
private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
throws XmlPullParserException, IOException {
final boolean selected = parseCaseCondition(parser);
if (row == null) {
// Processing Rows.
parseKeyboardContent(parser, selected ? skip : true);
} else {
// Processing Keys.
parseRowContent(parser, row, selected ? skip : true);
}
return selected;
}
private boolean parseCaseCondition(final XmlPullParser parser) {
final KeyboardId id = mParams.mId;
if (id == null) {
return true;
}
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Case);
try {
final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
KeyboardId.elementIdToName(id.mElementId));
final boolean modeMatched = matchTypedValue(a,
R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
final boolean navigateNextMatched = matchBoolean(a,
R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
final boolean navigatePreviousMatched = matchBoolean(a,
R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
final boolean passwordInputMatched = matchBoolean(a,
R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
final boolean clobberSettingsKeyMatched = matchBoolean(a,
R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
final boolean shortcutKeyEnabledMatched = matchBoolean(a,
R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
final boolean hasShortcutKeyMatched = matchBoolean(a,
R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
id.mLanguageSwitchKeyEnabled);
final boolean isMultiLineMatched = matchBoolean(a,
R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
final boolean imeActionMatched = matchInteger(a,
R.styleable.Keyboard_Case_imeAction, id.imeAction());
final boolean localeCodeMatched = matchString(a,
R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
final boolean languageCodeMatched = matchString(a,
R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
final boolean countryCodeMatched = matchString(a,
R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
final boolean selected = keyboardLayoutSetElementMatched && modeMatched
&& navigateNextMatched && navigatePreviousMatched && passwordInputMatched
&& clobberSettingsKeyMatched && shortcutKeyEnabledMatched
&& hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
&& isMultiLineMatched && imeActionMatched && localeCodeMatched
&& languageCodeMatched && countryCodeMatched;
if (DEBUG) {
startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
textAttr(a.getString(
R.styleable.Keyboard_Case_keyboardLayoutSetElement),
"keyboardLayoutSetElement"),
textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
"imeAction"),
booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
"navigateNext"),
booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
"navigatePrevious"),
booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
"clobberSettingsKey"),
booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
"passwordInput"),
booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
"shortcutKeyEnabled"),
booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
"hasShortcutKey"),
booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
"languageSwitchKeyEnabled"),
booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
"isMultiLine"),
textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
"localeCode"),
textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
"languageCode"),
textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
"countryCode"),
selected ? "" : " skipped");
}
return selected;
} finally {
a.recycle();
}
}
private static boolean matchInteger(final TypedArray a, final int index, final int value) {
// If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute.
return !a.hasValue(index) || a.getInt(index, 0) == value;
}
private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) {
// If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute.
return !a.hasValue(index) || a.getBoolean(index, false) == value;
}
private static boolean matchString(final TypedArray a, final int index, final String value) {
// If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute.
return !a.hasValue(index)
|| StringUtils.containsInArray(value, a.getString(index).split("\\|"));
}
private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue,
final String strValue) {
// If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute.
final TypedValue v = a.peekValue(index);
if (v == null) {
return true;
}
if (ResourceUtils.isIntegerValue(v)) {
return intValue == a.getInt(index, 0);
} else if (ResourceUtils.isStringValue(v)) {
return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
}
return false;
}
private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
if (DEBUG) startTag("<%s>", TAG_DEFAULT);
if (row == null) {
parseKeyboardContent(parser, skip);
} else {
parseRowContent(parser, row, skip);
}
return true;
}
private void parseKeyStyle(final XmlPullParser parser, final boolean skip)
throws XmlPullParserException, IOException {
TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_KeyStyle);
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
try {
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+ "/> needs styleName attribute", parser);
}
if (DEBUG) {
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
skip ? " skipped" : "");
}
if (!skip) {
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
}
} finally {
keyStyleAttr.recycle();
keyAttrs.recycle();
}
XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
}
private void startKeyboard() {
mCurrentY += mParams.mTopPadding;
mTopEdge = true;
}
private void startRow(final KeyboardRow row) {
addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
mCurrentRow = row;
mLeftEdge = true;
mRightEdgeKey = null;
}
private void endRow(final KeyboardRow row) {
if (mCurrentRow == null) {
throw new InflateException("orphan end row tag");
}
if (mRightEdgeKey != null) {
mRightEdgeKey.markAsRightEdge(mParams);
mRightEdgeKey = null;
}
addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
mCurrentY += row.mRowHeight;
mCurrentRow = null;
mTopEdge = false;
}
private void endKey(final Key key) {
mParams.onAddKey(key);
if (mLeftEdge) {
key.markAsLeftEdge(mParams);
mLeftEdge = false;
}
if (mTopEdge) {
key.markAsTopEdge(mParams);
}
mRightEdgeKey = key;
}
private void endKeyboard() {
// nothing to do here.
}
private void addEdgeSpace(final float width, final KeyboardRow row) {
row.advanceXPos(width);
mLeftEdge = false;
mRightEdgeKey = null;
}
private static String textAttr(final String value, final String name) {
return value != null ? String.format(" %s=%s", name, value) : "";
}
private static String booleanAttr(final TypedArray a, final int index, final String name) {
return a.hasValue(index)
? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
}
}

View file

@ -0,0 +1,143 @@
/*
* Copyright (C) 2012 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.internal;
import android.graphics.Typeface;
import android.util.SparseIntArray;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ResourceUtils;
import java.util.ArrayList;
import java.util.HashSet;
public class KeyboardParams {
public KeyboardId mId;
public int mThemeId;
/** Total height and width of the keyboard, including the paddings and keys */
public int mOccupiedHeight;
public int mOccupiedWidth;
/** Base height and width of the keyboard used to calculate rows' or keys' heights and
* widths
*/
public int mBaseHeight;
public int mBaseWidth;
public int mTopPadding;
public int mBottomPadding;
public int mHorizontalEdgesPadding;
public int mHorizontalCenterPadding;
public Typeface mKeyTypeface = null;
public float mKeyLetterRatio = ResourceUtils.UNDEFINED_RATIO;
public int mKeyLetterSize = ResourceUtils.UNDEFINED_DIMENSION;
public float mKeyHintLetterRatio = ResourceUtils.UNDEFINED_RATIO;
public float mKeyShiftedLetterHintRatio = ResourceUtils.UNDEFINED_RATIO;
public int mDefaultRowHeight;
public int mDefaultKeyWidth;
public int mHorizontalGap;
public int mVerticalGap;
public int mMoreKeysTemplate;
public int mMaxMoreKeysKeyboardColumn;
public int GRID_WIDTH;
public int GRID_HEIGHT;
public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
public KeysCache mKeysCache;
public int mMostCommonKeyHeight = 0;
public int mMostCommonKeyWidth = 0;
public boolean mProximityCharsCorrectionEnabled;
public final TouchPositionCorrection mTouchPositionCorrection =
new TouchPositionCorrection();
protected void clearKeys() {
mKeys.clear();
mShiftKeys.clear();
clearHistogram();
}
public void onAddKey(final Key newKey) {
final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
if (!zeroWidthSpacer) {
mKeys.add(key);
updateHistogram(key);
}
if (key.mCode == Keyboard.CODE_SHIFT) {
mShiftKeys.add(key);
}
if (key.altCodeWhileTyping()) {
mAltCodeKeysWhileTyping.add(key);
}
}
private int mMaxHeightCount = 0;
private int mMaxWidthCount = 0;
private final SparseIntArray mHeightHistogram = new SparseIntArray();
private final SparseIntArray mWidthHistogram = new SparseIntArray();
private void clearHistogram() {
mMostCommonKeyHeight = 0;
mMaxHeightCount = 0;
mHeightHistogram.clear();
mMaxWidthCount = 0;
mMostCommonKeyWidth = 0;
mWidthHistogram.clear();
}
private static int updateHistogramCounter(final SparseIntArray histogram, final int key) {
final int index = histogram.indexOfKey(key);
final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
histogram.put(key, count);
return count;
}
private void updateHistogram(final Key key) {
final int height = key.mHeight + mVerticalGap;
final int heightCount = updateHistogramCounter(mHeightHistogram, height);
if (heightCount > mMaxHeightCount) {
mMaxHeightCount = heightCount;
mMostCommonKeyHeight = height;
}
final int width = key.mWidth + mHorizontalGap;
final int widthCount = updateHistogramCounter(mWidthHistogram, width);
if (widthCount > mMaxWidthCount) {
mMaxWidthCount = widthCount;
mMostCommonKeyWidth = width;
}
}
}

View file

@ -0,0 +1,154 @@
/*
* Copyright (C) 2012 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.internal;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.Xml;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResourceUtils;
import org.xmlpull.v1.XmlPullParser;
/**
* 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 {@link Keyboard}
* defines.
*/
public class KeyboardRow {
// keyWidth enum constants
private static final int KEYWIDTH_NOT_ENUM = 0;
private static final int KEYWIDTH_FILL_RIGHT = -1;
private final KeyboardParams mParams;
/** Default width of a key in this row. */
private float mDefaultKeyWidth;
/** Default height of a key in this row. */
public final int mRowHeight;
/** Default keyLabelFlags in this row. */
private int mDefaultKeyLabelFlags;
/** Default backgroundType for this row */
private int mDefaultBackgroundType;
private final int mCurrentY;
// Will be updated by {@link Key}'s constructor.
private float mCurrentX;
public KeyboardRow(final Resources res, final KeyboardParams params, final XmlPullParser parser,
final int y) {
mParams = params;
TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_rowHeight,
params.mBaseHeight, params.mDefaultRowHeight);
keyboardAttr.recycle();
TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
mDefaultKeyWidth = ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_keyWidth,
params.mBaseWidth, params.mDefaultKeyWidth);
mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
Key.BACKGROUND_TYPE_NORMAL);
keyAttr.recycle();
// TODO: Initialize this with <Row> attribute as backgroundType is done.
mDefaultKeyLabelFlags = 0;
mCurrentY = y;
mCurrentX = 0.0f;
}
public float getDefaultKeyWidth() {
return mDefaultKeyWidth;
}
public void setDefaultKeyWidth(final float defaultKeyWidth) {
mDefaultKeyWidth = defaultKeyWidth;
}
public int getDefaultKeyLabelFlags() {
return mDefaultKeyLabelFlags;
}
public void setDefaultKeyLabelFlags(final int keyLabelFlags) {
mDefaultKeyLabelFlags = keyLabelFlags;
}
public int getDefaultBackgroundType() {
return mDefaultBackgroundType;
}
public void setDefaultBackgroundType(final int backgroundType) {
mDefaultBackgroundType = backgroundType;
}
public void setXPos(final float keyXPos) {
mCurrentX = keyXPos;
}
public void advanceXPos(final float width) {
mCurrentX += width;
}
public int getKeyY() {
return mCurrentY;
}
public float getKeyX(final TypedArray keyAttr) {
final int keyboardRightEdge = mParams.mOccupiedWidth
- mParams.mHorizontalEdgesPadding;
if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
final float keyXPos = ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
if (keyXPos < 0) {
// If keyXPos is negative, the actual x-coordinate will be
// keyboardWidth + keyXPos.
// keyXPos shouldn't be less than mCurrentX because drawable area for this
// key starts at mCurrentX. Or, this key will overlaps the adjacent key on
// its left hand side.
return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
} else {
return keyXPos + mParams.mHorizontalEdgesPadding;
}
}
return mCurrentX;
}
public float getKeyWidth(final TypedArray keyAttr) {
return getKeyWidth(keyAttr, mCurrentX);
}
public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
final int widthType = ResourceUtils.getEnumValue(keyAttr,
R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
switch (widthType) {
case KEYWIDTH_FILL_RIGHT:
final int keyboardRightEdge =
mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
// If keyWidth is fillRight, the actual key width will be determined to fill
// out the area up to the right edge of the keyboard.
return keyboardRightEdge - keyXPos;
default: // KEYWIDTH_NOT_ENUM
return ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_keyWidth,
mParams.mBaseWidth, mDefaultKeyWidth);
}
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (C) 2012 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.internal;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.CollectionUtils;
import java.util.HashMap;
public class KeysCache {
private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
public void clear() {
mMap.clear();
}
public Key get(final Key key) {
final Key existingKey = mMap.get(key);
if (existingKey != null) {
// Reuse the existing element that equals to "key" without adding "key" to the map.
return existingKey;
}
mMap.put(key, key);
return key;
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2012 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.internal;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.StringUtils;
import java.util.Locale;
public class MoreKeySpec {
public final int mCode;
public final String mLabel;
public final String mOutputText;
public final int mIconId;
public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
final KeyboardCodesSet codesSet) {
mLabel = KeySpecParser.toUpperCaseOfStringForLocale(
KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
final int code = KeySpecParser.toUpperCaseOfCodeForLocale(
KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale);
if (code == Keyboard.CODE_UNSPECIFIED) {
// Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
// upper case representation ("SS").
mCode = Keyboard.CODE_OUTPUT_TEXT;
mOutputText = mLabel;
} else {
mCode = code;
mOutputText = KeySpecParser.toUpperCaseOfStringForLocale(
KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
}
mIconId = KeySpecParser.getIconId(moreKeySpec);
}
@Override
public String toString() {
final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
: KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
: Keyboard.printableCode(mCode));
if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
return output;
} else {
return label + "|" + output;
}
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2012 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.internal;
import com.android.inputmethod.latin.LatinImeLogger;
public class TouchPositionCorrection {
private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
public boolean mEnabled;
public float[] mXs;
public float[] mYs;
public float[] mRadii;
public void load(final String[] data) {
final int dataLength = data.length;
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
if (LatinImeLogger.sDBG) {
throw new RuntimeException(
"the size of touch position correction data is invalid");
}
return;
}
final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
mXs = new float[length];
mYs = new float[length];
mRadii = new float[length];
try {
for (int i = 0; i < dataLength; ++i) {
final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
final float value = Float.parseFloat(data[i]);
if (type == 0) {
mXs[index] = value;
} else if (type == 1) {
mYs[index] = value;
} else {
mRadii[index] = value;
}
}
} catch (NumberFormatException e) {
if (LatinImeLogger.sDBG) {
throw new RuntimeException(
"the number format for touch position correction data is invalid");
}
mXs = null;
mYs = null;
mRadii = null;
}
}
// TODO: Remove this method.
public void setEnabled(final boolean enabled) {
mEnabled = enabled;
}
public boolean isValid() {
return mEnabled && mXs != null && mYs != null && mRadii != null
&& mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
}
}

View file

@ -23,7 +23,9 @@ import android.graphics.drawable.Drawable;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.Utils;
@ -31,145 +33,149 @@ import com.android.inputmethod.latin.Utils;
public class MoreSuggestions extends Keyboard { public class MoreSuggestions extends Keyboard {
public static final int SUGGESTION_CODE_BASE = 1024; public static final int SUGGESTION_CODE_BASE = 1024;
MoreSuggestions(Builder.MoreSuggestionsParam params) { MoreSuggestions(final MoreSuggestionsParam params) {
super(params); super(params);
} }
public static class Builder extends Keyboard.Builder<Builder.MoreSuggestionsParam> { private static class MoreSuggestionsParam extends KeyboardParams {
private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
private static final int MAX_COLUMNS_IN_ROW = 3;
private int mNumRows;
public Drawable mDivider;
public int mDividerWidth;
public MoreSuggestionsParam() {
super();
}
public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
final int minWidth, final int maxRow, final MoreSuggestionsView view) {
clearKeys();
final Resources res = view.getContext().getResources();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
final int padding = (int) res.getDimension(
R.dimen.more_suggestions_key_horizontal_padding);
final Paint paint = view.newDefaultLabelPaint();
int row = 0;
int pos = fromPos, rowStartPos = fromPos;
final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
while (pos < size) {
final String word = suggestions.getWord(pos).toString();
// TODO: Should take care of text x-scaling.
mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
final int numColumn = pos - rowStartPos + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
if (numColumn > MAX_COLUMNS_IN_ROW
|| !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
if ((row + 1) >= maxRow) {
break;
}
mNumColumnsInRow[row] = pos - rowStartPos;
rowStartPos = pos;
row++;
}
mColumnOrders[pos] = pos - rowStartPos;
mRowNumbers[pos] = row;
pos++;
}
mNumColumnsInRow[row] = pos - rowStartPos;
mNumRows = row + 1;
mBaseWidth = mOccupiedWidth = Math.max(
minWidth, calcurateMaxRowWidth(fromPos, pos));
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
return pos - fromPos;
}
private boolean fitInWidth(final int startPos, final int endPos, final int width) {
for (int pos = startPos; pos < endPos; pos++) {
if (mWidths[pos] > width)
return false;
}
return true;
}
private int calcurateMaxRowWidth(final int startPos, final int endPos) {
int maxRowWidth = 0;
int pos = startPos;
for (int row = 0; row < mNumRows; row++) {
final int numColumnInRow = mNumColumnsInRow[row];
int maxKeyWidth = 0;
while (pos < endPos && mRowNumbers[pos] == row) {
maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
pos++;
}
maxRowWidth = Math.max(maxRowWidth,
maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
}
return maxRowWidth;
}
private static final int[][] COLUMN_ORDER_TO_NUMBER = {
{ 0, },
{ 1, 0, },
{ 2, 0, 1},
};
public int getNumColumnInRow(final int pos) {
return mNumColumnsInRow[mRowNumbers[pos]];
}
public int getColumnNumber(final int pos) {
final int columnOrder = mColumnOrders[pos];
final int numColumn = getNumColumnInRow(pos);
return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
}
public int getX(final int pos) {
final int columnNumber = getColumnNumber(pos);
return columnNumber * (getWidth(pos) + mDividerWidth);
}
public int getY(final int pos) {
final int row = mRowNumbers[pos];
return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
}
public int getWidth(final int pos) {
final int numColumnInRow = getNumColumnInRow(pos);
return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
}
public void markAsEdgeKey(final Key key, final int pos) {
final int row = mRowNumbers[pos];
if (row == 0)
key.markAsBottomEdge(this);
if (row == mNumRows - 1)
key.markAsTopEdge(this);
final int numColumnInRow = mNumColumnsInRow[row];
final int column = getColumnNumber(pos);
if (column == 0)
key.markAsLeftEdge(this);
if (column == numColumnInRow - 1)
key.markAsRightEdge(this);
}
}
public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
private final MoreSuggestionsView mPaneView; private final MoreSuggestionsView mPaneView;
private SuggestedWords mSuggestions; private SuggestedWords mSuggestions;
private int mFromPos; private int mFromPos;
private int mToPos; private int mToPos;
public static class MoreSuggestionsParam extends Keyboard.Params { public Builder(final MoreSuggestionsView paneView) {
private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
private static final int MAX_COLUMNS_IN_ROW = 3;
private int mNumRows;
public Drawable mDivider;
public int mDividerWidth;
public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth,
int maxRow, MoreSuggestionsView view) {
clearKeys();
final Resources res = view.getContext().getResources();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
final int padding = (int) res.getDimension(
R.dimen.more_suggestions_key_horizontal_padding);
final Paint paint = view.newDefaultLabelPaint();
int row = 0;
int pos = fromPos, rowStartPos = fromPos;
final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
while (pos < size) {
final String word = suggestions.getWord(pos).toString();
// TODO: Should take care of text x-scaling.
mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
final int numColumn = pos - rowStartPos + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
if (numColumn > MAX_COLUMNS_IN_ROW
|| !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
if ((row + 1) >= maxRow) {
break;
}
mNumColumnsInRow[row] = pos - rowStartPos;
rowStartPos = pos;
row++;
}
mColumnOrders[pos] = pos - rowStartPos;
mRowNumbers[pos] = row;
pos++;
}
mNumColumnsInRow[row] = pos - rowStartPos;
mNumRows = row + 1;
mBaseWidth = mOccupiedWidth = Math.max(
minWidth, calcurateMaxRowWidth(fromPos, pos));
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
return pos - fromPos;
}
private boolean fitInWidth(int startPos, int endPos, int width) {
for (int pos = startPos; pos < endPos; pos++) {
if (mWidths[pos] > width)
return false;
}
return true;
}
private int calcurateMaxRowWidth(int startPos, int endPos) {
int maxRowWidth = 0;
int pos = startPos;
for (int row = 0; row < mNumRows; row++) {
final int numColumnInRow = mNumColumnsInRow[row];
int maxKeyWidth = 0;
while (pos < endPos && mRowNumbers[pos] == row) {
maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
pos++;
}
maxRowWidth = Math.max(maxRowWidth,
maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
}
return maxRowWidth;
}
private static final int[][] COLUMN_ORDER_TO_NUMBER = {
{ 0, },
{ 1, 0, },
{ 2, 0, 1},
};
public int getNumColumnInRow(int pos) {
return mNumColumnsInRow[mRowNumbers[pos]];
}
public int getColumnNumber(int pos) {
final int columnOrder = mColumnOrders[pos];
final int numColumn = getNumColumnInRow(pos);
return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
}
public int getX(int pos) {
final int columnNumber = getColumnNumber(pos);
return columnNumber * (getWidth(pos) + mDividerWidth);
}
public int getY(int pos) {
final int row = mRowNumbers[pos];
return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
}
public int getWidth(int pos) {
final int numColumnInRow = getNumColumnInRow(pos);
return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
}
public void markAsEdgeKey(Key key, int pos) {
final int row = mRowNumbers[pos];
if (row == 0)
key.markAsBottomEdge(this);
if (row == mNumRows - 1)
key.markAsTopEdge(this);
final int numColumnInRow = mNumColumnsInRow[row];
final int column = getColumnNumber(pos);
if (column == 0)
key.markAsLeftEdge(this);
if (column == numColumnInRow - 1)
key.markAsRightEdge(this);
}
}
public Builder(MoreSuggestionsView paneView) {
super(paneView.getContext(), new MoreSuggestionsParam()); super(paneView.getContext(), new MoreSuggestionsParam());
mPaneView = paneView; mPaneView = paneView;
} }
public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth, public Builder layout(final SuggestedWords suggestions, final int fromPos,
int minWidth, int maxRow) { final int maxWidth, final int minWidth, final int maxRow) {
final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard(); final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
final int xmlId = R.xml.kbd_suggestions_pane_template; final int xmlId = R.xml.kbd_suggestions_pane_template;
load(xmlId, keyboard.mId); load(xmlId, keyboard.mId);
@ -183,25 +189,6 @@ public class MoreSuggestions extends Keyboard {
return this; return this;
} }
private static class Divider extends Key.Spacer {
private final Drawable mIcon;
public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width,
int height) {
super(params, x, y, width, height);
mIcon = icon;
}
@Override
public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
// KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
// constructor.
// TODO: Drawable itself should have an alpha value.
mIcon.setAlpha(128);
return mIcon;
}
}
@Override @Override
public MoreSuggestions build() { public MoreSuggestions build() {
final MoreSuggestionsParam params = mParams; final MoreSuggestionsParam params = mParams;
@ -228,4 +215,23 @@ public class MoreSuggestions extends Keyboard {
return new MoreSuggestions(params); return new MoreSuggestions(params);
} }
} }
private static class Divider extends Key.Spacer {
private final Drawable mIcon;
public Divider(final KeyboardParams params, final Drawable icon, final int x,
final int y, final int width, final int height) {
super(params, x, y, width, height);
mIcon = icon;
}
@Override
public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
// KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
// constructor.
// TODO: Drawable itself should have an alpha value.
mIcon.setAlpha(128);
return mIcon;
}
}
} }

View file

@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams; import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase { public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase {
private static final int WIDTH = 10; private static final int WIDTH = 10;

View file

@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams; import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
public class MoreKeysKeyboardBuilderTests extends AndroidTestCase { public class MoreKeysKeyboardBuilderTests extends AndroidTestCase {
private static final int WIDTH = 10; private static final int WIDTH = 10;

View file

@ -23,7 +23,6 @@ import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UN
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;