diff options
Diffstat (limited to 'java/src')
12 files changed, 648 insertions, 323 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index e74c968ff..1c165e323 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -16,8 +16,8 @@ package com.android.inputmethod.keyboard; -import com.android.inputmethod.keyboard.KeyboardParser.ParseException; import com.android.inputmethod.keyboard.KeyStyles.KeyStyle; +import com.android.inputmethod.keyboard.KeyboardParser.ParseException; import com.android.inputmethod.latin.R; import android.content.res.Resources; @@ -71,12 +71,9 @@ public class Key { /** Text to output when pressed. This can be multiple characters, like ".com" */ public final CharSequence mOutputText; /** Popup characters */ - public final CharSequence mPopupCharacters; - /** - * If this key pops up a mini keyboard, this is the resource id for the XML layout for that - * keyboard. - */ - public final int mPopupResId; + public final CharSequence[] mPopupCharacters; + /** Popup keyboard maximum column number */ + public final int mMaxPopupColumn; /** * Flags that specify the anchoring to edges of the keyboard for detecting touch events @@ -136,8 +133,13 @@ public class Key { android.R.attr.state_pressed }; - /** Create an empty key with no attributes. */ - public Key(Row row, char letter, int x, int y) { + private static final int[] DUMMY_CODES = { 0 }; + + /** + * Create an empty key with no attributes. + * This constructor is being used only for key in mini popup keyboard. + */ + public Key(Resources res, Row row, CharSequence popupCharacter, int x, int y) { mKeyboard = row.getKeyboard(); mHeight = row.mDefaultHeight - row.mVerticalGap; mGap = row.mDefaultHorizontalGap; @@ -150,18 +152,21 @@ public class Key { mModifier = false; mSticky = false; mRepeatable = false; - mOutputText = null; mPopupCharacters = null; - mPopupResId = 0; - mLabel = String.valueOf(letter); - mCodes = new int[] { letter }; + mMaxPopupColumn = 0; + final String popupSpecification = popupCharacter.toString(); + mLabel = PopupCharactersParser.getLabel(popupSpecification); + mOutputText = PopupCharactersParser.getOutputText(popupSpecification); + mCodes = PopupCharactersParser.getCodes(res, popupSpecification); + mIcon = PopupCharactersParser.getIcon(res, popupSpecification); // Horizontal gap is divided equally to both sides of the key. mX = x + mGap / 2; mY = y; } - /** Create a key with the given top-left coordinate and extract its attributes from - * the XML parser. + /** + * Create a key with the given top-left coordinate and extract its attributes from the XML + * parser. * @param res resources associated with the caller's context * @param row the row that this key belongs to. The row must already be attached to * a {@link Keyboard}. @@ -173,83 +178,84 @@ public class Key { KeyStyles keyStyles) { mKeyboard = row.getKeyboard(); - TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), + final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard); - mHeight = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_rowHeight, - mKeyboard.getKeyboardHeight(), row.mDefaultHeight) - row.mVerticalGap; - mGap = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_horizontalGap, - mKeyboard.getDisplayWidth(), row.mDefaultHorizontalGap); - mWidth = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_keyWidth, - mKeyboard.getDisplayWidth(), row.mDefaultWidth) - mGap; - a.recycle(); - - a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); - - final KeyStyle style; - if (a.hasValue(R.styleable.Keyboard_Key_keyStyle)) { - String styleName = a.getString(R.styleable.Keyboard_Key_keyStyle); - style = keyStyles.getKeyStyle(styleName); - if (style == null) - throw new ParseException("Unknown key style: " + styleName, parser); - } else { - style = keyStyles.getEmptyKeyStyle(); + try { + mHeight = KeyboardParser.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_rowHeight, + mKeyboard.getKeyboardHeight(), row.mDefaultHeight) - row.mVerticalGap; + mGap = KeyboardParser.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_horizontalGap, + mKeyboard.getDisplayWidth(), row.mDefaultHorizontalGap); + mWidth = KeyboardParser.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyWidth, + mKeyboard.getDisplayWidth(), row.mDefaultWidth) - mGap; + } finally { + keyboardAttr.recycle(); } // Horizontal gap is divided equally to both sides of the key. this.mX = x + mGap / 2; this.mY = y; - final CharSequence popupCharacters = style.getText(a, - R.styleable.Keyboard_Key_popupCharacters); - final int popupResId = style.getResourceId(a, R.styleable.Keyboard_Key_popupKeyboard, 0); - // We should not display mini keyboard when both popupResId and popupCharacters are - // specified but popupCharacters is empty string. - if (popupResId != 0 && popupCharacters != null && popupCharacters.length() == 0) { - mPopupResId = 0; - mPopupCharacters = null; - } else { - mPopupResId = popupResId; - mPopupCharacters = popupCharacters; - } - - mRepeatable = style.getBoolean(a, R.styleable.Keyboard_Key_isRepeatable, false); - mModifier = style.getBoolean(a, R.styleable.Keyboard_Key_isModifier, false); - mSticky = style.getBoolean(a, R.styleable.Keyboard_Key_isSticky, false); - mEdgeFlags = style.getFlag(a, R.styleable.Keyboard_Key_keyEdgeFlags, 0) - | row.mRowEdgeFlags; - - mPreviewIcon = style.getDrawable(a, R.styleable.Keyboard_Key_iconPreview); - Keyboard.setDefaultBounds(mPreviewIcon); - mIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyIcon); - Keyboard.setDefaultBounds(mIcon); - mHintIcon = style.getDrawable(a, R.styleable.Keyboard_Key_keyHintIcon); - Keyboard.setDefaultBounds(mHintIcon); - mManualTemporaryUpperCaseHintIcon = style.getDrawable(a, - R.styleable.Keyboard_Key_manualTemporaryUpperCaseHintIcon); - Keyboard.setDefaultBounds(mManualTemporaryUpperCaseHintIcon); - - mLabel = style.getText(a, R.styleable.Keyboard_Key_keyLabel); - mLabelOption = style.getFlag(a, R.styleable.Keyboard_Key_keyLabelOption, 0); - mManualTemporaryUpperCaseCode = style.getInt(a, - R.styleable.Keyboard_Key_manualTemporaryUpperCaseCode, 0); - mOutputText = style.getText(a, R.styleable.Keyboard_Key_keyOutputText); - // Choose the first letter of the label as primary code if not specified. - final int[] codes = style.getIntArray(a, R.styleable.Keyboard_Key_codes); - if (codes == null && !TextUtils.isEmpty(mLabel)) { - mCodes = new int[] { mLabel.charAt(0) }; - } else { - mCodes = codes; - } + final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Key); + try { + final KeyStyle style; + if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) { + String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle); + style = keyStyles.getKeyStyle(styleName); + if (style == null) + throw new ParseException("Unknown key style: " + styleName, parser); + } else { + style = keyStyles.getEmptyKeyStyle(); + } - final Drawable shiftedIcon = style.getDrawable(a, - R.styleable.Keyboard_Key_shiftedIcon); - if (shiftedIcon != null) - mKeyboard.getShiftedIcons().put(this, shiftedIcon); + mPopupCharacters = style.getTextArray(keyAttr, + R.styleable.Keyboard_Key_popupCharacters); + mMaxPopupColumn = style.getInt(keyboardAttr, + R.styleable.Keyboard_Key_maxPopupKeyboardColumn, + mKeyboard.getMaxPopupKeyboardColumn()); + + mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false); + mModifier = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier, false); + mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false); + mEdgeFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyEdgeFlags, 0) + | row.mRowEdgeFlags; + + mPreviewIcon = style.getDrawable(keyAttr, R.styleable.Keyboard_Key_iconPreview); + Keyboard.setDefaultBounds(mPreviewIcon); + mIcon = style.getDrawable(keyAttr, R.styleable.Keyboard_Key_keyIcon); + Keyboard.setDefaultBounds(mIcon); + mHintIcon = style.getDrawable(keyAttr, R.styleable.Keyboard_Key_keyHintIcon); + Keyboard.setDefaultBounds(mHintIcon); + mManualTemporaryUpperCaseHintIcon = style.getDrawable(keyAttr, + R.styleable.Keyboard_Key_manualTemporaryUpperCaseHintIcon); + Keyboard.setDefaultBounds(mManualTemporaryUpperCaseHintIcon); + + mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel); + mLabelOption = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption, 0); + mManualTemporaryUpperCaseCode = style.getInt(keyAttr, + R.styleable.Keyboard_Key_manualTemporaryUpperCaseCode, 0); + mOutputText = style.getText(keyAttr, R.styleable.Keyboard_Key_keyOutputText); + // Choose the first letter of the label as primary code if not + // specified. + final int[] codes = style.getIntArray(keyAttr, R.styleable.Keyboard_Key_codes); + if (codes == null && !TextUtils.isEmpty(mLabel)) { + mCodes = new int[] { mLabel.charAt(0) }; + } else if (codes != null) { + mCodes = codes; + } else { + mCodes = DUMMY_CODES; + } - a.recycle(); + final Drawable shiftedIcon = style.getDrawable(keyAttr, + R.styleable.Keyboard_Key_shiftedIcon); + if (shiftedIcon != null) + mKeyboard.getShiftedIcons().put(this, shiftedIcon); + } finally { + keyAttr.recycle(); + } } public Drawable getIcon() { diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java index e3b107160..9e87816bc 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java @@ -25,11 +25,13 @@ import android.graphics.drawable.Drawable; import android.util.Log; import android.util.TypedValue; +import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; public class KeyStyles { private static final String TAG = "KeyStyles"; + private static final boolean DEBUG = false; private final HashMap<String, DeclaredKeyStyle> mStyles = new HashMap<String, DeclaredKeyStyle>(); @@ -37,15 +39,15 @@ public class KeyStyles { public interface KeyStyle { public int[] getIntArray(TypedArray a, int index); + public CharSequence[] getTextArray(TypedArray a, int index); public Drawable getDrawable(TypedArray a, int index); public CharSequence getText(TypedArray a, int index); - public int getResourceId(TypedArray a, int index, int defaultValue); public int getInt(TypedArray a, int index, int defaultValue); public int getFlag(TypedArray a, int index, int defaultValue); public boolean getBoolean(TypedArray a, int index, boolean defaultValue); } - public static class EmptyKeyStyle implements KeyStyle { + /* package */ static class EmptyKeyStyle implements KeyStyle { private EmptyKeyStyle() { // Nothing to do. } @@ -56,6 +58,11 @@ public class KeyStyles { } @Override + public CharSequence[] getTextArray(TypedArray a, int index) { + return parseTextArray(a, index); + } + + @Override public Drawable getDrawable(TypedArray a, int index) { return a.getDrawable(index); } @@ -66,11 +73,6 @@ public class KeyStyles { } @Override - public int getResourceId(TypedArray a, int index, int defaultValue) { - return a.getResourceId(index, defaultValue); - } - - @Override public int getInt(TypedArray a, int index, int defaultValue) { return a.getInt(index, defaultValue); } @@ -85,19 +87,71 @@ public class KeyStyles { return a.getBoolean(index, defaultValue); } + protected static CharSequence[] parseTextArray(TypedArray a, int index) { + if (!a.hasValue(index)) + return null; + final CharSequence text = a.getText(index); + return parseCsvText(text); + } + + /* package */ static CharSequence[] parseCsvText(CharSequence text) { + final int size = text.length(); + if (size == 0) return null; + if (size == 1) return new CharSequence[] { text }; + final StringBuilder sb = new StringBuilder(); + ArrayList<CharSequence> list = null; + int start = 0; + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == ',') { + if (list == null) list = new ArrayList<CharSequence>(); + if (sb.length() == 0) { + list.add(text.subSequence(start, pos)); + } else { + list.add(sb.toString()); + sb.setLength(0); + } + start = pos + 1; + continue; + } else if (c == '\\') { + if (start == pos) { + // Skip escape character at the beginning of the value. + start++; + pos++; + } else { + if (start < pos && sb.length() == 0) + sb.append(text.subSequence(start, pos)); + pos++; + if (pos < size) + sb.append(text.charAt(pos)); + } + } else if (sb.length() > 0) { + sb.append(c); + } + } + if (list == null) { + return new CharSequence[] { sb.length() > 0 ? sb : text.subSequence(start, size) }; + } else { + list.add(sb.length() > 0 ? sb : text.subSequence(start, size)); + return list.toArray(new CharSequence[list.size()]); + } + } + protected static int[] parseIntArray(TypedArray a, int index) { + if (!a.hasValue(index)) + return null; TypedValue v = new TypedValue(); a.getValue(index, v); if (v.type == TypedValue.TYPE_INT_DEC || v.type == TypedValue.TYPE_INT_HEX) { return new int[] { v.data }; } else if (v.type == TypedValue.TYPE_STRING) { - return parseCSV(v.string.toString()); + return parseCsvInt(v.string.toString()); } else { return null; } } - private static int[] parseCSV(String value) { + /* package */ static int[] parseCsvInt(String value) { int count = 0; int lastIndex = 0; if (value.length() > 0) { @@ -110,17 +164,13 @@ public class KeyStyles { count = 0; StringTokenizer st = new StringTokenizer(value, ","); while (st.hasMoreTokens()) { - try { - values[count++] = Integer.parseInt(st.nextToken()); - } catch (NumberFormatException nfe) { - Log.w(TAG, "Error parsing integer CSV " + value); - } + values[count++] = Integer.parseInt(st.nextToken()); } return values; } } - public static class DeclaredKeyStyle extends EmptyKeyStyle { + private static class DeclaredKeyStyle extends EmptyKeyStyle { private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>(); @Override @@ -130,6 +180,12 @@ public class KeyStyles { } @Override + public CharSequence[] getTextArray(TypedArray a, int index) { + return a.hasValue(index) + ? super.getTextArray(a, index) : (CharSequence[])mAttributes.get(index); + } + + @Override public Drawable getDrawable(TypedArray a, int index) { return a.hasValue(index) ? super.getDrawable(a, index) : (Drawable)mAttributes.get(index); @@ -142,9 +198,9 @@ public class KeyStyles { } @Override - public int getResourceId(TypedArray a, int index, int defaultValue) { + public int getInt(TypedArray a, int index, int defaultValue) { final Integer value = (Integer)mAttributes.get(index); - return super.getResourceId(a, index, (value != null) ? value : defaultValue); + return super.getInt(a, index, (value != null) ? value : defaultValue); } @Override @@ -163,20 +219,21 @@ public class KeyStyles { super(); } - private void parseKeyStyleAttributes(TypedArray a) { + private void parseKeyStyleAttributes(TypedArray keyAttr) { // TODO: Currently not all Key attributes can be declared as style. - readIntArray(a, R.styleable.Keyboard_Key_codes); - readText(a, R.styleable.Keyboard_Key_keyLabel); - readFlag(a, R.styleable.Keyboard_Key_keyLabelOption); - readText(a, R.styleable.Keyboard_Key_keyOutputText); - readDrawable(a, R.styleable.Keyboard_Key_keyIcon); - readDrawable(a, R.styleable.Keyboard_Key_iconPreview); - readDrawable(a, R.styleable.Keyboard_Key_keyHintIcon); - readDrawable(a, R.styleable.Keyboard_Key_shiftedIcon); - readResourceId(a, R.styleable.Keyboard_Key_popupKeyboard); - readBoolean(a, R.styleable.Keyboard_Key_isModifier); - readBoolean(a, R.styleable.Keyboard_Key_isSticky); - readBoolean(a, R.styleable.Keyboard_Key_isRepeatable); + readIntArray(keyAttr, R.styleable.Keyboard_Key_codes); + readText(keyAttr, R.styleable.Keyboard_Key_keyLabel); + readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption); + readTextArray(keyAttr, R.styleable.Keyboard_Key_popupCharacters); + readInt(keyAttr, R.styleable.Keyboard_Key_maxPopupKeyboardColumn); + readText(keyAttr, R.styleable.Keyboard_Key_keyOutputText); + readDrawable(keyAttr, R.styleable.Keyboard_Key_keyIcon); + readDrawable(keyAttr, R.styleable.Keyboard_Key_iconPreview); + readDrawable(keyAttr, R.styleable.Keyboard_Key_keyHintIcon); + readDrawable(keyAttr, R.styleable.Keyboard_Key_shiftedIcon); + readBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier); + readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky); + readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable); } private void readDrawable(TypedArray a, int index) { @@ -189,9 +246,9 @@ public class KeyStyles { mAttributes.put(index, a.getText(index)); } - private void readResourceId(TypedArray a, int index) { + private void readInt(TypedArray a, int index) { if (a.hasValue(index)) - mAttributes.put(index, a.getResourceId(index, 0)); + mAttributes.put(index, a.getInt(index, 0)); } private void readFlag(TypedArray a, int index) { @@ -206,11 +263,15 @@ public class KeyStyles { } private void readIntArray(TypedArray a, int index) { - if (a.hasValue(index)) { - final int[] value = parseIntArray(a, index); - if (value != null) - mAttributes.put(index, value); - } + final int[] value = parseIntArray(a, index); + if (value != null) + mAttributes.put(index, value); + } + + private void readTextArray(TypedArray a, int index) { + final CharSequence[] value = parseTextArray(a, index); + if (value != null) + mAttributes.put(index, value); } private void addParent(DeclaredKeyStyle parentStyle) { @@ -218,15 +279,17 @@ public class KeyStyles { } } - public void parseKeyStyleAttributes(TypedArray a, TypedArray keyAttrs, + public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs, XmlResourceParser parser) { - String styleName = a.getString(R.styleable.Keyboard_KeyStyle_styleName); + String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName); + if (DEBUG) Log.d(TAG, String.format("<%s styleName=%s />", + KeyboardParser.TAG_KEY_STYLE, styleName)); if (mStyles.containsKey(styleName)) throw new ParseException("duplicate key style declared: " + styleName, parser); final DeclaredKeyStyle style = new DeclaredKeyStyle(); - if (a.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) { - String parentStyle = a.getString( + if (keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) { + String parentStyle = keyStyleAttr.getString( R.styleable.Keyboard_KeyStyle_parentStyle); final DeclaredKeyStyle parent = mStyles.get(parentStyle); if (parent == null) diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 7641b4d7a..a20c86142 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -22,7 +22,6 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.res.Resources; -import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.util.Log; @@ -92,6 +91,12 @@ public class Keyboard { /** Default gap between rows */ private int mDefaultVerticalGap; + /** Popup keyboard template */ + private int mPopupKeyboardResId; + + /** Maximum column for popup keyboard */ + private int mMaxPopupColumn; + /** List of shift keys in this keyboard and its icons and state */ private final List<Key> mShiftKeys = new ArrayList<Key>(); private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); @@ -108,10 +113,10 @@ public class Keyboard { private int mTotalHeight; /** - * Total width of the keyboard, including left side gaps and keys, but not any gaps on the - * right side. + * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any + * gaps on the right side. */ - private int mTotalWidth; + private int mMinWidth; /** List of keys in this keyboard */ private final List<Key> mKeys = new ArrayList<Key>(); @@ -144,27 +149,9 @@ public class Keyboard { * Creates a keyboard from the given xml key layout file. * @param context the application or service context * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. - */ - public Keyboard(Context context, int xmlLayoutResId) { - this(context, xmlLayoutResId, null); - } - - /** - * Creates a keyboard from the given keyboard identifier. - * @param context the application or service context - * @param id keyboard identifier - */ - public Keyboard(Context context, KeyboardId id) { - this(context, id.getXmlId(), id); - } - - /** - * Creates a keyboard from the given xml key layout file. - * @param context the application or service context - * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. * @param id keyboard identifier */ - private Keyboard(Context context, int xmlLayoutResId, KeyboardId id) { + public Keyboard(Context context, int xmlLayoutResId, KeyboardId id) { this(context, xmlLayoutResId, id, context.getResources().getDisplayMetrics().widthPixels, context.getResources().getDisplayMetrics().heightPixels); @@ -188,49 +175,6 @@ public class Keyboard { loadKeyboard(context, xmlLayoutResId); } - /** - * <p>Creates a blank keyboard from the given resource file and populates it with the specified - * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. - * </p> - * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as - * possible in each row.</p> - * @param context the application or service context - * @param layoutTemplateResId the layout template file, containing no keys. - * @param characters the list of characters to display on the keyboard. One key will be created - * for each character. - * @param columns the number of columns of keys to display. If this number is greater than the - * number of keys that can fit in a row, it will be ignored. If this number is -1, the - * keyboard will fit as many keys as possible in each row. - */ - public Keyboard(Context context, int layoutTemplateResId, - CharSequence characters, int columns, int horizontalPadding) { - this(context, layoutTemplateResId); - int x = 0; - int y = 0; - int column = 0; - mTotalWidth = 0; - - final Row row = new Row(this); - final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; - for (int i = 0; i < characters.length(); i++) { - char c = characters.charAt(i); - if (column >= maxColumns - || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { - x = 0; - y += mDefaultVerticalGap + mDefaultHeight; - column = 0; - } - final Key key = new Key(row, c, x, y); - column++; - x += key.mWidth + key.mGap; - mKeys.add(key); - if (x > mTotalWidth) { - mTotalWidth = x; - } - } - mTotalHeight = y + mDefaultHeight; - } - public List<Key> getKeys() { return mKeys; } @@ -277,8 +221,16 @@ public class Keyboard { return mTotalHeight; } + public void setHeight(int height) { + mTotalHeight = height; + } + public int getMinWidth() { - return mTotalWidth; + return mMinWidth; + } + + public void setMinWidth(int minWidth) { + mMinWidth = minWidth; } public int getDisplayHeight() { @@ -297,6 +249,22 @@ public class Keyboard { mKeyboardHeight = height; } + public int getPopupKeyboardResId() { + return mPopupKeyboardResId; + } + + public void setPopupKeyboardResId(int resId) { + mPopupKeyboardResId = resId; + } + + public int getMaxPopupKeyboardColumn() { + return mMaxPopupColumn; + } + + public void setMaxPopupKeyboardColumn(int column) { + mMaxPopupColumn = column; + } + public List<Key> getShiftKeys() { return mShiftKeys; } @@ -429,24 +397,12 @@ public class Keyboard { return EMPTY_INT_ARRAY; } - // TODO should be private - protected Row createRowFromXml(Resources res, XmlResourceParser parser) { - return new Row(res, this, parser); - } - - // TODO should be private - protected Key createKeyFromXml(Resources res, Row parent, int x, int y, - XmlResourceParser parser, KeyStyles keyStyles) { - return new Key(res, parent, x, y, parser, keyStyles); - } - private void loadKeyboard(Context context, int xmlLayoutResId) { try { - final Resources res = context.getResources(); - KeyboardParser parser = new KeyboardParser(this, res); - parser.parseKeyboard(res.getXml(xmlLayoutResId)); - // mTotalWidth is the width of this keyboard which is maximum width of row. - mTotalWidth = parser.getMaxRowWidth(); + KeyboardParser parser = new KeyboardParser(this, context.getResources()); + parser.parseKeyboard(xmlLayoutResId); + // mMinWidth is the width of this keyboard which is maximum width of row. + mMinWidth = parser.getMaxRowWidth(); mTotalHeight = parser.getTotalHeight(); } catch (XmlPullParserException e) { Log.w(TAG, "keyboard XML parse error: " + e); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 883d2175c..f6577e747 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -46,12 +46,13 @@ public class KeyboardId { public final boolean mHasVoiceKey; public final int mImeOptions; public final boolean mEnableShiftLock; + public final String mXmlName; private final int mHashCode; - public KeyboardId(Locale locale, int orientation, int mode, - int xmlId, int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, - boolean hasVoiceKey, int imeOptions, boolean enableShiftLock) { + public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int mode, + int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey, + int imeOptions, boolean enableShiftLock) { this.mLocale = locale; this.mOrientation = orientation; this.mMode = mode; @@ -64,6 +65,7 @@ public class KeyboardId { this.mImeOptions = imeOptions & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION); this.mEnableShiftLock = enableShiftLock; + this.mXmlName = xmlName; this.mHashCode = Arrays.hashCode(new Object[] { locale, @@ -120,12 +122,12 @@ public class KeyboardId { @Override public String toString() { - return String.format("[%s %s %5s imeOptions=0x%08x xml=0x%08x %s%s%s%s%s]", + return String.format("[%s.xml %s %s %s imeOptions=%s %s%s%s%s%s]", + mXmlName, mLocale, (mOrientation == 1 ? "port" : "land"), modeName(mMode), - mImeOptions, - mXmlId, + imeOptionsName(mImeOptions), colorSchemeName(mColorScheme), (mHasSettingsKey ? " hasSettingsKey" : ""), (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), @@ -133,7 +135,7 @@ public class KeyboardId { (mEnableShiftLock ? " enableShiftLock" : "")); } - private static String modeName(int mode) { + public static String modeName(int mode) { switch (mode) { case MODE_TEXT: return "text"; case MODE_URL: return "url"; @@ -146,11 +148,33 @@ public class KeyboardId { return null; } - private static String colorSchemeName(int colorScheme) { + public static String colorSchemeName(int colorScheme) { switch (colorScheme) { case KeyboardView.COLOR_SCHEME_WHITE: return "white"; case KeyboardView.COLOR_SCHEME_BLACK: return "black"; } return null; } + + public static String imeOptionsName(int imeOptions) { + if (imeOptions == -1) return null; + final int actionNo = imeOptions & EditorInfo.IME_MASK_ACTION; + final String action; + switch (actionNo) { + case EditorInfo.IME_ACTION_UNSPECIFIED: action = "actionUnspecified"; break; + case EditorInfo.IME_ACTION_NONE: action = "actionNone"; break; + case EditorInfo.IME_ACTION_GO: action = "actionGo"; break; + case EditorInfo.IME_ACTION_SEARCH: action = "actionSearch"; break; + case EditorInfo.IME_ACTION_SEND: action = "actionSend"; break; + case EditorInfo.IME_ACTION_DONE: action = "actionDone"; break; + case EditorInfo.IME_ACTION_PREVIOUS: action = "actionPrevious"; break; + default: action = "actionUnknown(" + actionNo + ")"; break; + } + if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { + return "flagNoEnterAction|" + action; + } else { + return action; + } + } } + diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index 8a7d67451..ed59b8be5 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -30,6 +30,7 @@ import android.util.Xml; import android.view.InflateException; import java.io.IOException; +import java.util.Arrays; import java.util.List; /** @@ -103,7 +104,7 @@ import java.util.List; public class KeyboardParser { private static final String TAG = "KeyboardParser"; - private static final boolean DEBUG_TAG = false; + private static final boolean DEBUG = false; // Keyboard XML Tags private static final String TAG_KEYBOARD = "Keyboard"; @@ -115,7 +116,7 @@ public class KeyboardParser { private static final String TAG_SWITCH = "switch"; private static final String TAG_CASE = "case"; private static final String TAG_DEFAULT = "default"; - private static final String TAG_KEY_STYLE = "key-style"; + public static final String TAG_KEY_STYLE = "key-style"; private final Keyboard mKeyboard; private final Resources mResources; @@ -140,13 +141,13 @@ public class KeyboardParser { return mTotalHeight; } - public void parseKeyboard(XmlResourceParser parser) - throws XmlPullParserException, IOException { + public void parseKeyboard(int resId) throws XmlPullParserException, IOException { + if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mKeyboard.mId)); + final XmlResourceParser parser = mResources.getXml(resId); int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { if (event == XmlPullParser.START_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseKeyboard", tag, false); if (TAG_KEYBOARD.equals(tag)) { parseKeyboardAttributes(parser); parseKeyboardContent(parser, mKeyboard.getKeys()); @@ -160,28 +161,38 @@ public class KeyboardParser { private void parseKeyboardAttributes(XmlResourceParser parser) { final Keyboard keyboard = mKeyboard; - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + final TypedArray keyboardAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard); - final int displayHeight = keyboard.getDisplayHeight(); - final int keyboardHeight = (int)a.getDimension( - R.styleable.Keyboard_keyboardHeight, displayHeight / 2); - final int maxKeyboardHeight = getDimensionOrFraction(a, - R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2); - // Keyboard height will not exceed maxKeyboardHeight. - final int height = Math.min(keyboardHeight, maxKeyboardHeight); - final int width = keyboard.getDisplayWidth(); - - keyboard.setKeyboardHeight(height); - keyboard.setKeyWidth(getDimensionOrFraction(a, - R.styleable.Keyboard_keyWidth, width, width / 10)); - keyboard.setRowHeight(getDimensionOrFraction(a, - R.styleable.Keyboard_rowHeight, height, 50)); - keyboard.setHorizontalGap(getDimensionOrFraction(a, - R.styleable.Keyboard_horizontalGap, width, 0)); - keyboard.setVerticalGap(getDimensionOrFraction(a, - R.styleable.Keyboard_verticalGap, height, 0)); - a.recycle(); - if (DEBUG_TAG) Log.d(TAG, "id=" + keyboard.mId); + final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Key); + try { + final int displayHeight = keyboard.getDisplayHeight(); + final int keyboardHeight = (int)keyboardAttr.getDimension( + R.styleable.Keyboard_keyboardHeight, displayHeight / 2); + final int maxKeyboardHeight = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2); + // Keyboard height will not exceed maxKeyboardHeight. + final int height = Math.min(keyboardHeight, maxKeyboardHeight); + final int width = keyboard.getDisplayWidth(); + + keyboard.setKeyboardHeight(height); + keyboard.setKeyWidth(getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyWidth, width, width / 10)); + keyboard.setRowHeight(getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_rowHeight, height, 50)); + keyboard.setHorizontalGap(getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_horizontalGap, width, 0)); + keyboard.setVerticalGap(getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_verticalGap, height, 0)); + keyboard.setPopupKeyboardResId(keyboardAttr.getResourceId( + R.styleable.Keyboard_popupKeyboardTemplate, 0)); + + keyboard.setMaxPopupKeyboardColumn(keyAttr.getInt( + R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5)); + } finally { + keyAttr.recycle(); + keyboardAttr.recycle(); + } } private void parseKeyboardContent(XmlResourceParser parser, List<Key> keys) @@ -190,9 +201,9 @@ public class KeyboardParser { while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { if (event == XmlPullParser.START_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseKeyboardContent", tag, keys == null); if (TAG_ROW.equals(tag)) { - Row row = mKeyboard.createRowFromXml(mResources, parser); + Row row = new Row(mResources, mKeyboard, parser); + if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW)); if (keys != null) startRow(row); parseRowContent(parser, row, keys); @@ -207,13 +218,12 @@ public class KeyboardParser { } } else if (event == XmlPullParser.END_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugEndTag("parseKeyboardContent", tag, keys == null); if (TAG_KEYBOARD.equals(tag)) { endKeyboard(mKeyboard.getVerticalGap()); break; - } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) { - break; - } else if (TAG_MERGE.equals(tag)) { + } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) + || TAG_MERGE.equals(tag)) { + if (DEBUG) Log.d(TAG, String.format("</%s>", tag)); break; } else if (TAG_KEY_STYLE.equals(tag)) { continue; @@ -230,7 +240,6 @@ public class KeyboardParser { while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { if (event == XmlPullParser.START_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseRowContent", tag, keys == null); if (TAG_KEY.equals(tag)) { parseKey(parser, row, keys); } else if (TAG_SPACER.equals(tag)) { @@ -246,14 +255,14 @@ public class KeyboardParser { } } else if (event == XmlPullParser.END_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); if (TAG_ROW.equals(tag)) { + if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_ROW)); if (keys != null) endRow(); break; - } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)) { - break; - } else if (TAG_MERGE.equals(tag)) { + } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) + || TAG_MERGE.equals(tag)) { + if (DEBUG) Log.d(TAG, String.format("</%s>", tag)); break; } else if (TAG_KEY_STYLE.equals(tag)) { continue; @@ -269,8 +278,10 @@ public class KeyboardParser { if (keys == null) { checkEndTag(TAG_KEY, parser); } else { - Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser, - mKeyStyles); + Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles); + if (DEBUG) Log.d(TAG, String.format("<%s keyLabel=%s codes=%s popupCharacters=%s />", + TAG_KEY, key.mLabel, Arrays.toString(key.mCodes), + Arrays.toString(key.mPopupCharacters))); checkEndTag(TAG_KEY, parser); keys.add(key); if (key.mCodes[0] == Keyboard.CODE_SHIFT) @@ -286,6 +297,7 @@ public class KeyboardParser { if (keys == null) { checkEndTag(TAG_SPACER, parser); } else { + if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER)); final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard); final int gap = getDimensionOrFraction(a, R.styleable.Keyboard_horizontalGap, @@ -320,7 +332,8 @@ public class KeyboardParser { checkEndTag(TAG_INCLUDE, parser); if (keyboardLayout == 0) throw new ParseException("No keyboardLayout attribute in <include/>", parser); - if (DEBUG_TAG) Log.d(TAG, String.format(" keyboardLayout=0x%08x", keyboardLayout)); + if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />", + TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout))); parseMerge(mResources.getLayout(keyboardLayout), row, keys); } } @@ -331,7 +344,6 @@ public class KeyboardParser { while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { if (event == XmlPullParser.START_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseMerge", tag, keys == null); if (TAG_MERGE.equals(tag)) { if (row == null) { parseKeyboardContent(parser, keys); @@ -359,13 +371,12 @@ public class KeyboardParser { private void parseSwitchInternal(XmlResourceParser parser, Row row, List<Key> keys) throws XmlPullParserException, IOException { + if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mKeyboard.mId)); boolean selected = false; int event; - if (DEBUG_TAG) Log.d(TAG, "parseSwitchInternal: id=" + mKeyboard.mId); while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { if (event == XmlPullParser.START_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugStartTag("parseSwitchInternal", tag, keys == null); if (TAG_CASE.equals(tag)) { selected |= parseCase(parser, row, selected ? null : keys); } else if (TAG_DEFAULT.equals(tag)) { @@ -375,8 +386,8 @@ public class KeyboardParser { } } else if (event == XmlPullParser.END_TAG) { final String tag = parser.getName(); - if (DEBUG_TAG) debugEndTag("parseRowContent", tag, keys == null); if (TAG_SWITCH.equals(tag)) { + if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_SWITCH)); break; } else { throw new IllegalEndTag(parser, TAG_KEY); @@ -426,20 +437,17 @@ public class KeyboardParser { final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched && voiceKeyMatched && colorSchemeMatched && imeOptionsMatched; - if (DEBUG_TAG) { - Log.d(TAG, String.format( - "parseCaseCondition: %s%s%s%s%s%s%s", - Boolean.toString(selected).toUpperCase(), - debugInteger(a, R.styleable.Keyboard_Case_mode, "mode"), - debugBoolean(a, R.styleable.Keyboard_Case_hasSettingsKey, - "hasSettingsKey"), - debugBoolean(a, R.styleable.Keyboard_Case_voiceKeyEnabled, - "voiceKeyEnabled"), - debugBoolean(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), - debugInteger(viewAttr, R.styleable.KeyboardView_colorScheme, - "colorScheme"), - debugInteger(a, R.styleable.Keyboard_Case_imeOptions, "imeOptions"))); - } + if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s> %s", TAG_CASE, + textAttr(KeyboardId.modeName( + a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"), + textAttr(KeyboardId.colorSchemeName( + a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"), + booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"), + booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), + booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), + textAttr(KeyboardId.imeOptionsName( + a.getInt(R.styleable.Keyboard_Case_imeOptions, -1)), "imeOptions"), + Boolean.toString(selected))); return selected; } finally { @@ -462,6 +470,7 @@ public class KeyboardParser { private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys) throws XmlPullParserException, IOException { + if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT)); if (row == null) { parseKeyboardContent(parser, keys); } else { @@ -471,18 +480,18 @@ public class KeyboardParser { } private void parseKeyStyle(XmlResourceParser parser, List<Key> keys) { - TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_KeyStyle); TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); try { - if (!a.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) + if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) throw new ParseException("<" + TAG_KEY_STYLE + "/> needs styleName attribute", parser); if (keys != null) - mKeyStyles.parseKeyStyleAttributes(a, keyAttrs, parser); + mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); } finally { - a.recycle(); + keyStyleAttr.recycle(); keyAttrs.recycle(); } } @@ -561,19 +570,11 @@ public class KeyboardParser { } } - private static void debugStartTag(String title, String tag, boolean skip) { - Log.d(TAG, title + ": <" + tag + ">" + (skip ? " skip" : "")); - } - - private static void debugEndTag(String title, String tag, boolean skip) { - Log.d(TAG, title + ": </" + tag + ">" + (skip ? " skip" : "")); - } - - private static String debugInteger(TypedArray a, int index, String name) { - return a.hasValue(index) ? " " + name + "=" + a.getInt(index, 0) : ""; + private static String textAttr(String value, String name) { + return value != null ? String.format(" %s=%s", name, value) : ""; } - private static String debugBoolean(TypedArray a, int index, String name) { - return a.hasValue(index) ? " " + name + "=" + a.getBoolean(index, false) : ""; + private static String booleanAttr(TypedArray a, int index, String name) { + return a.hasValue(index) ? String.format(" %s=%s", name, a.getBoolean(index, false)) : ""; } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index cd57db360..17d01f89f 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -115,7 +115,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private void makeSymbolsKeyboardIds() { final Locale locale = mSubtypeSwitcher.getInputLocale(); - final int orientation = mInputMethodService.getResources().getConfiguration().orientation; + final Resources res = mInputMethodService.getResources(); + final int orientation = res.getConfiguration().orientation; final int mode = mMode; final int colorScheme = getColorScheme(); final boolean hasSettingsKey = mHasSettingsKey; @@ -129,12 +130,14 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // "more" and "locked more" key labels. To achieve these behavior, we should initialize // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard" // respectively here for xlarge device's layout switching. - mSymbolsId = new KeyboardId(locale, orientation, mode, - mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols, - colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); - mSymbolsShiftedId = new KeyboardId(locale, orientation, mode, - mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift, - colorScheme, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); + int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols; + mSymbolsId = new KeyboardId( + res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme, + hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); + xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift; + mSymbolsShiftedId = new KeyboardId( + res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme, + hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); } private boolean hasVoiceKey(boolean isSymbols) { @@ -230,9 +233,11 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha enableShiftLock = true; } } - final int orientation = mInputMethodService.getResources().getConfiguration().orientation; + final Resources res = mInputMethodService.getResources(); + final int orientation = res.getConfiguration().orientation; final Locale locale = mSubtypeSwitcher.getInputLocale(); - return new KeyboardId(locale, orientation, mode, xmlId, charColorId, + return new KeyboardId( + res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 4a3a58b94..2ad414c90 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -1000,7 +1000,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } // Set the preview background state mPreviewText.getBackground().setState( - key.mPopupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); + key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); popupPreviewX += mOffsetInWindow[0]; popupPreviewY += mOffsetInWindow[1]; @@ -1100,7 +1100,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } private View inflateMiniKeyboardContainer(Key popupKey) { - int popupKeyboardId = popupKey.mPopupResId; + int popupKeyboardResId = mKeyboard.getPopupKeyboardResId(); LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); View container = inflater.inflate(mPopupLayout, null); @@ -1157,13 +1157,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { // Remove gesture detector on mini-keyboard miniKeyboard.mGestureDetector = null; - Keyboard keyboard; - if (popupKey.mPopupCharacters != null) { - keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.mPopupCharacters, - -1, getPaddingLeft() + getPaddingRight()); - } else { - keyboard = new Keyboard(getContext(), popupKeyboardId); - } + Keyboard keyboard = new MiniKeyboardBuilder(getContext(), popupKeyboardResId, popupKey) + .build(); miniKeyboard.setKeyboard(keyboard); miniKeyboard.setPopupParent(this); @@ -1194,7 +1189,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { * method on the base class if the subclass doesn't wish to handle the call. */ protected boolean onLongPress(Key popupKey) { - if (popupKey.mPopupResId == 0) + if (popupKey.mPopupCharacters == null) return false; View container = mMiniKeyboardCache.get(popupKey); @@ -1272,15 +1267,14 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } private static boolean hasMultiplePopupChars(Key key) { - if (key.mPopupCharacters != null && key.mPopupCharacters.length() > 1) { + if (key.mPopupCharacters != null && key.mPopupCharacters.length > 1) { return true; } return false; } private static boolean isNumberAtLeftmostPopupChar(Key key) { - if (key.mPopupCharacters != null && key.mPopupCharacters.length() > 0 - && isAsciiDigit(key.mPopupCharacters.charAt(0))) { + if (key.mPopupCharacters != null && isAsciiDigit(key.mPopupCharacters[0].charAt(0))) { return true; } return false; diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index 7cae4f1df..b9041e36b 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -44,15 +44,13 @@ public class LatinKeyboard extends Keyboard { public static final int OPACITY_FULLY_OPAQUE = 255; private static final int SPACE_LED_LENGTH_PERCENT = 80; - private Drawable mShiftLockPreviewIcon; - private Drawable mSpaceAutoCorrectionIndicator; + private final Drawable mSpaceAutoCorrectionIndicator; private final Drawable mButtonArrowLeftIcon; private final Drawable mButtonArrowRightIcon; private final int mSpaceBarTextShadowColor; private int mSpaceKeyIndex = -1; private int mSpaceDragStartX; private int mSpaceDragLastDiff; - private final Resources mRes; private final Context mContext; private boolean mCurrentlyInSpace; private SlidingLocaleDrawable mSlidingLocaleIcon; @@ -79,10 +77,9 @@ public class LatinKeyboard extends Keyboard { private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium"; public LatinKeyboard(Context context, KeyboardId id) { - super(context, id); + super(context, id.getXmlId(), id); final Resources res = context.getResources(); mContext = context; - mRes = res; if (id.mColorScheme == KeyboardView.COLOR_SCHEME_BLACK) { mSpaceBarTextShadowColor = res.getColor( R.color.latinkeyboard_bar_language_shadow_black); @@ -90,8 +87,6 @@ public class LatinKeyboard extends Keyboard { mSpaceBarTextShadowColor = res.getColor( R.color.latinkeyboard_bar_language_shadow_white); } - mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked); - setDefaultBounds(mShiftLockPreviewIcon); mSpaceAutoCorrectionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led); mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left); mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right); @@ -109,7 +104,7 @@ public class LatinKeyboard extends Keyboard { } private void updateSpaceBarForLocale(boolean isAutoCorrection) { - final Resources res = mRes; + final Resources res = mContext.getResources(); // If application locales are explicitly selected. if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) { mSpaceKey.setIcon(new BitmapDrawable(res, @@ -181,7 +176,7 @@ public class LatinKeyboard extends Keyboard { final int height = mSpaceIcon.getIntrinsicHeight(); final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(buffer); - final Resources res = mRes; + final Resources res = mContext.getResources(); canvas.drawColor(res.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR); SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance(); diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java new file mode 100644 index 000000000..1eb0c3f37 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard; + +import android.content.Context; +import android.content.res.Resources; + +import java.util.List; + +public class MiniKeyboardBuilder { + private final Resources mRes; + private final Keyboard mKeyboard; + private final CharSequence[] mPopupCharacters; + private final int mMaxColumns; + private final int mNumRows; + private int mColPos; + private int mRowPos; + private Row mRow; + private int mX; + private int mY; + + public MiniKeyboardBuilder(Context context, int layoutTemplateResId, Key popupKey) { + mRes = context.getResources(); + mKeyboard = new Keyboard(context, layoutTemplateResId, null); + mPopupCharacters = popupKey.mPopupCharacters; + final int numKeys = mPopupCharacters.length; + final int maxColumns = popupKey.mMaxPopupColumn; + int numRows = numKeys / maxColumns; + if (numKeys % maxColumns != 0) numRows++; + mMaxColumns = maxColumns; + mNumRows = numRows; + // TODO: To determine key width we should pay attention to key label length. + mRow = new Row(mKeyboard, getRowFlags()); + if (numRows > 1) { + mColPos = numKeys % maxColumns; + if (mColPos > 0) mColPos = maxColumns - mColPos; + // Centering top-row keys. + mX = mColPos * (mRow.mDefaultWidth + mRow.mDefaultHorizontalGap) / 2; + } + mKeyboard.setMinWidth(0); + } + + public Keyboard build() { + List<Key> keys = mKeyboard.getKeys(); + for (CharSequence label : mPopupCharacters) { + refresh(); + final Key key = new Key(mRes, mRow, label, mX, mY); + keys.add(key); + advance(); + } + finish(); + return mKeyboard; + } + + private int getRowFlags() { + final int rowPos = mRowPos; + int rowFlags = 0; + if (rowPos == 0) rowFlags |= Keyboard.EDGE_TOP; + if (rowPos == mNumRows - 1) rowFlags |= Keyboard.EDGE_BOTTOM; + return rowFlags; + } + + private void refresh() { + if (mColPos >= mMaxColumns) { + final Row row = mRow; + // TODO: Allocate key position depending the precedence of popup characters. + mX = 0; + mY += row.mDefaultHeight + row.mVerticalGap; + mColPos = 0; + // TODO: To determine key width we should pay attention to key label length from + // bottom to up for rows. + mRow = new Row(mKeyboard, getRowFlags()); + mRowPos++; + } + } + + private void advance() { + final Row row = mRow; + final Keyboard keyboard = mKeyboard; + // TODO: Allocate key position depending the precedence of popup characters. + mX += row.mDefaultWidth + row.mDefaultHorizontalGap; + if (mX > keyboard.getMinWidth()) + keyboard.setMinWidth(mX); + mColPos++; + } + + private void finish() { + mKeyboard.setHeight(mY + mRow.mDefaultHeight); + } +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/keyboard/PopupCharactersParser.java b/java/src/com/android/inputmethod/keyboard/PopupCharactersParser.java new file mode 100644 index 000000000..cad3da03e --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/PopupCharactersParser.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard; + +import com.android.inputmethod.latin.R; + +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; + +/** + * String parser of popupCharacters attribute of Key. + * The string is comma separated texts each of which represents one popup key. + * Each popup key text is one of the following: + * - A single letter (Letter) + * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText). + * - Icon followed by keyOutputText or code (@drawable/icon|@integer/key_code) + * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' + * character. + * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well. + */ +public class PopupCharactersParser { + private static final char ESCAPE = '\\'; + private static final String LABEL_END = "|"; + private static final String PREFIX_AT = "@"; + private static final String PREFIX_ICON = PREFIX_AT + "drawable/"; + private static final String PREFIX_CODE = PREFIX_AT + "integer/"; + private static final int[] DUMMY_CODES = { 0 }; + + private PopupCharactersParser() { + // Intentional empty constructor for utility class. + } + + private static boolean hasIcon(String popupSpec) { + if (popupSpec.startsWith(PREFIX_ICON)) { + final int end = indexOfLabelEnd(popupSpec, 0); + if (end > 0) + return true; + throw new PopupCharactersParserError("outputText or code not specified: " + popupSpec); + } + return false; + } + + private static boolean hasCode(String popupSpec) { + final int end = indexOfLabelEnd(popupSpec, 0); + if (end > 0 && end + 1 < popupSpec.length() + && popupSpec.substring(end + 1).startsWith(PREFIX_CODE)) { + return true; + } + return false; + } + + private static String parseEscape(String text) { + if (text.indexOf(ESCAPE) < 0) + return text; + final int length = text.length(); + final StringBuilder sb = new StringBuilder(); + for (int pos = 0; pos < length; pos++) { + final char c = text.charAt(pos); + if (c == ESCAPE && pos + 1 < length) { + sb.append(text.charAt(++pos)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + private static int indexOfLabelEnd(String popupSpec, int start) { + if (popupSpec.indexOf(ESCAPE, start) < 0) { + final int end = popupSpec.indexOf(LABEL_END, start); + if (end == 0) + throw new PopupCharactersParserError(LABEL_END + " at " + start + ": " + popupSpec); + return end; + } + final int length = popupSpec.length(); + for (int pos = start; pos < length; pos++) { + final char c = popupSpec.charAt(pos); + if (c == ESCAPE && pos + 1 < length) { + pos++; + } else if (popupSpec.startsWith(LABEL_END, pos)) { + return pos; + } + } + return -1; + } + + public static String getLabel(String popupSpec) { + if (hasIcon(popupSpec)) + return null; + final int end = indexOfLabelEnd(popupSpec, 0); + final String label = (end > 0) ? parseEscape(popupSpec.substring(0, end)) + : parseEscape(popupSpec); + if (TextUtils.isEmpty(label)) + throw new PopupCharactersParserError("Empty label: " + popupSpec); + return label; + } + + public static String getOutputText(String popupSpec) { + if (hasCode(popupSpec)) + return null; + final int end = indexOfLabelEnd(popupSpec, 0); + if (end > 0) { + if (indexOfLabelEnd(popupSpec, end + 1) >= 0) + throw new PopupCharactersParserError("Multiple " + LABEL_END + ": " + + popupSpec); + final String outputText = parseEscape(popupSpec.substring(end + LABEL_END.length())); + if (!TextUtils.isEmpty(outputText)) + return outputText; + throw new PopupCharactersParserError("Empty outputText: " + popupSpec); + } + final String label = getLabel(popupSpec); + if (label == null) + throw new PopupCharactersParserError("Empty label: " + popupSpec); + // Code is automatically generated for one letter label. See getCode(). + if (label.length() == 1) + return null; + return label; + } + + public static int[] getCodes(Resources res, String popupSpec) { + if (hasCode(popupSpec)) { + final int end = indexOfLabelEnd(popupSpec, 0); + if (indexOfLabelEnd(popupSpec, end + 1) >= 0) + throw new PopupCharactersParserError("Multiple " + LABEL_END + ": " + popupSpec); + final int resId = getResourceId(res, + popupSpec.substring(end + LABEL_END.length() + PREFIX_AT.length())); + final int code = res.getInteger(resId); + return new int[] { code }; + } + if (indexOfLabelEnd(popupSpec, 0) > 0) + return DUMMY_CODES; + final String label = getLabel(popupSpec); + // Code is automatically generated for one letter label. + if (label != null && label.length() == 1) + return new int[] { label.charAt(0) }; + return DUMMY_CODES; + } + + public static Drawable getIcon(Resources res, String popupSpec) { + if (hasIcon(popupSpec)) { + int end = popupSpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1); + int resId = getResourceId(res, popupSpec.substring(PREFIX_AT.length(), end)); + return res.getDrawable(resId); + } + return null; + } + + private static int getResourceId(Resources res, String name) { + String packageName = res.getResourcePackageName(R.string.english_ime_name); + int resId = res.getIdentifier(name, null, packageName); + if (resId == 0) + throw new PopupCharactersParserError("Unknown resource: " + name); + return resId; + } + + @SuppressWarnings("serial") + public static class PopupCharactersParserError extends RuntimeException { + public PopupCharactersParserError(String message) { + super(message); + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/Row.java b/java/src/com/android/inputmethod/keyboard/Row.java index 7c158bca0..198f02ca8 100644 --- a/java/src/com/android/inputmethod/keyboard/Row.java +++ b/java/src/com/android/inputmethod/keyboard/Row.java @@ -45,13 +45,13 @@ public class Row { private final Keyboard mKeyboard; - public Row(Keyboard keyboard) { + public Row(Keyboard keyboard, int rowFlags) { this.mKeyboard = keyboard; mDefaultHeight = keyboard.getRowHeight(); mDefaultWidth = keyboard.getKeyWidth(); mDefaultHorizontalGap = keyboard.getHorizontalGap(); mVerticalGap = keyboard.getVerticalGap(); - mRowEdgeFlags = Keyboard.EDGE_TOP | Keyboard.EDGE_BOTTOM; + mRowEdgeFlags = rowFlags; } public Row(Resources res, Keyboard keyboard, XmlResourceParser parser) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 243306a35..12a24e87a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -2039,7 +2039,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false); mPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON, - mResources.getBoolean(R.bool.default_popup_preview)); + mResources.getBoolean(R.bool.config_default_popup_preview)); mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); mQuickFixes = prefs.getBoolean(Settings.PREF_QUICK_FIXES, true); |