Use symbolic names for keys.
[enjoyable.git] / Classes / NJKeyInputField.m
1 //
2 // NJKeyInputField.h
3 // Enjoyable
4 //
5 // Copyright 2013 Joe Wreschnig.
6 //
7
8 #import "NJKeyInputField.h"
9
10 #include <Carbon/Carbon.h>
11 // Only used for kVK_... codes.
12
13 enum {
14 kVK_RightCommand = kVK_Command - 1,
15 kVK_MAX = 0xFFFF,
16 };
17
18 const CGKeyCode NJKeyInputFieldEmpty = kVK_MAX;
19
20 @implementation NJKeyInputField
21
22 - (id)initWithFrame:(NSRect)frameRect {
23 if ((self = [super initWithFrame:frameRect])) {
24 self.alignment = NSCenterTextAlignment;
25 self.editable = NO;
26 self.selectable = NO;
27 }
28 return self;
29 }
30
31 - (void)clear {
32 self.keyCode = NJKeyInputFieldEmpty;
33 if ([self.delegate respondsToSelector:@selector(keyInputFieldDidClear:)])
34 [self.delegate keyInputFieldDidClear:self];
35 [self resignIfFirstResponder];
36 }
37
38 - (BOOL)hasKeyCode {
39 return self.keyCode != NJKeyInputFieldEmpty;
40 }
41
42 + (NSString *)stringForKeyCode:(CGKeyCode)keyCode {
43 switch (keyCode) {
44 case kVK_F1: return @"F1";
45 case kVK_F2: return @"F2";
46 case kVK_F3: return @"F3";
47 case kVK_F4: return @"F4";
48 case kVK_F5: return @"F5";
49 case kVK_F6: return @"F6";
50 case kVK_F7: return @"F7";
51 case kVK_F8: return @"F8";
52 case kVK_F9: return @"F9";
53 case kVK_F10: return @"F10";
54 case kVK_F11: return @"F11";
55 case kVK_F12: return @"F12";
56 case kVK_F13: return @"F13";
57 case kVK_F14: return @"F14";
58 case kVK_F15: return @"F15";
59 case kVK_F16: return @"F16";
60 case kVK_F17: return @"F17";
61 case kVK_F18: return @"F18";
62 case kVK_F19: return @"F19";
63 case kVK_F20: return @"F20";
64
65 case kVK_Escape: return @"⎋";
66 case kVK_ANSI_Grave: return @"`";
67
68 case kVK_ANSI_1: return @"1";
69 case kVK_ANSI_2: return @"2";
70 case kVK_ANSI_3: return @"3";
71 case kVK_ANSI_4: return @"4";
72 case kVK_ANSI_5: return @"5";
73 case kVK_ANSI_6: return @"6";
74 case kVK_ANSI_7: return @"7";
75 case kVK_ANSI_8: return @"8";
76 case kVK_ANSI_9: return @"9";
77 case kVK_ANSI_0: return @"0";
78 case kVK_ANSI_Minus: return @"-";
79 case kVK_ANSI_Equal: return @"=";
80
81 case kVK_Function: return @"Fn";
82 case kVK_CapsLock: return @"⇪";
83 case kVK_Command: return NSLocalizedString(@"Left ⌘", @"keyboard key");
84 case kVK_RightCommand: return NSLocalizedString(@"Right ⌘", @"keyboard key");
85 case kVK_Option: return NSLocalizedString(@"Left ⌥", @"keyboard key");
86 case kVK_RightOption: return NSLocalizedString(@"Right ⌥", @"keyboard key");
87 case kVK_Control: return NSLocalizedString(@"Left ⌃", @"keyboard key");
88 case kVK_RightControl: return NSLocalizedString(@"Right ⌃", @"keyboard key");
89 case kVK_Shift: return NSLocalizedString(@"Left ⇧", @"keyboard key");
90 case kVK_RightShift: return NSLocalizedString(@"Right ⇧", @"keyboard key");
91
92 case kVK_Home: return @"↖";
93 case kVK_PageUp: return @"⇞";
94 case kVK_End: return @"↘";
95 case kVK_PageDown: return @"⇟";
96
97 case kVK_ForwardDelete: return @"⌦";
98 case kVK_Delete: return @"⌫";
99
100 case kVK_Tab: return @"⇥";
101 case kVK_Return: return @"↩";
102 case kVK_Space: return @"␣";
103
104 case kVK_ANSI_A: return @"A";
105 case kVK_ANSI_B: return @"B";
106 case kVK_ANSI_C: return @"C";
107 case kVK_ANSI_D: return @"D";
108 case kVK_ANSI_E: return @"E";
109 case kVK_ANSI_F: return @"F";
110 case kVK_ANSI_G: return @"G";
111 case kVK_ANSI_H: return @"H";
112 case kVK_ANSI_I: return @"I";
113 case kVK_ANSI_J: return @"J";
114 case kVK_ANSI_K: return @"K";
115 case kVK_ANSI_L: return @"L";
116 case kVK_ANSI_M: return @"M";
117 case kVK_ANSI_N: return @"N";
118 case kVK_ANSI_O: return @"O";
119 case kVK_ANSI_P: return @"P";
120 case kVK_ANSI_Q: return @"Q";
121 case kVK_ANSI_R: return @"R";
122 case kVK_ANSI_S: return @"S";
123 case kVK_ANSI_T: return @"T";
124 case kVK_ANSI_U: return @"U";
125 case kVK_ANSI_V: return @"V";
126 case kVK_ANSI_W: return @"W";
127 case kVK_ANSI_X: return @"X";
128 case kVK_ANSI_Y: return @"Y";
129 case kVK_ANSI_Z: return @"Z";
130 case kVK_ANSI_LeftBracket: return @"[";
131 case kVK_ANSI_RightBracket: return @"]";
132 case kVK_ANSI_Backslash: return @"\\";
133 case kVK_ANSI_Semicolon: return @";";
134 case kVK_ANSI_Quote: return @"'";
135 case kVK_ANSI_Comma: return @",";
136 case kVK_ANSI_Period: return @".";
137 case kVK_ANSI_Slash: return @"/";
138
139 case kVK_ANSI_Keypad0: return NSLocalizedString(@"Key Pad 0", @"numeric pad key");
140 case kVK_ANSI_Keypad1: return NSLocalizedString(@"Key Pad 1", @"numeric pad key");
141 case kVK_ANSI_Keypad2: return NSLocalizedString(@"Key Pad 2", @"numeric pad key");
142 case kVK_ANSI_Keypad3: return NSLocalizedString(@"Key Pad 3", @"numeric pad key");
143 case kVK_ANSI_Keypad4: return NSLocalizedString(@"Key Pad 4", @"numeric pad key");
144 case kVK_ANSI_Keypad5: return NSLocalizedString(@"Key Pad 5", @"numeric pad key");
145 case kVK_ANSI_Keypad6: return NSLocalizedString(@"Key Pad 6", @"numeric pad key");
146 case kVK_ANSI_Keypad7: return NSLocalizedString(@"Key Pad 7", @"numeric pad key");
147 case kVK_ANSI_Keypad8: return NSLocalizedString(@"Key Pad 8", @"numeric pad key");
148 case kVK_ANSI_Keypad9: return NSLocalizedString(@"Key Pad 9", @"numeric pad key");
149 case kVK_ANSI_KeypadClear: return @"⌧";
150 case kVK_ANSI_KeypadEnter: return @"⌤";
151
152 case kVK_ANSI_KeypadEquals:
153 return NSLocalizedString(@"Key Pad =", @"numeric pad key");
154 case kVK_ANSI_KeypadDivide:
155 return NSLocalizedString(@"Key Pad /", @"numeric pad key");
156 case kVK_ANSI_KeypadMultiply:
157 return NSLocalizedString(@"Key Pad *", @"numeric pad key");
158 case kVK_ANSI_KeypadMinus:
159 return NSLocalizedString(@"Key Pad -", @"numeric pad key");
160 case kVK_ANSI_KeypadPlus:
161 return NSLocalizedString(@"Key Pad +", @"numeric pad key");
162 case kVK_ANSI_KeypadDecimal:
163 return NSLocalizedString(@"Key Pad .", @"numeric pad key");
164
165 case kVK_LeftArrow: return @"←";
166 case kVK_RightArrow: return @"→";
167 case kVK_UpArrow: return @"↑";
168 case kVK_DownArrow: return @"↓";
169
170 case kVK_JIS_Yen: return @"¥";
171 case kVK_JIS_Underscore: return @"_";
172 case kVK_JIS_KeypadComma:
173 return NSLocalizedString(@"Key Pad ,", @"numeric pad key");
174 case kVK_JIS_Eisu: return @"英数";
175 case kVK_JIS_Kana: return @"かな";
176
177
178 case kVK_MAX: // NJKeyInputFieldEmpty
179 return @"";
180 default:
181 return [[NSString alloc] initWithFormat:
182 NSLocalizedString(@"key 0x%x", @"unknown key code"),
183 keyCode];
184 }
185 }
186
187 - (BOOL)acceptsFirstResponder {
188 return self.isEnabled;
189 }
190
191 - (BOOL)becomeFirstResponder {
192 self.backgroundColor = NSColor.selectedTextBackgroundColor;
193 return [super becomeFirstResponder];
194 }
195
196 - (BOOL)resignFirstResponder {
197 self.backgroundColor = NSColor.textBackgroundColor;
198 return [super resignFirstResponder];
199 }
200
201 - (void)setKeyCode:(CGKeyCode)keyCode {
202 _keyCode = keyCode;
203 self.stringValue = [NJKeyInputField stringForKeyCode:keyCode];
204 }
205
206 - (void)keyDown:(NSEvent *)event {
207 static const NSUInteger IGNORE = NSAlternateKeyMask | NSCommandKeyMask;
208 if (!event.isARepeat) {
209 if ((event.modifierFlags & IGNORE) && event.keyCode == kVK_Delete) {
210 // Allow Alt/Command+Delete to clear the field.
211 self.keyCode = NJKeyInputFieldEmpty;
212 if ([self.delegate respondsToSelector:@selector(keyInputFieldDidClear:)])
213 [self.delegate keyInputFieldDidClear:self];
214 } else if (!(event.modifierFlags & IGNORE)) {
215 self.keyCode = event.keyCode;
216 if ([self.delegate respondsToSelector:@selector(keyInputField:didChangeKey:)])
217 [self.delegate keyInputField:self didChangeKey:self.keyCode];
218 }
219 [self resignIfFirstResponder];
220 }
221 }
222
223 - (void)mouseDown:(NSEvent *)theEvent {
224 if (self.window.firstResponder == self)
225 [self.window makeFirstResponder:nil];
226 else if (self.acceptsFirstResponder)
227 [self.window makeFirstResponder:self];
228 }
229
230 - (void)flagsChanged:(NSEvent *)theEvent {
231 // Many keys are only available on MacBook keyboards by using the
232 // Fn modifier key (e.g. Fn+Left for Home), so delay processing
233 // modifiers until the up event is received in order to let the
234 // user type these virtual keys. However, there is no actual event
235 // for modifier key up - so detect it by checking to see if any
236 // modifiers are still down.
237 if (!(theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask)) {
238 self.keyCode = theEvent.keyCode;
239 if ([self.delegate respondsToSelector:@selector(keyInputField:didChangeKey:)])
240 [self.delegate keyInputField:self didChangeKey:_keyCode];
241 }
242 }
243
244 - (void)setDelegate:(id<NJKeyInputFieldDelegate, NSTextFieldDelegate>)delegate {
245 [super setDelegate:delegate];
246 }
247
248 - (id <NJKeyInputFieldDelegate, NSTextFieldDelegate>)delegate {
249 return (id)[super delegate];
250 }
251
252 @end