Back to index

lightning-sunbird  0.9+nobinonly
nsMenuItemIcon.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is support for icons in native menu items on Mac OS X.
00016  *
00017  * The Initial Developer of the Original Code is Google Inc.
00018  * Portions created by the Initial Developer are Copyright (C) 2006
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *  Mark Mentovai <mark@moxienet.com> (Original Author)
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 /*
00039  * Retrieves and displays icons in native menu items on Mac OS X.
00040  */
00041 
00042 
00043 #include "nsMenuItemIcon.h"
00044 
00045 #include "prmem.h"
00046 #include "nsIMenu.h"
00047 #include "nsIMenuItem.h"
00048 #include "nsIContent.h"
00049 #include "nsIDocument.h"
00050 #include "nsINameSpaceManager.h"
00051 #include "nsWidgetAtoms.h"
00052 #include "nsIDOMDocumentView.h"
00053 #include "nsIDOMViewCSS.h"
00054 #include "nsIDOMElement.h"
00055 #include "nsIDOMCSSStyleDeclaration.h"
00056 #include "nsIDOMCSSValue.h"
00057 #include "nsIDOMCSSPrimitiveValue.h"
00058 #include "nsIEventQueueService.h"
00059 #include "nsToolkit.h"
00060 #include "nsNetUtil.h"
00061 #include "imgILoader.h"
00062 #include "imgIRequest.h"
00063 #include "gfxIImageFrame.h"
00064 #include "nsIImage.h"
00065 #include "nsIImageMac.h"
00066 
00067 #include <Carbon/Carbon.h>
00068 
00069 
00070 static const PRUint32 kIconWidth = 16;
00071 static const PRUint32 kIconHeight = 16;
00072 static const PRUint32 kIconBitsPerComponent = 8;
00073 static const PRUint32 kIconComponents = 4;
00074 static const PRUint32 kIconBitsPerPixel = kIconBitsPerComponent *
00075                                           kIconComponents;
00076 static const PRUint32 kIconBytesPerRow = kIconWidth * kIconBitsPerPixel / 8;
00077 static const PRUint32 kIconBytes = kIconBytesPerRow * kIconHeight;
00078 
00079 
00080 static void
00081 PRAllocCGFree(void* aInfo, const void* aData, size_t aSize) {
00082   PR_Free((void*)aData);
00083 }
00084 
00085 
00086 NS_IMPL_ISUPPORTS3(nsMenuItemIcon, imgIContainerObserver, imgIDecoderObserver,
00087                    imgIDecoderObserver_MOZILLA_1_8_BRANCH)
00088 
00089 nsMenuItemIcon::nsMenuItemIcon(nsISupports*                aMenuItem,
00090                                nsIMenu_MOZILLA_1_8_BRANCH* aMenu,
00091                                nsIContent*                 aContent)
00092 : mContent(aContent)
00093 , mMenuItem(aMenuItem)
00094 , mMenu(aMenu)
00095 , mMenuRef(NULL)
00096 , mMenuItemIndex(0)
00097 , mLoadedIcon(PR_FALSE)
00098 , mSetIcon(PR_FALSE)
00099 {
00100 }
00101 
00102 
00103 nsMenuItemIcon::~nsMenuItemIcon()
00104 {
00105   if (mIconRequest)
00106     mIconRequest->Cancel(NS_BINDING_ABORTED);
00107 }
00108 
00109 
00110 nsresult
00111 nsMenuItemIcon::SetupIcon()
00112 {
00113   nsresult rv;
00114   if (!mMenuRef || !mMenuItemIndex) {
00115     // These values are initialized here instead of in the constructor
00116     // because they depend on the parent menu, mMenu, having inserted
00117     // this object into its array of children.  That can only happen after
00118     // the object is constructed.
00119     rv = mMenu->GetMenuRefAndItemIndexForMenuItem(mMenuItem,
00120                                                   (void**)&mMenuRef,
00121                                                   &mMenuItemIndex);
00122     if (NS_FAILED(rv)) return rv;
00123   }
00124 
00125   nsCOMPtr<nsIURI> iconURI;
00126   rv = GetIconURI(getter_AddRefs(iconURI));
00127   if (NS_FAILED(rv)) {
00128     // There is no icon for this menu item.  An icon might have been set
00129     // earlier.  Clear it.
00130     OSStatus err;
00131     err = ::SetMenuItemIconHandle(mMenuRef, mMenuItemIndex, kMenuNoIcon, NULL);
00132     if (err != noErr) return NS_ERROR_FAILURE;
00133 
00134     return NS_OK;
00135   }
00136 
00137   rv = LoadIcon(iconURI);
00138 
00139   return rv;
00140 }
00141 
00142 
00143 nsresult
00144 nsMenuItemIcon::GetIconURI(nsIURI** aIconURI)
00145 {
00146   // Mac native menu items support having both a checkmark and an icon
00147   // simultaneously, but this is unheard of in the cross-platform toolkit,
00148   // seemingly because the win32 theme is unable to cope with both at once.
00149   // The downside is that it's possible to get a menu item marked with a
00150   // native checkmark and a checkmark for an icon.  Head off that possibility
00151   // by pretending that no icon exists if this is a checkable menu item.
00152   nsCOMPtr<nsIMenuItem> menuItem = do_QueryInterface(mMenuItem);
00153   if (menuItem) {
00154     nsIMenuItem::EMenuItemType menuItemType;
00155     menuItem->GetMenuItemType(&menuItemType);
00156     if (menuItemType == nsIMenuItem::eCheckbox ||
00157         menuItemType == nsIMenuItem::eRadio)
00158       return NS_ERROR_FAILURE;
00159   }
00160 
00161   if (!mContent) return NS_ERROR_FAILURE;
00162 
00163   // First, look at the content node's "image" attribute.
00164   nsAutoString imageURIString;
00165   nsresult rv = mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::image,
00166                                   imageURIString);
00167 
00168   if (rv != NS_CONTENT_ATTR_HAS_VALUE) {
00169     // If the content node has no "image" attribute, get the
00170     // "list-style-image" property from CSS.
00171     nsCOMPtr<nsIDOMDocumentView> domDocumentView =
00172      do_QueryInterface(mContent->GetDocument());
00173     if (!domDocumentView) return NS_ERROR_FAILURE;
00174 
00175     nsCOMPtr<nsIDOMAbstractView> domAbstractView;
00176     rv = domDocumentView->GetDefaultView(getter_AddRefs(domAbstractView));
00177     if (NS_FAILED(rv)) return rv;
00178 
00179     nsCOMPtr<nsIDOMViewCSS> domViewCSS = do_QueryInterface(domAbstractView);
00180     if (!domViewCSS) return NS_ERROR_FAILURE;
00181 
00182     nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(mContent);
00183     if (!domElement) return NS_ERROR_FAILURE;
00184 
00185     nsCOMPtr<nsIDOMCSSStyleDeclaration> cssStyleDecl;
00186     nsAutoString empty;
00187     rv = domViewCSS->GetComputedStyle(domElement, empty,
00188                                       getter_AddRefs(cssStyleDecl));
00189     if (NS_FAILED(rv)) return rv;
00190 
00191     NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image");
00192     nsCOMPtr<nsIDOMCSSValue> cssValue;
00193     rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage,
00194                                            getter_AddRefs(cssValue));
00195     if (NS_FAILED(rv)) return rv;
00196 
00197     nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue =
00198      do_QueryInterface(cssValue);
00199     if (!primitiveValue) return NS_ERROR_FAILURE;
00200 
00201     PRUint16 primitiveType;
00202     rv = primitiveValue->GetPrimitiveType(&primitiveType);
00203     if (NS_FAILED(rv)) return rv;
00204     if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI)
00205       return NS_ERROR_FAILURE;
00206 
00207     rv = primitiveValue->GetStringValue(imageURIString);
00208     if (NS_FAILED(rv)) return rv;
00209   }
00210 
00211   // If this menu item shouldn't have an icon, the string will be empty,
00212   // and NS_NewURI will fail.
00213   nsCOMPtr<nsIURI> iconURI;
00214   rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
00215   if (NS_FAILED(rv)) return rv;
00216 
00217   *aIconURI = iconURI;
00218   NS_ADDREF(*aIconURI);
00219   return NS_OK;
00220 }
00221 
00222 
00223 nsresult
00224 nsMenuItemIcon::LoadIcon(nsIURI* aIconURI)
00225 {
00226   if (mIconRequest) {
00227     // Another icon request is already in flight.  Kill it.
00228     mIconRequest->Cancel(NS_BINDING_ABORTED);
00229     mIconRequest = nsnull;
00230   }
00231 
00232   mLoadedIcon = PR_FALSE;
00233 
00234   if (!mContent) return NS_ERROR_FAILURE;
00235 
00236   nsCOMPtr<nsIDocument> document = mContent->GetOwnerDoc();
00237   if (!document) return NS_ERROR_FAILURE;
00238 
00239   nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
00240   if (!loadGroup) return NS_ERROR_FAILURE;
00241 
00242   nsresult rv = NS_ERROR_FAILURE;
00243   nsCOMPtr<imgILoader> loader = do_GetService("@mozilla.org/image/loader;1",
00244                                               &rv);
00245   if (NS_FAILED(rv)) return rv;
00246 
00247   if (!mSetIcon) {
00248     // Set a completely transparent 16x16 image as the icon on this menu item
00249     // as a placeholder.  This keeps the menu item text displayed in the same
00250     // position that it will be displayed when the real icon is loaded, and
00251     // prevents it from jumping around or looking misaligned.
00252 
00253     static PRBool sInitializedPlaceholder;
00254     static CGImageRef sPlaceholderIconImage;
00255     if (!sInitializedPlaceholder) {
00256       sInitializedPlaceholder = PR_TRUE;
00257 
00258       PRUint8* bitmap = (PRUint8*)PR_Malloc(kIconBytes);
00259 
00260       CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB();
00261 
00262       CGContextRef bitmapContext;
00263       bitmapContext = ::CGBitmapContextCreate(bitmap, kIconWidth, kIconHeight,
00264                                               kIconBitsPerComponent,
00265                                               kIconBytesPerRow,
00266                                               colorSpace,
00267                                               kCGImageAlphaPremultipliedFirst);
00268       if (!bitmapContext) {
00269         PR_Free(bitmap);
00270         ::CGColorSpaceRelease(colorSpace);
00271         return NS_ERROR_FAILURE;
00272       }
00273 
00274       CGRect iconRect = ::CGRectMake(0, 0, kIconWidth, kIconHeight);
00275       ::CGContextClearRect(bitmapContext, iconRect);
00276       ::CGContextRelease(bitmapContext);
00277 
00278       CGDataProviderRef provider;
00279       provider = ::CGDataProviderCreateWithData(NULL, bitmap, kIconBytes,
00280                                               PRAllocCGFree);
00281       if (!provider) {
00282         PR_Free(bitmap);
00283         ::CGColorSpaceRelease(colorSpace);
00284         return NS_ERROR_FAILURE;
00285       }
00286 
00287       sPlaceholderIconImage =
00288        ::CGImageCreate(kIconWidth, kIconHeight, kIconBitsPerComponent,
00289                        kIconBitsPerPixel, kIconBytesPerRow, colorSpace,
00290                        kCGImageAlphaPremultipliedFirst, provider, NULL, TRUE,
00291                        kCGRenderingIntentDefault);
00292       ::CGColorSpaceRelease(colorSpace);
00293       ::CGDataProviderRelease(provider);
00294     }
00295 
00296     if (!sPlaceholderIconImage) return NS_ERROR_FAILURE;
00297 
00298     OSStatus err;
00299     err = ::SetMenuItemIconHandle(mMenuRef, mMenuItemIndex, kMenuCGImageRefType,
00300                                   (Handle)sPlaceholderIconImage);
00301     if (err != noErr) return NS_ERROR_FAILURE;
00302   }
00303 
00304   rv = loader->LoadImage(aIconURI, nsnull, nsnull, loadGroup, this,
00305                          nsnull, nsIRequest::LOAD_NORMAL, nsnull,
00306                          nsnull, getter_AddRefs(mIconRequest));
00307   if (NS_FAILED(rv)) return rv;
00308 
00309   // The icon will be picked up in OnStopFrame, which may be called after
00310   // LoadImage returns.  If the load is to be synchronous, ensure that
00311   // it completes now.
00312 
00313   if (ShouldLoadSync(aIconURI)) {
00314     // If there are any failures at this point, just return NS_OK and let
00315     // the image load asynchronously to completion.
00316 
00317     nsCOMPtr<nsIEventQueueService> eventQueueService = 
00318      do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00319     if (NS_FAILED(rv)) return NS_OK;
00320 
00321     nsCOMPtr<nsIEventQueue> eventQueue;
00322     rv = eventQueueService->GetSpecialEventQueue(
00323      nsIEventQueueService::CURRENT_THREAD_EVENT_QUEUE,
00324      getter_AddRefs(eventQueue));
00325     if (NS_FAILED(rv)) return NS_OK;
00326 
00327     PLEvent* event;
00328     rv = NS_OK;
00329     while (!mLoadedIcon && mIconRequest && NS_SUCCEEDED(rv)) {
00330       rv = eventQueue->WaitForEvent(&event);
00331       if (NS_SUCCEEDED(rv))
00332         rv = eventQueue->HandleEvent(event);
00333     }
00334   }
00335 
00336   return NS_OK;
00337 }
00338 
00339 
00340 PRBool
00341 nsMenuItemIcon::ShouldLoadSync(nsIURI* aURI)
00342 {
00343   // Older menu managers are unable to cope with menu item icons changing
00344   // while a menu is open in tracking.  On Panther (10.3), the updated icon
00345   // will not be displayed and highlighting of menu items in the affected
00346   // menu will be incorrect until menu tracking ends and the menu is
00347   // reopened.  On Jaguar (10.2), the updated icon will not be displayed
00348   // until the menu item is selected or deselected.  Tiger (10.4) does
00349   // not have these problems.
00350   //
00351   // Because icons are set in an imgIDecoderObserver notification, it's
00352   // possible and even likely that some icons will not be set until after the
00353   // menu is open.  On systems where this is known to cause trouble,
00354   // LoadIcon is made to set the icon on the menu item synchronously when
00355   // the source of the icon is local, as determined by the URI scheme.
00356 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
00357   return PR_FALSE;
00358 #else
00359   static PRBool sNeedsSync;
00360 
00361   static PRBool sInitialized;
00362   if (!sInitialized) {
00363     sInitialized = PR_TRUE;
00364     sNeedsSync = (nsToolkit::OSXVersion() < MAC_OS_X_VERSION_10_4_HEX);
00365   }
00366 
00367   if (sNeedsSync) {
00368     PRBool isLocalScheme;
00369     if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isLocalScheme)) &&
00370         isLocalScheme)
00371       return PR_TRUE;
00372     if (NS_SUCCEEDED(aURI->SchemeIs("data", &isLocalScheme)) &&
00373         isLocalScheme)
00374       return PR_TRUE;
00375     if (NS_SUCCEEDED(aURI->SchemeIs("moz-anno", &isLocalScheme)) &&
00376         isLocalScheme)
00377       return PR_TRUE;
00378   }
00379 
00380   return PR_FALSE;
00381 #endif
00382 }
00383 
00384 
00385 // imgIContainerObserver
00386 
00387 
00388 NS_IMETHODIMP
00389 nsMenuItemIcon::FrameChanged(imgIContainer*  aContainer,
00390                              gfxIImageFrame* aFrame,
00391                              nsIntRect*      aDirtyRect)
00392 {
00393   return NS_OK;
00394 }
00395 
00396 
00397 // imgIDecoderObserver
00398 
00399 
00400 NS_IMETHODIMP
00401 nsMenuItemIcon::OnStartRequest(imgIRequest* aRequest)
00402 {
00403   return NS_OK;
00404 }
00405 
00406 
00407 NS_IMETHODIMP
00408 nsMenuItemIcon::OnStartDecode(imgIRequest* aRequest)
00409 {
00410   return NS_OK;
00411 }
00412 
00413 
00414 NS_IMETHODIMP
00415 nsMenuItemIcon::OnStartContainer(imgIRequest*   aRequest,
00416                                  imgIContainer* aContainer)
00417 {
00418   return NS_OK;
00419 }
00420 
00421 
00422 NS_IMETHODIMP
00423 nsMenuItemIcon::OnStartFrame(imgIRequest* aRequest, gfxIImageFrame* aFrame)
00424 {
00425   return NS_OK;
00426 }
00427 
00428 
00429 NS_IMETHODIMP
00430 nsMenuItemIcon::OnDataAvailable(imgIRequest*     aRequest,
00431                                 gfxIImageFrame*  aFrame,
00432                                 const nsIntRect* aRect)
00433 {
00434   return NS_OK;
00435 }
00436 
00437 
00438 NS_IMETHODIMP
00439 nsMenuItemIcon::OnStopFrame(imgIRequest*    aRequest,
00440                             gfxIImageFrame* aFrame)
00441 {
00442   if (aRequest != mIconRequest) return NS_ERROR_FAILURE;
00443 
00444   // Only support one frame.
00445   if (mLoadedIcon)
00446     return NS_OK;
00447 
00448   nsCOMPtr<gfxIImageFrame> frame = aFrame;
00449   nsCOMPtr<nsIImage> iimage = do_GetInterface(frame);
00450   if (!iimage) return NS_ERROR_FAILURE;
00451 
00452   nsCOMPtr<nsIImageMac_MOZILLA_1_8_BRANCH> imageMac = do_QueryInterface(iimage);
00453   if (!imageMac) return NS_ERROR_FAILURE;
00454 
00455   CGImageRef cgImage;
00456   nsresult rv = imageMac->GetCGImageRef(&cgImage);
00457   if (NS_FAILED(rv)) return rv;
00458   ::CGImageRetain(cgImage);
00459 
00460   // The CGImageRef obtained from the nsIImageMac can't be used as-is.
00461   // It may not be the right size for a menu icon (16x16), and it's
00462   // flipped upside-down.  Create a new CGImage for the menu item.
00463   PRUint8* bitmap = (PRUint8*)PR_Malloc(kIconBytes);
00464 
00465   CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB();
00466   CGImageAlphaInfo alphaInfo = ::CGImageGetAlphaInfo(cgImage);
00467 
00468   CGContextRef bitmapContext;
00469   bitmapContext = ::CGBitmapContextCreate(bitmap, kIconWidth, kIconHeight,
00470                                           kIconBitsPerComponent,
00471                                           kIconBytesPerRow,
00472                                           colorSpace,
00473                                           alphaInfo);
00474   if (!bitmapContext) {
00475     ::CGImageRelease(cgImage);
00476     PR_Free(bitmap);
00477     ::CGColorSpaceRelease(colorSpace);
00478     return NS_ERROR_FAILURE;
00479   }
00480 
00481   // The menu manager expects the icon flipped vertically from the way it
00482   // comes out of nsIImageMac.  Set up a transform to flip it.
00483   ::CGContextTranslateCTM(bitmapContext, 0, kIconHeight);
00484   ::CGContextScaleCTM(bitmapContext, 1, -1);
00485 
00486   CGRect iconRect = ::CGRectMake(0, 0, kIconWidth, kIconHeight);
00487   ::CGContextClearRect(bitmapContext, iconRect);
00488   ::CGContextDrawImage(bitmapContext, iconRect, cgImage);
00489   ::CGImageRelease(cgImage);
00490   ::CGContextRelease(bitmapContext);
00491 
00492   CGDataProviderRef provider;
00493   provider = ::CGDataProviderCreateWithData(NULL, bitmap, kIconBytes,
00494                                             PRAllocCGFree);
00495   if (!provider) {
00496     PR_Free(bitmap);
00497     ::CGColorSpaceRelease(colorSpace);
00498     return NS_ERROR_FAILURE;
00499   }
00500 
00501   CGImageRef iconImage =
00502    ::CGImageCreate(kIconWidth, kIconHeight, kIconBitsPerComponent,
00503                    kIconBitsPerPixel, kIconBytesPerRow, colorSpace, alphaInfo,
00504                    provider, NULL, TRUE, kCGRenderingIntentDefault);
00505   ::CGColorSpaceRelease(colorSpace);
00506   ::CGDataProviderRelease(provider);
00507   if (!iconImage) return NS_ERROR_FAILURE;
00508 
00509   OSStatus err;
00510   err = ::SetMenuItemIconHandle(mMenuRef, mMenuItemIndex, kMenuCGImageRefType,
00511                                 (Handle)iconImage);
00512   ::CGImageRelease(iconImage);
00513   if (err != noErr) return NS_ERROR_FAILURE;
00514 
00515   mLoadedIcon = PR_TRUE;
00516   mSetIcon = PR_TRUE;
00517 
00518   return NS_OK;
00519 }
00520 
00521 
00522 NS_IMETHODIMP
00523 nsMenuItemIcon::OnStopContainer(imgIRequest*   aRequest,
00524                                 imgIContainer* aContainer)
00525 {
00526   return NS_OK;
00527 }
00528 
00529 
00530 NS_IMETHODIMP
00531 nsMenuItemIcon::OnStopDecode(imgIRequest*     aRequest,
00532                              nsresult         status,
00533                              const PRUnichar* statusArg)
00534 {
00535   return NS_OK;
00536 }
00537 
00538 
00539 NS_IMETHODIMP
00540 nsMenuItemIcon::OnStopRequest(imgIRequest* aRequest,
00541                               PRBool       aIsLastPart)
00542 {
00543   mIconRequest->Cancel(NS_BINDING_ABORTED);
00544   mIconRequest = nsnull;
00545   return NS_OK;
00546 }