Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | /* |
2 | * This file is part of lanterna (http://code.google.com/p/lanterna/). | |
3 | * | |
4 | * lanterna is free software: you can redistribute it and/or modify | |
5 | * it under the terms of the GNU Lesser General Public License as published by | |
6 | * the Free Software Foundation, either version 3 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU Lesser General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU Lesser General Public License | |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | * | |
17 | * Copyright (C) 2010-2015 Martin | |
18 | */ | |
19 | package com.googlecode.lanterna.input; | |
20 | ||
21 | import java.util.ArrayList; | |
22 | import java.util.Arrays; | |
23 | ||
24 | /** | |
25 | * Represents the user pressing a key on the keyboard. If the user held down ctrl and/or alt before pressing the key, | |
26 | * this may be recorded in this class, depending on the terminal implementation and if such information in available. | |
27 | * KeyStroke objects are normally constructed by a KeyDecodingProfile, which works off a character stream that likely | |
28 | * coming from the system's standard input. Because of this, the class can only represent what can be read and | |
29 | * interpreted from the input stream; for example, certain key-combinations like ctrl+i is indistinguishable from a tab | |
30 | * key press. | |
31 | * <p> | |
32 | * Use the <tt>keyType</tt> field to determine what kind of key was pressed. For ordinary letters, numbers and symbols, the | |
33 | * <tt>keyType</tt> will be <tt>KeyType.Character</tt> and the actual character value of the key is in the | |
34 | * <tt>character</tt> field. Please note that return (\n) and tab (\t) are not sorted under type <tt>KeyType.Character</tt> | |
35 | * but <tt>KeyType.Enter</tt> and <tt>KeyType.Tab</tt> instead. | |
36 | * @author martin | |
37 | */ | |
38 | public class KeyStroke { | |
39 | private final KeyType keyType; | |
40 | private final Character character; | |
41 | private final boolean ctrlDown; | |
42 | private final boolean altDown; | |
43 | private final boolean shiftDown; | |
44 | private final long eventTime; | |
45 | ||
46 | /** | |
47 | * Constructs a KeyStroke based on a supplied keyType; character will be null and both ctrl and alt will be | |
48 | * considered not pressed. If you try to construct a KeyStroke with type KeyType.Character with this constructor, it | |
49 | * will always throw an exception; use another overload that allows you to specify the character value instead. | |
50 | * @param keyType Type of the key pressed by this keystroke | |
51 | */ | |
52 | public KeyStroke(KeyType keyType) { | |
53 | this(keyType, false, false); | |
54 | } | |
55 | ||
56 | /** | |
57 | * Constructs a KeyStroke based on a supplied keyType; character will be null. | |
58 | * If you try to construct a KeyStroke with type KeyType.Character with this constructor, it | |
59 | * will always throw an exception; use another overload that allows you to specify the character value instead. | |
60 | * @param keyType Type of the key pressed by this keystroke | |
61 | * @param ctrlDown Was ctrl held down when the main key was pressed? | |
62 | * @param altDown Was alt held down when the main key was pressed? | |
63 | */ | |
64 | public KeyStroke(KeyType keyType, boolean ctrlDown, boolean altDown) { | |
65 | this(keyType, null, ctrlDown, altDown, false); | |
66 | } | |
67 | ||
68 | /** | |
69 | * Constructs a KeyStroke based on a supplied keyType; character will be null. | |
70 | * If you try to construct a KeyStroke with type KeyType.Character with this constructor, it | |
71 | * will always throw an exception; use another overload that allows you to specify the character value instead. | |
72 | * @param keyType Type of the key pressed by this keystroke | |
73 | * @param ctrlDown Was ctrl held down when the main key was pressed? | |
74 | * @param altDown Was alt held down when the main key was pressed? | |
75 | * @param shiftDown Was shift held down when the main key was pressed? | |
76 | */ | |
77 | public KeyStroke(KeyType keyType, boolean ctrlDown, boolean altDown, boolean shiftDown) { | |
78 | this(keyType, null, ctrlDown, altDown, shiftDown); | |
79 | } | |
80 | ||
81 | /** | |
82 | * Constructs a KeyStroke based on a supplied character, keyType is implicitly KeyType.Character. | |
83 | * <p> | |
84 | * A character-based KeyStroke does not support the shiftDown flag, as the shift state has | |
85 | * already been accounted for in the character itself, depending on user's keyboard layout. | |
86 | * @param character Character that was typed on the keyboard | |
87 | * @param ctrlDown Was ctrl held down when the main key was pressed? | |
88 | * @param altDown Was alt held down when the main key was pressed? | |
89 | */ | |
90 | public KeyStroke(Character character, boolean ctrlDown, boolean altDown) { | |
91 | this(KeyType.Character, character, ctrlDown, altDown, false); | |
92 | } | |
93 | ||
94 | private KeyStroke(KeyType keyType, Character character, boolean ctrlDown, boolean altDown, boolean shiftDown) { | |
95 | if(keyType == KeyType.Character && character == null) { | |
96 | throw new IllegalArgumentException("Cannot construct a KeyStroke with type KeyType.Character but no character information"); | |
97 | } | |
98 | //Enforce character for some key types | |
99 | switch(keyType) { | |
100 | case Backspace: | |
101 | character = '\b'; | |
102 | break; | |
103 | case Enter: | |
104 | character = '\n'; | |
105 | break; | |
106 | case Tab: | |
107 | character = '\t'; | |
108 | break; | |
109 | default: | |
110 | } | |
111 | this.keyType = keyType; | |
112 | this.character = character; | |
113 | this.shiftDown = shiftDown; | |
114 | this.ctrlDown = ctrlDown; | |
115 | this.altDown = altDown; | |
116 | this.eventTime = System.currentTimeMillis(); | |
117 | } | |
118 | ||
119 | /** | |
120 | * Type of key that was pressed on the keyboard, as represented by the KeyType enum. If the value if | |
121 | * KeyType.Character, you need to call getCharacter() to find out which letter, number or symbol that was actually | |
122 | * pressed. | |
123 | * @return Type of key on the keyboard that was pressed | |
124 | */ | |
125 | public KeyType getKeyType() { | |
126 | return keyType; | |
127 | } | |
128 | ||
129 | /** | |
130 | * For keystrokes of ordinary keys (letters, digits, symbols), this method returns the actual character value of the | |
131 | * key. For all other key types, it returns null. | |
132 | * @return Character value of the key pressed, or null if it was a special key | |
133 | */ | |
134 | public Character getCharacter() { | |
135 | return character; | |
136 | } | |
137 | ||
138 | /** | |
139 | * @return Returns true if ctrl was help down while the key was typed (depending on terminal implementation) | |
140 | */ | |
141 | public boolean isCtrlDown() { | |
142 | return ctrlDown; | |
143 | } | |
144 | ||
145 | /** | |
146 | * @return Returns true if alt was help down while the key was typed (depending on terminal implementation) | |
147 | */ | |
148 | public boolean isAltDown() { | |
149 | return altDown; | |
150 | } | |
151 | ||
152 | /** | |
153 | * @return Returns true if shift was help down while the key was typed (depending on terminal implementation) | |
154 | */ | |
155 | public boolean isShiftDown() { | |
156 | return shiftDown; | |
157 | } | |
158 | ||
159 | /** | |
160 | * Gets the time when the keystroke was recorded. This isn't necessarily the time the keystroke happened, but when | |
161 | * Lanterna received the event, so it may not be accurate down to the millisecond. | |
162 | * @return The unix time of when the keystroke happened, in milliseconds | |
163 | */ | |
164 | public long getEventTime() { | |
165 | return eventTime; | |
166 | } | |
167 | ||
168 | @Override | |
169 | public String toString() { | |
170 | return "KeyStroke{" + "keyType=" + keyType + ", character=" + character + | |
171 | ", ctrlDown=" + ctrlDown + | |
172 | ", altDown=" + altDown + | |
173 | ", shiftDown=" + shiftDown + '}'; | |
174 | } | |
175 | ||
176 | @Override | |
177 | public int hashCode() { | |
178 | int hash = 3; | |
179 | hash = 41 * hash + (this.keyType != null ? this.keyType.hashCode() : 0); | |
180 | hash = 41 * hash + (this.character != null ? this.character.hashCode() : 0); | |
181 | hash = 41 * hash + (this.ctrlDown ? 1 : 0); | |
182 | hash = 41 * hash + (this.altDown ? 1 : 0); | |
183 | hash = 41 * hash + (this.shiftDown ? 1 : 0); | |
184 | return hash; | |
185 | } | |
186 | ||
187 | @SuppressWarnings("SimplifiableIfStatement") | |
188 | @Override | |
189 | public boolean equals(Object obj) { | |
190 | if (obj == null) { | |
191 | return false; | |
192 | } | |
193 | if (getClass() != obj.getClass()) { | |
194 | return false; | |
195 | } | |
196 | final KeyStroke other = (KeyStroke) obj; | |
197 | if (this.keyType != other.keyType) { | |
198 | return false; | |
199 | } | |
200 | if (this.character != other.character && (this.character == null || !this.character.equals(other.character))) { | |
201 | return false; | |
202 | } | |
203 | return this.ctrlDown == other.ctrlDown && | |
204 | this.altDown == other.altDown && | |
205 | this.shiftDown == other.shiftDown; | |
206 | } | |
207 | ||
208 | /** | |
209 | * Creates a Key from a string representation in Vim's key notation. | |
210 | * | |
211 | * @param keyStr the string representation of this key | |
212 | * @return the created {@link KeyType} | |
213 | */ | |
214 | public static KeyStroke fromString(String keyStr) { | |
215 | String keyStrLC = keyStr.toLowerCase(); | |
216 | KeyStroke k; | |
217 | if (keyStr.length() == 1) { | |
218 | k = new KeyStroke(KeyType.Character, keyStr.charAt(0), false, false, false); | |
219 | } else if (keyStr.startsWith("<") && keyStr.endsWith(">")) { | |
220 | if (keyStrLC.equals("<s-tab>")) { | |
221 | k = new KeyStroke(KeyType.ReverseTab); | |
222 | } else if (keyStr.contains("-")) { | |
223 | ArrayList<String> segments = new ArrayList<String>(Arrays.asList(keyStr.substring(1, keyStr.length() - 1).split("-"))); | |
224 | if (segments.size() < 2) { | |
225 | throw new IllegalArgumentException("Invalid vim notation: " + keyStr); | |
226 | } | |
227 | String characterStr = segments.remove(segments.size() - 1); | |
228 | boolean altPressed = false; | |
229 | boolean ctrlPressed = false; | |
230 | for (String modifier : segments) { | |
231 | if ("c".equals(modifier.toLowerCase())) { | |
232 | ctrlPressed = true; | |
233 | } else if ("a".equals(modifier.toLowerCase())) { | |
234 | altPressed = true; | |
235 | } else if ("s".equals(modifier.toLowerCase())) { | |
236 | characterStr = characterStr.toUpperCase(); | |
237 | } | |
238 | } | |
239 | k = new KeyStroke(characterStr.charAt(0), ctrlPressed, altPressed); | |
240 | } else { | |
241 | if (keyStrLC.startsWith("<esc")) { | |
242 | k = new KeyStroke(KeyType.Escape); | |
243 | } else if (keyStrLC.equals("<cr>") || keyStrLC.equals("<enter>") || keyStrLC.equals("<return>")) { | |
244 | k = new KeyStroke(KeyType.Enter); | |
245 | } else if (keyStrLC.equals("<bs>")) { | |
246 | k = new KeyStroke(KeyType.Backspace); | |
247 | } else if (keyStrLC.equals("<tab>")) { | |
248 | k = new KeyStroke(KeyType.Tab); | |
249 | } else if (keyStrLC.equals("<space>")) { | |
250 | k = new KeyStroke(' ', false, false); | |
251 | } else if (keyStrLC.equals("<up>")) { | |
252 | k = new KeyStroke(KeyType.ArrowUp); | |
253 | } else if (keyStrLC.equals("<down>")) { | |
254 | k = new KeyStroke(KeyType.ArrowDown); | |
255 | } else if (keyStrLC.equals("<left>")) { | |
256 | k = new KeyStroke(KeyType.ArrowLeft); | |
257 | } else if (keyStrLC.equals("<right>")) { | |
258 | k = new KeyStroke(KeyType.ArrowRight); | |
259 | } else if (keyStrLC.equals("<insert>")) { | |
260 | k = new KeyStroke(KeyType.Insert); | |
261 | } else if (keyStrLC.equals("<del>")) { | |
262 | k = new KeyStroke(KeyType.Delete); | |
263 | } else if (keyStrLC.equals("<home>")) { | |
264 | k = new KeyStroke(KeyType.Home); | |
265 | } else if (keyStrLC.equals("<end>")) { | |
266 | k = new KeyStroke(KeyType.End); | |
267 | } else if (keyStrLC.equals("<pageup>")) { | |
268 | k = new KeyStroke(KeyType.PageUp); | |
269 | } else if (keyStrLC.equals("<pagedown>")) { | |
270 | k = new KeyStroke(KeyType.PageDown); | |
271 | } else if (keyStrLC.equals("<f1>")) { | |
272 | k = new KeyStroke(KeyType.F1); | |
273 | } else if (keyStrLC.equals("<f2>")) { | |
274 | k = new KeyStroke(KeyType.F2); | |
275 | } else if (keyStrLC.equals("<f3>")) { | |
276 | k = new KeyStroke(KeyType.F3); | |
277 | } else if (keyStrLC.equals("<f4>")) { | |
278 | k = new KeyStroke(KeyType.F4); | |
279 | } else if (keyStrLC.equals("<f5>")) { | |
280 | k = new KeyStroke(KeyType.F5); | |
281 | } else if (keyStrLC.equals("<f6>")) { | |
282 | k = new KeyStroke(KeyType.F6); | |
283 | } else if (keyStrLC.equals("<f7>")) { | |
284 | k = new KeyStroke(KeyType.F7); | |
285 | } else if (keyStrLC.equals("<f8>")) { | |
286 | k = new KeyStroke(KeyType.F8); | |
287 | } else if (keyStrLC.equals("<f9>")) { | |
288 | k = new KeyStroke(KeyType.F9); | |
289 | } else if (keyStrLC.equals("<f10>")) { | |
290 | k = new KeyStroke(KeyType.F10); | |
291 | } else if (keyStrLC.equals("<f11>")) { | |
292 | k = new KeyStroke(KeyType.F11); | |
293 | } else if (keyStrLC.equals("<f12>")) { | |
294 | k = new KeyStroke(KeyType.F12); | |
295 | } else { | |
296 | throw new IllegalArgumentException("Invalid vim notation: " + keyStr); | |
297 | } | |
298 | } | |
299 | } else { | |
300 | throw new IllegalArgumentException("Invalid vim notation: " + keyStr); | |
301 | } | |
302 | return k; | |
303 | } | |
304 | } |