Back to index

lightning-sunbird  0.9+nobinonly
nsMessengerOSXIntegration.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 Mac OSX New Mail Notification Code..
00016  *
00017  * The Initial Developer of the Original Code is
00018  * The Mozilla Foundation.
00019  * Portions created by the Initial Developer are Copyright (C) 2005
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Scott MacGregor <mscott@mozilla.org>
00024  *  Jon Baumgartner <jon@bergenstreetsoftware.com>
00025  *  
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039  
00040 #include "nscore.h"
00041 #include "nsMessengerOSXIntegration.h"
00042 #include "nsIMsgAccountManager.h"
00043 #include "nsIMsgMailSession.h"
00044 #include "nsIMsgIncomingServer.h"
00045 #include "nsIMsgIdentity.h"
00046 #include "nsIMsgAccount.h"
00047 #include "nsIRDFResource.h"
00048 #include "nsIMsgFolder.h"
00049 #include "nsCOMPtr.h"
00050 #include "nsMsgBaseCID.h"
00051 #include "nsMsgFolderFlags.h"
00052 #include "nsDirectoryServiceDefs.h"
00053 #include "nsIDirectoryService.h"
00054 
00055 #include "nsIWindowMediator.h"
00056 #include "nsIDOMChromeWindow.h"
00057 #include "nsIDOMWindowInternal.h"
00058 #include "nsPIDOMWindow.h"
00059 #include "nsIScriptGlobalObject.h"
00060 #include "nsIDocShell.h"
00061 #include "nsIBaseWindow.h"
00062 #include "nsIWidget.h"
00063 #include "nsIObserverService.h"
00064 #include "nsIPrefService.h"
00065 #include "nsIPrefBranch.h" 
00066 #include "nsIMessengerWindowService.h"
00067 #include "prprf.h"
00068 #include "nsIWeakReference.h"
00069 
00070 #include <Carbon/Carbon.h>
00071 
00072 #define kBiffBadgeIcon "mail-biff-badge.png"
00073 
00074 nsMessengerOSXIntegration::nsMessengerOSXIntegration()
00075 {
00076   mBiffStateAtom = do_GetAtom("BiffState");
00077 
00078   mBiffIconVisible = PR_FALSE;
00079   mSuppressBiffIcon = PR_FALSE;
00080   mAlertInProgress = PR_FALSE;
00081   NS_NewISupportsArray(getter_AddRefs(mFoldersWithNewMail));
00082 }
00083 
00084 nsMessengerOSXIntegration::~nsMessengerOSXIntegration()
00085 {
00086   if (mBiffIconVisible) 
00087   {
00088     RestoreApplicationDockTileImage();
00089     mBiffIconVisible = PR_FALSE;
00090   }
00091 }
00092 
00093 NS_IMPL_ADDREF(nsMessengerOSXIntegration)
00094 NS_IMPL_RELEASE(nsMessengerOSXIntegration)
00095 
00096 NS_INTERFACE_MAP_BEGIN(nsMessengerOSXIntegration)
00097    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
00098    NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
00099    NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
00100 NS_INTERFACE_MAP_END
00101 
00102 
00103 nsresult
00104 nsMessengerOSXIntegration::Init()
00105 {
00106   nsresult rv;
00107     
00108   nsCOMPtr <nsIMsgAccountManager> accountManager = 
00109     do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
00110   NS_ENSURE_SUCCESS(rv,rv);
00111 
00112   // because we care if the default server changes
00113   rv = accountManager->AddRootFolderListener(this);
00114   NS_ENSURE_SUCCESS(rv,rv);
00115 
00116   nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
00117   NS_ENSURE_SUCCESS(rv,rv);
00118 
00119   // because we care if the unread total count changes
00120   rv = mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
00121   NS_ENSURE_SUCCESS(rv,rv);
00122 
00123   return NS_OK;
00124 }
00125 
00126 NS_IMETHODIMP
00127 nsMessengerOSXIntegration::OnItemPropertyChanged(nsIRDFResource *, nsIAtom *, char const *, char const *)
00128 {
00129   return NS_OK;
00130 }
00131 
00132 NS_IMETHODIMP
00133 nsMessengerOSXIntegration::OnItemUnicharPropertyChanged(nsIRDFResource *, nsIAtom *, const PRUnichar *, const PRUnichar *)
00134 {
00135   return NS_OK;
00136 }
00137 
00138 NS_IMETHODIMP
00139 nsMessengerOSXIntegration::OnItemRemoved(nsIRDFResource *, nsISupports *)
00140 {
00141   return NS_OK;
00142 }
00143 
00144 PRInt32 nsMessengerOSXIntegration::CountNewMessages()
00145 {
00146   // iterate over all the folders in mFoldersWithNewMail
00147   nsCOMPtr<nsIMsgFolder> folder;
00148   nsCOMPtr<nsIWeakReference> weakReference;
00149   PRInt32 numNewMessages = 0;
00150   PRInt32 totalNewMessages = 0;
00151   
00152   PRUint32 count = 0;
00153   mFoldersWithNewMail->Count(&count);
00154 
00155   for (PRUint32 index = 0; index < count; index++)
00156   {
00157     weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
00158     folder = do_QueryReferent(weakReference);
00159     if (folder)
00160     {
00161 
00162       numNewMessages = 0;   
00163       folder->GetNumNewMessages(PR_TRUE, &numNewMessages);
00164       totalNewMessages += numNewMessages;
00165     } // if we got a folder
00166   } // for each folder
00167 
00168   return totalNewMessages;
00169   
00170 }
00171 
00172 nsresult nsMessengerOSXIntegration::OnAlertFinished(const PRUnichar * aAlertCookie)
00173 {
00174   nsresult rv = NS_OK;
00175   nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
00176   NS_ENSURE_SUCCESS(rv, rv);
00177   
00178   PRBool bounceDockIcon = PR_FALSE; 
00179   prefBranch->GetBoolPref("mail.biff.animate_dock_icon", &bounceDockIcon);
00180 
00181   // This will call GetAttention(), which will bounce the dock icon.
00182   if (!mSuppressBiffIcon)
00183   {
00184     nsCOMPtr<nsIWindowMediator> mediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
00185     if (bounceDockIcon && mediator)
00186     {
00187       nsCOMPtr<nsIDOMWindowInternal> domWindow;
00188       mediator->GetMostRecentWindow(NS_LITERAL_STRING("mail:3pane").get(), getter_AddRefs(domWindow));
00189       if (domWindow)
00190          {
00191         nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(domWindow));
00192         chromeWindow->GetAttention();
00193          }
00194     }
00195 
00196     // This will change the dock icon.     
00197     // If we want to overlay the number of new messages on top of
00198     // the icon ...
00199     
00200     // use OverlayApplicationDockTileImage
00201     // -- you'll have to pass it a CGImage, and somehow we have to
00202     // create the CGImage with the numbers. tricky    
00203     PRInt32 totalNewMessages = CountNewMessages();
00204     CGContextRef context = ::BeginCGContextForApplicationDockTile();
00205     
00206     // Draw a circle.
00207 
00208     ::CGContextBeginPath(context);
00209     ::CGContextAddArc(context, 95.0, 95.0, 25.0, 0.0, 2 * M_PI, true);
00210     ::CGContextClosePath(context);
00211 
00212     // use #2fc600 for the color.
00213     ::CGContextSetRGBFillColor(context, 0.184, 0.776, 0.0, 1);
00214 
00215     //::CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.7);
00216     ::CGContextFillPath(context);
00217 
00218     // Draw the number.
00219     nsAutoString total;
00220     total.AppendInt(totalNewMessages);
00221 
00222     // Use a system font (kThemeUtilityWindowTitleFont)
00223     ScriptCode sysScript = ::GetScriptManagerVariable(smSysScript);
00224 
00225     Str255 fontName;
00226     SInt16 fontSize;
00227     Style fontStyle;
00228     ::GetThemeFont(kThemeSmallEmphasizedSystemFont, sysScript, fontName,
00229                    &fontSize, &fontStyle);
00230 
00231     FMFontFamily family = ::FMGetFontFamilyFromName(fontName);
00232     FMFont fmFont;
00233     OSStatus err = ::FMGetFontFromFontFamilyInstance(family, fontStyle, &fmFont,
00234                                                      nsnull);
00235     if (err != noErr) 
00236        {
00237          NS_WARNING("FMGetFontFromFontFamilyInstance failed");
00238          ::EndCGContextForApplicationDockTile(context);
00239          return NS_ERROR_FAILURE;
00240     }
00241 
00242     ATSUStyle style;
00243     err = ::ATSUCreateStyle(&style);
00244     if (err != noErr) 
00245        {
00246       NS_WARNING("ATSUCreateStyle failed");
00247          ::EndCGContextForApplicationDockTile(context);
00248       return NS_ERROR_FAILURE;
00249        }
00250         
00251     Fixed size = Long2Fix(24);
00252     RGBColor white = { 0xFFFF, 0xFFFF, 0xFFFF };
00253     
00254     ATSUAttributeTag tags[3] = { kATSUFontTag, kATSUSizeTag, kATSUColorTag };
00255     ByteCount valueSizes[3] = { sizeof(ATSUFontID), sizeof(Fixed),
00256                                 sizeof(RGBColor) };
00257     ATSUAttributeValuePtr values[3] = { &fmFont, &size, &white };
00258 
00259     err = ::ATSUSetAttributes(style, 3, tags, valueSizes, values);
00260     if (err != noErr) {
00261         NS_WARNING("ATSUSetAttributes failed");
00262         ::ATSUDisposeStyle(style);
00263         ::EndCGContextForApplicationDockTile(context);
00264 
00265         return NS_ERROR_FAILURE;
00266       }
00267 
00268     UniCharCount runLengths = kATSUToTextEnd;
00269     ATSUTextLayout textLayout;
00270     err = ::ATSUCreateTextLayoutWithTextPtr(total.get(),
00271                                             kATSUFromTextBeginning,
00272                                             kATSUToTextEnd, total.Length(), 1,
00273                                             &runLengths, &style, &textLayout);
00274     if (err != noErr) 
00275        {
00276         NS_WARNING("ATSUCreateTextLayoutWithTextPtr failed");
00277         ::ATSUDisposeStyle(style);
00278         ::EndCGContextForApplicationDockTile(context);
00279 
00280         return NS_ERROR_FAILURE;
00281     }
00282 
00283     ATSUAttributeTag layoutTags[1] = { kATSUCGContextTag };
00284     ByteCount layoutValueSizes[1] = { sizeof(CGContextRef) };
00285     ATSUAttributeValuePtr layoutValues[1] = { &context };
00286 
00287     err = ::ATSUSetLayoutControls(textLayout, 1, layoutTags, layoutValueSizes,
00288                                   layoutValues);
00289     if (err != noErr) 
00290        {
00291         NS_WARNING("ATSUSetLayoutControls failed");
00292         ::ATSUDisposeStyle(style);
00293         ::EndCGContextForApplicationDockTile(context);
00294         return NS_ERROR_FAILURE;
00295     }
00296 
00297     Rect boundingBox;
00298     err = ::ATSUMeasureTextImage(textLayout, kATSUFromTextBeginning,
00299                                  kATSUToTextEnd, Long2Fix(0), Long2Fix(0),
00300                                  &boundingBox);
00301     if (err != noErr) 
00302        {
00303         NS_WARNING("ATSUMeasureTextImage failed");
00304         ::ATSUDisposeStyle(style);
00305         ::EndCGContextForApplicationDockTile(context);
00306         return NS_ERROR_FAILURE;
00307     }
00308 
00309     // Center text inside circle
00310     err = ::ATSUDrawText(textLayout, kATSUFromTextBeginning, kATSUToTextEnd,
00311                          Long2Fix(90 - (boundingBox.right - boundingBox.left) / 2),
00312                          Long2Fix(95 - (boundingBox.bottom - boundingBox.top) / 2));
00313 
00314     ::ATSUDisposeStyle(style);
00315     ::ATSUDisposeTextLayout(textLayout);
00316 
00317     ::CGContextFlush(context);
00318 
00319     ::EndCGContextForApplicationDockTile(context);
00320     mBiffIconVisible = PR_TRUE;
00321   }
00322 
00323   mSuppressBiffIcon = PR_FALSE;
00324   mAlertInProgress = PR_FALSE;
00325   return NS_OK;
00326 }
00327 
00328 NS_IMETHODIMP
00329 nsMessengerOSXIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, PRUint32 oldFlag, PRUint32 newFlag)
00330 {
00331   return NS_OK;
00332 }
00333 
00334 NS_IMETHODIMP
00335 nsMessengerOSXIntegration::OnItemAdded(nsIRDFResource *, nsISupports *)
00336 {
00337   return NS_OK;
00338 }
00339 
00340 NS_IMETHODIMP
00341 nsMessengerOSXIntegration::OnItemBoolPropertyChanged(nsIRDFResource *aItem,
00342                                                          nsIAtom *aProperty,
00343                                                          PRBool aOldValue,
00344                                                          PRBool aNewValue)
00345 {
00346   return NS_OK;
00347 }
00348 
00349 NS_IMETHODIMP
00350 nsMessengerOSXIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
00351 {
00352   return NS_OK;
00353 }
00354 
00355 NS_IMETHODIMP
00356 nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIRDFResource *aItem, nsIAtom *aProperty, PRInt32 aOldValue, PRInt32 aNewValue)
00357 {
00358   nsresult rv;
00359   // if we got new mail bounce the Dock icon and/or apply badge to Dock icon
00360   if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
00361   {
00362     nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(aItem);
00363     NS_ENSURE_TRUE(folder, NS_OK);
00364 
00365     if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail) 
00366     {
00367       // if the icon is not already visible, only show a system tray icon iff 
00368       // we are performing biff (as opposed to the user getting new mail)
00369       if (!mBiffIconVisible)
00370       {
00371         PRBool performingBiff = PR_FALSE;
00372         nsCOMPtr<nsIMsgIncomingServer> server;
00373               folder->GetServer(getter_AddRefs(server));
00374         if (server)
00375                 server->GetPerformingBiff(&performingBiff);
00376         if (!performingBiff) 
00377           return NS_OK; // kick out right now...
00378       }
00379 
00380       nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(folder); 
00381       // remove the element if it is already in the array....
00382       PRUint32 count = 0;
00383       PRUint32 index = 0; 
00384       mFoldersWithNewMail->Count(&count);
00385       nsCOMPtr<nsIMsgFolder> oldFolder;
00386       nsCOMPtr<nsIWeakReference> weakReference;
00387       for (index = 0; index < count; index++)
00388       {
00389         weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
00390         oldFolder = do_QueryReferent(weakReference);
00391         if (oldFolder == folder) // if they point to the same folder
00392           break;
00393         oldFolder = nsnull;
00394       }
00395 
00396         if (oldFolder)
00397        mFoldersWithNewMail->ReplaceElementAt(weakFolder, index);
00398         else
00399        mFoldersWithNewMail->AppendElement(weakFolder);
00400       // now regenerate the tooltip
00401       OnAlertFinished(nsnull);  
00402     }
00403     else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
00404        {
00405       // we are always going to remove the icon whenever we get our first no mail
00406       // notification. 
00407       mFoldersWithNewMail->Clear(); 
00408       if (mBiffIconVisible) 
00409       {
00410         RestoreApplicationDockTileImage();
00411         mBiffIconVisible = PR_FALSE;
00412       }
00413     }
00414   } // if the biff property changed
00415   
00416   return NS_OK;
00417 }