Key.java revision 18453d69e0ef7631500826bf4e0b6f684c948cb3
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.keyboard;
18
19import android.content.res.Resources;
20import android.content.res.TypedArray;
21import android.content.res.XmlResourceParser;
22import android.graphics.Typeface;
23import android.graphics.drawable.Drawable;
24import android.text.TextUtils;
25import android.util.Xml;
26
27import com.android.inputmethod.keyboard.internal.KeyStyles;
28import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
29import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
30import com.android.inputmethod.keyboard.internal.KeyboardBuilder.ParseException;
31import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
32import com.android.inputmethod.keyboard.internal.KeyboardParams;
33import com.android.inputmethod.keyboard.internal.MoreKeySpecParser;
34import com.android.inputmethod.latin.R;
35
36import java.util.HashMap;
37import java.util.Map;
38
39/**
40 * Class for describing the position and characteristics of a single key in the keyboard.
41 */
42public class Key {
43    /**
44     * The key code (unicode or custom code) that this key generates.
45     */
46    public final int mCode;
47
48    /** Label to display */
49    public final CharSequence mLabel;
50    /** Hint label to display on the key in conjunction with the label */
51    public final CharSequence mHintLabel;
52    /** Option of the label */
53    private final int mLabelOption;
54    private static final int LABEL_OPTION_ALIGN_LEFT = 0x01;
55    private static final int LABEL_OPTION_ALIGN_RIGHT = 0x02;
56    private static final int LABEL_OPTION_ALIGN_LEFT_OF_CENTER = 0x08;
57    private static final int LABEL_OPTION_LARGE_LETTER = 0x10;
58    private static final int LABEL_OPTION_FONT_NORMAL = 0x20;
59    private static final int LABEL_OPTION_FONT_MONO_SPACE = 0x40;
60    private static final int LABEL_OPTION_FOLLOW_KEY_LETTER_RATIO = 0x80;
61    private static final int LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100;
62    private static final int LABEL_OPTION_HAS_POPUP_HINT = 0x200;
63    private static final int LABEL_OPTION_HAS_UPPERCASE_LETTER = 0x400;
64    private static final int LABEL_OPTION_HAS_HINT_LABEL = 0x800;
65    private static final int LABEL_OPTION_WITH_ICON_LEFT = 0x1000;
66    private static final int LABEL_OPTION_WITH_ICON_RIGHT = 0x2000;
67
68    /** Icon to display instead of a label. Icon takes precedence over a label */
69    private Drawable mIcon;
70    /** Preview version of the icon, for the preview popup */
71    private Drawable mPreviewIcon;
72
73    /** Width of the key, not including the gap */
74    public final int mWidth;
75    /** Height of the key, not including the gap */
76    public final int mHeight;
77    /** The horizontal gap around this key */
78    public final int mHorizontalGap;
79    /** The vertical gap below this key */
80    public final int mVerticalGap;
81    /** The visual insets */
82    public final int mVisualInsetsLeft;
83    public final int mVisualInsetsRight;
84    /** Whether this key is sticky, i.e., a toggle key */
85    public final boolean mSticky;
86    /** X coordinate of the key in the keyboard layout */
87    public final int mX;
88    /** Y coordinate of the key in the keyboard layout */
89    public final int mY;
90    /** Text to output when pressed. This can be multiple characters, like ".com" */
91    public final CharSequence mOutputText;
92    /** More keys */
93    public final CharSequence[] mMoreKeys;
94    /** More keys maximum column number */
95    public final int mMaxMoreKeysColumn;
96
97    /**
98     * Flags that specify the anchoring to edges of the keyboard for detecting touch events
99     * that are just out of the boundary of the key. This is a bit mask of
100     * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT},
101     * {@link Keyboard#EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM}.
102     */
103    private int mEdgeFlags;
104    /** Whether this is a functional key which has different key top than normal key */
105    public final boolean mFunctional;
106    /** Whether this key repeats itself when held down */
107    public final boolean mRepeatable;
108
109    /** The current pressed state of this key */
110    private boolean mPressed;
111    /** If this is a sticky key, is its highlight on? */
112    private boolean mHighlightOn;
113    /** Key is enabled and responds on press */
114    private boolean mEnabled = true;
115    /** Whether this key needs to show the "..." popup hint for special purposes */
116    private boolean mNeedsSpecialPopupHint;
117
118    // keyWidth enum constants
119    private static final int KEYWIDTH_NOT_ENUM = 0;
120    private static final int KEYWIDTH_FILL_RIGHT = -1;
121    private static final int KEYWIDTH_FILL_BOTH = -2;
122
123    private final static int[] KEY_STATE_NORMAL_ON = {
124        android.R.attr.state_checkable,
125        android.R.attr.state_checked
126    };
127
128    private final static int[] KEY_STATE_PRESSED_ON = {
129        android.R.attr.state_pressed,
130        android.R.attr.state_checkable,
131        android.R.attr.state_checked
132    };
133
134    private final static int[] KEY_STATE_NORMAL_OFF = {
135        android.R.attr.state_checkable
136    };
137
138    private final static int[] KEY_STATE_PRESSED_OFF = {
139        android.R.attr.state_pressed,
140        android.R.attr.state_checkable
141    };
142
143    private final static int[] KEY_STATE_NORMAL = {
144    };
145
146    private final static int[] KEY_STATE_PRESSED = {
147        android.R.attr.state_pressed
148    };
149
150    // functional normal state (with properties)
151    private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
152            android.R.attr.state_single
153    };
154
155    // functional pressed state (with properties)
156    private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
157            android.R.attr.state_single,
158            android.R.attr.state_pressed
159    };
160
161    // RTL parenthesis character swapping map.
162    private static final Map<Integer, Integer> sRtlParenthesisMap = new HashMap<Integer, Integer>();
163
164    static {
165        // The all letters need to be mirrored are found at
166        // http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt
167        addRtlParenthesisPair('(', ')');
168        addRtlParenthesisPair('[', ']');
169        addRtlParenthesisPair('{', '}');
170        addRtlParenthesisPair('<', '>');
171        // \u00ab: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
172        // \u00bb: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
173        addRtlParenthesisPair('\u00ab', '\u00bb');
174        // \u2039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK
175        // \u203a: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
176        addRtlParenthesisPair('\u2039', '\u203a');
177        // \u2264: LESS-THAN OR EQUAL TO
178        // \u2265: GREATER-THAN OR EQUAL TO
179        addRtlParenthesisPair('\u2264', '\u2265');
180    }
181
182    private static void addRtlParenthesisPair(int left, int right) {
183        sRtlParenthesisMap.put(left, right);
184        sRtlParenthesisMap.put(right, left);
185    }
186
187    public static int getRtlParenthesisCode(int code, boolean isRtl) {
188        if (isRtl && sRtlParenthesisMap.containsKey(code)) {
189            return sRtlParenthesisMap.get(code);
190        } else {
191            return code;
192        }
193    }
194
195    private static int getCode(Resources res, KeyboardParams params, String moreKeySpec) {
196        return getRtlParenthesisCode(
197                MoreKeySpecParser.getCode(res, moreKeySpec), params.mIsRtlKeyboard);
198    }
199
200    private static Drawable getIcon(KeyboardParams params, String moreKeySpec) {
201        return params.mIconsSet.getIcon(MoreKeySpecParser.getIconId(moreKeySpec));
202    }
203
204    /**
205     * This constructor is being used only for key in more keys keyboard.
206     */
207    public Key(Resources res, KeyboardParams params, String moreKeySpec,
208            int x, int y, int width, int height, int edgeFlags) {
209        this(params, MoreKeySpecParser.getLabel(moreKeySpec), null, getIcon(params, moreKeySpec),
210                getCode(res, params, moreKeySpec), MoreKeySpecParser.getOutputText(moreKeySpec),
211                x, y, width, height, edgeFlags);
212    }
213
214    /**
215     * This constructor is being used only for key in popup suggestions pane.
216     */
217    public Key(KeyboardParams params, CharSequence label, CharSequence hintLabel, Drawable icon,
218            int code, CharSequence outputText, int x, int y, int width, int height, int edgeFlags) {
219        mHeight = height - params.mVerticalGap;
220        mHorizontalGap = params.mHorizontalGap;
221        mVerticalGap = params.mVerticalGap;
222        mVisualInsetsLeft = mVisualInsetsRight = 0;
223        mWidth = width - mHorizontalGap;
224        mEdgeFlags = edgeFlags;
225        mHintLabel = hintLabel;
226        mLabelOption = 0;
227        mFunctional = false;
228        mSticky = false;
229        mRepeatable = false;
230        mMoreKeys = null;
231        mMaxMoreKeysColumn = 0;
232        mLabel = label;
233        mOutputText = outputText;
234        mCode = code;
235        mIcon = icon;
236        // Horizontal gap is divided equally to both sides of the key.
237        mX = x + mHorizontalGap / 2;
238        mY = y;
239    }
240
241    /**
242     * Create a key with the given top-left coordinate and extract its attributes from the XML
243     * parser.
244     * @param res resources associated with the caller's context
245     * @param params the keyboard building parameters.
246     * @param row the row that this key belongs to. row's x-coordinate will be the right edge of
247     *        this key.
248     * @param parser the XML parser containing the attributes for this key
249     * @param keyStyles active key styles set
250     */
251    public Key(Resources res, KeyboardParams params, KeyboardBuilder.Row row,
252            XmlResourceParser parser, KeyStyles keyStyles) {
253        final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
254                R.styleable.Keyboard);
255        mHeight = (int)KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
256                R.styleable.Keyboard_rowHeight, params.mHeight, row.mRowHeight)
257                - params.mVerticalGap;
258        final float horizontalGap = isSpacer() ? 0
259                : KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
260                        R.styleable.Keyboard_horizontalGap, params.mWidth, params.mHorizontalGap);
261        mVerticalGap = params.mVerticalGap;
262        final int widthType = KeyboardBuilder.getEnumValue(keyboardAttr,
263                R.styleable.Keyboard_keyWidth, KEYWIDTH_NOT_ENUM);
264        float keyWidth = KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
265                R.styleable.Keyboard_keyWidth, params.mWidth, row.mDefaultKeyWidth);
266        keyboardAttr.recycle();
267
268        final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
269                R.styleable.Keyboard_Key);
270
271        final KeyStyle style;
272        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
273            String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
274            style = keyStyles.getKeyStyle(styleName);
275            if (style == null)
276                throw new ParseException("Unknown key style: " + styleName, parser);
277        } else {
278            style = keyStyles.getEmptyKeyStyle();
279        }
280
281        final int keyboardWidth = params.mOccupiedWidth;
282        final float x = row.mCurrentX;
283        float keyXPos = KeyboardBuilder.getDimensionOrFraction(keyAttr,
284                R.styleable.Keyboard_Key_keyXPos, keyboardWidth, x);
285        if (keyXPos < 0) {
286            // If keyXPos is negative, the actual x-coordinate will be keyboardWidth + keyXPos.
287            keyXPos += keyboardWidth;
288            if (keyXPos < x) {
289                // keyXPos shouldn't be less than x because drawable area for this key starts
290                // at x. Or, this key will overlaps the adjacent key on its left hand side.
291                keyXPos = x;
292            }
293        }
294        if (widthType == KEYWIDTH_FILL_RIGHT) {
295            // If keyWidth is zero, the actual key width will be determined to fill out the
296            // area up to the right edge of the keyboard.
297            keyWidth = keyboardWidth - keyXPos;
298        } else if (widthType == KEYWIDTH_FILL_BOTH) {
299            // If keyWidth is negative, the actual key width will be determined to fill out the
300            // area between the nearest key on the left hand side and the right edge of the
301            // keyboard.
302            keyXPos = x;
303            keyWidth = keyboardWidth - keyXPos;
304        }
305
306        // Horizontal gap is divided equally to both sides of the key.
307        mX = (int) (keyXPos + horizontalGap / 2);
308        mY = row.mCurrentY;
309        mWidth = (int) (keyWidth - horizontalGap);
310        mHorizontalGap = (int) horizontalGap;
311        // Update row to have current x coordinate.
312        row.mCurrentX = keyXPos + keyWidth;
313
314        final CharSequence[] moreKeys = style.getTextArray(keyAttr,
315                R.styleable.Keyboard_Key_moreKeys);
316        // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of
317        // config_digit_more_keys_enabled.
318        if (params.mId.isAlphabetKeyboard()
319                && !res.getBoolean(R.bool.config_digit_more_keys_enabled)) {
320            mMoreKeys = MoreKeySpecParser.filterOut(res, moreKeys, MoreKeySpecParser.DIGIT_FILTER);
321        } else {
322            mMoreKeys = moreKeys;
323        }
324        mMaxMoreKeysColumn = style.getInt(keyboardAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn,
325                params.mMaxMiniKeyboardColumn);
326
327        mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
328        mFunctional = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional, false);
329        mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false);
330        mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
331        mEdgeFlags = 0;
332
333        final KeyboardIconsSet iconsSet = params.mIconsSet;
334        mVisualInsetsLeft = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr,
335                R.styleable.Keyboard_Key_visualInsetsLeft, keyboardWidth, 0);
336        mVisualInsetsRight = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr,
337                R.styleable.Keyboard_Key_visualInsetsRight, keyboardWidth, 0);
338        mPreviewIcon = iconsSet.getIcon(style.getInt(keyAttr,
339                R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED));
340        mIcon = iconsSet.getIcon(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIcon,
341                KeyboardIconsSet.ICON_UNDEFINED));
342        final int shiftedIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted,
343                KeyboardIconsSet.ICON_UNDEFINED);
344        if (shiftedIconId != KeyboardIconsSet.ICON_UNDEFINED) {
345            final Drawable shiftedIcon = iconsSet.getIcon(shiftedIconId);
346            params.addShiftedIcon(this, shiftedIcon);
347        }
348        mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
349
350        mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
351        mLabelOption = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption, 0);
352        mOutputText = style.getText(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
353        // Choose the first letter of the label as primary code if not
354        // specified.
355        final int code = style.getInt(keyAttr, R.styleable.Keyboard_Key_code,
356                Keyboard.CODE_UNSPECIFIED);
357        if (code == Keyboard.CODE_UNSPECIFIED && !TextUtils.isEmpty(mLabel)) {
358            final int firstChar = mLabel.charAt(0);
359            mCode = getRtlParenthesisCode(firstChar, params.mIsRtlKeyboard);
360        } else if (code != Keyboard.CODE_UNSPECIFIED) {
361            mCode = code;
362        } else {
363            mCode = Keyboard.CODE_DUMMY;
364        }
365
366        keyAttr.recycle();
367    }
368
369    public void addEdgeFlags(int flags) {
370        mEdgeFlags |= flags;
371    }
372
373    public boolean isSpacer() {
374        return false;
375    }
376
377    public Typeface selectTypeface(Typeface defaultTypeface) {
378        // TODO: Handle "bold" here too?
379        if ((mLabelOption & LABEL_OPTION_FONT_NORMAL) != 0) {
380            return Typeface.DEFAULT;
381        } else if ((mLabelOption & LABEL_OPTION_FONT_MONO_SPACE) != 0) {
382            return Typeface.MONOSPACE;
383        } else {
384            return defaultTypeface;
385        }
386    }
387
388    public int selectTextSize(int letter, int largeLetter, int label, int hintLabel) {
389        if (mLabel.length() > 1
390                && (mLabelOption & (LABEL_OPTION_FOLLOW_KEY_LETTER_RATIO
391                        | LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
392            return label;
393        } else if ((mLabelOption & LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) {
394            return hintLabel;
395        } else if ((mLabelOption & LABEL_OPTION_LARGE_LETTER) != 0) {
396            return largeLetter;
397        } else {
398            return letter;
399        }
400    }
401
402    public boolean isAlignLeft() {
403        return (mLabelOption & LABEL_OPTION_ALIGN_LEFT) != 0;
404    }
405
406    public boolean isAlignRight() {
407        return (mLabelOption & LABEL_OPTION_ALIGN_RIGHT) != 0;
408    }
409
410    public boolean isAlignLeftOfCenter() {
411        return (mLabelOption & LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0;
412    }
413
414    public boolean hasPopupHint() {
415        return (mLabelOption & LABEL_OPTION_HAS_POPUP_HINT) != 0;
416    }
417
418    public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) {
419        mNeedsSpecialPopupHint = needsSpecialPopupHint;
420    }
421
422    public boolean needsSpecialPopupHint() {
423        return mNeedsSpecialPopupHint;
424    }
425
426    public boolean hasUppercaseLetter() {
427        return (mLabelOption & LABEL_OPTION_HAS_UPPERCASE_LETTER) != 0;
428    }
429
430    public boolean hasHintLabel() {
431        return (mLabelOption & LABEL_OPTION_HAS_HINT_LABEL) != 0;
432    }
433
434    public boolean hasLabelWithIconLeft() {
435        return (mLabelOption & LABEL_OPTION_WITH_ICON_LEFT) != 0;
436    }
437
438    public boolean hasLabelWithIconRight() {
439        return (mLabelOption & LABEL_OPTION_WITH_ICON_RIGHT) != 0;
440    }
441
442    public Drawable getIcon() {
443        return mIcon;
444    }
445
446    public Drawable getPreviewIcon() {
447        return mPreviewIcon;
448    }
449
450    public void setIcon(Drawable icon) {
451        mIcon = icon;
452    }
453
454    public void setPreviewIcon(Drawable icon) {
455        mPreviewIcon = icon;
456    }
457
458    /**
459     * Informs the key that it has been pressed, in case it needs to change its appearance or
460     * state.
461     * @see #onReleased()
462     */
463    public void onPressed() {
464        mPressed = true;
465    }
466
467    /**
468     * Informs the key that it has been released, in case it needs to change its appearance or
469     * state.
470     * @see #onPressed()
471     */
472    public void onReleased() {
473        mPressed = false;
474    }
475
476    public void setHighlightOn(boolean highlightOn) {
477        mHighlightOn = highlightOn;
478    }
479
480    public boolean isEnabled() {
481        return mEnabled;
482    }
483
484    public void setEnabled(boolean enabled) {
485        mEnabled = enabled;
486    }
487
488    /**
489     * Detects if a point falls on this key.
490     * @param x the x-coordinate of the point
491     * @param y the y-coordinate of the point
492     * @return whether or not the point falls on the key. If the key is attached to an edge, it will
493     * assume that all points between the key and the edge are considered to be on the key.
494     */
495    public boolean isOnKey(int x, int y) {
496        final int left = mX - mHorizontalGap / 2;
497        final int right = left + mWidth + mHorizontalGap;
498        final int top = mY;
499        final int bottom = top + mHeight + mVerticalGap;
500        final int flags = mEdgeFlags;
501        if (flags == 0) {
502            return x >= left && x <= right && y >= top && y <= bottom;
503        }
504        final boolean leftEdge = (flags & Keyboard.EDGE_LEFT) != 0;
505        final boolean rightEdge = (flags & Keyboard.EDGE_RIGHT) != 0;
506        final boolean topEdge = (flags & Keyboard.EDGE_TOP) != 0;
507        final boolean bottomEdge = (flags & Keyboard.EDGE_BOTTOM) != 0;
508        // In order to mitigate rounding errors, we use (left <= x <= right) here.
509        return (x >= left || leftEdge) && (x <= right || rightEdge)
510                && (y >= top || topEdge) && (y <= bottom || bottomEdge);
511    }
512
513    /**
514     * Returns the square of the distance to the nearest edge of the key and the given point.
515     * @param x the x-coordinate of the point
516     * @param y the y-coordinate of the point
517     * @return the square of the distance of the point from the nearest edge of the key
518     */
519    public int squaredDistanceToEdge(int x, int y) {
520        final int left = mX;
521        final int right = left + mWidth;
522        final int top = mY;
523        final int bottom = top + mHeight;
524        final int edgeX = x < left ? left : (x > right ? right : x);
525        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
526        final int dx = x - edgeX;
527        final int dy = y - edgeY;
528        return dx * dx + dy * dy;
529    }
530
531    /**
532     * Returns the drawable state for the key, based on the current state and type of the key.
533     * @return the drawable state of the key.
534     * @see android.graphics.drawable.StateListDrawable#setState(int[])
535     */
536    public int[] getCurrentDrawableState() {
537        final boolean pressed = mPressed;
538        if (!mSticky && mFunctional) {
539            if (pressed) {
540                return KEY_STATE_FUNCTIONAL_PRESSED;
541            } else {
542                return KEY_STATE_FUNCTIONAL_NORMAL;
543            }
544        }
545
546        int[] states = KEY_STATE_NORMAL;
547
548        if (mHighlightOn) {
549            if (pressed) {
550                states = KEY_STATE_PRESSED_ON;
551            } else {
552                states = KEY_STATE_NORMAL_ON;
553            }
554        } else {
555            if (mSticky) {
556                if (pressed) {
557                    states = KEY_STATE_PRESSED_OFF;
558                } else {
559                    states = KEY_STATE_NORMAL_OFF;
560                }
561            } else {
562                if (pressed) {
563                    states = KEY_STATE_PRESSED;
564                }
565            }
566        }
567        return states;
568    }
569
570    public static class Spacer extends Key {
571        public Spacer(Resources res, KeyboardParams params, KeyboardBuilder.Row row,
572                XmlResourceParser parser, KeyStyles keyStyles) {
573            super(res, params, row, parser, keyStyles);
574        }
575
576        @Override
577        public boolean isSpacer() {
578            return true;
579        }
580    }
581}
582