OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / text / method / QwertyKeyListener.java
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
17 package android.text.method;
18
19 import android.text.*;
20 import android.text.method.TextKeyListener.Capitalize;
21 import android.util.SparseArray;
22 import android.view.KeyCharacterMap;
23 import android.view.KeyEvent;
24 import android.view.View;
25
26 /**
27  * This is the standard key listener for alphabetic input on qwerty
28  * keyboards.  You should generally not need to instantiate this yourself;
29  * TextKeyListener will do it for you.
30  */
31 public class QwertyKeyListener extends BaseKeyListener {
32     private static QwertyKeyListener[] sInstance =
33         new QwertyKeyListener[Capitalize.values().length * 2];
34
35     public QwertyKeyListener(Capitalize cap, boolean autotext) {
36         mAutoCap = cap;
37         mAutoText = autotext;
38     }
39
40     /**
41      * Returns a new or existing instance with the specified capitalization
42      * and correction properties.
43      */
44     public static QwertyKeyListener getInstance(boolean autotext,
45                                               Capitalize cap) {
46         int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
47
48         if (sInstance[off] == null) {
49             sInstance[off] = new QwertyKeyListener(cap, autotext);
50         }
51
52         return sInstance[off];
53     }
54
55     public int getInputType() {
56         return makeTextContentType(mAutoCap, mAutoText);
57     }
58     
59     public boolean onKeyDown(View view, Editable content,
60                              int keyCode, KeyEvent event) {
61         int selStart, selEnd;
62         int pref = 0;
63
64         if (view != null) {
65             pref = TextKeyListener.getInstance().getPrefs(view.getContext());
66         }
67
68         {
69             int a = Selection.getSelectionStart(content);
70             int b = Selection.getSelectionEnd(content);
71
72             selStart = Math.min(a, b);
73             selEnd = Math.max(a, b);
74
75             if (selStart < 0 || selEnd < 0) {
76                 selStart = selEnd = 0;
77                 Selection.setSelection(content, 0, 0);
78             }
79         }
80
81         int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
82         int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);
83
84         // QWERTY keyboard normal case
85
86         int i = event.getUnicodeChar(getMetaState(content));
87
88         int count = event.getRepeatCount();
89         if (count > 0 && selStart == selEnd && selStart > 0) {
90             char c = content.charAt(selStart - 1);
91
92             if (c == i || c == Character.toUpperCase(i) && view != null) {
93                 if (showCharacterPicker(view, content, c, false, count)) {
94                     resetMetaState(content);
95                     return true;
96                 }
97             }
98         }
99
100         if (i == KeyCharacterMap.PICKER_DIALOG_INPUT) {
101             if (view != null) {
102                 showCharacterPicker(view, content,
103                                     KeyCharacterMap.PICKER_DIALOG_INPUT, true, 1);
104             }
105             resetMetaState(content);
106             return true;
107         }
108
109         if (i == KeyCharacterMap.HEX_INPUT) {
110             int start;
111
112             if (selStart == selEnd) {
113                 start = selEnd;
114
115                 while (start > 0 && selEnd - start < 4 &&
116                        Character.digit(content.charAt(start - 1), 16) >= 0) {
117                     start--;
118                 }
119             } else {
120                 start = selStart;
121             }
122
123             int ch = -1;
124             try {
125                 String hex = TextUtils.substring(content, start, selEnd);
126                 ch = Integer.parseInt(hex, 16);
127             } catch (NumberFormatException nfe) { }
128
129             if (ch >= 0) {
130                 selStart = start;
131                 Selection.setSelection(content, selStart, selEnd);
132                 i = ch;
133             } else {
134                 i = 0;
135             }
136         }
137
138         if (i != 0) {
139             boolean dead = false;
140
141             if ((i & KeyCharacterMap.COMBINING_ACCENT) != 0) {
142                 dead = true;
143                 i = i & KeyCharacterMap.COMBINING_ACCENT_MASK;
144             }
145
146             if (activeStart == selStart && activeEnd == selEnd) {
147                 boolean replace = false;
148
149                 if (selEnd - selStart - 1 == 0) {
150                     char accent = content.charAt(selStart);
151                     int composed = event.getDeadChar(accent, i);
152
153                     if (composed != 0) {
154                         i = composed;
155                         replace = true;
156                     }
157                 }
158
159                 if (!replace) {
160                     Selection.setSelection(content, selEnd);
161                     content.removeSpan(TextKeyListener.ACTIVE);
162                     selStart = selEnd;
163                 }
164             }
165
166             if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
167                 Character.isLowerCase(i) && 
168                 TextKeyListener.shouldCap(mAutoCap, content, selStart)) {
169                 int where = content.getSpanEnd(TextKeyListener.CAPPED);
170                 int flags = content.getSpanFlags(TextKeyListener.CAPPED);
171
172                 if (where == selStart && (((flags >> 16) & 0xFFFF) == i)) {
173                     content.removeSpan(TextKeyListener.CAPPED);
174                 } else {
175                     flags = i << 16;
176                     i = Character.toUpperCase(i);
177
178                     if (selStart == 0)
179                         content.setSpan(TextKeyListener.CAPPED, 0, 0,
180                                         Spannable.SPAN_MARK_MARK | flags);
181                     else
182                         content.setSpan(TextKeyListener.CAPPED,
183                                         selStart - 1, selStart,
184                                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
185                                         flags);
186                 }
187             }
188
189             if (selStart != selEnd) {
190                 Selection.setSelection(content, selEnd);
191             }
192             content.setSpan(OLD_SEL_START, selStart, selStart,
193                             Spannable.SPAN_MARK_MARK);
194
195             content.replace(selStart, selEnd, String.valueOf((char) i));
196
197             int oldStart = content.getSpanStart(OLD_SEL_START);
198             selEnd = Selection.getSelectionEnd(content);
199
200             if (oldStart < selEnd) {
201                 content.setSpan(TextKeyListener.LAST_TYPED,
202                                 oldStart, selEnd,
203                                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
204
205                 if (dead) {
206                     Selection.setSelection(content, oldStart, selEnd);
207                     content.setSpan(TextKeyListener.ACTIVE, oldStart, selEnd,
208                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
209                 }
210             }
211
212             adjustMetaAfterKeypress(content);
213
214             // potentially do autotext replacement if the character
215             // that was typed was an autotext terminator
216
217             if ((pref & TextKeyListener.AUTO_TEXT) != 0 && mAutoText &&
218                 (i == ' ' || i == '\t' || i == '\n' ||
219                  i == ',' || i == '.' || i == '!' || i == '?' ||
220                  i == '"' || Character.getType(i) == Character.END_PUNCTUATION) &&
221                  content.getSpanEnd(TextKeyListener.INHIBIT_REPLACEMENT)
222                      != oldStart) {
223                 int x;
224
225                 for (x = oldStart; x > 0; x--) {
226                     char c = content.charAt(x - 1);
227                     if (c != '\'' && !Character.isLetter(c)) {
228                         break;
229                     }
230                 }
231
232                 String rep = getReplacement(content, x, oldStart, view);
233
234                 if (rep != null) {
235                     Replaced[] repl = content.getSpans(0, content.length(),
236                                                      Replaced.class);
237                     for (int a = 0; a < repl.length; a++)
238                         content.removeSpan(repl[a]);
239
240                     char[] orig = new char[oldStart - x];
241                     TextUtils.getChars(content, x, oldStart, orig, 0);
242
243                     content.setSpan(new Replaced(orig), x, oldStart,
244                                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
245                     content.replace(x, oldStart, rep);
246                 }
247             }
248
249             // Replace two spaces by a period and a space.
250
251             if ((pref & TextKeyListener.AUTO_PERIOD) != 0 && mAutoText) {
252                 selEnd = Selection.getSelectionEnd(content);
253                 if (selEnd - 3 >= 0) {
254                     if (content.charAt(selEnd - 1) == ' ' &&
255                         content.charAt(selEnd - 2) == ' ') {
256                         char c = content.charAt(selEnd - 3);
257
258                         for (int j = selEnd - 3; j > 0; j--) {
259                             if (c == '"' ||
260                                 Character.getType(c) == Character.END_PUNCTUATION) {
261                                 c = content.charAt(j - 1);
262                             } else {
263                                 break;
264                             }
265                         }
266
267                         if (Character.isLetter(c) || Character.isDigit(c)) {
268                             content.replace(selEnd - 2, selEnd - 1, ".");
269                         }
270                     }
271                 }
272             }
273
274             return true;
275         } else if (keyCode == KeyEvent.KEYCODE_DEL && selStart == selEnd) {
276             // special backspace case for undoing autotext
277
278             int consider = 1;
279
280             // if backspacing over the last typed character,
281             // it undoes the autotext prior to that character
282             // (unless the character typed was newline, in which
283             // case this behavior would be confusing)
284
285             if (content.getSpanEnd(TextKeyListener.LAST_TYPED) == selStart) {
286                 if (content.charAt(selStart - 1) != '\n')
287                     consider = 2;
288             }
289
290             Replaced[] repl = content.getSpans(selStart - consider, selStart,
291                                              Replaced.class);
292
293             if (repl.length > 0) {
294                 int st = content.getSpanStart(repl[0]);
295                 int en = content.getSpanEnd(repl[0]);
296                 String old = new String(repl[0].mText);
297
298                 content.removeSpan(repl[0]);
299
300                 // only cancel the autocomplete if the cursor is at the end of
301                 // the replaced span (or after it, because the user is
302                 // backspacing over the space after the word, not the word
303                 // itself).
304                 if (selStart >= en) {
305                     content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
306                                     en, en, Spannable.SPAN_POINT_POINT);
307                     content.replace(st, en, old);
308
309                     en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
310                     if (en - 1 >= 0) {
311                         content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
312                                         en - 1, en,
313                                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
314                     } else {
315                         content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
316                     }
317                     adjustMetaAfterKeypress(content);
318                 } else {
319                     adjustMetaAfterKeypress(content);
320                     return super.onKeyDown(view, content, keyCode, event);
321                 }
322
323                 return true;
324             }
325         }
326
327         return super.onKeyDown(view, content, keyCode, event);
328     }
329
330     private String getReplacement(CharSequence src, int start, int end,
331                                   View view) {
332         int len = end - start;
333         boolean changecase = false;
334         
335         String replacement = AutoText.get(src, start, end, view);
336         
337         if (replacement == null) {
338             String key = TextUtils.substring(src, start, end).toLowerCase();
339             replacement = AutoText.get(key, 0, end - start, view);
340             changecase = true;
341
342             if (replacement == null)
343                 return null;
344         }
345         
346         int caps = 0;
347
348         if (changecase) {
349             for (int j = start; j < end; j++) {
350                 if (Character.isUpperCase(src.charAt(j)))
351                     caps++;
352             }
353         }
354
355         String out;
356
357         if (caps == 0)
358             out = replacement;
359         else if (caps == 1)
360             out = toTitleCase(replacement);
361         else if (caps == len)
362             out = replacement.toUpperCase();
363         else
364             out = toTitleCase(replacement);
365
366         if (out.length() == len &&
367             TextUtils.regionMatches(src, start, out, 0, len))
368             return null;
369
370         return out;
371     }
372
373     /**
374      * Marks the specified region of <code>content</code> as having
375      * contained <code>original</code> prior to AutoText replacement.
376      * Call this method when you have done or are about to do an
377      * AutoText-style replacement on a region of text and want to let
378      * the same mechanism (the user pressing DEL immediately after the
379      * change) undo the replacement.
380      *
381      * @param content the Editable text where the replacement was made
382      * @param start the start of the replaced region
383      * @param end the end of the replaced region; the location of the cursor
384      * @param original the text to be restored if the user presses DEL
385      */
386     public static void markAsReplaced(Spannable content, int start, int end,
387                                       String original) {
388         Replaced[] repl = content.getSpans(0, content.length(), Replaced.class);
389         for (int a = 0; a < repl.length; a++) {
390             content.removeSpan(repl[a]);
391         }
392
393         int len = original.length();
394         char[] orig = new char[len];
395         original.getChars(0, len, orig, 0);
396
397         content.setSpan(new Replaced(orig), start, end,
398                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
399     }
400
401     private static SparseArray<String> PICKER_SETS =
402                         new SparseArray<String>();
403     static {
404         PICKER_SETS.put('A', "\u00C0\u00C1\u00C2\u00C4\u00C6\u00C3\u00C5\u0104\u0100");
405         PICKER_SETS.put('C', "\u00C7\u0106\u010C");
406         PICKER_SETS.put('D', "\u010E");
407         PICKER_SETS.put('E', "\u00C8\u00C9\u00CA\u00CB\u0118\u011A\u0112");
408         PICKER_SETS.put('G', "\u011E");
409         PICKER_SETS.put('L', "\u0141");
410         PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF\u012A\u0130");
411         PICKER_SETS.put('N', "\u00D1\u0143\u0147");
412         PICKER_SETS.put('O', "\u00D8\u0152\u00D5\u00D2\u00D3\u00D4\u00D6\u014C");
413         PICKER_SETS.put('R', "\u0158");
414         PICKER_SETS.put('S', "\u015A\u0160\u015E");
415         PICKER_SETS.put('T', "\u0164");
416         PICKER_SETS.put('U', "\u00D9\u00DA\u00DB\u00DC\u016E\u016A");
417         PICKER_SETS.put('Y', "\u00DD\u0178");
418         PICKER_SETS.put('Z', "\u0179\u017B\u017D");
419         PICKER_SETS.put('a', "\u00E0\u00E1\u00E2\u00E4\u00E6\u00E3\u00E5\u0105\u0101");
420         PICKER_SETS.put('c', "\u00E7\u0107\u010D");
421         PICKER_SETS.put('d', "\u010F");
422         PICKER_SETS.put('e', "\u00E8\u00E9\u00EA\u00EB\u0119\u011B\u0113");
423         PICKER_SETS.put('g', "\u011F");
424         PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF\u012B\u0131");
425         PICKER_SETS.put('l', "\u0142");
426         PICKER_SETS.put('n', "\u00F1\u0144\u0148");
427         PICKER_SETS.put('o', "\u00F8\u0153\u00F5\u00F2\u00F3\u00F4\u00F6\u014D");
428         PICKER_SETS.put('r', "\u0159");
429         PICKER_SETS.put('s', "\u00A7\u00DF\u015B\u0161\u015F");
430         PICKER_SETS.put('t', "\u0165");
431         PICKER_SETS.put('u', "\u00F9\u00FA\u00FB\u00FC\u016F\u016B");
432         PICKER_SETS.put('y', "\u00FD\u00FF");
433         PICKER_SETS.put('z', "\u017A\u017C\u017E");
434         PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT,
435                              "\u2026\u00A5\u2022\u00AE\u00A9\u00B1[]{}\\|");
436         PICKER_SETS.put('/', "\\");
437
438         // From packages/inputmethods/LatinIME/res/xml/kbd_symbols.xml
439
440         PICKER_SETS.put('1', "\u00b9\u00bd\u2153\u00bc\u215b");
441         PICKER_SETS.put('2', "\u00b2\u2154");
442         PICKER_SETS.put('3', "\u00b3\u00be\u215c");
443         PICKER_SETS.put('4', "\u2074");
444         PICKER_SETS.put('5', "\u215d");
445         PICKER_SETS.put('7', "\u215e");
446         PICKER_SETS.put('0', "\u207f\u2205");
447         PICKER_SETS.put('$', "\u00a2\u00a3\u20ac\u00a5\u20a3\u20a4\u20b1");
448         PICKER_SETS.put('%', "\u2030");
449         PICKER_SETS.put('*', "\u2020\u2021");
450         PICKER_SETS.put('-', "\u2013\u2014");
451         PICKER_SETS.put('+', "\u00b1");
452         PICKER_SETS.put('(', "[{<");
453         PICKER_SETS.put(')', "]}>");
454         PICKER_SETS.put('!', "\u00a1");
455         PICKER_SETS.put('"', "\u201c\u201d\u00ab\u00bb\u02dd");
456         PICKER_SETS.put('?', "\u00bf");
457         PICKER_SETS.put(',', "\u201a\u201e");
458
459         // From packages/inputmethods/LatinIME/res/xml/kbd_symbols_shift.xml
460
461         PICKER_SETS.put('=', "\u2260\u2248\u221e");
462         PICKER_SETS.put('<', "\u2264\u00ab\u2039");
463         PICKER_SETS.put('>', "\u2265\u00bb\u203a");
464     };
465
466     private boolean showCharacterPicker(View view, Editable content, char c,
467                                         boolean insert, int count) {
468         String set = PICKER_SETS.get(c);
469         if (set == null) {
470             return false;
471         }
472
473         if (count == 1) {
474             new CharacterPickerDialog(view.getContext(),
475                                       view, content, set, insert).show();
476         }
477
478         return true;
479     }
480
481     private static String toTitleCase(String src) {
482         return Character.toUpperCase(src.charAt(0)) + src.substring(1);
483     }
484
485     /* package */ static class Replaced implements NoCopySpan
486     {
487         public Replaced(char[] text) {
488             mText = text;
489         }
490
491         private char[] mText;
492     }
493
494     private Capitalize mAutoCap;
495     private boolean mAutoText;
496 }
497