Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  nsMenuItemX.mm   Sprache: unbekannt

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#include "nsMenuItemX.h"
#include "nsMenuBarX.h"
#include "nsMenuX.h"
#include "nsMenuItemIconX.h"
#include "nsMenuUtilsX.h"
#include "nsCocoaUtils.h"

#include "nsObjCExceptions.h"

#include "nsCOMPtr.h"
#include "nsGkAtoms.h"

#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/ErrorResult.h"
#include "nsIWidget.h"
#include "mozilla/dom/Document.h"

using namespace mozilla;

using mozilla::dom::CallerType;
using mozilla::dom::Event;

nsMenuItemX::nsMenuItemX(nsMenuX* aParent, const nsString& aLabel,
                         EMenuItemType aItemType,
                         nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
    : mContent(aNode),
      mType(aItemType),
      mMenuParent(aParent),
      mMenuGroupOwner(aMenuGroupOwner) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  MOZ_COUNT_CTOR(nsMenuItemX);

  MOZ_RELEASE_ASSERT(mContent->IsElement(),
                     "nsMenuItemX should only be created for elements");
  NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");

  mMenuGroupOwner->RegisterForContentChanges(mContent, this);

  dom::Document* doc = mContent->GetUncomposedDoc();

  // if we have a command associated with this menu item, register for changes
  // to the command DOM node
  if (doc) {
    nsAutoString ourCommand;
    mContent->AsElement()->GetAttr(nsGkAtoms::command, ourCommand);

    if (!ourCommand.IsEmpty()) {
      dom::Element* commandElement = doc->GetElementById(ourCommand);

      if (commandElement) {
        mCommandElement = commandElement;
        // register to observe the command DOM element
        mMenuGroupOwner->RegisterForContentChanges(mCommandElement, this);
      }
    }
  }

  // decide enabled state based on command content if it exists, otherwise do it
  // based on our own content
  bool isEnabled;
  if (mCommandElement) {
    isEnabled = !mCommandElement->AttrValueIs(
        kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
  } else {
    isEnabled = !mContent->AsElement()->AttrValueIs(
        kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
  }

  // set up the native menu item
  if (mType == eSeparatorMenuItemType) {
    mNativeMenuItem = [[NSMenuItem separatorItem] retain];
  } else {
    NSString* newCocoaLabelString =
        nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
    mNativeMenuItem = [[GeckoNSMenuItem alloc] initWithTitle:newCocoaLabelString
                                                      action:nil
                                               keyEquivalent:@""];

    mIsChecked = mContent->AsElement()->AttrValueIs(
        kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true, eCaseMatters);

    mNativeMenuItem.enabled = isEnabled;
    mNativeMenuItem.state =
        mIsChecked ? NSControlStateValueOn : NSControlStateValueOff;

    SetKeyEquiv();
  }

  mIcon = MakeUnique<nsMenuItemIconX>(this);

  mIsVisible = !nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);

  // All menu items other than the "Copy" menu item share the same target and
  // action, and are differentiated be a unique (representedObject, tag) pair.
  // The "Copy" menu item is a special case that requires a macOS-default
  // action of `copy:` and a default target in order for the "Edit" menu to be
  // populated with OS-provided menu items such as the Emoji picker,
  // especially in multi-language environments (see bug 1478347). Our
  // application delegate implements `copy:` by simply forwarding it to
  // [nsMenuBarX::sNativeEventTarget menuItemHit:].
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
                                         u"menu_copy"_ns, eCaseMatters)) {
    mNativeMenuItem.action = @selector(copy:);
  } else {
    mNativeMenuItem.action = @selector(menuItemHit:);
    mNativeMenuItem.target = nsMenuBarX::sNativeEventTarget;
  }

  mNativeMenuItem.representedObject = mMenuGroupOwner->GetRepresentedObject();
  mNativeMenuItem.tag = mMenuGroupOwner->RegisterForCommand(this);

  if (mIsVisible) {
    SetupIcon();
  }

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

nsMenuItemX::~nsMenuItemX() {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  // autorelease the native menu item so that anything else happening to this
  // object happens before the native menu item actually dies
  [mNativeMenuItem autorelease];

  DetachFromGroupOwner();

  MOZ_COUNT_DTOR(nsMenuItemX);

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

void nsMenuItemX::DetachFromGroupOwner() {
  if (mMenuGroupOwner) {
    mMenuGroupOwner->UnregisterCommand(mNativeMenuItem.tag);

    if (mContent) {
      mMenuGroupOwner->UnregisterForContentChanges(mContent);
    }
    if (mCommandElement) {
      mMenuGroupOwner->UnregisterForContentChanges(mCommandElement);
    }
  }

  mMenuGroupOwner = nullptr;
}

nsresult nsMenuItemX::SetChecked(bool aIsChecked) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  mIsChecked = aIsChecked;

  // update the content model. This will also handle unchecking our siblings
  // if we are a radiomenu
  if (mIsChecked) {
    mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
                                   u"true"_ns, true);
  } else {
    mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
                                     true);
  }

  // update native menu item
  mNativeMenuItem.state =
      mIsChecked ? NSControlStateValueOn : NSControlStateValueOff;

  return NS_OK;

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

