support drag feedback image and multiple drag data for dragging of trees

This commit is contained in:
SHIMODA Hiroshi 2010-11-30 12:23:08 +09:00
parent fedf1f4c5f
commit 2932d89da7
4 changed files with 257 additions and 73 deletions

View File

@ -0,0 +1,244 @@
/*
Multiple Tabs Drag and Drop Utilities for Firefox 3.5 or later
Usage:
window['piro.sakura.ne.jp'].tabsDragUtils.initTabBrowser(gBrowser);
// in dragstart event listener
window['piro.sakura.ne.jp'].tabsDragUtils.startTabsDrag(aEvent, aArrayOfTabs);
license: The MIT License, Copyright (c) 2010 SHIMODA "Piro" Hiroshi
http://github.com/piroor/fxaddonlibs/blob/master/license.txt
original:
http://github.com/piroor/fxaddonlibs/blob/master/tabsDragUtils.js
*/
(function() {
const currentRevision = 1;
if (!('piro.sakura.ne.jp' in window)) window['piro.sakura.ne.jp'] = {};
var loadedRevision = 'tabsDragUtils' in window['piro.sakura.ne.jp'] ?
window['piro.sakura.ne.jp'].tabsDragUtils.revision :
0 ;
if (loadedRevision && loadedRevision > currentRevision) {
return;
}
if (loadedRevision &&
'destroy' in window['piro.sakura.ne.jp'].tabsDragUtils)
window['piro.sakura.ne.jp'].tabsDragUtils.destroy();
const Cc = Components.classes;
const Ci = Components.interfaces;
var tabsDragUtils = {
revision : currentRevision,
init : function TDU_init()
{
window.addEventListener('load', this._delayedInit, false);
},
_delayedInit : function TDU_delayedInit()
{
window.removeEventListener('load', arguments.callee, false);
// BarTap
// https://addons.mozilla.org/firefox/addon/67651
if ('BarTap' in window &&
'writeBarTap' in BarTap) {
eval('BarTap.writeBarTap = '+
BarTap.writeBarTap.toSource().replace(
'bartap = JSON.stringify',
'window["piro.sakura.ne.jp"].tabsDragUtils._backupArgumentURI(aURI, aBrowser); $&'
)
);
}
delete tabsDragUtils._delayedInit;
},
destroy : function TDU_destroy()
{
if (this._delayedInit)
window.removeEventListener('load', this._delayedInit, false);
},
initTabBrowser : function TDU_initTabBrowser(aTabBrowser)
{
var tabDNDObserver = (aTabBrowser.tabContainer && aTabBrowser.tabContainer.tabbrowser == aTabBrowser) ?
aTabBrowser.tabContainer : // Firefox 4.0 or later
aTabBrowser ; // Firefox 3.5 - 3.6
if ('_setEffectAllowedForDataTransfer' in tabDNDObserver &&
tabDNDObserver._setEffectAllowedForDataTransfer.toSource().indexOf('tabDragUtils') < 0) {
eval('tabDNDObserver._setEffectAllowedForDataTransfer = '+
tabDNDObserver._setEffectAllowedForDataTransfer.toSource().replace(
'dt.mozItemCount > 1',
'$& && !window["piro.sakura.ne.jp"].tabsDragUtils.isTabsDragging(arguments[0])'
)
);
}
},
destroyTabBrowser : function TDU_destroyTabBrowser(aTabBrowser)
{
},
startTabsDrag : function TDU_startTabsDrag(aEvent, aTabs)
{
var draggedTab = this.getTabFromEvent(aEvent);
var tabs = aTabs || [];
var index = tabs.indexOf(draggedTab);
if (index < 0)
return;
var dt = aEvent.dataTransfer;
dt.setDragImage(this.createDragFeedbackImage(tabs), 0, 0);
tabs.splice(index, 1);
tabs.unshift(draggedTab);
tabs.forEach(function(aTab, aIndex) {
dt.mozSetDataAt(TAB_DROP_TYPE, aTab, aIndex);
dt.mozSetDataAt('text/x-moz-text-internal', this.getCurrentURIOfTab(aTab), aIndex);
}, this);
dt.mozCursor = 'default';
aEvent.stopPropagation();
},
createDragFeedbackImage : function TDU_createDragFeedbackImage(aTabs)
{
var previews = aTabs.map(function(aTab) {
return tabPreviews.capture(aTab, false);
}, this);
var offset = 16;
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
canvas.width = previews[0].width + (offset * aTabs.length);
canvas.height = previews[0].height + (offset * aTabs.length);
var ctx = canvas.getContext('2d');
ctx.save();
try {
ctx.clearRect(0, 0, canvas.width, canvas.height);
previews.forEach(function(aPreview, aIndex) {
ctx.drawImage(aPreview, 0, 0);
ctx.translate(offset, offset);
ctx.globalAlpha = 1 / (aIndex+1);
}, this);
}
catch(e) {
}
ctx.restore();
return canvas;
},
getTabFromEvent : function TDU_getTabFromEvent(aEvent, aReallyOnTab)
{
var tab = (aEvent.originalTarget || aEvent.target).ownerDocument.evaluate(
'ancestor-or-self::*[local-name()="tab"]',
aEvent.originalTarget || aEvent.target,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (tab || aReallyOnTab)
return tab;
var b = this.getTabBrowserFromChild(aEvent.originalTarget);
if (b &&
'treeStyleTab' in b &&
'getTabFromTabbarEvent' in b.treeStyleTab) { // Tree Style Tab
return b.treeStyleTab.getTabFromTabbarEvent(aEvent);
}
return null;
},
getTabBrowserFromChild : function TDU_getTabBrowserFromChild(aTabBrowserChild)
{
if (!aTabBrowserChild)
return null;
if (aTabBrowserChild.localName == 'tabbrowser') // itself
return aTabBrowserChild;
if (aTabBrowserChild.tabbrowser) // tabs, Firefox 4.0 or later
return aTabBrowserChild.tabbrowser;
if (aTabBrowserChild.id == 'TabsToolbar') // tabs toolbar, Firefox 4.0 or later
return aTabBrowserChild.getElementsByTagName('tabs')[0].tabbrowser;
var b = aTabBrowserChild.ownerDocument.evaluate(
'ancestor-or-self::*[local-name()="tabbrowser"] | '+
'ancestor-or-self::*[local-name()="tabs" and @tabbrowser]',
aTabBrowserChild,
XPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
return (b && b.tabbrowser) || b;
},
isTabsDragging : function TDU_isTabsDragging(aEvent)
{
var dt = aEvent.dataTransfer;
if (dt.mozItemCount < 1)
return false;
for (let i = 0, maxi = dt.mozItemCount; i < maxi; i++)
{
if (Array.slice(dt.mozTypesAt(i)).indexOf(TAB_DROP_TYPE) < 0)
return false;
}
return true;
},
getDraggedTabs : function TDU_getDraggedTabs(aEvent)
{
var dt = aEvent.dataTransfer;
var tabs = [];
if (dt.mozItemCount < 1 ||
Array.slice(dt.mozTypesAt(0)).indexOf(TAB_DROP_TYPE) < 0)
return tabs;
for (let i = 0, maxi = dt.mozItemCount; i < maxi; i++)
{
tabs.push(dt.mozGetDataAt(TAB_DROP_TYPE, i));
}
return tabs.sort(function(aA, aB) { return aA._tPos - aB._tPos; });
},
getCurrentURIOfTab : function TDU_getCurrentURIOfTab(aTab)
{
if (aTab.getAttribute('ontap') == 'true') {
// If BarTap ( https://addons.mozilla.org/firefox/addon/67651 ) is installed,
// currentURI is possibly 'about:blank'. So, we have to get correct URI
// from the attribute or the session histrory.
var b = aTab.linkedBrowser;
try {
if (b.hasAttribute(this.kARGUMENT_URI))
return b.getAttribute(this.kARGUMENT_URI);
}
catch(e) {
}
try {
var h = b.sessionHistory;
var entry = h.getEntryAtIndex(h.index, false);
return entry.URI.spec;
}
catch(e) {
}
}
// Firefox 4.0-
if (aTab.linkedBrowser.__SS_needsRestore) {
let data = aTab.linkedBrowser.__SS_data;
let entry = data.entries[Math.max(data.index, data.entries.length-1)];
return entry.url;
}
return aTab.linkedBrowser.currentURI.spec;
},
_backupArgumentURI : function TDU_backupArgumentURI(aURI, aBrowser)
{
if (aURI) {
var uri = (aURI instanceof Ci.nsIURI) ? aURI.spec : aURI ;
aBrowser.setAttribute(this.kARGUMENT_URI, uri);
}
},
kARGUMENT_URI : 'tabs-drag-utils-bartap-uri'
};
window['piro.sakura.ne.jp'].tabsDragUtils = tabsDragUtils;
tabsDragUtils.init();
})();

View File

@ -265,6 +265,7 @@ var TreeStyleTabService = {
behavior += this.kGROUP_BOOKMARK_USE_DUMMY;
if (!this.getTreePref('openGroupBookmarkBehavior.confirm')) {
behavior += (
@ -425,9 +426,6 @@ var TreeStyleTabService = {
/\.screenX/g, '[TreeStyleTabService.getTabBrowserFromChild(TSTTabBrowser).treeStyleTab.positionProp]'
).replace(
/\.width/g, '[TreeStyleTabService.getTabBrowserFromChild(TSTTabBrowser).treeStyleTab.sizeProp]'
).replace(
'dt.mozItemCount > 1',
'$& && !TreeStyleTabService.isTabsDragging(arguments[0])'
).replace(
/(return (?:true|dt.effectAllowed = "copyMove");)/,
<![CDATA[
@ -480,6 +478,7 @@ var TreeStyleTabService = {
).replace(
'document.getBindingParent(aEvent.originalTarget).localName != "tab"',
'!TreeStyleTabService.getTabFromEvent(aEvent)'
).replace(
'var tab = aEvent.target;',
<![CDATA[$&
@ -567,17 +566,6 @@ catch(e) {
}
},
isTabsDragging : function TSTService_isTabsDragging(aEvent)
{
var dt = aEvent.dataTransfer;
for (let i = 0, maxi = dt.mozItemCount; i < maxi; i++)
{
if (Array.slice(dt.mozTypesAt(i)).indexOf(TAB_DROP_TYPE) < 0)
return false;
}
return true;
},
onTabDragStart : function TSTService_onTabDragStart(aEvent)
{
var b = this.getTabBrowserFromChild(aEvent.currentTarget);
@ -589,51 +577,14 @@ catch(e) {
return;
var actionInfo = {
action : this.kACTIONS_FOR_SOURCE,
action : this.kACTIONS_FOR_DESTINATION | this.kACTION_MOVE,
event : aEvent
};
var tabsInfo = b.treeStyleTab.getDraggedTabsInfoFromOneTab(actionInfo, tab);
if (tabsInfo.draggedTabs.length <= 1)
return;
var index = tabsInfo.draggedTabs.indexOf(tab);
if (index > -1) {
tabsInfo.draggedTabs.splice(index, 1);
tabsInfo.draggedTabs.unshift(tab);
}
if ('MultipleTabService' in window &&
'setUpTabsDragData' in MultipleTabService) {
MultipleTabService.setUpTabsDragData(aEvent, tabsInfo.draggedTabs);
}
else {
let dt = aEvent.dataTransfer;
tabsInfo.draggedTabs.forEach(function(aTab, aIndex) {
dt.mozSetDataAt(TAB_DROP_TYPE, aTab, aIndex);
dt.mozSetDataAt('text/x-moz-text-internal', this.getCurrentURIOfTab(aTab), aIndex);
}, this);
}
},
getCurrentURIOfTab : function TSTService_getCurrentURIOfTab(aTab)
{
if (aTab.getAttribute('ontap') == 'true') {
// If BarTap ( https://addons.mozilla.org/firefox/addon/67651 ) is installed,
// currentURI is possibly 'about:blank'. So, we have to get correct URI
// from the session histrory.
var b = aTab.linkedBrowser;
try {
var h = b.sessionHistory;
var entry = h.getEntryAtIndex(h.index, false);
return entry.URI;
}
catch(e) {
}
}
// Firefox 4.0-
if (aTab.linkedBrowser.__SS_needsRestore) {
let data = aTab.linkedBrowser.__SS_data;
let entry = data.entries[Math.max(data.index, data.entries.length-1)];
return this.makeURIFromSpec(entry.url);
}
return aTab.linkedBrowser.currentURI;
window['piro.sakura.ne.jp'].tabsDragUtils.startTabsDrag(aEvent, tabsInfo.draggedTabs);
},
onTabbarDragStart : function TSTService_onTabbarDragStart(aEvent, aTabBrowser)

View File

@ -8,6 +8,7 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="res/stopRendering.js" type="application/javascript"/>
<script src="res/tabsDragUtils.js" type="application/javascript"/>
<script src="res/UninstallationListener.js" type="application/javascript"/>
<script src="treestyletab.js" type="application/javascript"/>

View File

@ -350,6 +350,8 @@ TreeStyleTabBrowser.prototype = {
b.addEventListener('MultipleTabHandlerTabsClosing', this, false);
window['piro.sakura.ne.jp'].tabsDragUtils.initTabBrowser(b);
/* Closing collapsed last tree breaks selected tab.
To solve this problem, I override the setter to
@ -1641,6 +1643,8 @@ TreeStyleTabBrowser.prototype = {
b.removeEventListener('MultipleTabHandlerTabsClosing', this, false);
window['piro.sakura.ne.jp'].tabsDragUtils.destroyTabBrowser(b);
TreeStyleTabService.destroyTabDNDObserver(b);
this.tabbarDNDObserver.destroy();
@ -3718,12 +3722,7 @@ TreeStyleTabBrowser.prototype = {
var sourceBrowser = this.getTabBrowserFromChild(aTab);
var dt = aInfo.event && aInfo.event.dataTransfer;
var isMultipleDragEvent = (
dt &&
dt.mozItemCount > 1 &&
Array.slice(dt.mozTypesAt(0)).indexOf(TAB_DROP_TYPE) > -1
);
var isMultipleDragEvent = window['piro.sakura.ne.jp'].tabsDragUtils.isTabsDragging(aInfo.event);
var isMultipleMove = (
isMultipleDragEvent ||
(
@ -3735,7 +3734,7 @@ TreeStyleTabBrowser.prototype = {
if (isMultipleMove) {
draggedTabs = isMultipleDragEvent ?
this.getTabsFromDragEvent(aInfo.event) :
window['piro.sakura.ne.jp'].tabsDragUtils.getDraggedTabs(aInfo.event) :
sourceWindow.MultipleTabService.getSelectedTabs(sourceBrowser);
if (!(aInfo.action & this.kACTIONS_FOR_DESTINATION)) {
draggedRoots = [];
@ -3765,17 +3764,6 @@ TreeStyleTabBrowser.prototype = {
};
},
getTabsFromDragEvent : function TSTBrowser_getTabsFromDragEvent(aEvent)
{
var tabs = [];
var dt = aEvent.dataTransfer;
for (let i = 0, maxi = dt.mozItemCount; i < maxi; i++)
{
tabs.push(dt.mozGetDataAt(TAB_DROP_TYPE, i));
}
return tabs.sort(this.sortTabsByOrder);
},
attachTabsOnDrop : function TSTBrowser_attachTabsOnDrop(aTabs, aParent)
{
this.mTabBrowser.movingSelectedTabs = true; // Multiple Tab Handler