diff --git a/content/treestyletab/treestyletab.xul b/content/treestyletab/treestyletab.xul index 614c5cec..7752080b 100644 --- a/content/treestyletab/treestyletab.xul +++ b/content/treestyletab/treestyletab.xul @@ -153,13 +153,7 @@ oncommand="TreeStyleTabService.toggleFixed(TreeStyleTabService.getTabBrowserFromChild(this));"/> - + diff --git a/modules/browser.js b/modules/browser.js index 9a6a2d48..eb232d34 100644 --- a/modules/browser.js +++ b/modules/browser.js @@ -42,6 +42,7 @@ const Ci = Components.interfaces; // rap(); Components.utils.import('resource://treestyletab-modules/window.js'); +Components.utils.import('resource://treestyletab-modules/fullTooltip.js'); function TreeStyleTabBrowser(aWindowService, aTabBrowser) { @@ -136,12 +137,6 @@ TreeStyleTabBrowser.prototype = { { return (this._tabStripPlaceHolder = value); }, - - get tabTooltip() - { - return this.document.getElementById('tabbrowser-tab-tooltip') || // Firefox 4.0- - this.evaluateXPath('descendant::xul:tooltip', this.mTabBrowser.mStrip, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; // -Firefox 3.6 - }, /* properties */ @@ -1279,7 +1274,7 @@ TreeStyleTabBrowser.prototype = { this.scrollBox.addEventListener('overflow', this, true); this.scrollBox.addEventListener('underflow', this, true); - this.tabTooltip.addEventListener('popupshowing', this, true); + this.fullTooltipManager = new FullTooltipManager(this); }, _ensureNewSplitter : function TSTBrowser__ensureNewSplitter() @@ -1845,7 +1840,8 @@ TreeStyleTabBrowser.prototype = { this.scrollBox.removeEventListener('overflow', this, true); this.scrollBox.removeEventListener('underflow', this, true); - this.tabTooltip.removeEventListener('popupshowing', this, true); + this.fullTooltipManager.destroy(); + delete this.fullTooltipManager; }, saveCurrentState : function TSTBrowser_saveCurrentState() @@ -2404,7 +2400,10 @@ TreeStyleTabBrowser.prototype = { return this.onScroll(aEvent); case 'popupshowing': - return this.onPopupShowing(aEvent) + return this.onPopupShowing(aEvent); + + case 'popuphiding': + return this.onPopupHiding(aEvent); case 'mouseover': this._initDNDObservers(); @@ -3915,178 +3914,10 @@ TreeStyleTabBrowser.prototype = { onPopupShowing : function TSTBrowser_onPopupShowing(aEvent) { - if (aEvent.target.localName == 'tooltip') - this.handleTooltip(aEvent); - else if (aEvent.target == aEvent.currentTarget) + if (aEvent.target == aEvent.currentTarget) this.initTabContextMenu(aEvent); }, - handleTooltip : function TSTBrowser_handleTooltip(aEvent) - { - this.cancelFullTooltip(); - - var tab = this.getTabFromChild(this.document.tooltipNode); - if (!tab || tab.localName != 'tab') - return; - - var label; - var collapsed = this.isSubtreeCollapsed(tab); - var mode = this.getTreePref('tooltip.mode'); - var showTree = collapsed || mode == this.kTOOLTIP_MODE_ALWAYS; - - var base = parseInt(tab.getAttribute(this.kNEST) || 0); - var descendant = this.getDescendantTabs(tab); - var indentPart = ' '; - var tree = null; - var fullTooltipExtraLabel = ''; - if (mode > this.kTOOLTIP_MODE_DEFAULT && - descendant.length) { - let tabs = [tab].concat(descendant); - let tabsToBeListed = tabs.slice(0, Math.max(1, this.getTreePref('tooltip.maxCount'))); - tree = tabsToBeListed - .map(function(aTab) { - let label = aTab.getAttribute('label'); - let indent = ''; - let nest = parseInt(aTab.getAttribute(this.kNEST) || 0) - base; - for (let i = 0; i < nest; i++) - { - indent += indentPart; - } - return this.treeBundle.getFormattedString('tooltip.item.label', [label, indent]); - }, this) - .join('\n'); - if (tabs.length != tabsToBeListed.length) { - tree += '\n'+indentPart+this.treeBundle.getFormattedString('tooltip.more', [tabs.length-tabsToBeListed.length]); - } - } - - if ('mOverCloseButton' in tab && tab.mOverCloseButton) { - if (descendant.length && - (collapsed || this.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN)) { - label = tree || tab.getAttribute('label'); - label = showTree ? - this.treeBundle.getFormattedString('tooltip.closeTree.labeled', [label]) : - this.treeBundle.getString('tooltip.closeTree') ; - if (showTree) - fullTooltipExtraLabel = this.treeBundle.getFormattedString('tooltip.closeTree.labeled', ['%TREE%']).split(/\s*%TREE%\s*/); - } - } - else if (tab.getAttribute(this.kTWISTY_HOVER) == 'true') { - let key = showTree ? - 'tooltip.expandSubtree' : - 'tooltip.collapseSubtree' ; - label = tree || tab.getAttribute('label'); - label = label ? - this.treeBundle.getFormattedString(key+'.labeled', [label]) : - this.treeBundle.getString(key) ; - } - else if (showTree) { - label = tree; - } - - if (label) { - aEvent.target.setAttribute('label', label); - aEvent.stopPropagation(); - - this.showFullTooltip(aEvent.target, tab, fullTooltipExtraLabel); - } - }, - kTOOLTIP_MODE_DEFAULT : 0, - kTOOLTIP_MODE_COLLAPSED : 1, - kTOOLTIP_MODE_ALWAYS : 2, - - showFullTooltip : function TSTBrowser_showFullTooltip(aBaseTooltip, aTab, aExtraLabels) - { - this.cancelFullTooltip(); - - var delay = this.getTreePref('tooltip.fullTooltipDelay'); - if (delay < 0) - return; - - var doc = this.document; - var tooltip = doc.getElementById('treestyletab-full-tree-tooltip'); - tooltip.style.maxWidth = this.window.screen.availWidth+'px'; - tooltip.style.maxHeight = this.window.screen.availHeight+'px'; - - var range = doc.createRange(); - range.selectNodeContents(tooltip.firstChild); - range.deleteContents(); - range.insertNode(this.createFullTooltipContents(aTab, aExtraLabels)); - - this._fullTooltipTimer = this.window.setTimeout(function() { - aBaseTooltip.hidePopup(); - // open as a context menu popup to reposition it automatically - tooltip.openPopup(aBaseTooltip, 'overlap', 0, 0, true, false); - }, Math.max(delay, 0)); - - var self = this; - aBaseTooltip.addEventListener('popuphiding', function() { - aBaseTooltip.removeEventListener('popuphiding', arguments.callee, true); - self.cancelFullTooltip(); - }, true); - }, - cancelFullTooltip : function TSTBrowser_destroyFullTooltip() - { - if (this._fullTooltipTimer) { - this.window.clearTimeout(this._fullTooltipTimer); - this._fullTooltipTimer = null; - } - this.document.getElementById('treestyletab-full-tree-tooltip').hidePopup(); - }, - createFullTooltipContents : function TSTBrowser_createFullTooltipContents(aTab, aExtraLabels) - { - var doc = this.document; - - const XHTMLNS = 'http://www.w3.org/1999/xhtml'; - var tree = (function(aTab) { - var item = doc.createElement('hbox'); - item.setAttribute('align', 'center'); - var favicon = item.appendChild(doc.createElement('image')); - favicon.setAttribute('src', aTab.getAttribute('image') || 'chrome://mozapps/skin/places/defaultFavicon.png'); - favicon.setAttribute('style', 'max-width:16px;max-height:16px;'); - var label = item.appendChild(doc.createElement('label')); - label.setAttribute('value', aTab.label); - label.setAttribute('tooltiptext', aTab.label); - label.setAttribute('crop', 'end'); - label.setAttribute('class', 'text-link'); - label.addEventListener('click', function(aEvent) { - doc.defaultView.gBrowser.selectedTab = aTab; // aTab.selected = true; - }, true); - var children = this.getChildTabs(aTab); - if (children.length) { - let items = children.map(arguments.callee, this); - let childrenBox = doc.createElement('vbox'); - items.forEach(function(aChild) { - childrenBox.appendChild(aChild); - }); - childrenBox.setAttribute('align', 'stretch'); - childrenBox.setAttribute('style', 'margin-left:1.5em'); - let container = doc.createElement('vbox'); - container.appendChild(item); - container.appendChild(childrenBox); - return container; - } - else { - return item; - } - }).call(this, aTab); - - var root = doc.createDocumentFragment(); - - if (aExtraLabels) { - if (typeof aExtraLabels == 'string') - aExtraLabels = [aExtraLabels]; - aExtraLabels.forEach(function(aLabel) { - root.appendChild(doc.createElement('description')) - .appendChild(doc.createTextNode(aLabel)); - }); - } - - root.insertBefore(tree, root.firstChild && root.firstChild.nextSibling); - - return root; - }, - initTabContextMenu : function TSTBrowser_initTabContextMenu(aEvent) { var b = this.mTabBrowser; @@ -4646,7 +4477,7 @@ TreeStyleTabBrowser.prototype = { ), this); }, - partTabs : function TSTBrowser_partTabs(aTabs) + partTabs : function TSTBrowser_partTabs(aTabs) { var aTabs = Array.slice(aTabs); for each (let tab in aTabs) @@ -5026,7 +4857,7 @@ TreeStyleTabBrowser.prototype = { { return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); }, - duplicateTabs : function TSTBrowser_duplicateTabs(aTabs, aInsertBefore) /* PUBLIC API */ + duplicateTabs : function TSTBrowser_duplicateTabs(aTabs, aInsertBefore) /* PUBLIC API */ { return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore, duplicate : true }); }, @@ -5129,7 +4960,7 @@ TreeStyleTabBrowser.prototype = { return newTabs; }, - importTab : function TSTBrowser_importTab(aTab) + importTab : function TSTBrowser_importTab(aTab) { var newTab = this.mTabBrowser.addTab(); newTab.linkedBrowser.stop(); @@ -5139,7 +4970,7 @@ TreeStyleTabBrowser.prototype = { return newTab; }, - duplicateTabAsOrphan : function TSTBrowser_duplicateTabAsOrphan(aTab) + duplicateTabAsOrphan : function TSTBrowser_duplicateTabAsOrphan(aTab) { var newTab = this.mTabBrowser.duplicateTab(aTab); this.deleteTabValue(newTab, this.kCHILDREN); diff --git a/modules/fullTooltip.js b/modules/fullTooltip.js new file mode 100644 index 00000000..3517f4c2 --- /dev/null +++ b/modules/fullTooltip.js @@ -0,0 +1,315 @@ +/* ***** 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 Tree Style Tab. + * + * The Initial Developer of the Original Code is SHIMODA Hiroshi. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): SHIMODA Hiroshi + * + * 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 EXPORTED_SYMBOLS = ['FullTooltipManager']; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import('resource://treestyletab-modules/utils.js'); + +function FullTooltipManager(aOwner) +{ + this.init(aOwner); +} +FullTooltipManager.prototype = { + __proto__ : TreeStyleTabUtils, + + kTOOLTIP_MODE_DEFAULT : 0, + kTOOLTIP_MODE_COLLAPSED : 1, + kTOOLTIP_MODE_ALWAYS : 2, + + get window() + { + return this.owner.window; + }, + + get document() + { + return this.owner.document; + }, + + get tabTooltip() + { + return this.document.getElementById('tabbrowser-tab-tooltip') || // Firefox 4.0- + this.evaluateXPath('descendant::xul:tooltip', this.mTabBrowser.mStrip, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; // -Firefox 3.6 + }, + + get tabFullTooltip() + { + return this.document.getElementById('treestyletab-full-tree-tooltip'); + }, + + + init : function FTM_init(aOwner) + { + this.owner = aOwner; + + this.tabTooltip.addEventListener('popupshowing', this, true); + this.tabTooltip.addEventListener('popuphiding', this, true); + this.tabFullTooltip.addEventListener('click', this, true); + this.tabFullTooltip.addEventListener('popuphiding', this, true); + }, + + destroy : function FTM_destroy() + { + this.tabTooltip.removeEventListener('popupshowing', this, true); + this.tabTooltip.removeEventListener('popuphiding', this, true); + this.tabFullTooltip.removeEventListener('click', this, true); + this.tabFullTooltip.removeEventListener('popuphiding', this, true); + + delete this.owner; + }, + + handleEvent : function FTM_handleEvent(aEvent) + { + switch (aEvent.type) + { + case 'click': + return this.onClick(aEvent); + + case 'popupshowing': + return this.onPopupShowing(aEvent); + + case 'popuphiding': + return this.onPopupHiding(aEvent); + } + }, + + onClick : function FTM_onClick(aEvent) + { + var label = this.evaluateXPath('ancestor-or-self::xul:label[@class="text-link"][1]', aEvent.target, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; + if (label) { + let id = label.getAttribute(this.kID); + let tab = this.getTabById(id, this.owner.browser); + if (tab) + this.owner.browser.selectedTab = tab; + } + + this.tabFullTooltip.hidePopup(); + }, + + onPopupShowing : function FTM_onPopupShowing(aEvent) + { + this.cancel(); + this.handleDefaultTooltip(aEvent); + }, + + onPopupHiding : function FTM_onPopupHiding(aEvent) + { + if (aEvent.target == this.tabTooltip) + this.cancel(); + else if (aEvent.target == this.tabFullTooltip) + this.clear(); + }, + + + handleDefaultTooltip : function FTM_handleDefaultTooltip(aEvent) + { + var tab = this.getTabFromChild(this.document.tooltipNode); + if (!tab || tab.localName != 'tab') + return; + + var label; + var collapsed = this.isSubtreeCollapsed(tab); + var mode = this.getTreePref('tooltip.mode'); + var showTree = collapsed || mode == this.kTOOLTIP_MODE_ALWAYS; + + var base = parseInt(tab.getAttribute(this.kNEST) || 0); + var descendant = this.getDescendantTabs(tab); + var indentPart = ' '; + var tree = null; + var fullTooltipExtraLabel = ''; + if (mode > this.kTOOLTIP_MODE_DEFAULT && + descendant.length) { + let tabs = [tab].concat(descendant); + let tabsToBeListed = tabs.slice(0, Math.max(1, this.getTreePref('tooltip.maxCount'))); + tree = tabsToBeListed + .map(function(aTab) { + let label = aTab.getAttribute('label'); + let indent = ''; + let nest = parseInt(aTab.getAttribute(this.kNEST) || 0) - base; + for (let i = 0; i < nest; i++) + { + indent += indentPart; + } + return this.treeBundle.getFormattedString('tooltip.item.label', [label, indent]); + }, this) + .join('\n'); + if (tabs.length != tabsToBeListed.length) { + tree += '\n'+indentPart+this.treeBundle.getFormattedString('tooltip.more', [tabs.length-tabsToBeListed.length]); + } + } + + if ('mOverCloseButton' in tab && tab.mOverCloseButton) { + if (descendant.length && + (collapsed || this.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN)) { + label = tree || tab.getAttribute('label'); + label = showTree ? + this.treeBundle.getFormattedString('tooltip.closeTree.labeled', [label]) : + this.treeBundle.getString('tooltip.closeTree') ; + if (showTree) + fullTooltipExtraLabel = this.treeBundle.getFormattedString('tooltip.closeTree.labeled', ['%TREE%']).split(/\s*%TREE%\s*/); + } + } + else if (tab.getAttribute(this.kTWISTY_HOVER) == 'true') { + let key = showTree ? + 'tooltip.expandSubtree' : + 'tooltip.collapseSubtree' ; + label = tree || tab.getAttribute('label'); + label = label ? + this.treeBundle.getFormattedString(key+'.labeled', [label]) : + this.treeBundle.getString(key) ; + } + else if (showTree) { + label = tree; + } + + if (!label) + return; + + aEvent.target.setAttribute('label', label); + aEvent.stopPropagation(); + + this.setup(aEvent.target, tab, fullTooltipExtraLabel); + }, + + + setup : function FTM_setup(aBaseTooltip, aTab, aExtraLabels) + { + this.cancel(); + + var delay = this.getTreePref('tooltip.fullTooltipDelay'); + if (delay < 0) + return; + + this._fullTooltipTimer = this.window.setTimeout(function(aSelf) { + aBaseTooltip.hidePopup(); + + var tooltip = aSelf.tabFullTooltip; + tooltip.style.maxWidth = aSelf.window.screen.availWidth+'px'; + tooltip.style.maxHeight = aSelf.window.screen.availHeight+'px'; + + aSelf.fill(aTab, aExtraLabels); + + // open as a context menu popup to reposition it automatically + tooltip.openPopup(aBaseTooltip, 'overlap', 0, 0, true, false); + }, Math.max(delay, 0), this); + }, + + cancel : function FTM_destroyFullTooltip() + { + if (this._fullTooltipTimer) { + this.window.clearTimeout(this._fullTooltipTimer); + this._fullTooltipTimer = null; + } + this.tabFullTooltip.hidePopup(); + }, + + fill : function FTM_fill(aTab, aExtraLabels) + { + this.clear(); + + var tree = this.createTabItem(aTab); + var root = this.document.createDocumentFragment(); + + if (aExtraLabels) { + if (typeof aExtraLabels == 'string') + aExtraLabels = [aExtraLabels]; + aExtraLabels.forEach(function(aLabel) { + root.appendChild(this.document.createElement('description')) + .appendChild(this.document.createTextNode(aLabel)); + }); + } + + root.insertBefore(tree, root.firstChild && root.firstChild.nextSibling); + + var range = this.document.createRange(); + range.selectNodeContents(this.tabFullTooltip.firstChild); + range.insertNode(root); + range.detach(); + }, + + clear : function FTM_clear() + { + var range = this.document.createRange(); + range.selectNodeContents(this.tabFullTooltip.firstChild); + range.deleteContents(); + range.detach(); + }, + + createTabItem : function FTM_createTabItem(aTab) + { + var item = this.document.createElement('hbox'); + item.setAttribute('align', 'center'); + + var favicon = item.appendChild(this.document.createElement('image')); + favicon.setAttribute('src', aTab.getAttribute('image') || 'chrome://mozapps/skin/places/defaultFavicon.png'); + favicon.setAttribute('style', 'max-width:16px;max-height:16px;'); + + var label = item.appendChild(this.document.createElement('label')); + label.setAttribute('value', aTab.label); + label.setAttribute('tooltiptext', aTab.label); + label.setAttribute('crop', 'end'); + label.setAttribute('class', 'text-link'); + label.setAttribute(this.kID, this.getTabValue(aTab, this.kID)); + + var children = this.createTabChildren(aTab); + if (children) { + let container = this.document.createElement('vbox'); + container.appendChild(item); + container.appendChild(children); + return container; + } + else { + return item; + } + }, + + createTabChildren : function FTM_createTabChildren(aTab) + { + var children = this.getChildTabs(aTab); + if (!children.length) + return null; + + var container = this.document.createElement('vbox'); + children.forEach(function(aChild) { + container.appendChild(this.createTabItem(aChild)); + }, this); + container.setAttribute('align', 'stretch'); + container.setAttribute('style', 'margin-left:1.5em'); + return container; + } +};