|
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 <objc/objc-runtime.h> |
|
7 |
|
8 #include "nsMenuBarX.h" |
|
9 #include "nsMenuX.h" |
|
10 #include "nsMenuItemX.h" |
|
11 #include "nsMenuUtilsX.h" |
|
12 #include "nsCocoaFeatures.h" |
|
13 #include "nsCocoaUtils.h" |
|
14 #include "nsCocoaWindow.h" |
|
15 #include "nsChildView.h" |
|
16 |
|
17 #include "nsCOMPtr.h" |
|
18 #include "nsString.h" |
|
19 #include "nsGkAtoms.h" |
|
20 #include "nsObjCExceptions.h" |
|
21 #include "nsThreadUtils.h" |
|
22 |
|
23 #include "nsIContent.h" |
|
24 #include "nsIWidget.h" |
|
25 #include "nsIDocument.h" |
|
26 #include "nsIDOMDocument.h" |
|
27 #include "nsIDOMElement.h" |
|
28 |
|
29 NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil; |
|
30 nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; // Weak |
|
31 nsMenuBarX* nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; // Weak |
|
32 NSMenu* sApplicationMenu = nil; |
|
33 BOOL gSomeMenuBarPainted = NO; |
|
34 |
|
35 // We keep references to the first quit and pref item content nodes we find, which |
|
36 // will be from the hidden window. We use these when the document for the current |
|
37 // window does not have a quit or pref item. We don't need strong refs here because |
|
38 // these items are always strong ref'd by their owning menu bar (instance variable). |
|
39 static nsIContent* sAboutItemContent = nullptr; |
|
40 static nsIContent* sUpdateItemContent = nullptr; |
|
41 static nsIContent* sPrefItemContent = nullptr; |
|
42 static nsIContent* sQuitItemContent = nullptr; |
|
43 |
|
44 NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService) |
|
45 |
|
46 NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) |
|
47 { |
|
48 NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!"); |
|
49 |
|
50 nsRefPtr<nsMenuBarX> mb = new nsMenuBarX(); |
|
51 if (!mb) |
|
52 return NS_ERROR_OUT_OF_MEMORY; |
|
53 |
|
54 return mb->Create(aParent, aMenuBarNode); |
|
55 } |
|
56 |
|
57 nsMenuBarX::nsMenuBarX() |
|
58 : nsMenuGroupOwnerX(), mParentWindow(nullptr), mAwaitingDelayedPaint(false) |
|
59 { |
|
60 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
61 |
|
62 mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar" andMenuBarOwner:this]; |
|
63 |
|
64 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
65 } |
|
66 |
|
67 nsMenuBarX::~nsMenuBarX() |
|
68 { |
|
69 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
70 |
|
71 if (nsMenuBarX::sLastGeckoMenuBarPainted == this) |
|
72 nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; |
|
73 |
|
74 // the quit/pref items of a random window might have been used if there was no |
|
75 // hidden window, thus we need to invalidate the weak references. |
|
76 if (sAboutItemContent == mAboutItemContent) |
|
77 sAboutItemContent = nullptr; |
|
78 if (sUpdateItemContent == mUpdateItemContent) |
|
79 sUpdateItemContent = nullptr; |
|
80 if (sQuitItemContent == mQuitItemContent) |
|
81 sQuitItemContent = nullptr; |
|
82 if (sPrefItemContent == mPrefItemContent) |
|
83 sPrefItemContent = nullptr; |
|
84 |
|
85 // make sure we unregister ourselves as a content observer |
|
86 UnregisterForContentChanges(mContent); |
|
87 |
|
88 // We have to manually clear the array here because clearing causes menu items |
|
89 // to call back into the menu bar to unregister themselves. We don't want to |
|
90 // depend on member variable ordering to ensure that the array gets cleared |
|
91 // before the registration hash table is destroyed. |
|
92 mMenuArray.Clear(); |
|
93 |
|
94 [mNativeMenu resetMenuBarOwner]; |
|
95 [mNativeMenu release]; |
|
96 |
|
97 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
98 } |
|
99 |
|
100 nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent) |
|
101 { |
|
102 if (!aParent || !aContent) |
|
103 return NS_ERROR_INVALID_ARG; |
|
104 |
|
105 mParentWindow = aParent; |
|
106 mContent = aContent; |
|
107 |
|
108 AquifyMenuBar(); |
|
109 |
|
110 nsresult rv = nsMenuGroupOwnerX::Create(aContent); |
|
111 if (NS_FAILED(rv)) |
|
112 return rv; |
|
113 |
|
114 RegisterForContentChanges(aContent, this); |
|
115 |
|
116 ConstructNativeMenus(); |
|
117 |
|
118 // Give this to the parent window. The parent takes ownership. |
|
119 static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this); |
|
120 |
|
121 return NS_OK; |
|
122 } |
|
123 |
|
124 void nsMenuBarX::ConstructNativeMenus() |
|
125 { |
|
126 uint32_t count = mContent->GetChildCount(); |
|
127 for (uint32_t i = 0; i < count; i++) { |
|
128 nsIContent *menuContent = mContent->GetChildAt(i); |
|
129 if (menuContent && |
|
130 menuContent->Tag() == nsGkAtoms::menu && |
|
131 menuContent->IsXUL()) { |
|
132 nsMenuX* newMenu = new nsMenuX(); |
|
133 if (newMenu) { |
|
134 nsresult rv = newMenu->Create(this, this, menuContent); |
|
135 if (NS_SUCCEEDED(rv)) |
|
136 InsertMenuAtIndex(newMenu, GetMenuCount()); |
|
137 else |
|
138 delete newMenu; |
|
139 } |
|
140 } |
|
141 } |
|
142 } |
|
143 |
|
144 uint32_t nsMenuBarX::GetMenuCount() |
|
145 { |
|
146 return mMenuArray.Length(); |
|
147 } |
|
148 |
|
149 bool nsMenuBarX::MenuContainsAppMenu() |
|
150 { |
|
151 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
152 |
|
153 return ([mNativeMenu numberOfItems] > 0 && |
|
154 [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu); |
|
155 |
|
156 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); |
|
157 } |
|
158 |
|
159 nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex) |
|
160 { |
|
161 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
162 |
|
163 // If we haven't created a global Application menu yet, do it. |
|
164 if (!sApplicationMenu) { |
|
165 nsresult rv = NS_OK; // avoid warning about rv being unused |
|
166 rv = CreateApplicationMenu(aMenu); |
|
167 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu"); |
|
168 |
|
169 // Hook the new Application menu up to the menu bar. |
|
170 NSMenu* mainMenu = [NSApp mainMenu]; |
|
171 NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); |
|
172 [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu]; |
|
173 } |
|
174 |
|
175 // add menu to array that owns our menus |
|
176 mMenuArray.InsertElementAt(aIndex, aMenu); |
|
177 |
|
178 // hook up submenus |
|
179 nsIContent* menuContent = aMenu->Content(); |
|
180 if (menuContent->GetChildCount() > 0 && |
|
181 !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) { |
|
182 int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu); |
|
183 if (MenuContainsAppMenu()) |
|
184 insertionIndex++; |
|
185 [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex]; |
|
186 } |
|
187 |
|
188 return NS_OK; |
|
189 |
|
190 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
191 } |
|
192 |
|
193 void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex) |
|
194 { |
|
195 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
196 |
|
197 NS_ASSERTION(aIndex < mMenuArray.Length(), "Attempting submenu removal with bad index!"); |
|
198 |
|
199 // Our native menu and our internal menu object array might be out of sync. |
|
200 // This happens, for example, when a submenu is hidden. Because of this we |
|
201 // should not assume that a native submenu is hooked up. |
|
202 NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem(); |
|
203 int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem]; |
|
204 if (nativeMenuItemIndex != -1) |
|
205 [mNativeMenu removeItemAtIndex:nativeMenuItemIndex]; |
|
206 |
|
207 mMenuArray.RemoveElementAt(aIndex); |
|
208 |
|
209 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
210 } |
|
211 |
|
212 void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument, |
|
213 nsIContent* aContent, |
|
214 nsIAtom* aAttribute) |
|
215 { |
|
216 } |
|
217 |
|
218 void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument, |
|
219 nsIContent* aChild, |
|
220 int32_t aIndexInContainer) |
|
221 { |
|
222 RemoveMenuAtIndex(aIndexInContainer); |
|
223 } |
|
224 |
|
225 void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument, |
|
226 nsIContent* aContainer, |
|
227 nsIContent* aChild) |
|
228 { |
|
229 nsMenuX* newMenu = new nsMenuX(); |
|
230 if (newMenu) { |
|
231 nsresult rv = newMenu->Create(this, this, aChild); |
|
232 if (NS_SUCCEEDED(rv)) |
|
233 InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild)); |
|
234 else |
|
235 delete newMenu; |
|
236 } |
|
237 } |
|
238 |
|
239 void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString) |
|
240 { |
|
241 NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) |
|
242 length:indexString.Length()]; |
|
243 NSArray* indexes = [locationString componentsSeparatedByString:@"|"]; |
|
244 unsigned int indexCount = [indexes count]; |
|
245 if (indexCount == 0) |
|
246 return; |
|
247 |
|
248 nsMenuX* currentMenu = NULL; |
|
249 int targetIndex = [[indexes objectAtIndex:0] intValue]; |
|
250 int visible = 0; |
|
251 uint32_t length = mMenuArray.Length(); |
|
252 // first find a menu in the menu bar |
|
253 for (unsigned int i = 0; i < length; i++) { |
|
254 nsMenuX* menu = mMenuArray[i]; |
|
255 if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) { |
|
256 visible++; |
|
257 if (visible == (targetIndex + 1)) { |
|
258 currentMenu = menu; |
|
259 break; |
|
260 } |
|
261 } |
|
262 } |
|
263 |
|
264 if (!currentMenu) |
|
265 return; |
|
266 |
|
267 // fake open/close to cause lazy update to happen so submenus populate |
|
268 currentMenu->MenuOpened(); |
|
269 currentMenu->MenuClosed(); |
|
270 |
|
271 // now find the correct submenu |
|
272 for (unsigned int i = 1; currentMenu && i < indexCount; i++) { |
|
273 targetIndex = [[indexes objectAtIndex:i] intValue]; |
|
274 visible = 0; |
|
275 length = currentMenu->GetItemCount(); |
|
276 for (unsigned int j = 0; j < length; j++) { |
|
277 nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j); |
|
278 if (!targetMenu) |
|
279 return; |
|
280 if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) { |
|
281 visible++; |
|
282 if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) { |
|
283 currentMenu = static_cast<nsMenuX*>(targetMenu); |
|
284 // fake open/close to cause lazy update to happen |
|
285 currentMenu->MenuOpened(); |
|
286 currentMenu->MenuClosed(); |
|
287 break; |
|
288 } |
|
289 } |
|
290 } |
|
291 } |
|
292 } |
|
293 |
|
294 // Calling this forces a full reload of the menu system, reloading all native |
|
295 // menus and their items. |
|
296 // Without this testing is hard because changes to the DOM affect the native |
|
297 // menu system lazily. |
|
298 void nsMenuBarX::ForceNativeMenuReload() |
|
299 { |
|
300 // tear down everything |
|
301 while (GetMenuCount() > 0) |
|
302 RemoveMenuAtIndex(0); |
|
303 |
|
304 // construct everything |
|
305 ConstructNativeMenus(); |
|
306 } |
|
307 |
|
308 nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex) |
|
309 { |
|
310 if (mMenuArray.Length() <= aIndex) { |
|
311 NS_ERROR("Requesting menu at invalid index!"); |
|
312 return NULL; |
|
313 } |
|
314 return mMenuArray[aIndex]; |
|
315 } |
|
316 |
|
317 nsMenuX* nsMenuBarX::GetXULHelpMenu() |
|
318 { |
|
319 // The Help menu is usually (always?) the last one, so we start there and |
|
320 // count back. |
|
321 for (int32_t i = GetMenuCount() - 1; i >= 0; --i) { |
|
322 nsMenuX* aMenu = GetMenuAt(i); |
|
323 if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content())) |
|
324 return aMenu; |
|
325 } |
|
326 return nil; |
|
327 } |
|
328 |
|
329 // On SnowLeopard and later we must tell the OS which is our Help menu. |
|
330 // Otherwise it will only add Spotlight for Help (the Search item) to our |
|
331 // Help menu if its label/title is "Help" -- i.e. if the menu is in English. |
|
332 // This resolves bugs 489196 and 539317. |
|
333 void nsMenuBarX::SetSystemHelpMenu() |
|
334 { |
|
335 nsMenuX* xulHelpMenu = GetXULHelpMenu(); |
|
336 if (xulHelpMenu) { |
|
337 NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData(); |
|
338 if (helpMenu) |
|
339 [NSApp setHelpMenu:helpMenu]; |
|
340 } |
|
341 } |
|
342 |
|
343 nsresult nsMenuBarX::Paint(bool aDelayed) |
|
344 { |
|
345 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
346 |
|
347 if (!aDelayed && mAwaitingDelayedPaint) { |
|
348 return NS_OK; |
|
349 } |
|
350 mAwaitingDelayedPaint = false; |
|
351 |
|
352 // Don't try to optimize anything in this painting by checking |
|
353 // sLastGeckoMenuBarPainted because the menubar can be manipulated by |
|
354 // native dialogs and sheet code and other things besides this paint method. |
|
355 |
|
356 // We have to keep the same menu item for the Application menu so we keep |
|
357 // passing it along. |
|
358 NSMenu* outgoingMenu = [NSApp mainMenu]; |
|
359 NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); |
|
360 |
|
361 // To work around bug 722676, we sometimes need to delay making mNativeMenu |
|
362 // the main menu. This is an Apple bug that sometimes causes a top-level |
|
363 // menu item to remain highlighted after pressing a Cmd+key combination that |
|
364 // opens a new window, then closing the window. The OS temporarily |
|
365 // highlights the appropriate top-level menu item whenever you press the |
|
366 // Cmd+key combination for one of its submenus. (It does this by setting a |
|
367 // "pressed" attribute on it.) The OS then uses a timer to remove this |
|
368 // "pressed" attribute. But without our workaround we sometimes change the |
|
369 // main menu before the timer has fired, so when it fires the menu item it |
|
370 // was intended to unhighlight is no longer present in the main menu. This |
|
371 // causes the item to remain semi-permanently highlighted (until you quit |
|
372 // Firefox or navigate the main menu by hand). |
|
373 if ((outgoingMenu != mNativeMenu) && |
|
374 [outgoingMenu isKindOfClass:[GeckoNSMenu class]]) { |
|
375 if (aDelayed) { |
|
376 [(GeckoNSMenu *)outgoingMenu setDelayResignMainMenu:false]; |
|
377 } else if ([(GeckoNSMenu *)outgoingMenu delayResignMainMenu]) { |
|
378 PaintMenuBarAfterDelay(); |
|
379 return NS_OK; |
|
380 } |
|
381 } |
|
382 |
|
383 if (outgoingMenu != mNativeMenu) { |
|
384 NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain]; |
|
385 [outgoingMenu removeItemAtIndex:0]; |
|
386 [mNativeMenu insertItem:appMenuItem atIndex:0]; |
|
387 [appMenuItem release]; |
|
388 // Set menu bar and event target. |
|
389 [NSApp setMainMenu:mNativeMenu]; |
|
390 } |
|
391 SetSystemHelpMenu(); |
|
392 nsMenuBarX::sLastGeckoMenuBarPainted = this; |
|
393 |
|
394 gSomeMenuBarPainted = YES; |
|
395 |
|
396 return NS_OK; |
|
397 |
|
398 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
399 } |
|
400 |
|
401 // Used to delay a call to nsMenuBarX::Paint(). Needed to work around |
|
402 // bug 722676. |
|
403 void nsMenuBarX::PaintMenuBarAfterDelay() |
|
404 { |
|
405 mAwaitingDelayedPaint = true; |
|
406 nsMenuBarX::sCurrentPaintDelayedMenuBar = this; |
|
407 [mNativeMenu retain]; |
|
408 // The delay for Apple's unhighlight timer is 0.1f, so we make ours a bit longer. |
|
409 [mNativeMenu performSelector:@selector(delayedPaintMenuBar:) |
|
410 withObject:nil |
|
411 afterDelay:0.15f]; |
|
412 } |
|
413 |
|
414 // Returns the 'key' attribute of the 'shortcutID' object (if any) in the |
|
415 // currently active menubar's DOM document. 'shortcutID' should be the id |
|
416 // (i.e. the name) of a component that defines a commonly used (and |
|
417 // localized) cmd+key shortcut, and belongs to a keyset containing similar |
|
418 // objects. For example "key_selectAll". Returns a value that can be |
|
419 // compared to the first character of [NSEvent charactersIgnoringModifiers] |
|
420 // when [NSEvent modifierFlags] == NSCommandKeyMask. |
|
421 char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID) |
|
422 { |
|
423 if (!sLastGeckoMenuBarPainted) |
|
424 return 0; |
|
425 |
|
426 nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mDocument)); |
|
427 if (!domDoc) |
|
428 return 0; |
|
429 |
|
430 NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID); |
|
431 nsCOMPtr<nsIDOMElement> shortcutElement; |
|
432 domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement)); |
|
433 nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement); |
|
434 if (!shortcutContent) |
|
435 return 0; |
|
436 |
|
437 nsAutoString key; |
|
438 shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key); |
|
439 NS_LossyConvertUTF16toASCII keyASC(key.get()); |
|
440 const char *keyASCPtr = keyASC.get(); |
|
441 if (!keyASCPtr) |
|
442 return 0; |
|
443 // If keyID's 'key' attribute isn't exactly one character long, it's not |
|
444 // what we're looking for. |
|
445 if (strlen(keyASCPtr) != sizeof(char)) |
|
446 return 0; |
|
447 // Make sure retval is lower case. |
|
448 char retval = tolower(keyASCPtr[0]); |
|
449 |
|
450 return retval; |
|
451 } |
|
452 |
|
453 // Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so |
|
454 // the caller can hang onto it if they so choose. It is acceptable to pass nsull |
|
455 // for |outHiddenNode| if the caller doesn't care about the hidden node. |
|
456 void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode) |
|
457 { |
|
458 nsCOMPtr<nsIDOMElement> menuItem; |
|
459 inDoc->GetElementById(inID, getter_AddRefs(menuItem)); |
|
460 nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem)); |
|
461 if (menuContent) { |
|
462 menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false); |
|
463 if (outHiddenNode) { |
|
464 *outHiddenNode = menuContent.get(); |
|
465 NS_IF_ADDREF(*outHiddenNode); |
|
466 } |
|
467 } |
|
468 } |
|
469 |
|
470 // Do what is necessary to conform to the Aqua guidelines for menus. |
|
471 void nsMenuBarX::AquifyMenuBar() |
|
472 { |
|
473 nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetDocument())); |
|
474 if (domDoc) { |
|
475 // remove the "About..." item and its separator |
|
476 HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr); |
|
477 HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent)); |
|
478 if (!sAboutItemContent) |
|
479 sAboutItemContent = mAboutItemContent; |
|
480 |
|
481 // Hide the software update menu item, since it belongs in the application |
|
482 // menu on Mac OS X. |
|
483 HideItem(domDoc, NS_LITERAL_STRING("updateSeparator"), nullptr); |
|
484 HideItem(domDoc, NS_LITERAL_STRING("checkForUpdates"), getter_AddRefs(mUpdateItemContent)); |
|
485 if (!sUpdateItemContent) |
|
486 sUpdateItemContent = mUpdateItemContent; |
|
487 |
|
488 // remove quit item and its separator |
|
489 HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr); |
|
490 HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent)); |
|
491 if (!sQuitItemContent) |
|
492 sQuitItemContent = mQuitItemContent; |
|
493 |
|
494 // remove prefs item and its separator, but save off the pref content node |
|
495 // so we can invoke its command later. |
|
496 HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr); |
|
497 HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent)); |
|
498 if (!sPrefItemContent) |
|
499 sPrefItemContent = mPrefItemContent; |
|
500 |
|
501 // hide items that we use for the Application menu |
|
502 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr); |
|
503 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr); |
|
504 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr); |
|
505 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr); |
|
506 } |
|
507 } |
|
508 |
|
509 // for creating menu items destined for the Application menu |
|
510 NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action, |
|
511 int tag, NativeMenuItemTarget* target) |
|
512 { |
|
513 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
514 |
|
515 nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetDocument(); |
|
516 if (!doc) |
|
517 return nil; |
|
518 |
|
519 nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc)); |
|
520 if (!domdoc) |
|
521 return nil; |
|
522 |
|
523 // Get information from the gecko menu item |
|
524 nsAutoString label; |
|
525 nsAutoString modifiers; |
|
526 nsAutoString key; |
|
527 nsCOMPtr<nsIDOMElement> menuItem; |
|
528 domdoc->GetElementById(nodeID, getter_AddRefs(menuItem)); |
|
529 if (menuItem) { |
|
530 menuItem->GetAttribute(NS_LITERAL_STRING("label"), label); |
|
531 menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers); |
|
532 menuItem->GetAttribute(NS_LITERAL_STRING("key"), key); |
|
533 } |
|
534 else { |
|
535 return nil; |
|
536 } |
|
537 |
|
538 // Get more information about the key equivalent. Start by |
|
539 // finding the key node we need. |
|
540 NSString* keyEquiv = nil; |
|
541 unsigned int macKeyModifiers = 0; |
|
542 if (!key.IsEmpty()) { |
|
543 nsCOMPtr<nsIDOMElement> keyElement; |
|
544 domdoc->GetElementById(key, getter_AddRefs(keyElement)); |
|
545 if (keyElement) { |
|
546 nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement)); |
|
547 // first grab the key equivalent character |
|
548 nsAutoString keyChar(NS_LITERAL_STRING(" ")); |
|
549 keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar); |
|
550 if (!keyChar.EqualsLiteral(" ")) { |
|
551 keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get()) |
|
552 length:keyChar.Length()] lowercaseString]; |
|
553 } |
|
554 // now grab the key equivalent modifiers |
|
555 nsAutoString modifiersStr; |
|
556 keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr); |
|
557 uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr); |
|
558 macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers); |
|
559 } |
|
560 } |
|
561 // get the label into NSString-form |
|
562 NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get()) |
|
563 length:label.Length()]; |
|
564 |
|
565 if (!labelString) |
|
566 labelString = @""; |
|
567 if (!keyEquiv) |
|
568 keyEquiv = @""; |
|
569 |
|
570 // put together the actual NSMenuItem |
|
571 NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv]; |
|
572 |
|
573 [newMenuItem setTag:tag]; |
|
574 [newMenuItem setTarget:target]; |
|
575 [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers]; |
|
576 |
|
577 MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this]; |
|
578 [newMenuItem setRepresentedObject:info]; |
|
579 [info release]; |
|
580 |
|
581 return newMenuItem; |
|
582 |
|
583 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
584 } |
|
585 |
|
586 // build the Application menu shared by all menu bars |
|
587 nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu) |
|
588 { |
|
589 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
590 |
|
591 // At this point, the application menu is the application menu from |
|
592 // the nib in cocoa widgets. We do not have a way to create an application |
|
593 // menu manually, so we grab the one from the nib and use that. |
|
594 sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain]; |
|
595 |
|
596 /* |
|
597 We support the following menu items here: |
|
598 |
|
599 Menu Item DOM Node ID Notes |
|
600 |
|
601 ======================== |
|
602 = About This App = <- aboutName |
|
603 = Check for Updates... = <- checkForUpdates |
|
604 ======================== |
|
605 = Preferences... = <- menu_preferences |
|
606 ======================== |
|
607 = Services > = <- menu_mac_services <- (do not define key equivalent) |
|
608 ======================== |
|
609 = Hide App = <- menu_mac_hide_app |
|
610 = Hide Others = <- menu_mac_hide_others |
|
611 = Show All = <- menu_mac_show_all |
|
612 ======================== |
|
613 = Quit = <- menu_FileQuitItem |
|
614 ======================== |
|
615 |
|
616 If any of them are ommitted from the application's DOM, we just don't add |
|
617 them. We always add a "Quit" item, but if an app developer does not provide a |
|
618 DOM node with the right ID for the Quit item, we add it in English. App |
|
619 developers need only add each node with a label and a key equivalent (if they |
|
620 want one). Other attributes are optional. Like so: |
|
621 |
|
622 <menuitem id="menu_preferences" |
|
623 label="&preferencesCmdMac.label;" |
|
624 key="open_prefs_key"/> |
|
625 |
|
626 We need to use this system for localization purposes, until we have a better way |
|
627 to define the Application menu to be used on Mac OS X. |
|
628 */ |
|
629 |
|
630 if (sApplicationMenu) { |
|
631 // This code reads attributes we are going to care about from the DOM elements |
|
632 |
|
633 NSMenuItem *itemBeingAdded = nil; |
|
634 BOOL addAboutSeparator = FALSE; |
|
635 |
|
636 // Add the About menu item |
|
637 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:), |
|
638 eCommand_ID_About, nsMenuBarX::sNativeEventTarget); |
|
639 if (itemBeingAdded) { |
|
640 [sApplicationMenu addItem:itemBeingAdded]; |
|
641 [itemBeingAdded release]; |
|
642 itemBeingAdded = nil; |
|
643 |
|
644 addAboutSeparator = TRUE; |
|
645 } |
|
646 |
|
647 // Add the software update menu item |
|
648 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("checkForUpdates"), @selector(menuItemHit:), |
|
649 eCommand_ID_Update, nsMenuBarX::sNativeEventTarget); |
|
650 if (itemBeingAdded) { |
|
651 [sApplicationMenu addItem:itemBeingAdded]; |
|
652 [itemBeingAdded release]; |
|
653 itemBeingAdded = nil; |
|
654 |
|
655 addAboutSeparator = TRUE; |
|
656 } |
|
657 |
|
658 // Add separator if either the About item or software update item exists |
|
659 if (addAboutSeparator) |
|
660 [sApplicationMenu addItem:[NSMenuItem separatorItem]]; |
|
661 |
|
662 // Add the Preferences menu item |
|
663 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:), |
|
664 eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget); |
|
665 if (itemBeingAdded) { |
|
666 [sApplicationMenu addItem:itemBeingAdded]; |
|
667 [itemBeingAdded release]; |
|
668 itemBeingAdded = nil; |
|
669 |
|
670 // Add separator after Preferences menu |
|
671 [sApplicationMenu addItem:[NSMenuItem separatorItem]]; |
|
672 } |
|
673 |
|
674 // Add Services menu item |
|
675 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil, |
|
676 0, nil); |
|
677 if (itemBeingAdded) { |
|
678 [sApplicationMenu addItem:itemBeingAdded]; |
|
679 |
|
680 // set this menu item up as the Mac OS X Services menu |
|
681 NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""]; |
|
682 [itemBeingAdded setSubmenu:servicesMenu]; |
|
683 [NSApp setServicesMenu:servicesMenu]; |
|
684 |
|
685 [itemBeingAdded release]; |
|
686 itemBeingAdded = nil; |
|
687 |
|
688 // Add separator after Services menu |
|
689 [sApplicationMenu addItem:[NSMenuItem separatorItem]]; |
|
690 } |
|
691 |
|
692 BOOL addHideShowSeparator = FALSE; |
|
693 |
|
694 // Add menu item to hide this application |
|
695 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:), |
|
696 eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget); |
|
697 if (itemBeingAdded) { |
|
698 [sApplicationMenu addItem:itemBeingAdded]; |
|
699 [itemBeingAdded release]; |
|
700 itemBeingAdded = nil; |
|
701 |
|
702 addHideShowSeparator = TRUE; |
|
703 } |
|
704 |
|
705 // Add menu item to hide other applications |
|
706 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:), |
|
707 eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget); |
|
708 if (itemBeingAdded) { |
|
709 [sApplicationMenu addItem:itemBeingAdded]; |
|
710 [itemBeingAdded release]; |
|
711 itemBeingAdded = nil; |
|
712 |
|
713 addHideShowSeparator = TRUE; |
|
714 } |
|
715 |
|
716 // Add menu item to show all applications |
|
717 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:), |
|
718 eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget); |
|
719 if (itemBeingAdded) { |
|
720 [sApplicationMenu addItem:itemBeingAdded]; |
|
721 [itemBeingAdded release]; |
|
722 itemBeingAdded = nil; |
|
723 |
|
724 addHideShowSeparator = TRUE; |
|
725 } |
|
726 |
|
727 // Add a separator after the hide/show menus if at least one exists |
|
728 if (addHideShowSeparator) |
|
729 [sApplicationMenu addItem:[NSMenuItem separatorItem]]; |
|
730 |
|
731 // Add quit menu item |
|
732 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:), |
|
733 eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget); |
|
734 if (itemBeingAdded) { |
|
735 [sApplicationMenu addItem:itemBeingAdded]; |
|
736 [itemBeingAdded release]; |
|
737 itemBeingAdded = nil; |
|
738 } |
|
739 else { |
|
740 // the current application does not have a DOM node for "Quit". Add one |
|
741 // anyway, in English. |
|
742 NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:) |
|
743 keyEquivalent:@"q"] autorelease]; |
|
744 [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget]; |
|
745 [defaultQuitItem setTag:eCommand_ID_Quit]; |
|
746 [sApplicationMenu addItem:defaultQuitItem]; |
|
747 } |
|
748 } |
|
749 |
|
750 return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE; |
|
751 |
|
752 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
753 } |
|
754 |
|
755 void nsMenuBarX::SetParent(nsIWidget* aParent) |
|
756 { |
|
757 mParentWindow = aParent; |
|
758 } |
|
759 |
|
760 |
|
761 // |
|
762 // Objective-C class used to allow us to have keyboard commands |
|
763 // look like they are doing something but actually do nothing. |
|
764 // We allow mouse actions to work normally. |
|
765 // |
|
766 |
|
767 // Controls whether or not native menu items should invoke their commands. |
|
768 static BOOL gMenuItemsExecuteCommands = YES; |
|
769 |
|
770 @implementation GeckoNSMenu |
|
771 |
|
772 - (id)initWithTitle:(NSString *)aTitle |
|
773 { |
|
774 if (self = [super initWithTitle:aTitle]) { |
|
775 mMenuBarOwner = nullptr; |
|
776 mDelayResignMainMenu = false; |
|
777 } |
|
778 return self; |
|
779 } |
|
780 |
|
781 - (id)initWithTitle:(NSString *)aTitle andMenuBarOwner:(nsMenuBarX *)aMenuBarOwner |
|
782 { |
|
783 if (self = [super initWithTitle:aTitle]) { |
|
784 mMenuBarOwner = aMenuBarOwner; |
|
785 mDelayResignMainMenu = false; |
|
786 } |
|
787 return self; |
|
788 } |
|
789 |
|
790 - (void)resetMenuBarOwner |
|
791 { |
|
792 mMenuBarOwner = nil; |
|
793 } |
|
794 |
|
795 - (bool)delayResignMainMenu |
|
796 { |
|
797 return mDelayResignMainMenu; |
|
798 } |
|
799 |
|
800 - (void)setDelayResignMainMenu:(bool)aShouldDelay |
|
801 { |
|
802 mDelayResignMainMenu = aShouldDelay; |
|
803 } |
|
804 |
|
805 // Used to delay a call to nsMenuBarX::Paint(). Needed to work around |
|
806 // bug 722676. |
|
807 - (void)delayedPaintMenuBar:(id)unused |
|
808 { |
|
809 if (mMenuBarOwner) { |
|
810 if (mMenuBarOwner == nsMenuBarX::sCurrentPaintDelayedMenuBar) { |
|
811 mMenuBarOwner->Paint(true); |
|
812 nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; |
|
813 } else { |
|
814 mMenuBarOwner->ResetAwaitingDelayedPaint(); |
|
815 } |
|
816 } |
|
817 [self release]; |
|
818 } |
|
819 |
|
820 // Undocumented method, present unchanged since OS X 10.6, used to temporarily |
|
821 // highlight a top-level menu item when an appropriate Cmd+key combination is |
|
822 // pressed. |
|
823 - (void)_performActionWithHighlightingForItemAtIndex:(NSInteger)index; |
|
824 { |
|
825 NSMenu *mainMenu = [NSApp mainMenu]; |
|
826 if ([mainMenu isKindOfClass:[GeckoNSMenu class]]) { |
|
827 [(GeckoNSMenu *)mainMenu setDelayResignMainMenu:true]; |
|
828 } |
|
829 [super _performActionWithHighlightingForItemAtIndex:index]; |
|
830 } |
|
831 |
|
832 // Keyboard commands should not cause menu items to invoke their |
|
833 // commands when there is a key window because we'd rather send |
|
834 // the keyboard command to the window. We still have the menus |
|
835 // go through the mechanics so they'll give the proper visual |
|
836 // feedback. |
|
837 - (BOOL)performKeyEquivalent:(NSEvent *)theEvent |
|
838 { |
|
839 // We've noticed that Mac OS X expects this check in subclasses before |
|
840 // calling NSMenu's "performKeyEquivalent:". |
|
841 // |
|
842 // There is no case in which we'd need to do anything or return YES |
|
843 // when we have no items so we can just do this check first. |
|
844 if ([self numberOfItems] <= 0) { |
|
845 return NO; |
|
846 } |
|
847 |
|
848 NSWindow *keyWindow = [NSApp keyWindow]; |
|
849 |
|
850 // If there is no key window then just behave normally. This |
|
851 // probably means that this menu is associated with Gecko's |
|
852 // hidden window. |
|
853 if (!keyWindow) { |
|
854 return [super performKeyEquivalent:theEvent]; |
|
855 } |
|
856 |
|
857 // Plugins normally eat all keyboard commands, this hack mitigates |
|
858 // the problem. |
|
859 BOOL handleForPluginHack = NO; |
|
860 NSResponder *firstResponder = [keyWindow firstResponder]; |
|
861 if (firstResponder && |
|
862 [firstResponder isKindOfClass:[ChildView class]] && |
|
863 [(ChildView*)firstResponder isPluginView]) { |
|
864 handleForPluginHack = YES; |
|
865 // Maintain a list of cmd+key combinations that we never act on (in the |
|
866 // browser) when the keyboard focus is in a plugin. What a particular |
|
867 // cmd+key combo means here (to the browser) is governed by browser.dtd, |
|
868 // which "contains the browser main menu items". |
|
869 UInt32 modifierFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; |
|
870 if (modifierFlags == NSCommandKeyMask) { |
|
871 NSString *unmodchars = [theEvent charactersIgnoringModifiers]; |
|
872 if ([unmodchars length] == 1) { |
|
873 if ([unmodchars characterAtIndex:0] == nsMenuBarX::GetLocalizedAccelKey("key_selectAll")) { |
|
874 handleForPluginHack = NO; |
|
875 } |
|
876 } |
|
877 } |
|
878 } |
|
879 |
|
880 gMenuItemsExecuteCommands = handleForPluginHack; |
|
881 [super performKeyEquivalent:theEvent]; |
|
882 gMenuItemsExecuteCommands = YES; // return to default |
|
883 |
|
884 // Return YES if we invoked a command and there is now no key window or we changed |
|
885 // the first responder. In this case we do not want to propagate the event because |
|
886 // we don't want it handled again. |
|
887 if (handleForPluginHack) { |
|
888 if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) { |
|
889 return YES; |
|
890 } |
|
891 } |
|
892 |
|
893 // Return NO so that we can handle the event via NSView's "keyDown:". |
|
894 return NO; |
|
895 } |
|
896 |
|
897 @end |
|
898 |
|
899 // |
|
900 // Objective-C class used as action target for menu items |
|
901 // |
|
902 |
|
903 @implementation NativeMenuItemTarget |
|
904 |
|
905 // called when some menu item in this menu gets hit |
|
906 -(IBAction)menuItemHit:(id)sender |
|
907 { |
|
908 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
909 |
|
910 // menuGroupOwner below is an nsMenuBarX object, which we sometimes access |
|
911 // after it's been deleted, causing crashes (see bug 704866 and bug 670914). |
|
912 // To fix this "correctly", in nsMenuBarX::~nsMenuBarX() we'd need to |
|
913 // iterate through every NSMenuItem in nsMenuBarX::mNativeMenu and its |
|
914 // submenus, which might be quite time consuming. (For every NSMenuItem |
|
915 // that has a "representedObject" that's a MenuItemInfo object, we'd need |
|
916 // need to null out its "menuGroupOwner" if it's the same as the nsMenuBarX |
|
917 // object being destroyed.) But if the nsMenuBarX object being destroyed |
|
918 // corresponds to the currently focused window, it's likely that the |
|
919 // nsMenuBarX destructor will null out sLastGeckoMenuBarPainted. So we can |
|
920 // probably eliminate most of these crashes if we use this variable being |
|
921 // null as an indicator that we're likely to crash below when we dereference |
|
922 // menuGroupOwner. |
|
923 if (!nsMenuBarX::sLastGeckoMenuBarPainted) { |
|
924 return; |
|
925 } |
|
926 |
|
927 if (!gMenuItemsExecuteCommands) { |
|
928 return; |
|
929 } |
|
930 |
|
931 int tag = [sender tag]; |
|
932 |
|
933 MenuItemInfo* info = [sender representedObject]; |
|
934 if (!info) |
|
935 return; |
|
936 |
|
937 nsMenuGroupOwnerX* menuGroupOwner = [info menuGroupOwner]; |
|
938 if (!menuGroupOwner) |
|
939 return; |
|
940 |
|
941 nsMenuBarX* menuBar = nullptr; |
|
942 if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) |
|
943 menuBar = static_cast<nsMenuBarX*>(menuGroupOwner); |
|
944 |
|
945 // Do special processing if this is for an app-global command. |
|
946 if (tag == eCommand_ID_About) { |
|
947 nsIContent* mostSpecificContent = sAboutItemContent; |
|
948 if (menuBar && menuBar->mAboutItemContent) |
|
949 mostSpecificContent = menuBar->mAboutItemContent; |
|
950 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); |
|
951 return; |
|
952 } |
|
953 else if (tag == eCommand_ID_Update) { |
|
954 nsIContent* mostSpecificContent = sUpdateItemContent; |
|
955 if (menuBar && menuBar->mUpdateItemContent) |
|
956 mostSpecificContent = menuBar->mUpdateItemContent; |
|
957 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); |
|
958 } |
|
959 else if (tag == eCommand_ID_Prefs) { |
|
960 nsIContent* mostSpecificContent = sPrefItemContent; |
|
961 if (menuBar && menuBar->mPrefItemContent) |
|
962 mostSpecificContent = menuBar->mPrefItemContent; |
|
963 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); |
|
964 return; |
|
965 } |
|
966 else if (tag == eCommand_ID_HideApp) { |
|
967 [NSApp hide:sender]; |
|
968 return; |
|
969 } |
|
970 else if (tag == eCommand_ID_HideOthers) { |
|
971 [NSApp hideOtherApplications:sender]; |
|
972 return; |
|
973 } |
|
974 else if (tag == eCommand_ID_ShowAll) { |
|
975 [NSApp unhideAllApplications:sender]; |
|
976 return; |
|
977 } |
|
978 else if (tag == eCommand_ID_Quit) { |
|
979 nsIContent* mostSpecificContent = sQuitItemContent; |
|
980 if (menuBar && menuBar->mQuitItemContent) |
|
981 mostSpecificContent = menuBar->mQuitItemContent; |
|
982 // If we have some content for quit we execute it. Otherwise we send a native app terminate |
|
983 // message. If you want to stop a quit from happening, provide quit content and return |
|
984 // the event as unhandled. |
|
985 if (mostSpecificContent) { |
|
986 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); |
|
987 } |
|
988 else { |
|
989 [NSApp terminate:nil]; |
|
990 } |
|
991 return; |
|
992 } |
|
993 |
|
994 // given the commandID, look it up in our hashtable and dispatch to |
|
995 // that menu item. |
|
996 if (menuGroupOwner) { |
|
997 nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag)); |
|
998 if (menuItem) |
|
999 menuItem->DoCommand(); |
|
1000 } |
|
1001 |
|
1002 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
1003 } |
|
1004 |
|
1005 @end |
|
1006 |
|
1007 // Objective-C class used for menu items on the Services menu to allow Gecko |
|
1008 // to override their standard behavior in order to stop key equivalents from |
|
1009 // firing in certain instances. When gMenuItemsExecuteCommands is NO, we return |
|
1010 // a dummy target and action instead of the actual target and action. |
|
1011 |
|
1012 @implementation GeckoServicesNSMenuItem |
|
1013 |
|
1014 - (id) target |
|
1015 { |
|
1016 id realTarget = [super target]; |
|
1017 if (gMenuItemsExecuteCommands) |
|
1018 return realTarget; |
|
1019 else |
|
1020 return realTarget ? self : nil; |
|
1021 } |
|
1022 |
|
1023 - (SEL) action |
|
1024 { |
|
1025 SEL realAction = [super action]; |
|
1026 if (gMenuItemsExecuteCommands) |
|
1027 return realAction; |
|
1028 else |
|
1029 return realAction ? @selector(_doNothing:) : NULL; |
|
1030 } |
|
1031 |
|
1032 - (void) _doNothing:(id)sender |
|
1033 { |
|
1034 } |
|
1035 |
|
1036 @end |
|
1037 |
|
1038 // Objective-C class used as the Services menu so that Gecko can override the |
|
1039 // standard behavior of the Services menu in order to stop key equivalents |
|
1040 // from firing in certain instances. |
|
1041 |
|
1042 @implementation GeckoServicesNSMenu |
|
1043 |
|
1044 - (void)addItem:(NSMenuItem *)newItem |
|
1045 { |
|
1046 [self _overrideClassOfMenuItem:newItem]; |
|
1047 [super addItem:newItem]; |
|
1048 } |
|
1049 |
|
1050 - (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv |
|
1051 { |
|
1052 NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv]; |
|
1053 [self _overrideClassOfMenuItem:newItem]; |
|
1054 return newItem; |
|
1055 } |
|
1056 |
|
1057 - (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index |
|
1058 { |
|
1059 [self _overrideClassOfMenuItem:newItem]; |
|
1060 [super insertItem:newItem atIndex:index]; |
|
1061 } |
|
1062 |
|
1063 - (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index |
|
1064 { |
|
1065 NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index]; |
|
1066 [self _overrideClassOfMenuItem:newItem]; |
|
1067 return newItem; |
|
1068 } |
|
1069 |
|
1070 - (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem |
|
1071 { |
|
1072 if ([menuItem class] == [NSMenuItem class]) |
|
1073 object_setClass(menuItem, [GeckoServicesNSMenuItem class]); |
|
1074 } |
|
1075 |
|
1076 @end |