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