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