widget/cocoa/nsStandaloneNativeMenu.mm
author Ms2ger <ms2ger@gmail.com>
Sat, 25 Aug 2012 13:18:18 +0200
changeset 103431 941fff75a9e7ec1cee54538af443c1e7e16f1cf7
parent 102997 a16372ce30b5f6b747246b01fcd215a4bf3b6342
child 106706 c4f83d9d8243f3f853a5356188164a5fddee2b5a
permissions -rw-r--r--
Back out bug 636063, bug 774988 and bug 784647 for busting all of Android.

/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#import <Cocoa/Cocoa.h>

#include "nsStandaloneNativeMenu.h"
#include "nsMenuUtilsX.h"
#include "nsIDOMElement.h"
#include "nsIMutationObserver.h"
#include "nsEvent.h"
#include "nsGUIEvent.h"
#include "nsGkAtoms.h"
#include "nsContentUtils.h"
#include "nsObjCExceptions.h"


NS_IMPL_ISUPPORTS2(nsStandaloneNativeMenu, nsIMutationObserver, nsIStandaloneNativeMenu)

nsStandaloneNativeMenu::nsStandaloneNativeMenu()
: mMenu(nullptr)
{
}

nsStandaloneNativeMenu::~nsStandaloneNativeMenu()
{
  if (mMenu) delete mMenu;
}

NS_IMETHODIMP
nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement)
{
  NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!");

  nsresult rv;

  nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMElement, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsIAtom * tag = content->Tag();
  if (!content->IsXUL() ||
      (tag != nsGkAtoms::menu && tag != nsGkAtoms::menupopup))
    return NS_ERROR_FAILURE;

  rv = nsMenuGroupOwnerX::Create(content);
  if (NS_FAILED(rv))
    return rv;

  mMenu = new nsMenuX();
  rv = mMenu->Create(this, this, content);
  if (NS_FAILED(rv)) {
    delete mMenu;
    mMenu = nullptr;
    return rv;
  }

  return NS_OK;
}

static void
UpdateMenu(nsMenuX * aMenu)
{
  aMenu->MenuOpened();
  aMenu->MenuClosed();

  uint32_t itemCount = aMenu->GetItemCount();
  for (uint32_t i = 0; i < itemCount; i++) {
    nsMenuObjectX * menuObject = aMenu->GetItemAt(i);
    if (menuObject->MenuObjectType() == eSubmenuObjectType) {
      UpdateMenu(static_cast<nsMenuX*>(menuObject));
    }
  }
}

NS_IMETHODIMP
nsStandaloneNativeMenu::MenuWillOpen(bool * aResult)
{
  NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!");

  // Force an update on the mMenu by faking an open/close on all of
  // its submenus.
  UpdateMenu(mMenu);

  *aResult = true;
  return NS_OK;
}

NS_IMETHODIMP
nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer)
{
  if (mMenu) {
    *aVoidPointer = mMenu->NativeData();
    [[(NSObject *)(*aVoidPointer) retain] autorelease];
    return NS_OK;
  }  else {
    *aVoidPointer = nullptr;
    return NS_ERROR_NOT_INITIALIZED;
  }
}

static NSMenuItem *
NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString)
{
  NSArray * indexes = [locationString componentsSeparatedByString:@"|"];
  NSUInteger indexCount = [indexes count];
  if (indexCount == 0)
    return nil;

  for (NSUInteger i = 0; i < indexCount; i++) {
    NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue];
    NSInteger itemCount = [currentSubmenu numberOfItems];
    if (targetIndex < itemCount) {
      NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];

      // If this is the last index, just return the menu item.
      if (i == (indexCount - 1))
        return menuItem;

      // If this is not the last index, find the submenu and keep going.
      if ([menuItem hasSubmenu])
        currentSubmenu = [menuItem submenu];
      else
        return nil;
    }
  }

  return nil;
}

NS_IMETHODIMP
nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString)
{
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
  
  if (!mMenu)
    return NS_ERROR_NOT_INITIALIZED;

  NSString * locationString = [NSString stringWithCharacters:indexString.BeginReading() length:indexString.Length()];
  NSMenu * menu = static_cast<NSMenu *> (mMenu->NativeData());
  NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString);

  // We can't perform an action on an item with a submenu, that will raise
  // an obj-c exception.
  if (item && ![item hasSubmenu]) {
    NSMenu * parent = [item menu];
    if (parent) {
      // NSLog(@"Performing action for native menu item titled: %@\n",
      //       [[currentSubmenu itemAtIndex:targetIndex] title]);
      [parent performActionForItemAtIndex:[parent indexOfItem:item]];
      return NS_OK;
    }
  }

  return NS_ERROR_FAILURE;
  
  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

NS_IMETHODIMP
nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString)
{
  if (!mMenu)
    return NS_ERROR_NOT_INITIALIZED;

  NSString* locationString = [NSString stringWithCharacters:indexString.BeginReading() length:indexString.Length()];
  NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
  unsigned int indexCount = [indexes count];
  if (indexCount == 0)
    return NS_OK;

  nsMenuX* currentMenu = mMenu;

  // now find the correct submenu
  for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
    int targetIndex = [[indexes objectAtIndex:i] intValue];
    int visible = 0;
    uint32_t length = currentMenu->GetItemCount();
    for (unsigned int j = 0; j < length; j++) {
      nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
      if (!targetMenu)
        return NS_OK;
      if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
        visible++;
        if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
          currentMenu = static_cast<nsMenuX*>(targetMenu);
          // fake open/close to cause lazy update to happen
          currentMenu->MenuOpened();
          currentMenu->MenuClosed();
          break;
        }
      }
    }
  }

  return NS_OK;
}