c10499700fc2dc2c14dbd809b922bc246e193181
[jvcard.git] / src / com / googlecode / lanterna / input / EscapeSequenceCharacterPattern.java
1 package com.googlecode.lanterna.input;
2
3 import static com.googlecode.lanterna.input.KeyDecodingProfile.ESC_CODE;
4
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8
9 /**
10 * This implementation of CharacterPattern matches two similar patterns
11 * of Escape sequences, that many terminals produce for special keys.<p>
12 *
13 * These sequences all start with Escape, followed by either an open bracket
14 * or a capital letter O (these two are treated as equivalent).<p>
15 *
16 * Then follows a list of zero or up to two decimals separated by a
17 * semicolon, and a non-digit last character.<p>
18 *
19 * If the last character is a tilde (~) then the first number defines
20 * the key (through stdMap), otherwise the last character itself defines
21 * the key (through finMap).<p>
22 *
23 * The second number, if provided by the terminal, specifies the modifier
24 * state (shift,alt,ctrl). The value is 1 + sum(modifiers), where shift is 1,
25 * alt is 2 and ctrl is 4.<p>
26 *
27 * The two maps stdMap and finMap can be customized in subclasses to add,
28 * remove or replace keys - to support non-standard Terminals.<p>
29 *
30 * Examples: (on a gnome terminal)<br>
31 * ArrowUp is "Esc [ A"; Alt-ArrowUp is "Esc [ 1 ; 3 A"<br>
32 * both are handled by finMap mapping 'A' to ArrowUp <br><br>
33 * F6 is "Esc [ 1 7 ~"; Ctrl-Shift-F6 is "Esc [ 1 7 ; 6 R"<br>
34 * both are handled by stdMap mapping 17 to F6 <br><br>
35 *
36 * @author Andreas
37 *
38 */
39 public class EscapeSequenceCharacterPattern implements CharacterPattern {
40 // state machine used to match key sequence:
41 private enum State {
42 START, INTRO, NUM1, NUM2, DONE
43 }
44 // bit-values for modifier keys: only used internally
45 public static final int SHIFT = 1, ALT = 2, CTRL = 4;
46
47 /**
48 * Map of recognized "standard pattern" sequences:<br>
49 * e.g.: 24 -&gt; F12 : "Esc [ <b>24</b> ~"
50 */
51 protected final Map<Integer, KeyType> stdMap = new HashMap<Integer, KeyType>();
52 /**
53 * Map of recognized "finish pattern" sequences:<br>
54 * e.g.: 'A' -&gt; ArrowUp : "Esc [ <b>A</b>"
55 */
56 protected final Map<Character, KeyType> finMap = new HashMap<Character, KeyType>();
57 /**
58 * A flag to control, whether an Esc-prefix for an Esc-sequence is to be treated
59 * as Alt-pressed. Some Terminals (e.g. putty) report the Alt-modifier like that.<p>
60 * If the application is e.g. more interested in seeing separate Escape and plain
61 * Arrow keys, then it should replace this class by a subclass that sets this flag
62 * to false. (It might then also want to remove the CtrlAltAndCharacterPattern.)
63 */
64 protected boolean useEscEsc = true;
65
66 /**
67 * Create an instance with a standard set of mappings.
68 */
69 public EscapeSequenceCharacterPattern() {
70 finMap.put('A', KeyType.ArrowUp);
71 finMap.put('B', KeyType.ArrowDown);
72 finMap.put('C', KeyType.ArrowRight);
73 finMap.put('D', KeyType.ArrowLeft);
74 finMap.put('E', KeyType.Unknown); // gnome-terminal center key on numpad
75 finMap.put('G', KeyType.Unknown); // putty center key on numpad
76 finMap.put('H', KeyType.Home);
77 finMap.put('F', KeyType.End);
78 finMap.put('P', KeyType.F1);
79 finMap.put('Q', KeyType.F2);
80 finMap.put('R', KeyType.F3);
81 finMap.put('S', KeyType.F4);
82 finMap.put('Z', KeyType.ReverseTab);
83
84 stdMap.put(1, KeyType.Home);
85 stdMap.put(2, KeyType.Insert);
86 stdMap.put(3, KeyType.Delete);
87 stdMap.put(4, KeyType.End);
88 stdMap.put(5, KeyType.PageUp);
89 stdMap.put(6, KeyType.PageDown);
90 stdMap.put(11, KeyType.F1);
91 stdMap.put(12, KeyType.F2);
92 stdMap.put(13, KeyType.F3);
93 stdMap.put(14, KeyType.F4);
94 stdMap.put(15, KeyType.F5);
95 stdMap.put(16, KeyType.F5);
96 stdMap.put(17, KeyType.F6);
97 stdMap.put(18, KeyType.F7);
98 stdMap.put(19, KeyType.F8);
99 stdMap.put(20, KeyType.F9);
100 stdMap.put(21, KeyType.F10);
101 stdMap.put(23, KeyType.F11);
102 stdMap.put(24, KeyType.F12);
103 stdMap.put(25, KeyType.F13);
104 stdMap.put(26, KeyType.F14);
105 stdMap.put(28, KeyType.F15);
106 stdMap.put(29, KeyType.F16);
107 stdMap.put(31, KeyType.F17);
108 stdMap.put(32, KeyType.F18);
109 stdMap.put(33, KeyType.F19);
110 }
111
112 /**
113 * combines a KeyType and modifiers into a KeyStroke.
114 * Subclasses can override this for customization purposes.
115 *
116 * @param key the KeyType as determined by parsing the sequence.
117 * It will be null, if the pattern looked like a key sequence but wasn't
118 * identified.
119 * @param mods the bitmask of the modifer keys pressed along with the key.
120 * @return either null (to report mis-match), or a valid KeyStroke.
121 */
122 protected KeyStroke getKeyStroke(KeyType key, int mods) {
123 boolean bShift = false, bCtrl = false, bAlt = false;
124 if (key == null) { return null; } // alternative: key = KeyType.Unknown;
125 if (mods >= 0) { // only use when non-negative!
126 bShift = (mods & SHIFT) != 0;
127 bAlt = (mods & ALT) != 0;
128 bCtrl = (mods & CTRL) != 0;
129 }
130 return new KeyStroke( key , bCtrl, bAlt, bShift);
131 }
132
133 /**
134 * combines the raw parts of the sequence into a KeyStroke.
135 * This method does not check the first char, but overrides may do so.
136 *
137 * @param first the char following after Esc in the sequence (either [ or O)
138 * @param num1 the first decimal, or 0 if not in the sequence
139 * @param num2 the second decimal, or 0 if not in the sequence
140 * @param last the terminating char.
141 * @param bEsc whether an extra Escape-prefix was found.
142 * @return either null (to report mis-match), or a valid KeyStroke.
143 */
144 protected KeyStroke getKeyStrokeRaw(char first,int num1,int num2,char last,boolean bEsc) {
145 KeyType kt = null; boolean bPuttyCtrl = false;
146 if (last == '~' && stdMap.containsKey(num1)) {
147 kt = stdMap.get(num1);
148 } else if (finMap.containsKey(last)) {
149 kt = finMap.get(last);
150 // Putty sends ^[OA for ctrl arrow-up, ^[[A for plain arrow-up:
151 // but only for A-D -- other ^[O... sequences are just plain keys
152 if (first == 'O' && last >= 'A' && last <= 'D') { bPuttyCtrl = true; }
153 // if we ever stumble into "keypad-mode", then it will end up inverted.
154 } else {
155 kt = null; // unknown key.
156 }
157 int mods = num2 - 1;
158 if (bEsc) {
159 if (mods >= 0) { mods |= ALT; }
160 else { mods = ALT; }
161 }
162 if (bPuttyCtrl) {
163 if (mods >= 0) { mods |= CTRL; }
164 else { mods = CTRL; }
165 }
166 return getKeyStroke( kt, mods );
167 }
168
169 @Override
170 public Matching match(List<Character> cur) {
171 State state = State.START;
172 int num1 = 0, num2 = 0;
173 char first = '\0', last = '\0';
174 boolean bEsc = false;
175
176 for (char ch : cur) {
177 switch (state) {
178 case START:
179 if (ch != ESC_CODE) {
180 return null; // nope
181 }
182 state = State.INTRO;
183 continue;
184 case INTRO:
185 // Recognize a second Escape to mean "Alt is pressed".
186 // (at least putty sends it that way)
187 if (useEscEsc && ch == ESC_CODE && ! bEsc) {
188 bEsc = true; continue;
189 }
190
191 // Key sequences supported by this class must
192 // start either with Esc-[ or Esc-O
193 if (ch != '[' && ch != 'O') {
194 return null; // nope
195 }
196 first = ch; state = State.NUM1;
197 continue;
198 case NUM1:
199 if (ch == ';') {
200 state = State.NUM2;
201 } else if (Character.isDigit(ch)) {
202 num1 = num1 * 10 + Character.digit(ch, 10);
203 } else {
204 last = ch; state = State.DONE;
205 }
206 continue;
207 case NUM2:
208 if (Character.isDigit(ch)) {
209 num2 = num2 * 10 + Character.digit(ch, 10);
210 } else {
211 last = ch; state = State.DONE;
212 }
213 continue;
214 case DONE: // once done, extra characters spoil it
215 return null; // nope
216 }
217 }
218 if (state == State.DONE) {
219 KeyStroke ks = getKeyStrokeRaw(first,num1,num2,last,bEsc);
220 return ks != null ? new Matching( ks ) : null; // depends
221 } else {
222 return Matching.NOT_YET; // maybe later
223 }
224 }
225 }