diff --git a/src/chrome/content/overlay.js b/src/chrome/content/overlay.js index 934f86d..f93652e 100644 --- a/src/chrome/content/overlay.js +++ b/src/chrome/content/overlay.js @@ -8,20 +8,10 @@ if ("undefined" == typeof(Ci)) var Ci = Components.interfaces; if ("undefined" == typeof(Cu)) var Cu = Components.utils; // https://groups.google.com/group/mozilla.dev.extensions/browse_thread/thread/e89e9c2a834ff2b6# -var firetrayChrome = { +var firetrayChrome = { // each new window gets a new firetrayChrome ! onLoad: function(win) { - // strings a chrome-specific - this.strings = document.getElementById("firetray-strings"); - - try { - // Set up preference change observer - firetray.Utils.prefService.QueryInterface(Ci.nsIPrefBranch2); - firetray.Utils.prefService.addObserver("", firetrayChrome, false); - } catch (x) { - ERROR(x); - return false; - } + this.strings = document.getElementById("firetray-strings"); // chrome-specific LOG("Handler initialized: "+firetray.Handler.initialized); let init = firetray.Handler.initialized || firetray.Handler.init(); @@ -35,8 +25,6 @@ var firetrayChrome = { // prevent window closing. win.addEventListener('close', firetrayChrome.onClose, true); - // NOTE: each new window gets a new firetrayChrome, and hence listens to - // pref changes if (!firetray.Handler.appStarted && firetray.Utils.prefService.getBoolPref('start_hidden')) { @@ -51,13 +39,10 @@ var firetrayChrome = { }, onQuit: function(win) { - // Remove observer - firetray.Utils.prefService.removeObserver("", firetrayChrome); - firetray.Handler.unregisterWindow(win); - /* NOTE: don't firetray.Handler.initialized=false here, otherwise after a - window close, a new window will create a new handler (and hence, a new + /* NOTE: don't do firetray.Handler.initialized=false here, otherwise after + a window close, a new window will create a new handler (and hence, a new tray icon) */ LOG('Firetray UNLOADED !'); }, @@ -80,28 +65,7 @@ var firetrayChrome = { firetray.Handler.hideAllWindows(); event && event.preventDefault(); // no event when called directly (xul) } - }, - - // the chosen design is not to destroy/re-create existing objects, but - // show/hide (Gtk objects) and apply/not (callbacks) them instead - observe: function(subject, topic, data) { - switch (topic) { - case "nsPref:changed": - LOG('Pref changed: '+data); - switch (data) { - case 'hides_single_window': - firetray.Handler.updatePopupMenu(); - break; - case 'show_icon_on_hide': - firetray.Handler.showHideIcon(); - break; - default: - } - break; - default: - } } - }; // should be sufficient for a delayed Startup (no need for window.setTimeout()) diff --git a/src/modules/FiretrayHandler.jsm b/src/modules/FiretrayHandler.jsm index dcafdc3..25aea8d 100644 --- a/src/modules/FiretrayHandler.jsm +++ b/src/modules/FiretrayHandler.jsm @@ -11,6 +11,7 @@ Cu.import("resource://gre/modules/ctypes.jsm"); Cu.import("resource://firetray/ctypes/gobject.jsm"); Cu.import("resource://firetray/ctypes/gtk.jsm"); Cu.import("resource://firetray/commons.js"); +Cu.import("resource://firetray/FiretrayPrefListener.jsm"); Cu.import("resource://firetray/FiretrayVersionChange.jsm"); /** @@ -45,6 +46,8 @@ firetray.Handler = { visibleWindowsCount: 0, init: function() { // does creates icon + firetray.PrefListener.register(false); + this.appNameOriginal = Services.appinfo.name; this.FILENAME_DEFAULT = firetray.Utils.chromeToPath( "chrome://firetray/skin/" + this.appNameOriginal.toLowerCase() + this.FILENAME_SUFFIX); @@ -105,6 +108,8 @@ firetray.Handler = { }, shutdown: function() { + firetray.PrefListener.unregister(); + if (this.inMailApp) firetray.Messaging.shutdown(); firetray.StatusIcon.shutdown(); @@ -272,3 +277,19 @@ firetray.Handler = { } }; // firetray.Handler + + +firetray.PrefListener = new PrefListener( + "extensions.firetray.", + function(branch, name) { + LOG('Pref changed: '+name); + switch (name) { + case 'hides_single_window': + firetray.Handler.updatePopupMenu(); + break; + case 'show_icon_on_hide': + firetray.Handler.showHideIcon(); + break; + default: + } + }); diff --git a/src/modules/FiretrayPrefListener.jsm b/src/modules/FiretrayPrefListener.jsm new file mode 100644 index 0000000..670ee68 --- /dev/null +++ b/src/modules/FiretrayPrefListener.jsm @@ -0,0 +1,50 @@ +/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// https://developer.mozilla.org/en/Code_snippets/Preferences + +var EXPORTED_SYMBOLS = [ "PrefListener" ]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + + +/** + * @constructor + * + * @param {string} branch_name + * @param {Function} callback must have the following arguments: + * branch, pref_leaf_name + */ +function PrefListener(branch_name, callback) { + // Keeping a reference to the observed preference branch or it will get + // garbage collected. + var prefService = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + this._branch = prefService.getBranch(branch_name); + this._branch.QueryInterface(Components.interfaces.nsIPrefBranch2); + this._callback = callback; +} + +PrefListener.prototype.observe = function(subject, topic, data) { + if (topic == 'nsPref:changed') + this._callback(this._branch, data); +}; + +/** + * @param {boolean=} trigger if true triggers the registered function + * on registration, that is, when this method is called. + */ +PrefListener.prototype.register = function(trigger) { + this._branch.addObserver('', this, false); + if (trigger) { + let that = this; + this._branch.getChildList('', {}). + forEach(function (pref_leaf_name) + { that._callback(that._branch, pref_leaf_name); }); + } +}; + +PrefListener.prototype.unregister = function() { + if (this._branch) + this._branch.removeObserver('', this); +}; diff --git a/src/modules/gtk2/FiretrayPopupMenu.jsm b/src/modules/gtk2/FiretrayPopupMenu.jsm new file mode 100644 index 0000000..207bf82 --- /dev/null +++ b/src/modules/gtk2/FiretrayPopupMenu.jsm @@ -0,0 +1,221 @@ +/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +var EXPORTED_SYMBOLS = [ "firetray" ]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/ctypes.jsm"); +Cu.import("resource://firetray/ctypes/gobject.jsm"); +Cu.import("resource://firetray/ctypes/gtk.jsm"); +Cu.import("resource://firetray/commons.js"); + +if ("undefined" == typeof(firetray.StatusIcon)) + ERROR("This module MUST be imported from/after StatusIcon !"); + + +firetray.PopupMenu = { + initialized: false, + // pointers to JS functions. MUST LIVE DURING ALL THE EXECUTION + callbacks: {menuItemWindowActivate: {}}, + menu: null, + menuSeparatorWindows: null, + MIN_FONT_SIZE: 4, + + init: function() { // FIXME: function too long + this.menu = gtk.gtk_menu_new(); + var menuShell = ctypes.cast(this.menu, gtk.GtkMenuShell.ptr); + var addMenuSeparator = false; + + if (firetray.Handler.inBrowserApp) { + var menuItemNewWindowLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel.NewWindow"); + var menuItemNewWindow = gtk.gtk_image_menu_item_new_with_label( + menuItemNewWindowLabel); + var menuItemNewWindowIcon = gtk.gtk_image_new_from_stock( + "gtk-new", gtk.GTK_ICON_SIZE_MENU); + gtk.gtk_image_menu_item_set_image(menuItemNewWindow, menuItemNewWindowIcon); + gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuItemNewWindow, gtk.GtkWidget.ptr)); + + this.callbacks.menuItemNewWindowActivate = gobject.GCallback_t( + firetray.Handler.openBrowserWindow); + gobject.g_signal_connect(menuItemNewWindow, "activate", + firetray.PopupMenu.callbacks.menuItemNewWindowActivate, null); + + addMenuSeparator = true; + } + + if (firetray.Handler.inMailApp) { + var menuItemNewMessageLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel.NewMessage"); + var menuItemNewMessage = gtk.gtk_image_menu_item_new_with_label( + menuItemNewMessageLabel); + var menuItemNewMessageIcon = gtk.gtk_image_new_from_stock( + "gtk-edit", gtk.GTK_ICON_SIZE_MENU); + gtk.gtk_image_menu_item_set_image(menuItemNewMessage, menuItemNewMessageIcon); + gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuItemNewMessage, gtk.GtkWidget.ptr)); + + this.callbacks.menuItemNewMessageActivate = gobject.GCallback_t( + firetray.Handler.openMailMessage); + gobject.g_signal_connect(menuItemNewMessage, "activate", + firetray.PopupMenu.callbacks.menuItemNewMessageActivate, null); + + addMenuSeparator = true; + } + + if (addMenuSeparator) { + var menuSeparator = gtk.gtk_separator_menu_item_new(); + gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuSeparator, gtk.GtkWidget.ptr)); + } + + // shouldn't need to convert to utf8 thank to js-ctypes + var menuItemQuitLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel.Quit"); + var menuItemQuit = gtk.gtk_image_menu_item_new_with_label( + menuItemQuitLabel); + var menuItemQuitIcon = gtk.gtk_image_new_from_stock( + "gtk-quit", gtk.GTK_ICON_SIZE_MENU); + gtk.gtk_image_menu_item_set_image(menuItemQuit, menuItemQuitIcon); + gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuItemQuit, gtk.GtkWidget.ptr)); + + this.callbacks.menuItemQuitActivate = gobject.GCallback_t( + firetray.Handler.quitApplication); + gobject.g_signal_connect(menuItemQuit, "activate", + firetray.PopupMenu.callbacks.menuItemQuitActivate, null); + + var menuWidget = ctypes.cast(this.menu, gtk.GtkWidget.ptr); + gtk.gtk_widget_show_all(menuWidget); + + var menuSeparatorWindows = gtk.gtk_separator_menu_item_new(); + gtk.gtk_menu_shell_prepend(menuShell, ctypes.cast(menuSeparatorWindows, gtk.GtkWidget.ptr)); + this.menuSeparatorWindows = menuSeparatorWindows; + + this.initialized = true; + return true; + }, + + shutdown: function() { + firetray.Utils.tryCloseLibs([gobject, gtk]); + this.initialized = false; + }, + + popup: function(icon, button, activateTime, menu) { + LOG("menu-popup"); + LOG("ARGS="+icon+", "+button+", "+activateTime+", "+menu); + + try { + var gtkMenuPtr = ctypes.cast(menu, gtk.GtkMenu.ptr); + var iconGpointer = ctypes.cast(icon, gobject.gpointer); + gtk.gtk_menu_popup( + gtkMenuPtr, null, null, gtk.gtk_status_icon_position_menu, + iconGpointer, button, activateTime); + } catch (x) { ERROR(x); } + }, + + // we'll be creating menuItems for windows (and not showing them) even if + // hides_single_window is false, because if hides_single_window becomes true, + // we'll just have to show the menuItems + addWindowItem: function(xid) { // on registerWindow + var menuItemWindow = this.addItem(); + firetray.Handler.gtkPopupMenuWindowItems.insert(xid, menuItemWindow); + + this.callbacks.menuItemWindowActivate[xid] = gobject.GCallback_t( + function(){firetray.Handler.showSingleWindow(xid);}); + gobject.g_signal_connect(menuItemWindow, "activate", + firetray.PopupMenu.callbacks.menuItemWindowActivate[xid], null); + this.setWindowItemLabel(menuItemWindow, xid); // default to xid + + LOG("add gtkPopupMenuWindowItems: "+firetray.Handler.gtkPopupMenuWindowItems.count); + }, + + addItem: function() { + var menuItem = gtk.gtk_image_menu_item_new(); + var menuShell = ctypes.cast(this.menu, gtk.GtkMenuShell.ptr); + gtk.gtk_menu_shell_prepend(menuShell, ctypes.cast(menuItem, gtk.GtkWidget.ptr)); + return menuItem; + }, + + removeWindowItem: function(xid) { // on unregisterWindow + let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid); + firetray.Handler.gtkPopupMenuWindowItems.remove(xid); + this.removeItem(menuItemWindow); + LOG("remove gtkPopupMenuWindowItems: "+firetray.Handler.gtkPopupMenuWindowItems.count); + }, + removeItem: function(item) { + gtk.gtk_widget_destroy(ctypes.cast(item, gtk.GtkWidget.ptr)); + }, + + showAllWindowItemsOnlyVisibleWindows: function() { + for (let xid in firetray.Handler.windows) + if (!firetray.Handler.windows[xid].visibility) + this.showSingleWindowItem(xid); + }, + + showSingleWindowItem: function(xid) { + LOG("showSingleWindowItem"); + let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid); + this.showItem(menuItemWindow); + this.setWindowItemLabel(menuItemWindow, firetray.Window.getWindowTitle(xid)); + this.showWindowSeparator(); + }, + + showItem: function(menuItem) { + gtk.gtk_widget_show(ctypes.cast(menuItem, gtk.GtkWidget.ptr)); + }, + + setWindowItemLabel: function(menuItem, label) { + LOG("about to set title: "+label); + if (label) + gtk.gtk_menu_item_set_label(ctypes.cast(menuItem, gtk.GtkMenuItem.ptr), label); + }, + + hideAllWindowItems: function() { + for (let xid in firetray.Handler.windows) + this.hideSingleWindowItemAndSeparator(xid); + }, + + // PopupMenu.hideItem(firetray.Handler.gtkPopupMenuWindowItems.get(xid)) + hideSingleWindowItemAndSeparator: function(xid) { + this.hideSingleWindowItem(xid); + this.hideWindowSeparator(); + }, + + hideSingleWindowItemAndSeparatorMaybe: function(xid) { + this.hideSingleWindowItem(xid); + if (firetray.Handler.visibleWindowsCount === firetray.Handler.windowsCount) + this.hideWindowSeparator(); + }, + + hideSingleWindowItem: function(xid) { + LOG("hideSingleWindowItem"); + let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid); + this.hideItem(menuItemWindow); + }, + + hideItem: function(menuItem) { + gtk.gtk_widget_hide(ctypes.cast(menuItem, gtk.GtkWidget.ptr)); + }, + + showWindowSeparator: function() { + LOG("showing menuSeparatorWindows"); + gtk.gtk_widget_show(ctypes.cast(this.menuSeparatorWindows, gtk.GtkWidget.ptr)); + }, + hideWindowSeparator: function() { + LOG("hiding menuSeparatorWindows"); + gtk.gtk_widget_hide(ctypes.cast(this.menuSeparatorWindows, gtk.GtkWidget.ptr)); + } + +}; // firetray.PopupMenu + + +firetray.Handler.popupMenuWindowItemsHandled = function() { + return (firetray.Handler.inBrowserApp && + firetray.Utils.prefService.getBoolPref('hides_single_window')); +}; + +firetray.Handler.updatePopupMenu = function() { + if (firetray.Handler.popupMenuWindowItemsHandled()) + firetray.PopupMenu.showAllWindowItemsOnlyVisibleWindows(); + else + firetray.PopupMenu.hideAllWindowItems(); +}; diff --git a/src/modules/gtk2/FiretrayStatusIcon.jsm b/src/modules/gtk2/FiretrayStatusIcon.jsm index 5541a01..7174ac8 100644 --- a/src/modules/gtk2/FiretrayStatusIcon.jsm +++ b/src/modules/gtk2/FiretrayStatusIcon.jsm @@ -13,10 +13,8 @@ Cu.import("resource://firetray/ctypes/cairo.jsm"); Cu.import("resource://firetray/ctypes/gobject.jsm"); Cu.import("resource://firetray/ctypes/gdk.jsm"); Cu.import("resource://firetray/ctypes/gtk.jsm"); -Cu.import("resource://firetray/ctypes/libc.jsm"); Cu.import("resource://firetray/ctypes/pango.jsm"); Cu.import("resource://firetray/ctypes/pangocairo.jsm"); -Cu.import("resource://firetray/ctypes/x11.jsm"); Cu.import("resource://firetray/commons.js"); if ("undefined" == typeof(firetray.Handler)) @@ -26,11 +24,8 @@ if ("undefined" == typeof(firetray.Handler)) firetray.StatusIcon = { initialized: false, // pointers to JS functions. MUST LIVE DURING ALL THE EXECUTION - callbacks: {menuItemWindowActivate: {}}, + callbacks: {}, trayIcon: null, - menu: null, - menuSeparatorWindows: null, - MIN_FONT_SIZE: 4, init: function() { try { @@ -43,16 +38,13 @@ firetray.StatusIcon = { firetray.Handler.setIconImageDefault(); - this._buildPopupMenu(); - firetray.Handler.setIconTooltipDefault(); - LOG("showHideAllWindows: "+firetray.Handler.hasOwnProperty("showHideAllWindows")); - this.callbacks.iconActivate = gtk.GCallbackStatusIconActivate_t( - firetray.Handler.showHideAllWindows); - let handlerId = gobject.g_signal_connect(firetray.StatusIcon.trayIcon, - "activate", firetray.StatusIcon.callbacks.iconActivate, null); - LOG("g_connect activate="+handlerId); + Cu.import("resource://firetray/gtk2/FiretrayPopupMenu.jsm"); + if (!firetray.PopupMenu.init()) + return false; + + this.addCallbacks(); this.initialized = true; return true; @@ -63,179 +55,23 @@ firetray.StatusIcon = { this.initialized = false; }, - _buildPopupMenu: function() { // FIXME: function too long - this.menu = gtk.gtk_menu_new(); - var menuShell = ctypes.cast(this.menu, gtk.GtkMenuShell.ptr); - var addMenuSeparator = false; - - if (firetray.Handler.inBrowserApp) { - var menuItemNewWindowLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel.NewWindow"); - var menuItemNewWindow = gtk.gtk_image_menu_item_new_with_label( - menuItemNewWindowLabel); - var menuItemNewWindowIcon = gtk.gtk_image_new_from_stock( - "gtk-new", gtk.GTK_ICON_SIZE_MENU); - gtk.gtk_image_menu_item_set_image(menuItemNewWindow, menuItemNewWindowIcon); - gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuItemNewWindow, gtk.GtkWidget.ptr)); - - this.callbacks.menuItemNewWindowActivate = gobject.GCallback_t( - firetray.Handler.openBrowserWindow); - gobject.g_signal_connect(menuItemNewWindow, "activate", - firetray.StatusIcon.callbacks.menuItemNewWindowActivate, null); - - addMenuSeparator = true; - } - - if (firetray.Handler.inMailApp) { - var menuItemNewMessageLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel.NewMessage"); - var menuItemNewMessage = gtk.gtk_image_menu_item_new_with_label( - menuItemNewMessageLabel); - var menuItemNewMessageIcon = gtk.gtk_image_new_from_stock( - "gtk-edit", gtk.GTK_ICON_SIZE_MENU); - gtk.gtk_image_menu_item_set_image(menuItemNewMessage, menuItemNewMessageIcon); - gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuItemNewMessage, gtk.GtkWidget.ptr)); - - this.callbacks.menuItemNewMessageActivate = gobject.GCallback_t( - firetray.Handler.openMailMessage); - gobject.g_signal_connect(menuItemNewMessage, "activate", - firetray.StatusIcon.callbacks.menuItemNewMessageActivate, null); - - addMenuSeparator = true; - } - - if (addMenuSeparator) { - var menuSeparator = gtk.gtk_separator_menu_item_new(); - gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuSeparator, gtk.GtkWidget.ptr)); - } - - // shouldn't need to convert to utf8 thank to js-ctypes - var menuItemQuitLabel = firetray.Utils.strings.GetStringFromName("popupMenu.itemLabel.Quit"); - var menuItemQuit = gtk.gtk_image_menu_item_new_with_label( - menuItemQuitLabel); - var menuItemQuitIcon = gtk.gtk_image_new_from_stock( - "gtk-quit", gtk.GTK_ICON_SIZE_MENU); - gtk.gtk_image_menu_item_set_image(menuItemQuit, menuItemQuitIcon); - gtk.gtk_menu_shell_append(menuShell, ctypes.cast(menuItemQuit, gtk.GtkWidget.ptr)); - - this.callbacks.menuItemQuitActivate = gobject.GCallback_t( - firetray.Handler.quitApplication); - gobject.g_signal_connect(menuItemQuit, "activate", - firetray.StatusIcon.callbacks.menuItemQuitActivate, null); - - var menuWidget = ctypes.cast(this.menu, gtk.GtkWidget.ptr); - gtk.gtk_widget_show_all(menuWidget); - + addCallbacks: function() { /* NOTE: here we do use a function handler (instead of a function - definition) because we need the args passed to it ! As a consequence, we - need to abandon 'this' in popupMenu() */ - let that = this; - this.callbacks.popupMenu = gtk.GCallbackMenuPopup_t(that.popupMenu); + definition) because we need the args passed to it ! As a consequence, we + need to abandon 'this' in PopupMenu.popup() */ + this.callbacks.menuPopup = gtk.GCallbackMenuPopup_t(firetray.PopupMenu.popup); gobject.g_signal_connect(this.trayIcon, "popup-menu", - firetray.StatusIcon.callbacks.popupMenu, this.menu); - this.callbacks.onScroll = gtk.GCallbackOnScroll_t(that.onScroll); + firetray.StatusIcon.callbacks.menuPopup, firetray.PopupMenu.menu); + this.callbacks.onScroll = gtk.GCallbackOnScroll_t(firetray.StatusIcon.onScroll); gobject.g_signal_connect(this.trayIcon, "scroll-event", - firetray.StatusIcon.callbacks.onScroll, null); + firetray.StatusIcon.callbacks.onScroll, null); - var menuSeparatorWindows = gtk.gtk_separator_menu_item_new(); - gtk.gtk_menu_shell_prepend(menuShell, ctypes.cast(menuSeparatorWindows, gtk.GtkWidget.ptr)); - firetray.StatusIcon.menuSeparatorWindows = menuSeparatorWindows; - - }, - - popupMenu: function(icon, button, activateTime, menu) { - LOG("menu-popup"); - LOG("ARGS="+icon+", "+button+", "+activateTime+", "+menu); - - try { - var gtkMenuPtr = ctypes.cast(menu, gtk.GtkMenu.ptr); - var iconGpointer = ctypes.cast(icon, gobject.gpointer); - gtk.gtk_menu_popup( - gtkMenuPtr, null, null, gtk.gtk_status_icon_position_menu, - iconGpointer, button, activateTime); - } catch (x) { - ERROR(x); - } - }, - - // we keep the definition here, as it is(?) specific to the - // platform-dependant StatusIcon (there might be no popup menu in other - // platforms) - popupMenuWindowItemsHandled: function() { - return (firetray.Handler.inBrowserApp && - firetray.Utils.prefService.getBoolPref('hides_single_window')); - }, - - // we'll be creating menuItems for windows (and not showing them) even if - // hides_single_window is false, because if hides_single_window becomes true, - // we'll just have to show the menuItems - addPopupMenuWindowItem: function(xid) { // on registerWindow - var menuItemWindow = gtk.gtk_image_menu_item_new(); - firetray.Handler.gtkPopupMenuWindowItems.insert(xid, menuItemWindow); - - var menuShell = ctypes.cast(firetray.StatusIcon.menu, gtk.GtkMenuShell.ptr); - gtk.gtk_menu_shell_prepend(menuShell, - ctypes.cast(menuItemWindow, gtk.GtkWidget.ptr)); - - this.callbacks.menuItemWindowActivate[xid] = gobject.GCallback_t( - function(){firetray.Handler.showSingleWindow(xid);}); - gobject.g_signal_connect(menuItemWindow, "activate", - firetray.StatusIcon.callbacks.menuItemWindowActivate[xid], null); - - LOG("add gtkPopupMenuWindowItems: "+firetray.Handler.gtkPopupMenuWindowItems.count); - }, - - setPopupMenuWindowItemLabel: function(menuItem, xid) { - let title = firetray.Handler.windows[xid].baseWin.title; - let tailIndex = title.indexOf(" - Mozilla "+firetray.Handler.appNameOriginal); - if (tailIndex !== -1) - title = title.substring(0, tailIndex); - gtk.gtk_menu_item_set_label(ctypes.cast(menuItem, gtk.GtkMenuItem.ptr), title); - }, - - removePopupMenuWindowItem: function(xid) { // on unregisterWindow - let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid); - firetray.Handler.gtkPopupMenuWindowItems.remove(xid); - gtk.gtk_widget_destroy(ctypes.cast(menuItemWindow, gtk.GtkWidget.ptr)); - - LOG("remove gtkPopupMenuWindowItems: "+firetray.Handler.gtkPopupMenuWindowItems.count); - }, - - showAllPopupMenuWindowItems: function(filterVisibleWindows) { - for (let xid in firetray.Handler.windows) - if (!filterVisibleWindows || !firetray.Handler.windows[xid].visibility) - this.showSinglePopupMenuWindowItem(xid); - }, - - showSinglePopupMenuWindowItem: function(xid) { - LOG("showSinglePopupMenuWindowItem"); - let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid); - gtk.gtk_widget_show(ctypes.cast(menuItemWindow, gtk.GtkWidget.ptr)); - this.setPopupMenuWindowItemLabel(menuItemWindow, xid); // not when creating item ! - this.showPopupMenuWindowSeparator(); - }, - - hideAllPopupMenuWindowItems: function(forceHideSeparator) { - for (let xid in firetray.Handler.windows) - this.hideSinglePopupMenuWindowItem(xid, forceHideSeparator); - }, - - // PopupMenu.hideItem(firetray.Handler.gtkPopupMenuWindowItems.get(xid)) - hideSinglePopupMenuWindowItem: function(xid, forceHideSeparator) { - LOG("hideSinglePopupMenuWindowItem"); - let menuItemWindow = firetray.Handler.gtkPopupMenuWindowItems.get(xid); - gtk.gtk_widget_hide(ctypes.cast(menuItemWindow, gtk.GtkWidget.ptr)); // on hideSingleWindow - - if (forceHideSeparator || (firetray.Handler.visibleWindowsCount === firetray.Handler.windowsCount)) { - this.hidePopupMenuWindowSeparator(); - } - }, - - showPopupMenuWindowSeparator: function() { - LOG("showing menuSeparatorWindows"); - gtk.gtk_widget_show(ctypes.cast(firetray.StatusIcon.menuSeparatorWindows, gtk.GtkWidget.ptr)); - }, - hidePopupMenuWindowSeparator: function() { - LOG("hiding menuSeparatorWindows"); - gtk.gtk_widget_hide(ctypes.cast(firetray.StatusIcon.menuSeparatorWindows, gtk.GtkWidget.ptr)); + LOG("showHideAllWindows: "+firetray.Handler.hasOwnProperty("showHideAllWindows")); + this.callbacks.iconActivate = gtk.GCallbackStatusIconActivate_t( + firetray.Handler.showHideAllWindows); + let handlerId = gobject.g_signal_connect(firetray.StatusIcon.trayIcon, + "activate", firetray.StatusIcon.callbacks.iconActivate, null); + LOG("g_connect activate="+handlerId); }, onScroll: function(icon, event, data) { @@ -421,10 +257,3 @@ firetray.Handler.setIconVisibility = function(visible) { gtk.gtk_status_icon_set_visible(firetray.StatusIcon.trayIcon, visible); return true; }; - -firetray.Handler.updatePopupMenu = function() { - if (firetray.StatusIcon.popupMenuWindowItemsHandled()) - firetray.StatusIcon.showAllPopupMenuWindowItems(true); - else - firetray.StatusIcon.hideAllPopupMenuWindowItems(true); -}; diff --git a/src/modules/gtk2/FiretrayWindow.jsm b/src/modules/gtk2/FiretrayWindow.jsm index 5336599..22a4e76 100644 --- a/src/modules/gtk2/FiretrayWindow.jsm +++ b/src/modules/gtk2/FiretrayWindow.jsm @@ -46,6 +46,13 @@ var _find_data_t = ctypes.StructType("_find_data_t", [ { outWindow: gtk.GtkWindow.ptr } ]); +// NOTE: storing ctypes pointers into a JS object doesn't work: pointers are +// "evolving" after a while (maybe due to back and forth conversion). So we +// need to store them into a real ctypes array ! +firetray.Handler.gtkWindows = new ctypesMap(gtk.GtkWindow.ptr), +firetray.Handler.gdkWindows = new ctypesMap(gdk.GdkWindow.ptr), +firetray.Handler.gtkPopupMenuWindowItems = new ctypesMap(gtk.GtkImageMenuItem.ptr), + firetray.Window = { @@ -172,7 +179,7 @@ firetray.Window = { throw new DeleteError(); firetray.Handler.gtkWindows.remove(xid); firetray.Handler.gdkWindows.remove(xid); - firetray.StatusIcon.removePopupMenuWindowItem(xid); + firetray.PopupMenu.removeWindowItem(xid); } else { ERROR("can't unregister unknown window "+xid); return false; @@ -368,6 +375,15 @@ firetray.Window = { return desktop; }, + getWindowTitle: function(xid) { + let title = firetray.Handler.windows[xid].baseWin.title; + let tailIndex = title.indexOf(" - Mozilla "+firetray.Handler.appNameOriginal); + if (tailIndex !== -1) + return title.substring(0, tailIndex) + else + return null; + }, + filterWindow: function(xev, gdkEv, data) { if (!xev) return gdk.GDK_FILTER_CONTINUE; @@ -395,19 +411,6 @@ firetray.Window = { } break; - case x11.ClientMessage: // not very useful - LOG("ClientMessage"); -/* KEPT FOR LATER USE - let xclient = ctypes.cast(xev, x11.XClientMessageEvent.ptr); - LOG("xclient.data="+xclient.contents.data); - LOG("message_type="+xclient.contents.message_type+", format="+xclient.contents.format); - if (strEquals(xclient.contents.data[0], x11.current.Atoms.WM_DELETE_WINDOW)) { - LOG("Delete Window prevented"); - return gdk.GDK_FILTER_REMOVE; - } -*/ - break; - default: // LOG("xany.type="+xany.contents.type); break; @@ -424,13 +427,6 @@ firetray.Window = { ///////////////////////// firetray.Handler overriding ///////////////////////// -// NOTE: storing ctypes pointers into a JS object doesn't work: pointers are -// "evolving" after a while (maybe due to back and forth conversion). So we -// need to store them into a real ctypes array ! -firetray.Handler.gtkWindows = new ctypesMap(gtk.GtkWindow.ptr), -firetray.Handler.gdkWindows = new ctypesMap(gdk.GdkWindow.ptr), -firetray.Handler.gtkPopupMenuWindowItems = new ctypesMap(gtk.GtkImageMenuItem.ptr), - /** debug facility */ firetray.Handler.dumpWindows = function() { LOG(firetray.Handler.windowsCount); @@ -450,7 +446,7 @@ firetray.Handler.registerWindow = function(win) { try { this.gtkWindows.insert(xid, gtkWin); this.gdkWindows.insert(xid, gdkWin); - firetray.StatusIcon.addPopupMenuWindowItem(xid); + firetray.PopupMenu.addWindowItem(xid); } catch (x) { if (x.name === "RangeError") // instanceof not working :-( win.alert(x+"\n\nYou seem to have more than "+FIRETRAY_WINDOW_COUNT_MAX @@ -470,12 +466,6 @@ firetray.Handler.registerWindow = function(win) { // delete_event_cb (in gtk2/nsWindow.cpp), but we prefer to use the // provided 'close' JS event -/* KEPT FOR LATER USE - this.windows[xid].onWindowStateCb = gtk.GCallbackWindowStateEvent_t(firetray.Window.onWindowState); - this.windows[xid].onWindowStateCbId = gobject.g_signal_connect(gtkWin, "window-state-event", this.windows[xid].onWindowStateCb, null); - LOG("g_connect window-state-event="+this.windows[xid].onWindowStateCbId); -*/ - this.windows[xid].filterWindowCb = gdk.GdkFilterFunc_t(firetray.Window.filterWindow); gdk.gdk_window_add_filter(gdkWin, this.windows[xid].filterWindowCb, null); @@ -492,7 +482,6 @@ firetray.Handler.registerWindow = function(win) { firetray.Handler.unregisterWindow = function(win) { LOG("unregister window"); - let xid = firetray.Window.getXIDFromChromeWindow(win); return firetray.Window.unregisterWindowByXID(xid); }; @@ -510,8 +499,8 @@ firetray.Handler.showSingleWindow = function(xid) { firetray.Handler.windows[xid].visibility = true; firetray.Handler.visibleWindowsCount += 1; - if (firetray.StatusIcon.popupMenuWindowItemsHandled()) - firetray.StatusIcon.hideSinglePopupMenuWindowItem(xid); + if (firetray.Handler.popupMenuWindowItemsHandled()) + firetray.PopupMenu.hideSingleWindowItemAndSeparatorMaybe(xid); firetray.Handler.showHideIcon(); }; @@ -529,8 +518,8 @@ firetray.Handler.hideSingleWindow = function(xid) { firetray.Handler.windows[xid].visibility = false; firetray.Handler.visibleWindowsCount -= 1; - if (firetray.StatusIcon.popupMenuWindowItemsHandled()) - firetray.StatusIcon.showSinglePopupMenuWindowItem(xid); + if (firetray.Handler.popupMenuWindowItemsHandled()) + firetray.PopupMenu.showSingleWindowItem(xid); firetray.Handler.showHideIcon(); }; @@ -543,10 +532,11 @@ firetray.Handler.showHideAllWindows = function(gtkStatusIcon, userData) { LOG("windowsCount="+firetray.Handler.windowsCount); let visibilityRate = firetray.Handler.visibleWindowsCount/firetray.Handler.windowsCount; LOG("visibilityRate="+visibilityRate); - if (visibilityRate > 0.5) // TODO: should be configurable - firetray.Handler.hideAllWindows(); - else + if ((0.5 < visibilityRate) && (visibilityRate < 1) + || visibilityRate === 0) // TODO: should be configurable firetray.Handler.showAllWindows(); + else + firetray.Handler.hideAllWindows(); let stopPropagation = true; return stopPropagation;