EMenuItemType nsMenuItemX::GetMenuItemType() { return mType; }

// Executes the "cached" javaScript command.
// Returns NS_OK if the command was executed properly, otherwise an error code.
void nsMenuItemX::DoCommand(NSEventModifierFlags aModifierFlags,
                            int16_t aButton) {
  // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
  if (mType == eCheckboxMenuItemType ||
      (mType == eRadioMenuItemType && !mIsChecked)) {
    if (!mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
                                            nsGkAtoms::autocheck,
                                            nsGkAtoms::_false, eCaseMatters)) {
      SetChecked(!mIsChecked);
    }
    /* the AttributeChanged code will update all the internal state */
  }

  nsMenuUtilsX::DispatchCommandTo(mContent, aModifierFlags, aButton);
}

nsresult nsMenuItemX::DispatchDOMEvent(const nsString& eventName,
                                       bool* preventDefaultCalled) {
  if (!mContent) {
    return NS_ERROR_FAILURE;
  }

  // get owner document for content
  nsCOMPtr<dom::Document> parentDoc = mContent->OwnerDoc();

  // create DOM event
  ErrorResult rv;
  RefPtr<Event> event =
      parentDoc->CreateEvent(u"Events"_ns, CallerType::System, rv);
  if (rv.Failed()) {
    NS_WARNING("Failed to create Event");
    return rv.StealNSResult();
  }
  event->InitEvent(eventName, true, true);

  // mark DOM event as trusted
  event->SetTrusted(true);

  // send DOM event
  *preventDefaultCalled =
      mContent->DispatchEvent(*event, CallerType::System, rv);
  if (rv.Failed()) {
    NS_WARNING("Failed to send DOM event via EventTarget");
    return rv.StealNSResult();
  }

  return NS_OK;
}

// Walk the sibling list looking for nodes with the same name and
// uncheck them all.
void nsMenuItemX::UncheckRadioSiblings(nsIContent* aCheckedContent) {
  nsAutoString myGroupName;
  aCheckedContent->AsElement()->GetAttr(nsGkAtoms::name, myGroupName);
  if (!myGroupName.Length()) {  // no groupname, nothing to do
    return;
  }

  nsCOMPtr<nsIContent> parent = aCheckedContent->GetParent();
  if (!parent) {
    return;
  }

  // loop over siblings
  for (nsIContent* sibling = parent->GetFirstChild(); sibling;
       sibling = sibling->GetNextSibling()) {
    if (sibling != aCheckedContent && sibling->IsElement()) {  // skip this node
      // if the current sibling is in the same group, clear it
      if (sibling->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
                                            myGroupName, eCaseMatters)) {
        sibling->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
                                      u"false"_ns, true);
      }
    }
  }
}

void nsMenuItemX::SetKeyEquiv() {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  // Set key shortcut and modifiers
  nsAutoString keyValue;
  mContent->AsElement()->GetAttr(nsGkAtoms::key, keyValue);

  if (!keyValue.IsEmpty() && mContent->GetUncomposedDoc()) {
    dom::Element* keyContent =
        mContent->GetUncomposedDoc()->GetElementById(keyValue);
    if (keyContent) {
      nsAutoString keyChar;
      bool hasKey = keyContent->GetAttr(nsGkAtoms::key, keyChar);

      if (!hasKey || keyChar.IsEmpty()) {
        nsAutoString keyCodeName;
        keyContent->GetAttr(nsGkAtoms::keycode, keyCodeName);
        uint32_t charCode =
            nsCocoaUtils::ConvertGeckoNameToMacCharCode(keyCodeName);
        if (charCode) {
          keyChar.Assign(charCode);
        } else {
          keyChar.AssignLiteral(u" ");
        }
      }

      nsAutoString modifiersStr;
      keyContent->GetAttr(nsGkAtoms::modifiers, modifiersStr);
      uint8_t modifiers =
          nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);

      unsigned int macModifiers =
          nsMenuUtilsX::MacModifiersForGeckoModifiers(modifiers);
      mNativeMenuItem.keyEquivalentModifierMask = macModifiers;

      NSString* keyEquivalent =
          [[NSString stringWithCharacters:(unichar*)keyChar.get()
                                   length:keyChar.Length()] lowercaseString];
      if ([keyEquivalent isEqualToString:@" "]) {
        mNativeMenuItem.keyEquivalent = @"";
      } else {
        mNativeMenuItem.keyEquivalent = keyEquivalent;
      }

      return;
    }
  }

  // if the key was removed, clear the key
  mNativeMenuItem.keyEquivalent = @"";

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

