import it.sephiroth.iTreeDnd import mx.events.EventDispatcher import mx.core.UIComponent [Event("double_click")] [Event("drag_start")] [Event("drag_complete")] [Event("drag_fail")] [InspectableList("enabled","visible","dragRules","rowHeight")] /** * Drag and Drop extension for Macromedia Tree component. * It allows you to define different rules for dragging items and folder * into a tree component * @author alessandro crugnola * @version 1.5 */ class it.sephiroth.TreeDnd extends UIComponent implements iTreeDnd { // define component specific variables static var symbolName:String = "TreeDnd" static var symbolOwner:Object = TreeDnd private var className:String = "TreeDnd" static var componentVersion:String = "1.5"; // define private variables private var label:MovieClip private var tree:mx.controls.Tree private var icon:MovieClip private var tree_listener:Object private var controller_mc:MovieClip private var boundingBox_mc:MovieClip private var tfList:Object private var __dropTarget:Array private var _iconFunction:Function private var __rowHeight:Number = 20 private var __dropFunction:Function private var __dragFunction:Function private var TREEDEPTH:Number = 10 private var CONTDEPTH:Number = 11 private var ICONDEPTH:Number = 12 var addEventListener:Function var removeEventListener:Function var dispatchEvent:Function /** * deny drag a folder. * Each of treednd constant can be used associated with multiple other constants * @usage import it.sephiroth.TreeDnd * myDndTree.dragRules = TreeDnd.DENYDRAGFOLDER | TreeDnd.DENYDROPITEM * */ static var DENYDRAGFOLDER:Number = 1 /** deny drag an item node */ static var DENYDRAGITEM:Number = 2 /** deny drop into an item */ static var DENYDROPITEM:Number = 4 /** everything is allowed */ static var DEFAULT:Number = 0 /** deny drop through nodes, only into is allowed */ static var DENYDROPUPSIDEDOWN:Number = 8 /** deny all */ static var DENYALL:Number = DENYDRAGITEM | DENYDRAGFOLDER | DENYDROPITEM | DENYDROPUPSIDEDOWN private var __options:Number = DEFAULT /** * constructor */ function TreeDnd() { } /** * internal, set enabled on off * @param enabled */ private function setEnabled(enabled:Boolean):Void { super.setEnabled(); tree.enabled = enabled } // ****************************************** // Private methods // ****************************************** /** * override UIComponent init constructor * @return Void */ private function init():Void { super.init(); var width = __width var height = __height __options = DEFAULT boundingBox_mc._visible = false boundingBox_mc._width = boundingBox_mc._height = 0; // create the objects to be used in the component createClassObject(mx.controls.Tree, "tree", TREEDEPTH, {_width : width, _height : height}) controller_mc = createEmptyObject("controller_mc", CONTDEPTH); // initialize the event object EventDispatcher.initialize( this ); initTreeListener(); setEnabled(enabled) tree.rowHeight = __rowHeight } /** * * @param Void * @return Void */ function size(Void) : Void { super.size(); tree.setSize( __width, __height, true ); } /** * * @param Void * @return Void */ function draw(Void):Void{ super.draw(); } /** * Initialize Tree listeners and Tree MovieClip controller * @usage * @return Void */ private function initTreeListener():Void { // Tree listeners tfList = new Object(); tfList.selectedItem = undefined tfList.selectedIndex = undefined tfList._parent = this tfList._time = getTimer(); tfList._oldItem = undefined // tree itemRollOver tfList.itemRollOver = function( evt ){ var item = this._parent.tree.getItemAt(evt.index) if( item != undefined ) { var opt:Number = this._parent.dragRules var opt_1:Number = opt if(opt >= TreeDnd.DENYDROPUPSIDEDOWN){ opt_1 = opt ^ TreeDnd.DENYDROPUPSIDEDOWN } if( this._parent.__dragFunction != undefined){ if(!this._parent.__dragFunction( item )){ this.itemRollOut() return } } else { if( (opt_1 == DENYDRAGFOLDER or opt_1 == (DENYDRAGFOLDER | DENYDROPITEM) or opt_1 == (DENYDRAGITEM | DENYDRAGFOLDER) or opt == DENYALL) and this._parent.tree.getIsBranch( item )) { this.itemRollOut() return } else if( !this._parent.tree.getIsBranch( item ) and (opt_1 == DENYDRAGITEM or opt == DENYALL or opt_1 == (DENYDRAGITEM | DENYDROPITEM) or opt_1 == (DENYDRAGITEM | DENYDRAGFOLDER))) { this.itemRollOut() return } } this.selectedItem = item this.selectedIndex = evt.index } else { this.itemRollOut(); } } // tree itemRollOut tfList.itemRollOut = function(evt:Object) { this.selectedItem = undefined this.selectedIndex = undefined } tfList.change = function( evt:Object) { if( (getTimer() - this._time) < 300) { if( evt.target.selectedItem == this._oldItem) { this._parent.dispatchEvent({type:"double_click", target: this._parent.tree}); } } this._oldItem = evt.target.selectedItem this._time = getTimer() } // add tree listeners tree.addEventListener("change", tfList) tree.addEventListener("itemRollOver", tfList) tree.addEventListener("itemRollOut", tfList) // controller functions controller_mc.tree = tree controller_mc.item = undefined controller_mc.index = undefined controller_mc.__canDrop = false controller_mc.__canDropTarget = false controller_mc.__dropTargetMc = undefined controller_mc.__dropIndex = undefined controller_mc.__targetNode = undefined controller_mc.__dragStart = false controller_mc.onMouseDown = function(){ this.__dragStart = false this.item = this._parent.tfList.selectedItem this.index = this._parent.tfList.selectedIndex this.added = false this.__targetNode = undefined this.points = new Array( this._xmouse, this._ymouse); if( this.item == undefined ){ return; } this.onEnterFrame = function(){ /** * canDrop: * -1 => deny always * 0 => deny if dropIndex == undefined * 1 => allow */ this.clear(); var canDrop:Number = -1 var dropIndex:Number = undefined var opt:Number = this._parent.dragRules var opt_1:Number = opt this.__canDropTarget = false this.__dropTargetMc = undefined if(opt >= TreeDnd.DENYDROPUPSIDEDOWN){ opt_1 = opt ^ TreeDnd.DENYDROPUPSIDEDOWN } var targetLabel:Boolean = false var default_icon:String var point:Object = new Object() point.x = this._parent._xmouse point.y = this._parent._ymouse this._parent.localToGlobal( point ) if( !this.added ){ var x = this._xmouse var y = this._ymouse if(Math.abs( x - this.points[0] ) > 2 or Math.abs( y - this.points[1] ) > 2 ){ if( !this.added and this.item != undefined ){ this.__dragStart = true this.added = true this._parent.createIcon( ) this._parent.dispatchEvent({type:"drag_start", target:this.tree, sourceNode: this.item}); } } } else { for(var a = 0; a < this.tree.rows.length; a++) { if( this.tree.rows[a].item != undefined) { //if( this.tree.rows[a].hitTest( this._parent._xmouse, this._parent._ymouse, true) ) if( this.tree.rows[a].hitTest( point.x, point.y, true) ) { var item = this.tree.rows[a] this.__targetNode = item if( item.item == this.item ){ // if the same item, then DENY canDrop = 0; // If trying to DROP an item inside itself, DENY } else if ( this._parent.isSubNode(this.item , item.item ) ){ canDrop = -1; } else { if( this._parent.__dropFunction != undefined ){ canDrop = this._parent.__dropFunction( this.item, this.__targetNode.node ) } else { canDrop = 1; // now core functions.. // check if item can be dropped and where it will be dropped! // deny drop into item 3,4,5,6,7 if( (opt_1 >= (DENYDRAGITEM | DENYDRAGFOLDER) and opt <= DENYALL) and !this.tree.getIsBranch( item.node )){ if( opt == DENYALL ){ canDrop = -1 } else { canDrop = 0; } } } } if( item._ymouse > ((item.bG_mc._height/2) + item.bG_mc._height/4) and opt < DENYDROPUPSIDEDOWN && (this._parent.tree._ymouse + item._height < this._parent.tree._height)) { this.beginFill(this._parent.getStyle("separatorColor") ? this._parent.getStyle("separatorColor") : 0x666666,100) this.drawRect( 0, item._y + item.bG_mc._height, this._parent.tree.width - (this._parent.tree.vSB.width ? this._parent.tree.vSB.width : 0) - 1, item._y + item.bG_mc._height + 1 ) this.endFill(); // try to retrieve the item index if( this.tree.getIsBranch( item.node ) ) { if( this.tree.getIsOpen( item.node )) { // it's the first element of the branch dropIndex = item.rowIndex } } dropIndex = item.rowIndex } break; } } } // Now apply permissions to dragging icon if( canDrop == 1 ) { targetLabel = true this.__canDrop = true } else if(canDrop == 0) { if(dropIndex == undefined) { targetLabel = false this.__canDrop = false } else { targetLabel = true this.__canDrop = true } } else { targetLabel = false this.__canDrop = false } this.__dropIndex = dropIndex default_icon = this._parent._iconFunction( targetLabel ) // dropTarget if(this._parent.dropTarget != undefined){ for(var a in this._parent.dropTarget){ if(this._parent.dropTarget[a].hitTest(point.x, point.y, true)) { targetLabel = true this.__canDropTarget = true this.__dropTargetMc = this._parent.dropTarget[a] break; } } } if( default_icon == undefined ) { default_icon = targetLabel ? "icon_allow_drag" : "icon_deny_drag" } if( this._parent.icon[default_icon]._name != default_icon or this._parent.icon[default_icon] == undefined) { this._parent.icon.attachMovie( default_icon, default_icon , 1 ) } } var _mouseP = new Object(); _mouseP.x = this._parent._xmouse _mouseP.y = this._parent._ymouse //this._parent.globalToLocal( _mouseP ) this._parent.icon._x = _mouseP.x + 5 this._parent.icon._y = _mouseP.y + 15 } this.onEnterFrame(); } /** * mouse up, drag and drop end */ controller_mc.onMouseUp = function(){ delete this.onEnterFrame if( this.__dragStart != true ){ return; } if( this.__canDrop == true ) { var node = this.tree.getItemAt(this.index) var cloned = node.cloneNode(true) if( this.__dropIndex != undefined ) { if( this.tree.getIsBranch( this.__targetNode.item ) and this.tree.getIsOpen( this.__targetNode.item )) { node.removeNode() this.__targetNode.item.addTreeNodeAt(0, cloned ) } else { if(this.__targetNode.item.nextSibling == null) { node.removeNode() this.__targetNode.item.parentNode.addTreeNode( cloned ) } else { if( node != this.__targetNode.item and node != this.__targetNode.item.nextSibling ) // fix by TAKATAMA, Hirokazu { node.removeNode() this.__targetNode.item.parentNode.insertBefore( cloned, this.__targetNode.item.nextSibling ) this.tree.refresh() } } } } else { node.removeNode() this.__targetNode.item.addTreeNode( cloned ) } this._parent.dispatchEvent({type:"drag_complete", target: this.tree, sourceNode: node, targetNode: this.__targetNode.item}) this.tree.dataProvider = this.tree.getDataProvider() } else { this._parent.dispatchEvent({type:"drag_fail", target: this.tree, sourceNode: node, targetNode: this.__targetNode.item}) } if(this.__canDropTarget == true){ var node = this.tree.getItemAt(this.index) this._parent.dispatchEvent({type:"drag_target", target: this.tree, sourceNode: node, targetMc: this.__dropTargetMc}) } this.clear(); this.__canDrop = false this.__dropIndex = undefined this._parent.removeIcon() this.points = new Array(); this.item = undefined this.index = undefined this.added = false } } /** * Internal, remove the dragging mouse icon * @usage * @return Void */ private function removeIcon():Void { icon.removeMovieClip(); } /** * internal, create the dragging icon attaching from library * @usage * @return MovieClip */ private function createIcon():MovieClip { return createEmptyObject( "icon", ICONDEPTH ); } /** * Verify that targetNode is a subnode of dragNode * @usage TreeDnd.isSubNode( sourceNode, targetNode ) * @param dragNode (XMLNode) * @param targetNode (XMLNode) * @return Boolean */ private function isSubNode( dragNode:XMLNode, targetNode:XMLNode):Boolean { var ret:Boolean = false; while( targetNode.parentNode != undefined) { if(targetNode == dragNode) { ret = true; break; } targetNode = targetNode.parentNode } return ret; } // ****************************************** // Getter / Setter // ****************************************** /** * Set the Tree Drag and Drop rules * @usage TreeDnd.dragRules = TreeDnd.DENYDRAGITEM | TreeDnd.DENYDROPITEM * @param value (Number) * @return Void */ [Inspectable(defaultValue=0,type=Number)] public function set dragRules(value:Number) { if(value >= 0 and value <= DENYALL and value != undefined) { __options = value } else { throw new Error("IndexError: value must be an integer between " + DEFAULT + " and " + DENYALL); } /** 0 DEFAULT -> Allow everything 1 DENYDRAGFOLDER -> DENY drag folders 2 DENYDRAGITEM -> DENY drag items 4 DENYDROPITEM -> DENY drop into items 5 DENYDRAGFOLDER | DENYDROPITEM -> Deny Drag folder & deny drop on items 6 DENYDRAGITEM | DENYDROPITEM -> Deny drag item & deny drop item 7 DENYALL -> Deny All 3 DENYDRAGITEM | DENYDRAGFOLDER -> Deny All 8 DENYDROPUPSIDEDOWN -> deny lines */ } /** * return the current drag rules * @usage * @return Number */ public function get dragRules():Number { return __options } // set icon function for display allow/deny dragging icon public function set iconFunction(func:Function) { _iconFunction = func } public function get iconFunction():Function { return _iconFunction } // ****************************************** // Tree public functions // ****************************************** /** * Return a pointer to the current used Tree component * @usage * @return mx.controls.Tree the tree component used */ public function getTree():mx.controls.Tree { return this.tree } public function set dropTarget(mc:Array):Void { this.__dropTarget = mc } public function get dropTarget():Array { return this.__dropTarget } [Inspectable(defaultValue=20,type=Number)] public function set rowHeight(w:Number) { __rowHeight = w this.tree.rowHeight = w } public function get rowHeight():Number { return __rowHeight } /** * set a user defined drop function, * this function must return a boolean * @usage * myDndTree.dropFunction = function(sourceNode:XMLNode, targetNode:XMLNode){ // in this way you can define a custom dragRule * return sourceNode.attributes.name != targetNode.attributes.value * } * */ public function set dropFunction(fn:Function){ __dropFunction = fn } /** * user defined drag function, which decide if item * can be dragged * @usage * myDndTree.dragFunction = function(item:XMLNode){ * return item.attributes.myattribue == 'some value' * } * */ public function set dragFunction(fn:Function){ __dragFunction = fn } }