michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMenuBarListener.h" michael@0: #include "nsMenuBarFrame.h" michael@0: #include "nsMenuPopupFrame.h" michael@0: #include "nsIDOMEvent.h" michael@0: michael@0: // Drag & Drop, Clipboard michael@0: #include "nsIServiceManager.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMElement.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /* michael@0: * nsMenuBarListener implementation michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsMenuBarListener, nsIDOMEventListener) michael@0: michael@0: #define MODIFIER_SHIFT 1 michael@0: #define MODIFIER_CONTROL 2 michael@0: #define MODIFIER_ALT 4 michael@0: #define MODIFIER_META 8 michael@0: #define MODIFIER_OS 16 michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: michael@0: int32_t nsMenuBarListener::mAccessKey = -1; michael@0: uint32_t nsMenuBarListener::mAccessKeyMask = 0; michael@0: bool nsMenuBarListener::mAccessKeyFocuses = false; michael@0: michael@0: nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar) michael@0: :mAccessKeyDown(false), mAccessKeyDownCanceled(false) michael@0: { michael@0: mMenuBarFrame = aMenuBar; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: nsMenuBarListener::~nsMenuBarListener() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsMenuBarListener::InitializeStatics() michael@0: { michael@0: Preferences::AddBoolVarCache(&mAccessKeyFocuses, michael@0: "ui.key.menuAccessKeyFocuses"); michael@0: } michael@0: michael@0: nsresult michael@0: nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey) michael@0: { michael@0: if (!aAccessKey) michael@0: return NS_ERROR_INVALID_POINTER; michael@0: InitAccessKey(); michael@0: *aAccessKey = mAccessKey; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsMenuBarListener::InitAccessKey() michael@0: { michael@0: if (mAccessKey >= 0) michael@0: return; michael@0: michael@0: // Compiled-in defaults, in case we can't get LookAndFeel -- michael@0: // mac doesn't have menu shortcuts, other platforms use alt. michael@0: #ifdef XP_MACOSX michael@0: mAccessKey = 0; michael@0: mAccessKeyMask = 0; michael@0: #else michael@0: mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT; michael@0: mAccessKeyMask = MODIFIER_ALT; michael@0: #endif michael@0: michael@0: // Get the menu access key value from prefs, overriding the default: michael@0: mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey); michael@0: if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) michael@0: mAccessKeyMask = MODIFIER_SHIFT; michael@0: else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) michael@0: mAccessKeyMask = MODIFIER_CONTROL; michael@0: else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) michael@0: mAccessKeyMask = MODIFIER_ALT; michael@0: else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) michael@0: mAccessKeyMask = MODIFIER_META; michael@0: else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_WIN) michael@0: mAccessKeyMask = MODIFIER_OS; michael@0: } michael@0: michael@0: void michael@0: nsMenuBarListener::ToggleMenuActiveState() michael@0: { michael@0: nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState(); michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm && closemenu) { michael@0: nsMenuPopupFrame* popupFrame = closemenu->GetPopup(); michael@0: if (popupFrame) michael@0: pm->HidePopup(popupFrame->GetContent(), false, false, true, false); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: nsresult michael@0: nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) michael@0: { michael@0: nsCOMPtr keyEvent = do_QueryInterface(aKeyEvent); michael@0: if (!keyEvent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: InitAccessKey(); michael@0: michael@0: //handlers shouldn't be triggered by non-trusted events. michael@0: bool trustedEvent = false; michael@0: aKeyEvent->GetIsTrusted(&trustedEvent); michael@0: michael@0: if (!trustedEvent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mAccessKey && mAccessKeyFocuses) michael@0: { michael@0: bool defaultPrevented = false; michael@0: aKeyEvent->GetDefaultPrevented(&defaultPrevented); michael@0: michael@0: // On a press of the ALT key by itself, we toggle the menu's michael@0: // active/inactive state. michael@0: // Get the ascii key code. michael@0: uint32_t theChar; michael@0: keyEvent->GetKeyCode(&theChar); michael@0: michael@0: if (!defaultPrevented && mAccessKeyDown && !mAccessKeyDownCanceled && michael@0: (int32_t)theChar == mAccessKey) michael@0: { michael@0: // The access key was down and is now up, and no other michael@0: // keys were pressed in between. michael@0: if (!mMenuBarFrame->IsActive()) { michael@0: mMenuBarFrame->SetActiveByKeyboard(); michael@0: } michael@0: ToggleMenuActiveState(); michael@0: } michael@0: mAccessKeyDown = false; michael@0: mAccessKeyDownCanceled = false; michael@0: michael@0: bool active = mMenuBarFrame->IsActive(); michael@0: if (active) { michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: return NS_OK; // I am consuming event michael@0: } michael@0: } michael@0: michael@0: return NS_OK; // means I am NOT consuming event michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: nsresult michael@0: nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) michael@0: { michael@0: // if event has already been handled, bail michael@0: if (aKeyEvent) { michael@0: bool eventHandled = false; michael@0: aKeyEvent->GetDefaultPrevented(&eventHandled); michael@0: if (eventHandled) { michael@0: return NS_OK; // don't consume event michael@0: } michael@0: } michael@0: michael@0: //handlers shouldn't be triggered by non-trusted events. michael@0: bool trustedEvent = false; michael@0: if (aKeyEvent) { michael@0: aKeyEvent->GetIsTrusted(&trustedEvent); michael@0: } michael@0: michael@0: if (!trustedEvent) michael@0: return NS_OK; michael@0: michael@0: nsresult retVal = NS_OK; // default is to not consume event michael@0: michael@0: InitAccessKey(); michael@0: michael@0: if (mAccessKey) michael@0: { michael@0: bool preventDefault; michael@0: aKeyEvent->GetDefaultPrevented(&preventDefault); michael@0: if (!preventDefault) { michael@0: nsCOMPtr keyEvent = do_QueryInterface(aKeyEvent); michael@0: uint32_t keyCode, charCode; michael@0: keyEvent->GetKeyCode(&keyCode); michael@0: keyEvent->GetCharCode(&charCode); michael@0: michael@0: bool hasAccessKeyCandidates = charCode != 0; michael@0: if (!hasAccessKeyCandidates) { michael@0: WidgetKeyboardEvent* nativeKeyEvent = michael@0: aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); michael@0: if (nativeKeyEvent) { michael@0: nsAutoTArray keys; michael@0: nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, keys); michael@0: hasAccessKeyCandidates = !keys.IsEmpty(); michael@0: } michael@0: } michael@0: michael@0: // Cancel the access key flag unless we are pressing the access key. michael@0: if (keyCode != (uint32_t)mAccessKey) { michael@0: mAccessKeyDownCanceled = true; michael@0: } michael@0: michael@0: if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) { michael@0: // Do shortcut navigation. michael@0: // A letter was pressed. We want to see if a shortcut gets matched. If michael@0: // so, we'll know the menu got activated. michael@0: nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent); michael@0: if (result) { michael@0: mMenuBarFrame->SetActiveByKeyboard(); michael@0: mMenuBarFrame->SetActive(true); michael@0: result->OpenMenu(true); michael@0: michael@0: // The opened menu will listen next keyup event. michael@0: // Therefore, we should clear the keydown flags here. michael@0: mAccessKeyDown = mAccessKeyDownCanceled = false; michael@0: michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: retVal = NS_OK; // I am consuming event michael@0: } michael@0: } michael@0: #ifndef XP_MACOSX michael@0: // Also need to handle F10 specially on Non-Mac platform. michael@0: else if (keyCode == NS_VK_F10) { michael@0: if ((GetModifiers(keyEvent) & ~MODIFIER_CONTROL) == 0) { michael@0: // The F10 key just went down by itself or with ctrl pressed. michael@0: // In Windows, both of these activate the menu bar. michael@0: mMenuBarFrame->SetActiveByKeyboard(); michael@0: ToggleMenuActiveState(); michael@0: michael@0: if (mMenuBarFrame->IsActive()) { michael@0: #ifdef MOZ_WIDGET_GTK michael@0: // In GTK, this also opens the first menu. michael@0: mMenuBarFrame->GetCurrentMenuItem()->OpenMenu(true); michael@0: #endif michael@0: aKeyEvent->StopPropagation(); michael@0: aKeyEvent->PreventDefault(); michael@0: return NS_OK; // consume the event michael@0: } michael@0: } michael@0: } michael@0: #endif // !XP_MACOSX michael@0: } michael@0: } michael@0: michael@0: return retVal; michael@0: } michael@0: michael@0: bool michael@0: nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: InitAccessKey(); michael@0: // No other modifiers are allowed to be down except for Shift. michael@0: uint32_t modifiers = GetModifiers(aKeyEvent); michael@0: michael@0: return (mAccessKeyMask != MODIFIER_SHIFT && michael@0: (modifiers & mAccessKeyMask) && michael@0: (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0); michael@0: } michael@0: michael@0: uint32_t michael@0: nsMenuBarListener::GetModifiers(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: uint32_t modifiers = 0; michael@0: WidgetInputEvent* inputEvent = michael@0: aKeyEvent->GetInternalNSEvent()->AsInputEvent(); michael@0: MOZ_ASSERT(inputEvent); michael@0: michael@0: if (inputEvent->IsShift()) { michael@0: modifiers |= MODIFIER_SHIFT; michael@0: } michael@0: michael@0: if (inputEvent->IsControl()) { michael@0: modifiers |= MODIFIER_CONTROL; michael@0: } michael@0: michael@0: if (inputEvent->IsAlt()) { michael@0: modifiers |= MODIFIER_ALT; michael@0: } michael@0: michael@0: if (inputEvent->IsMeta()) { michael@0: modifiers |= MODIFIER_META; michael@0: } michael@0: michael@0: if (inputEvent->IsOS()) { michael@0: modifiers |= MODIFIER_OS; michael@0: } michael@0: michael@0: return modifiers; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: nsresult michael@0: nsMenuBarListener::KeyDown(nsIDOMEvent* aKeyEvent) michael@0: { michael@0: InitAccessKey(); michael@0: michael@0: //handlers shouldn't be triggered by non-trusted events. michael@0: bool trustedEvent = false; michael@0: if (aKeyEvent) { michael@0: aKeyEvent->GetIsTrusted(&trustedEvent); michael@0: } michael@0: michael@0: if (!trustedEvent) michael@0: return NS_OK; michael@0: michael@0: if (mAccessKey && mAccessKeyFocuses) michael@0: { michael@0: bool defaultPrevented = false; michael@0: aKeyEvent->GetDefaultPrevented(&defaultPrevented); michael@0: michael@0: nsCOMPtr keyEvent = do_QueryInterface(aKeyEvent); michael@0: uint32_t theChar; michael@0: keyEvent->GetKeyCode(&theChar); michael@0: michael@0: // No other modifiers can be down. michael@0: // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US michael@0: // enhanced 102-key keyboards if we don't check this. michael@0: bool isAccessKeyDownEvent = michael@0: ((theChar == (uint32_t)mAccessKey) && michael@0: (GetModifiers(keyEvent) & ~mAccessKeyMask) == 0); michael@0: michael@0: if (!mAccessKeyDown) { michael@0: // If accesskey isn't being pressed and the key isn't the accesskey, michael@0: // ignore the event. michael@0: if (!isAccessKeyDownEvent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise, accept the accesskey state. michael@0: mAccessKeyDown = true; michael@0: // If default is prevented already, cancel the access key down. michael@0: mAccessKeyDownCanceled = defaultPrevented; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If the pressed accesskey was canceled already or the event was michael@0: // consumed already, ignore the event. michael@0: if (mAccessKeyDownCanceled || defaultPrevented) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Some key other than the access key just went down, michael@0: // so we won't activate the menu bar when the access key is released. michael@0: mAccessKeyDownCanceled = !isAccessKeyDownEvent; michael@0: } michael@0: michael@0: return NS_OK; // means I am NOT consuming event michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsresult michael@0: nsMenuBarListener::Blur(nsIDOMEvent* aEvent) michael@0: { michael@0: if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) { michael@0: ToggleMenuActiveState(); michael@0: } michael@0: // Reset the accesskey state because we cannot receive the keyup event for michael@0: // the pressing accesskey. michael@0: mAccessKeyDown = false; michael@0: mAccessKeyDownCanceled = false; michael@0: return NS_OK; // means I am NOT consuming event michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: nsresult michael@0: nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent) michael@0: { michael@0: // NOTE: MouseDown method listens all phases michael@0: michael@0: // Even if the mousedown event is canceled, it means the user don't want michael@0: // to activate the menu. Therefore, we need to record it at capturing (or michael@0: // target) phase. michael@0: if (mAccessKeyDown) { michael@0: mAccessKeyDownCanceled = true; michael@0: } michael@0: michael@0: uint16_t phase = 0; michael@0: nsresult rv = aMouseEvent->GetEventPhase(&phase); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Don't do anything at capturing phase, any behavior should be cancelable. michael@0: if (phase == nsIDOMEvent::CAPTURING_PHASE) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) michael@0: ToggleMenuActiveState(); michael@0: michael@0: return NS_OK; // means I am NOT consuming event michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: nsresult michael@0: nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: michael@0: if (eventType.EqualsLiteral("keyup")) { michael@0: return KeyUp(aEvent); michael@0: } michael@0: if (eventType.EqualsLiteral("keydown")) { michael@0: return KeyDown(aEvent); michael@0: } michael@0: if (eventType.EqualsLiteral("keypress")) { michael@0: return KeyPress(aEvent); michael@0: } michael@0: if (eventType.EqualsLiteral("blur")) { michael@0: return Blur(aEvent); michael@0: } michael@0: if (eventType.EqualsLiteral("mousedown")) { michael@0: return MouseDown(aEvent); michael@0: } michael@0: michael@0: NS_ABORT(); michael@0: michael@0: return NS_OK; michael@0: }