
Ext.tree.FixedMultiSelectionModel = function(config){
    Ext.tree.FixedMultiSelectionModel.superclass.constructor.call(this, config);
};
Ext.extend(Ext.tree.FixedMultiSelectionModel, Ext.tree.MultiSelectionModel, {
	// disabled tracking of mouse clicks because it doubles up drag selection...
    onNodeClick : function(node, e){
    	if (e.shiftKey) e.preventDefault();
        // this.select(node);
    },

	// private
	sortSelNodes: function() {
        if (this.selNodes.length > 0) {
        	// sort nodes into document order.. (taken from quirksmode)
			if (this.selNodes[0].ui.elNode.sourceIndex) {
				// IE source index method
				this.selNodes.sort(function (a,b) {
						return a.ui.elNode.sourceIndex - b.ui.elNode.sourceIndex;
					});
			} else if (this.selNodes[0].ui.elNode.compareDocumentPosition) {
				// W3C DOM lvl 3 method (Gecko)
				this.selNodes.sort(function (a,b) {
						return 3 - (a.ui.elNode.compareDocumentPosition(b.ui.elNode) & 6);
					});
			}
		}
	},

	// overwritten from MultiSelectionModel to fix unselecting...
    select : function(node, e, keepExisting){
    	// Add in setting an array as selected... (for multi-selecting D&D nodes)
	    if(node instanceof Array){
	    	for (var c=0;c<node.length;c++) {
				this.selMap[node[c].id] = node[c];
				this.selNodes.push(node[c]);
				node[c].ui.onSelectedChange(true);
	    	}
	    	this.sortSelNodes();
			this.fireEvent("selectionchange", this, this.selNodes, this.lastSelNode);
			return node;
	    }
		// Shift Select to select a range
		// NOTE: Doesn't change lastSelNode
		// EEK has to be a prettier way to do this
		if (e.shiftKey && this.selNodes.length > 0) {
			this.lastSelNode = this.lastSelNode || this.selNodes[0];
			var before = false;
			if (this.lastSelNode == node) {
			} else if (node.ui.elNode.sourceIndex) {
				// IE source index method
				before = (this.lastSelNode.ui.elNode.sourceIndex - node.ui.elNode.sourceIndex) < 0;
			} else if (node.ui.elNode.compareDocumentPosition) {
				// W3C DOM lvl 3 method (Gecko)
				var rel = this.lastSelNode.ui.elNode.compareDocumentPosition(node.ui.elNode);
				before = !!(rel & 2);
			}
			this.clearSelections(true);
			var cont = true;
			var inside = false;
			var parent = this.lastSelNode;
			// ummm... yeah don't read this bit...
			do {
				for (var next=parent;next!=null;next=(before?next.previousSibling:next.nextSibling)) {
					// hack to make cascade work the way I want it to
					inside = inside || (before && next.contains(node));
					next.cascade(function(n) {
						console.debug(cont, n);
						if (cont != inside) {
							this.selNodes.push(n);
							this.selMap[n.id] = n;
							n.ui.onSelectedChange(true);
						}
						cont = (cont && n != node);
						return true;
					}, this);
					if (!cont) break;
				}
				if (!cont) break;
				while ((parent = parent.parentNode) != null) {
					if (before) {
						this.selNodes.push(parent);
						this.selMap[parent.id] = parent;
						parent.ui.onSelectedChange(true);						
					}
					cont = (cont && parent != node);
					if (before && parent.previousSibling) {
						parent = parent.previousSibling;
						break;
					}
					if (!before && parent.nextSibling) {
						parent = parent.nextSibling;
						break;
					}
				}
				if (!cont) break;
			} while (parent != null);
			if (!node.isSelected()) {
				this.selNodes.push(node);
				this.selMap[node.id] = node;
				node.ui.onSelectedChange(true);				
			}
			this.sortSelNodes();
	        this.fireEvent("selectionchange", this, this.selNodes, node);
	        e.preventDefault();
	        return node;
        } else if(keepExisting !== true) {
            this.clearSelections(true);
        }
        if(this.isSelected(node)) {
        	// handle deselect of node...
	       	if (keepExisting === true) {
        		this.unselect(node);
        		if (this.lastSelNode === node) {
        			this.lastSelNode = this.selNodes[0];
        		}
        		return node;
        	}
            this.lastSelNode = node;
            return node;
        }
        // save a resort later on...
        this.selNodes.push(node);
        this.selMap[node.id] = node;
        node.ui.onSelectedChange(true);
		this.sortSelNodes();
        this.lastSelNode = node;
        this.fireEvent("selectionchange", this, this.selNodes, this.lastSelNode);
        return node;
    },
  
  	// returns selected nodes precluding children of other selected nodes...  
  	// used for multi drag and drop...
    getUniqueSelectedNodes: function() {
    	var ret = [];
		for (var c=0;c<this.selNodes.length;c++) {
			var parent = this.selNodes[c];
			ret.push(parent);
			// nodes are sorted(?) so skip over subsequent nodes inside this one..
			while ((c+1)<this.selNodes.length && parent.contains(this.selNodes[c+1])) c++;
		}
		return ret;
    }
});

