1/*
2 * Copyright (C) 2012 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.Resources;
20import android.content.res.TypedArray;
21import android.util.Xml;
22
23import com.android.inputmethod.keyboard.Key;
24import com.android.inputmethod.keyboard.Keyboard;
25import com.android.inputmethod.latin.R;
26import com.android.inputmethod.latin.utils.ResourceUtils;
27
28import org.xmlpull.v1.XmlPullParser;
29
30import java.util.ArrayDeque;
31
32/**
33 * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
34 * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
35 * defines.
36 */
37public final class KeyboardRow {
38    // keyWidth enum constants
39    private static final int KEYWIDTH_NOT_ENUM = 0;
40    private static final int KEYWIDTH_FILL_RIGHT = -1;
41
42    private final KeyboardParams mParams;
43    /** The height of this row. */
44    private final int mRowHeight;
45
46    private final ArrayDeque<RowAttributes> mRowAttributesStack = new ArrayDeque<>();
47
48    // TODO: Add keyActionFlags.
49    private static class RowAttributes {
50        /** Default width of a key in this row. */
51        public final float mDefaultKeyWidth;
52        /** Default keyLabelFlags in this row. */
53        public final int mDefaultKeyLabelFlags;
54        /** Default backgroundType for this row */
55        public final int mDefaultBackgroundType;
56
57        /**
58         * Parse and create key attributes. This constructor is used to parse Row tag.
59         *
60         * @param keyAttr an attributes array of Row tag.
61         * @param defaultKeyWidth a default key width.
62         * @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
63         */
64        public RowAttributes(final TypedArray keyAttr, final float defaultKeyWidth,
65                final int keyboardWidth) {
66            mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
67                    keyboardWidth, keyboardWidth, defaultKeyWidth);
68            mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
69            mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
70                    Key.BACKGROUND_TYPE_NORMAL);
71        }
72
73        /**
74         * Parse and update key attributes using default attributes. This constructor is used
75         * to parse include tag.
76         *
77         * @param keyAttr an attributes array of include tag.
78         * @param defaultRowAttr default Row attributes.
79         * @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
80         */
81        public RowAttributes(final TypedArray keyAttr, final RowAttributes defaultRowAttr,
82                final int keyboardWidth) {
83            mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
84                    keyboardWidth, keyboardWidth, defaultRowAttr.mDefaultKeyWidth);
85            mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0)
86                    | defaultRowAttr.mDefaultKeyLabelFlags;
87            mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
88                    defaultRowAttr.mDefaultBackgroundType);
89        }
90    }
91
92    private final int mCurrentY;
93    // Will be updated by {@link Key}'s constructor.
94    private float mCurrentX;
95
96    public KeyboardRow(final Resources res, final KeyboardParams params,
97            final XmlPullParser parser, final int y) {
98        mParams = params;
99        final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
100                R.styleable.Keyboard);
101        mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
102                R.styleable.Keyboard_rowHeight, params.mBaseHeight, params.mDefaultRowHeight);
103        keyboardAttr.recycle();
104        final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
105                R.styleable.Keyboard_Key);
106        mRowAttributesStack.push(new RowAttributes(
107                keyAttr, params.mDefaultKeyWidth, params.mBaseWidth));
108        keyAttr.recycle();
109
110        mCurrentY = y;
111        mCurrentX = 0.0f;
112    }
113
114    public int getRowHeight() {
115        return mRowHeight;
116    }
117
118    public void pushRowAttributes(final TypedArray keyAttr) {
119        final RowAttributes newAttributes = new RowAttributes(
120                keyAttr, mRowAttributesStack.peek(), mParams.mBaseWidth);
121        mRowAttributesStack.push(newAttributes);
122    }
123
124    public void popRowAttributes() {
125        mRowAttributesStack.pop();
126    }
127
128    public float getDefaultKeyWidth() {
129        return mRowAttributesStack.peek().mDefaultKeyWidth;
130    }
131
132    public int getDefaultKeyLabelFlags() {
133        return mRowAttributesStack.peek().mDefaultKeyLabelFlags;
134    }
135
136    public int getDefaultBackgroundType() {
137        return mRowAttributesStack.peek().mDefaultBackgroundType;
138    }
139
140    public void setXPos(final float keyXPos) {
141        mCurrentX = keyXPos;
142    }
143
144    public void advanceXPos(final float width) {
145        mCurrentX += width;
146    }
147
148    public int getKeyY() {
149        return mCurrentY;
150    }
151
152    public float getKeyX(final TypedArray keyAttr) {
153        if (keyAttr == null || !keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
154            return mCurrentX;
155        }
156        final float keyXPos = keyAttr.getFraction(R.styleable.Keyboard_Key_keyXPos,
157                mParams.mBaseWidth, mParams.mBaseWidth, 0);
158        if (keyXPos >= 0) {
159            return keyXPos + mParams.mLeftPadding;
160        }
161        // If keyXPos is negative, the actual x-coordinate will be
162        // keyboardWidth + keyXPos.
163        // keyXPos shouldn't be less than mCurrentX because drawable area for this
164        // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
165        // its left hand side.
166        final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
167        return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
168    }
169
170    public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
171        if (keyAttr == null) {
172            return getDefaultKeyWidth();
173        }
174        final int widthType = ResourceUtils.getEnumValue(keyAttr,
175                R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
176        switch (widthType) {
177        case KEYWIDTH_FILL_RIGHT:
178            // If keyWidth is fillRight, the actual key width will be determined to fill
179            // out the area up to the right edge of the keyboard.
180            final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
181            return keyboardRightEdge - keyXPos;
182        default: // KEYWIDTH_NOT_ENUM
183            return keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
184                    mParams.mBaseWidth, mParams.mBaseWidth, getDefaultKeyWidth());
185        }
186    }
187}
188