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