bug 179656 - allow drag and drop reordering of tabs, patch originally based on miniT by dorando, r=vlad, a=shaver

This commit is contained in:
mconnor%steelgryphon.com 2007-08-22 05:03:26 +00:00
Родитель 9d38a066c5
Коммит 98970d4d4b
3 изменённых файлов: 294 добавлений и 61 удалений

Просмотреть файл

@ -22,6 +22,7 @@
-
- Contributor(s):
- David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
- Mike Connor <mconnor@steelgryphon.com>
-
- 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
@ -56,7 +57,14 @@
<xul:stringbundle src="chrome://global/locale/tabbrowser.properties"/>
<xul:tabbox flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
onselect="if (!('updateCurrentBrowser' in this.parentNode) || event.target.localName != 'tabpanels') return; this.parentNode.updateCurrentBrowser();">
<xul:hbox class="tabbrowser-strip chromeclass-toolbar" collapsed="true" tooltip="_child" context="_child">
<xul:hbox id="tab-drop-indicator-bar">
<xul:hbox id="tab-drop-indicator"/>
</xul:hbox>
<xul:hbox class="tabbrowser-strip chromeclass-toolbar" collapsed="true" tooltip="_child" context="_child"
ondraggesture="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
ondragdrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
<xul:tooltip onpopupshowing="event.preventBubble(); if (document.tooltipNode.hasAttribute('label')) { this.setAttribute('label', document.tooltipNode.getAttribute('label')); return true; } return false;"/>
<xul:menupopup onpopupshowing="this.parentNode.parentNode.parentNode.updatePopupMenu(this);">
<xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"
@ -124,13 +132,16 @@
document.getAnonymousNodes(this)[1]
</field>
<field name="mStrip">
this.mTabBox.firstChild
this.mTabBox.childNodes[1]
</field>
<field name="mTabContainer">
this.mStrip.childNodes[2]
</field>
<field name="mPanelContainer">
this.mTabBox.childNodes[1]
this.mTabBox.childNodes[2]
</field>
<field name="mTabs">
this.mTabContainer.childNodes
</field>
<field name="mStringBundle">
document.getAnonymousNodes(this)[0]
@ -165,12 +176,19 @@
<field name="mModalDialogShowing">
false
</field>
<field name="arrowKeysShouldWrap" readonly="true">
#ifdef XP_MACOSX
true
#else
false
#endif
</field>
<method name="getBrowserAtIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.mPanelContainer.childNodes[aIndex].firstChild.nextSibling;
return this.mTabContainer.childNodes[aIndex].linkedBrowser;
]]>
</body>
</method>
@ -545,7 +563,7 @@
<method name="updateCurrentBrowser">
<body>
<![CDATA[
var newBrowser = this.getBrowserAtIndex(this.mPanelContainer.selectedIndex);
var newBrowser = this.getBrowserAtIndex(this.mTabContainer.selectedIndex);
if (this.mCurrentBrowser == newBrowser)
return;
@ -620,7 +638,7 @@
p.onLocationChange(webProgress, null, loc);
if (securityUI)
p.onSecurityChange(webProgress, null, securityUI.state);
var listener = this.mTabListeners[this.mPanelContainer.selectedIndex];
var listener = this.mTabListeners[this.mTabContainer.selectedIndex];
if (listener.mIcon) {
if (this.isFavIconKnownMissing(listener.mIcon))
listener.mIcon = null;
@ -819,7 +837,7 @@
var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;
var tab = tabBrowser.mTabContainer.childNodes[i];
var tab = document.getAnonymousElementByAttribute(tabBrowser, "linkedpanel", this.parentNode.id);
tabBrowser.setTabTitle(tab);
if (tab == tabBrowser.mCurrentTab)
@ -1021,6 +1039,14 @@
b._fastFind = this.fastFind;
var uniqueId = "panel" + Date.now() + position;
this.mPanelContainer.lastChild.id = uniqueId;
t.linkedPanel = uniqueId;
t.linkedBrowser = b;
t._tPos = position;
if (t.previousSibling.selected)
t.setAttribute("afterselected", true);
if (!blank) {
// pretend the user typed this so it'll be available till
// the document successfully loads
@ -1145,7 +1171,7 @@
var index = -1;
if (this.mCurrentTab == aTab)
index = this.mPanelContainer.selectedIndex;
index = this.mTabContainer.selectedIndex;
else {
// Find and locate the tab in our list.
for (var i = 0; i < l; i++)
@ -1168,7 +1194,7 @@
oldBrowser.setAttribute("type", "content");
// Now select the new tab before nuking the old one.
var currentIndex = this.mPanelContainer.selectedIndex;
var currentIndex = this.mTabContainer.selectedIndex;
var newIndex = -1;
if (currentIndex > index)
@ -1198,11 +1224,16 @@
oldBrowser.destroy();
this.mTabContainer.removeChild(oldTab);
this.mPanelContainer.removeChild(this.mPanelContainer.childNodes[index]);
this.mPanelContainer.removeChild(oldBrowser.parentNode);
this.selectedTab = this.mTabContainer.childNodes[newIndex];
this.mPanelContainer.selectedIndex = newIndex;
var i;
for (i = oldTab._tPos; i < this.mTabContainer.childNodes.length; i++) {
this.mTabContainer.childNodes[i]._tPos = i;
}
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
this.mCurrentTab.selected = true;
this.updateCurrentBrowser();
// see comment above destroy above
@ -1317,16 +1348,7 @@
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.mCurrentTab == aTab)
return this.mCurrentBrowser;
for (var i = 0; i < this.mTabContainer.childNodes.length; i++) {
if (this.mTabContainer.childNodes[i] == aTab) {
return this.getBrowserAtIndex(i);
}
}
return null;
return aTab.linkedBrowser;
]]>
</body>
</method>
@ -1355,26 +1377,73 @@
readonly="true"/>
<property name="browsers"
onget="return this.mPanelContainer.getElementsByTagName('browser');"
readonly="true"/>
<property name="browsers" readonly="true">
<getter>
<![CDATA[
var browsers = [];
var i;
browsers.item = function(i) {return this[i];}
for (i = 0; i < this.mTabContainer.childNodes.length; i++)
browsers.push(this.mTabContainer.childNodes[i].linkedBrowser);
return browsers;
]]>
</getter>
</property>
<!-- Drag and drop observer API -->
<!--<method name="onDragStart">
<method name="onDragStart">
<parameter name="aEvent"/>
<parameter name="aXferData"/>
<parameter name="aDragAction"/>
<body/>
</method>-->
<body>
<![CDATA[
if (aEvent.target.localName == "tab") {
aXferData.data = new TransferData();
aXferData.data.addDataForFlavour("text/x-moz-tab", aEvent.target._tPos);
var URI = this.getBrowserForTab(aEvent.target).currentURI;
if (URI) {
aXferData.data.addDataForFlavour("text/unicode", URI.spec);
aXferData.data.addDataForFlavour("text/x-moz-url", URI.spec + "\n" + aEvent.target.label);
aXferData.data.addDataForFlavour("text/html", '<a href="' + URI.spec + '">' + aEvent.target.label + '</a>');
}
}
]]>
</body>
</method>
<method name="onDragOver">
<parameter name="aEvent"/>
<parameter name="aFlavour"/>
<parameter name="aDragSession"/>
<body>
<![CDATA[
return; // Just having this makes our feedback correct.
]]>
<![CDATA[
if (aDragSession.canDrop && aDragSession.sourceNode.parentNode == this.mTabContainer) {
var newIndex = this.getNewIndex(aEvent);
var ib = document.getElementById('tab-drop-indicator-bar');
var ind = document.getElementById('tab-drop-indicator');
ib.setAttribute('dragging','true');
if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
if (newIndex == this.mTabs.length) {
ind.style.marginLeft = this.mTabs[newIndex-1].boxObject.x +
this.mTabs[newIndex-1].boxObject.width - this.boxObject.x - 7 + 'px';
} else {
ind.style.marginLeft = this.mTabs[newIndex].boxObject.x - this.boxObject.x - 7 + 'px';
}
} else {
if (newIndex == gBrowser.mTabs.length) {
ind.style.marginRight = gBrowser.boxObject.width + gBrowser.boxObject.x -
gBrowser.mTabs[newIndex-1].boxObject.x + 'px';
} else {
ind.style.marginRight = gBrowser.boxObject.width + gBrowser.boxObject.x -
gBrowser.mTabs[newIndex].boxObject.x -
gBrowser.mTabs[newIndex].boxObject.width + 'px';
}
}
}
]]>
</body>
</method>
@ -1384,30 +1453,49 @@
<parameter name="aDragSession"/>
<body>
<![CDATA[
var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
if (aDragSession.sourceNode.parentNode == this.mTabContainer) {
var newIndex = this.getNewIndex(aEvent);
if (newIndex > aXferData.data)
newIndex--;
if (newIndex != aXferData.data)
this.moveTabTo(this.mTabs[aXferData.data], newIndex);
} else {
var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
// Also disallow dropping javascript: or data: urls--bail out
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
/^\s*(javascript|data):/.test(url))
return;
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
// Also disallow dropping javascript: or data: urls--bail out
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
/^\s*(javascript|data):/.test(url))
return;
this.dragDropSecurityCheck(aEvent, aDragSession, url);
this.dragDropSecurityCheck(aEvent, aDragSession, url);
var bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
var bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
var tab = null;
if (aEvent.originalTarget.localName != "tab") {
// We're adding a new tab.
tab = this.addTab(getShortcutOrURI(url));
var tab = null;
if (aEvent.originalTarget.localName != "tab") {
// We're adding a new tab.
tab = this.addTab(getShortcutOrURI(url));
}
else {
// Load in an existing tab.
tab = aEvent.originalTarget;
this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
}
if (this.mCurrentTab != tab && !bgLoad)
this.selectedTab = tab;
}
else {
// Load in an existing tab.
tab = aEvent.originalTarget;
this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
}
if (this.mCurrentTab != tab && !bgLoad)
this.selectedTab = tab;
]]>
</body>
</method>
<method name="onDragExit">
<parameter name="aEvent"/>
<parameter name="aDragSession"/>
<body>
<![CDATA[
var ib = document.getElementById('tab-drop-indicator-bar');
ib.setAttribute('dragging','false');
]]>
</body>
</method>
@ -1416,6 +1504,7 @@
<body>
<![CDATA[
var flavourSet = new FlavourSet();
flavourSet.appendFlavour("text/x-moz-tab"); // this has to be first to support DnD reordering
flavourSet.appendFlavour("text/x-moz-url");
flavourSet.appendFlavour("text/unicode");
flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
@ -1423,7 +1512,116 @@
]]>
</body>
</method>
<method name="moveTabTo">
<parameter name="aTab"/>
<parameter name="aIndex"/>
<body>
<![CDATA[
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
this.mCurrentTab.selected = false;
this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes[aIndex]);
var i;
for (i = 0; i < this.mTabContainer.childNodes.length; i++) {
this.mTabContainer.childNodes[i]._tPos = i;
}
this.mCurrentTab.selected = true;
return aTab;
]]>
</body>
</method>
<method name="getNewIndex">
<parameter name="aEvent"/>
<body>
<![CDATA[
var i;
if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
if (aEvent.clientX < this.mTabs[i].boxObject.x + this.mTabs[i].boxObject.width / 2)
return i;
} else {
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
if (aEvent.clientX > this.mTabs[i].boxObject.x + this.mTabs[i].boxObject.width / 2)
return i;
}
return this.mTabs.length;
]]>
</body>
</method>
<method name="moveTabForward">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab, tabPos + 1);
this.mCurrentTab.focus();
}
else if (this.arrowKeysShouldWrap)
this.moveTabToStart();
]]>
</body>
</method>
<method name="moveTabBackward">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, tabPos - 1);
this.mCurrentTab.focus();
}
else if (this.arrowKeysShouldWrap)
this.moveTabToEnd();
]]>
</body>
</method>
<method name="moveTabToStart">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, 0);
this.mCurrentTab.focus();
}
]]>
</body>
</method>
<method name="moveTabToEnd">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab,
this.browsers.length - 1);
this.mCurrentTab.focus();
}
]]>
</body>
</method>
<method name="moveTabOver">
<parameter name="aEvent"/>
<body>
<![CDATA[
var direction = window.getComputedStyle(this.parentNode, null).direction;
if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
(direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
this.moveTabForward();
else
this.moveTabBackward();
]]>
</body>
</method>
<!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
MAKE SURE TO ADD IT HERE AS WELL. -->
<property name="canGoBack"
@ -1689,15 +1887,44 @@
<![CDATA[({
tabbrowser: this,
handleEvent: function handleEvent(aEvent) {
if (!aEvent.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
#ifndef XP_MACOSX
if (aEvent.ctrlKey && aEvent.keyCode == KeyEvent.DOM_VK_F4 && this.tabbrowser.mTabBox.handleCtrlPageUpDown)
this.tabbrowser.removeCurrentTab();
if (!aEvent.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
#ifdef XP_MACOSX
if ('metaKey' in aEvent && aEvent.metaKey) {
#else
if ('ctrlKey' in aEvent && aEvent.ctrlKey) {
if (aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
this.tabbrowser.mTabBox.handleCtrlPageUpDown) {
this.tabbrowser.removeCurrentTab();
return;
}
#endif
if (aEvent.target.localName == "tabbrowser") {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_UP:
this.tabbrowser.moveTabBackward();
break;
case KeyEvent.DOM_VK_DOWN:
this.tabbrowser.moveTabForward();
break;
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_LEFT:
this.tabbrowser.moveTabOver(aEvent);
break;
case KeyEvent.DOM_VK_HOME:
this.tabbrowser.moveTabToStart();
break;
case KeyEvent.DOM_VK_END:
this.tabbrowser.moveTabToEnd();
break;
default:
break;
}
}
}
}
})]]>
</field>
@ -1719,10 +1946,16 @@
<constructor>
<![CDATA[
this.mCurrentBrowser = this.getBrowserAtIndex(0);
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild.nextSibling;
this.mCurrentTab = this.mTabContainer.firstChild;
this.mTabBox.handleCtrlTab = !/Mac/.test(navigator.platform);
document.addEventListener("keypress", this._keyEventHandler, false);
var uniqueId = "panel" + Date.now();
this.mPanelContainer.childNodes[0].id = uniqueId;
this.mTabContainer.childNodes[0].linkedPanel = uniqueId;
this.mTabContainer.childNodes[0]._tPos = 0;
this.mTabContainer.childNodes[0].linkedBrowser = this.mPanelContainer.childNodes[0].firstChild.nextSibling;
]]>
</constructor>

Двоичные данные
browser/themes/pinstripe/browser/tabbrowser/tabDragIndicator.png Executable file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 515 B

Двоичные данные
browser/themes/winstripe/browser/tabbrowser/tabDragIndicator.png Executable file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 515 B