1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.keyboard.internal;
18
19import android.content.res.TypedArray;
20import android.util.Log;
21import android.util.SparseArray;
22
23import com.android.inputmethod.latin.R;
24import com.android.inputmethod.latin.utils.XmlParseUtils;
25
26import org.xmlpull.v1.XmlPullParser;
27import org.xmlpull.v1.XmlPullParserException;
28
29import java.util.Arrays;
30import java.util.HashMap;
31
32import javax.annotation.Nonnull;
33import javax.annotation.Nullable;
34
35public final class KeyStylesSet {
36    private static final String TAG = KeyStylesSet.class.getSimpleName();
37    private static final boolean DEBUG = false;
38
39    @Nonnull
40    private final HashMap<String, KeyStyle> mStyles = new HashMap<>();
41
42    @Nonnull
43    private final KeyboardTextsSet mTextsSet;
44    @Nonnull
45    private final KeyStyle mEmptyKeyStyle;
46    @Nonnull
47    private static final String EMPTY_STYLE_NAME = "<empty>";
48
49    public KeyStylesSet(@Nonnull final KeyboardTextsSet textsSet) {
50        mTextsSet = textsSet;
51        mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
52        mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
53    }
54
55    private static final class EmptyKeyStyle extends KeyStyle {
56        EmptyKeyStyle(@Nonnull final KeyboardTextsSet textsSet) {
57            super(textsSet);
58        }
59
60        @Override
61        @Nullable
62        public String[] getStringArray(final TypedArray a, final int index) {
63            return parseStringArray(a, index);
64        }
65
66        @Override
67        @Nullable
68        public String getString(final TypedArray a, final int index) {
69            return parseString(a, index);
70        }
71
72        @Override
73        public int getInt(final TypedArray a, final int index, final int defaultValue) {
74            return a.getInt(index, defaultValue);
75        }
76
77        @Override
78        public int getFlags(final TypedArray a, final int index) {
79            return a.getInt(index, 0);
80        }
81    }
82
83    private static final class DeclaredKeyStyle extends KeyStyle {
84        private final HashMap<String, KeyStyle> mStyles;
85        private final String mParentStyleName;
86        private final SparseArray<Object> mStyleAttributes = new SparseArray<>();
87
88        public DeclaredKeyStyle(@Nonnull final String parentStyleName,
89                @Nonnull final KeyboardTextsSet textsSet,
90                @Nonnull final HashMap<String, KeyStyle> styles) {
91            super(textsSet);
92            mParentStyleName = parentStyleName;
93            mStyles = styles;
94        }
95
96        @Override
97        @Nullable
98        public String[] getStringArray(final TypedArray a, final int index) {
99            if (a.hasValue(index)) {
100                return parseStringArray(a, index);
101            }
102            final Object value = mStyleAttributes.get(index);
103            if (value != null) {
104                final String[] array = (String[])value;
105                return Arrays.copyOf(array, array.length);
106            }
107            final KeyStyle parentStyle = mStyles.get(mParentStyleName);
108            return parentStyle.getStringArray(a, index);
109        }
110
111        @Override
112        @Nullable
113        public String getString(final TypedArray a, final int index) {
114            if (a.hasValue(index)) {
115                return parseString(a, index);
116            }
117            final Object value = mStyleAttributes.get(index);
118            if (value != null) {
119                return (String)value;
120            }
121            final KeyStyle parentStyle = mStyles.get(mParentStyleName);
122            return parentStyle.getString(a, index);
123        }
124
125        @Override
126        public int getInt(final TypedArray a, final int index, final int defaultValue) {
127            if (a.hasValue(index)) {
128                return a.getInt(index, defaultValue);
129            }
130            final Object value = mStyleAttributes.get(index);
131            if (value != null) {
132                return (Integer)value;
133            }
134            final KeyStyle parentStyle = mStyles.get(mParentStyleName);
135            return parentStyle.getInt(a, index, defaultValue);
136        }
137
138        @Override
139        public int getFlags(final TypedArray a, final int index) {
140            final int parentFlags = mStyles.get(mParentStyleName).getFlags(a, index);
141            final Integer value = (Integer)mStyleAttributes.get(index);
142            final int styleFlags = (value != null) ? value : 0;
143            final int flags = a.getInt(index, 0);
144            return flags | styleFlags | parentFlags;
145        }
146
147        public void readKeyAttributes(final TypedArray keyAttr) {
148            // TODO: Currently not all Key attributes can be declared as style.
149            readString(keyAttr, R.styleable.Keyboard_Key_altCode);
150            readString(keyAttr, R.styleable.Keyboard_Key_keySpec);
151            readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
152            readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
153            readStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
154            readFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
155            readString(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled);
156            readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
157            readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType);
158            readFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
159        }
160
161        private void readString(final TypedArray a, final int index) {
162            if (a.hasValue(index)) {
163                mStyleAttributes.put(index, parseString(a, index));
164            }
165        }
166
167        private void readInt(final TypedArray a, final int index) {
168            if (a.hasValue(index)) {
169                mStyleAttributes.put(index, a.getInt(index, 0));
170            }
171        }
172
173        private void readFlags(final TypedArray a, final int index) {
174            if (a.hasValue(index)) {
175                final Integer value = (Integer)mStyleAttributes.get(index);
176                final int styleFlags = value != null ? value : 0;
177                mStyleAttributes.put(index, a.getInt(index, 0) | styleFlags);
178            }
179        }
180
181        private void readStringArray(final TypedArray a, final int index) {
182            if (a.hasValue(index)) {
183                mStyleAttributes.put(index, parseStringArray(a, index));
184            }
185        }
186    }
187
188    public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
189            final XmlPullParser parser) throws XmlPullParserException {
190        final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
191        if (styleName == null) {
192            throw new XmlParseUtils.ParseException(
193                    KeyboardBuilder.TAG_KEY_STYLE + " has no styleName attribute", parser);
194        }
195        if (DEBUG) {
196            Log.d(TAG, String.format("<%s styleName=%s />",
197                    KeyboardBuilder.TAG_KEY_STYLE, styleName));
198            if (mStyles.containsKey(styleName)) {
199                Log.d(TAG, KeyboardBuilder.TAG_KEY_STYLE + " " + styleName + " is overridden at "
200                        + parser.getPositionDescription());
201            }
202        }
203
204        final String parentStyleInAttr = keyStyleAttr.getString(
205                R.styleable.Keyboard_KeyStyle_parentStyle);
206        if (parentStyleInAttr != null && !mStyles.containsKey(parentStyleInAttr)) {
207            throw new XmlParseUtils.ParseException(
208                    "Unknown parentStyle " + parentStyleInAttr, parser);
209        }
210        final String parentStyleName = (parentStyleInAttr == null) ? EMPTY_STYLE_NAME
211                : parentStyleInAttr;
212        final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
213        style.readKeyAttributes(keyAttrs);
214        mStyles.put(styleName, style);
215    }
216
217    @Nonnull
218    public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
219            throws XmlParseUtils.ParseException {
220        final String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
221        if (styleName == null) {
222            return mEmptyKeyStyle;
223        }
224        final KeyStyle style = mStyles.get(styleName);
225        if (style == null) {
226            throw new XmlParseUtils.ParseException("Unknown key style: " + styleName, parser);
227        }
228        return style;
229    }
230}
231