|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsMenuBarListener.h" |
|
7 #include "nsMenuBarFrame.h" |
|
8 #include "nsMenuPopupFrame.h" |
|
9 #include "nsIDOMEvent.h" |
|
10 |
|
11 // Drag & Drop, Clipboard |
|
12 #include "nsIServiceManager.h" |
|
13 #include "nsWidgetsCID.h" |
|
14 #include "nsCOMPtr.h" |
|
15 #include "nsIDOMKeyEvent.h" |
|
16 #include "nsIContent.h" |
|
17 #include "nsIDOMNode.h" |
|
18 #include "nsIDOMElement.h" |
|
19 |
|
20 #include "nsContentUtils.h" |
|
21 #include "mozilla/Preferences.h" |
|
22 #include "mozilla/TextEvents.h" |
|
23 |
|
24 using namespace mozilla; |
|
25 |
|
26 /* |
|
27 * nsMenuBarListener implementation |
|
28 */ |
|
29 |
|
30 NS_IMPL_ISUPPORTS(nsMenuBarListener, nsIDOMEventListener) |
|
31 |
|
32 #define MODIFIER_SHIFT 1 |
|
33 #define MODIFIER_CONTROL 2 |
|
34 #define MODIFIER_ALT 4 |
|
35 #define MODIFIER_META 8 |
|
36 #define MODIFIER_OS 16 |
|
37 |
|
38 //////////////////////////////////////////////////////////////////////// |
|
39 |
|
40 int32_t nsMenuBarListener::mAccessKey = -1; |
|
41 uint32_t nsMenuBarListener::mAccessKeyMask = 0; |
|
42 bool nsMenuBarListener::mAccessKeyFocuses = false; |
|
43 |
|
44 nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar) |
|
45 :mAccessKeyDown(false), mAccessKeyDownCanceled(false) |
|
46 { |
|
47 mMenuBarFrame = aMenuBar; |
|
48 } |
|
49 |
|
50 //////////////////////////////////////////////////////////////////////// |
|
51 nsMenuBarListener::~nsMenuBarListener() |
|
52 { |
|
53 } |
|
54 |
|
55 void |
|
56 nsMenuBarListener::InitializeStatics() |
|
57 { |
|
58 Preferences::AddBoolVarCache(&mAccessKeyFocuses, |
|
59 "ui.key.menuAccessKeyFocuses"); |
|
60 } |
|
61 |
|
62 nsresult |
|
63 nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey) |
|
64 { |
|
65 if (!aAccessKey) |
|
66 return NS_ERROR_INVALID_POINTER; |
|
67 InitAccessKey(); |
|
68 *aAccessKey = mAccessKey; |
|
69 return NS_OK; |
|
70 } |
|
71 |
|
72 void nsMenuBarListener::InitAccessKey() |
|
73 { |
|
74 if (mAccessKey >= 0) |
|
75 return; |
|
76 |
|
77 // Compiled-in defaults, in case we can't get LookAndFeel -- |
|
78 // mac doesn't have menu shortcuts, other platforms use alt. |
|
79 #ifdef XP_MACOSX |
|
80 mAccessKey = 0; |
|
81 mAccessKeyMask = 0; |
|
82 #else |
|
83 mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT; |
|
84 mAccessKeyMask = MODIFIER_ALT; |
|
85 #endif |
|
86 |
|
87 // Get the menu access key value from prefs, overriding the default: |
|
88 mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey); |
|
89 if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) |
|
90 mAccessKeyMask = MODIFIER_SHIFT; |
|
91 else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) |
|
92 mAccessKeyMask = MODIFIER_CONTROL; |
|
93 else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) |
|
94 mAccessKeyMask = MODIFIER_ALT; |
|
95 else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) |
|
96 mAccessKeyMask = MODIFIER_META; |
|
97 else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_WIN) |
|
98 mAccessKeyMask = MODIFIER_OS; |
|
99 } |
|
100 |
|
101 void |
|
102 nsMenuBarListener::ToggleMenuActiveState() |
|
103 { |
|
104 nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState(); |
|
105 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
|
106 if (pm && closemenu) { |
|
107 nsMenuPopupFrame* popupFrame = closemenu->GetPopup(); |
|
108 if (popupFrame) |
|
109 pm->HidePopup(popupFrame->GetContent(), false, false, true, false); |
|
110 } |
|
111 } |
|
112 |
|
113 //////////////////////////////////////////////////////////////////////// |
|
114 nsresult |
|
115 nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) |
|
116 { |
|
117 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); |
|
118 if (!keyEvent) { |
|
119 return NS_OK; |
|
120 } |
|
121 |
|
122 InitAccessKey(); |
|
123 |
|
124 //handlers shouldn't be triggered by non-trusted events. |
|
125 bool trustedEvent = false; |
|
126 aKeyEvent->GetIsTrusted(&trustedEvent); |
|
127 |
|
128 if (!trustedEvent) { |
|
129 return NS_OK; |
|
130 } |
|
131 |
|
132 if (mAccessKey && mAccessKeyFocuses) |
|
133 { |
|
134 bool defaultPrevented = false; |
|
135 aKeyEvent->GetDefaultPrevented(&defaultPrevented); |
|
136 |
|
137 // On a press of the ALT key by itself, we toggle the menu's |
|
138 // active/inactive state. |
|
139 // Get the ascii key code. |
|
140 uint32_t theChar; |
|
141 keyEvent->GetKeyCode(&theChar); |
|
142 |
|
143 if (!defaultPrevented && mAccessKeyDown && !mAccessKeyDownCanceled && |
|
144 (int32_t)theChar == mAccessKey) |
|
145 { |
|
146 // The access key was down and is now up, and no other |
|
147 // keys were pressed in between. |
|
148 if (!mMenuBarFrame->IsActive()) { |
|
149 mMenuBarFrame->SetActiveByKeyboard(); |
|
150 } |
|
151 ToggleMenuActiveState(); |
|
152 } |
|
153 mAccessKeyDown = false; |
|
154 mAccessKeyDownCanceled = false; |
|
155 |
|
156 bool active = mMenuBarFrame->IsActive(); |
|
157 if (active) { |
|
158 aKeyEvent->StopPropagation(); |
|
159 aKeyEvent->PreventDefault(); |
|
160 return NS_OK; // I am consuming event |
|
161 } |
|
162 } |
|
163 |
|
164 return NS_OK; // means I am NOT consuming event |
|
165 } |
|
166 |
|
167 //////////////////////////////////////////////////////////////////////// |
|
168 nsresult |
|
169 nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) |
|
170 { |
|
171 // if event has already been handled, bail |
|
172 if (aKeyEvent) { |
|
173 bool eventHandled = false; |
|
174 aKeyEvent->GetDefaultPrevented(&eventHandled); |
|
175 if (eventHandled) { |
|
176 return NS_OK; // don't consume event |
|
177 } |
|
178 } |
|
179 |
|
180 //handlers shouldn't be triggered by non-trusted events. |
|
181 bool trustedEvent = false; |
|
182 if (aKeyEvent) { |
|
183 aKeyEvent->GetIsTrusted(&trustedEvent); |
|
184 } |
|
185 |
|
186 if (!trustedEvent) |
|
187 return NS_OK; |
|
188 |
|
189 nsresult retVal = NS_OK; // default is to not consume event |
|
190 |
|
191 InitAccessKey(); |
|
192 |
|
193 if (mAccessKey) |
|
194 { |
|
195 bool preventDefault; |
|
196 aKeyEvent->GetDefaultPrevented(&preventDefault); |
|
197 if (!preventDefault) { |
|
198 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); |
|
199 uint32_t keyCode, charCode; |
|
200 keyEvent->GetKeyCode(&keyCode); |
|
201 keyEvent->GetCharCode(&charCode); |
|
202 |
|
203 bool hasAccessKeyCandidates = charCode != 0; |
|
204 if (!hasAccessKeyCandidates) { |
|
205 WidgetKeyboardEvent* nativeKeyEvent = |
|
206 aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); |
|
207 if (nativeKeyEvent) { |
|
208 nsAutoTArray<uint32_t, 10> keys; |
|
209 nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, keys); |
|
210 hasAccessKeyCandidates = !keys.IsEmpty(); |
|
211 } |
|
212 } |
|
213 |
|
214 // Cancel the access key flag unless we are pressing the access key. |
|
215 if (keyCode != (uint32_t)mAccessKey) { |
|
216 mAccessKeyDownCanceled = true; |
|
217 } |
|
218 |
|
219 if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) { |
|
220 // Do shortcut navigation. |
|
221 // A letter was pressed. We want to see if a shortcut gets matched. If |
|
222 // so, we'll know the menu got activated. |
|
223 nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent); |
|
224 if (result) { |
|
225 mMenuBarFrame->SetActiveByKeyboard(); |
|
226 mMenuBarFrame->SetActive(true); |
|
227 result->OpenMenu(true); |
|
228 |
|
229 // The opened menu will listen next keyup event. |
|
230 // Therefore, we should clear the keydown flags here. |
|
231 mAccessKeyDown = mAccessKeyDownCanceled = false; |
|
232 |
|
233 aKeyEvent->StopPropagation(); |
|
234 aKeyEvent->PreventDefault(); |
|
235 retVal = NS_OK; // I am consuming event |
|
236 } |
|
237 } |
|
238 #ifndef XP_MACOSX |
|
239 // Also need to handle F10 specially on Non-Mac platform. |
|
240 else if (keyCode == NS_VK_F10) { |
|
241 if ((GetModifiers(keyEvent) & ~MODIFIER_CONTROL) == 0) { |
|
242 // The F10 key just went down by itself or with ctrl pressed. |
|
243 // In Windows, both of these activate the menu bar. |
|
244 mMenuBarFrame->SetActiveByKeyboard(); |
|
245 ToggleMenuActiveState(); |
|
246 |
|
247 if (mMenuBarFrame->IsActive()) { |
|
248 #ifdef MOZ_WIDGET_GTK |
|
249 // In GTK, this also opens the first menu. |
|
250 mMenuBarFrame->GetCurrentMenuItem()->OpenMenu(true); |
|
251 #endif |
|
252 aKeyEvent->StopPropagation(); |
|
253 aKeyEvent->PreventDefault(); |
|
254 return NS_OK; // consume the event |
|
255 } |
|
256 } |
|
257 } |
|
258 #endif // !XP_MACOSX |
|
259 } |
|
260 } |
|
261 |
|
262 return retVal; |
|
263 } |
|
264 |
|
265 bool |
|
266 nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent) |
|
267 { |
|
268 InitAccessKey(); |
|
269 // No other modifiers are allowed to be down except for Shift. |
|
270 uint32_t modifiers = GetModifiers(aKeyEvent); |
|
271 |
|
272 return (mAccessKeyMask != MODIFIER_SHIFT && |
|
273 (modifiers & mAccessKeyMask) && |
|
274 (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0); |
|
275 } |
|
276 |
|
277 uint32_t |
|
278 nsMenuBarListener::GetModifiers(nsIDOMKeyEvent* aKeyEvent) |
|
279 { |
|
280 uint32_t modifiers = 0; |
|
281 WidgetInputEvent* inputEvent = |
|
282 aKeyEvent->GetInternalNSEvent()->AsInputEvent(); |
|
283 MOZ_ASSERT(inputEvent); |
|
284 |
|
285 if (inputEvent->IsShift()) { |
|
286 modifiers |= MODIFIER_SHIFT; |
|
287 } |
|
288 |
|
289 if (inputEvent->IsControl()) { |
|
290 modifiers |= MODIFIER_CONTROL; |
|
291 } |
|
292 |
|
293 if (inputEvent->IsAlt()) { |
|
294 modifiers |= MODIFIER_ALT; |
|
295 } |
|
296 |
|
297 if (inputEvent->IsMeta()) { |
|
298 modifiers |= MODIFIER_META; |
|
299 } |
|
300 |
|
301 if (inputEvent->IsOS()) { |
|
302 modifiers |= MODIFIER_OS; |
|
303 } |
|
304 |
|
305 return modifiers; |
|
306 } |
|
307 |
|
308 //////////////////////////////////////////////////////////////////////// |
|
309 nsresult |
|
310 nsMenuBarListener::KeyDown(nsIDOMEvent* aKeyEvent) |
|
311 { |
|
312 InitAccessKey(); |
|
313 |
|
314 //handlers shouldn't be triggered by non-trusted events. |
|
315 bool trustedEvent = false; |
|
316 if (aKeyEvent) { |
|
317 aKeyEvent->GetIsTrusted(&trustedEvent); |
|
318 } |
|
319 |
|
320 if (!trustedEvent) |
|
321 return NS_OK; |
|
322 |
|
323 if (mAccessKey && mAccessKeyFocuses) |
|
324 { |
|
325 bool defaultPrevented = false; |
|
326 aKeyEvent->GetDefaultPrevented(&defaultPrevented); |
|
327 |
|
328 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); |
|
329 uint32_t theChar; |
|
330 keyEvent->GetKeyCode(&theChar); |
|
331 |
|
332 // No other modifiers can be down. |
|
333 // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US |
|
334 // enhanced 102-key keyboards if we don't check this. |
|
335 bool isAccessKeyDownEvent = |
|
336 ((theChar == (uint32_t)mAccessKey) && |
|
337 (GetModifiers(keyEvent) & ~mAccessKeyMask) == 0); |
|
338 |
|
339 if (!mAccessKeyDown) { |
|
340 // If accesskey isn't being pressed and the key isn't the accesskey, |
|
341 // ignore the event. |
|
342 if (!isAccessKeyDownEvent) { |
|
343 return NS_OK; |
|
344 } |
|
345 |
|
346 // Otherwise, accept the accesskey state. |
|
347 mAccessKeyDown = true; |
|
348 // If default is prevented already, cancel the access key down. |
|
349 mAccessKeyDownCanceled = defaultPrevented; |
|
350 return NS_OK; |
|
351 } |
|
352 |
|
353 // If the pressed accesskey was canceled already or the event was |
|
354 // consumed already, ignore the event. |
|
355 if (mAccessKeyDownCanceled || defaultPrevented) { |
|
356 return NS_OK; |
|
357 } |
|
358 |
|
359 // Some key other than the access key just went down, |
|
360 // so we won't activate the menu bar when the access key is released. |
|
361 mAccessKeyDownCanceled = !isAccessKeyDownEvent; |
|
362 } |
|
363 |
|
364 return NS_OK; // means I am NOT consuming event |
|
365 } |
|
366 |
|
367 //////////////////////////////////////////////////////////////////////// |
|
368 |
|
369 nsresult |
|
370 nsMenuBarListener::Blur(nsIDOMEvent* aEvent) |
|
371 { |
|
372 if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) { |
|
373 ToggleMenuActiveState(); |
|
374 } |
|
375 // Reset the accesskey state because we cannot receive the keyup event for |
|
376 // the pressing accesskey. |
|
377 mAccessKeyDown = false; |
|
378 mAccessKeyDownCanceled = false; |
|
379 return NS_OK; // means I am NOT consuming event |
|
380 } |
|
381 |
|
382 //////////////////////////////////////////////////////////////////////// |
|
383 nsresult |
|
384 nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent) |
|
385 { |
|
386 // NOTE: MouseDown method listens all phases |
|
387 |
|
388 // Even if the mousedown event is canceled, it means the user don't want |
|
389 // to activate the menu. Therefore, we need to record it at capturing (or |
|
390 // target) phase. |
|
391 if (mAccessKeyDown) { |
|
392 mAccessKeyDownCanceled = true; |
|
393 } |
|
394 |
|
395 uint16_t phase = 0; |
|
396 nsresult rv = aMouseEvent->GetEventPhase(&phase); |
|
397 NS_ENSURE_SUCCESS(rv, rv); |
|
398 // Don't do anything at capturing phase, any behavior should be cancelable. |
|
399 if (phase == nsIDOMEvent::CAPTURING_PHASE) { |
|
400 return NS_OK; |
|
401 } |
|
402 |
|
403 if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) |
|
404 ToggleMenuActiveState(); |
|
405 |
|
406 return NS_OK; // means I am NOT consuming event |
|
407 } |
|
408 |
|
409 //////////////////////////////////////////////////////////////////////// |
|
410 nsresult |
|
411 nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent) |
|
412 { |
|
413 nsAutoString eventType; |
|
414 aEvent->GetType(eventType); |
|
415 |
|
416 if (eventType.EqualsLiteral("keyup")) { |
|
417 return KeyUp(aEvent); |
|
418 } |
|
419 if (eventType.EqualsLiteral("keydown")) { |
|
420 return KeyDown(aEvent); |
|
421 } |
|
422 if (eventType.EqualsLiteral("keypress")) { |
|
423 return KeyPress(aEvent); |
|
424 } |
|
425 if (eventType.EqualsLiteral("blur")) { |
|
426 return Blur(aEvent); |
|
427 } |
|
428 if (eventType.EqualsLiteral("mousedown")) { |
|
429 return MouseDown(aEvent); |
|
430 } |
|
431 |
|
432 NS_ABORT(); |
|
433 |
|
434 return NS_OK; |
|
435 } |