MetaKeyKeyListener.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/*
2 * Copyright (C) 2006 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 android.text.method;
18
19import android.view.KeyEvent;
20import android.view.View;
21import android.text.*;
22
23/**
24 * This base class encapsulates the behavior for handling the meta keys
25 * (caps, fn, sym).  Key listener that care about meta state should
26 * inherit from it; you should not instantiate this class directly in a client.
27 */
28
29public abstract class MetaKeyKeyListener {
30    public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
31    public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
32    public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
33
34    public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8;
35    public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8;
36    public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8;
37
38    private static final Object CAP = new Object();
39    private static final Object ALT = new Object();
40    private static final Object SYM = new Object();
41
42    /**
43     * Resets all meta state to inactive.
44     */
45    public static void resetMetaState(Spannable text) {
46        text.removeSpan(CAP);
47        text.removeSpan(ALT);
48        text.removeSpan(SYM);
49    }
50
51    /**
52     * Gets the state of the meta keys.
53     *
54     * @param text the buffer in which the meta key would have been pressed.
55     *
56     * @return an integer in which each bit set to one represents a pressed
57     *         or locked meta key.
58     */
59    public static final int getMetaState(CharSequence text) {
60        return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
61               getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
62               getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED);
63    }
64
65    /**
66     * Gets the state of a particular meta key.
67     *
68     * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
69     * @param text the buffer in which the meta key would have been pressed.
70     *
71     * @return 0 if inactive, 1 if active, 2 if locked.
72     */
73    public static final int getMetaState(CharSequence text, int meta) {
74        switch (meta) {
75            case META_SHIFT_ON:
76                return getActive(text, CAP, 1, 2);
77
78            case META_ALT_ON:
79                return getActive(text, ALT, 1, 2);
80
81            case META_SYM_ON:
82                return getActive(text, SYM, 1, 2);
83
84            default:
85                return 0;
86        }
87    }
88
89    private static int getActive(CharSequence text, Object meta,
90                                 int on, int lock) {
91        if (!(text instanceof Spanned)) {
92            return 0;
93        }
94
95        Spanned sp = (Spanned) text;
96        int flag = sp.getSpanFlags(meta);
97
98        if (flag == LOCKED) {
99            return lock;
100        } else if (flag != 0) {
101            return on;
102        } else {
103            return 0;
104        }
105    }
106
107    /**
108     * Call this method after you handle a keypress so that the meta
109     * state will be reset to unshifted (if it is not still down)
110     * or primed to be reset to unshifted (once it is released).
111     */
112    public static void adjustMetaAfterKeypress(Spannable content) {
113        adjust(content, CAP);
114        adjust(content, ALT);
115        adjust(content, SYM);
116    }
117
118    /**
119     * Returns true if this object is one that this class would use to
120     * keep track of meta state in the specified text.
121     */
122    public static boolean isMetaTracker(CharSequence text, Object what) {
123        return what == CAP || what == ALT || what == SYM;
124    }
125
126    private static void adjust(Spannable content, Object what) {
127        int current = content.getSpanFlags(what);
128
129        if (current == PRESSED)
130            content.setSpan(what, 0, 0, USED);
131        else if (current == RELEASED)
132            content.removeSpan(what);
133    }
134
135    /**
136     * Call this if you are a method that ignores the locked meta state
137     * (arrow keys, for example) and you handle a key.
138     */
139    protected static void resetLockedMeta(Spannable content) {
140        resetLock(content, CAP);
141        resetLock(content, ALT);
142        resetLock(content, SYM);
143    }
144
145    private static void resetLock(Spannable content, Object what) {
146        int current = content.getSpanFlags(what);
147
148        if (current == LOCKED)
149            content.removeSpan(what);
150    }
151
152    /**
153     * Handles presses of the meta keys.
154     */
155    public boolean onKeyDown(View view, Editable content,
156                             int keyCode, KeyEvent event) {
157        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
158            press(content, CAP);
159            return true;
160        }
161
162        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
163                || keyCode == KeyEvent.KEYCODE_NUM) {
164            press(content, ALT);
165            return true;
166        }
167
168        if (keyCode == KeyEvent.KEYCODE_SYM) {
169            press(content, SYM);
170            return true;
171        }
172
173        return false; // no super to call through to
174    }
175
176    private void press(Editable content, Object what) {
177        int state = content.getSpanFlags(what);
178
179        if (state == PRESSED)
180            ; // repeat before use
181        else if (state == RELEASED)
182            content.setSpan(what, 0, 0, LOCKED);
183        else if (state == USED)
184            ; // repeat after use
185        else if (state == LOCKED)
186            content.removeSpan(what);
187        else
188            content.setSpan(what, 0, 0, PRESSED);
189    }
190
191    /**
192     * Handles release of the meta keys.
193     */
194    public boolean onKeyUp(View view, Editable content, int keyCode,
195                                    KeyEvent event) {
196        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
197            release(content, CAP);
198            return true;
199        }
200
201        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
202                || keyCode == KeyEvent.KEYCODE_NUM) {
203            release(content, ALT);
204            return true;
205        }
206
207        if (keyCode == KeyEvent.KEYCODE_SYM) {
208            release(content, SYM);
209            return true;
210        }
211
212        return false; // no super to call through to
213    }
214
215    private void release(Editable content, Object what) {
216        int current = content.getSpanFlags(what);
217
218        if (current == USED)
219            content.removeSpan(what);
220        else if (current == PRESSED)
221            content.setSpan(what, 0, 0, RELEASED);
222    }
223
224    /**
225     * The meta key has been pressed but has not yet been used.
226     */
227    private static final int PRESSED =
228        Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
229
230    /**
231     * The meta key has been pressed and released but has still
232     * not yet been used.
233     */
234    private static final int RELEASED =
235        Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
236
237    /**
238     * The meta key has been pressed and used but has not yet been released.
239     */
240    private static final int USED =
241        Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
242
243    /**
244     * The meta key has been pressed and released without use, and then
245     * pressed again; it may also have been released again.
246     */
247    private static final int LOCKED =
248        Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
249}
250
251