diff --git a/instantbird/app/profile/all-instantbird.js b/instantbird/app/profile/all-instantbird.js --- a/instantbird/app/profile/all-instantbird.js +++ b/instantbird/app/profile/all-instantbird.js @@ -240,3 +240,24 @@ // On debug builds, show warning, errors and debug information. pref("purple.debug.loglevel", 2); #endif + +// Tabbed browser +pref("browser.tabs.autoHide", false); +pref("browser.tabs.warnOnClose", true); +pref("browser.tabs.warnOnOpen", true); +pref("browser.tabs.maxOpenBeforeWarn", 15); +pref("browser.tabs.loadInBackground", true); +pref("browser.tabs.loadFolderAndReplace", true); +pref("browser.tabs.opentabfor.middleclick", true); +pref("browser.tabs.loadDivertedInBackground", false); +pref("browser.tabs.loadBookmarksInBackground", false); +pref("browser.tabs.tabMinWidth", 100); +pref("browser.tabs.tabMaxWidth", 250); +pref("browser.tabs.tabClipWidth", 140); + +// Where to show tab close buttons: +// 0 on active tab only +// 1 on all tabs until tabClipWidth is reached, then active tab only +// 2 no close buttons at all +// 3 at the end of the tabstrip +pref("browser.tabs.closeButtons", 1); diff --git a/instantbird/base/content/Makefile.in b/instantbird/base/content/Makefile.in --- a/instantbird/base/content/Makefile.in +++ b/instantbird/base/content/Makefile.in @@ -45,7 +45,7 @@ DEFINES += -DWINCE endif -EXTRA_JS_MODULES = instantbird/imContentSink.jsm instantbird/imSmileys.jsm instantbird/imThemes.jsm +EXTRA_JS_MODULES = instantbird/imContentSink.jsm instantbird/imSmileys.jsm instantbird/imThemes.jsm instantbird/imWindows.jsm EXTRA_COMPONENTS = instantbird/smileProtocolHandler.js include $(topsrcdir)/config/rules.mk diff --git a/instantbird/base/content/instantbird/blist.js b/instantbird/base/content/instantbird/blist.js --- a/instantbird/base/content/instantbird/blist.js +++ b/instantbird/base/content/instantbird/blist.js @@ -42,8 +42,6 @@ "buddy-idle", "account-connected", "account-disconnected", - "new-text", - "new-conversation", "status-away", "status-back", "purple-quit", @@ -53,7 +51,6 @@ var buddyList = { observe: function bl_observe(aBuddy, aTopic, aMsg) { - //dump("received signal: " + aTopic + "\n"); if (aTopic == "quit-application-requested") { this._onQuitRequest(aBuddy, aMsg); @@ -101,21 +98,6 @@ return; } - if (aTopic == "new-text" || aTopic == "new-conversation") { - if (!this.win) { - this.win = window.open(convWindow, "Conversations", "chrome,resizable"); - this.win.pendingNotifications = [{object: aBuddy, topic: aTopic, msg: aMsg}]; - this.win.addEventListener("unload", function(aEvent) { - if (aEvent.target.location.href == convWindow) - buddyList.win = null; - }, false); - } - else if ("pendingNotifications" in this.win) - this.win.pendingNotifications.push({object: aBuddy, topic: aTopic, msg: aMsg}); - - return; - } - if (aTopic == "account-connected" || aTopic == "account-disconnected") { var account = aBuddy.QueryInterface(Ci.purpleIAccount); if (account.protocol.id == "prpl-irc") { @@ -251,6 +233,9 @@ return; } + Components.utils.import("resource://app/modules/imWindows.jsm"); + Conversations.init(); + buddyList.checkNotDisconnected(true); buddyList.checkForIrcAccount(); this.addEventListener("unload", buddyList.unload, false); diff --git a/instantbird/base/content/instantbird/buddy.xml b/instantbird/base/content/instantbird/buddy.xml --- a/instantbird/base/content/instantbird/buddy.xml +++ b/instantbird/base/content/instantbird/buddy.xml @@ -430,13 +430,7 @@ break; } - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); - var convWindow = wm.getMostRecentWindow("Messenger:convs"); - if (convWindow) { - convWindow.msgObserver.focusConv(conv); - convWindow.focus(); - } + Conversations.focusConversation(conv); ]]> diff --git a/instantbird/base/content/instantbird/convbrowser.xml b/instantbird/base/content/instantbird/convbrowser.xml --- a/instantbird/base/content/instantbird/convbrowser.xml +++ b/instantbird/base/content/instantbird/convbrowser.xml @@ -415,7 +415,7 @@ + + + + + + + diff --git a/instantbird/base/content/instantbird/conversation.xml b/instantbird/base/content/instantbird/conversation.xml --- a/instantbird/base/content/instantbird/conversation.xml +++ b/instantbird/base/content/instantbird/conversation.xml @@ -136,6 +136,7 @@ if (this._conv) { this._conv.close(); this._conv.removeObserver(this); + Conversations.unregisterConversation(this); delete this._conv; this.browser.destroy(); @@ -148,6 +149,14 @@ + + + + + + false [] @@ -173,6 +182,7 @@ @@ -580,6 +590,8 @@ @@ -626,6 +638,22 @@ + + + + + + @@ -644,6 +672,7 @@ if (this._conv instanceof Components.interfaces.purpleIConvChat) { this.updateTopic(); this.setAttribute("chat", "true"); + this.tab.setAttribute("chat", "true"); // Set an id on the participant count for accessibility reasons (see bug 216) let id = "pc" + Date.now(); @@ -660,6 +689,8 @@ this.updateParticipantCount(); this.browser.buddies = this.buddies; } + else + this.updateBuddyStatus(); Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService) @@ -679,6 +710,10 @@ this.updateTyping(); break; + case "update-buddy-status": + this.updateBuddyStatus(); + break; + case "chat-buddy-add": aSubject.QueryInterface(Ci.nsISimpleEnumerator); while (aSubject.hasMoreElements()) @@ -753,10 +788,27 @@ diff --git a/instantbird/base/content/instantbird/imWindows.jsm b/instantbird/base/content/instantbird/imWindows.jsm new file mode 100644 --- /dev/null +++ b/instantbird/base/content/instantbird/imWindows.jsm @@ -0,0 +1,136 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Instantbird messenging client, released + * 2009. + * + * The Initial Developer of the Original Code is + * Florian QUEZE . + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const CONVERSATION_WINDOW_URI = "chrome://instantbird/content/instantbird.xul"; +var EXPORTED_SYMBOLS = ["Conversations"]; + +var Conversations = { + _windows: [], + registerWindow: function(aWindow) { + if (this._windows.indexOf(aWindow) == -1) + this._windows.push(aWindow); + + if (this._pendingNotifications) { + this._pendingNotifications.forEach(function(aNotif) { + this.observe(aNotif.object, aNotif.topic, aNotif.msg); + }, this); + delete this._pendingNotifications; + } + }, + unregisterWindow: function(aWindow) { + let index = this._windows.indexOf(aWindow); + if (index != -1) + this._windows.splice(index, 1); + }, + + _purpleConv: {}, + _conversations: [], + registerConversation: function(aConversation) { + if (this._conversations.indexOf(aConversation) == -1) + this._conversations.push(aConversation); + + this._purpleConv[aConversation.conv.id] = aConversation; + }, + unregisterConversation: function(aConversation) { + let index = this._conversations.indexOf(aConversation); + if (index != -1) + this._conversations.splice(index, 1); + + if (this._purpleConv[aConversation.conv.id] == aConversation) + delete this._purpleConv[aConversation.conv.id]; + }, + + focusConversation: function(aConv) { + let id = aConv.id; + if (id in this._purpleConv) { + let conv = this._purpleConv[id]; + let doc = conv.ownerDocument; + doc.getElementById("conversations").selectedTab = conv.tab; + conv.focus(); + doc.defaultView.focus(); + } + }, + + init: function() { + let os = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + ["new-text", "new-conversation", "purple-quit"].forEach(function (aTopic) { + os.addObserver(Conversations, aTopic, false); + }); + }, + + observe: function(aSubject, aTopic, aMsg) { + if (aTopic == "purple-quit") { + for (let id in this._purpleConv) + this._purpleConv[id].unInit(); + } + + if (aTopic != "new-text" && aTopic != "new-conversation") + return; + + if (!this._windows.length) { + if (this._pendingNotifications) { + this._pendingNotifications.push({object: aSubject, topic: aTopic, + msg: aMsg}); + return; + } + + var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Components.interfaces.nsIWindowWatcher); + wwatch.openWindow(null, CONVERSATION_WINDOW_URI, "_blank", + "chrome,toolbar,resizable", null); + this._pendingNotifications = [{object: aSubject, topic: aTopic, msg: aMsg}]; + return; + } + + let conv = aTopic == "new-conversation" ? aSubject : aSubject.conversation; + if (!(conv.id in this._purpleConv)) { + this._windows[this._windows.length - 1].document + .getElementById("conversations").addConversation(conv); + } + + if (aTopic == "new-text") { + let conv = this._purpleConv[aSubject.conversation.id]; + if (!conv.loaded) + conv.addMsg(aSubject); + if (aSubject.incoming && !aSubject.system && + (!(aSubject.conversation instanceof Components.interfaces.purpleIConvChat) || + aSubject.containsNick)) + conv.ownerDocument.defaultView.getAttention(); + } + } +}; diff --git a/instantbird/base/content/instantbird/instantbird.js b/instantbird/base/content/instantbird/instantbird.js --- a/instantbird/base/content/instantbird/instantbird.js +++ b/instantbird/base/content/instantbird/instantbird.js @@ -35,211 +35,24 @@ * * ***** END LICENSE BLOCK ***** */ -const events = ["new-text", - "new-conversation", - "purple-quit"]; -var msgObserver = { - convs: { }, +var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab"; + +const events = ["purple-quit"]; + +var convWindow = { // Components.interfaces.nsIObserver observe: function mo_observe(aObject, aTopic, aData) { switch(aTopic) { case "purple-quit": - for (let i in this.convs) - this.convs[i].unInit(); window.close(); break; - case "new-text": - var conv = aObject.conversation; - var tab = this.convs[conv.id] || this.addConvTab(conv); - if (!tab.loaded) // until we can load all messages from a conversation - tab.addMsg(aObject); - - if (aObject.incoming && !aObject.system && - (!(aObject.conversation instanceof Ci.purpleIConvChat) || - aObject.containsNick)) - window.getAttention(); - break; - - case "new-conversation": - this.addConvTab(aObject); - break; - default: throw "Bad notification"; } }, - addConvTab: function mo_addConvTab(aConv) { - if (aConv.id in this.convs) - return this.convs[aConv.id]; - - var conv = document.createElement("conversation"); - conv.setAttribute("contenttooltip", "aHTMLTooltip"); - conv.setAttribute("contentcontextmenu", "contentAreaContextMenu"); - var panels = document.getElementById("panels"); - panels.appendChild(conv); - conv.conv = aConv; - - var tabs = document.getElementById("tabs"); - var tab = document.createElement("convtab"); - tab.tooltipText = aConv.name; - let title = aConv.title - .replace(/^([a-zA-Z0-9.]+)[@\s].*/, "$1") - .replace(/(.{15}).*/, "$1…"); - tab.setAttribute("label", title); - tabs.appendChild(tab); - conv.tab = tab; - - if (!tabs.selectedItem) { - tabs.selectedItem = tab; - panels.selectedPanel.focus(); - } - - this.convs[aConv.id] = conv; - return conv; - }, - - focusConv: function mo_focusConv(aConv) { - var id = aConv.id; - if (!(id in this.convs)) { - // We only support a single chat window ATM so we can safely - // re-add a closed conversation tab - this.addConvTab(aConv); - if (!(id in this.convs)) - throw "Can't find the conversation, even after trying to add it again!"; - } - var panels = document.getElementById("panels"); - var conv = this.convs[id]; - panels.selectedPanel = conv; - document.getElementById("tabs").selectedIndex = panels.selectedIndex; - this.focusSelectedTab(); - }, - - focusSelectedTab: function mo_focusSelectedTab() { - if (msgObserver.focusTimeoutId) { - clearTimeout(msgObserver.focusTimeoutId); - msgObserver.focusTimeoutId = null; - } - msgObserver.focusClickTimeoutId = null; - var tabs = document.getElementById("tabs"); - var tab = tabs.selectedItem; - tab.removeAttribute("unread"); - tab.removeAttribute("attention"); - var panels = document.getElementById("panels"); - panels.selectedPanel.focus(); - }, - - onTabboxKeyPress: function mo_onTabboxKeyPress(aEvent) { - // When switching tab with ctrl(+shift)+tab, we need to set the - // focus immediatly to the textbox - if (aEvent.keyCode == aEvent.DOM_VK_TAB && - aEvent.ctrlKey && !aEvent.altKey && !aEvent.metaKey) { - msgObserver.focusClickTimeoutId = setTimeout(msgObserver.focusSelectedTab, 0); - } - }, - - onSelectTab: function mo_onSelectTab() { -#ifdef WINCE - // work around the brokenness of tabpanels / display:-moz-deck on WinCE - let panels = document.getElementById("panels"); - let selectedPanel = panels.selectedPanel; - for (var panel = panels.firstChild; panel; panel = panel.nextSibling) { - if (panel == selectedPanel) - panel.setAttribute("selected", "true"); - else - panel.removeAttribute("selected"); - } - -#endif - if (this.focusClickTimeoutId) { - // if a click event already started a shorter timeout to focus - // the tab, ignore the select event - return; - } - - if (this.focusTimeoutId) - clearTimeout(this.focusTimeoutId); - this.focusTimeoutId = setTimeout(this.focusSelectedTab, 1000); - }, - - onClickTab: function mo_onClickTab(aEvent) { - if (aEvent.target.localName != "convtab") - return; - - if (aEvent.button == 1) - this.closeTab(aEvent.target); - - // the call to focusSelectedTab needs to be delayed because we - // want it to always happen after the onselect event is dispatched - // (and tabbox.xml selects the new tab after the mousedown event - // with a timeout) - if (aEvent.button == 0) - this.focusClickTimeoutId = setTimeout(this.focusSelectedTab, 0); - }, - - closeCurrentTab: function mo_closeCurrentTab() { - var tabs = document.getElementById("tabs"); - this.closeTab(tabs.selectedItem); - }, - - closeTab: function mo_closeTab(aTab) { - var tabs = aTab.parentNode.childNodes; - var i = aTab.parentNode.getIndexOfItem(aTab); - if (i == -1) - throw "Can't find the tab that should be closed"; - - var panels = document.getElementById("panels"); - var conv = panels.childNodes[i]; - if (!conv) - throw "Can't find the conversation associated with the tab."; - delete this.convs[conv.convId]; - - // Because of the way XBL works (fields just set JS - // properties on the element) and the code in place - // to preserve the JS objects for any elements that have - // JS properties set on them, the conversation element won't be - // destroyed until the document goes away. Force a cleanup. - conv.destroy(); - - if (aTab.selected) { - if (aTab.nextSibling) { - // we are not on the last tab - aTab.parentNode.selectedItem = aTab.nextSibling; - panels.selectedPanel = conv.nextSibling; - } - else { - // we remove the last tab, which is selected - if (aTab.previousSibling) { - // at least a tab remain - aTab.parentNode.selectedItem = aTab.previousSibling; - panels.selectedPanel = conv.previousSibling; - } - else { - window.close(); - } - } - } - - panels.removeChild(conv); - // Workaround an ugly bug: when removing a panel, the selected panel disappears - panels.selectedPanel = panels.selectedPanel; - - aTab.parentNode.removeChild(aTab); - }, - - onPopupShowing: function mo_onPopupShowing(aEvent) { - if (aEvent.explicitOriginalTarget.localName == "convtab") - this.contextTab = aEvent.explicitOriginalTarget; - else - aEvent.preventDefault(); - }, - - onCommandClose: function mo_onCommandClose(aEvent) { - this.closeTab(this.contextTab); - }, - onMouseZoom: function mo_onMouseZoom(event) { if (!event.ctrlKey || event.altKey || event.shiftKey || !event.detail) return; @@ -249,17 +62,20 @@ }, load: function mo_load() { - addObservers(msgObserver, events); - if (window.pendingNotifications) { - let notifications = window.pendingNotifications; - for (let i = 0; i < notifications.length; ++i) { - let notif = notifications[i]; - msgObserver.observe(notif.object, notif.topic, notif.msg); - } - delete window.pendingNotifications; + Components.utils.import("resource://app/modules/imWindows.jsm"); + Conversations.registerWindow(window); + + addObservers(convWindow, events); + + if ("arguments" in window && window.arguments[0] instanceof XULElement) { + // swap the given tab with the default dummy conversation tab + // and then close the original tab in the other window. + document.getElementById("conversations") + .importConversation(window.arguments[0]); } - window.addEventListener("unload", msgObserver.unload, false); - window.addEventListener("DOMMouseScroll", msgObserver.onMouseZoom, false); + + window.addEventListener("unload", convWindow.unload, false); + window.addEventListener("DOMMouseScroll", convWindow.onMouseZoom, false); window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner @@ -268,13 +84,16 @@ .XULBrowserWindow = window.XULBrowserWindow; }, unload: function mo_unload() { - removeObservers(msgObserver, events); + removeObservers(convWindow, events); + Conversations.unregisterWindow(window); } }; +function getConvWindowURL() "chrome://instantbird/content/instantbird.xul" + function getBrowser() { - return document.getElementById("panels").selectedPanel.browser; + return document.getElementById("conversations"); } // Inspired from the same function in mozilla/browser/base/content/browser.js @@ -376,4 +195,4 @@ } } -this.addEventListener("load", msgObserver.load, false); +this.addEventListener("load", convWindow.load, false); diff --git a/instantbird/base/content/instantbird/instantbird.xul b/instantbird/base/content/instantbird/instantbird.xul --- a/instantbird/base/content/instantbird/instantbird.xul +++ b/instantbird/base/content/instantbird/instantbird.xul @@ -39,6 +39,8 @@ + + @@ -55,6 +57,8 @@ id = "convWindow" windowtype="Messenger:convs" title = "&convWindow.title;" + titlemenuseparator="&convWindow.titlemodifiermenuseparator;" + titlemodifier="&convWindow.titlemodifier;" width = "500" height = "600" persist= "width height screenX screenY" @@ -70,7 +74,6 @@