1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsStandaloneNativeMenu.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,196 @@ 1.4 +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#import <Cocoa/Cocoa.h> 1.10 + 1.11 +#include "nsStandaloneNativeMenu.h" 1.12 +#include "nsMenuUtilsX.h" 1.13 +#include "nsIDOMElement.h" 1.14 +#include "nsIMutationObserver.h" 1.15 +#include "nsGkAtoms.h" 1.16 +#include "nsObjCExceptions.h" 1.17 + 1.18 + 1.19 +NS_IMPL_ISUPPORTS(nsStandaloneNativeMenu, nsIMutationObserver, nsIStandaloneNativeMenu) 1.20 + 1.21 +nsStandaloneNativeMenu::nsStandaloneNativeMenu() 1.22 +: mMenu(nullptr) 1.23 +{ 1.24 +} 1.25 + 1.26 +nsStandaloneNativeMenu::~nsStandaloneNativeMenu() 1.27 +{ 1.28 + if (mMenu) delete mMenu; 1.29 +} 1.30 + 1.31 +NS_IMETHODIMP 1.32 +nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement) 1.33 +{ 1.34 + NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!"); 1.35 + 1.36 + nsresult rv; 1.37 + 1.38 + nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMElement, &rv); 1.39 + NS_ENSURE_SUCCESS(rv, rv); 1.40 + 1.41 + nsIAtom * tag = content->Tag(); 1.42 + if (!content->IsXUL() || 1.43 + (tag != nsGkAtoms::menu && tag != nsGkAtoms::menupopup)) 1.44 + return NS_ERROR_FAILURE; 1.45 + 1.46 + rv = nsMenuGroupOwnerX::Create(content); 1.47 + if (NS_FAILED(rv)) 1.48 + return rv; 1.49 + 1.50 + mMenu = new nsMenuX(); 1.51 + rv = mMenu->Create(this, this, content); 1.52 + if (NS_FAILED(rv)) { 1.53 + delete mMenu; 1.54 + mMenu = nullptr; 1.55 + return rv; 1.56 + } 1.57 + 1.58 + return NS_OK; 1.59 +} 1.60 + 1.61 +static void 1.62 +UpdateMenu(nsMenuX * aMenu) 1.63 +{ 1.64 + aMenu->MenuOpened(); 1.65 + aMenu->MenuClosed(); 1.66 + 1.67 + uint32_t itemCount = aMenu->GetItemCount(); 1.68 + for (uint32_t i = 0; i < itemCount; i++) { 1.69 + nsMenuObjectX * menuObject = aMenu->GetItemAt(i); 1.70 + if (menuObject->MenuObjectType() == eSubmenuObjectType) { 1.71 + UpdateMenu(static_cast<nsMenuX*>(menuObject)); 1.72 + } 1.73 + } 1.74 +} 1.75 + 1.76 +NS_IMETHODIMP 1.77 +nsStandaloneNativeMenu::MenuWillOpen(bool * aResult) 1.78 +{ 1.79 + NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!"); 1.80 + 1.81 + // Force an update on the mMenu by faking an open/close on all of 1.82 + // its submenus. 1.83 + UpdateMenu(mMenu); 1.84 + 1.85 + *aResult = true; 1.86 + return NS_OK; 1.87 +} 1.88 + 1.89 +NS_IMETHODIMP 1.90 +nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer) 1.91 +{ 1.92 + if (mMenu) { 1.93 + *aVoidPointer = mMenu->NativeData(); 1.94 + [[(NSObject *)(*aVoidPointer) retain] autorelease]; 1.95 + return NS_OK; 1.96 + } else { 1.97 + *aVoidPointer = nullptr; 1.98 + return NS_ERROR_NOT_INITIALIZED; 1.99 + } 1.100 +} 1.101 + 1.102 +static NSMenuItem * 1.103 +NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString) 1.104 +{ 1.105 + NSArray * indexes = [locationString componentsSeparatedByString:@"|"]; 1.106 + NSUInteger indexCount = [indexes count]; 1.107 + if (indexCount == 0) 1.108 + return nil; 1.109 + 1.110 + for (NSUInteger i = 0; i < indexCount; i++) { 1.111 + NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue]; 1.112 + NSInteger itemCount = [currentSubmenu numberOfItems]; 1.113 + if (targetIndex < itemCount) { 1.114 + NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex]; 1.115 + 1.116 + // If this is the last index, just return the menu item. 1.117 + if (i == (indexCount - 1)) 1.118 + return menuItem; 1.119 + 1.120 + // If this is not the last index, find the submenu and keep going. 1.121 + if ([menuItem hasSubmenu]) 1.122 + currentSubmenu = [menuItem submenu]; 1.123 + else 1.124 + return nil; 1.125 + } 1.126 + } 1.127 + 1.128 + return nil; 1.129 +} 1.130 + 1.131 +NS_IMETHODIMP 1.132 +nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString) 1.133 +{ 1.134 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.135 + 1.136 + if (!mMenu) 1.137 + return NS_ERROR_NOT_INITIALIZED; 1.138 + 1.139 + NSString * locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) 1.140 + length:indexString.Length()]; 1.141 + NSMenu * menu = static_cast<NSMenu *> (mMenu->NativeData()); 1.142 + NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString); 1.143 + 1.144 + // We can't perform an action on an item with a submenu, that will raise 1.145 + // an obj-c exception. 1.146 + if (item && ![item hasSubmenu]) { 1.147 + NSMenu * parent = [item menu]; 1.148 + if (parent) { 1.149 + // NSLog(@"Performing action for native menu item titled: %@\n", 1.150 + // [[currentSubmenu itemAtIndex:targetIndex] title]); 1.151 + [parent performActionForItemAtIndex:[parent indexOfItem:item]]; 1.152 + return NS_OK; 1.153 + } 1.154 + } 1.155 + 1.156 + return NS_ERROR_FAILURE; 1.157 + 1.158 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.159 +} 1.160 + 1.161 +NS_IMETHODIMP 1.162 +nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString) 1.163 +{ 1.164 + if (!mMenu) 1.165 + return NS_ERROR_NOT_INITIALIZED; 1.166 + 1.167 + NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) 1.168 + length:indexString.Length()]; 1.169 + NSArray* indexes = [locationString componentsSeparatedByString:@"|"]; 1.170 + unsigned int indexCount = [indexes count]; 1.171 + if (indexCount == 0) 1.172 + return NS_OK; 1.173 + 1.174 + nsMenuX* currentMenu = mMenu; 1.175 + 1.176 + // now find the correct submenu 1.177 + for (unsigned int i = 1; currentMenu && i < indexCount; i++) { 1.178 + int targetIndex = [[indexes objectAtIndex:i] intValue]; 1.179 + int visible = 0; 1.180 + uint32_t length = currentMenu->GetItemCount(); 1.181 + for (unsigned int j = 0; j < length; j++) { 1.182 + nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j); 1.183 + if (!targetMenu) 1.184 + return NS_OK; 1.185 + if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) { 1.186 + visible++; 1.187 + if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) { 1.188 + currentMenu = static_cast<nsMenuX*>(targetMenu); 1.189 + // fake open/close to cause lazy update to happen 1.190 + currentMenu->MenuOpened(); 1.191 + currentMenu->MenuClosed(); 1.192 + break; 1.193 + } 1.194 + } 1.195 + } 1.196 + } 1.197 + 1.198 + return NS_OK; 1.199 +}