diff --git a/src/chrome/content/options.js b/src/chrome/content/options.js index 1b1e2ce..bde3d46 100644 --- a/src/chrome/content/options.js +++ b/src/chrome/content/options.js @@ -36,9 +36,10 @@ var firetrayUIOptions = { this.hidePrefPane("pref-pane-mail"); } - if (firetray.Handler.isChatProvided()) + if (firetray.Handler.isChatProvided()) { Cu.import("resource://firetray/FiretrayChat.jsm"); - else + this.initChatControls(); + } else this.hidePrefPane("pref-pane-chat"); this.updateWindowAndIconOptions(); @@ -195,6 +196,11 @@ var firetrayUIOptions = { this.toggleNotifications(firetray.Utils.prefService.getBoolPref("mail_notification_enabled")); }, + initChatControls: function() { + this.initChatBlinkSettings(); + this.toggleChatIcon(firetray.Utils.prefService.getBoolPref("chat_icon_enable")); + }, + initNotificationSettings: function() { document.getElementById("ui_radio_mail_notification_unread_count").value = FIRETRAY_NOTIFICATION_MESSAGE_COUNT; @@ -224,6 +230,17 @@ var firetrayUIOptions = { // this.disableMessageCountMaybe(prefMsgCountType); // done in toggleNotifications() }, + initChatBlinkSettings: function() { + document.getElementById("ui_chat_icon_blink_style_normal").value = + FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL; + document.getElementById("ui_chat_icon_blink_style_fade").value = + FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE; + + let blinkStyle = document.getElementById("ui_chat_icon_blink_style"); + let prefBlinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style"); + blinkStyle.selectedIndex = this.radioGetIndexByValue(blinkStyle, prefBlinkStyle); + }, + radioGetIndexByValue: function(radio, value) { for (let i=0, len=radio.itemCount; i + + + + accesskey="&chat_icon_enable.accesskey;" + oncommand="firetrayUIOptions.toggleChatIcon(this.checked)"/> + + + + + + + diff --git a/src/chrome/locale/en-US/options.dtd b/src/chrome/locale/en-US/options.dtd index 7048410..1154e83 100644 --- a/src/chrome/locale/en-US/options.dtd +++ b/src/chrome/locale/en-US/options.dtd @@ -90,3 +90,11 @@ + + + + + + + + diff --git a/src/defaults/preferences/prefs.js b/src/defaults/preferences/prefs.js index ec954fc..6fea825 100644 --- a/src/defaults/preferences/prefs.js +++ b/src/defaults/preferences/prefs.js @@ -22,6 +22,8 @@ pref("extensions.firetray.show_icon_on_hide", false); pref("extensions.firetray.scroll_hides", true); pref("extensions.firetray.scroll_mode", "down_hides"); pref("extensions.firetray.chat_icon_enable", true); +pref("extensions.firetray.chat_icon_blink", true); +pref("extensions.firetray.chat_icon_blink_style", 0); pref("extensions.firetray.message_count_type", 0); pref("extensions.firetray.mail_notification_enabled", true); diff --git a/src/modules/FiretrayChat.jsm b/src/modules/FiretrayChat.jsm index d1aea37..a8b7c54 100644 --- a/src/modules/FiretrayChat.jsm +++ b/src/modules/FiretrayChat.jsm @@ -15,7 +15,7 @@ let log = firetray.Logging.getLogger("firetray.Chat"); firetray.Chat = { initialized: false, observedTopics: {}, - shouldAcknowledgeConvs: { + convsToAcknowledge: { ids: {}, length: function(){return Object.keys(this.ids).length;} }, @@ -35,6 +35,9 @@ firetray.Chat = { ]); firetray.ChatStatusIcon.init(); + if (firetray.Utils.prefService.getBoolPref("chat_icon_blink") && + firetray.Chat.convsToAcknowledge.length()) + this.startGetAttention(); this.updateIcon(); this.initialized = true; @@ -44,6 +47,9 @@ firetray.Chat = { if (!this.initialized) return; log.debug("Disabling Chat"); + if (firetray.Chat.convsToAcknowledge.length()) + this.stopGetAttention(); + firetray.ChatStatusIcon.shutdown(); firetray.Utils.removeAllObservers(firetray.Chat); @@ -122,21 +128,34 @@ firetray.Chat = { startGetAttentionMaybe: function(conv) { log.debug('startGetAttentionMaybe conv.id='+conv.id); - if (this.shouldAcknowledgeConvs.ids[conv.id]) return; // multiple messages let convIsCurrentlyShown = this.isConvCurrentlyShown(conv, firetray.Handler.findActiveWindow()); log.debug("convIsCurrentlyShown="+convIsCurrentlyShown); if (convIsCurrentlyShown) return; // don't blink when conv tab already on top - this.shouldAcknowledgeConvs.ids[conv.id] = conv; - log.debug(conv.id+' added to shouldAcknowledgeConvs'); - log.debug('shouldAcknowledgeConvs.length='+this.shouldAcknowledgeConvs.length()); + log.debug("firetray.ChatStatusIcon.isBlinking="+firetray.ChatStatusIcon.isBlinking); + if (firetray.Utils.prefService.getBoolPref("chat_icon_blink") && + !firetray.ChatStatusIcon.isBlinking) + this.startGetAttention(conv); - if (this.shouldAcknowledgeConvs.length() > 1) return; // already calling attention + this.convsToAcknowledge.ids[conv.id] = conv; + log.debug(conv.id+' added to convsToAcknowledge, length='+this.convsToAcknowledge.length()); + }, - this.setUrgencyMaybe(conv); - firetray.ChatStatusIcon.startIconBlinking(); + startGetAttention: function(conv) { + log.debug("startGetAttention"); + if (conv) + this.setUrgencyMaybe(conv); + + let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style"); + log.debug("chat_icon_blink_style="+blinkStyle); + if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL) + firetray.ChatStatusIcon.startBlinking(); + else if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE) + firetray.ChatStatusIcon.startFading(); + else + throw new Error("Undefined chat icon blink style."); }, /** @@ -144,26 +163,38 @@ firetray.Chat = { */ stopGetAttentionMaybe: function(xid) { log.debug("stopGetAttentionMaybe"); - let shouldAcknowledgeConvsLength = this.shouldAcknowledgeConvs.length(); - log.debug("shouldAcknowledgeConvsLength="+shouldAcknowledgeConvsLength); - if (!shouldAcknowledgeConvsLength) return; + log.debug("convsToAcknowledgeLength="+this.convsToAcknowledge.length()); + if (!firetray.ChatStatusIcon.isBlinking) return; let selectedConv = this.getSelectedConv(xid); if (!selectedConv) return; - for (convId in this.shouldAcknowledgeConvs.ids) { + for (let convId in this.convsToAcknowledge.ids) { log.debug(convId+" == "+selectedConv.id); if (convId == selectedConv.id) { - delete this.shouldAcknowledgeConvs.ids[convId]; + delete this.convsToAcknowledge.ids[convId]; break; } } - if(this.shouldAcknowledgeConvs.length() === 0) { - log.debug("do stop icon blinking !!!"); + // don't check chat_icon_blink: stopGetAttention even if it was unset + log.debug("convsToAcknowledge.length()="+this.convsToAcknowledge.length()); + if (this.convsToAcknowledge.length() === 0) + this.stopGetAttention(xid); + }, + + stopGetAttention: function(xid) { + log.debug("do stop get attention !!!"); + if (xid) firetray.ChatStatusIcon.setUrgency(xid, false); - firetray.ChatStatusIcon.stopIconBlinking(); - } + + let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style"); + if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL) + firetray.ChatStatusIcon.stopBlinking(); + else if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE) + firetray.ChatStatusIcon.stopFading(); + else + throw new Error("Undefined chat icon blink style."); }, onSelect: function(event) { diff --git a/src/modules/FiretrayHandler.jsm b/src/modules/FiretrayHandler.jsm index b6a4c01..3e2b4a3 100644 --- a/src/modules/FiretrayHandler.jsm +++ b/src/modules/FiretrayHandler.jsm @@ -158,7 +158,7 @@ firetray.Handler = { firetray.Utils.removeAllObservers(this); - firetray.MailChatPrefListener.register(false); + firetray.MailChatPrefListener.unregister(false); firetray.PrefListener.unregister(); this.appStarted = false; @@ -232,7 +232,7 @@ firetray.Handler = { firetray.Utils.timer(FIRETRAY_DELAY_STARTUP_MILLISECONDS, Ci.nsITimer.TYPE_ONE_SHOT, function() { firetray.Handler.appStarted = true; - log.debug("*** appStarted ***"); + log.info("*** appStarted ***"); }); }, @@ -283,8 +283,8 @@ firetray.Handler = { } else { for (let winId in firetray.Handler.windows) { - firetray.ChatStatusIcon.detachOnFocusInCallback(winId); firetray.Chat.detachSelectListeners(firetray.Handler.windows[winId].chromeWin); + firetray.ChatStatusIcon.detachOnFocusInCallback(winId); } firetray.Chat.shutdown(); } @@ -481,6 +481,26 @@ firetray.PrefListener = new PrefListener( firetray.Handler.toggleChat(firetray.Handler.isChatEnabled()); break; + case 'chat_icon_blink': + if (!firetray.ChatStatusIcon.isBlinking) + return; + let startBlinking = firetray.Utils.prefService.getBoolPref('chat_icon_blink'); + if (startBlinking) { + firetray.Chat.startGetAttention(); + } else { + firetray.Chat.stopGetAttention(); + } + break; + + case 'chat_icon_blink_style': + if (!firetray.Utils.prefService.getBoolPref('chat_icon_blink') || + !firetray.ChatStatusIcon.isBlinking) + break; + + firetray.ChatStatusIcon.toggleBlinkStyle( + firetray.Utils.prefService.getIntPref("chat_icon_blink_style")); + break; + default: } }); diff --git a/src/modules/commons.js b/src/modules/commons.js index 41190bd..8d57c1d 100644 --- a/src/modules/commons.js +++ b/src/modules/commons.js @@ -15,6 +15,8 @@ var EXPORTED_SYMBOLS = "FIRETRAY_DELAY_NOWAIT_MILLISECONDS", "FIRETRAY_DELAY_PREF_CLEANING_MILLISECONDS", "FIRETRAY_MESSAGE_COUNT_TYPE_UNREAD", "FIRETRAY_MESSAGE_COUNT_TYPE_NEW", + "FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL", + "FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE", "FIRETRAY_APP_DB" ]; const Cc = Components.classes; @@ -50,6 +52,9 @@ const FIRETRAY_DELAY_STARTUP_MILLISECONDS = 500; const FIRETRAY_DELAY_NOWAIT_MILLISECONDS = 0; const FIRETRAY_DELAY_PREF_CLEANING_MILLISECONDS = 15*60*1000; +const FIRETRAY_CHAT_ICON_BLINK_STYLE_NORMAL = 0; +const FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE = 1; + const FIRETRAY_APP_DB = { firefox: { diff --git a/src/modules/ctypes/linux/gdk.jsm b/src/modules/ctypes/linux/gdk.jsm index b665499..473e92c 100644 --- a/src/modules/ctypes/linux/gdk.jsm +++ b/src/modules/ctypes/linux/gdk.jsm @@ -55,7 +55,10 @@ Cu.import("resource://firetray/ctypes/linux/x11.jsm"); function gdk_defines(lib) { this.GdkInterpType = ctypes.int; // enum - this.GDK_INTERP_NEAREST = 0; + this.GDK_INTERP_NEAREST = 0; + this.GDK_INTERP_TILES = 1; + this.GDK_INTERP_BILINEAR = 2; + this.GDK_INTERP_HYPE = 3; this.GdkFilterReturn = ctypes.int; // enum this.GDK_FILTER_CONTINUE = 0; this.GDK_FILTER_TRANSLATE = 1; diff --git a/src/modules/linux/FiretrayChatStatusIcon.jsm b/src/modules/linux/FiretrayChatStatusIcon.jsm index 6881ca6..2c2e473 100644 --- a/src/modules/linux/FiretrayChatStatusIcon.jsm +++ b/src/modules/linux/FiretrayChatStatusIcon.jsm @@ -23,6 +23,11 @@ if ("undefined" == typeof(firetray.Handler)) let log = firetray.Logging.getLogger("firetray.ChatStatusIcon"); +const ALPHA_STEP = 5; +const ALPHA_STEP_SLEEP_MILLISECONDS = 10; +const FADE_OVER_SLEEP_MILLISECONDS = 500; +const BLINK_TOGGLE_PERIOD_MILLISECONDS = 500; + firetray.ChatStatusIcon = { GTK_THEME_ICON_PATH: null, @@ -39,7 +44,11 @@ firetray.ChatStatusIcon = { })(), themedIconNameCurrent: null, signals: {'focus-in': {callback: {}, handler: {}}}, - timers: {}, + timers: {'blink': null, 'fade-step': null, 'fade-loop': null}, + events: {}, + generators: {}, + pixBuffer: {}, + get isBlinking () {return (firetray.Chat.convsToAcknowledge.length() > 0);}, init: function() { if (!firetray.Handler.inMailApp) throw "ChatStatusIcon for mail app only"; @@ -47,14 +56,16 @@ firetray.ChatStatusIcon = { this.trayIcon = gtk.gtk_status_icon_new(); this.loadThemedIcons(); - this.setIconImage(FIRETRAY_IM_STATUS_OFFLINE); + this.setIconImage(this.themedIconNameCurrent || FIRETRAY_IM_STATUS_OFFLINE); // updated in Chat anyway this.setIconTooltipDefault(); + this.initTimers(); this.initialized = true; return true; }, shutdown: function() { + this.destroyTimers(); this.destroyIcons(); this.initialized = false; }, @@ -80,6 +91,14 @@ firetray.ChatStatusIcon = { setIconImage: function(name) { this.themedIconNameCurrent = name; + + let blinkStyle = firetray.Utils.prefService.getIntPref("chat_icon_blink_style"); + if (blinkStyle === FIRETRAY_CHAT_ICON_BLINK_STYLE_FADE && + this.isBlinking) { + this.events['icon-changed'] = true; + return; + } + this.setIconImageFromGIcon(this.themedIcons[name]); }, @@ -87,24 +106,184 @@ firetray.ChatStatusIcon = { gtk.gtk_status_icon_set_from_pixbuf(this.trayIcon, null); }, - startIconBlinking: function() { // gtk_status_icon_set_blinking() deprecated + initTimers: function() { + for (let tname in this.timers) + this.timers[tname] = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + }, + + destroyTimers: function() { + for (let tname in this.timers) { + this.timers[tname].cancel(); + this.timers[tname] = null; + } + this.events = {}; + }, + + buildPixBuf: function() { + let icon_theme = gtk.gtk_icon_theme_get_for_screen(gdk.gdk_screen_get_default()); + + // get pixbuf + let arry = gobject.gchar.ptr.array()(2); + arry[0] = gobject.gchar.array()(firetray.ChatStatusIcon.themedIconNameCurrent); + arry[1] = null; + log.debug("icon name="+firetray.ChatStatusIcon.themedIconNameCurrent+", theme="+icon_theme+", arry="+arry); + let icon_info = gtk.gtk_icon_theme_choose_icon(icon_theme, arry, 22, gtk.GTK_ICON_LOOKUP_FORCE_SIZE); + + // create pixbuf + let pixbuf = gdk.gdk_pixbuf_copy(gtk.gtk_icon_info_load_icon(icon_info, null)); + gtk.gtk_icon_info_free(icon_info); // gobject.g_object_unref(icon_info) in 3.8 + + // checks + if (gdk.gdk_pixbuf_get_colorspace(pixbuf) != gdk.GDK_COLORSPACE_RGB) + log.error("wrong colorspace for pixbuf"); + if (gdk.gdk_pixbuf_get_bits_per_sample(pixbuf) != 8) + log.error("wrong bits_per_sample for pixbuf"); + if (!gdk.gdk_pixbuf_get_has_alpha(pixbuf)) + log.error("pixbuf doesn't have alpha"); + let n_channels = gdk.gdk_pixbuf_get_n_channels(pixbuf); + if (n_channels != 4) + log.error("wrong nb of channels for pixbuf"); + + // init transform + let width = gdk.gdk_pixbuf_get_width(pixbuf); + let height = gdk.gdk_pixbuf_get_height(pixbuf); + log.debug("width="+width+", height="+height); + let length = width*height*n_channels; + let pixels = ctypes.cast(gdk.gdk_pixbuf_get_pixels(pixbuf), + gobject.guchar.array(length).ptr); + log.debug("pixels="+pixels); + + // backup alpha for later fade-in + let buffer = new ArrayBuffer(width*height); + let alpha_bak = new Uint8Array(buffer); + for (let i=3; i0; a-=ALPHA_STEP) { + for(let i=3; i0) + pixbuf.pixels.contents[i] -= ALPHA_STEP; + gtk.gtk_status_icon_set_from_pixbuf(firetray.ChatStatusIcon.trayIcon, pixbuf.pixbuf); + yield true; + } + + for (let a=255; a>0; a-=ALPHA_STEP) { + for(let i=3; i chatHandler.showCurrentConversation() + // TODO: onclick/activate -> chatHandler.showCurrentConversation() }; // firetray.ChatStatusIcon diff --git a/src/modules/linux/FiretrayStatusIcon.jsm b/src/modules/linux/FiretrayStatusIcon.jsm index 87d9ea9..5c13c97 100644 --- a/src/modules/linux/FiretrayStatusIcon.jsm +++ b/src/modules/linux/FiretrayStatusIcon.jsm @@ -328,9 +328,10 @@ firetray.Handler.setIconText = function(text, color) { // FIXME: function too lo gobject.g_object_unref(buf); // merge the rendered text on top - gdk.gdk_pixbuf_composite(bufAlpha,dest,0,0,w,h,0,0,1,1,gdk.GDK_INTERP_NEAREST,255); + gdk.gdk_pixbuf_composite(bufAlpha,dest,0,0,w,h,0,0,1,1,gdk.GDK_INTERP_BILINEAR,255); gobject.g_object_unref(bufAlpha); + log.info("gtk_status_icon_set_from_pixbuf="+dest); gtk.gtk_status_icon_set_from_pixbuf(firetray.StatusIcon.trayIcon, dest); } catch (x) { log.error(x); diff --git a/src/modules/linux/FiretrayWindow.jsm b/src/modules/linux/FiretrayWindow.jsm index eae4f0c..7230474 100644 --- a/src/modules/linux/FiretrayWindow.jsm +++ b/src/modules/linux/FiretrayWindow.jsm @@ -610,7 +610,7 @@ firetray.Window = { if (visibilityRate < 1) firetray.Handler.showAllWindows(); - for(var key in firetray.Handler.windows); + for(var key in firetray.Handler.windows); // FIXME: this is not the proper way for finding the last registered window ! firetray.Window.activate(key); } diff --git a/src/modules/logging.jsm b/src/modules/logging.jsm index 63b02ec..a967c6e 100644 --- a/src/modules/logging.jsm +++ b/src/modules/logging.jsm @@ -54,7 +54,7 @@ firetray.Logging = { if (this.initialized) return; ["resource://services-common/log4moz.js", // FF - "resource:///modules/gloda/log4moz.js", // TB + "resource:///app/modules/gloda/log4moz.js", // TB "resource://firetray/log4moz.js"] // default .forEach(function(file){ try {Cu.import(file);} catch(x) {}