void nsMenuItemX::Dump(uint32_t aIndent) const {
  printf("%*s - item [%p] %-16s <%s>\n", aIndent * 2, "", this,
         mType == eSeparatorMenuItemType ? "----"
                                         : [mNativeMenuItem.title UTF8String],
         NS_ConvertUTF16toUTF8(mContent->NodeName()).get());
}

//
// nsChangeObserver
//

void nsMenuItemX::ObserveAttributeChanged(dom::Document* aDocument,
                                          nsIContent* aContent,
                                          nsAtom* aAttribute) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  if (!aContent) {
    return;
  }

  if (aContent == mContent) {  // our own content node changed
    if (aAttribute == nsGkAtoms::checked) {
      // if we're a radio menu, uncheck our sibling radio items. No need to
      // do any of this if we're just a normal check menu.
      if (mType == eRadioMenuItemType &&
          mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
                                             nsGkAtoms::checked,
                                             nsGkAtoms::_true, eCaseMatters)) {
        UncheckRadioSiblings(mContent);
      }
      mMenuParent->SetRebuild(true);
    } else if (aAttribute == nsGkAtoms::hidden ||
               aAttribute == nsGkAtoms::collapsed) {
      bool isVisible = !nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
      if (isVisible != mIsVisible) {
        mIsVisible = isVisible;
        RefPtr<nsMenuItemX> self = this;
        mMenuParent->MenuChildChangedVisibility(nsMenuParentX::MenuChild(self),
                                                isVisible);
        if (mIsVisible) {
          SetupIcon();
        }
      }
      mMenuParent->SetRebuild(true);
    } else if (aAttribute == nsGkAtoms::label) {
      if (mType != eSeparatorMenuItemType) {
        nsAutoString newLabel;
        mContent->AsElement()->GetAttr(nsGkAtoms::label, newLabel);
        mNativeMenuItem.title = nsMenuUtilsX::GetTruncatedCocoaLabel(newLabel);
      }
    } else if (aAttribute == nsGkAtoms::key) {
      SetKeyEquiv();
    } else if (aAttribute == nsGkAtoms::image) {
      SetupIcon();
    } else if (aAttribute == nsGkAtoms::disabled) {
      mNativeMenuItem.enabled = !aContent->AsElement()->AttrValueIs(
          kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true,
          eCaseMatters);
    }
  } else if (aContent == mCommandElement) {
    // the only thing that really matters when the menu isn't showing is the
    // enabled state since it enables/disables keyboard commands
    if (aAttribute == nsGkAtoms::disabled) {
      // first we sync our menu item DOM node with the command DOM node
      nsAutoString commandDisabled;
      nsAutoString menuDisabled;
      aContent->AsElement()->GetAttr(nsGkAtoms::disabled, commandDisabled);
      mContent->AsElement()->GetAttr(nsGkAtoms::disabled, menuDisabled);
      if (!commandDisabled.Equals(menuDisabled)) {
        // The menu's disabled state needs to be updated to match the command.
        if (commandDisabled.IsEmpty()) {
          mContent->AsElement()->UnsetAttr(kNameSpaceID_None,
                                           nsGkAtoms::disabled, true);
        } else {
          mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
                                         commandDisabled, true);
        }
      }
      // now we sync our native menu item with the command DOM node
      mNativeMenuItem.enabled = !aContent->AsElement()->AttrValueIs(
          kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true,
          eCaseMatters);
    }
  }

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

bool IsMenuStructureElement(nsIContent* aContent) {
  return aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem,
                                      nsGkAtoms::menuseparator);
}

void nsMenuItemX::ObserveContentRemoved(dom::Document* aDocument,
                                        nsIContent* aContainer,
                                        nsIContent* aChild) {
  MOZ_RELEASE_ASSERT(mMenuGroupOwner);
  MOZ_RELEASE_ASSERT(mMenuParent);

  if (aChild == mCommandElement) {
    mMenuGroupOwner->UnregisterForContentChanges(mCommandElement);
    mCommandElement = nullptr;
  }
  if (IsMenuStructureElement(aChild)) {
    mMenuParent->SetRebuild(true);
  }
}

void nsMenuItemX::ObserveContentInserted(dom::Document* aDocument,
                                         nsIContent* aContainer,
                                         nsIContent* aChild) {
  MOZ_RELEASE_ASSERT(mMenuParent);

  // The child node could come from the custom element that is for display, so
  // only rebuild the menu if the child is related to the structure of the
  // menu.
  if (IsMenuStructureElement(aChild)) {
    mMenuParent->SetRebuild(true);
  }
}

void nsMenuItemX::SetupIcon() {
  if (mType != eRegularMenuItemType) {
    // Don't support icons on checkbox and radio menuitems, for consistency with
    // Windows & Linux.
    return;
  }

  mIcon->SetupIcon(mContent);
  mNativeMenuItem.image = mIcon->GetIconImage();
}

void nsMenuItemX::IconUpdated() {
  mNativeMenuItem.image = mIcon->GetIconImage();
}

[ Dauer der Verarbeitung: 0.18 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge