RetroZilla/extensions/inspector/resources/content/jsutil/xul/inTreeBuilder.js
2015-10-20 23:03:22 -04:00

670 lines
19 KiB
JavaScript

/* ***** 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 mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2001
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Hewitt <hewitt@netscape.com> (original author)
*
* 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 ***** */
/***************************************************************
* inTreeBuilder -------------------------------------------------
* Automatically builds up an tree so that it will display a tabular
* set of data with titled columns and optionally an icon for each row.
* The tree that is supplied must have an treechildren with an
* empty template inside of it.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* REQUIRED IMPORTS:
* chrome://inspector/content/jsutil/xul/DNDUtils.js
****************************************************************/
//////////// global variables /////////////////////
var inTreeBuilderPartCount = 0;
//////////// global constants ////////////////////
////////////////////////////////////////////////////////////////////////////
//// class inTreeBuilder
function inTreeBuilder(aTree, aNameSpace, aArcName)
{
this.tree = aTree;
this.nameSpace = aNameSpace;
this.arcName = aArcName;
this.mColumns = [];
this.mExtras = [];
}
inTreeBuilder.prototype =
{
mTree: null,
mTreeBody: null,
// datasource stuff
mNameSpace: null,
mArcName: null,
mIsRefContainer: false,
mIsContainer: true,
// table structure stuff
mIsIconic: false,
mColumns: null,
mSplitters: true,
mRowFields: null,
mRowAttrs: null,
mAllowDND: false,
mLastDragCol: null,
mLastDragColWhere: null,
////////////////////////////////////////////////////////////////////////////
//// properties
// the xul tree node we will construct
get tree() { return this.mTree },
set tree(aVal)
{
this.mTree = aVal;
if (aVal)
this.mTreeBody = aVal.getElementsByTagName("treechildren")[0];
aVal._treeBuilder = this
},
// the namespace to use for all fields
get nameSpace() { return this.mNameSpace },
set nameSpace(aVal) { this.mNameSpace = aVal },
// the name of the arc to the container
get arcName() { return this.mArcName },
set arcName(aVal) { this.mArcName = aVal },
// is the datasource ref an arc to a container, or a container itself
get isRefContainer() { return this.mIsRefContainer },
set isRefContainer(aVal) { this.mIsRefContainer = aVal },
// is each row a potential container?
get isContainer() { return this.mIsContainer },
set isContainer(aVal) { this.mIsContainer = aVal },
// place an icon before the first column of each row
get isIconic() { return this.mIsIconic },
set isIconic(aVal) { this.mIsIconic = aVal },
// put splitters between the columns?
get useSplitters() { return this.mSplitters },
set useSplitters(aVal) { this.mSplitters = aVal },
// extra attributes to set on the treeitem
get rowAttributes() { return this.mRowAttrs },
set rowAttributes(aVal) { this.mRowAttrs = aVal },
// extra data fields to set on the treeitem
get rowFields() { return this.mRowFields },
set rowFields(aVal) { this.mRowFields = aVal },
// extra data fields to set on the treeitem
get allowDragColumns() { return this.mAllowDND },
set allowDragColumns(aVal) { this.mAllowDND = aVal },
////////////////////////////////////////////////////////////////////////////
//// event handlers
get onColumnAdd() { return this.mOnColumnAdd },
set onColumnAdd(aFn) { this.mOnColumnAdd = aFn },
get onColumnRemove() { return this.mOnColumnRemove },
set onColumnRemove(aFn) { this.mOnColumnRemove = aFn },
////////////////////////////////////////////////////////////////////////////
//// initialization
initialize: function()
{
this.initColumns();
this.initTemplate();
},
initColumns: function()
{
this.initDND();
},
initTemplate: function()
{
var template = this.mTree.getElementsByTagNameNS(kXULNSURI, "template")[0];
this.mTemplate = template;
this.clearChildren(template);
var rule = document.createElementNS(kXULNSURI, "rule");
template.appendChild(rule);
this.mRule = rule;
this.createDefaultConditions();
var bindings = document.createElementNS(kXULNSURI, "bindings");
rule.appendChild(bindings);
this.mBindings = bindings;
this.createDefaultAction();
},
createDefaultConditions: function()
{
var conditions = document.createElementNS(kXULNSURI, "conditions");
this.mRule.appendChild(conditions);
var content = document.createElementNS(kXULNSURI, "treerow");
content.setAttribute("uri", "?uri");
conditions.appendChild(content);
var triple = this.createTriple("?uri", this.mNameSpace+this.mArcName,
this.mIsRefContainer ? "?table" : "?row");
conditions.appendChild(triple);
if (this.mIsRefContainer) {
var member = this.createMember("?table", "?row");
conditions.appendChild(member);
}
},
createDefaultAction: function()
{
var action = document.createElementNS(kXULNSURI, "action");
this.mRule.appendChild(action);
var orow = this.createTemplatePart("treerow");
orow.setAttribute("uri", "?row");
action.appendChild(orow);
this.mTreeRow = orow;
// assign the item attributes
if (this.mRowAttrs)
for (key in this.mRowAttrs)
orow.setAttribute(key, this.mRowAttrs[key]);
},
createDefaultBindings: function()
{
// assign the item fields
var binding;
if (this.mRowFields) {
var props = "";
for (key in this.mRowFields) {
binding = this.createBinding(this.mRowFields[key]);
this.mBindings.appendChild(binding);
props += key+"-?"+this.mRowFields[key]+" ";
}
this.mTreeRow.setAttribute("properties", props);
}
},
createTriple: function(aSubject, aPredicate, aObject)
{
var triple = document.createElementNS(kXULNSURI, "triple");
triple.setAttribute("subject", aSubject);
triple.setAttribute("predicate", aPredicate);
triple.setAttribute("object", aObject);
return triple;
},
createMember: function(aContainer, aChild)
{
var member = document.createElementNS(kXULNSURI, "member");
member.setAttribute("container", aContainer);
member.setAttribute("child", aChild);
return member;
},
reset: function()
{
this.mColumns = [];
this.mIsIconic = false;
this.resetTree();
},
resetTree: function()
{
var kids = this.mTree.childNodes;
for (var i = kids.length - 1; i >= 0; --i)
if (kids[i].localName != "treechildren")
this.mTree.removeChild(kids[i]);
this.clearChildren(this.mBindings);
this.clearChildren(this.mTreeRow);
this.createDefaultBindings();
},
clearChildren: function(aEl)
{
while (aEl.hasChildNodes())
aEl.removeChild(aEl.lastChild);
},
////////////////////////////////////////////////////////////////////////////
//// column drag and drop
initDND: function()
{
if (this.mAllowDND) {
// addEventListener doesn't work for dnd events, apparently... so we use the attributes
this.addDNDListener(this.mTree, "ondragenter");
this.addDNDListener(this.mTree, "ondragover");
this.addDNDListener(this.mTree, "ondragexit");
this.addDNDListener(this.mTree, "ondraggesture");
this.addDNDListener(this.mTree, "ondragdrop");
}
},
onDragEnter: function(aEvent)
{
},
onDragOver: function(aEvent)
{
if (!DNDUtils.checkCanDrop("TreeBuilder/column-add"))
return;
var idx = this.getColumnIndexFromX(aEvent.clientX, 0.5);
this.mColumnInsertIndex = idx;
var col = this.getColumnAt(idx);
if (idx == -1)
this.markColumnInsert(col, "after");
else
this.markColumnInsert(col, "before");
},
onDragExit: function(aEvent)
{
},
onDragDrop: function(aEvent)
{
this.markColumnInsert(null);
var dragService = XPCU.getService("@mozilla.org/widget/dragservice;1", "nsIDragService");
var dragSession = dragService.getCurrentSession();
if (!dragSession.isDataFlavorSupported("TreeBuilder/column-add"))
return false;
var trans = XPCU.createInstance("@mozilla.org/widget/transferable;1", "nsITransferable");
trans.addDataFlavor("TreeBuilder/column-add");
dragSession.getData(trans, 0);
var data = {};
trans.getAnyTransferData ({}, data, {});
var string = XPCU.QI(data.value, "nsISupportsString");
this.insertColumn(this.mColumnInsertIndex,
{
name: string.data,
title: string.data,
flex: 1
});
// if we rebuildContent during this thread, it will crash in the dnd service
setTimeout(function(me) { me.build(); me.buildContent() }, 1, this);
// bug 56270 - dragSession.sourceDocument is null --
// causes me to code this very temporary, very nasty hack
// to tell columnsDialog.js about the drop
if (this.mTree.onClientDrop) {
this.mTree.onClientDrop();
}
},
markColumnInsert: function (aColumn, aWhere)
{
var col = this.mLastDragCol;
var lastWhere = this.mLastDragColWhere;
if (col)
col.setAttribute("properties", "");
if (aWhere != "before" && aWhere != "after") {
this.mLastDragCol = null;
this.mLastDragColWhere = null;
return;
}
col = aColumn;
if (col) {
this.mLastDragCol = col;
this.mLastDragColWhere = aWhere;
col.setAttribute("properties", "dnd-insert-"+aWhere);
}
var bx = this.mTree.boxObject.QueryInterface(Components.interfaces.nsITreeBoxObject);
bx.invalidate();
},
getColumnIndexFromX: function(aX, aThresh)
{
var width = 0;
var col, cw;
for (var i = 0; i < this.columnCount; ++i) {
col = this.getColumnAt(i);
cw = col.boxObject.width;
width += cw;
if (width-(cw*aThresh) > aX)
return i;
}
return -1;
},
getColumnIndexFromHeader: function(aHeader)
{
var headers = this.mTree.getElementsByTagName("treecol");
for (var i = 0; i < headers.length; ++i) {
if (headers[i] == aHeader)
return i;
}
return -1;
},
//// for drag-n-drop removal/arrangement of columns
onDragGesture: function(aEvent)
{
var target = aEvent.target;
if (target.parentNode == this.mTree) {
var column = target.getAttribute("label");
var idx = this.getColumnIndexFromHeader(target);
if (idx == -1) return;
this.mColumnDragging = idx;
DNDUtils.invokeSession(target, ["TreeBuilder/column-remove"], [column]);
}
},
addColumnDropTarget: function(aBox)
{
aBox._treeBuilderDropTarget = this;
this.addDNDListener(aBox, "ondragover", "Target");
this.addDNDListener(aBox, "ondragdrop", "Target");
},
removeColumnDropTarget: function(aBox)
{
aBox._treeBuilderDropTarget = this;
this.removeDNDListener(aBox, "ondragover", "Target");
this.removeDNDListener(aBox, "ondragdrop", "Target");
},
onDragOverTarget: function(aBox, aEvent)
{
DNDUtils.checkCanDrop("TreeBuilder/column-remove");
},
onDragDropTarget: function(aBox, aEvent)
{
this.removeColumn(this.mColumnDragging);
this.build();
// if we rebuildContent during this thread, it will crash in the dnd service
setTimeout(function(aTreeBuilder) { aTreeBuilder.buildContent() }, 1, this);
},
// these are horrible hacks to compensate for the lack of true multiple
// listener support for the DND events
addDNDListener: function(aBox, aType, aModifier)
{
var js = "inTreeBuilder_"+aType+(aModifier?"_"+aModifier:"")+"(this, event);";
var attr = aBox.getAttribute(aType);
attr = attr ? attr : "";
aBox.setAttribute(aType, attr + js);
},
removeDNDListener: function(aBox, aType, aModifier)
{
var js = "inTreeBuilder_"+aType+(aModifier?"_"+aModifier:"")+"(this, event);";
var attr = aBox.getAttribute(aType);
var idx = attr.indexOf(js);
if (idx != -1)
attr = attr.substr(0,idx) + attr.substr(idx+1);
aBox.setAttribute(aType, attr);
},
////////////////////////////////////////////////////////////////////////////
//// column properties
addColumn: function(aData)
{
this.mColumns.push(aData);
if (this.mOnColumnAdd)
this.mOnColumnAdd(this.mColumns.length-1)
},
insertColumn: function(aIndex, aData)
{
var idx;
if (aIndex == -1) {
this.mColumns.push(aData);
idx = this.mColumns.length-1;
} else {
this.mColumns.splice(aIndex, 0, aData);
idx = aIndex;
}
if (this.mOnColumnAdd)
this.mOnColumnAdd(idx);
},
removeColumn: function(aIndex)
{
this.mColumns.splice(aIndex, 1);
if (this.mOnColumnRemove)
this.mOnColumnRemove(aIndex);
},
getColumnAt: function(aIndex)
{
var kids = this.mTree.getElementsByTagName("treecol");
return aIndex < 0 || aIndex >= kids.length ? kids[kids.length-1] : kids[aIndex];
},
get columnCount() { return this.mColumns.length },
getColumnName: function(aIndex) { return this.mColumns[aIndex].name },
setColumnName: function(aIndex, aValue) { this.mColumns[aIndex].name = aValue },
getColumnTitle: function(aIndex) { return this.mColumns[aIndex].title },
setColumnTitle: function(aIndex, aValue) { this.mColumns[aIndex].title = aValue },
getColumnClassName: function(aIndex) { return this.mColumns[aIndex].className },
setColumnClassName: function(aIndex, aValue) { this.mColumns[aIndex].className = aValue },
getColumnFlex: function(aIndex) { return this.mColumns[aIndex].flex },
setColumnFlex: function(aIndex, aValue) { this.mColumns[aIndex].flex = aValue },
getExtras: function(aIndex) { return this.mColumns[aIndex].extras },
setExtras: function(aIndex, aValue) { this.mColumns[aIndex].extras = aExtras },
getAttrs: function(aIndex) { return this.mColumns[aIndex].attrs },
setAttrs: function(aIndex, aValue) { this.mColumns[aIndex].attrs = aExtras },
////////////////////////////////////////////////////////////////////////////
//// template building
build: function(aBuildContent)
{
try {
this.resetTree();
this.buildColumns();
this.buildTemplate();
if (aBuildContent)
this.buildContent();
} catch (ex) {
debug("### ERROR - inTreeBuilder::build failed.\n" + ex);
}
//dumpDOM2(this.mTree);
},
buildContent: function()
{
this.mTreeBody.builder.rebuild();
},
buildColumns: function()
{
var cols = document.createElementNS(kXULNSURI, "treecols");
var col, val, split;
for (var i = 0; i < this.mColumns.length; i++) {
col = document.createElementNS(kXULNSURI, "treecol");
col.setAttribute("id", "treecol-"+this.mColumns[i].name);
col.setAttribute("persist", "width");
col.setAttribute("label", this.mColumns[i].title);
// mark first node as primary, if necessary
if (i == 0 && this.mIsContainer)
col.setAttribute("primary", "true");
val = cols[i].className;
if (val)
col.setAttribute("class", val);
val = cols[i].flex;
if (val)
col.setAttribute("flex", val);
cols.appendChild(col);
if (this.mSplitters && i < this.mColumns.length-1) {
split = document.createElementNS(kXULNSURI, "splitter");
split.setAttribute("class", "tree-splitter");
cols.appendChild(split);
}
}
this.mTree.appendChild(cols);
},
buildTemplate: function()
{
var cols = this.mColumns;
var bindings = this.mBindings;
var row = this.mTreeRow;
var cell, binding, val, extras, attrs, key, className;
if (this.mIsIconic) {
binding = this.createBinding("_icon");
bindings.appendChild(binding);
}
for (var i = 0; i < cols.length; ++i) {
val = cols[i].name;
if (!val)
throw "Column data is incomplete - missing name at index " + i + ".";
cell = this.createTemplatePart("treecell");
className = "";
// build the default value data field
binding = this.createBinding(val);
bindings.appendChild(binding);
cell.setAttribute("label", "?" + val);
cell.setAttribute("ref", "treecol-"+cols[i].name);
cell.setAttribute("class", className);
var props = "";
for (key in this.mRowFields)
props += key+"-?"+this.mRowFields[key]+" ";
cell.setAttribute("properties", props);
row.appendChild(cell);
}
},
createBinding: function(aName)
{
var binding = document.createElementNS(kXULNSURI, "binding");
binding.setAttribute("subject", "?row");
binding.setAttribute("predicate", this.mNameSpace+aName);
binding.setAttribute("object", "?" + aName);
return binding;
},
createTemplatePart: function(aTagName)
{
var el = document.createElementNS(kXULNSURI, aTagName);
el.setAttribute("id", "templatePart"+inTreeBuilderPartCount);
inTreeBuilderPartCount++;
return el;
}
};
function inTreeBuilder_ondraggesture(aTree, aEvent)
{
return aTree._treeBuilder.onDragGesture(aEvent);
}
function inTreeBuilder_ondragenter(aTree, aEvent)
{
return aTree._treeBuilder.onDragEnter(aEvent);
}
function inTreeBuilder_ondragover(aTree, aEvent)
{
return aTree._treeBuilder.onDragOver(aEvent);
}
function inTreeBuilder_ondragexit(aTree, aEvent)
{
return aTree._treeBuilder.onDragExit(aEvent);
}
function inTreeBuilder_ondragdrop(aTree, aEvent)
{
return aTree._treeBuilder.onDragDrop(aEvent);
}
function inTreeBuilder_ondragover_Target(aBox, aEvent)
{
return aBox._treeBuilderDropTarget.onDragOverTarget(aBox, aEvent);
}
function inTreeBuilder_ondragdrop_Target(aBox, aEvent)
{
return aBox._treeBuilderDropTarget.onDragDropTarget(aBox, aEvent);
}