Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(13)

Side by Side Diff: chrome/browser/cocoa/nsmenuitem_additions_unittest.mm

Issue 319001: Add a function that can check if a menu item would be fired by a keypress. (Closed)
Patch Set: remove done todo, 80cols Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #import "chrome/browser/cocoa/nsmenuitem_additions.h"
2
3 #include <iostream>
4 #include <Carbon/Carbon.h>
5
6 #include "base/scoped_nsobject.h"
7 #include "base/sys_string_conversions.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9
10 NSEvent* KeyEvent(const NSUInteger modifierFlags,
11 NSString* chars,
12 NSString* charsNoMods,
13 const NSUInteger keyCode) {
14 return [NSEvent keyEventWithType:NSKeyDown
15 location:NSZeroPoint
16 modifierFlags:modifierFlags
17 timestamp:0.0
18 windowNumber:0
19 context:nil
20 characters:chars
21 charactersIgnoringModifiers:charsNoMods
22 isARepeat:NO
23 keyCode:keyCode];
24 }
25
26 NSMenuItem* MenuItem(NSString* equiv, NSUInteger mask) {
27 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:@""
28 action:NULL
29 keyEquivalent:@""] autorelease];
30 [item setKeyEquivalent:equiv];
31 [item setKeyEquivalentModifierMask:mask];
32 return item;
33 }
34
35 std::ostream& operator<<(std::ostream& out, NSObject* obj) {
36 return out << base::SysNSStringToUTF8([obj description]);
37 }
38
39 std::ostream& operator<<(std::ostream& out, NSMenuItem* item) {
40 return out << "NSMenuItem " << base::SysNSStringToUTF8([item keyEquivalent]);
41 }
42
43 void ExpectKeyFiresItemEq(bool result, NSEvent* key, NSMenuItem* item,
44 bool compareCocoa ) {
45 EXPECT_EQ(result, [item cr_firesForKeyEvent:key]) << key << '\n' << item;
46
47 // Make sure that Cocoa does in fact agree with our expectations. However,
48 // in some cases cocoa behaves weirdly (if you create e.g. a new event that
49 // contains all fields of the event that you get when hitting cmd-a with a
50 // russion keyboard layout, the copy won't fire a menu item that has cmd-a as
51 // key equivalent, even though the original event would) and isn't a good
52 // oracle function.
53 if (compareCocoa) {
54 scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Menu!"]);
55 [menu setAutoenablesItems:NO];
56 EXPECT_FALSE([menu performKeyEquivalent:key]);
57 [menu addItem:item];
58 EXPECT_EQ(result, [menu performKeyEquivalent:key]) << key << '\n' << item;
59 }
60 }
61
62 void ExpectKeyFiresItem(
63 NSEvent* key, NSMenuItem* item, bool compareCocoa = true) {
64 ExpectKeyFiresItemEq(true, key, item, compareCocoa);
65 }
66
67 void ExpectKeyDoesntFireItem(
68 NSEvent* key, NSMenuItem* item, bool compareCocoa = true) {
69 ExpectKeyFiresItemEq(false, key, item, compareCocoa);
70 }
71
72 TEST(NSMenuItemAdditionsTest, TestFiresForKeyEvent) {
73 // These test cases were built by writing a small test app that has a
74 // MainMenu.xib with a given key equivalent set in Interface Builder and a
75 // some code that prints both the key equivalent that fires a menu item and
76 // the menu item's key equivalent and modifier masks. I then pasted those
77 // below. This was done with a US layout, unless otherwise noted. In the
78 // comments, "z" always means the physical "z" key on a US layout no matter
79 // what character that key produces.
80
81 NSMenuItem* item;
82 NSEvent* key;
83 unichar ch;
84 NSString* s;
85
86 // Sanity
87 item = MenuItem(@"", 0);
88 EXPECT_TRUE([item isEnabled]);
89
90 // a
91 key = KeyEvent(0x100, @"a", @"a", 0);
92 item = MenuItem(@"a", 0);
93 ExpectKeyFiresItem(key, item);
94 ExpectKeyDoesntFireItem(KeyEvent(0x20102, @"A", @"A", 0), item);
95
96 // Disabled menu item
97 key = KeyEvent(0x100, @"a", @"a", 0);
98 item = MenuItem(@"a", 0);
99 [item setEnabled:NO];
100 ExpectKeyDoesntFireItem(key, item, false);
101
102 // shift-a
103 key = KeyEvent(0x20102, @"A", @"A", 0);
104 item = MenuItem(@"A", 0);
105 ExpectKeyFiresItem(key, item);
106 ExpectKeyDoesntFireItem(KeyEvent(0x100, @"a", @"a", 0), item);
107
108 // cmd-opt-shift-a
109 key = KeyEvent(0x1a012a, @"\u00c5", @"A", 0);
110 item = MenuItem(@"A", 0x180000);
111 ExpectKeyFiresItem(key, item);
112
113 // cmd-opt-a
114 key = KeyEvent(0x18012a, @"\u00e5", @"a", 0);
115 item = MenuItem(@"a", 0x180000);
116 ExpectKeyFiresItem(key, item);
117
118 // cmd-=
119 key = KeyEvent(0x100110, @"=", @"=", 0x18);
120 item = MenuItem(@"=", 0x100000);
121 ExpectKeyFiresItem(key, item);
122
123 // cmd-shift-=
124 key = KeyEvent(0x12010a, @"=", @"+", 0x18);
125 item = MenuItem(@"+", 0x100000);
126 ExpectKeyFiresItem(key, item);
127
128 // Turns out Cocoa fires "+ 100108 + 18" if you hit cmd-= and the menu only
129 // has a cmd-+ shortcut. But that's transparent for |cr_firesForKeyEvent:|.
130
131 // ctrl-3
132 key = KeyEvent(0x40101, @"3", @"3", 0x14);
133 item = MenuItem(@"3", 0x40000);
134 ExpectKeyFiresItem(key, item);
135
136 // return
137 key = KeyEvent(0, @"\r", @"\r", 0x24);
138 item = MenuItem(@"\r", 0);
139 ExpectKeyFiresItem(key, item);
140
141 // shift-return
142 key = KeyEvent(0x20102, @"\r", @"\r", 0x24);
143 item = MenuItem(@"\r", 0x20000);
144 ExpectKeyFiresItem(key, item);
145
146 // shift-left
147 ch = NSLeftArrowFunctionKey;
148 s = [NSString stringWithCharacters:&ch length:1];
149 key = KeyEvent(0xa20102, s, s, 0x7b);
150 item = MenuItem(s, 0x20000);
151 ExpectKeyFiresItem(key, item);
152
153 // shift-f1 (with a layout that needs the fn key down for f1)
154 ch = NSF1FunctionKey;
155 s = [NSString stringWithCharacters:&ch length:1];
156 key = KeyEvent(0x820102, s, s, 0x7a);
157 item = MenuItem(s, 0x20000);
158 ExpectKeyFiresItem(key, item);
159
160 // esc
161 // Turns out this doesn't fire.
162 key = KeyEvent(0x100, @"\e", @"\e", 0x35);
163 item = MenuItem(@"\e", 0);
164 ExpectKeyDoesntFireItem(key,item, false);
165
166 // shift-esc
167 // Turns out this doesn't fire.
168 key = KeyEvent(0x20102, @"\e", @"\e", 0x35);
169 item = MenuItem(@"\e", 0x20000);
170 ExpectKeyDoesntFireItem(key,item, false);
171
172 // cmd-esc
173 key = KeyEvent(0x100108, @"\e", @"\e", 0x35);
174 item = MenuItem(@"\e", 0x100000);
175 ExpectKeyFiresItem(key, item);
176
177 // ctrl-esc
178 key = KeyEvent(0x40101, @"\e", @"\e", 0x35);
179 item = MenuItem(@"\e", 0x40000);
180 ExpectKeyFiresItem(key, item);
181
182 // delete ("backspace")
183 key = KeyEvent(0x100, @"\x7f", @"\x7f", 0x33);
184 item = MenuItem(@"\x08", 0);
185 ExpectKeyFiresItem(key, item, false);
186
187 // shift-delete
188 key = KeyEvent(0x20102, @"\x7f", @"\x7f", 0x33);
189 item = MenuItem(@"\x08", 0x20000);
190 ExpectKeyFiresItem(key, item, false);
191
192 // forwarddelete (fn-delete / fn-backspace)
193 ch = NSDeleteFunctionKey;
194 s = [NSString stringWithCharacters:&ch length:1];
195 key = KeyEvent(0x800100, s, s, 0x75);
196 item = MenuItem(@"\x7f", 0);
197 ExpectKeyFiresItem(key, item, false);
198
199 // shift-forwarddelete (shift-fn-delete / shift-fn-backspace)
200 ch = NSDeleteFunctionKey;
201 s = [NSString stringWithCharacters:&ch length:1];
202 key = KeyEvent(0x820102, s, s, 0x75);
203 item = MenuItem(@"\x7f", 0x20000);
204 ExpectKeyFiresItem(key, item, false);
205
206 // fn-left
207 ch = NSHomeFunctionKey;
208 s = [NSString stringWithCharacters:&ch length:1];
209 key = KeyEvent(0x800100, s, s, 0x73);
210 item = MenuItem(s, 0);
211 ExpectKeyFiresItem(key, item);
212
213 // cmd-left
214 ch = NSLeftArrowFunctionKey;
215 s = [NSString stringWithCharacters:&ch length:1];
216 key = KeyEvent(0xb00108, s, s, 0x7b);
217 item = MenuItem(s, 0x100000);
218 ExpectKeyFiresItem(key, item);
219
220 // Hitting the "a" key with a russian keyboard layout -- does not fire
221 // a menu item that has "a" as key equiv.
222 key = KeyEvent(0x100, @"\u0444", @"\u0444", 0);
223 item = MenuItem(@"a", 0);
224 ExpectKeyDoesntFireItem(key,item);
225
226 // cmd-a on a russion layout -- fires for a menu item with cmd-a as key equiv.
227 key = KeyEvent(0x100108, @"a", @"\u0444", 0);
228 item = MenuItem(@"a", 0x100000);
229 ExpectKeyFiresItem(key, item, false);
230
231 // cmd-z on US layout
232 key = KeyEvent(0x100108, @"z", @"z", 6);
233 item = MenuItem(@"z", 0x100000);
234 ExpectKeyFiresItem(key, item);
235
236 // cmd-y on german layout (has same keycode as cmd-z on us layout, shouldn't
237 // fire).
238 key = KeyEvent(0x100108, @"y", @"y", 6);
239 item = MenuItem(@"z", 0x100000);
240 ExpectKeyDoesntFireItem(key,item);
241
242 // cmd-z on german layout
243 key = KeyEvent(0x100108, @"z", @"z", 0x10);
244 item = MenuItem(@"z", 0x100000);
245 ExpectKeyFiresItem(key, item);
246
247 // fn-return (== enter)
248 key = KeyEvent(0x800100, @"\x3", @"\x3", 0x4c);
249 item = MenuItem(@"\r", 0);
250 ExpectKeyDoesntFireItem(key,item);
251
252 // cmd-z on dvorak layout (so that the key produces ';')
253 key = KeyEvent(0x100108, @";", @";", 6);
254 ExpectKeyDoesntFireItem(key, MenuItem(@"z", 0x100000));
255 ExpectKeyFiresItem(key, MenuItem(@";", 0x100000));
256
257 // cmd-z on dvorak qwerty layout (so that the key produces ';', but 'z' if
258 // cmd is down)
259 key = KeyEvent(0x100108, @"z", @";", 6);
260 ExpectKeyFiresItem(key, MenuItem(@"z", 0x100000), false);
261 ExpectKeyDoesntFireItem(key, MenuItem(@";", 0x100000), false);
262
263 // cmd-shift-z on dvorak layout (so that we get a ':')
264 key = KeyEvent(0x12010a, @";", @":", 6);
265 ExpectKeyFiresItem(key, MenuItem(@":", 0x100000));
266 ExpectKeyDoesntFireItem(key, MenuItem(@";", 0x100000));
267
268 // cmd-s with a serbian layout (just "s" produces something that looks a lot
269 // like "c" in some fonts, but is actually \u0441. cmd-s activates a menu item
270 // with key equivalent "s", not "c")
271 key = KeyEvent(0x100108, @"s", @"\u0441", 1);
272 ExpectKeyFiresItem(key, MenuItem(@"s", 0x100000), false);
273 ExpectKeyDoesntFireItem(key, MenuItem(@"c", 0x100000));
274 }
275
276 NSString* keyCodeToCharacter(NSUInteger keyCode,
277 EventModifiers modifiers,
278 TISInputSourceRef layout) {
279 CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(
280 layout, kTISPropertyUnicodeKeyLayoutData);
281 UCKeyboardLayout* keyLayout = (UCKeyboardLayout*)CFDataGetBytePtr(uchr);
282
283 UInt32 deadKeyState = 0;
284 OSStatus err = noErr;
285 UniCharCount maxStringLength = 4, actualStringLength;
286 UniChar unicodeString[4];
287 err = UCKeyTranslate(keyLayout,
288 (UInt16)keyCode,
289 kUCKeyActionDown,
290 modifiers,
291 LMGetKbdType(),
292 kUCKeyTranslateNoDeadKeysBit,
293 &deadKeyState,
294 maxStringLength,
295 &actualStringLength,
296 unicodeString);
297 assert(err == noErr);
298
299 CFStringRef temp = CFStringCreateWithCharacters(
300 kCFAllocatorDefault, unicodeString, 1);
301 return [(NSString*)temp autorelease];
302 }
303
304 TEST(NSMenuItemAdditionsTest, TestMOnDifferentLayouts) {
305 // There's one key -- "m" -- that has the same keycode on most keyboard
306 //layouts. This function tests a menu item with cmd-m as key equivalent
307 // can be fired on all layouts.
308 NSMenuItem* item = MenuItem(@"m", 0x100000);
309
310 NSDictionary* filter = [NSDictionary
311 dictionaryWithObject:(NSString*)kTISTypeKeyboardLayout
312 forKey:(NSString*)kTISPropertyInputSourceType];
313
314 // Docs say that including all layouts instead of just the active ones is
315 // slow, but there's no way around that.
316 NSArray* list = (NSArray*)TISCreateInputSourceList(
317 (CFDictionaryRef)filter, true);
318 for (id layout in list) {
319 TISInputSourceRef ref = (TISInputSourceRef)layout;
320
321 NSUInteger keyCode = 0x2e; // "m" on a us layout and most other layouts.
322
323 // On a few layouts, "m" has a different key code.
324 NSString* layoutId = (NSString*)TISGetInputSourceProperty(
325 ref, kTISPropertyInputSourceID);
326 if ([layoutId isEqualToString:@"com.apple.keylayout.Belgian"] ||
327 [layoutId isEqualToString:@"com.apple.keylayout.French"] ||
328 [layoutId isEqualToString:@"com.apple.keylayout.French-numerical"] ||
329 [layoutId isEqualToString:@"com.apple.keylayout.Italian"])
330 keyCode = 0x29;
331 else if ([layoutId isEqualToString:@"com.apple.keylayout.Turkish"])
332 keyCode = 0x28;
333
334 EventModifiers modifiers = cmdKey >> 8;
335 NSString* chars = keyCodeToCharacter(keyCode, modifiers, ref);
336 NSString* charsIgnoringMods = keyCodeToCharacter(keyCode, 0, ref);
337 NSEvent* key = KeyEvent(0x100000, chars, charsIgnoringMods, keyCode);
338 ExpectKeyFiresItem(key, item, false);
339 }
340 CFRelease(list);
341 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698