diff --git a/navigator/iTop/GraphNode.as b/navigator/iTop/GraphNode.as index 7a51a0f71..f52d93dc4 100644 --- a/navigator/iTop/GraphNode.as +++ b/navigator/iTop/GraphNode.as @@ -5,6 +5,7 @@ import flash.net.*; import flash.events.*; import flash.text.*; + import flash.xml.*; import flash.ui.ContextMenu; import flash.ui.ContextMenuItem; import iTop.ToolTip; @@ -14,27 +15,46 @@ public class GraphNode extends Sprite { private var m_oIcon:Loader; - private var m_sClass;String; + private var m_sClass:String; + private var m_sClassName: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; + private static const ROUND:Number = 20; + private static const PADDING:Number = 5; + public var m_speed_x:Number = 0; + public var m_speed_y:Number = 0; + public var m_bInDrag:Boolean = false; - public function GraphNode(oParent:Navigator, oPt:Point, sClass:String, iId:Number, sLabel:String, sIconPath:String, sParentKey:String, fZoom:Number) + public function GraphNode(oParent:Navigator, oPt:Point, sClass:String, sClassName:String, iId:Number, sLabel:String, sIconPath:String, sParentKey:String, fZoom:Number, oDetails:Object) { x = oPt.x; y = oPt.y; m_fZoom = fZoom; m_sClass = sClass; + m_sClassName = sClassName; m_iId = iId; + m_sLabel.autoSize = TextFieldAutoSize.LEFT; + m_sLabel.multiline = false; 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; + // Draw the background + graphics.beginFill( 0xf1f1f6, 0.8 ); + graphics.drawRoundRect( m_sLabel.x -PADDING, m_sLabel.y - PADDING, m_sLabel.width+PADDING*2, m_sLabel.height+PADDING*2, ROUND ); + graphics.endFill(); + m_sParentKey = sParentKey; - m_oToolTip = new ToolTip( "

"+m_sClass+"

"+sLabel+"

"); + var sTooltipText:String = "

"+m_sClassName+"

"+sLabel+"

"; + for (var s:String in oDetails) + { + sTooltipText += '

'+s+': '+oDetails[s]+'

'; + } + trace('Tooltip text: '+sTooltipText); + m_oToolTip = new ToolTip(sTooltipText); m_oToolTip.scaleX = 1 / m_fZoom; m_oToolTip.scaleY = 1 / m_fZoom; m_oParent = oParent; @@ -94,11 +114,14 @@ { trace("Click in Node"); m_oParent.m_bChildDragging = true; + m_bInDrag = true; + m_oToolTip.timer.stop(); // Don't show the tooltip while dragging startDrag(); } function mouseReleased(event:MouseEvent):void { + m_bInDrag = false; stopDrag(); m_oParent.m_bChildDragging = false; } @@ -130,5 +153,10 @@ return sDefaultValue; } } + + public function GetLabelWidth():Number + { + return m_sLabel.width; + } } } \ No newline at end of file diff --git a/navigator/iTop/Navigator.as b/navigator/iTop/Navigator.as index 327f27220..b2c08ad45 100644 --- a/navigator/iTop/Navigator.as +++ b/navigator/iTop/Navigator.as @@ -1,4 +1,4 @@ -package iTop +package iTop { import flash.display.*; import flash.geom.*; @@ -8,7 +8,7 @@ import fl.controls.Slider; import fl.events.SliderEvent; import fl.controls.Label; - + // The main canvas public class Navigator extends MovieClip { @@ -28,8 +28,14 @@ protected var m_sObjId:String; // Constants - protected var m_RADIUS = 120; - protected var m_ITEMS_PER_ROW = 8; + protected var m_RADIUS = 150; + protected var m_Q = 0.9; // Electrostatic forces coeff + protected var m_K = 1.0; // Elastic forces coeff + protected var m_Kf = 0.7; // Fluid friction coeff + protected var m_Ks = 30; // Solid friction coeff + protected var m_deltaT = 0.1; // Interval of time between updates + protected var m_MAX_ITEMS_PER_ROW = 8; + protected var m_FOCUS_DELAY_COUNTDOWN = 50; // 50 images to zoom & pan correctly protected var m_fZoom:Number; // Constructor @@ -41,6 +47,8 @@ initParameters(); doLoadData(); addEventListener(Event.ENTER_FRAME, initGraphics); + //Stop scaling the flash content + stage.scaleMode = StageScaleMode.NO_SCALE; } protected function initParameters():void @@ -62,11 +70,6 @@ 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 { @@ -87,14 +90,24 @@ function onZoomChange(event:SliderEvent):void { - m_fZoom = event.value/100; + SetZoomLevel(event.value/100); + } + + function SetZoomLevel(fZoomLevel:Number):void + { + m_fZoom = fZoomLevel; 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; + var sSeparator:String = '?'; + if (m_sDataUrl.indexOf(sSeparator) != -1) + { + sSeparator = '&'; + } + var myString:String = m_sDataUrl+sSeparator+'relation='+m_sRelation+'&class='+m_sObjClass+'&id='+m_sObjId; trace("Requesting:"+myString); var myXMLURL:URLRequest = new URLRequest(myString); m_oLoader = new URLLoader(); @@ -109,10 +122,17 @@ var myXML:XML = XML(m_oLoader.data); //trace("Data loaded." + myXML); //trace("==========================="); - parseXMLData(null, myXML, 0); + parseXMLData(null, myXML, 0, 0); m_sTitle.text = myXML.attribute("title"); m_oZoomSlider.enabled = true; removeChild(m_oPreloader); + 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); + //trace('======= Initial Posistions ========='); + //DumpPositions(); } function onXMLLoadError(event:IOErrorEvent):void @@ -120,11 +140,11 @@ trace("An error occured:" + Event); } - function parseXMLData(oParentNode:GraphNode, oXMLData:XML, iChildIndex:Number) + function parseXMLData(oParentNode:GraphNode, oXMLData:XML, iChildIndex:Number, iChildCount:Number) { //trace(oXMLData.child("node").length()); var oNode:GraphNode; - oNode = addNode(oParentNode, oXMLData.child("node")[0], iChildIndex); + oNode = addNode(oParentNode, oXMLData.child("node")[0], iChildIndex, iChildCount); if (oParentNode != null) { AddLink(oParentNode.GetKey(), oNode.GetKey()); @@ -138,30 +158,43 @@ var oLink = oLinks.link; for each(var oChild:XML in oLink) { - parseXMLData(oNode, oChild, iChildIndex); + parseXMLData(oNode, oChild, iChildIndex, oLinks.link.length()); iChildIndex++; } } } - function addNode(oParent:GraphNode, oXMLData:XML, iChildIndex) + function addNode(oParent:GraphNode, oXMLData:XML, iChildIndex:Number, iChildCount:Number) { - var sClass = oXMLData.@obj_class; + var sClass:String = oXMLData.@obj_class; + var sClassName:String = oXMLData.@obj_class_name; var iId = oXMLData.@id; - var sLabel = oXMLData.@name; - var sIcon = oXMLData.@icon; + var sLabel:String = oXMLData.@name; + var sIcon:String = oXMLData.@icon; + var oDetails:Object = new Object; + var sZlist:String = oXMLData.@zlist; 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 oPt:Point = GetNextFreePosition(oParent, iChildIndex, iChildCount); var sParentKey = null; if (oParent != null) { sParentKey = oParent.GetKey(); } - oNode = new GraphNode(this, oPt, sClass, iId, sLabel, sIcon, sParentKey, m_fZoom); + // Read the details + var aDetails:Array; + aDetails = sZlist.split(','); + for(var i:String in aDetails) + { + //if (oXMLData.hasOwnProperty('att_'+i)) + //{ + oDetails[aDetails[i]] = oXMLData.attribute('att_'+i).toString(); + //} + } + oNode = new GraphNode(this, oPt, sClass, sClassName, iId, sLabel, sIcon, sParentKey, m_fZoom, oDetails); this.m_aNodes[oNode.GetKey()] = oNode; //Keep it referenced if (oParent == null) { @@ -185,7 +218,7 @@ } } - function GetNextFreePosition(oParent:GraphNode, iChildIndex:Number):Point + function GetNextFreePosition(oParent:GraphNode, iChildIndex:Number, iChildCount:Number):Point { var oPt:Point = GetInitialPosition(); var angle:Number = GetInitialAngle(); @@ -208,13 +241,43 @@ 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; + var nbItemsOnRow:Number = 0; + var nbRows:Number = 0; + // Determines the position of this element + // The elements are placed on circles of maximum m_MAX_ITEMS_PER_ROW elements per row + // The last row containing potentially less items + // nbRows indicates on which row (first row = 0) the item is to be placed + if (iChildCount > m_MAX_ITEMS_PER_ROW) + { + nbRows = Math.floor(iChildIndex / m_MAX_ITEMS_PER_ROW); + if ( iChildIndex > (Math.floor(iChildCount / m_MAX_ITEMS_PER_ROW)*m_MAX_ITEMS_PER_ROW)) + { + // node is on the last (incomplete) row + nbItemsOnRow = (iChildCount % m_MAX_ITEMS_PER_ROW); + } + else + { + nbItemsOnRow = m_MAX_ITEMS_PER_ROW; + } + } + else + { + if (iChildCount == 2) + { + nbItemsOnRow = 4; // Nicer display than everything aligned at 180 deg. + } + else + { + nbItemsOnRow = iChildCount; + } + } + var radius = this.m_RADIUS * (1 + nbRows); + angle += (1 - 2*((1+iChildIndex) % 2))*(Math.floor((1+iChildIndex) / 2))*(2*Math.PI) / nbItemsOnRow; - oPt.x += m_RADIUS * Math.cos(angle); - oPt.y += m_RADIUS * Math.sin(angle); + oPt.x += radius * Math.cos(angle); + oPt.y += radius * 0.7 * Math.sin(angle); // Ellipse because the labels are written horizontally ! - trace("iChildIndex: "+iChildIndex+" x: "+oPt.x+" y: "+oPt.y+" sGdParentKey: "+sGrandParentKey); + //trace("iChildIndex: "+iChildIndex+" (iChildCount: "+iChildCount+") x: "+oPt.x+" y: "+oPt.y+" sGdParentKey: "+sGrandParentKey); } return oPt; } @@ -280,8 +343,10 @@ function drawLines(event:Event):void { + var color:uint = 0x666666; m_oCanvas.graphics.clear(); m_oCanvas.graphics.lineStyle(2,0x666666,100); + UpdatePositions(); for (var index:String in m_aLinks) { var oStartNode:GraphNode = GetNode(m_aLinks[index].GetStart()); @@ -289,18 +354,28 @@ 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); + drawArrow(oMiddlePoint, oEndNode.x - oStartNode.x, oEndNode.y - oStartNode.y, color); + } + + if (this.m_FOCUS_DELAY_COUNTDOWN > 0) + { + this.m_FOCUS_DELAY_COUNTDOWN--; + trace('FOCUS_DELAY:'+this.m_FOCUS_DELAY_COUNTDOWN); + UpdatePanAndZoom(m_FOCUS_DELAY_COUNTDOWN / 30); } } - function drawArrow(oPt:Point, dx:Number, dy:Number):void + function drawArrow(oPt:Point, dx:Number, dy:Number, color:uint):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); + if (l > 0) + { + m_oCanvas.graphics.lineStyle(2,color,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) @@ -316,6 +391,226 @@ return sDefaultValue; } } + + public function ComputeElectrostaticForces():Array + { + var aForces:Array = new Array; + //trace('====== BEGIN ComputeElectrostaticForces() ======='); + + for (var i:String in this.m_aNodes) + { + aForces[i] = new Object; + aForces[i].FxTotal = 0; + aForces[i].FyTotal = 0; + var oCurrentNode:GraphNode = GetNode(i); + for (var j:String in this.m_aNodes) + { + if (i != j) + { + var oRemoteNode:GraphNode = GetNode(j); + var dx:Number = oRemoteNode.x - oCurrentNode.x; + var dy:Number = oRemoteNode.y - oCurrentNode.y; + var d2:Number = (dx*dx + dy*dy) / (this.m_RADIUS * this.m_RADIUS); + var d:Number = Math.sqrt(d2); + var Fx:Number = 0; + var Fy:Number = 0; + if (d2 < 0.05) + { + d2 = 0.05; + } + if (d2 < 2 ) // Full influence under 2 * m_RADIUS px + { + Fx = -this.m_Q * dx / d2; + Fy = -this.m_Q * dy / d2; + aForces[i].FxTotal += Fx; + aForces[i].FyTotal += Fy; + } + else if (d2 < 4 ) // Decrease the influence to between 4 and 2 * m_RADIUS px + { + Fx = -this.m_Q * (4 - d2) * dx / d2; + Fy = -this.m_Q * (4 - d2) * dy / d2; + aForces[i].FxTotal += Fx; + aForces[i].FyTotal += Fy; + } + } + } + } + //for (i in this.m_aNodes) + //{ + // trace('ELECTROSTATIC forces on '+i+': Fx='+aForces[i].FxTotal+', Fy='+aForces[i].FyTotal); + // if (Math.abs(aForces[i].FyTotal) > 1) + // { + // for (i in this.m_aNodes) + // { + // var oNode:GraphNode = GetNode(i); + // trace('node: '+i+' (x='+oNode.x+', y='+oNode.y+')'); + // } + // } + //} + //trace('====== END ComputeElectrostaticForces() ======='); + return aForces; + } + + + function ComputeElasticForces() + { + //trace('====== BEGIN ComputeElasticForces() ======='); + var aForces:Array = new Array; + + for (var i:String in this.m_aNodes) + { + aForces[i] = new Object; + aForces[i].FxTotal = 0; + aForces[i].FyTotal = 0; + } + // Elastic forces: each link applies a force proportional to its length (F = - K * x) + for(i in this.m_aLinks) + { + var oStartNode:GraphNode = GetNode(m_aLinks[i].GetStart()); + var oEndNode = GetNode(m_aLinks[i].GetEnd()); + var dx = oStartNode.x - oEndNode.x; + var dy = oStartNode.y - oEndNode.y; + //d = Math.sqrt(dx*dx + dy*dy); + //Fx = -K * d * dx / d; + //Fy = -K * d * dy / d; + // Links with more weight attached are more rigid ! + //weightCoeff = (aWeights[aLinks[l].start] + aWeights[aLinks[l].end])/2; + var Fx = -this.m_K * dx; + var Fy = -this.m_K * dy; + aForces[oStartNode.GetKey()].FxTotal += Fx; + aForces[oStartNode.GetKey()].FyTotal += Fy; + aForces[oEndNode.GetKey()].FxTotal -= Fx; + aForces[oEndNode.GetKey()].FyTotal -= Fy; + } + //for (i in this.m_aNodes) + //{ + // trace('Elastic forces on '+i+': Fx='+aForces[i].FxTotal+', Fy='+aForces[i].FyTotal); + // if (Math.abs(aForces[i].FyTotal) > 1) + // { + // for (i in this.m_aNodes) + // { + // var oNode:GraphNode = GetNode(i); + // trace('node: '+i+' (x='+oNode.x+', y='+oNode.y+')'); + // } + // } + //} + //trace('====== END ComputeElasticForces() ======='); + return aForces; + } + + /** + * Update the nodes' position based on their current movement and the forces applied + */ + function UpdatePositions() + { + //trace('====== BEGIN UpdatePositions() ======='); + var aElasticForces:Array = ComputeElasticForces(); + var aElectrostaticForces:Array = ComputeElectrostaticForces(); + //DrawForces(aElectrostaticForces, 0xcc0000); + //DrawForces(aElectrostaticForces, 0x0000cc); + for (var i:String in this.m_aNodes) + { + var oNode:GraphNode = GetNode(i); + if (!oNode.m_bInDrag) + { + var Fx:Number = aElasticForces[i].FxTotal + aElectrostaticForces[i].FxTotal; + var Fy:Number = aElasticForces[i].FyTotal + aElectrostaticForces[i].FyTotal; + + if ( (Fx*Fx + Fy*Fy) < (this.m_Ks*this.m_Ks)) + { + // Movement is less than minimum level (solid friction) => object is stopped + // otherwise let's keep it moving + oNode.m_speed_x = 0; + oNode.m_speed_y = 0; + //trace('object '+i+' stopped ! (x='+oNode.x+', y='+oNode.y+')'); + } + else + { + oNode.m_speed_x = oNode.m_speed_x*this.m_Kf + this.m_deltaT*Fx; + oNode.m_speed_y = oNode.m_speed_y*this.m_Kf + this.m_deltaT*Fy; + var dx:Number = oNode.m_speed_x * this.m_deltaT; + var dy:Number = oNode.m_speed_y * this.m_deltaT; + oNode.x = Math.round(oNode.x + dx); + oNode.y = Math.round(oNode.y + dy); + //trace('object '+i+' moves (Force: Fx='+Fx+', Fy='+Fy+')! '); + } + } + } + //trace('======= Updated Positions ========='); + //DumpPositions(); + //trace('====== END UpdatePositions() ======='); + } + + public function DrawForces(aForces:Array, color:uint) + { + for (var i:String in aForces) + { + var oNode:GraphNode = GetNode(i); + var oForce:Object = aForces[i]; + m_oCanvas.graphics.lineStyle(2,color,100,false,"normal",CapsStyle.ROUND); + m_oCanvas.graphics.moveTo(oNode.x, oNode.y); + var oEndPoint:Point = new Point; + oEndPoint.x = Math.round(oNode.x + oForce.FxTotal); + oEndPoint.y = Math.round(oNode.y + oForce.FyTotal); + m_oCanvas.graphics.lineTo(oEndPoint.x, oEndPoint.y); + drawArrow(oEndPoint, oForce.FxTotal, oForce.FyTotal, color); + //trace('Drawinf vector '+i+': (x='+oNode.x+', y='+oNode.y+') to (x='+oEndPoint.x+', y='+oEndPoint.y+')'); + } + } + + public function UpdatePanAndZoom(countDownRatio:Number) + { + var sceneRect:Rectangle = null; + for(var i:String in this.m_aNodes) + { + if (sceneRect == null) + { + sceneRect = GetNode(i).getBounds(m_oCanvas); + } + else + { + sceneRect = sceneRect.union(GetNode(i).getBounds(m_oCanvas)); + } + } + if (sceneRect != null) + { + var idealZoomLevel:Number = 1; + trace('Stage dimensions: width:'+stage.stageWidth+' height:'+stage.stageHeight); + + if ((sceneRect.width > stage.stageWidth) || (sceneRect.height > (stage.stageHeight - 50))) + { + var wRatio:Number = stage.stageWidth / sceneRect.width; + var hRatio:Number = (stage.stageHeight - 50) / sceneRect.height; + idealZoomLevel = Math.min(wRatio, hRatio); + SetZoomLevel(idealZoomLevel); + m_oZoomSlider.value = Math.round(100*idealZoomLevel); + + } + var xOffset:Number = 0; + var yOffset:Number = 50; + if (stage.stageWidth > sceneRect.width) + { + xOffset = (stage.stageWidth-sceneRect.width)/2 + } + if (stage.stageHeight > sceneRect.height) + { + yOffset = 50 + (stage.stageHeight-50-sceneRect.height)/2 + } + m_oCanvas.x = xOffset-sceneRect.x; + m_oCanvas.y = yOffset-sceneRect.y; + + trace('Scene bounding rect: x:'+sceneRect.x+' y:'+sceneRect.y+' width:'+sceneRect.width+' height:'+sceneRect.height+' zoomLevel:'+idealZoomLevel); + } + } + + public function DumpPositions() + { + for (var i:String in this.m_aNodes) + { + var oNode:GraphNode = GetNode(i); + trace(i+' Position: (x='+oNode.x+', y='+oNode.y+')'); + } + } } } diff --git a/navigator/iTop/ToolTip.as b/navigator/iTop/ToolTip.as index 3cb79ccbc..898a4b79a 100644 --- a/navigator/iTop/ToolTip.as +++ b/navigator/iTop/ToolTip.as @@ -1,6 +1,7 @@ -package iTop +package iTop { - import flash.display.Sprite; + import flash.display.*; + import flash.geom.*; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFieldAutoSize; @@ -15,17 +16,18 @@ // 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 ROUND:Number = 10; 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 MIN_ALPHA:Number = 0.0; + private static const ALPHA_INC:Number = 0.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; + private static const REFRESH:Number = (MAX_ALPHA - MIN_ALPHA) / ALPHA_INC; + private static const APPEAR_TIMEOUT = 1000; // ms + // For appearence, fading in and out + public var timer:Timer; public function ToolTip( tip:String ) { // Hold onto the tip for posterity @@ -51,7 +53,7 @@ _tf.y += PADDING; addChild( _tf ); // Draw the background - graphics.beginFill( 0xF6F6F1, 1 ); + graphics.beginFill( 0xEEEE99, 0.95 ); graphics.drawRoundRect( 0, 0, _tf.textWidth+PADDING*4, _tf.textHeight+PADDING*4, ROUND ); graphics.endFill(); this.alpha = MIN_ALPHA; @@ -65,28 +67,74 @@ } 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(); + // Make the tooltip appear smoothly after a delay + if (this.timer != null) + { + this.timer.stop(); + } + this.timer = new Timer( APPEAR_TIMEOUT, 1 ); + this.timer.addEventListener( "timer", appear ); + this.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(); + if (this.timer != null) + { + this.timer.stop(); + } + this.timer = new Timer( fadeSpeed, REFRESH ); + this.timer.addEventListener( "timer", fadeOut ); + this.timer.start(); + this.parent.removeEventListener( MouseEvent.MOUSE_OUT, mouse_out ); } + + private function appear(i:uint):void + { + // The delay has elapsed, show (smoothly) the tooltip + if (this.timer != null) + { + this.timer.stop(); + } + // Reuse the time for the fadeIn + this.parent.setChildIndex( this, this.parent.numChildren-1 ) + // Move the tool tip to the top! + var fadeSpeed:Number = 500 / REFRESH; + this.alpha = MIN_ALPHA; + if (this.timer != null) + { + this.timer.stop(); + } + this.timer = new Timer( fadeSpeed, REFRESH ); + this.timer.addEventListener( "timer", fadeIn ); + this.timer.start(); + this.parent.addEventListener( MouseEvent.MOUSE_OUT, mouse_out ); + } + private function fadeIn( i:uint ):void { - this.alpha += ALPHA_INC; + if (this.alpha < (1.0 - ALPHA_INC)) + { + this.alpha += ALPHA_INC; + } + else + { + this.alpha = 1.0; + } + //trace("++ Tooltip alpha: "+this.alpha+" ALPHA_INC:"+ALPHA_INC); } private function fadeOut( i:uint ):void { - this.alpha -= ALPHA_INC; + if (this.alpha > ALPHA_INC) + { + this.alpha -= ALPHA_INC; + } + else + { + this.alpha = 0.0; + } + //trace("-- Tooltip alpha: "+this.alpha+" ALPHA_INC:"+ALPHA_INC); } } } diff --git a/navigator/navigator.fla b/navigator/navigator.fla index 10ddd55dc..8049e4cd1 100755 Binary files a/navigator/navigator.fla and b/navigator/navigator.fla differ diff --git a/navigator/navigator.swf b/navigator/navigator.swf index c8d1ad05d..1eb040b70 100755 Binary files a/navigator/navigator.swf and b/navigator/navigator.swf differ diff --git a/pages/xml.navigator.php b/pages/xml.navigator.php index 04c398a72..e33cd87c5 100755 --- a/pages/xml.navigator.php +++ b/pages/xml.navigator.php @@ -37,7 +37,7 @@ define('MAX_RECURSION_DEPTH', 20); */ function AddNodeDetails(&$oNode, $oObj) { - $aZlist = MetaModel::GetZListItems(get_class($oObj), 'details'); + $aZlist = MetaModel::GetZListItems(get_class($oObj), 'list'); $aLabels = array(); $index = 0; foreach($aZlist as $sAttCode) @@ -46,7 +46,7 @@ function AddNodeDetails(&$oNode, $oObj) $aLabels[] = $oAttDef->GetLabel(); if (!$oAttDef->IsLinkSet()) { - $oNode->SetAttribute('att_'.$index, $oObj->Get($sAttCode)); + $oNode->SetAttribute('att_'.$index, $oObj->GetAsHTML($sAttCode)); } $index++; } @@ -78,6 +78,7 @@ function GetRelatedObjects(DBObject $oObj, $sRelationName, &$oLinks, &$oXmlDoc, $oLinkedNode = $oXmlDoc->CreateElement('node'); $oLinkedNode->SetAttribute('id', $oTargetObj->GetKey()); $oLinkedNode->SetAttribute('obj_class', get_class($oTargetObj)); + $oLinkedNode->SetAttribute('obj_class_name', MetaModel::GetName(get_class($oTargetObj))); $oLinkedNode->SetAttribute('name', $oTargetObj->GetName()); $oLinkedNode->SetAttribute('icon', BuildIconPath($oTargetObj->GetIcon(false /* No IMG tag */))); AddNodeDetails($oLinkedNode, $oTargetObj); @@ -104,11 +105,10 @@ function BuildIconPath($sIconPath) require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -//// For developping the Navigator +// For developping the Navigator from within Flash //session_start(); //$_SESSION['auth_user'] = 'admin'; -//$_SESSION['auth_pwd'] = 'admin2'; -//UserRights::Login($_SESSION['auth_user'], $_SESSION['auth_pwd']); // Set the user's language +//UserRights::Login($_SESSION['auth_user']); // Set the user's language LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oPage = new ajax_page(""); @@ -134,6 +134,7 @@ if ($id != 0) $oXmlNode = $oXmlDoc->CreateElement('node'); $oXmlNode->SetAttribute('id', $oObj->GetKey()); $oXmlNode->SetAttribute('obj_class', get_class($oObj)); + $oXmlNode->SetAttribute('obj_class_name', MetaModel::GetName(get_class($oObj))); $oXmlNode->SetAttribute('name', $oObj->GetName()); $oXmlNode->SetAttribute('icon', BuildIconPath($oObj->GetIcon(false /* No IMG tag */))); // Hard coded for the moment AddNodeDetails($oXmlNode, $oObj);