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 "nsMenuUtilsX.h" 7 8#include "mozilla/dom/Document.h" 9#include "mozilla/dom/DocumentInlines.h" 10#include "mozilla/dom/Event.h" 11#include "mozilla/dom/XULCommandEvent.h" 12#include "nsMenuBarX.h" 13#include "nsMenuX.h" 14#include "nsMenuItemX.h" 15#include "nsStandaloneNativeMenu.h" 16#include "nsObjCExceptions.h" 17#include "nsCocoaUtils.h" 18#include "nsCocoaWindow.h" 19#include "nsGkAtoms.h" 20#include "nsGlobalWindowInner.h" 21#include "nsPIDOMWindow.h" 22#include "nsQueryObject.h" 23 24using namespace mozilla; 25 26void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent) { 27 MOZ_ASSERT(aTargetContent, "null ptr"); 28 29 dom::Document* doc = aTargetContent->OwnerDoc(); 30 if (doc) { 31 RefPtr<dom::XULCommandEvent> event = 32 new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr); 33 34 IgnoredErrorResult rv; 35 event->InitCommandEvent(NS_LITERAL_STRING("command"), true, true, 36 nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0, false, false, 37 false, false, nullptr, 0, rv); 38 // FIXME: Should probably figure out how to init this with the actual 39 // pressed keys, but this is a big old edge case anyway. -dwh 40 if (!rv.Failed()) { 41 event->SetTrusted(true); 42 aTargetContent->DispatchEvent(*event); 43 } 44 } 45} 46 47NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel) { 48 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 49 50 // We want to truncate long strings to some reasonable pixel length but there is no 51 // good API for doing that which works for all OS versions and architectures. For now 52 // we'll do nothing for consistency and depend on good user interface design to limit 53 // string lengths. 54 return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get()) 55 length:itemLabel.Length()]; 56 57 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 58} 59 60uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute) { 61 uint8_t modifiers = knsMenuItemNoModifier; 62 char* str = ToNewCString(modifiersAttribute); 63 char* newStr; 64 char* token = strtok_r(str, ", \t", &newStr); 65 while (token != NULL) { 66 if (strcmp(token, "shift") == 0) 67 modifiers |= knsMenuItemShiftModifier; 68 else if (strcmp(token, "alt") == 0) 69 modifiers |= knsMenuItemAltModifier; 70 else if (strcmp(token, "control") == 0) 71 modifiers |= knsMenuItemControlModifier; 72 else if ((strcmp(token, "accel") == 0) || (strcmp(token, "meta") == 0)) { 73 modifiers |= knsMenuItemCommandModifier; 74 } 75 token = strtok_r(newStr, ", \t", &newStr); 76 } 77 free(str); 78 79 return modifiers; 80} 81 82unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers) { 83 unsigned int macModifiers = 0; 84 85 if (geckoModifiers & knsMenuItemShiftModifier) macModifiers |= NSShiftKeyMask; 86 if (geckoModifiers & knsMenuItemAltModifier) macModifiers |= NSAlternateKeyMask; 87 if (geckoModifiers & knsMenuItemControlModifier) macModifiers |= NSControlKeyMask; 88 if (geckoModifiers & knsMenuItemCommandModifier) macModifiers |= NSCommandKeyMask; 89 90 return macModifiers; 91} 92 93nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar() { 94 nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget(); 95 if (hiddenWindowWidgetNoCOMPtr) 96 return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar(); 97 else 98 return nullptr; 99} 100 101// It would be nice if we could localize these edit menu names. 102NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem() { 103 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 104 105 // In principle we should be able to allocate this once and then always 106 // return the same object. But weird interactions happen between native 107 // app-modal dialogs and Gecko-modal dialogs that open above them. So what 108 // we return here isn't always released before it needs to be added to 109 // another menu. See bmo bug 468393. 110 NSMenuItem* standardEditMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Edit" 111 action:nil 112 keyEquivalent:@""] autorelease]; 113 NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; 114 [standardEditMenuItem setSubmenu:standardEditMenu]; 115 [standardEditMenu release]; 116 117 // Add Undo 118 NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" 119 action:@selector(undo:) 120 keyEquivalent:@"z"]; 121 [standardEditMenu addItem:undoItem]; 122 [undoItem release]; 123 124 // Add Redo 125 NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" 126 action:@selector(redo:) 127 keyEquivalent:@"Z"]; 128 [standardEditMenu addItem:redoItem]; 129 [redoItem release]; 130 131 // Add separator 132 [standardEditMenu addItem:[NSMenuItem separatorItem]]; 133 134 // Add Cut 135 NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut" 136 action:@selector(cut:) 137 keyEquivalent:@"x"]; 138 [standardEditMenu addItem:cutItem]; 139 [cutItem release]; 140 141 // Add Copy 142 NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" 143 action:@selector(copy:) 144 keyEquivalent:@"c"]; 145 [standardEditMenu addItem:copyItem]; 146 [copyItem release]; 147 148 // Add Paste 149 NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste" 150 action:@selector(paste:) 151 keyEquivalent:@"v"]; 152 [standardEditMenu addItem:pasteItem]; 153 [pasteItem release]; 154 155 // Add Delete 156 NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete" 157 action:@selector(delete:) 158 keyEquivalent:@""]; 159 [standardEditMenu addItem:deleteItem]; 160 [deleteItem release]; 161 162 // Add Select All 163 NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All" 164 action:@selector(selectAll:) 165 keyEquivalent:@"a"]; 166 [standardEditMenu addItem:selectAllItem]; 167 [selectAllItem release]; 168 169 return standardEditMenuItem; 170 171 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 172} 173 174bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* inContent) { 175 return inContent->IsElement() && 176 (inContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, 177 nsGkAtoms::_true, eCaseMatters) || 178 inContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed, 179 nsGkAtoms::_true, eCaseMatters)); 180} 181 182// Determines how many items are visible among the siblings in a menu that are 183// before the given child. This will not count the application menu. 184int nsMenuUtilsX::CalculateNativeInsertionPoint(nsMenuObjectX* aParent, nsMenuObjectX* aChild) { 185 int insertionPoint = 0; 186 nsMenuObjectTypeX parentType = aParent->MenuObjectType(); 187 if (parentType == eMenuBarObjectType) { 188 nsMenuBarX* menubarParent = static_cast<nsMenuBarX*>(aParent); 189 uint32_t numMenus = menubarParent->GetMenuCount(); 190 for (uint32_t i = 0; i < numMenus; i++) { 191 nsMenuX* currMenu = menubarParent->GetMenuAt(i); 192 if (currMenu == aChild) return insertionPoint; // we found ourselves, break out 193 if (currMenu && [currMenu->NativeMenuItem() menu]) insertionPoint++; 194 } 195 } else if (parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType) { 196 nsMenuX* menuParent; 197 if (parentType == eSubmenuObjectType) 198 menuParent = static_cast<nsMenuX*>(aParent); 199 else 200 menuParent = static_cast<nsStandaloneNativeMenu*>(aParent)->GetMenuXObject(); 201 202 uint32_t numItems = menuParent->GetItemCount(); 203 for (uint32_t i = 0; i < numItems; i++) { 204 // Using GetItemAt instead of GetVisibleItemAt to avoid O(N^2) 205 nsMenuObjectX* currItem = menuParent->GetItemAt(i); 206 if (currItem == aChild) return insertionPoint; // we found ourselves, break out 207 NSMenuItem* nativeItem = nil; 208 nsMenuObjectTypeX currItemType = currItem->MenuObjectType(); 209 if (currItemType == eSubmenuObjectType) 210 nativeItem = static_cast<nsMenuX*>(currItem)->NativeMenuItem(); 211 else 212 nativeItem = (NSMenuItem*)(currItem->NativeData()); 213 if ([nativeItem menu]) insertionPoint++; 214 } 215 } 216 return insertionPoint; 217} 218