Ext.tree.MultiTreeDragZone = function(tree, config) {
    Ext.tree.MultiTreeDragZone.superclass.constructor.call(this, tree, config);
}
Ext.extend(Ext.tree.MultiTreeDragZone, Ext.tree.TreeDragZone, {

    onBeforeDrag : function(data, e){
    	return !data.nodes || data.nodes.length > 0;
    },

	// what a mess!!!
	// fixed to handle multiSelectionModel, however the result is very hacky
	// known bugs:
	// sometimes doesn't drag (dragging from spacer or handles...)
	getDragData : function(e) {
		// use tree selection model..
		var selModel = this.tree.getSelectionModel();
		// get event target
		var target = Ext.dd.Registry.getHandleFromEvent(e);
		// if no target (die)
		if (target == null) return;
		if (target.node.isSelected() && e.ctrlKey) {
			selModel.unselect(target.node);
			return;
		}
		var selNodes = [];
		if (!selModel.getSelectedNodes) {
			// if not multiSelectionModel.. just use the target...
			selNodes = [target.node];
		} else {
			// if target not selected select it...
			if (!target.node.isSelected() || e.shiftKey) {
				selModel.select(target.node, e, e.ctrlKey);
			}
			// get selected nodes - nested nodes...
			selNodes = selModel.getUniqueSelectedNodes();
		}
        // if no nodes selected stop now...
        if (!selNodes || selNodes.length < 1) return;
		var dragData = {
			nodes: selNodes
		};
		// create a container for the proxy...
		var div = document.createElement('ul'); // create the multi element drag "ghost"
		// add classes to keep is pretty...
		div.className = 'x-tree-node-ct x-tree-lines';
		// add actual dom nodes to div (instead of tree nodes)
		var height = 0;
		for(var i = 0, len = selNodes.length; i < len; i++) {
			height += Ext.fly(selNodes[i].ui.elNode.parentNode).getHeight();
			// add entire node to proxy
			div.appendChild(selNodes[i].ui.elNode.parentNode.cloneNode(true));
			// limit proxy height to around 150px (need setting for this really)
			if (height>150 && (i+1)<selNodes.length) {
				var elipsis = document.createElement("div");
				elipsis.innerHTML = "<b>...</b>";
				div.appendChild(elipsis);
				break;
			}
		}
		// fix extra indenting by removing extra spacers 
		// should really modify UI rendering code to render a duplicate subtree but this is simpler...
		// no idea if this really gets all nodes or not...
		var nodes = Ext.query(".x-tree-node-el", div);
		for (var c=0;c<nodes.length;c++) {
			// remove highlighting...
			Ext.fly(nodes[c]).removeClass(['x-tree-selected','x-tree-node-over']);
			// start at 1 to leave in folder/user icon
			var depth = 1;
			// calculate indenting required in proxy
			for (var node=nodes[c].parentNode.parentNode;node!=null && node.parentNode!=null;node=node.parentNode.parentNode) {
				depth++;
			}
			var spacers = Ext.query("img", nodes[c]);
			for (var r=0;r<spacers.length&&r<spacers.length-depth;r++) {
				spacers[r].parentNode.removeChild(spacers[r]);
			}
		}
		dragData.ddel = div;
		return dragData;
    },

    // fix from TreeDragZone (references dragData.node instead of dragData.nodes)
    onInitDrag : function(e){
        var data = this.dragData;
        this.tree.eventModel.disable();
        this.proxy.update("");
		this.proxy.ghost.dom.appendChild(data.ddel);
        this.tree.fireEvent("startdrag", this.tree, data.nodes, e);
    },

	// Called from TreeDropZone (looks like hack for handling multiple tree nodes)
    getTreeNode: function() {
    	return this.dragData.nodes;
    },

    // fix from TreeDragZone (refers to data.node instead of data.nodes)
    getRepairXY : function(e, data){
        return data.nodes[0].ui.getDDRepairXY();
    },
    
    // fix from TreeDragZone (refers to data.node instead of data.nodes)
    onEndDrag : function(data, e){
        this.tree.eventModel.enable.defer(100, this.tree.eventModel);
        this.tree.fireEvent("enddrag", this.tree, data.nodes, e);
    },

    // fix from TreeDragZone (refers to dragData.node instead of dragData.nodes)
    onValidDrop : function(dd, e, id){
        this.tree.fireEvent("dragdrop", this.tree, this.dragData.nodes, dd, e);
        this.hideProxy();
    },
    
    // fix for invalid Drop
    beforeInvalidDrop : function(e, id){
        // this scrolls the original position back into view
        var sm = this.tree.getSelectionModel();
        sm.clearSelections();
        sm.select(this.dragData.nodes, e, true);
    }
    
});

