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