diff --git a/navigator/iTop/GraphNode.as b/navigator/iTop/GraphNode.as new file mode 100644 index 000000000..7a51a0f71 --- /dev/null +++ b/navigator/iTop/GraphNode.as @@ -0,0 +1,134 @@ +package iTop +{ + import flash.display.*; + import flash.geom.*; + import flash.net.*; + import flash.events.*; + import flash.text.*; + import flash.ui.ContextMenu; + import flash.ui.ContextMenuItem; + import iTop.ToolTip; + import iTop.Navigator; + + // Items to load on the main chart + public class GraphNode extends Sprite + { + private var m_oIcon:Loader; + private var m_sClass;String; + private var m_iId:Number; + private var m_sParentKey:String; + private var m_oToolTip:ToolTip; + private var m_fZoom:Number; + private var m_oParent:Navigator; + + public function GraphNode(oParent:Navigator, oPt:Point, sClass:String, iId:Number, sLabel:String, sIconPath:String, sParentKey:String, fZoom:Number) + { + x = oPt.x; + y = oPt.y; + m_fZoom = fZoom; + m_sClass = sClass; + m_iId = iId; + m_sLabel.text = sLabel; + m_sLabel.autoSize = TextFieldAutoSize.CENTER; + m_sLabel.width = m_sLabel.textWidth; + m_sLabel.x = -m_sLabel.width/2; + m_sLabel.height = m_sLabel.textHeight; + m_sParentKey = sParentKey; + m_oToolTip = new ToolTip( "

"+m_sClass+"

"+sLabel+"

"); + m_oToolTip.scaleX = 1 / m_fZoom; + m_oToolTip.scaleY = 1 / m_fZoom; + m_oParent = oParent; + + var myURL:URLRequest = new URLRequest(sIconPath); + m_oIcon = new Loader(); + m_oIcon.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + m_oIcon.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError); + m_oIcon.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete); + m_oIcon.load(myURL); + addChild(m_oIcon); + addEventListener(MouseEvent.MOUSE_DOWN, mouseDown) + addEventListener(MouseEvent.MOUSE_UP, mouseReleased); + addEventListener( MouseEvent.MOUSE_OVER, mouseOver ); + + var oContextMenu:ContextMenu = new ContextMenu(); + oContextMenu.hideBuiltInItems(); + var oCMI:ContextMenuItem = new ContextMenuItem('Details...'); + oCMI.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, navigateToObjectDetails); + oContextMenu.customItems.push(oCMI); + this.contextMenu = oContextMenu; + + } + + public function GetKey() + { + return m_sClass+'/'+m_iId; + } + + public function GetParentKey() + { + return m_sParentKey; + } + + function onLoadError(event:ErrorEvent):void + { + // Display error message to user in case of loading error. + trace ("Sorry that there is an error during the loading of an external image. The error is:" + "\n" + event); + } + function onLoadComplete(event:Event):void + { + // Add the Loader on the Sprite when the loading is completed + m_oIcon.x = -m_oIcon.width / 2; + m_oIcon.y = -m_oIcon.height + 8; // Slightly shifted downward + + // Construct a tooltip + addChild(m_oToolTip); + addChild(m_oIcon); + trace('m_sLabel, getChildIndex:'+getChildIndex(m_sLabel)); + trace('m_oToolTip, getChildIndex:'+getChildIndex(m_oToolTip)); + //swapChildren(m_oToolTip, ); + // Start the tooltip + m_oToolTip.start(); + } + + function mouseDown(event:MouseEvent):void + { + trace("Click in Node"); + m_oParent.m_bChildDragging = true; + startDrag(); + } + + function mouseReleased(event:MouseEvent):void + { + stopDrag(); + m_oParent.m_bChildDragging = false; + } + + public function mouseOver( e:MouseEvent ):void + { + // Move to the top + parent.setChildIndex( this, this.parent.numChildren-1 ); + } + + private function navigateToObjectDetails(evt:ContextMenuEvent):void + { + var sUrl:String = ReadParam('drillUrl', 'http://localhost/pages/UI.php?operation=details'); + sUrl += '&class='+m_sClass+'&id='+m_iId; + var oReq:URLRequest = new URLRequest(sUrl); + navigateToURL(oReq, '_top'); + } + + public function ReadParam(sName:String, sDefaultValue:String) + { + var paramObj:Object = LoaderInfo(this.root.loaderInfo).parameters; + + if (paramObj.hasOwnProperty(sName)) + { + return unescape(paramObj[sName]); + } + else + { + return sDefaultValue; + } + } + } +} \ No newline at end of file diff --git a/navigator/iTop/Navigator.as b/navigator/iTop/Navigator.as new file mode 100644 index 000000000..327f27220 --- /dev/null +++ b/navigator/iTop/Navigator.as @@ -0,0 +1,351 @@ +package iTop +{ + import flash.display.*; + import flash.geom.*; + import flash.net.*; + import flash.events.*; + import iTop.GraphNode; + import fl.controls.Slider; + import fl.events.SliderEvent; + import fl.controls.Label; + + // The main canvas + public class Navigator extends MovieClip + { + protected var m_oLoader:URLLoader; + protected var m_aNodes:Object; + protected var m_aLinks:Array; + protected var m_oRootNode:GraphNode; + protected var m_oCanvas:NavigatorCanvas; + public var m_bChildDragging:Boolean; + + // Parameters + protected var m_sStartPosition:String; + protected var m_sDataUrl:String; + protected var m_sDetailsUrl:String; + protected var m_sRelation:String; + protected var m_sObjClass:String; + protected var m_sObjId:String; + + // Constants + protected var m_RADIUS = 120; + protected var m_ITEMS_PER_ROW = 8; + protected var m_fZoom:Number; + + // Constructor + public function Navigator() + { + m_aLinks = new Array(); + m_aNodes = new Array(); + m_fZoom = 1; + initParameters(); + doLoadData(); + addEventListener(Event.ENTER_FRAME, initGraphics); + } + + protected function initParameters():void + { + + m_sDataUrl = ReadParam('xmlUrl', 'http://localhost:81/pages/xml.navigator.php?operation=relation'); + m_sDetailsUrl = ReadParam('drillUrl', 'http://localhost/pages/UI.php?operation=details'); + m_sRelation = ReadParam('relation', 'impacts'); + m_sObjClass = ReadParam('obj_class', 'Server'); + m_sObjId = ReadParam('obj_id', '1'); + m_sStartPosition = ReadParam('start_pos', 'left'); + } + + function initGraphics(event:Event):void + { + m_oCanvas = new NavigatorCanvas(); // All drawings will occur here + addChild(m_oCanvas); + m_oCanvas.scaleX = m_fZoom; + m_oCanvas.scaleY = m_fZoom; + // Handle listeners... + removeEventListener(Event.ENTER_FRAME,initGraphics); + addEventListener(Event.ENTER_FRAME, drawLines); + m_oZoomSlider.value = 100; + m_oZoomSlider.addEventListener(SliderEvent.CHANGE, onZoomChange); + stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown) + stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased); + } + function mouseDown(event:MouseEvent):void + { + trace("Click in canvas"); + if (!m_bChildDragging) + { + m_oCanvas.startDrag(); + } + } + + function mouseReleased(event:MouseEvent):void + { + if (!m_bChildDragging) + { + m_oCanvas.stopDrag(); + } + } + + function onZoomChange(event:SliderEvent):void + { + m_fZoom = event.value/100; + m_oCanvas.scaleX = m_fZoom; + m_oCanvas.scaleY = m_fZoom; + } + + function doLoadData() + { + var myString:String = m_sDataUrl+'?relation='+m_sRelation+'&class='+m_sObjClass+'&id='+m_sObjId; + trace("Requesting:"+myString); + var myXMLURL:URLRequest = new URLRequest(myString); + m_oLoader = new URLLoader(); + m_oLoader.addEventListener(Event.COMPLETE, onXMLLoadComplete); + m_oLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onXMLLoadError); + m_oLoader.addEventListener(IOErrorEvent.IO_ERROR, onXMLLoadError); + m_oLoader.load(myXMLURL); + } + + function onXMLLoadComplete(event:Event):void + { + var myXML:XML = XML(m_oLoader.data); + //trace("Data loaded." + myXML); + //trace("==========================="); + parseXMLData(null, myXML, 0); + m_sTitle.text = myXML.attribute("title"); + m_oZoomSlider.enabled = true; + removeChild(m_oPreloader); + } + + function onXMLLoadError(event:IOErrorEvent):void + { + trace("An error occured:" + Event); + } + + function parseXMLData(oParentNode:GraphNode, oXMLData:XML, iChildIndex:Number) + { + //trace(oXMLData.child("node").length()); + var oNode:GraphNode; + oNode = addNode(oParentNode, oXMLData.child("node")[0], iChildIndex); + if (oParentNode != null) + { + AddLink(oParentNode.GetKey(), oNode.GetKey()); + } + //trace('Root node:'+oRoot.toString()); + var oLinks = oXMLData.child("node")[0].links; + var iChildIndex:Number = 0; + if (oLinks.length() > 0) + { + //trace('links: '+oLinks.toString()); + var oLink = oLinks.link; + for each(var oChild:XML in oLink) + { + parseXMLData(oNode, oChild, iChildIndex); + iChildIndex++; + } + } + } + + function addNode(oParent:GraphNode, oXMLData:XML, iChildIndex) + { + var sClass = oXMLData.@obj_class; + var iId = oXMLData.@id; + var sLabel = oXMLData.@name; + var sIcon = oXMLData.@icon; + + var oNode:GraphNode = GetNode(sClass+'/'+iId); + if (oNode == null) + { + // If the node does not already exist, let's create it + var oPt:Point = GetNextFreePosition(oParent, iChildIndex); + var sParentKey = null; + if (oParent != null) + { + sParentKey = oParent.GetKey(); + } + oNode = new GraphNode(this, oPt, sClass, iId, sLabel, sIcon, sParentKey, m_fZoom); + this.m_aNodes[oNode.GetKey()] = oNode; //Keep it referenced + if (oParent == null) + { + m_oRootNode = oNode; + } + m_oCanvas.addChild(oNode); + } + return oNode; + //trace("class: "+sClass+", id: "+iId+", name: "+sLabel+", Icon: "+sIcon); + } + + function GetNode(sKey) + { + if (m_aNodes.hasOwnProperty(sKey)) + { + return m_aNodes[sKey]; + } + else + { + return null; + } + } + + function GetNextFreePosition(oParent:GraphNode, iChildIndex:Number):Point + { + var oPt:Point = GetInitialPosition(); + var angle:Number = GetInitialAngle(); + if (oParent != null) + { + oPt.x = oParent.x; + oPt.y = oParent.y; + var sGrandParentKey:String = oParent.GetParentKey(); + if (sGrandParentKey != null) + { + var oGrandParent:GraphNode = GetNode(sGrandParentKey); + var dx:Number = oParent.x - oGrandParent.x; + var dy:Number = oParent.y - oGrandParent.y; + if ((dx == 0) && (dy == 0)) + { + angle = GetInitialAngle(); + } + else + { + angle = Math.atan2(dy, dx); + } + } + var radius = m_RADIUS * Math.floor(iChildIndex / m_ITEMS_PER_ROW); + angle += iChildIndex*(2*Math.PI) / m_ITEMS_PER_ROW; + + oPt.x += m_RADIUS * Math.cos(angle); + oPt.y += m_RADIUS * Math.sin(angle); + + trace("iChildIndex: "+iChildIndex+" x: "+oPt.x+" y: "+oPt.y+" sGdParentKey: "+sGrandParentKey); + } + return oPt; + } + + function GetInitialPosition():Point + { + trace('width: '+stage.stageWidth+' height: '+stage.stageHeight); + var oPos:Point = new Point(0,0); + switch(m_sStartPosition) + { + case 'left': + oPos.x = m_RADIUS; + oPos.y = stage.stageHeight / 2; + break; + + case 'right': + oPos.x = stage.stageWidth - m_RADIUS; + oPos.y = stage.stageHeight / 2; + break; + + case 'top': + oPos.x = stage.stageWidth/2; + oPos.y = m_RADIUS; + break; + + case 'bottom': + oPos.x = stage.stageWidth/2; + oPos.y = stage.stageHeight - m_RADIUS; + break; + } + return oPos; + } + + function GetInitialAngle():Number + { + var angle:Number; + switch(m_sStartPosition) + { + case 'left': + angle = 0; + break; + + case 'right': + angle = Math.PI; + break; + + case 'top': + angle = -Math.PI / 2; + break; + + case 'right': + angle = Math.PI / 2; + break; + } + return angle; + } + + function AddLink(sStart:String, sEnd:String) + { + var oLink = new Link(sStart, sEnd); + m_aLinks.push(oLink); + } + + function drawLines(event:Event):void + { + m_oCanvas.graphics.clear(); + m_oCanvas.graphics.lineStyle(2,0x666666,100); + for (var index:String in m_aLinks) + { + var oStartNode:GraphNode = GetNode(m_aLinks[index].GetStart()); + var oEndNode = GetNode(m_aLinks[index].GetEnd()); + m_oCanvas.graphics.moveTo(oStartNode.x, oStartNode.y); + m_oCanvas.graphics.lineTo(oEndNode.x, oEndNode.y); + var oMiddlePoint:Point = new Point((oEndNode.x+oStartNode.x)/2, (oEndNode.y+oStartNode.y)/2); + drawArrow(oMiddlePoint, oEndNode.x - oStartNode.x, oEndNode.y - oStartNode.y); + } + } + function drawArrow(oPt:Point, dx:Number, dy:Number):void + { + var l:Number = Math.sqrt(dx*dx+dy*dy); + var arrowSize:Number = 5; + m_oCanvas.graphics.lineStyle(2,0x666666,100,false,"normal",CapsStyle.ROUND); + m_oCanvas.graphics.moveTo(oPt.x, oPt.y); + m_oCanvas.graphics.lineTo(oPt.x + arrowSize*(dy-dx)/l, oPt.y - arrowSize*(dx+dy)/l); + m_oCanvas.graphics.moveTo(oPt.x, oPt.y); + m_oCanvas.graphics.lineTo(oPt.x - arrowSize*(dx+dy)/l, oPt.y - arrowSize*(dy-dx)/l); + } + + public function ReadParam(sName:String, sDefaultValue:String) + { + var paramObj:Object = LoaderInfo(this.root.loaderInfo).parameters; + + if (paramObj.hasOwnProperty(sName)) + { + return unescape(paramObj[sName]); + } + else + { + return sDefaultValue; + } + } + } +} + +class Link extends Object +{ + protected var m_sStart:String; + protected var m_sEnd:String; + public function Link(sStartNodeKey:String, sEndNodeKey:String) + { + m_sStart = sStartNodeKey; + m_sEnd = sEndNodeKey; + } + + public function GetStart():String + { + return m_sStart; + } + public function GetEnd():String + { + return m_sEnd; + } +} + +import flash.display.*; +import flash.geom.*; +import flash.events.*; + +class NavigatorCanvas extends Sprite +{ + public function NavigatorCanvas() + { + } +} diff --git a/navigator/iTop/ToolTip.as b/navigator/iTop/ToolTip.as new file mode 100644 index 000000000..3cb79ccbc --- /dev/null +++ b/navigator/iTop/ToolTip.as @@ -0,0 +1,93 @@ +package iTop +{ + import flash.display.Sprite; + import flash.text.TextField; + import flash.text.TextFormat; + import flash.text.TextFieldAutoSize; + import flash.text.TextLineMetrics; + import flash.display.BlendMode; + import flash.utils.Timer; + import flash.events.MouseEvent; + + public class ToolTip extends Sprite + { + private var _tip:String; + // You'll need this for proper text formatting + private var _tf:TextField = new TextField(); + private var _format:TextFormat = new TextFormat(); + private static const ROUND:Number = 2; + private static const HEIGHT:Number = 25; + private static const FONT_SIZE:uint = 12; + private static const FONT:String = 'Arial'; + private static const PADDING:Number = 5; + private static const MIN_ALPHA:Number = 0; + private static const ALPHA_INC:Number = .1; + private static const MAX_ALPHA:Number = 1; + private static const REFRESH:Number = MAX_ALPHA / ALPHA_INC; + // For fading in and out + private var _timer:Timer; + public function ToolTip( tip:String ) + { + // Hold onto the tip for posterity + _tip = tip; + // This ensures the textfield inherits this class's + // alpha property. Very important because otherwise tf + // would always have an alpha of 1 meaning it will always be visible + this.blendMode = BlendMode.LAYER; + _format.size = FONT_SIZE; + _format.font = FONT; + // Make sure the text behaves and looks + // the way text on a button should + _tf.defaultTextFormat = _format; + // Always call defaultTextFormat before setting text otherwise + // the text doesn't use the formatting defined in tf + _tf.autoSize = TextFieldAutoSize.LEFT; + // You have to set autoSize to TextFieldAutoSize.LEFT + // for box.textWidth to be accurate + _tf.multiline = true; + _tf.htmlText = tip; + _tf.selectable = false; + _tf.x += PADDING; + _tf.y += PADDING; + addChild( _tf ); + // Draw the background + graphics.beginFill( 0xF6F6F1, 1 ); + graphics.drawRoundRect( 0, 0, _tf.textWidth+PADDING*4, _tf.textHeight+PADDING*4, ROUND ); + graphics.endFill(); + this.alpha = MIN_ALPHA; + } + // You have to call this after + // the tooltip has been added to the + // display list + public function start():void + { + this.parent.addEventListener( MouseEvent.MOUSE_OVER, mouse_over ); + } + public function mouse_over( e:MouseEvent ):void + { + this.parent.setChildIndex( this, this.parent.numChildren-1 ) + // Move the tool tip to the top! + var fadeSpeed:Number = 500 / REFRESH; + _timer = new Timer( fadeSpeed, REFRESH ); + _timer.addEventListener( "timer", fadeIn ); + _timer.start(); + this.parent.addEventListener( MouseEvent.MOUSE_OUT, mouse_out ); + } + public function mouse_out( e:MouseEvent ):void + { + var fadeSpeed:Number = 500 / REFRESH; + _timer = new Timer( fadeSpeed, REFRESH ); + _timer.addEventListener( "timer", fadeOut ); + _timer.start(); + } + private function fadeIn( i:uint ):void + { + this.alpha += ALPHA_INC; + } + private function fadeOut( i:uint ):void + { + this.alpha -= ALPHA_INC; + } + } +} + diff --git a/navigator/navigator.fla b/navigator/navigator.fla index d7cd2dc62..10ddd55dc 100755 Binary files a/navigator/navigator.fla and b/navigator/navigator.fla differ diff --git a/navigator/navigator.swf b/navigator/navigator.swf index ab0059935..c8d1ad05d 100755 Binary files a/navigator/navigator.swf and b/navigator/navigator.swf differ