diff options
Diffstat (limited to 'widget/cocoa/nsMenuItemIconX.mm')
-rw-r--r-- | widget/cocoa/nsMenuItemIconX.mm | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/widget/cocoa/nsMenuItemIconX.mm b/widget/cocoa/nsMenuItemIconX.mm new file mode 100644 index 0000000000..20d9d895d7 --- /dev/null +++ b/widget/cocoa/nsMenuItemIconX.mm @@ -0,0 +1,228 @@ +/* -*- 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/. */ + +/* + * Retrieves and displays icons in native menu items on Mac OS X. + */ + +/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c + exceptions and produces errors like: error: unexpected '@' in program'. + If we define __EXCEPTIONS exception_defines.h will avoid doing this. + + See bug 666609 for more information. + + We use <limits> to get the libstdc++ version. */ +#include <limits> +#if __GLIBCXX__ <= 20070719 +# ifndef __EXCEPTIONS +# define __EXCEPTIONS +# endif +#endif + +#include "mozilla/dom/Document.h" +#include "nsCocoaUtils.h" +#include "nsComputedDOMStyle.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIContentPolicy.h" +#include "nsMenuItemX.h" +#include "nsMenuItemIconX.h" +#include "nsNameSpaceManager.h" +#include "nsObjCExceptions.h" + +using namespace mozilla; + +using mozilla::dom::Element; +using mozilla::widget::IconLoader; +using mozilla::widget::IconLoaderHelperCocoa; + +static const uint32_t kIconSize = 16; + +nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem, nsIContent* aContent, + NSMenuItem* aNativeMenuItem) + : mContent(aContent), + mContentType(nsIContentPolicy::TYPE_INTERNAL_IMAGE), + mMenuObject(aMenuItem), + mSetIcon(false), + mNativeMenuItem(aNativeMenuItem) { + MOZ_COUNT_CTOR(nsMenuItemIconX); +} + +nsMenuItemIconX::~nsMenuItemIconX() { + Destroy(); + MOZ_COUNT_DTOR(nsMenuItemIconX); +} + +// Called from mMenuObjectX's destructor, to prevent us from outliving it +// (as might otherwise happen if calls to our imgINotificationObserver methods +// are still outstanding). mMenuObjectX owns our mNativeMenuItem. +void nsMenuItemIconX::Destroy() { + if (mIconLoader) { + mIconLoader = nullptr; + } + if (mIconLoaderHelper) { + mIconLoaderHelper = nullptr; + } + mMenuObject = nullptr; + mNativeMenuItem = nil; +} + +nsresult nsMenuItemIconX::SetupIcon() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Still don't have one, then something is wrong, get out of here. + if (!mNativeMenuItem) { + NS_ERROR("No native menu item"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> iconURI; + nsresult rv = GetIconURI(getter_AddRefs(iconURI)); + if (NS_FAILED(rv)) { + // There is no icon for this menu item. An icon might have been set + // earlier. Clear it. + [mNativeMenuItem setImage:nil]; + + return NS_OK; + } + + if (!mIconLoader) { + mIconLoaderHelper = new IconLoaderHelperCocoa(this, kIconSize, kIconSize); + mIconLoader = new IconLoader(mIconLoaderHelper, mContent, mImageRegionRect); + if (!mIconLoader) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (!mSetIcon) { + // Load placeholder icon. + [mNativeMenuItem setImage:mIconLoaderHelper->GetNativeIconImage()]; + } + + rv = mIconLoader->LoadIcon(iconURI); + if (NS_FAILED(rv)) { + // There is no icon for this menu item, as an error occurred while loading it. + // An icon might have been set earlier or the place holder icon may have + // been set. Clear it. + [mNativeMenuItem setImage:nil]; + } + + mSetIcon = true; + + return rv; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult nsMenuItemIconX::GetIconURI(nsIURI** aIconURI) { + if (!mMenuObject) return NS_ERROR_FAILURE; + + // Mac native menu items support having both a checkmark and an icon + // simultaneously, but this is unheard of in the cross-platform toolkit, + // seemingly because the win32 theme is unable to cope with both at once. + // The downside is that it's possible to get a menu item marked with a + // native checkmark and a checkmark for an icon. Head off that possibility + // by pretending that no icon exists if this is a checkable menu item. + if (mMenuObject->MenuObjectType() == eMenuItemObjectType) { + nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject); + if (menuItem->GetMenuItemType() != eRegularMenuItemType) return NS_ERROR_FAILURE; + } + + if (!mContent) return NS_ERROR_FAILURE; + + // First, look at the content node's "image" attribute. + nsAutoString imageURIString; + bool hasImageAttr = + mContent->IsElement() && + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString); + + nsresult rv; + RefPtr<ComputedStyle> sc; + nsCOMPtr<nsIURI> iconURI; + if (!hasImageAttr) { + // If the content node has no "image" attribute, get the + // "list-style-image" property from CSS. + RefPtr<mozilla::dom::Document> document = mContent->GetComposedDoc(); + if (!document || !mContent->IsElement()) { + return NS_ERROR_FAILURE; + } + + sc = nsComputedDOMStyle::GetComputedStyle(mContent->AsElement(), nullptr); + if (!sc) { + return NS_ERROR_FAILURE; + } + + iconURI = sc->StyleList()->GetListStyleImageURI(); + if (!iconURI) { + return NS_ERROR_FAILURE; + } + } else { + uint64_t dummy = 0; + nsCOMPtr<nsIPrincipal> triggeringPrincipal = mContent->NodePrincipal(); + nsContentUtils::GetContentPolicyTypeForUIImageLoading( + mContent, getter_AddRefs(triggeringPrincipal), mContentType, &dummy); + + // If this menu item shouldn't have an icon, the string will be empty, + // and NS_NewURI will fail. + rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString); + if (NS_FAILED(rv)) return rv; + } + + // Empty the mImageRegionRect initially as the image region CSS could + // have been changed and now have an error or have been removed since the + // last GetIconURI call. + mImageRegionRect.SetEmpty(); + + iconURI.forget(aIconURI); + + if (!hasImageAttr) { + // Check if the icon has a specified image region so that it can be + // cropped appropriately before being displayed. + const nsRect r = sc->StyleList()->GetImageRegion(); + + // Return NS_ERROR_FAILURE if the image region is invalid so the image + // is not drawn, and behavior is similar to XUL menus. + if (r.X() < 0 || r.Y() < 0 || r.Width() < 0 || r.Height() < 0) { + return NS_ERROR_FAILURE; + } + + // 'auto' is represented by a [0, 0, 0, 0] rect. Only set mImageRegionRect + // if we have some other value. + if (!r.IsEmpty()) { + mImageRegionRect = r.ToNearestPixels(mozilla::AppUnitsPerCSSPixel()); + } + } + + return NS_OK; +} + +// +// mozilla::widget::IconLoaderListenerCocoa +// + +nsresult nsMenuItemIconX::OnComplete() { + if (!mIconLoaderHelper) { + return NS_ERROR_FAILURE; + } + + NSImage* image = mIconLoaderHelper->GetNativeIconImage(); + if (!mNativeMenuItem) { + mIconLoaderHelper->Destroy(); + return NS_ERROR_FAILURE; + } + + if (!image) { + [mNativeMenuItem setImage:nil]; + return NS_OK; + } + + [mNativeMenuItem setImage:image]; + if (mMenuObject) { + mMenuObject->IconUpdated(); + } + + mIconLoaderHelper->Destroy(); + return NS_OK; +} |