Bug 274688 - "The dock icon often shows an incorrect number of unread messages" [r=Standard8 sr=bienvenu]

This commit is contained in:
David Humphrey 2009-07-31 10:17:26 +01:00
Родитель 011882ba2a
Коммит 03fb3d3e3a
3 изменённых файлов: 288 добавлений и 108 удалений

Просмотреть файл

@ -22,7 +22,8 @@
* Contributor(s):
* Scott MacGregor <mscott@mozilla.org>
* Jon Baumgartner <jon@bergenstreetsoftware.com>
*
* David Humphrey <david.humphrey@senecac.on.ca>
*
* 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"),
@ -36,7 +37,7 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nscore.h"
#include "nsMessengerOSXIntegration.h"
#include "nsIMsgMailSession.h"
@ -62,7 +63,6 @@
#include "nsIPrefBranch.h"
#include "nsIMessengerWindowService.h"
#include "prprf.h"
#include "nsIWeakReference.h"
#include "nsIAlertsService.h"
#include "nsIStringBundle.h"
#include "nsToolkitCompsCID.h"
@ -75,11 +75,13 @@
#include "nsMsgLocalCID.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIMsgWindow.h"
#include "nsIMsgAccountManager.h"
#include <Carbon/Carbon.h>
#define kNewMailAlertIcon "chrome://messenger/skin/icons/new-mail-alert.png"
#define kBiffShowAlertPref "mail.biff.show_alert"
#define kCountInboxesPref "mail.notification.count.inbox_only"
#define kMaxDisplayCount 10
// HACK: Limitations in Focus/SetFocus on Mac (see bug 465446)
@ -163,17 +165,14 @@ nsMessengerOSXIntegration::nsMessengerOSXIntegration()
{
mBiffStateAtom = do_GetAtom("BiffState");
mNewMailReceivedAtom = do_GetAtom("NewMailReceived");
mBiffIconVisible = PR_FALSE;
NS_NewISupportsArray(getter_AddRefs(mFoldersWithNewMail));
mTotalUnreadMessagesAtom = do_GetAtom("TotalUnreadMessages");
mUnreadTotal = 0;
mOnlyCountInboxes = PR_TRUE;
}
nsMessengerOSXIntegration::~nsMessengerOSXIntegration()
{
if (mBiffIconVisible)
{
RestoreApplicationDockTileImage();
mBiffIconVisible = PR_FALSE;
}
RestoreApplicationDockTileImage();
}
NS_IMPL_ADDREF(nsMessengerOSXIntegration)
@ -194,10 +193,13 @@ nsMessengerOSXIntegration::Init()
nsresult rv;
nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
if (NS_SUCCEEDED(rv))
{
observerService->AddObserver(this, "before-growl-registration", PR_FALSE);
observerService->AddObserver(this, "mail-startup-done", PR_FALSE);
}
nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv,rv);
NS_ENSURE_SUCCESS(rv, rv);
// because we care if the unread total count changes
return mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
@ -230,6 +232,17 @@ nsMessengerOSXIntegration::Observe(nsISupports* aSubject, const char* aTopic, co
if (!strcmp(aTopic, "alertclickcallback"))
return OnAlertClicked(aData);
// get the initial unread count for the dock icon and badge
if (!strcmp(aTopic, "mail-startup-done"))
{
nsresult rv;
nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
if (NS_SUCCEEDED(rv))
observerService->RemoveObserver(this, "mail-startup-done");
InitUnreadCount();
BadgeDockIcon();
}
// register named Growl notification for new mail alerts.
if (!strcmp(aTopic, "before-growl-registration"))
{
@ -254,33 +267,6 @@ nsMessengerOSXIntegration::Observe(nsISupports* aSubject, const char* aTopic, co
return NS_OK;
}
PRInt32
nsMessengerOSXIntegration::CountNewMessages()
{
// iterate over all the folders in mFoldersWithNewMail
nsCOMPtr<nsIMsgFolder> folder;
nsCOMPtr<nsIWeakReference> weakReference;
PRInt32 numNewMessages = 0;
PRInt32 totalNewMessages = 0;
PRUint32 count = 0;
mFoldersWithNewMail->Count(&count);
for (PRUint32 index = 0; index < count; index++)
{
weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
folder = do_QueryReferent(weakReference);
if (folder)
{
numNewMessages = 0;
folder->GetNumNewMessages(PR_TRUE, &numNewMessages);
totalNewMessages += numNewMessages;
} // if we got a folder
} // for each folder
return totalNewMessages;
}
nsresult
nsMessengerOSXIntegration::GetStringBundle(nsIStringBundle **aBundle)
{
@ -419,8 +405,6 @@ nsMessengerOSXIntegration::ShowAlertMessage(const nsAString& aAlertTitle,
BounceDockIcon();
}
BadgeDockIcon();
if (!showAlert || NS_FAILED(rv))
OnAlertFinished();
@ -434,29 +418,19 @@ nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
PRInt32 aNewValue)
{
// if we got new mail show an alert
if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
if (mBiffStateAtom == aProperty)
{
NS_ENSURE_TRUE(aFolder, NS_OK);
if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
{
// if the icon is not already visible, only show a system tray icon if
// we are performing biff (as opposed to the user getting new mail)
if (!mBiffIconVisible)
{
PRBool performingBiff = PR_FALSE;
nsCOMPtr<nsIMsgIncomingServer> server;
aFolder->GetServer(getter_AddRefs(server));
if (server)
server->GetPerformingBiff(&performingBiff);
if (!performingBiff)
return NS_OK; // kick out right now...
}
nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aFolder);
if (mFoldersWithNewMail->IndexOf(weakFolder) == -1)
mFoldersWithNewMail->AppendElement(weakFolder);
PRBool performingBiff = PR_FALSE;
nsCOMPtr<nsIMsgIncomingServer> server;
aFolder->GetServer(getter_AddRefs(server));
if (server)
server->GetPerformingBiff(&performingBiff);
if (!performingBiff)
return NS_OK; // kick out right now...
// Biff happens for the root folder, but we want info for the child with new mail
nsCString folderUri;
@ -471,17 +445,6 @@ nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
childFolder->GetNumNewMessages(PR_TRUE, &numNewMessages);
FillToolTipInfo(childFolder, numNewMessages);
}
else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
{
// we are always going to remove the icon whenever we get our first no
// mail notification.
mFoldersWithNewMail->Clear();
if (mBiffIconVisible)
{
RestoreApplicationDockTileImage();
mBiffIconVisible = PR_FALSE;
}
}
}
else if (mNewMailReceivedAtom == aProperty)
{
@ -489,12 +452,34 @@ nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
nsresult rv = aFolder->GetRootFolder(getter_AddRefs(rootFolder));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(rootFolder);
if (mFoldersWithNewMail->IndexOf(weakFolder) == -1)
mFoldersWithNewMail->AppendElement(weakFolder);
FillToolTipInfo(aFolder, aNewValue);
}
else if (mTotalUnreadMessagesAtom == aProperty)
{
PRUint32 flags;
nsresult rv = aFolder->GetFlags(&flags);
NS_ENSURE_SUCCESS(rv, rv);
// Count this folder if: 1) we want only inboxes and this is an inbox; or
// 2) we want any folder (see ConfirmShouldCount() for folders included).
if ((mOnlyCountInboxes && flags & nsMsgFolderFlags::Inbox) || !mOnlyCountInboxes)
{
// Give extensions a chance to suppress counting for this folder,
// and filter out ones we don't want to count.
PRBool countFolder;
rv = ConfirmShouldCount(aFolder, &countFolder);
NS_ENSURE_SUCCESS(rv, rv);
if (!countFolder)
return NS_OK;
// Increment count by difference, treating -1 (i.e., "don't know") as 0
mUnreadTotal += aNewValue - (aOldValue > -1 ? aOldValue : 0);
NS_ASSERTION(mUnreadTotal > -1, "Updated unread message count is less than zero.");
BadgeDockIcon();
}
}
return NS_OK;
}
@ -534,10 +519,42 @@ nsMessengerOSXIntegration::BadgeDockIcon()
// This will change the dock icon. If we want to overlay the number of
// new messages on top of the icon use OverlayApplicationDockTileImage
// you'll have to pass it a CGImage, and somehow we have to/ create
// the CGImage with the numbers. tricky
PRInt32 totalNewMessages = CountNewMessages();
// the CGImage with the numbers.
// Clean-up dock icon, since we will either redraw (need to get rid of
// any text left over from large numbers), or remove completely.
RestoreApplicationDockTileImage();
// Only badge if unread count > 0.
if (mUnreadTotal < 1)
return NS_OK;
// Draw the number, first giving extensions a chance to modify.
// Extensions might wish to transform "1000" into "100+" or some
// other short string. Getting back the empty string will cause
// nothing to be drawn and us to return early.
nsAutoString total;
total.AppendInt(mUnreadTotal);
nsresult rv;
nsCOMPtr<nsIObserverService> os
(do_GetService("@mozilla.org/observer-service;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsString> str
(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
str->SetData(total);
os->NotifyObservers(str, "before-unread-count-display",
total.get());
nsAutoString badgeString;
str->GetData(badgeString);
if (badgeString.IsEmpty())
return NS_OK;
CGContextRef context = ::BeginCGContextForApplicationDockTile();
// Draw a circle.
::CGContextBeginPath(context);
::CGContextAddArc(context, 95.0, 95.0, 25.0, 0.0, 2 * M_PI, true);
@ -547,10 +564,6 @@ nsMessengerOSXIntegration::BadgeDockIcon()
::CGContextSetRGBFillColor(context, 0.184, 0.776, 0.0, 1);
::CGContextFillPath(context);
// Draw the number.
nsAutoString total;
total.AppendInt(totalNewMessages);
// Use a system font (kThemeUtilityWindowTitleFont)
ScriptCode sysScript = ::GetScriptManagerVariable(smSysScript);
@ -574,22 +587,22 @@ nsMessengerOSXIntegration::BadgeDockIcon()
}
ATSUStyle style;
if (::ATSUCreateStyle(&style) != noErr)
if (::ATSUCreateStyle(&style) != noErr)
{
NS_WARNING("ATSUCreateStyle failed");
::EndCGContextForApplicationDockTile(context);
return NS_ERROR_FAILURE;
}
Fixed size = Long2Fix(24);
RGBColor white = { 0xFFFF, 0xFFFF, 0xFFFF };
ATSUAttributeTag tags[3] = { kATSUFontTag, kATSUSizeTag, kATSUColorTag };
ByteCount valueSizes[3] = { sizeof(ATSUFontID), sizeof(Fixed),
sizeof(RGBColor) };
ATSUAttributeValuePtr values[3] = { &fmFont, &size, &white };
if (::ATSUSetAttributes(style, 3, tags, valueSizes, values) != noErr)
if (::ATSUSetAttributes(style, 3, tags, valueSizes, values) != noErr)
{
NS_WARNING("ATSUSetAttributes failed");
::ATSUDisposeStyle(style);
@ -599,14 +612,14 @@ nsMessengerOSXIntegration::BadgeDockIcon()
UniCharCount runLengths = kATSUToTextEnd;
ATSUTextLayout textLayout;
if (::ATSUCreateTextLayoutWithTextPtr(total.get(),
kATSUFromTextBeginning,
kATSUToTextEnd,
total.Length(),
1,
&runLengths,
&style,
&textLayout) != noErr)
if (::ATSUCreateTextLayoutWithTextPtr(badgeString.get(),
kATSUFromTextBeginning,
kATSUToTextEnd,
badgeString.Length(),
1,
&runLengths,
&style,
&textLayout) != noErr)
{
NS_WARNING("ATSUCreateTextLayoutWithTextPtr failed");
::ATSUDisposeStyle(style);
@ -618,11 +631,11 @@ nsMessengerOSXIntegration::BadgeDockIcon()
ByteCount layoutValueSizes[1] = { sizeof(CGContextRef) };
ATSUAttributeValuePtr layoutValues[1] = { &context };
if (::ATSUSetLayoutControls(textLayout,
1,
layoutTags,
layoutValueSizes,
layoutValues) != noErr)
if (::ATSUSetLayoutControls(textLayout,
1,
layoutTags,
layoutValueSizes,
layoutValues) != noErr)
{
NS_WARNING("ATSUSetLayoutControls failed");
::ATSUDisposeStyle(style);
@ -631,12 +644,12 @@ nsMessengerOSXIntegration::BadgeDockIcon()
}
Rect boundingBox;
if (::ATSUMeasureTextImage(textLayout,
if (::ATSUMeasureTextImage(textLayout,
kATSUFromTextBeginning,
kATSUToTextEnd,
Long2Fix(0),
Long2Fix(0),
&boundingBox) != noErr)
&boundingBox) != noErr)
{
NS_WARNING("ATSUMeasureTextImage failed");
::ATSUDisposeStyle(style);
@ -646,7 +659,7 @@ nsMessengerOSXIntegration::BadgeDockIcon()
// Center text inside circle
::ATSUDrawText(textLayout, kATSUFromTextBeginning, kATSUToTextEnd,
Long2Fix(90 - (boundingBox.right - boundingBox.left) / 2),
Long2Fix(95 - (boundingBox.right - boundingBox.left) / 2),
Long2Fix(95 - (boundingBox.bottom - boundingBox.top) / 2));
::ATSUDisposeStyle(style);
@ -654,8 +667,6 @@ nsMessengerOSXIntegration::BadgeDockIcon()
::CGContextFlush(context);
::EndCGContextForApplicationDockTile(context);
mBiffIconVisible = PR_TRUE;
return NS_OK;
}
@ -732,7 +743,7 @@ nsMessengerOSXIntegration::GetNewMailAuthors(nsIMsgFolder* aFolder,
{
if (0 == aNewCount || displayed == kMaxDisplayCount)
break;
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = db->GetMsgHdrForKey(newMessageKeys[i],
getter_AddRefs(hdr));
@ -822,3 +833,168 @@ nsMessengerOSXIntegration::GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCS
return NS_OK;
}
void
nsMessengerOSXIntegration::InitUnreadCount()
{
// We either count just inboxes, or all folders
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, );
rv = prefBranch->GetBoolPref(kCountInboxesPref, &mOnlyCountInboxes);
NS_ENSURE_SUCCESS(rv, );
nsCOMPtr<nsIMsgAccountManager> accountManager =
do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, );
nsCOMPtr<nsISupportsArray> servers;
rv = accountManager->GetAllServers(getter_AddRefs(servers));
NS_ENSURE_SUCCESS(rv, );
PRUint32 count;
rv = servers->Count(&count);
NS_ENSURE_SUCCESS(rv, );
PRUint32 i;
for (i = 0; i < count; i++)
{
nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(servers, i);
if (!server)
continue;
nsCOMPtr<nsIMsgFolder> rootFolder;
server->GetRootFolder(getter_AddRefs(rootFolder));
if (!rootFolder)
continue;
// Get a combined unread count for all desired folders
PRInt32 numUnread = 0;
if (mOnlyCountInboxes)
{
nsCOMPtr<nsIMsgFolder> inboxFolder;
rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder));
if (inboxFolder)
GetTotalUnread(inboxFolder, PR_FALSE, &numUnread);
}
else
GetTotalUnread(rootFolder, PR_TRUE, &numUnread);
mUnreadTotal += numUnread;
NS_ASSERTION(mUnreadTotal > -1, "Initial unread message count is less than zero.");
}
}
nsresult
nsMessengerOSXIntegration::ConfirmShouldCount(nsIMsgFolder* aFolder, PRBool* aCountFolder)
{
// We give extensions a chance to say yes/no to counting for a folder. By
// default we count every folder that is mail and isn't
// Trash, Junk, Drafts, "Outbox", or a Virtual folder.
nsCOMPtr<nsIMsgIncomingServer> server;
nsresult rv = aFolder->GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
PRBool defaultValue = PR_TRUE;
nsCAutoString type;
rv = server->GetType(type);
if (NS_FAILED(rv) || (type.EqualsLiteral("rss") || type.EqualsLiteral("nntp")))
{
defaultValue = PR_FALSE;
return NS_OK;
}
nsCOMPtr<nsIObserverService> os =
do_GetService("@mozilla.org/observer-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupportsPRBool> shouldCount =
do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 flags;
aFolder->GetFlags(&flags);
if ((flags & nsMsgFolderFlags::Trash) ||
(flags & nsMsgFolderFlags::Drafts) ||
(flags & nsMsgFolderFlags::Queue) ||
(flags & nsMsgFolderFlags::Virtual) ||
(flags & nsMsgFolderFlags::Junk))
defaultValue = PR_FALSE;
rv = shouldCount->SetData(defaultValue);
NS_ENSURE_SUCCESS(rv, rv);
nsCString folderUri;
rv = aFolder->GetURI(folderUri);
NS_ENSURE_SUCCESS(rv, rv);
rv = os->NotifyObservers(shouldCount, "before-count-unread-for-folder",
NS_ConvertUTF8toUTF16(folderUri).get());
NS_ENSURE_SUCCESS(rv, rv);
return shouldCount->GetData(aCountFolder);
}
nsresult
nsMessengerOSXIntegration::GetTotalUnread(nsIMsgFolder* aFolder, PRBool deep, PRInt32* aTotal)
{
// This simulates nsIMsgFolder::GetNumUnread, but gives extensions
// a chance to decide whether folders should be counted as part of
// the total.
*aTotal = 0;
PRBool countFolder;
nsresult rv = ConfirmShouldCount(aFolder, &countFolder);
NS_ENSURE_SUCCESS(rv, rv);
if (!countFolder)
return NS_OK;
PRInt32 total = 0;
rv = aFolder->GetNumUnread(PR_FALSE, &total);
NS_ENSURE_SUCCESS(rv, rv);
// Use zero instead of -1 (don't know) or other special nums.
total = total >= 0 ? total : 0;
if (deep)
{
PRBool hasChildren;
rv = aFolder->GetHasSubFolders(&hasChildren);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 flags;
aFolder->GetFlags(&flags);
if (hasChildren && !(flags & nsMsgFolderFlags::Virtual))
{
nsCOMPtr<nsISimpleEnumerator> children;
rv = aFolder->GetSubFolders(getter_AddRefs(children));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgFolder> childFolder;
PRBool moreFolders;
while (NS_SUCCEEDED(children->HasMoreElements(&moreFolders)) &&
moreFolders)
{
nsCOMPtr<nsISupports> child;
rv = children->GetNext(getter_AddRefs(child));
if (NS_SUCCEEDED(rv) && child)
{
childFolder = do_QueryInterface(child, &rv);
if (NS_SUCCEEDED(rv) && childFolder)
{
PRInt32 childFolderCount = 0;
rv = GetTotalUnread(childFolder, PR_TRUE, &childFolderCount);
if (NS_FAILED(rv))
continue;
total += childFolderCount;
}
}
}
}
}
*aTotal = total;
return NS_OK;
}

Просмотреть файл

@ -22,7 +22,8 @@
* Contributor(s):
* Scott MacGregor <mscott@mozilla.org>
* Jon Baumgartner <jon@bergenstreetsoftware.com>
*
* David Humphrey <david.humphrey@senecac.on.ca>
*
* 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"),
@ -47,7 +48,6 @@
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsInt64.h"
#include "nsISupportsArray.h"
#include "nsIObserver.h"
#include "nsIAlertsService.h"
@ -72,10 +72,9 @@ public:
NS_DECL_NSIOBSERVER
private:
nsCOMPtr<nsISupportsArray> mFoldersWithNewMail; // keep track of all the root folders with pending new mail
nsCOMPtr<nsIAtom> mBiffStateAtom;
nsCOMPtr<nsIAtom> mNewMailReceivedAtom;
PRInt32 CountNewMessages();
nsCOMPtr<nsIAtom> mTotalUnreadMessagesAtom;
nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI);
nsresult OnAlertFinished();
nsresult OnAlertClicked(const PRUnichar * aAlertCookie);
@ -85,8 +84,12 @@ private:
nsresult BadgeDockIcon();
nsresult BounceDockIcon();
nsresult GetNewMailAuthors(nsIMsgFolder* aFolder, nsString& aAuthors, PRInt32 aNewCount, PRInt32* aNotDisplayed);
nsresult GetTotalUnread(nsIMsgFolder* aFolder, PRBool deep, PRInt32* aTotal);
nsresult ConfirmShouldCount(nsIMsgFolder* aFolder, PRBool* aCountFolder);
void InitUnreadCount();
PRPackedBool mBiffIconVisible;
PRInt32 mUnreadTotal;
PRBool mOnlyCountInboxes;
};
#endif // __nsMessengerOSXIntegration_h

Просмотреть файл

@ -812,6 +812,7 @@ pref("ldap_2.servers.osx.uri", "moz-abosxdirectory:///");
pref("ldap_2.servers.osx.description", "chrome://messenger/locale/addressbook/addressBook.properties");
pref("ldap_2.servers.osx.dirType", 3);
pref("mail.notification.sound", "");
pref("mail.notification.count.inbox_only", true);
#else
#ifdef XP_UNIX
pref("mail.check_new_mail", true);