/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is support for icons in native menu items on Mac OS X. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Mark Mentovai (Original Author) * Josh Aas * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Retrieves and displays icons in native menu items on Mac OS X. */ #include "nsMenuItemIconX.h" #include "nsObjCExceptions.h" #include "prmem.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsINameSpaceManager.h" #include "nsWidgetAtoms.h" #include "nsIDOMDocumentView.h" #include "nsIDOMViewCSS.h" #include "nsIDOMElement.h" #include "nsIDOMCSSStyleDeclaration.h" #include "nsIDOMCSSValue.h" #include "nsIDOMCSSPrimitiveValue.h" #include "nsThreadUtils.h" #include "nsToolkit.h" #include "nsNetUtil.h" #include "imgILoader.h" #include "imgIRequest.h" #include "nsMenuItemX.h" #include "gfxImageSurface.h" #include "imgIContainer.h" static const PRUint32 kIconWidth = 16; static const PRUint32 kIconHeight = 16; static const PRUint32 kIconBitsPerComponent = 8; static const PRUint32 kIconComponents = 4; static const PRUint32 kIconBitsPerPixel = kIconBitsPerComponent * kIconComponents; static const PRUint32 kIconBytesPerRow = kIconWidth * kIconBitsPerPixel / 8; static const PRUint32 kIconBytes = kIconBytesPerRow * kIconHeight; static void PRAllocCGFree(void* aInfo, const void* aData, size_t aSize) { free((void*)aData); } NS_IMPL_ISUPPORTS2(nsMenuItemIconX, imgIContainerObserver, imgIDecoderObserver) nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem, nsIContent* aContent, NSMenuItem* aNativeMenuItem) : mContent(aContent) , mMenuObject(aMenuItem) , mLoadedIcon(PR_FALSE) , mSetIcon(PR_FALSE) , mNativeMenuItem(aNativeMenuItem) { // printf("Creating icon for menu item %d, menu %d, native item is %d\n", aMenuItem, aMenu, aNativeMenuItem); } nsMenuItemIconX::~nsMenuItemIconX() { if (mIconRequest) mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); } // Called from mMenuObjectX's destructor, to prevent us from outliving it // (as might otherwise happen if calls to our imgIDecoderObserver methods // are still outstanding). mMenuObjectX owns our nNativeMenuItem. void nsMenuItemIconX::Destroy() { if (mIconRequest) { mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); mIconRequest = nsnull; } mMenuObject = nsnull; 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\n"); return NS_ERROR_FAILURE; } nsCOMPtr 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; } return LoadIcon(iconURI); 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(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; PRBool hasImageAttr = mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::image, imageURIString); nsresult rv; if (!hasImageAttr) { // If the content node has no "image" attribute, get the // "list-style-image" property from CSS. nsCOMPtr domDocumentView = do_QueryInterface(mContent->GetDocument()); if (!domDocumentView) return NS_ERROR_FAILURE; nsCOMPtr domAbstractView; rv = domDocumentView->GetDefaultView(getter_AddRefs(domAbstractView)); if (NS_FAILED(rv)) return rv; nsCOMPtr domViewCSS = do_QueryInterface(domAbstractView); if (!domViewCSS) return NS_ERROR_FAILURE; nsCOMPtr domElement = do_QueryInterface(mContent); if (!domElement) return NS_ERROR_FAILURE; nsCOMPtr cssStyleDecl; nsAutoString empty; rv = domViewCSS->GetComputedStyle(domElement, empty, getter_AddRefs(cssStyleDecl)); if (NS_FAILED(rv)) return rv; NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image"); nsCOMPtr cssValue; rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage, getter_AddRefs(cssValue)); if (NS_FAILED(rv)) return rv; nsCOMPtr primitiveValue = do_QueryInterface(cssValue); if (!primitiveValue) return NS_ERROR_FAILURE; PRUint16 primitiveType; rv = primitiveValue->GetPrimitiveType(&primitiveType); if (NS_FAILED(rv)) return rv; if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI) return NS_ERROR_FAILURE; rv = primitiveValue->GetStringValue(imageURIString); if (NS_FAILED(rv)) return rv; } // If this menu item shouldn't have an icon, the string will be empty, // and NS_NewURI will fail. nsCOMPtr iconURI; rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString); if (NS_FAILED(rv)) return rv; *aIconURI = iconURI; NS_ADDREF(*aIconURI); return NS_OK; } nsresult nsMenuItemIconX::LoadIcon(nsIURI* aIconURI) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; if (mIconRequest) { // Another icon request is already in flight. Kill it. mIconRequest->Cancel(NS_BINDING_ABORTED); mIconRequest = nsnull; } mLoadedIcon = PR_FALSE; if (!mContent) return NS_ERROR_FAILURE; nsCOMPtr document = mContent->GetOwnerDoc(); if (!document) return NS_ERROR_FAILURE; nsCOMPtr loadGroup = document->GetDocumentLoadGroup(); if (!loadGroup) return NS_ERROR_FAILURE; nsresult rv = NS_ERROR_FAILURE; nsCOMPtr loader = do_GetService("@mozilla.org/image/loader;1", &rv); if (NS_FAILED(rv)) return rv; if (!mSetIcon) { // Set a completely transparent 16x16 image as the icon on this menu item // as a placeholder. This keeps the menu item text displayed in the same // position that it will be displayed when the real icon is loaded, and // prevents it from jumping around or looking misaligned. static PRBool sInitializedPlaceholder; static NSImage* sPlaceholderIconImage; if (!sInitializedPlaceholder) { sInitializedPlaceholder = PR_TRUE; // Note that we only create the one and reuse it forever, so this is not a leak. sPlaceholderIconImage = [[NSImage alloc] initWithSize:NSMakeSize(kIconWidth, kIconHeight)]; } if (!sPlaceholderIconImage) return NS_ERROR_FAILURE; if (mNativeMenuItem) [mNativeMenuItem setImage:sPlaceholderIconImage]; } rv = loader->LoadImage(aIconURI, nsnull, nsnull, loadGroup, this, nsnull, nsIRequest::LOAD_NORMAL, nsnull, nsnull, getter_AddRefs(mIconRequest)); if (NS_FAILED(rv)) return rv; return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } // // imgIContainerObserver // NS_IMETHODIMP nsMenuItemIconX::FrameChanged(imgIContainer* aContainer, nsIntRect* aDirtyRect) { return NS_OK; } // // imgIDecoderObserver // NS_IMETHODIMP nsMenuItemIconX::OnStartRequest(imgIRequest* aRequest) { return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnStartDecode(imgIRequest* aRequest) { return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnStartContainer(imgIRequest* aRequest, imgIContainer* aContainer) { // Request a decode NS_ABORT_IF_FALSE(aContainer, "who sent the notification then?"); aContainer->RequestDecode(); return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnStartFrame(imgIRequest* aRequest, PRUint32 aFrame) { return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnDataAvailable(imgIRequest* aRequest, PRBool aCurrentFrame, const nsIntRect* aRect) { return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnStopFrame(imgIRequest* aRequest, PRUint32 aFrame) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; if (aRequest != mIconRequest) return NS_ERROR_FAILURE; // Only support one frame. if (mLoadedIcon) return NS_OK; if (!mNativeMenuItem) return NS_ERROR_FAILURE; nsCOMPtr imageContainer; aRequest->GetImage(getter_AddRefs(imageContainer)); if (!imageContainer) return NS_ERROR_FAILURE; nsRefPtr image; nsresult rv = imageContainer->CopyFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_NONE, getter_AddRefs(image)); if (NS_FAILED(rv) || !image) return NS_ERROR_FAILURE; PRInt32 height = image->Height(); PRInt32 stride = image->Stride(); PRInt32 width = image->Width(); PRUint32 imageLength = ((stride * height) / 4); if ((stride % 4 != 0) || (height < 1) || (width < 1)) return NS_ERROR_FAILURE; PRUint32* imageData = (PRUint32*)image->Data(); PRUint32* reorderedData = (PRUint32*)malloc(height * stride); if (!reorderedData) return NS_ERROR_OUT_OF_MEMORY; // We have to reorder data to have alpha last because only Tiger can handle // alpha being first. Also the data must always be big endian (silly). for (PRUint32 i = 0; i < imageLength; i++) { PRUint32 pixel = imageData[i]; reorderedData[i] = CFSwapInt32HostToBig((pixel << 8) | (pixel >> 24)); } CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL, reorderedData, imageLength, PRAllocCGFree); if (!provider) { free(reorderedData); return NS_ERROR_FAILURE; } CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB(); CGImageRef cgImage = ::CGImageCreate(width, height, 8, 32, stride, colorSpace, kCGImageAlphaPremultipliedLast, provider, NULL, true, kCGRenderingIntentDefault); ::CGDataProviderRelease(provider); // The image may not be the right size for a menu icon (16x16). // Create a new CGImage for the menu item. PRUint8* bitmap = (PRUint8*)malloc(kIconBytes); CGImageAlphaInfo alphaInfo = ::CGImageGetAlphaInfo(cgImage); CGContextRef bitmapContext; bitmapContext = ::CGBitmapContextCreate(bitmap, kIconWidth, kIconHeight, kIconBitsPerComponent, kIconBytesPerRow, colorSpace, alphaInfo); if (!bitmapContext) { ::CGImageRelease(cgImage); free(bitmap); ::CGColorSpaceRelease(colorSpace); return NS_ERROR_FAILURE; } CGRect iconRect = ::CGRectMake(0, 0, kIconWidth, kIconHeight); ::CGContextClearRect(bitmapContext, iconRect); ::CGContextDrawImage(bitmapContext, iconRect, cgImage); ::CGImageRelease(cgImage); ::CGContextRelease(bitmapContext); provider = ::CGDataProviderCreateWithData(NULL, bitmap, kIconBytes, PRAllocCGFree); if (!provider) { free(bitmap); ::CGColorSpaceRelease(colorSpace); return NS_ERROR_FAILURE; } CGImageRef iconImage = ::CGImageCreate(kIconWidth, kIconHeight, kIconBitsPerComponent, kIconBitsPerPixel, kIconBytesPerRow, colorSpace, alphaInfo, provider, NULL, TRUE, kCGRenderingIntentDefault); ::CGColorSpaceRelease(colorSpace); ::CGDataProviderRelease(provider); if (!iconImage) return NS_ERROR_FAILURE; NSRect imageRect = NSMakeRect(0.0, 0.0, 0.0, 0.0); CGContextRef imageContext = nil; // Get the image dimensions. imageRect.size.width = kIconWidth; imageRect.size.height = kIconHeight; // Create a new image to receive the Quartz image data. NSImage* newImage = [[NSImage alloc] initWithSize:imageRect.size]; [newImage lockFocus]; // Get the Quartz context and draw. imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; CGContextDrawImage(imageContext, *(CGRect*)&imageRect, iconImage); [newImage unlockFocus]; if (!mNativeMenuItem) return NS_ERROR_FAILURE; [mNativeMenuItem setImage:newImage]; [newImage release]; ::CGImageRelease(iconImage); mLoadedIcon = PR_TRUE; mSetIcon = PR_TRUE; return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsMenuItemIconX::OnStopContainer(imgIRequest* aRequest, imgIContainer* aContainer) { return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnStopDecode(imgIRequest* aRequest, nsresult status, const PRUnichar* statusArg) { return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnStopRequest(imgIRequest* aRequest, PRBool aIsLastPart) { NS_ASSERTION(mIconRequest, "NULL mIconRequest! Multiple calls to OnStopRequest()?"); if (mIconRequest) { mIconRequest->Cancel(NS_BINDING_ABORTED); mIconRequest = nsnull; } return NS_OK; } NS_IMETHODIMP nsMenuItemIconX::OnDiscard(imgIRequest* aRequest) { return NS_OK; }