MetaKeyKeyListener.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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 * (shift and alt) and the pseudo-meta state of selecting text.
26 * Key listeners that care about meta state should
27 * inherit from it; you should not instantiate this class directly in a client.
28 */
29
30public abstract class MetaKeyKeyListener {
31    public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
32    public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
33    public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
34
35    private static final int LOCKED_SHIFT = 8;
36
37    public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT;
38    public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT;
39    public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT;
40
41    /**
42     * @hide pending API review
43     */
44    public static final int META_SELECTING = 1 << 16;
45
46    private static final int USED_SHIFT = 24;
47
48    private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT;
49    private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT;
50    private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT;
51
52    private static final int PRESSED_SHIFT = 32;
53
54    private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT;
55    private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT;
56    private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT;
57
58    private static final int RELEASED_SHIFT = 40;
59
60    private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT;
61    private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT;
62    private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT;
63
64    private static final long META_SHIFT_MASK = META_SHIFT_ON
65            | META_CAP_LOCKED | META_CAP_USED
66            | META_CAP_PRESSED | META_CAP_RELEASED;
67    private static final long META_ALT_MASK = META_ALT_ON
68            | META_ALT_LOCKED | META_ALT_USED
69            | META_ALT_PRESSED | META_ALT_RELEASED;
70    private static final long META_SYM_MASK = META_SYM_ON
71            | META_SYM_LOCKED | META_SYM_USED
72            | META_SYM_PRESSED | META_SYM_RELEASED;
73
74    private static final Object CAP = new NoCopySpan.Concrete();
75    private static final Object ALT = new NoCopySpan.Concrete();
76    private static final Object SYM = new NoCopySpan.Concrete();
77    private static final Object SELECTING = new NoCopySpan.Concrete();
78
79    /**
80     * Resets all meta state to inactive.
81     */
82    public static void resetMetaState(Spannable text) {
83        text.removeSpan(CAP);
84        text.removeSpan(ALT);
85        text.removeSpan(SYM);
86        text.removeSpan(SELECTING);
87    }
88
89    /**
90     * Gets the state of the meta keys.
91     *
92     * @param text the buffer in which the meta key would have been pressed.
93     *
94     * @return an integer in which each bit set to one represents a pressed
95     *         or locked meta key.
96     */
97    public static final int getMetaState(CharSequence text) {
98        return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
99               getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
100               getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
101               getActive(text, SELECTING, META_SELECTING, META_SELECTING);
102    }
103
104    /**
105     * Gets the state of a particular meta key.
106     *
107     * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
108     * @param text the buffer in which the meta key would have been pressed.
109     *
110     * @return 0 if inactive, 1 if active, 2 if locked.
111     */
112    public static final int getMetaState(CharSequence text, int meta) {
113        switch (meta) {
114            case META_SHIFT_ON:
115                return getActive(text, CAP, 1, 2);
116
117            case META_ALT_ON:
118                return getActive(text, ALT, 1, 2);
119
120            case META_SYM_ON:
121                return getActive(text, SYM, 1, 2);
122
123            case META_SELECTING:
124                return getActive(text, SELECTING, 1, 2);
125
126            default:
127                return 0;
128        }
129    }
130
131    private static int getActive(CharSequence text, Object meta,
132                                 int on, int lock) {
133        if (!(text instanceof Spanned)) {
134            return 0;
135        }
136
137        Spanned sp = (Spanned) text;
138        int flag = sp.getSpanFlags(meta);
139
140        if (flag == LOCKED) {
141            return lock;
142        } else if (flag != 0) {
143            return on;
144        } else {
145            return 0;
146        }
147    }
148
149    /**
150     * Call this method after you handle a keypress so that the meta
151     * state will be reset to unshifted (if it is not still down)
152     * or primed to be reset to unshifted (once it is released).
153     */
154    public static void adjustMetaAfterKeypress(Spannable content) {
155        adjust(content, CAP);
156        adjust(content, ALT);
157        adjust(content, SYM);
158    }
159
160    /**
161     * Returns true if this object is one that this class would use to
162     * keep track of meta state in the specified text.
163     */
164    public static boolean isMetaTracker(CharSequence text, Object what) {
165        return what == CAP || what == ALT || what == SYM ||
166               what == SELECTING;
167    }
168
169    private static void adjust(Spannable content, Object what) {
170        int current = content.getSpanFlags(what);
171
172        if (current == PRESSED)
173            content.setSpan(what, 0, 0, USED);
174        else if (current == RELEASED)
175            content.removeSpan(what);
176    }
177
178    /**
179     * Call this if you are a method that ignores the locked meta state
180     * (arrow keys, for example) and you handle a key.
181     */
182    protected static void resetLockedMeta(Spannable content) {
183        resetLock(content, CAP);
184        resetLock(content, ALT);
185        resetLock(content, SYM);
186        resetLock(content, SELECTING);
187    }
188
189    private static void resetLock(Spannable content, Object what) {
190        int current = content.getSpanFlags(what);
191
192        if (current == LOCKED)
193            content.removeSpan(what);
194    }
195
196    /**
197     * Handles presses of the meta keys.
198     */
199    public boolean onKeyDown(View view, Editable content,
200                             int keyCode, KeyEvent event) {
201        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
202            press(content, CAP);
203            return true;
204        }
205
206        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
207                || keyCode == KeyEvent.KEYCODE_NUM) {
208            press(content, ALT);
209            return true;
210        }
211
212        if (keyCode == KeyEvent.KEYCODE_SYM) {
213            press(content, SYM);
214            return true;
215        }
216
217        return false; // no super to call through to
218    }
219
220    private void press(Editable content, Object what) {
221        int state = content.getSpanFlags(what);
222
223        if (state == PRESSED)
224            ; // repeat before use
225        else if (state == RELEASED)
226            content.setSpan(what, 0, 0, LOCKED);
227        else if (state == USED)
228            ; // repeat after use
229        else if (state == LOCKED)
230            content.removeSpan(what);
231        else
232            content.setSpan(what, 0, 0, PRESSED);
233    }
234
235    /**
236     * Start selecting text.
237     * @hide pending API review
238     */
239    public static void startSelecting(View view, Spannable content) {
240        content.setSpan(SELECTING, 0, 0, PRESSED);
241    }
242
243    /**
244     * Stop selecting text.  This does not actually collapse the selection;
245     * call {@link android.text.Selection#setSelection} too.
246     * @hide pending API review
247     */
248    public static void stopSelecting(View view, Spannable content) {
249        content.removeSpan(SELECTING);
250    }
251
252    /**
253     * Handles release of the meta keys.
254     */
255    public boolean onKeyUp(View view, Editable content, int keyCode,
256                                    KeyEvent event) {
257        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
258            release(content, CAP);
259            return true;
260        }
261
262        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
263                || keyCode == KeyEvent.KEYCODE_NUM) {
264            release(content, ALT);
265            return true;
266        }
267
268        if (keyCode == KeyEvent.KEYCODE_SYM) {
269            release(content, SYM);
270            return true;
271        }
272
273        return false; // no super to call through to
274    }
275
276    private void release(Editable content, Object what) {
277        int current = content.getSpanFlags(what);
278
279        if (current == USED)
280            content.removeSpan(what);
281        else if (current == PRESSED)
282            content.setSpan(what, 0, 0, RELEASED);
283    }
284
285    public void clearMetaKeyState(View view, Editable content, int states) {
286        clearMetaKeyState(content, states);
287    }
288
289    public static void clearMetaKeyState(Editable content, int states) {
290        if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
291        if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
292        if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
293        if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
294    }
295
296    /**
297     * Call this if you are a method that ignores the locked meta state
298     * (arrow keys, for example) and you handle a key.
299     */
300    public static long resetLockedMeta(long state) {
301        state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
302        state = resetLock(state, META_ALT_ON, META_ALT_MASK);
303        state = resetLock(state, META_SYM_ON, META_SYM_MASK);
304        return state;
305    }
306
307    private static long resetLock(long state, int what, long mask) {
308        if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) {
309            state &= ~mask;
310        }
311        return state;
312    }
313
314    // ---------------------------------------------------------------------
315    // Version of API that operates on a state bit mask
316    // ---------------------------------------------------------------------
317
318    /**
319     * Gets the state of the meta keys.
320     *
321     * @param state the current meta state bits.
322     *
323     * @return an integer in which each bit set to one represents a pressed
324     *         or locked meta key.
325     */
326    public static final int getMetaState(long state) {
327        return getActive(state, META_SHIFT_ON, META_SHIFT_ON, META_CAP_LOCKED) |
328               getActive(state, META_ALT_ON, META_ALT_ON, META_ALT_LOCKED) |
329               getActive(state, META_SYM_ON, META_SYM_ON, META_SYM_LOCKED);
330    }
331
332    /**
333     * Gets the state of a particular meta key.
334     *
335     * @param state the current state bits.
336     * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
337     *
338     * @return 0 if inactive, 1 if active, 2 if locked.
339     */
340    public static final int getMetaState(long state, int meta) {
341        switch (meta) {
342            case META_SHIFT_ON:
343                return getActive(state, meta, 1, 2);
344
345            case META_ALT_ON:
346                return getActive(state, meta, 1, 2);
347
348            case META_SYM_ON:
349                return getActive(state, meta, 1, 2);
350
351            default:
352                return 0;
353        }
354    }
355
356    private static int getActive(long state, int meta, int on, int lock) {
357        if ((state&(meta<<LOCKED_SHIFT)) != 0) {
358            return lock;
359        } else if ((state&meta) != 0) {
360            return on;
361        } else {
362            return 0;
363        }
364    }
365
366    /**
367     * Call this method after you handle a keypress so that the meta
368     * state will be reset to unshifted (if it is not still down)
369     * or primed to be reset to unshifted (once it is released).  Takes
370     * the current state, returns the new state.
371     */
372    public static long adjustMetaAfterKeypress(long state) {
373        state = adjust(state, META_SHIFT_ON, META_SHIFT_MASK);
374        state = adjust(state, META_ALT_ON, META_ALT_MASK);
375        state = adjust(state, META_SYM_ON, META_SYM_MASK);
376        return state;
377    }
378
379    private static long adjust(long state, int what, long mask) {
380        if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
381            return (state&~mask) | what | ((long)what)<<USED_SHIFT;
382        else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
383            return state & ~mask;
384        return state;
385    }
386
387    /**
388     * Handles presses of the meta keys.
389     */
390    public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
391        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
392            return press(state, META_SHIFT_ON, META_SHIFT_MASK);
393        }
394
395        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
396                || keyCode == KeyEvent.KEYCODE_NUM) {
397            return press(state, META_ALT_ON, META_ALT_MASK);
398        }
399
400        if (keyCode == KeyEvent.KEYCODE_SYM) {
401            return press(state, META_SYM_ON, META_SYM_MASK);
402        }
403
404        return state;
405    }
406
407    private static long press(long state, int what, long mask) {
408        if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
409            ; // repeat before use
410        else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
411            state = (state&~mask) | what | (((long)what) << LOCKED_SHIFT);
412        else if ((state&(((long)what)<<USED_SHIFT)) != 0)
413            ; // repeat after use
414        else if ((state&(((long)what)<<LOCKED_SHIFT)) != 0)
415            state = state&~mask;
416        else
417            state = state | what | (((long)what)<<PRESSED_SHIFT);
418        return state;
419    }
420
421    /**
422     * Handles release of the meta keys.
423     */
424    public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
425        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
426            return release(state, META_SHIFT_ON, META_SHIFT_MASK);
427        }
428
429        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
430                || keyCode == KeyEvent.KEYCODE_NUM) {
431            return release(state, META_ALT_ON, META_ALT_MASK);
432        }
433
434        if (keyCode == KeyEvent.KEYCODE_SYM) {
435            return release(state, META_SYM_ON, META_SYM_MASK);
436        }
437
438        return state;
439    }
440
441    private static long release(long state, int what, long mask) {
442        if ((state&(((long)what)<<USED_SHIFT)) != 0)
443            state = state&~mask;
444        else if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
445            state = state | what | (((long)what)<<RELEASED_SHIFT);
446        return state;
447    }
448
449    public long clearMetaKeyState(long state, int which) {
450        if ((which&META_SHIFT_ON) != 0)
451            state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
452        if ((which&META_ALT_ON) != 0)
453            state = resetLock(state, META_ALT_ON, META_ALT_MASK);
454        if ((which&META_SYM_ON) != 0)
455            state = resetLock(state, META_SYM_ON, META_SYM_MASK);
456        return state;
457    }
458
459    /**
460     * The meta key has been pressed but has not yet been used.
461     */
462    private static final int PRESSED =
463        Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
464
465    /**
466     * The meta key has been pressed and released but has still
467     * not yet been used.
468     */
469    private static final int RELEASED =
470        Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
471
472    /**
473     * The meta key has been pressed and used but has not yet been released.
474     */
475    private static final int USED =
476        Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
477
478    /**
479     * The meta key has been pressed and released without use, and then
480     * pressed again; it may also have been released again.
481     */
482    private static final int LOCKED =
483        Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
484}
485
486