diff options
Diffstat (limited to 'widget/cocoa/nsMenuUtilsX.mm')
-rw-r--r-- | widget/cocoa/nsMenuUtilsX.mm | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/widget/cocoa/nsMenuUtilsX.mm b/widget/cocoa/nsMenuUtilsX.mm new file mode 100644 index 0000000000..aca279d64c --- /dev/null +++ b/widget/cocoa/nsMenuUtilsX.mm @@ -0,0 +1,294 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMenuUtilsX.h" +#include <unordered_set> + +#include "mozilla/EventForwards.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/XULCommandEvent.h" +#include "nsMenuBarX.h" +#include "nsMenuX.h" +#include "nsMenuItemX.h" +#include "NativeMenuMac.h" +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" +#include "nsCocoaWindow.h" +#include "nsGkAtoms.h" +#include "nsGlobalWindowInner.h" +#include "nsPIDOMWindow.h" +#include "nsQueryObject.h" + +using namespace mozilla; + +bool nsMenuUtilsX::gIsSynchronouslyActivatingNativeMenuItemDuringTest = false; + +void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent, + NSEventModifierFlags aModifierFlags, int16_t aButton) { + MOZ_ASSERT(aTargetContent, "null ptr"); + + dom::Document* doc = aTargetContent->OwnerDoc(); + if (doc) { + RefPtr<dom::XULCommandEvent> event = + new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr); + + bool ctrlKey = aModifierFlags & NSEventModifierFlagControl; + bool altKey = aModifierFlags & NSEventModifierFlagOption; + bool shiftKey = aModifierFlags & NSEventModifierFlagShift; + bool cmdKey = aModifierFlags & NSEventModifierFlagCommand; + + IgnoredErrorResult rv; + event->InitCommandEvent(u"command"_ns, true, true, + nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0, ctrlKey, altKey, + shiftKey, cmdKey, aButton, nullptr, 0, rv); + if (!rv.Failed()) { + event->SetTrusted(true); + aTargetContent->DispatchEvent(*event); + } + } +} + +NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // We want to truncate long strings to some reasonable pixel length but there is no + // good API for doing that which works for all OS versions and architectures. For now + // we'll do nothing for consistency and depend on good user interface design to limit + // string lengths. + return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get()) + length:itemLabel.Length()]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute) { + uint8_t modifiers = knsMenuItemNoModifier; + char* str = ToNewCString(modifiersAttribute); + char* newStr; + char* token = strtok_r(str, ", \t", &newStr); + while (token != nullptr) { + if (strcmp(token, "shift") == 0) { + modifiers |= knsMenuItemShiftModifier; + } else if (strcmp(token, "alt") == 0) { + modifiers |= knsMenuItemAltModifier; + } else if (strcmp(token, "control") == 0) { + modifiers |= knsMenuItemControlModifier; + } else if ((strcmp(token, "accel") == 0) || (strcmp(token, "meta") == 0)) { + modifiers |= knsMenuItemCommandModifier; + } + token = strtok_r(newStr, ", \t", &newStr); + } + free(str); + + return modifiers; +} + +unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers) { + unsigned int macModifiers = 0; + + if (geckoModifiers & knsMenuItemShiftModifier) { + macModifiers |= NSEventModifierFlagShift; + } + if (geckoModifiers & knsMenuItemAltModifier) { + macModifiers |= NSEventModifierFlagOption; + } + if (geckoModifiers & knsMenuItemControlModifier) { + macModifiers |= NSEventModifierFlagControl; + } + if (geckoModifiers & knsMenuItemCommandModifier) { + macModifiers |= NSEventModifierFlagCommand; + } + + return macModifiers; +} + +nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar() { + nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget(); + if (hiddenWindowWidgetNoCOMPtr) { + return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar(); + } + return nullptr; +} + +// It would be nice if we could localize these edit menu names. +NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // In principle we should be able to allocate this once and then always + // return the same object. But weird interactions happen between native + // app-modal dialogs and Gecko-modal dialogs that open above them. So what + // we return here isn't always released before it needs to be added to + // another menu. See bmo bug 468393. + NSMenuItem* standardEditMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Edit" + action:nil + keyEquivalent:@""] autorelease]; + NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; + standardEditMenuItem.submenu = standardEditMenu; + [standardEditMenu release]; + + // Add Undo + NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" + action:@selector(undo:) + keyEquivalent:@"z"]; + [standardEditMenu addItem:undoItem]; + [undoItem release]; + + // Add Redo + NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" + action:@selector(redo:) + keyEquivalent:@"Z"]; + [standardEditMenu addItem:redoItem]; + [redoItem release]; + + // Add separator + [standardEditMenu addItem:[NSMenuItem separatorItem]]; + + // Add Cut + NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut" + action:@selector(cut:) + keyEquivalent:@"x"]; + [standardEditMenu addItem:cutItem]; + [cutItem release]; + + // Add Copy + NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" + action:@selector(copy:) + keyEquivalent:@"c"]; + [standardEditMenu addItem:copyItem]; + [copyItem release]; + + // Add Paste + NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste" + action:@selector(paste:) + keyEquivalent:@"v"]; + [standardEditMenu addItem:pasteItem]; + [pasteItem release]; + + // Add Delete + NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete" + action:@selector(delete:) + keyEquivalent:@""]; + [standardEditMenu addItem:deleteItem]; + [deleteItem release]; + + // Add Select All + NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All" + action:@selector(selectAll:) + keyEquivalent:@"a"]; + [standardEditMenu addItem:selectAllItem]; + [selectAllItem release]; + + return standardEditMenuItem; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* aContent) { + return aContent->IsElement() && + (aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, + eCaseMatters) || + aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed, + nsGkAtoms::_true, eCaseMatters)); +} + +NSMenuItem* nsMenuUtilsX::NativeMenuItemWithLocation(NSMenu* aRootMenu, NSString* aLocationString, + bool aIsMenuBar) { + NSArray<NSString*>* indexes = [aLocationString componentsSeparatedByString:@"|"]; + unsigned int pathLength = indexes.count; + if (pathLength == 0) { + return nil; + } + + NSMenu* currentSubmenu = aRootMenu; + for (unsigned int depth = 0; depth < pathLength; depth++) { + NSInteger targetIndex = [indexes objectAtIndex:depth].integerValue; + if (aIsMenuBar && depth == 0) { + // We remove the application menu from consideration for the top-level menu. + targetIndex++; + } + int itemCount = currentSubmenu.numberOfItems; + if (targetIndex >= itemCount) { + return nil; + } + NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex]; + // if this is the last index just return the menu item + if (depth == pathLength - 1) { + return menuItem; + } + // if this is not the last index find the submenu and keep going + if (menuItem.hasSubmenu) { + currentSubmenu = menuItem.submenu; + } else { + return nil; + } + } + + return nil; +} + +static void CheckNativeMenuConsistencyImpl(NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects); + +static void CheckNativeMenuItemConsistencyImpl(NSMenuItem* aMenuItem, + std::unordered_set<void*>& aSeenObjects) { + bool inserted = aSeenObjects.insert(aMenuItem).second; + MOZ_RELEASE_ASSERT(inserted, "Duplicate NSMenuItem object in native menu structure"); + if (aMenuItem.hasSubmenu) { + CheckNativeMenuConsistencyImpl(aMenuItem.submenu, aSeenObjects); + } +} + +static void CheckNativeMenuConsistencyImpl(NSMenu* aMenu, std::unordered_set<void*>& aSeenObjects) { + bool inserted = aSeenObjects.insert(aMenu).second; + MOZ_RELEASE_ASSERT(inserted, "Duplicate NSMenu object in native menu structure"); + for (NSMenuItem* item in aMenu.itemArray) { + CheckNativeMenuItemConsistencyImpl(item, aSeenObjects); + } +} + +void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenu* aMenu) { + std::unordered_set<void*> seenObjects; + CheckNativeMenuConsistencyImpl(aMenu, seenObjects); +} + +void nsMenuUtilsX::CheckNativeMenuConsistency(NSMenuItem* aMenuItem) { + std::unordered_set<void*> seenObjects; + CheckNativeMenuItemConsistencyImpl(aMenuItem, seenObjects); +} + +static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent, + const Maybe<int>& aIndexInParentMenu); + +static void DumpNativeNSMenuImpl(NSMenu* aMenu, uint32_t aIndent) { + printf("%*sNSMenu [%p] %-16s\n", aIndent * 2, "", aMenu, + (aMenu.title.length == 0 ? "(no title)" : aMenu.title.UTF8String)); + int index = 0; + for (NSMenuItem* item in aMenu.itemArray) { + DumpNativeNSMenuItemImpl(item, aIndent + 1, Some(index)); + index++; + } +} + +static void DumpNativeNSMenuItemImpl(NSMenuItem* aItem, uint32_t aIndent, + const Maybe<int>& aIndexInParentMenu) { + printf("%*s", aIndent * 2, ""); + if (aIndexInParentMenu) { + printf("[%d] ", *aIndexInParentMenu); + } + printf("NSMenuItem [%p] %-16s%s\n", aItem, + aItem.isSeparatorItem ? "----" + : (aItem.title.length == 0 ? "(no title)" : aItem.title.UTF8String), + aItem.hasSubmenu ? " [hasSubmenu]" : ""); + if (aItem.hasSubmenu) { + DumpNativeNSMenuImpl(aItem.submenu, aIndent + 1); + } +} + +void nsMenuUtilsX::DumpNativeMenu(NSMenu* aMenu) { DumpNativeNSMenuImpl(aMenu, 0); } + +void nsMenuUtilsX::DumpNativeMenuItem(NSMenuItem* aMenuItem) { + DumpNativeNSMenuItemImpl(aMenuItem, 0, Nothing()); +} |