Merge lp:~chrisccoulson/messagingmenu-extension/me-fixes into lp:messagingmenu-extension

Proposed by Chris Coulson
Status: Merged
Merged at revision: 44
Proposed branch: lp:~chrisccoulson/messagingmenu-extension/me-fixes
Merge into: lp:messagingmenu-extension
Diff against target: 1021 lines (+650/-140)
7 files modified
config_build.sh (+2/-2)
defaults/preferences/defaults.js (+0/-4)
install.rdf (+0/-1)
locale/en-US/messagingmenu.properties (+1/-1)
modules/LibDbusmenu.jsm (+15/-0)
modules/LibIndicate.jsm (+14/-1)
modules/MessagingMenu.jsm (+618/-131)
To merge this branch: bzr merge lp:~chrisccoulson/messagingmenu-extension/me-fixes
Reviewer Review Type Date Requested Status
Mike Conley Approve
Review via email: mp+65678@code.launchpad.net

Description of the change

Here are some changes I've been working on :-)

To post a comment you must log in.
Revision history for this message
Mike Conley (mconley) wrote :

+ let folderURL = LibIndicate.IndicatorGetProperty(indicator,
FOLDER_URL_KEY).readString();
+ LOG("Received click event on indicator for folder " + folderURL);
+
+ let mmie = MessagingMenu.mIndicators[folderURL];
+ if (!mmie) {
+ WARN("No indicator for folder " + folderURL);
+ return;

I assume that "mmie" stands for something like "Messaging Menu Indicator
Entry". Still, I think "indicatorEntry" would suffice, and is clearer.

+// Small helper class which takes a directory containting messaging menu
+// launcher entries and tells the listener whether one of them is ours
+function LauncherEntryFinder(aDir, aDesktopFile, aListener, aMethod) {
+ LOG("Searching for launcher entry for " + aDesktopFile + " in " +
aDir.path);

Typo: "containting" -> "containing"

+ cleanup: function MM_cleanup() {
+ // We're being uninstalled or disabled. If we created a launcher
+ // entry in the messaging menu, make sure we clean it up
+ let userLauncherEntryDir = Services.dirsvc.get("Home",
Ci.nsILocalFile);
+ userLauncherEntryDir.appendRelativePath(USER_LAUNCHER_ENTRIES);
+ new LauncherEntryFinder(userLauncherEntryDir, this.desktopFile, this,
+ "removeLauncherEntry");
+ },

What if a launcher entry exists, but we didn't put it there?

+
+ //this.buildIndicators();
+

Please remove.

+ // Now see if there are any pending indicators to be shown
+ // Show the one which has been waiting the longest
+ let pendingIndicator = null;
+ for (let url in this.mIndicators) {

Oh, I quite like this idea. Good work. :)

I like it! Once the above is addressed, I think this is fine to merge.

58. By Chris Coulson

Rename mmie => indicatorEntry
Fix a typo
Delete some dead code

Revision history for this message
Chris Coulson (chrisccoulson) wrote :

Thanks for reviewing. I've pushed an update with most of those comments addressed, and I removed 2 dead functions that I'd added as well.
>
> + cleanup: function MM_cleanup() {
> + // We're being uninstalled or disabled. If we created a launcher
> + // entry in the messaging menu, make sure we clean it up
> + let userLauncherEntryDir = Services.dirsvc.get("Home",
> Ci.nsILocalFile);
> + userLauncherEntryDir.appendRelativePath(USER_LAUNCHER_ENTRIES);
> + new LauncherEntryFinder(userLauncherEntryDir, this.desktopFile, this,
> + "removeLauncherEntry");
> + },
>
> What if a launcher entry exists, but we didn't put it there?
>

Yeah, I guess this is a problem, but I think it is an edge case. In general, we don't offer any particular guarantees about application data stored in the profile (eg, we could save a pref so we know we created the file, but there's no guarantees the user hasn't modified it since we created it)

59. By Chris Coulson

Only clean up the local launcher entry if we created it (just based on a simple mtime check)

Revision history for this message
Chris Coulson (chrisccoulson) wrote :

I've fixed the remaining issue as well now

Revision history for this message
Mike Conley (mconley) wrote :

Hey Chris - looks good to me, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config_build.sh'
2--- config_build.sh 2011-06-07 15:01:14 +0000
3+++ config_build.sh 2011-06-24 11:05:55 +0000
4@@ -3,10 +3,10 @@
5 # Build config for the build script, build.sh. Look there for more info.
6
7 APP_NAME=messagingmenu
8-CHROME_PROVIDERS="content"
9+CHROME_PROVIDERS="content locale"
10 CLEAN_UP=1
11 ROOT_FILES=
12-ROOT_DIRS="modules locale defaults"
13+ROOT_DIRS="modules"
14 BEFORE_BUILD=
15 BEFORE_PACK=
16 AFTER_BUILD=
17
18=== removed directory 'defaults'
19=== removed directory 'defaults/preferences'
20=== removed file 'defaults/preferences/defaults.js'
21--- defaults/preferences/defaults.js 2011-03-29 17:55:47 +0000
22+++ defaults/preferences/defaults.js 1970-01-01 00:00:00 +0000
23@@ -1,4 +0,0 @@
24-pref("extensions.messagingmenu.showIndication", true);
25-pref("extensions.messagingmenu.includeMail", true);
26-pref("extensions.messagingmenu.includeNewsgroups", true);
27-pref("extensions.messagingmenu.includeRSS", true);
28
29=== modified file 'install.rdf'
30--- install.rdf 2011-06-06 19:04:21 +0000
31+++ install.rdf 2011-06-24 11:05:55 +0000
32@@ -5,7 +5,6 @@
33 <Description about="urn:mozilla:install-manifest">
34 <em:id>messagingmenu@mozilla.com</em:id>
35 <em:version>0.6</em:version>
36- <em:unpack>true</em:unpack>
37 <em:optionsURL>chrome://messagingmenu/content/options.xul</em:optionsURL>
38
39 <em:targetApplication>
40
41=== modified file 'locale/en-US/messagingmenu.properties'
42--- locale/en-US/messagingmenu.properties 2011-02-26 19:33:18 +0000
43+++ locale/en-US/messagingmenu.properties 2011-06-24 11:05:55 +0000
44@@ -1,2 +1,2 @@
45-compose_new_message=Compose New Message
46+composeNewMessage=Compose New Message
47 contacts=Contacts
48
49=== modified file 'modules/LibDbusmenu.jsm'
50--- modules/LibDbusmenu.jsm 2011-06-06 14:58:22 +0000
51+++ modules/LibDbusmenu.jsm 2011-06-24 11:05:55 +0000
52@@ -101,6 +101,20 @@
53 return dbusmenu_menuitem_property_set;
54 });
55
56+XPCOMUtils.defineLazyGetter(this, "dbusmenu_menuitem_property_set_bool", function() {
57+ var dbusmenu_menuitem_property_set_bool =
58+ libdbusmenu.declare("dbusmenu_menuitem_property_set_bool",
59+ ctypes.default_abi,
60+ LibGObject.gboolean,
61+ DbusmenuMenuitem.ptr,
62+ LibGObject.gchar.ptr,
63+ LibGObject.gboolean);
64+ if (!dbusmenu_menuitem_property_set_bool)
65+ throw "dbusmenu_menuitem_property_set is unavailable";
66+
67+ return dbusmenu_menuitem_property_set_bool;
68+});
69+
70 XPCOMUtils.defineLazyGetter(this, "dbusmenu_menuitem_child_append", function() {
71 var dbusmenu_menuitem_child_append =
72 libdbusmenu.declare("dbusmenu_menuitem_child_append",
73@@ -134,6 +148,7 @@
74 ServerNew: dbusmenu_server_new,
75 MenuitemNew: dbusmenu_menuitem_new,
76 MenuitemPropertySet: dbusmenu_menuitem_property_set,
77+ MenuitemPropertySetBool: dbusmenu_menuitem_property_set_bool,
78 MenuitemChildAppend: dbusmenu_menuitem_child_append,
79 ServerSetRoot: dbusmenu_server_set_root
80 };
81
82=== modified file 'modules/LibIndicate.jsm'
83--- modules/LibIndicate.jsm 2011-06-06 19:04:21 +0000
84+++ modules/LibIndicate.jsm 2011-06-24 11:05:55 +0000
85@@ -190,6 +190,18 @@
86 return indicate_indicator_hide;
87 });
88
89+XPCOMUtils.defineLazyGetter(this, "indicate_indicator_is_visible", function() {
90+ var indicate_indicator_is_visible =
91+ libindicate.declare("indicate_indicator_is_visible",
92+ ctypes.default_abi,
93+ LibGObject.gboolean,
94+ Indicator.ptr)
95+ if (!indicate_indicator_is_visible)
96+ throw "indicate_indicator_is_visible is unavailable";
97+
98+ return indicate_indicator_is_visible;
99+});
100+
101 var LibIndicate = {
102 Indicator: Indicator,
103 IndicateServer: IndicateServer,
104@@ -213,5 +225,6 @@
105 IndicatorSetProperty: indicate_indicator_set_property,
106 IndicatorGetProperty: indicate_indicator_get_property,
107 IndicatorShow: indicate_indicator_show,
108- IndicatorHide: indicate_indicator_hide
109+ IndicatorHide: indicate_indicator_hide,
110+ IndicatorIsVisible: indicate_indicator_is_visible
111 };
112
113=== modified file 'modules/MessagingMenu.jsm'
114--- modules/MessagingMenu.jsm 2011-06-07 15:02:53 +0000
115+++ modules/MessagingMenu.jsm 2011-06-24 11:05:55 +0000
116@@ -21,6 +21,7 @@
117 *
118 * Contributor(s):
119 * Mike Conley <mconley@mozillamessaging.com>
120+ * Chris Coulson <chris.coulson@canonical.com>
121 *
122 * Alternatively, the contents of this file may be used under the terms of
123 * either the GNU General Public License Version 2 or later (the "GPL"), or
124@@ -45,7 +46,11 @@
125
126 Cu.import("resource://gre/modules/ctypes.jsm");
127 Cu.import("resource://gre/modules/Services.jsm");
128+Cu.import("resource://gre/modules/NetUtil.jsm");
129+Cu.import("resource://gre/modules/FileUtils.jsm");
130+Cu.import("resource://gre/modules/AddonManager.jsm");
131 Cu.import("resource:///modules/mailServices.js");
132+Cu.import("resource:///modules/iteratorUtils.jsm");
133 Cu.import("resource://messagingmenu/LibGObject.jsm");
134 Cu.import("resource://messagingmenu/LibDbusmenu.jsm");
135 Cu.import("resource://messagingmenu/LibIndicate.jsm");
136@@ -53,17 +58,37 @@
137 // I need the GdkWindow lib to do focus hacking
138 Cu.import("resource://messagingmenu/LibGdkWindow.jsm");
139
140+["LOG", "WARN", "ERROR"].forEach(function(aName) {
141+ this.__defineGetter__(aName, function() {
142+ Components.utils.import("resource://gre/modules/AddonLogging.jsm");
143+
144+ LogManager.getLogger("messagingmenu", this);
145+ return this[aName];
146+ });
147+}, this);
148+
149 const FLDR_UNINTERESTING = Ci.nsMsgFolderFlags.Trash
150- & Ci.nsMsgFolderFlags.Junk
151- & Ci.nsMsgFolderFlags.SentMail
152- & Ci.nsMsgFolderFlags.Drafts
153- & Ci.nsMsgFolderFlags.Templates
154- & Ci.nsMsgFolderFlags.Queue;
155+ | Ci.nsMsgFolderFlags.Junk
156+ | Ci.nsMsgFolderFlags.SentMail
157+ | Ci.nsMsgFolderFlags.Drafts
158+ | Ci.nsMsgFolderFlags.Templates
159+ | Ci.nsMsgFolderFlags.Queue;
160
161-const DATE_IN_SECONDS_KEY = "date-in-seconds";
162-const MESSAGE_URL_KEY = "message-url";
163-const SHELL_EXECUTABLES = ["thunderbird.sh", "thunderbird"];
164-const USER_SHARE_APPLICATIONS = "/usr/share/applications/"
165+const FOLDER_URL_KEY = "url";
166+const SHELL_EXECUTABLES = ["thunderbird", "thunderbird-bin"];
167+const USER_SHARE_APPLICATIONS = "/usr/share/applications/";
168+const SYSTEM_LAUNCHER_ENTRIES = "/usr/share/indicators/messages/applications/";
169+const USER_LAUNCHER_ENTRIES = ".config/indicators/messages/applications/";
170+const USER_BLACKLIST_ENTRIES = ".config/indicators/messages/applications-blacklist/";
171+const MAX_INDICATORS = 6;
172+const ADDON_ID = "messagingmenu@mozilla.com";
173+const PREF_ROOT = "extensions.messagingmenu.";
174+const PREF_INCLUDE_NEWSGROUPS = "includeNewsgroups";
175+const PREF_INCLUDE_RSS = "includeRSS";
176+const PREF_ENABLED = "enabled";
177+const PREF_USER_LAUNCHER_PATH = "userLauncherPath";
178+const PREF_USER_LAUNCHER_MTIME = "userLauncherMTime";
179+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
180
181 var open3PaneCallback;
182 var contactsCallback;
183@@ -90,23 +115,37 @@
184 }
185
186 var openAndFocusAddressBook = function MM_openAndFocusAddressBook(aInstance, aTimestamp, aUserData) {
187+ LOG("Opening addressbook");
188 injectTimestamp(aTimestamp);
189 MessagingMenu.tbProc.run(false, ['-addressbook'], 1);
190 }
191
192 var openAndFocusComposer = function MM_openAndFocusComposer(aInstance, aTimestamp, aUserData) {
193+ LOG("Opening composer");
194 injectTimestamp(aTimestamp);
195 MessagingMenu.tbProc.run(false, ['-compose'], 1);
196 }
197
198 var onClickIndicator = function MM_onClickIndicator(aInstance, aTimestamp, aUserData) {
199 let indicator = LibIndicate.IndicatorPtrCast(aInstance);
200+ let folderURL = LibIndicate.IndicatorGetProperty(indicator, FOLDER_URL_KEY).readString();
201+ LOG("Received click event on indicator for folder " + folderURL);
202+
203+ let indicatorEntry = MessagingMenu.mIndicators[folderURL];
204+ if (!indicatorEntry) {
205+ WARN("No indicator for folder " + folderURL);
206+ return;
207+ }
208+
209 // Hide the indicator
210- MessagingMenu.hideIndicator(indicator);
211- let url = LibIndicate.IndicatorGetProperty(indicator, MESSAGE_URL_KEY);
212- var msg = MessagingMenu.messenger.msgHdrFromURI(url.readString());
213- if(!msg)
214+ MessagingMenu.hideIndicator(indicatorEntry);
215+
216+ var msg = MessagingMenu.messenger.msgHdrFromURI(indicatorEntry.messageURL);
217+ if(!msg) {
218+ WARN("Invalid message URI " + indicatorEntry.messageURL);
219 return;
220+ }
221+
222 // Focus 3pane
223 openAndFocus3Pane(aInstance, aTimestamp, aUserData);
224 gWindow.document.getElementById("tabmail").switchToTab(0);
225@@ -114,9 +153,237 @@
226 gWindow.gFolderDisplay.selectMessage(msg);
227 }
228
229+function hasMultipleAccounts() {
230+ let count = 0;
231+ // We don't want to just call Count() on the account nsISupportsArray, as we
232+ // want to filter out accounts with "none" as the incoming server type
233+ // (eg, for Local Folders)
234+ for (let account in fixIterator(MailServices.accounts.accounts, Ci.nsIMsgAccount)) {
235+ if (account.incomingServer.type != "none") {
236+ count++
237+ }
238+ }
239+
240+ return count > 1;
241+}
242+
243+// Helper class to wrap a nsIPrefBranch and allow the caller
244+// to specify default values to be used where the pref doesn't exist
245+function Prefs(aBranch) {
246+ this.branch = aBranch;
247+}
248+
249+Prefs.prototype = {
250+ getBoolPref: function P_getBoolPref(aName, aDefaultValue) {
251+ try {
252+ return this.branch.getBoolPref(aName);
253+ } catch(e) {
254+ return aDefaultValue;
255+ }
256+ },
257+
258+ setCharPref: function P_setCharPref(aName, aValue) {
259+ this.branch.setCharPref(aName, aValue);
260+ },
261+
262+ getCharPref: function P_getCharPref(aName, aDefaultValue) {
263+ try {
264+ return this.branch.getCharPref(aName);
265+ } catch(e) {
266+ return aDefaultValue;
267+ }
268+ },
269+
270+ setIntPrefAsChar: function P_setIntPrefAsChar(aName, aValue) {
271+ this.setCharPref(aName, aValue.toString());
272+ },
273+
274+ getIntPrefFromChar: function P_getIntPrefFromChar(aName, aDefaultValue) {
275+ return parseInt(this.getCharPref(aName, aDefaultValue.toString()));
276+ },
277+
278+ clearUserPref: function P_clearUserPref(aName) {
279+ this.branch.clearUserPref(aName);
280+ },
281+
282+ addObserver: function P_addObserver(aDomain, aObserver, aHoldWeak) {
283+ this.branch.QueryInterface(Ci.nsIPrefBranch2)
284+ .addObserver(aDomain, aObserver, aHoldWeak);
285+ },
286+
287+ removeObserver: function P_removeObserver(aDomain, aObserver) {
288+ this.branch.QueryInterface(Ci.nsIPrefBranch2)
289+ .removeObserver(aDomain, aObserver);
290+ }
291+};
292+
293+// Small helper class which takes a directory containing messaging menu
294+// launcher entries and tells the listener whether one of them is ours
295+function LauncherEntryFinder(aDir, aDesktopFile, aListener, aMethod) {
296+ LOG("Searching for launcher entry for " + aDesktopFile + " in " + aDir.path);
297+ if (!aDir.exists() || !aDir.isDirectory()) {
298+ WARN(aDir.path + " does not exist or is not a directory");
299+ aListener[method](aDir, null);
300+ }
301+
302+ this.listener = aListener;
303+ this.desktopFile = aDesktopFile;
304+ this.entries = aDir.directoryEntries;
305+ this.method = aMethod;
306+ this.dir = aDir;
307+
308+ this.processNextEntry();
309+}
310+
311+LauncherEntryFinder.prototype = {
312+ processNextEntry: function MMEF_processNextEntry() {
313+ if (this.entries.hasMoreElements()) {
314+ var entry = this.entries.getNext().QueryInterface(Ci.nsIFile);
315+ if (!entry.isFile()) {
316+ this.processNextEntry();
317+ }
318+ var self = this;
319+ NetUtil.asyncFetch(entry, function(inputStream, status) {
320+ let data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
321+ if (data.replace(/\n$/,"") == self.desktopFile) {
322+ LOG("Found launcher entry " + entry.path);
323+ self.listener[self.method](self.dir, entry);
324+ } else {
325+ self.processNextEntry();
326+ }
327+ });
328+ } else {
329+ LOG("No launcher entry found");
330+ this.listener[this.method](this.dir, null);
331+ }
332+ }
333+};
334+
335+function MMIndicatorEntry (aFolder) {
336+ LOG("Creating indicator for folder " + aFolder.folderURL);
337+ this.folder = aFolder;
338+ this.indicator = LibIndicate.IndicatorNew();
339+ if (hasMultipleAccounts()) {
340+ this.label = aFolder.prettiestName +
341+ " (" + aFolder.server.prettyName + ")";
342+ } else {
343+ this.label = aFolder.prettiestName;
344+ }
345+ this.unreadCount = 0;
346+ this.dateInSeconds = 0;
347+ this.cancelAttention();
348+ this.hide();
349+
350+ LibGObject.g_signal_connect(this.indicator, "user-display",
351+ clickIndicatorCallback, null);
352+
353+ LibIndicate.IndicatorSetProperty(this.indicator,
354+ FOLDER_URL_KEY,
355+ aFolder.folderURL);
356+
357+ Services.prefs.addObserver("mail.accountmanager.accounts",
358+ this, false);
359+}
360+
361+MMIndicatorEntry.prototype = {
362+ get indicator() {
363+ if (this._indicator) {
364+ return this._indicator;
365+ }
366+
367+ throw "No IndicateIndicator. Have we been destroyed?";
368+ },
369+
370+ set indicator(aIndicator) {
371+ this._indicator = aIndicator;
372+ },
373+
374+ getAttention: function MMIE_getAttention() {
375+ LOG("Requesting attention for folder " + this.folder.folderURL);
376+ LibIndicate.IndicatorSetProperty(this.indicator,
377+ LibIndicate.INDICATOR_MESSAGES_PROP_ATTENTION,
378+ "true");
379+ this._attention = true;
380+ },
381+
382+ cancelAttention: function MMIE_cancelAttention() {
383+ LOG("Cancelling attention for folder " + this.folder.folderURL);
384+ LibIndicate.IndicatorSetProperty(this.indicator,
385+ LibIndicate.INDICATOR_MESSAGES_PROP_ATTENTION,
386+ "false");
387+ this._attention = false;
388+ },
389+
390+ hasAttention: function MMIE_hasAttention() {
391+ return this._attention;
392+ },
393+
394+ set label(aName) {
395+ LOG("Setting label for folder " + this.folder.folderURL + " to " + aName);
396+ LibIndicate.IndicatorSetProperty(this.indicator,
397+ LibIndicate.INDICATOR_MESSAGES_PROP_NAME,
398+ aName);
399+ },
400+
401+ set unreadCount(aCount) {
402+ LOG("Setting unread count for folder " + this.folder.folderURL +
403+ " to " + aCount.toString());
404+ LibIndicate.IndicatorSetProperty(this.indicator,
405+ LibIndicate.INDICATOR_MESSAGES_PROP_COUNT,
406+ aCount.toString());
407+ this._unreadCount = aCount;
408+ },
409+
410+ get unreadCount() {
411+ return this._unreadCount;
412+ },
413+
414+ show: function MMIE_show() {
415+ LOG("Showing indicator for folder " + this.folder.folderURL);
416+ LibIndicate.IndicatorShow(this.indicator);
417+ },
418+
419+ hide: function MMIE_hide() {
420+ LOG("Hiding indicator for folder " + this.folder.folderURL);
421+ LibIndicate.IndicatorHide(this.indicator);
422+ },
423+
424+ get visible() {
425+ return LibIndicate.IndicatorIsVisible(this.indicator) != 0;
426+ },
427+
428+ destroy: function MMIE_destroy() {
429+ LOG("Destroying indicator for folder " + this.folder.folderURL);
430+ LibGObject.g_object_unref(this.indicator);
431+ this.indicator = null;
432+
433+ Services.prefs.removeObserver("mail.accountmanager.accounts",
434+ this);
435+ },
436+
437+ observe: function MMIE_observe(subject, topic, data) {
438+ // An account was added or removed. Note that this observer fires
439+ // before nsIMsgAccountManager is up-to-date, so we add the next
440+ // bit to the event loop
441+ LOG("Account settings updated. Updating label for folder " +
442+ this.folder.folderURL);
443+ var self = this;
444+ gWindow.setTimeout(function() {
445+ if (hasMultipleAccounts()) {
446+ self.label = self.folder.prettiestName +
447+ " (" + self.folder.server.prettyName + ")";
448+ } else {
449+ self.label = self.folder.prettiestName;
450+ }
451+ }, 0);
452+ }
453+};
454+
455 var MessagingMenu = {
456 initialized: false,
457+ enabled: false,
458 mIndicators: {},
459+ visibleIndicators: 0,
460
461 get messenger() {
462 if (this._messenger)
463@@ -163,10 +430,32 @@
464 get prefs() {
465 if (this._prefs)
466 return this._prefs;
467- this._prefs = Services.prefs.getBranch("extensions.messagingmenu.");
468+ this._prefs = new Prefs(Services.prefs.getBranch(PREF_ROOT));
469 return this._prefs;
470 },
471
472+ get shortcutVisibility() {
473+ if (this._shortcutVisibility == null) {
474+ this._shortcutVisibility = false;
475+ }
476+
477+ return this._shortcutVisibility ? 1 : 0;
478+ },
479+
480+ set shortcutVisibility(aVis) {
481+ this._shortcutVisibility = aVis;
482+
483+ if (this.contactsMi) {
484+ LibDbusmenu.MenuitemPropertySetBool(this.contactsMi, "visible",
485+ this.shortcutVisibility);
486+ }
487+
488+ if (this.composeMi) {
489+ LibDbusmenu.MenuitemPropertySetBool(this.composeMi, "visible",
490+ this.shortcutVisibility);
491+ }
492+ },
493+
494 get indicateServer() {
495 if (this._indicateServer)
496 return this._indicateServer;
497@@ -181,24 +470,35 @@
498 LibGObject.g_signal_connect(indicateServer, "server-display",
499 open3PaneCallback, null);
500
501+ let bundle = Services.strings.createBundle(
502+ "chrome://messagingmenu/locale/messagingmenu.properties");
503+
504 let server = LibDbusmenu.ServerNew("/messaging/commands");
505 let root = LibDbusmenu.MenuitemNew();
506- let mi = LibDbusmenu.MenuitemNew();
507- LibDbusmenu.MenuitemPropertySet(mi, "label", "Contacts");
508+ this.contactsMi = LibDbusmenu.MenuitemNew();
509+ LibDbusmenu.MenuitemPropertySet(this.contactsMi, "label",
510+ bundle.GetStringFromName("contacts"));
511+ LibDbusmenu.MenuitemPropertySetBool(this.contactsMi, "visible",
512+ this.shortcutVisibility);
513 contactsCallback = LibGObject.GCallbackFunction(openAndFocusAddressBook);
514
515- LibGObject.g_signal_connect(mi, LibDbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
516+ LibGObject.g_signal_connect(this.contactsMi,
517+ LibDbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
518 contactsCallback, null);
519- LibDbusmenu.MenuitemChildAppend(root, mi);
520+ LibDbusmenu.MenuitemChildAppend(root, this.contactsMi);
521
522- mi = LibDbusmenu.MenuitemNew();
523- LibDbusmenu.MenuitemPropertySet(mi, "label", "Compose a New Message");
524+ this.composeMi = LibDbusmenu.MenuitemNew();
525+ LibDbusmenu.MenuitemPropertySet(this.composeMi, "label",
526+ bundle.GetStringFromName("composeNewMessage"));
527+ LibDbusmenu.MenuitemPropertySetBool(this.composeMi, "visible",
528+ this.shortcutVisibility);
529 composerCallback = LibGObject.GCallbackFunction(openAndFocusComposer);
530
531- LibGObject.g_signal_connect(mi, LibDbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
532+ LibGObject.g_signal_connect(this.composeMi,
533+ LibDbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
534 composerCallback, null);
535
536- LibDbusmenu.MenuitemChildAppend(root, mi);
537+ LibDbusmenu.MenuitemChildAppend(root, this.composeMi);
538 LibDbusmenu.ServerSetRoot(server, root);
539 LibIndicate.ServerSetMenu(indicateServer, server);
540 LibIndicate.ServerShow(indicateServer);
541@@ -209,36 +509,173 @@
542 return this._indicateServer;
543 },
544
545+ sysLauncherFindResult: function MM_sysLauncherFindResult(aDir, aFound) {
546+ if (!aFound) {
547+ // There is no system-provided static launcher entry for us in the
548+ // messaging menu. Show the "Compose" and "Contacts" items, and then
549+ // add a static launcher entry if there isn't one already
550+ this.shortcutVisibility = true;
551+
552+ let userLauncherEntriesDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
553+ userLauncherEntriesDir.appendRelativePath(USER_LAUNCHER_ENTRIES);
554+ new LauncherEntryFinder(userLauncherEntriesDir, this.desktopFile, this,
555+ "createLauncherEntryIfNoneExists");
556+ }
557+ },
558+
559+ createLauncherEntryIfNoneExists: function MM_createLauncherEntryIfNoneExists(
560+ aDir, aFound) {
561+ if (!aFound) {
562+ let entry = aDir;
563+ entry.append(Services.appinfo.name.toLowerCase());
564+ let ostream = FileUtils.openSafeFileOutputStream(entry,
565+ FileUtils.MODE_WRONLY |
566+ FileUtils.MODE_CREATE |
567+ FileUtils.MODE_TRUNCATE);
568+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
569+ .createInstance(Ci.nsIScriptableUnicodeConverter);
570+ converter.charset = "UTF-8";
571+ let istream = converter.convertToInputStream(this.desktopFile);
572+ var self = this;
573+ NetUtil.asyncCopy(istream, ostream, function() {
574+ self.prefs.setCharPref(PREF_USER_LAUNCHER_PATH, entry.path);
575+ self.prefs.setIntPrefAsChar(PREF_USER_LAUNCHER_MTIME,
576+ entry.lastModifiedTime);
577+ });
578+ }
579+ },
580+
581 init: function MM_init(aWindow) {
582 if (this.initialized)
583 return;
584
585+ LOG("Initializing MessagingMenu");
586+
587 gWindow = aWindow;
588
589+ // Check if we have a static launcher entry in the messaging menu. If we
590+ // don't, then we should add one and also display "Contacts" and "Compose"
591+ // menu items. If there is one, then it was probably added by the Thunderbird
592+ // package. We don't need to create one or show the extra menu items in that case
593+ let sysLauncherEntriesDir = Cc["@mozilla.org/file/local;1"]
594+ .createInstance(Ci.nsILocalFile);
595+ sysLauncherEntriesDir.initWithPath(SYSTEM_LAUNCHER_ENTRIES);
596+ new LauncherEntryFinder(sysLauncherEntriesDir, this.desktopFile, this,
597+ "sysLauncherFindResult");
598+
599+ AddonManager.addAddonListener(this);
600+ this.prefs.addObserver("", this, false);
601+ Services.obs.addObserver(this, "xpcom-will-shutdown", false);
602+
603+ if (this.prefs.getBoolPref(PREF_ENABLED, true)) {
604+ this.enable();
605+ } else {
606+ this.disableAndHide();
607+ }
608+
609+ this.initialized = true;
610+ },
611+
612+ removeLauncherEntry: function MM_removeLauncherEntry(aDir, aFound) {
613+ if (aFound) {
614+ LOG("Removing launcher entry " + aFound.path);
615+ aFound.remove(false);
616+ }
617+ },
618+
619+ enable: function MM_enable() {
620+ if (this.enabled) {
621+ WARN("Trying to enable more than once");
622+ return;
623+ }
624+
625+ LOG("Enabling messaging indicator");
626+
627 if (!this.indicateServer) {
628 Cu.reportError("Could not construct the Messaging Menu server.");
629 return;
630 }
631
632+ let userBlacklistDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
633+ userBlacklistDir.appendRelativePath(USER_BLACKLIST_ENTRIES);
634+ new LauncherEntryFinder(userBlacklistDir, this.desktopFile, this,
635+ "removeLauncherEntry");
636+
637 let notificationFlags = Ci.nsIFolderListener.added
638 | Ci.nsIFolderListener.propertyFlagChanged;
639 MailServices.mailSession.AddFolderListener(this, notificationFlags);
640
641- Services.obs.addObserver(this, "xpcom-will-shutdown", false);
642- this.initialized = true;
643- },
644-
645- shutdown: function MM_shutdown() {
646+ this.enabled = true;
647+ },
648+
649+ disableAndHide: function MM_disableAndHide() {
650+ LOG("Hiding messaging indicator");
651+ let userBlacklistDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
652+ userBlacklistDir.appendRelativePath(USER_BLACKLIST_ENTRIES);
653+ new LauncherEntryFinder(userBlacklistDir, this.desktopFile, this,
654+ "createLauncherEntryIfNoneExists");
655+
656+ this.disable();
657+ },
658+
659+ cleanup: function MM_cleanup() {
660+ // We're being uninstalled or disabled. If we created a launcher
661+ // entry in the messaging menu, make sure we clean it up
662+ let userLauncherEntry = this.prefs.getCharPref(PREF_USER_LAUNCHER_PATH,
663+ null);
664+ let userLauncherEntryMTime =
665+ this.prefs.getIntPrefFromChar(PREF_USER_LAUNCHER_MTIME, 0);
666+ this.prefs.clearUserPref(PREF_USER_LAUNCHER_PATH);
667+ this.prefs.clearUserPref(PREF_USER_LAUNCHER_MTIME);
668+ if (userLauncherEntry) {
669+ let userLauncherEntryFile = Cc["@mozilla.org/file/local;1"]
670+ .createInstance(Ci.nsILocalFile);
671+ userLauncherEntryFile.initWithPath(userLauncherEntry);
672+ if (userLauncherEntryFile.exists() &&
673+ userLauncherEntryFile.isFile() &&
674+ userLauncherEntryFile.lastModifiedTime == userLauncherEntryMTime) {
675+ LOG("Removing launcher entry at " + userLauncherEntry);
676+ userLauncherEntryFile.remove(false);
677+ }
678+ }
679+ },
680+
681+ disable: function MM_disable() {
682+ if (!this.enabled)
683+ return;
684+
685+ LOG("Disabling messaging indicator");
686+
687+ MailServices.mailSession.RemoveFolderListener(this);
688+
689 // Remove references for any leftover indicators
690- for each (key in this.mIndicators)
691- LibGObject.g_object_unref(this.mIndicators[key]);
692-
693+ for (let key in this.mIndicators)
694+ this.mIndicators[key].destroy();
695 this.mIndicators = {};
696+ this.visibleIndicators = 0;
697
698 if (this._indicateServer)
699 LibGObject.g_object_unref(this._indicateServer);
700-
701 this._indicateServer = null;
702+
703+ this.enabled = false;
704+ },
705+
706+ shutdown: function MM_shutdown() {
707+ if (!this.initialized) {
708+ WARN("Calling shutdown before we are initialized");
709+ return;
710+ }
711+
712+ LOG("Shutting down");
713+
714+ this.disable();
715+
716+ AddonManager.removeAddonListener(this);
717+ Services.prefs.removeObserver(this, "xpcom-will-shutdown");
718+ this.prefs.removeObserver("", this);
719+
720+ this.initialized = false;
721 },
722
723 /* Given a particular message header, determines whether or not
724@@ -247,111 +684,95 @@
725 * @param header An nsIMsgDBHdr for a message.
726 */
727 worthIndication: function MM_worthIndication(header) {
728+ var folder = header.folder;
729+ LOG("Checking if message " + header.folder.getUriForMsg(header) +
730+ " in " + folder.folderURL + " is worth indicating");
731+
732 // Filter out the new messages that don't count...like junk.
733 var junkScore = header.getStringProperty("junkscore");
734-
735 if ((junkScore != "") && (junkScore != "0")) {
736 // We're junk. Not worth indicating.
737+ LOG("Rejecting message with junkscore = " + junkScore);
738 return false;
739 }
740
741- var folder = header.folder;
742-
743- if (this.prefs.getBoolPref("includeMail")) {
744- if (folder.flags & Ci.nsMsgFolderFlags.Inbox) {
745- return true;
746- }
747- }
748-
749 if (folder.flags & FLDR_UNINTERESTING) {
750- return false;
751- }
752-
753+ LOG("Rejecting message with flags = " + folder.flags.toString());
754+ return false;
755+ }
756+
757+ if (header.isRead) {
758+ // The item has already been read
759+ LOG("Rejecting message which has already been read");
760+ return false;
761+ }
762+
763+ if (folder.flags & Ci.nsMsgFolderFlags.Mail) {
764+ LOG("Accepting mail message");
765+ return true;
766+ }
767
768 // Are we checking news groups?
769- if (this.prefs.getBoolPref("includeNewsgroups")) {
770+ if (this.prefs.getBoolPref(PREF_INCLUDE_NEWSGROUPS, true)) {
771 if (folder.flags & Ci.nsMsgFolderFlags.Newsgroup) {
772+ LOG("Accepting newsgroup message");
773 return true;
774 }
775 }
776
777- if (this.prefs.getBoolPref("includeRSS")) {
778+ if (this.prefs.getBoolPref(PREF_INCLUDE_RSS, true)) {
779 if (folder.server.type == "rss") {
780+ LOG("Accepting message from RSS feed");
781 return true;
782 }
783 }
784
785+ LOG("Rejecting message with flags = " + folder.flags.toString());
786 return false;
787 },
788
789- /* Sends a request to Indicate to uMessagingMenuService.
790+ /* Given a message header, displays an indicator for it's folder
791+ * and requests attention
792 *
793 * @param aItemHeader A nsIMsgDBHdr for the message that we're
794 * trying to show in the Messaging Menu.
795 */
796- sendIndication: function MM_sendIndication(aItemHeader) {
797- var itemFolder = aItemHeader.folder;
798- // TODO: We want to only display the root folder's name if it disambiguates common
799- // subfolder names.
800- itemFolder.updateSummaryTotals(true);
801- let label = itemFolder.prettiestName + " ("
802- + itemFolder.rootFolder.prettiestName + ")";
803- let count = itemFolder.getNumUnread(true);
804- let folderUrl = itemFolder.folderURL;
805- let messageUrl = itemFolder.getUriForMsg(aItemHeader);
806-
807- this.doIndication(folderUrl, label, messageUrl, count,
808- aItemHeader.dateInSeconds);
809- },
810-
811- doIndication: function MM_doIndication(aFolderURL, aFolderName,
812- aMessageURL, count,
813- aDateInSeconds) {
814-
815- var indicator;
816- if (!this.mIndicators[aFolderURL]) {
817- indicator = LibIndicate.IndicatorNew();
818- LibIndicate.IndicatorSetProperty(indicator,
819- LibIndicate.INDICATOR_MESSAGES_PROP_NAME, aFolderName);
820- LibIndicate.IndicatorSetProperty(indicator, DATE_IN_SECONDS_KEY, "0")
821- LibIndicate.IndicatorSetProperty(indicator,
822- LibIndicate.INDICATOR_MESSAGES_PROP_ATTENTION, "false");
823-
824- LibGObject.g_signal_connect(indicator, "user-display",
825- clickIndicatorCallback, null);
826- this.mIndicators[aFolderURL] = indicator;
827- } else {
828- indicator = this.mIndicators[aFolderURL];
829- }
830-
831- let curDateInSeconds = LibIndicate.IndicatorGetProperty(indicator, DATE_IN_SECONDS_KEY);
832- curDateInSeconds = curDateInSeconds.readString();
833- let gettingAttention = LibIndicate
834- .IndicatorGetProperty(indicator,
835- LibIndicate.INDICATOR_MESSAGES_PROP_ATTENTION);
836- gettingAttention = gettingAttention.readString();
837-
838- if (gettingAttention != "true" || (curDateInSeconds > aDateInSeconds)) {
839- LibIndicate.IndicatorSetProperty(indicator,
840- MESSAGE_URL_KEY,
841- aMessageURL);
842- LibIndicate.IndicatorSetProperty(indicator,
843- DATE_IN_SECONDS_KEY,
844- aDateInSeconds.toString());
845- }
846-
847- LibIndicate.IndicatorSetProperty(indicator,
848- LibIndicate.INDICATOR_MESSAGES_PROP_COUNT, count.toString());
849-
850- LibIndicate.IndicatorSetProperty(indicator,
851- LibIndicate.INDICATOR_MESSAGES_PROP_ATTENTION, "true");
852-
853- LibIndicate.IndicatorShow(indicator);
854- },
855-
856- stopIndication: function MM_stopIndication(aItemHeader) {
857+ doIndication: function MM_doIndication(aItemHeader) {
858 let itemFolder = aItemHeader.folder;
859 let folderURL = itemFolder.folderURL;
860+ LOG("Doing indication for folder " + folderURL);
861+ if (!this.mIndicators[folderURL]) {
862+ // Create an indicator for this folder if one doesn't already exist
863+ this.mIndicators[folderURL] = new MMIndicatorEntry(itemFolder);
864+ }
865+
866+ let indicator = this.mIndicators[folderURL];
867+ indicator.unreadCount += 1;
868+
869+ LOG("Current indicator dateInSeconds = " + indicator.dateInSeconds.toString());
870+ LOG("Message item dateInSeconds = " + aItemHeader.dateInSeconds.toString());
871+ if (indicator.hasAttention()) {
872+ LOG("Indicator for folder already has attention");
873+ }
874+
875+ if (!indicator.hasAttention() || (indicator.dateInSeconds > aItemHeader.dateInSeconds)) {
876+ indicator.messageURL = itemFolder.getUriForMsg(aItemHeader);
877+ indicator.dateInSeconds = aItemHeader.dateInSeconds;
878+ }
879+
880+ indicator.getAttention();
881+
882+ // Don't show more than MAX_INDICATORS indicators
883+ if (!indicator.visible && (this.visibleIndicators < MAX_INDICATORS)) {
884+ this.visibleIndicators++;
885+ indicator.show();
886+ LOG("There are now " + this.visibleIndicators.toString() + " visible indicators");
887+ }
888+ },
889+
890+ stopIndication: function MM_stopIndication(aItemHeader) {
891+ let folderURL = aItemHeader.folder.folderURL;
892+ LOG("Stopping indication for folder " + folderURL);
893
894 if (!this.mIndicators[folderURL])
895 return;
896@@ -359,10 +780,41 @@
897 this.hideIndicator(this.mIndicators[folderURL]);
898 },
899
900- hideIndicator: function MM_hideIndicator(indicator) {
901- LibIndicate.IndicatorHide(indicator);
902- LibIndicate.IndicatorSetProperty(indicator,
903- LibIndicate.INDICATOR_MESSAGES_PROP_ATTENTION, "false");
904+ hideIndicator: function MM_hideIndicator(aIndicator) {
905+ if (aIndicator.visible) {
906+ aIndicator.hide();
907+ if (this.visibleIndicators > 0) {
908+ this.visibleIndicators--;
909+ LOG("There are now " + this.visibleIndicators.toString() + " visible indicators");
910+ } else {
911+ ERROR("Invalid MessagingMenu.visibleIndicators count: "
912+ + this.visibleIndicators.toString());
913+ }
914+ }
915+ aIndicator.cancelAttention();
916+ aIndicator.unreadCount = 0;
917+
918+ if (this.visibleIndicators >= MAX_INDICATORS) {
919+ return;
920+ }
921+
922+ // Now see if there are any pending indicators to be shown
923+ // Show the one which has been waiting the longest
924+ let pendingIndicator = null;
925+ for (let url in this.mIndicators) {
926+ let indicator = this.mIndicators[url];
927+ if (indicator.hasAttention() && !indicator.visible &&
928+ (!pendingIndicator ||
929+ (indicator.dateInSeconds < pendingIndicator.dateInSeconds))) {
930+ pendingIndicator = indicator;
931+ }
932+ }
933+
934+ if (pendingIndicator) {
935+ pendingIndicator.show();
936+ this.visibleIndicators++;
937+ LOG("There are now " + this.visibleIndicators.toString() + " visible indicators");
938+ }
939 },
940
941 /* Observes when items are added to folders, and when
942@@ -370,25 +822,60 @@
943 * sent by uMessagingMenuService for opening messages based
944 * on an Indicator that was clicked.
945 */
946- OnItemAdded: function MM_OnItemAdded(parentItem, item) {
947- let header = item.QueryInterface(Ci.nsIMsgDBHdr);
948- if(this.worthIndication(header)) {
949- this.sendIndication(header);
950- }
951- },
952-
953- OnItemPropertyFlagChanged: function MM_OnItemPropertyFlagChanged(item, property, oldFlag, newFlag) {
954- if((oldFlag & Ci.nsMsgMessageFlags.New)
955- && !(newFlag & Ci.nsMsgMessageFlags.New)) {
956- let header = item.QueryInterface(Ci.nsIMsgDBHdr);
957- this.stopIndication(header);
958- }
959- },
960-
961- observe: function(aSubject, aTopic, aData) {
962- if (aTopic == "xpcom-will-shutdown") {
963- this.shutdown();
964- }
965- }
966+ OnItemAdded: function MM_OnItemAdded(parentItem, item) {
967+ let header = item.QueryInterface(Ci.nsIMsgDBHdr);
968+ if(this.worthIndication(header)) {
969+ this.doIndication(header);
970+ }
971+ },
972+
973+ OnItemPropertyFlagChanged: function MM_OnItemPropertyFlagChanged(item, property, oldFlag, newFlag) {
974+ if((oldFlag & Ci.nsMsgMessageFlags.New)
975+ && !(newFlag & Ci.nsMsgMessageFlags.New)) {
976+ let header = item.QueryInterface(Ci.nsIMsgDBHdr);
977+ this.stopIndication(header);
978+ }
979+ },
980+
981+ observe: function(aSubject, aTopic, aData) {
982+ if (aTopic == "xpcom-will-shutdown") {
983+ LOG("Got shutdown notification");
984+ this.shutdown();
985+ } else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
986+ LOG("Got prefchange notification for " + aData);
987+ if (aData == PREF_ENABLED) {
988+ let prefs = new Prefs(aSubject.QueryInterface(Ci.nsIPrefBranch));
989+ let enabled = prefs.getBoolPref(aData, true);
990+ if (enabled) {
991+ this.enable();
992+ } else {
993+ this.disableAndHide();
994+ }
995+ }
996+ } else {
997+ WARN("Observer notification not intended for us: " + aTopic);
998+ }
999+ },
1000+
1001+ onUninstalling: function(aAddon, aNeedsRestart) {
1002+ if (aAddon.id == ADDON_ID) {
1003+ LOG("Addon is being uninstalled");
1004+ this.cleanup();
1005+ }
1006+ },
1007+
1008+ onDisabling: function(aAddon, aNeedsRestart) {
1009+ if (aAddon.id == ADDON_ID) {
1010+ LOG("Addon is being disabled");
1011+ this.disableAndHide();
1012+ }
1013+ },
1014+
1015+ onEnabling: function(aAddon, aNeedsRestart) {
1016+ if (aAddon.id == ADDON_ID) {
1017+ LOG("Addon is being enabled");
1018+ this.enable();
1019+ }
1020+ }
1021 }
1022

Subscribers

People subscribed via source and target branches

to all changes: