widget/cocoa/nsMenuItemX.mm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/. */
     6 #include "nsMenuItemX.h"
     7 #include "nsMenuBarX.h"
     8 #include "nsMenuX.h"
     9 #include "nsMenuItemIconX.h"
    10 #include "nsMenuUtilsX.h"
    11 #include "nsCocoaUtils.h"
    13 #include "nsObjCExceptions.h"
    15 #include "nsCOMPtr.h"
    16 #include "nsGkAtoms.h"
    18 #include "mozilla/dom/Element.h"
    19 #include "nsIWidget.h"
    20 #include "nsIDocument.h"
    21 #include "nsIDOMDocument.h"
    22 #include "nsIDOMElement.h"
    23 #include "nsIDOMEvent.h"
    25 nsMenuItemX::nsMenuItemX()
    26 {
    27   mType           = eRegularMenuItemType;
    28   mNativeMenuItem = nil;
    29   mMenuParent     = nullptr;
    30   mMenuGroupOwner = nullptr;
    31   mIsChecked      = false;
    33   MOZ_COUNT_CTOR(nsMenuItemX);
    34 }
    36 nsMenuItemX::~nsMenuItemX()
    37 {
    38   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
    40   // Prevent the icon object from outliving us.
    41   if (mIcon)
    42     mIcon->Destroy();
    44   // autorelease the native menu item so that anything else happening to this
    45   // object happens before the native menu item actually dies
    46   [mNativeMenuItem autorelease];
    48   if (mContent)
    49     mMenuGroupOwner->UnregisterForContentChanges(mContent);
    50   if (mCommandContent)
    51     mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
    53   MOZ_COUNT_DTOR(nsMenuItemX);
    55   NS_OBJC_END_TRY_ABORT_BLOCK;
    56 }
    58 nsresult nsMenuItemX::Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
    59                              nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
    60 {
    61   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
    63   mType = aItemType;
    64   mMenuParent = aParent;
    65   mContent = aNode;
    67   mMenuGroupOwner = aMenuGroupOwner;
    68   NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");
    70   mMenuGroupOwner->RegisterForContentChanges(mContent, this);
    72   nsIDocument *doc = mContent->GetCurrentDoc();
    74   // if we have a command associated with this menu item, register for changes
    75   // to the command DOM node
    76   if (doc) {
    77     nsAutoString ourCommand;
    78     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ourCommand);
    80     if (!ourCommand.IsEmpty()) {
    81       nsIContent *commandElement = doc->GetElementById(ourCommand);
    83       if (commandElement) {
    84         mCommandContent = commandElement;
    85         // register to observe the command DOM element
    86         mMenuGroupOwner->RegisterForContentChanges(mCommandContent, this);
    87       }
    88     }
    89   }
    91   // decide enabled state based on command content if it exists, otherwise do it based
    92   // on our own content
    93   bool isEnabled;
    94   if (mCommandContent)
    95     isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
    96   else
    97     isEnabled = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
    99   // set up the native menu item
   100   if (mType == eSeparatorMenuItemType) {
   101     mNativeMenuItem = [[NSMenuItem separatorItem] retain];
   102   }
   103   else {
   104     NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
   105     mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
   107     [mNativeMenuItem setEnabled:(BOOL)isEnabled];
   109     SetChecked(mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
   110                                      nsGkAtoms::_true, eCaseMatters));
   111     SetKeyEquiv();
   112   }
   114   mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
   115   if (!mIcon)
   116     return NS_ERROR_OUT_OF_MEMORY;
   118   return NS_OK;
   120   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   121 }
   123 nsresult nsMenuItemX::SetChecked(bool aIsChecked)
   124 {
   125   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   127   mIsChecked = aIsChecked;
   129   // update the content model. This will also handle unchecking our siblings
   130   // if we are a radiomenu
   131   mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, 
   132                     mIsChecked ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), true);
   134   // update native menu item
   135   if (mIsChecked)
   136     [mNativeMenuItem setState:NSOnState];
   137   else
   138     [mNativeMenuItem setState:NSOffState];
   140   return NS_OK;
   142   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   143 }
   145 EMenuItemType nsMenuItemX::GetMenuItemType()
   146 {
   147   return mType;
   148 }
   150 // Executes the "cached" javaScript command.
   151 // Returns NS_OK if the command was executed properly, otherwise an error code.
   152 void nsMenuItemX::DoCommand()
   153 {
   154   // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
   155   if (mType == eCheckboxMenuItemType ||
   156       (mType == eRadioMenuItemType && !mIsChecked)) {
   157     if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
   158                                nsGkAtoms::_false, eCaseMatters))
   159     SetChecked(!mIsChecked);
   160     /* the AttributeChanged code will update all the internal state */
   161   }
   163   nsMenuUtilsX::DispatchCommandTo(mContent);
   164 }
   166 nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, bool *preventDefaultCalled)
   167 {
   168   if (!mContent)
   169     return NS_ERROR_FAILURE;
   171   // get owner document for content
   172   nsCOMPtr<nsIDocument> parentDoc = mContent->OwnerDoc();
   174   // get interface for creating DOM events from content owner document
   175   nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(parentDoc);
   176   if (!domDoc) {
   177     NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocument");
   178     return NS_ERROR_FAILURE;
   179   }
   181   // create DOM event
   182   nsCOMPtr<nsIDOMEvent> event;
   183   nsresult rv = domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
   184   if (NS_FAILED(rv)) {
   185     NS_WARNING("Failed to create nsIDOMEvent");
   186     return rv;
   187   }
   188   event->InitEvent(eventName, true, true);
   190   // mark DOM event as trusted
   191   event->SetTrusted(true);
   193   // send DOM event
   194   rv = mContent->DispatchEvent(event, preventDefaultCalled);
   195   if (NS_FAILED(rv)) {
   196     NS_WARNING("Failed to send DOM event via EventTarget");
   197     return rv;
   198   }
   200   return NS_OK;
   201 }
   203 // Walk the sibling list looking for nodes with the same name and
   204 // uncheck them all.
   205 void nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent)
   206 {
   207   nsAutoString myGroupName;
   208   inCheckedContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, myGroupName);
   209   if (!myGroupName.Length()) // no groupname, nothing to do
   210     return;
   212   nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
   213   if (!parent)
   214     return;
   216   // loop over siblings
   217   uint32_t count = parent->GetChildCount();
   218   for (uint32_t i = 0; i < count; i++) {
   219     nsIContent *sibling = parent->GetChildAt(i);
   220     if (sibling) {      
   221       if (sibling != inCheckedContent) { // skip this node
   222         // if the current sibling is in the same group, clear it
   223         if (sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
   224                                  myGroupName, eCaseMatters))
   225           sibling->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, NS_LITERAL_STRING("false"), true);
   226       }
   227     }    
   228   }
   229 }
   231 void nsMenuItemX::SetKeyEquiv()
   232 {
   233   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   235   // Set key shortcut and modifiers
   236   nsAutoString keyValue;
   237   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
   238   if (!keyValue.IsEmpty() && mContent->GetCurrentDoc()) {
   239     nsIContent *keyContent = mContent->GetCurrentDoc()->GetElementById(keyValue);
   240     if (keyContent) {
   241       nsAutoString keyChar;
   242       bool hasKey = keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
   244       if (!hasKey || keyChar.IsEmpty()) {
   245         nsAutoString keyCodeName;
   246         keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeName);
   247         uint32_t charCode =
   248           nsCocoaUtils::ConvertGeckoNameToMacCharCode(keyCodeName);
   249         if (charCode) {
   250           keyChar.Assign(charCode);
   251         }
   252         else {
   253           keyChar.Assign(NS_LITERAL_STRING(" "));
   254         }
   255       }
   257       nsAutoString modifiersStr;
   258       keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
   259       uint8_t modifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
   261       unsigned int macModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(modifiers);
   262       [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
   264       NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)keyChar.get()
   265                                                          length:keyChar.Length()] lowercaseString];
   266       if ([keyEquivalent isEqualToString:@" "])
   267         [mNativeMenuItem setKeyEquivalent:@""];
   268       else
   269         [mNativeMenuItem setKeyEquivalent:keyEquivalent];
   271       return;
   272     }
   273   }
   275   // if the key was removed, clear the key
   276   [mNativeMenuItem setKeyEquivalent:@""];
   278   NS_OBJC_END_TRY_ABORT_BLOCK;
   279 }
   281 //
   282 // nsChangeObserver
   283 //
   285 void
   286 nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
   287 {
   288   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   290   if (!aContent)
   291     return;
   293   if (aContent == mContent) { // our own content node changed
   294     if (aAttribute == nsGkAtoms::checked) {
   295       // if we're a radio menu, uncheck our sibling radio items. No need to
   296       // do any of this if we're just a normal check menu.
   297       if (mType == eRadioMenuItemType) {
   298         if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
   299                                   nsGkAtoms::_true, eCaseMatters))
   300           UncheckRadioSiblings(mContent);
   301       }
   302       mMenuParent->SetRebuild(true);
   303     }
   304     else if (aAttribute == nsGkAtoms::hidden ||
   305              aAttribute == nsGkAtoms::collapsed ||
   306              aAttribute == nsGkAtoms::label) {
   307       mMenuParent->SetRebuild(true);
   308     }
   309     else if (aAttribute == nsGkAtoms::key) {
   310       SetKeyEquiv();
   311     }
   312     else if (aAttribute == nsGkAtoms::image) {
   313       SetupIcon();
   314     }
   315     else if (aAttribute == nsGkAtoms::disabled) {
   316       if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
   317         [mNativeMenuItem setEnabled:NO];
   318       else
   319         [mNativeMenuItem setEnabled:YES];
   320     }
   321   }
   322   else if (aContent == mCommandContent) {
   323     // the only thing that really matters when the menu isn't showing is the
   324     // enabled state since it enables/disables keyboard commands
   325     if (aAttribute == nsGkAtoms::disabled) {
   326       // first we sync our menu item DOM node with the command DOM node
   327       nsAutoString commandDisabled;
   328       nsAutoString menuDisabled;
   329       aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled);
   330       mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, menuDisabled);
   331       if (!commandDisabled.Equals(menuDisabled)) {
   332         // The menu's disabled state needs to be updated to match the command.
   333         if (commandDisabled.IsEmpty()) 
   334           mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
   335         else
   336           mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled, true);
   337       }
   338       // now we sync our native menu item with the command DOM node
   339       if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
   340         [mNativeMenuItem setEnabled:NO];
   341       else
   342         [mNativeMenuItem setEnabled:YES];
   343     }
   344   }
   346   NS_OBJC_END_TRY_ABORT_BLOCK;
   347 }
   349 void nsMenuItemX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer)
   350 {
   351   if (aChild == mCommandContent) {
   352     mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
   353     mCommandContent = nullptr;
   354   }
   356   mMenuParent->SetRebuild(true);
   357 }
   359 void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
   360                                          nsIContent *aChild)
   361 {
   362   mMenuParent->SetRebuild(true);
   363 }
   365 void nsMenuItemX::SetupIcon()
   366 {
   367   if (mIcon)
   368     mIcon->SetupIcon();
   369 }

mercurial