Ext.tree.MultiTreeDropZone = function(tree, config) {
    Ext.tree.MultiTreeDropZone.superclass.constructor.call(this, tree, config);
}
Ext.extend(Ext.tree.MultiTreeDropZone, Ext.tree.TreeDropZone, {
	
	// fix from TreeDropZone (referred to data.node instead of data.nodes)
    isValidDropPoint : function(n, pt, dd, e, data){
        if(!n || !data){ return false; }
        var targetNode = n.node;
        var dropNodes = data.nodes?data.nodes:[data.node];
        // default drop rules
        if(!(targetNode && targetNode.isTarget && pt)){
            return false;
        }
        if(pt == "append" && targetNode.allowChildren === false){
            return false;
        }
        if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
            return false;
        }
        for (var c=0;c<dropNodes.length;c++) {
	        if(dropNodes[c] && (targetNode == dropNodes[c] || dropNodes[c].contains(targetNode))){
	            return false;
	        }
	    }
        // reuse the object
        var overEvent = this.dragOverData;
        overEvent.tree = this.tree;
        overEvent.target = targetNode;
        overEvent.data = data;
        overEvent.point = pt;
        overEvent.source = dd;
        overEvent.rawEvent = e;
        overEvent.dropNode = dropNodes;
        overEvent.cancel = false;  
        var result = this.tree.fireEvent("nodedragover", overEvent);
        return overEvent.cancel === false && result !== false;
    },
    
    // fix from TreeDropZone (referred to data.node instead of data.nodes)
    afterRepair : function(data){
        if(data && Ext.enableFx){
        	for (var c=0;c<data.nodes.length;c++) {
            	data.nodes[c].ui.highlight();
            }
        }
        this.hideProxy();
    }
    
});
