GraphViewMySqlPhp

From CSclasswiki
Jump to: navigation, search
//// GraphViewMySqlPhp.java
// D. Thiebaut
//    _                _ _           _   _             
//   / \   _ __  _ __ | (_) ___ __ _| |_(_) ___  _ __  
//  / _ \ | '_ \| '_ \| | |/ __/ _` | __| |/ _ \| '_ \
// / ___ \| |_) | |_) | | | (_| (_| | |_| | (_) | | | |
///_/   \_\ .__/| .__/|_|_|\___\__,_|\__|_|\___/|_| |_|
//        |_|   |_|           
//
// Adapted from code by <a href="http://jheer.org">jeffrey heer</a>
// VERSIONS
// Version 17: with tooltips
// Version 16: Remove the automatic freeze after 5 sec and remove the extra
//             forces that were created (wrongly)
// Version 15: Makes animation restart with force field sliders are moved
// Version 14: stops animation after 5 sec, and zoom to fit afterward
// Version 13: Added delay and SlowInSlowOutPacer
// Version 12: uses applet context to open URL in browser window
// Version 11: 3/29/09
// Supports "url" field in mySql database.  Works with getdata3.php
//
// Version 10: 3/28/09
//            Added many features: save image, freeze/animate button
//            class to control freeze/animate
//            fixed bug about hover when item is searched and screen is cleared
//            fixed bug about distance not respected when cruising through graph
// Version 9: 3/27/09 added feature where database can be specified
//            to php tunnel
// Version 8: 3/26/09 (later) use sqrt( # of links) for node size
// Version 7: 3/26/09 working popup: updates force field!
// Version 6: working popup, no action on slider yet
// Version 5: Starting attaching Java Popup
// Version 4: starts showing information in right pane
// Version 3: 3 panes: two text, one information
// Version 2: 3/22/09: works (crashes at times) with two panes, one at the bottom
// Version 1: 3/17/09: the application version of the applet of the same name.

package DT;

import java.awt.BorderLayout;
import java.applet.AppletContext;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.*;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.GroupAction;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.assignment.DataSizeAction;
import prefuse.action.assignment.SizeAction;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.Control;
import prefuse.controls.ControlAdapter;
import prefuse.controls.DragControl;
import prefuse.controls.FocusControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Edge;
import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.collections.IntIterator;
import prefuse.util.display.DisplayLib;
import prefuse.util.force.DragForce;
import prefuse.util.force.Force;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.NBodyForce;
import prefuse.util.force.SpringForce;
import prefuse.util.ui.JPrefuseApplet;
import prefuse.util.ui.JValueSlider;
import prefuse.util.ui.UILib;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;

/** ================================================================================
 *    ____                 _  __     ___               __  __       ____        _
 *   / ___|_ __ __ _ _ __ | |_\ \   / (_) _____      _|  \/  |_   _/ ___|  __ _| |
 *  | |  _| '__/ _` | '_ \| '_ \ \ / /| |/ _ \ \ /\ / / |\/| | | | \___ \ / _` | |
 *  | |_| | | | (_| | |_) | | | \ V / | |  __/\ V  V /| |  | | |_| |___) | (_| | |
 *   \____|_|  \__,_| .__/|_| |_|\_/  |_|\___| \_/\_/ |_|  |_|\__, |____/ \__, |_|
 *                  |_|                                       |___/          |_|  
 *  ================================================================================
 */
public class GraphViewMySqlPhp extends Display {

    private static final long serialVersionUID = 1L;
    private static final String encyclopedia= "encyclopedia";
    private static final String graph       = "graph";
    private static final String nodes       = "graph.nodes";
    private static final String edges       = "graph.edges";
    private static final String nodeSize    = "nodeSize";
    private static final String links       = "links";
    private static final String IdStr       = "Id";
    private static final String name        = "name";
    private static final String info        = "info";
    private static final String url         = "url";
    private static final String visited     = "visited";
    private static final String directed    = "directed";
    
    private static final int    Kakhi0      = ColorLib.rgb( 0xad, 0xa9, 0x6e);
    private static final int    Kakhi3      = ColorLib.rgb( 0xc9, 0xbe, 0x62);
    private static final int    DarkGold    = ColorLib.rgb( 0xfb, 0xb1, 0x17);
    private static final int    DefaultBlue = ColorLib.rgb(200,200,255);
    
    private static Graph                    theGraph;
    private static Visualization            theVis;
    private static VisualGraph              vg;
    private static DefaultRendererFactory   defaultRenderFact;
    private static GraphDistanceFilter      filter;
    private static AccessMySqlThroughPhp2   mySqlTunnel;
    private static boolean                  busyUpdatingGraph = false;        
                                            // true if program has issued a query
                                            // and we are waiting for the result
    private static int                      frameWidth = 1000;
    private static int                      frameHeight = 800;
    private static EncyclopediaForm2        theForm;
    private static ForceSimulator           fsim;
    private static String                   databaseUsed = "encyclopedia76d";
    private static boolean                  isDirected = true;
    private static boolean                  normalGraph = true;
    private static boolean                  reverseGraph = false;
    private static AnimationControl         animationControl;
    //private static NeighborHighlightControl2 neighborHighlightControl;
    
    private static String                   lastRootNodeName;
    private JPrefuseApplet                  parentApplet = null;
    private static AppletContext            appletContext=null;
    
    /* -------------------------------------------------------------------------
     * main
     * -------------------------------------------------------------------------
     */
    public static void main(String argv[]) {

        UILib.setPlatformLookAndFeel();

        databaseUsed = (String)JOptionPane.showInputDialog(
                null,
                "Enter database name\n"
                +"(e.g. encyclopedia76)",
                "Select Database",
                JOptionPane.QUESTION_MESSAGE );


        DataFileGraphView df = new DataFileGraphView();
        if ( !df.isValidDatabase(databaseUsed))
            return;
        isDirected = df.getBooleanParam( databaseUsed, directed );
        demo( databaseUsed /* as title of jpanel */ );
    }
    
    /*
     * Call this when we run as an application
     */
    public static JFrame demo( String title  ) {
        
        //--- create the UI ---
        GraphViewMySqlPhp graphDisplay = new GraphViewMySqlPhp();    
        theForm = new EncyclopediaForm2( graphDisplay, filter );
        graphDisplay.setBackground(Color.white);

        JFrame frame = new JFrame( title );
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(1000,800));
        frame.setContentPane( theForm );
        frame.pack();
        frame.setVisible(true);
        
        //--- center the graph in the form
        centerGraphInDisplay( 0 ); // 0 --> first node in internal table
        return frame;
    }
    
    /*
     * Call this when we run as an applet
     */
    public static JComponent demoComponent( AppletContext ac ) { // JPrefuseApplet parent ) {
        appletContext = ac;
        databaseUsed = (String)JOptionPane.showInputDialog(
                null,
                "Enter database name\n"
                +"(e.g. epfl)",
                "Select Database",
                JOptionPane.QUESTION_MESSAGE );


        DataFileGraphView df = new DataFileGraphView();
        if ( !df.isValidDatabase(databaseUsed))
            return null;
        isDirected = df.getBooleanParam( databaseUsed, directed );
        
        //--- create the UI ---
        GraphViewMySqlPhp graphDisplay = new GraphViewMySqlPhp();    
        //graphDisplay.setParentApplet( parent );
        theForm = new EncyclopediaForm2( graphDisplay, filter );
        graphDisplay.setBackground(Color.white);
        //centerGraphInDisplay( 0 ); // 0 --> first node in internal table
        return theForm;
    }
        
    /*
     * displayInfoForces: display the information about forces
     */
    void displayInfoForces( ForceSimulator fsim ) {
        Force[] forces = fsim.getForces();
        for ( int i=0; i<forces.length; i++ ) {
            Force f = forces[i];
            String fname = f.getClass().getName();
            fname = fname.substring( fname.lastIndexOf( ".")+1);
            for ( int j=0; j<f.getParameterCount(); j++ ) {
                String pname = f.getParameterName( j );
                System.out.println( "Force["+String.valueOf( i )+"] ="+fname +","+pname+" --> "+String.valueOf(f.getParameter(j)));
                if ( fname.equals("NBodyForce" ) && pname.equals("Distance") )
                    System.out.println( "--Force["+String.valueOf( i )+"] = "+fname +","+pname+" --> "+String.valueOf(f.getParameter(j)));
            }
        }
    }

    private void displayGraph() {
        System.out.println("--------------------------------------------------------");
        System.out.println( "isDirected =" + String.valueOf( isDirected ));
        System.out.println( "reverseGraph =" + String.valueOf( reverseGraph ));
        System.out.println( "normalGraph =" + String.valueOf( normalGraph ));
        for ( Iterator<Node> nodeItem = theGraph.nodes(); nodeItem.hasNext(); ) {
            Node node = nodeItem.next();
            int noCol = node.getColumnCount();
                System.out.println(
                        "name:" + node.getString( name ) +
                        " visited:" + node.getInt( visited ) +
                        " IdStr:" + node.getInt( IdStr ) +
                        " links:" + node.getString( links ) +
                        " nodeSize:" + node.getInt( nodeSize ) +
                        " info:" + node.getString( info ) +
                        " url:" + node.getString( url ) );
        }
        System.out.println("--------------------------------------------------------");
    }

    /* -------------------------------------------------------------------------
     * GraphViewMySqlPhp
     * -------------------------------------------------------------------------
     */
    public GraphViewMySqlPhp() {
        super(new Visualization());

        //theGraph = new Graph( true /* is directed graph */);
        theGraph = createNewGraph( isDirected );
        createGraphColumns();

        mySqlTunnel = new AccessMySqlThroughPhp2( databaseUsed );
        mySqlTunnel.setDatabase( databaseUsed );
                
        //int resCode = addNodeToGraph( 55 ); // pick the node with Index 1 in database
        //Random generator = new Random();
        //while ( resCode==-1 ) {
        //    resCode = addNodeToGraph( generator.nextInt( 1000 ) );
        //}
        // create a new, empty visualization for our data
        theVis = m_vis; //new Visualization();
        
        // set the animation control.  Will need to be told of the button in
        // the form that controls the animation
        animationControl = new AnimationControl();
        animationControl.setVisualization( theVis, "layout");

        // VisualGraph
        vg = theVis.addGraph(graph, theGraph );
        theVis.setValue( edges, null, VisualItem.INTERACTIVE, Boolean.FALSE );

        // focus group
        TupleSet focusGroup = theVis.getGroup(Visualization.FOCUS_ITEMS);
        focusGroup.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem)
            {
                theVis.cancel( "draw" );
                for ( int i=0; i<rem.length; ++i )
                    ((VisualItem)rem[i]).setFixed(false);
                for ( int i=0; i<add.length; ++i ) {
                    ((VisualItem)add[i]).setFixed(false);
                    ((VisualItem)add[i]).setFixed(true);
                }
                theVis.run( "draw" );
            }
        });

        // renderers
        LabelRenderer tr = new LabelRenderer( name );
        tr.setRoundedCorner(8, 8);
        defaultRenderFact = new DefaultRendererFactory( tr );
        EdgeRenderer edgeRenderer = new EdgeRenderer( Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_FORWARD );
        edgeRenderer.setArrowHeadSize( 6, 10 );
        defaultRenderFact.add( new InGroupPredicate( edges ), edgeRenderer);
        theVis.setRendererFactory( defaultRenderFact );

        // -- set up the actions ----------------------------------------------
        int hops = 5;
        filter = new GraphDistanceFilter(graph, hops);
        ActionList draw = new ActionList();
        draw.add(filter);

        // create a datacoloraction for the nodes that are visited
        int[] palette = new int[] { DefaultBlue, DarkGold };

        DataColorAction fillVisited = new DataColorAction( nodes, visited,
                							Constants.NUMERICAL, VisualItem.FILLCOLOR, palette );
        fillVisited.add("_fixed", ColorLib.rgb(225, 170, 95) ); 
        fillVisited.add("_highlight", ColorLib.rgb(255,200,125));

        ItemAction edgeColor = new ColorAction( edges,
        									VisualItem.STROKECOLOR, ColorLib.gray(200) );
        ( (ColorAction) edgeColor ).add( "_highlight", ColorLib.rgb(255,200,125) );
        
        // EdgeSize is an idea to make highlighted edges bigger, but arrow heads are lost
        // in the process...
        SizeAction edgeSize = new SizeAction( edges );  // DFT add
        edgeSize.add( "_highlight", 2 );				// DFT add
        
        // add node data size action
        DataSizeAction nodeDataSizeAction = new DataSizeAction( nodes, nodeSize );
        nodeDataSizeAction.setScale( Constants.LINEAR_SCALE );

        //draw.add(new ColorAction(nodes, VisualItem.FILLCOLOR, ColorLib.rgb(200,200,255)));
        draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0));
        draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0,0,0)));
        draw.add( edgeColor );
        draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib.gray(200)));
        draw.add( edgeSize );							// DFT add
                
        // set force-simulation system
        //Force[0] =NBodyForce,GravitationalConstant --> -1.0
        //Force[0] =NBodyForce,Distance --> -1.0
        //--Force[0] = NBodyForce,Distance --> -1.0
        //Force[0] =NBodyForce,BarnesHutTheta --> 0.9
        //Force[1] =DragForce,DragCoefficient --> 0.01
        //Force[2] =NBodyForce,GravitationalConstant --> -32.0
        //Force[2] =NBodyForce,Distance --> 400.0
        //--Force[2] = NBodyForce,Distance --> 400.0
        //Force[2] =NBodyForce,BarnesHutTheta --> 0.9
        //Force[3] =DragForce,DragCoefficient --> 0.02
        //Force[4] =SpringForce,SpringCoefficient --> 1.2940001E-4
        //Force[4] =SpringForce,DefaultSpringLength --> 0.0
        //Force[5] =SpringForce,SpringCoefficient --> 1.09500004E-4
        //Force[5] =SpringForce,DefaultSpringLength --> 400.0

        ForceDirectedLayout fdl = new ForceDirectedLayout(graph);
        fsim = fdl.getForceSimulator();
        Force[] forces = fsim.getForces();
        forces[0].setParameter(0 , -1);            // gravitational constant
        forces[0].setParameter(1 , -1);            // Distance
        forces[0].setParameter(2, (float) 0.9 );   // Barnes Hut theta
        forces[1].setParameter(0, (float) 0.01 );
        forces[2].setParameter(0, (float) 1.0E-5 );
        forces[2].setParameter(1, (float) 216.0 );

        // Add "zoom to fit" as action
        ZoomToFitAction zoomToFit = new ZoomToFitAction( graph, this );
		zoomToFit.setStepTime( 100000 );
        //theVis.putAction( "zoomToFit", zoomToFit );
        //theVis.putAction( "zoomToFit", new ZoomToFitAction( graph, this ));

        ActionList animate = new ActionList(Activity.INFINITY);		// DFT add
        //ActionList animate = new ActionList(5000);				// DFT remove
        //animate.setPacingFunction(new SlowInSlowOutPacer()); 		// DFT remove
        animate.add(fdl);
        animate.add(fillVisited);
        animate.add( edgeColor );
        animate.add( edgeSize );									// DFT add
        animate.add(new RepaintAction());
        animate.add( nodeDataSizeAction );
        //animate.add( zoomToFit );									// DFT add
        
        // finally, we register our ActionList with the Visualization.
        // we can later execute our Actions by invoking a method on our
        // Visualization, using the name we've chosen below.
        theVis.putAction("draw",   draw);
        theVis.putAction("layout", animate);

        															// DFT remove
        theVis.runAfter( "draw",   "layout");
        //theVis.alwaysRunAfter( "layout", "zoomToFit");			// DFT remove
        
        // set the display, of which we are an extension
        setSize(1000,800);
        setForeground(Color.GRAY);
        setBackground(Color.WHITE);
       
        // main display controls
        addControlListener(new FocusControl(1));
        addControlListener(new DragControl());
        addControlListener(new PanControl());
        addControlListener(new ZoomControl());
        addControlListener(new WheelZoomControl());
        addControlListener(new ZoomToFitControl());
        addControlListener(new NeighborHighlightControl());
        // added separate highlight controller so that we can force the node that is
        // clicked on become the new center of the highlight
        //neighborHighlightControl = new NeighborHighlightControl2();
        //addControlListener( neighborHighlightControl );
        addControlListener(new HoverTooltip());
        addControlListener(new NodeClickControl2( this ) );
        setForeground(Color.GRAY);
        setBackground(Color.WHITE);

    } // end constructor GraphViewMySql()

    
    /* -------------------------------------------------------------------------
     *                        I N S P E C T O R S
     * -------------------------------------------------------------------------
     */
      
    public AnimationControl getAnimationControl() {
        return animationControl;
    }

    public GraphDistanceFilter getFilter() {
        return filter;
    }
    
    public AppletContext getAppletContext() {
        return appletContext;
    }
    
    public String getLastRootNode() {
        return lastRootNodeName;
    }
    
    public void setLastRootNode( String name ) {
        lastRootNodeName = name;
    }
    
    public void setParentApplet( JPrefuseApplet parent ) {
        parentApplet = parent;
    }

    public JPrefuseApplet getParentApplet(  ) {
        return parentApplet;
    }


    /* -------------------------------------------------------------------------
     * Graph and Id-related methods
     * -------------------------------------------------------------------------
     */
    protected int getDbId( int rowId ) {
        for ( Iterator<Node> nodeItem = theGraph.nodes(); nodeItem.hasNext(); ) {
            Node node = nodeItem.next();
            if ( node.getRow()==rowId )
                return node.getInt( "Id" );
        }
        return -1;
    }
    
    protected int getRowId( int dbId ) {
        for ( Iterator<Node> nodeItem = theGraph.nodes(); nodeItem.hasNext(); ) {
            Node node = nodeItem.next();
            if ( node.getInt( "Id" )== dbId )
                return node.getRow();
        }
        return -1;
    }
    
    public String getDataBaseName(){
        return databaseUsed;
    }
    
    public Graph createNewGraph( boolean dir ) {
        isDirected = dir;
        Graph graph = new Graph( isDirected /* whether the graph is directed or not */);
        return graph;
    }

    /* -------------------------------------------------------------------------
     * graphContainsDbId: returns true if graph contains given Id
     * -------------------------------------------------------------------------
     */
    protected boolean graphContainsDbId( int dbId ) {
        for ( Iterator<Node> nodeItem = theGraph.nodes(); nodeItem.hasNext(); ) {
            Node node = nodeItem.next();
            if ( node.getInt( "Id" )== dbId )
                return true;
        }
        return false;
        
    }
    protected Node getNodeWithDBId( int dbId ) {
        for ( Iterator<Node> nodeItem = theGraph.nodes(); nodeItem.hasNext(); ) {
            Node node = nodeItem.next();
            if ( node.getInt( "Id" )== dbId )
                return node;
        }
        return null;
    }
    
    public Graph getGraph() {
        return theGraph;
    }
    
    public ForceSimulator getForceSimulator() {
        return fsim;
    }
    
    public static boolean isValidDatabase( String db ) {
        DataFileGraphView df = new DataFileGraphView();
        return df.isValidDatabase( db );
    }
    
    public boolean setDatabase( String db ){
        DataFileGraphView df = new DataFileGraphView();
        if ( !df.isValidDatabase( db ) )
            return false;
        
        databaseUsed = db;
        isDirected = df.getBooleanParam( databaseUsed, directed );
        mySqlTunnel.setDatabase( db );
        theForm.setTitle( db );
        updateStatus( "database set to "+db);
        return true;
    }
    
    /* -------------------------------------------------------------------------
     * isBusy: returns true if we are in the middle of issuing a query
     * so that we won't instantiate one too soon.   
     * -------------------------------------------------------------------------
     */
    public  boolean isBusy() {
        return busyUpdatingGraph;
    }

    public  void setBusy( boolean b ) {
        busyUpdatingGraph = b;
    }

    /* -------------------------------------------------------------------------
     * noLinks: returns the number of links
     * -------------------------------------------------------------------------
     */
    protected  int numberOfLinks( String links ) {
        return links.split( "," ).length;      
    }

    /* -------------------------------------------------------------------------
     * ANIMATION CONTROLS
     * -------------------------------------------------------------------------
     */
    public void animate() {
        animationControl.animate();
    }
    public void freeze() {
        animationControl.freeze();
    }

    /* -------------------------------------------------------------------------
     * centerGraphInDisplay
     * -------------------------------------------------------------------------
     */
    public static void centerGraphInDisplay( int centerNodeId ) {
        TupleSet focusGroup = theVis.getGroup( Visualization.FOCUS_ITEMS );
        NodeItem focus = (NodeItem)vg.getNode( centerNodeId );
        PrefuseLib.setX( focus, null, frameWidth/2 );
        PrefuseLib.setY( focus, null, frameHeight/2 );
        focusGroup.setTuple( focus);
    }

    /* -------------------------------------------------------------------------
     * removeNodeFromGraph: removes the node from the graph
     * This shouldn't really be used, as we don't have a good way to deal with
     * the removal of a node that would break the graph into two graphs.
     * -------------------------------------------------------------------------
     */
    /*
    public void removeNodeFromGraph( String centerId ) {
        removeNodeFromGraph( Integer.valueOf( centerId.trim() ));
    }
    */
    public void removeNode(int node) {
        removeNodePrivate(node);
        theVis.run("repaint");
}

    private void removeNodePrivate(int node) {
        theGraph.removeNode(node);
}

    /* -------------------------------------------------------------------------
     * clearGraph: resets the graph
     * (Currently not used)
     * -------------------------------------------------------------------------
     */
    
    public  void clearGraph( ) {
        TupleSet focusGroup = theVis.getGroup(Visualization.FOCUS_ITEMS);
        theVis.cancel( "draw" );
        theVis.cancel( "layout" );

        focusGroup.clear();
        theGraph.clear();
        /*
        for ( Iterator<Node> nodeItem = theGraph.nodes(); nodeItem.hasNext(); ) {
            Node node = nodeItem.next();
            theGraph.removeNode( node );
        }
        for ( Iterator<Edge> edgeItem = theGraph.edges(); edgeItem.hasNext(); ) {
            Edge edge = edgeItem.next();
            theGraph.removeEdge( edge );
        }
        */
        theVis.run( "draw" );
        theVis.run( "layout" );
    }
    
    
    /* -------------------------------------------------------------------------
     * removeNodeFromGraph: remove node from graph
     * Don't use: buggy...
     * -------------------------------------------------------------------------
     */
    /*
    public void removeNodeFromGraph( int dbId ) {
        int row = getRowId( dbId );
        
        theVis.cancel( "draw" );
        for ( Iterator<Edge> edgeItem = theGraph.edges(); edgeItem.hasNext(); ) {
            Edge e = edgeItem.next();
            Node sourceNode = e.getSourceNode();
            if ( sourceNode.getRow() == row ) {
                theGraph.removeEdge( e );
            }
            else {
                Node targetNode = e.getTargetNode();
                if ( targetNode.getRow() == row ) {
                    theGraph.removeEdge( e );
                }
            }
        }
        //System.out.println( "removing Node Id" + String.valueOf( dbId ) + " "+ node.getString( "name") );        
        theGraph.removeNode( row );
        theVis.run( "draw" );
    }
    */

    /*
    private static void displayTable( Table myTable, String caption ) {
        
        System.out.println( "-------------------------------------------");
        System.out.println( "--" + caption );
        System.out.println( "-------------------------------------------");
        for ( int i=0; i< myTable.getRowCount(); i++ ) {
            int Id = myTable.getInt( i, 0 );
            String name = myTable.getString( i, 1 );
            String links = myTable.getString( i, 2 );
            String info  = myTable.getString( i, 3 );
            System.out.println( "Table["+String.valueOf(i)+"] "
                    +name + "("+String.valueOf(Id)+ ") - "+ links +": "+info);
            try {
                System.out.println( "       " + myTable.getString( i, 4 ) );
            }
            catch (IndexOutOfBoundsException e) {
                // do nothing
            }
        }
        System.out.println( "-------------------------------------------");
    }
    */
    
    protected void displayTable2( Table myTable ) {
        for ( int i=0; i< myTable.getRowCount(); i++ ) {
            int Id = myTable.getInt( i, 0 );
            String name = myTable.getString( i, 1 );
            String links = myTable.getString( i, 2 );
            String info = myTable.getString( i, 3 );
            System.out.println( "Table["+String.valueOf(i)+"] "
                    +name + "("+String.valueOf(Id)+ ") - "+ links);
        }
    }

    /* -------------------------------------------------------------------------
     * addNodeToGraph: adds a node to the graph.  If the graph is empty
     *             a node is added to it.
     * -------------------------------------------------------------------------
     */
    public int addNodeToGraph( String centerId ) {
        return addNodeToGraphUsingQuery( Integer.valueOf( centerId ), "", true );
    }    

    public  int addNodeToGraph( int centerId )  {
        return addNodeToGraphUsingQuery( centerId, "", true );
    }

    public  int addNodeToGraphByName( String searchName ) {
        return addNodeToGraphUsingQuery( 0, searchName, false );
    }

    
    public int addNodeToGraphUsingQuery( int centerId, String centerName, boolean searchById ) {
        Table dataTable = null;
        int newNodeRow=0;

        // returns table
        // Id  Name  Links  Info
        //  3  toto  2,3,4  some info about node   <-- the root of the graph
        //  2  tata  3,5
        //
        try {
            if ( searchById ) {
                dataTable = mySqlTunnel.getData( centerId );
                //System.out.println( "calling getData("+ String.valueOf( centerId )+")");
            }
            else {
                //System.out.println( "calling getDataByName("+ centerName +")");
                dataTable = mySqlTunnel.getDataByName( centerName );
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        
        if ( dataTable.getRowCount()==0 ) {
            updateStatus( "No new titles found");
            return -1;
        }
        //displayTable( dataTable, "table" );
        
        // Add the nodes first
        // First create a table assigning the database Id to the graph row
        for (int i = 0; i < dataTable.getRowCount(); i++){
            int Id = dataTable.getInt( i, 0 );
            if ( i==0 ) {
                setLastRootNode( dataTable.getString( 0, 1) );
            }
            if ( graphContainsDbId( Id ) ) {
                ////System.out.println( "Id already in IdMap" );
                if (i==0) {
                    Node clickedNode = getNodeWithDBId( Id );
                    clickedNode.set( visited, 1 );
                }
                continue;
            }
            Node n = theGraph.addNode();               
            n.set( name, dataTable.get( i, 1 ) );            
            n.set( links, dataTable.get( i, 2 ) );
            n.set( IdStr, Id );
            int noLinks = numberOfLinks( dataTable.getString( i, 2 ) );
            ////System.out.println( "adding noLinks = " + String.valueOf( noLinks ) );
            n.set( nodeSize, Math.round( 1+ Math.sqrt( noLinks ) ) );
            n.set( info, dataTable.getString( i, 3 ));
            n.set( url, dataTable.getString( i, 4 ));
            if ( i==0 )
                n.set( visited, 1); //first node in the table is visited
            else
                n.set( visited, 0); // other nodes aren't
            int row = n.getRow();
            if ( i==0 ) {
                newNodeRow = row;
            }
        }

        // add the edges next
        for (int i = 0; i< dataTable.getRowCount(); i++ ) {
            int sourceId = dataTable.getInt( i, 0 );
            int sourceRow = getRowId( sourceId );
            String IdList = dataTable.get( i, 2 ).toString();
            String[] individualIds = IdList.split( "," );

            for(int j = 0; j < individualIds.length; j++) {
                int destId = Integer.parseInt( individualIds[j].trim() );
                if ( graphContainsDbId( destId ) ) {
                    int destRow = getRowId( destId );
                    
                    if ( normalGraph & theGraph.getEdge( sourceRow, destRow ) == -1 ) {
                        theGraph.addEdge( sourceRow, destRow );
                    }
                    if ( reverseGraph & theGraph.getEdge( destRow, sourceRow ) == -1 ) {
                        theGraph.addEdge( destRow, sourceRow );
                    }
                }
            }
        }

        // close up the graph so all edges incident to the new node are added
        closeUpGraph();
        //displayGraph();
        return newNodeRow;
    }

    /* -------------------------------------------------------------------------
     * closeUpGraph: once a node is added to the graph, we pass over all the nodes
     * again and add edges that were not added before because some nodes were not
     * on the graph yet.
     * -------------------------------------------------------------------------
     */
    public  void closeUpGraph( ) {
        // look at all the nodes of the current graph
        for ( int i=0; i < theGraph.getNodeCount(); i++) {

            // get Node i
            Node n = theGraph.getNode( i );
            int sourceId = n.getInt( IdStr );
            int sourceRow = getRowId( sourceId );

            // get its list of nodes to which it is linked
            String IdList = n.getString( links );
            String[] individualIds = IdList.split( "," );

            // go through each linked node, and if an edge does not exist to that node
            // add it.
            for ( int j=0; j<individualIds.length; j++ ) {
                int destId = Integer.parseInt( individualIds[j].trim() );
                if ( graphContainsDbId( destId ) ) {
                    int destRow = getRowId( destId );
                    //if ( theGraph.getEdge( sourceRow, destRow ) == -1 ) {
                    //    theGraph.addEdge( sourceRow, destRow );
                    //}
                    if ( normalGraph & theGraph.getEdge( sourceRow, destRow ) == -1 ) {
                        theGraph.addEdge( sourceRow, destRow );
                    }
                    if ( reverseGraph & theGraph.getEdge( destRow, sourceRow ) == -1 ) {
                        theGraph.addEdge( destRow, sourceRow );
                    }
                }
            }
        }
    }
    
    /*
     * redoLastSearch: used when we're switching databases.
     */
    public void redoLastSearch() {
        setBusy( true );
        getVisualization().cancel( "draw" );
        getAnimationControl().cancel( "layout" );

        addNodeToGraph( lastRootNodeName );
        getFilter().run();
        getVisualization().run( "draw" );
        getAnimationControl().run( "layout" );
        setBusy( false );
    }
    

    /* -------------------------------------------------------------------------
     * initUI
     * -------------------------------------------------------------------------
     */
    private static void initUI() {

    }
    
    /* -------------------------------------------------------------------------
     * createGraphColumns
     * -------------------------------------------------------------------------
     */
    private void createGraphColumns() {
        theGraph.addColumn( name,         String.class   );
        theGraph.addColumn( links,        String.class   );
        theGraph.addColumn( IdStr,        int.class      );
        theGraph.addColumn( info,         String.class   );
        theGraph.addColumn( url,          String.class   );
        theGraph.addColumn( nodeSize,     int.class      );
        theGraph.addColumn( visited,      int.class         );
    }

    /* -------------------------------------------------------------------------
     * clearDisplayAddNewNodeByName
     * -------------------------------------------------------------------------
     */
    public void clearDisplayAddNewNodeByName( String title ) {
        setBusy( true );
        
        TupleSet focusGroup = theVis.getGroup(Visualization.FOCUS_ITEMS);
        focusGroup.clear();
        //theVis.cancel( "layout");
        theVis.cancel( "draw" );
        
        theGraph.clear();
        createGraphColumns();
        
        
        
        updateStatus( "Adding title " + title );
        
        int nodeId = addNodeToGraphByName( title );
        if ( nodeId == -1 ) {
            setBusy( false );
            animationControl.cancel("layout"); //theVis.cancel( "layout" );
            updateStatus( "WARNING: Page title starting with \"" + title + "\" could not be found." );
        }
        else {
            updateStatus( "New graph created around Page " + title );
            centerGraphInDisplay( nodeId );
            animationControl.run( "layout"); // theVis.run( "layout" );
            theVis.run( "draw" );
        }
        
        //theVis.run( "draw" );
        setBusy( false );
    }


    /* -------------------------------------------------------------------------
     * clearDisplayAddNewNodeByName
     * -------------------------------------------------------------------------
     */
    public void updateStatus( String msg ) {
        try {
            theForm.updateStatus( msg );
        }
        catch ( Exception e) {
            return;
        }
    }
    
    void updateProperties( String msg ) {
        theForm.updateProperties( msg );
    }
    
    /**
	 * Private "zoom to fit" action's implementation
	 * Note: copy-pasted from Prefuse demos
	 */
	private class ZoomToFitAction extends GroupAction {
		private Display display;
		//GroupAction(Visualization vis, long duration, long stepTime)
		public ZoomToFitAction(String graphGroup, Display display) {
			super(graphGroup);
			this.display = display;
		}
		public void run(double frac) {
			Visualization vis = display.getVisualization();
			Rectangle2D bounds = vis.getBounds(Visualization.ALL_ITEMS);
			GraphicsLib.expand(bounds, 50 + (int)(1/display.getScale()));
			DisplayLib.fitViewToBounds(display, bounds, 500);
		}
	}

    // end of GraphViewMySqlPhp Class
}

/** ================================================================================
 * AnimationControl
 *    _        _            _   _          ___         _           _
 *   /_\  _ _ (_)_ __  __ _| |_(_)___ _ _ / __|___ _ _| |_ _ _ ___| |
 *  / _ \| ' \| | '  \/ _` |  _| / _ \ ' \ (__/ _ \ ' \  _| '_/ _ \ |
 * /_/ \_\_||_|_|_|_|_\__,_|\__|_\___/_||_\___\___/_||_\__|_| \___/_|
 * =================================================================================
*/
class AnimationControl {
    private JButton button         = null;
    private Visualization theVis = null;
    boolean animationSuspended   = true;
    String actionString          = "";
    
    AnimationControl() {
    }
    AnimationControl( JButton button, Visualization theVis, String actionString ) {
        this.button = button;
        this.theVis = theVis;
        this.actionString = actionString;
    }
    public void setButton( JButton button ) {
        this.button = button;
    }
    public void setVisualization( Visualization theVis, String action ) {
        this.theVis = theVis;
        actionString = action;
    }
    public void run( String action ) {
        animate();
    }
    public void cancel( String action ) {
        freeze();
    }
    public void animate() {
        if ( button!=null) button.setText("Freeze");
        if (theVis !=null ) theVis.run( actionString );
        animationSuspended = false;
    }
    public void freeze() {
        if ( button != null ) button.setText("Animate");
        if ( theVis != null ) theVis.cancel( actionString );
        animationSuspended = true;
    }    
    public void toggle() {
        if ( animationSuspended  )
            animate();
        else
            freeze();
    }
    public boolean animationIsOn() {
        return !animationSuspended;
    }
    public boolean animationIsOff() {
        return !animationIsOn();
    }
}

/* ===========================================================================
 * EncyclopediaForm1: The main form
 *   ____ _   _ ___   _____                    
 *  / ___| | | |_ _| |  ___|__  _ __ _ __ ___  
 * | |  _| | | || |  | |_ / _ \| '__| '_ ` _ \
 * | |_| | |_| || |  |  _| (_) | |  | | | | | |
 *  \____|\___/|___| |_|  \___/|_|  |_| |_| |_|                                      
 * ===========================================================================
 */
class EncyclopediaForm2 extends JPanel {
    
    private static final long serialVersionUID = 1L;
    // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
    // Generated using JFormDesigner non-commercial license
    private JLabel         label1;
    private JTextField  textField1;
    private JPanel         hSpacer1;
    private JButton     button1;
    private JButton        button2;
    private JButton     button3;
    private JSpinner     spinner1;
    private JLabel         label2;
    private JPanel         panel2;
    private JPanel         hSpacer2;
    private JPopupMenu     popupMenu1;
    private JPanel         menuItem1;
    
    //--- visualization specific variables
    Display                    display;
    GraphDistanceFilter        filter;
    private GraphViewMySqlPhp  graphView;
    private Visualization      theVis;
    private ForceSimulator     fsim;
    /* -------------------------------------------------------------------------
     * Constructor
     * -------------------------------------------------------------------------
     */
    public EncyclopediaForm2(GraphViewMySqlPhp graphDisplay, GraphDistanceFilter fltr ) {
        filter    = fltr;
        graphView = graphDisplay;
        theVis    = graphView.getVisualization();
        fsim      = graphView.getForceSimulator();
        //setBorder(BorderFactory.createTitledBorder( "Encyclopedia 1976 (Application)"));
        setTitle( graphView.getDataBaseName() );
        initComponents( display, filter );
    }

    /* -------------------------------------------------------------------------
     * udpateStatus: prints a message in the bottom window
     * -------------------------------------------------------------------------
     */
    public void setTitle( String msg ){
        setBorder(BorderFactory.createTitledBorder( msg + " (Application)"));
    }

    /* -------------------------------------------------------------------------
     * udpateStatus: prints a message in the bottom window
     * -------------------------------------------------------------------------
     */
    public void updateStatus( String msg ) {
        //textArea2.append( msg + "\n" );
        System.out.println( msg );
    }

    /* -------------------------------------------------------------------------
     * updateProperties: prints a message in the right hand-side window
     * -------------------------------------------------------------------------
     */
    public void updateProperties( String msg ) {
        //textArea1.setText(  msg  );
        System.out.println( msg );
    }

    /* -------------------------------------------------------------------------
     * button1MouseClicked
     * -------------------------------------------------------------------------
     */
    private void button1MouseClicked(MouseEvent e) {
        String query = textField1.getText().trim();
        graphView.clearDisplayAddNewNodeByName(query);            
    }
    
    /* -------------------------------------------------------------------------
     * button2MouseClicked
     * -------------------------------------------------------------------------
     */
    private void button2MouseClicked(MouseEvent e) {
        // TODO add your code here
    }

    /* -------------------------------------------------------------------------
     * button2MouseClicked
     * -------------------------------------------------------------------------
     */
    private void button3MouseClicked(MouseEvent e) {
        AnimationControl animationControl = graphView.getAnimationControl();
        boolean frozen = animationControl.animationIsOff();
        if ( frozen )
            animationControl.animate();
        else
            animationControl.freeze();
    }

    /* -------------------------------------------------------------------------
     * spinner1StateChanged
     * -------------------------------------------------------------------------
     */
    private void spinner1StateChanged(ChangeEvent e) {
        filter.setDistance( (Integer) spinner1.getValue() );
        theVis.run("draw");
    }

    /* -------------------------------------------------------------------------
     * textField1KeyTyped
     * -------------------------------------------------------------------------
     */
    private void textField1KeyTyped(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_ENTER) {
            String query = textField1.getText().trim();
            System.out.println("ENTER typed: " + query );
            graphView.clearDisplayAddNewNodeByName(query);            
        }
    }
    private void textField1KeyPressed(KeyEvent e) {
         int key = e.getKeyCode();
         if (key == KeyEvent.VK_ENTER) {
             String query = textField1.getText().trim();
             System.out.println("ENTER pressed: " + query );
             graphView.clearDisplayAddNewNodeByName(query);            
        }
    }

    /* -------------------------------------------------------------------------
     * initComponents
     * -------------------------------------------------------------------------
     */
    private void initComponents( Display display, GraphDistanceFilter filter ) {
        // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
        // Generated using JFormDesigner non-commercial license
        label1             = new JLabel();
        textField1         = new JTextField();
        button1         = new JButton();
        hSpacer1         = new JPanel(null);
        spinner1         = new JSpinner();
        label2             = new JLabel();
        panel2             = new JPanel();
        hSpacer2         = new JPanel(null);
        button2         = new JButton();
        button3         = new JButton();
        popupMenu1         = new JPopupMenu();
        menuItem1         = new JPanel();
        CellConstraints cc = new CellConstraints();

        //======== this ========
        setLayout(new FormLayout(
                "default, $lcgap, [70dlu,min], 2*($lcgap, default), $lcgap, [36dlu,default], $lcgap, default:grow, 3*($lcgap, default)",
                "default, $lgap, fill:default:grow"));

        //---- label1 ----
        label1.setText("Page Title");
        add(label1, cc.xy(1, 1));
        add(textField1, cc.xy(3, 1));

        //---- textField1 ----
        //textField1.setText( "" );
        textField1.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                textField1KeyTyped(e);
            }
            @Override
            public void keyPressed(KeyEvent e) {
                textField1KeyPressed(e);
            }
        });

        //---- button1 ----
        button1.setText("Search");
        button1.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                button1MouseClicked(e);
            }
        });
        button1.addFocusListener( new FocusListener() {
            public void focusGained(FocusEvent e) {
                button1.doClick();
            }

            public void focusLost(FocusEvent e) {
            }            
        });
        add(button1, cc.xy(5, 1));
        add(hSpacer1, cc.xy(7, 1));

        //---- spinner1 ----
        spinner1.setModel(new SpinnerNumberModel(6, 1, null, 1));
        spinner1.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                spinner1StateChanged(e);
            }
        });
        add(spinner1, cc.xy(9, 1));

        //---- label2 ----
        label2.setText("Link Length");
        add(label2, cc.xy(11, 1));
        add(hSpacer2, cc.xy(13, 1));

        //---- button2 ----
        button2.setText("Settings");
        button2.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                popupMenu1.show(e.getComponent(),
                        e.getX(), e.getY());
                //button2MouseClicked(e);
            }
        });
        button2.addFocusListener( new FocusListener() {
            public void focusGained(FocusEvent e) {
                button2.doClick();
            }

            public void focusLost(FocusEvent e) {
            }            
        });
        button2.setComponentPopupMenu(popupMenu1);
        add(button2, cc.xy(17, 1));

        //---- button3 ----
        button3.setText("Freeze");
        button3.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                button3MouseClicked(e);
            }
        });
        button1.addFocusListener( new FocusListener() {
            public void focusGained(FocusEvent e) {
                button1.doClick();
            }
            public void focusLost(FocusEvent e) {
            }
        });
        graphView.getAnimationControl().setButton( button3 );
        add(button3, cc.xy(15, 1));

        //======== panel2 ========
        panel2.setLayout(new FlowLayout());

        //======== popupMenu1 ========        
        menuItem1.setLayout(new BoxLayout(menuItem1, BoxLayout.Y_AXIS));
        menuItem1.add( new JForcePanel2( popupMenu1, fsim, graphView ) );
        popupMenu1.add(menuItem1);
        
        //======== panel2 ========
        panel2.add( graphView );
        panel2.setBackground(new Color(255, 153, 0));

        panel2.setLayout(new BoxLayout(panel2, BoxLayout.PAGE_AXIS));
        panel2.setSize( 600, 600);
        
        add(panel2, cc.xywh(1, 3, 17, 1));
        //==========SET SIZE========

        // JFormDesigner - End of component initialization  //GEN-END:initComponents
    }
    // JFormDesigner - End of variables declaration  //GEN-END:variables
}

/** ================================================================================
 *  _   _       _ _     _                 ____            _             _ ____  
 * | \ | | __ _(_) |__ | |__   ___  _ __ / ___|___  _ __ | |_ _ __ ___ | |___ \
 * |  \| |/ _` | | '_ \| '_ \ / _ \| '__| |   / _ \| '_ \| __| '__/ _ \| | __) |
 * | |\  | (_| | | |_) | | | | (_) | |  | |__| (_) | | | | |_| | | (_) | |/ __/
 * |_| \_|\__, |_|_.__/|_| |_|\___/|_|   \____\___/|_| |_|\__|_|  \___/|_|_____|
 *        |___/                                                           
 *  ================================================================================
 */
class NeighborHighlightControl2 extends NeighborHighlightControl {
    public void makeItemNewCenter( VisualItem item ) {
        if ( item instanceof NodeItem )
            setNeighborHighlight( (NodeItem) item, true );
    }
}

/** ================================================================================
 *   _   _           _       ____ _ _      _     ____            _             _
 *  | \ | | ___   __| | ___ / ___| (_) ___| | __/ ___|___  _ __ | |_ _ __ ___ | |
 *  |  \| |/ _ \ / _` |/ _ \ |   | | |/ __| |/ / |   / _ \| '_ \| __| '__/ _ \| |
 *  | |\  | (_) | (_| |  __/ |___| | | (__|   <| |__| (_) | | | | |_| | | (_) | |
 *  |_| \_|\___/ \__,_|\___|\____|_|_|\___|_|\_\\____\___/|_| |_|\__|_|  \___/|_|
 *
 * Interactive control that creates a new graph when a node
 * is clicked
 *  ================================================================================
 */
class NodeClickControl2 extends ControlAdapter {
    //private VisualItem activeItem;
    protected Point2D down = new Point2D.Double();
    protected Point2D temp = new Point2D.Double();
    //private DebugClass log;
    private GraphViewMySqlPhp graphView;

    /* -------------------------------------------------------------------------
     * Constructor
     * -------------------------------------------------------------------------
     */
    public NodeClickControl2() {
    }

    /* -------------------------------------------------------------------------
     * Constructor: NodeClickControl
     * -------------------------------------------------------------------------
     */
    public NodeClickControl2( GraphViewMySqlPhp gView ) {
        graphView = gView;
        //log = gView.getLog();
    }

    /* -------------------------------------------------------------------------
     * itemEntered
     */
    public void itemEntered(VisualItem item, MouseEvent e) {
    }
    
    /* -------------------------------------------------------------------------
     * if node is clicked, build a new graph
     * -------------------------------------------------------------------------
     */
    public void itemClicked( VisualItem item, MouseEvent e ) {
        // don't do anything if we're busy querying...
        if ( graphView.isBusy() )
            return;

        if ( SwingUtilities.isLeftMouseButton(e) ) {
            graphView.setBusy( true );
            graphView.getVisualization().cancel( "draw" );
            graphView.getAnimationControl().cancel( "layout" );

            graphView.addNodeToGraph( item.getString( "Id" ) );
            graphView.getFilter().run();
            graphView.getVisualization().run( "draw" );
            graphView.getAnimationControl().run( "layout" );
            //graphView.getHighlightController().makeItemNewCenter( item );
            
            graphView.setBusy( false );
        }
        else if (SwingUtilities.isRightMouseButton( e ) ) {
            // otherwise add node to graph
            Graph g = graphView.getGraph();
            
               String nodeId = item.getString( "Id" ).trim();
            String toDisplay = "Node " + nodeId + "\n";
            int rowId = graphView.getRowId( Integer.parseInt( nodeId ) );
            //System.out.println( "accesing Node " + nodeId
            //        + " -- " + String.valueOf( rowId ));
            Node n  = g.getNode( rowId );
            toDisplay = toDisplay + n.get( "info" );
            toDisplay = toDisplay.replace( ", ", "\n" );
            toDisplay = toDisplay + "\n" + n.get( "url" );
            graphView.updateProperties( toDisplay );

            if ( graphView.getAppletContext()==null )
                return;
            
            // open url in new browser window
            URL theUrl;
            try {
                theUrl = new URL( (String) n.get( "url" ) );
            } catch (MalformedURLException e1) {
                e1.printStackTrace();
                graphView.updateStatus( "malformed URL expression when opening not doc");
                return;
            }
            try {
                
                graphView.getAppletContext().showDocument( theUrl, "geoframe" );
                graphView.updateStatus( "Opened document " + theUrl.toString() + " in geoframe");
            } catch (Exception e1) {
                graphView.updateStatus( "could not open url in geoframe");
            }
            /*
            if ( graphView.getParentApplet() != null ) {
                //graphView.getParentApplet().fireUrlInBrowser( n.get( "url" ) );
                graphView.getParentApplet().getAppletContext().showDocument( theUrl, "docframe" );
                graphView.updateStatus( "opening url in docframe page");
            }
            else
                graphView.updateStatus( "parent is null: cannot open browser");
            */
        }
    }
} // end of NodeClickControl2

/** ================================================================================
 *      _ _____                  ____                  _ ____  
 *     | |  ___|__  _ __ ___ ___|  _ \ __ _ _ __   ___| |___ \
 *  _  | | |_ / _ \| '__/ __/ _ \ |_) / _` | '_ \ / _ \ | __) |
 * | |_| |  _| (_) | | | (_|  __/  __/ (_| | | | |  __/ |/ __/
 *  \___/|_|  \___/|_|  \___\___|_|   \__,_|_| |_|\___|_|_____|

 * =================================================================================
*/
class JForcePanel2 extends JPanel {
    
    /**
     *
     */
    private static final long serialVersionUID = -6486631015288069409L;
    private ForcePanelChangeListener lstnr = new ForcePanelChangeListener();
    private ForceSimulator fsim;
    private static GraphViewMySqlPhp graphView;
    private JPopupMenu parent;
    /**
     * Create a new JForcePanel
     * @param fsim the ForceSimulator to configure
     */
    public JForcePanel2( JPopupMenu parent, ForceSimulator fsim, GraphViewMySqlPhp graphDisplay ) {
        this.parent = parent;
        this.fsim = fsim;
        this.graphView = graphDisplay;
        this.setBackground(Color.WHITE);
        initUI();
    }
    
    /*
     * textField1KeyPressed: triggered when user has entered a new database in the
     * text area of the settings
     */
    private void textField1KeyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        JTextField textField1 = (JTextField) e.getComponent();
        if (key == KeyEvent.VK_ENTER) {
            String database = textField1.getText().trim();
            //System.out.println("ENTER pressed: " + database );
            if ( graphView.setDatabase(database) ) {
                graphView.clearGraph();
                graphView.getAnimationControl().freeze();
            }
            else
                textField1.setText( "invalid database" );
            parent.setVisible( false );
       }
    }

    private void b1MouseClicked( MouseEvent e ){
        System.out.println( "Saving image to file");
        SaveDisplayDialog  fileDialog = new SaveDisplayDialog();
        String imageFileName = fileDialog.saveFile(new Frame(), "Save graph...", ".\\", "*.png");
        System.out.println( "image file =" + imageFileName );
        if ( imageFileName.equals( "" ))
            return;
        try {
            graphView.saveImage( new FileOutputStream( imageFileName ), "png", 1.0 );
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
            System.out.println( "Could not save to file" );
        }
    }
    
    /**
     * Initialize the UI.
     */
    private void initUI() {
        this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        Force[] forces = fsim.getForces();
        for ( int i=0; i<forces.length; i++ ) {
            Force f = forces[i];
            String name = f.getClass().getName();
            name = name.substring(name.lastIndexOf(".")+1);
            if ( !name.startsWith("SpringForce") ) continue;
            Box v = new Box(BoxLayout.Y_AXIS);
            for ( int j=0; j<f.getParameterCount(); j++ ) {
                JValueSlider field = createField(f,j);
                field.addChangeListener(lstnr);
                v.add(field);
            }
            v.setBorder(BorderFactory.createTitledBorder(name));
            this.add(v);
        }

        // set up last box of the series for the database name
        {
            Box v = new Box( BoxLayout.X_AXIS);
            JLabel label1 = new JLabel();
            JTextField textField1 = new JTextField();
            label1.setText("Database");
            v.add(label1);
            textField1.addKeyListener(new KeyAdapter() {
                @Override
                public void keyPressed(KeyEvent e) {
                    textField1KeyPressed(e);
                }
            });
            textField1.setText( graphView.getDataBaseName() );
            v.add(textField1);
            this.add( v );
        }
        
        // set up button to take picture of display
        {
            Box v = new Box( BoxLayout.X_AXIS);
            JLabel label1 = new JLabel();
            label1.setText( "Save image (.PNG)");
            v.add( label1 );
            final JButton b1 = new JButton();
            b1.setText( "Save" );
            v.add(b1);
            b1.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    b1MouseClicked(e);
                }
            });
            b1.addFocusListener( new FocusListener() {
                public void focusGained(FocusEvent e) {
                    b1.doClick();
                }
                public void focusLost(FocusEvent e) {
                }
            });            
            JPanel hspacer = new JPanel( null );
            v.add( hspacer );
            this.add(v);
        }

    }
    
    /**
     * Create an entry for configuring a single parameter.
     */
    private static JValueSlider createField(Force f, int param) {
        double value = f.getParameter(param);
        double min   = f.getMinValue(param);
        double max   = f.getMaxValue(param)*2;
        String name = f.getParameterName(param);
        
        JValueSlider s = new JValueSlider(name,min,max,value);
        s.setBackground(Color.WHITE);
        s.putClientProperty("force", f);
        s.putClientProperty("param", new Integer(param));
        s.setPreferredSize(new Dimension(300,30));
        s.setMaximumSize(new Dimension(300,30));
        return s;
    }
    
    /**
     * Change listener that updates paramters in response to interaction.
     */
    private  class ForcePanelChangeListener implements ChangeListener {
        public void stateChanged(ChangeEvent e) {
            JValueSlider s = (JValueSlider)e.getSource();
            float val = s.getValue().floatValue();
            Force   f = (Force)s.getClientProperty("force");
            Integer p = (Integer)s.getClientProperty("param");
            f.setParameter(p.intValue(), val);
            graphView.animate();									// latest change DFT
            graphView.getVisualization().run( "draw" );
            //graphView.displayInfoForces( fsim );
        }
    } // end of inner class ForcePanelChangeListener
    
    /**
     * Create and displays a new window showing a configuration panel
     * for the given ForceSimulator.
     * @param fsim the force simulator
     * @return a JFrame instance containing a configuration interface
     * for the force simulator
     */
    
} // end of class JForcePanel

/*
 * _____ _ _      ____  _       _             
 *|  ___(_) | ___|  _ \(_) __ _| | ___   __ _
 *| |_  | | |/ _ \ | | | |/ _` | |/ _ \ / _` |
 *|  _| | | |  __/ |_| | | (_| | | (_) | (_| |
 *|_|   |_|_|\___|____/|_|\__,_|_|\___/ \__, |
 *                                      |___/
 */
class SaveDisplayDialog {

    @SuppressWarnings("deprecation")
    public String saveFile
    (Frame f, String title, String defDir, String fileType) {
        FileDialog fd = new FileDialog(f, title,    FileDialog.SAVE);
        fd.setFile(fileType);
        fd.setDirectory(defDir);
        fd.setLocation(50, 50);
        fd.show();
        // return fd.getFile();
        return  fd.getDirectory() + System.getProperty("file.separator") + fd.getFile();

    }
} // end of class SaveDisplayDialog

/*
 * 
 *  _____           _ _____ _       
 *  |_   _|__   ___ | |_   _(_)_ __  
 *    | |/ _ \ / _ \| | | | | | '_ \ 
 *    | | (_) | (_) | | | | | | |_) |
 *    |_|\___/ \___/|_| |_| |_| .__/ 
 *                            |_|    
 *  from http://goosebumps4all.net/34all/bb/search.php?
 *             action=results&sid=25c9ef85cd43f7c268cefc09da2eac10
 */
abstract class PrefuseTooltip {
	protected javax.swing.JPanel popup;
	protected int startDelay, stopDelay;
	protected javax.swing.Timer startShowingTimer, stopShowingTimer;
	protected javax.swing.JComponent owner;
	protected boolean isSticky;

	public PrefuseTooltip(javax.swing.JComponent owner) {
		this(owner, 1000, 1000);
	}
	
	public PrefuseTooltip(javax.swing.JComponent owner, int startDelay, int stopDelay) {
		this.owner = owner;
		this.startDelay = startDelay;
		this.stopDelay = stopDelay;
		this.isSticky = false;
		
		startShowingTimer = new javax.swing.Timer(startDelay, new ShowTimerAction(true));
		stopShowingTimer = new javax.swing.Timer(stopDelay, new ShowTimerAction(false));
		startShowingTimer.setRepeats(false);
		stopShowingTimer.setRepeats(false);
	}
	
	public void startShowing(int x, int y) {
		java.awt.Component contents = getContents();
		contents.addMouseListener(new PrefuseTooltipListener());

		popup = new javax.swing.JPanel(new java.awt.BorderLayout(), true);
		popup.setVisible(false);
		popup.setLocation(x, y);
		popup.setSize(contents.getPreferredSize());
		popup.add(contents, java.awt.BorderLayout.CENTER);
		owner.add(popup);

		startShowingTimer.start();
	}
	
	public void stopShowing() {
		if(PrefuseTooltip.this.popup.isVisible() && !isSticky) {
			stopShowingTimer.start();
		} else {
			startShowingTimer.stop();
		}
	}
	
	public void startShowingImmediately() {
		stopShowingTimer.stop();
		if(!PrefuseTooltip.this.popup.isVisible()) {
			startShowingTimer.stop();
			bringToFront();
			popup.setVisible(true);
		}
	}
	
	public void stopShowingImmediately() {
		startShowingTimer.stop();
		if(PrefuseTooltip.this.popup.isVisible() && !isSticky) {
			stopShowingTimer.stop();
			popup.setVisible(false);
		}
	}
	
	public void setSticky(boolean isSticky) {
		this.isSticky = isSticky;
	}
	public boolean isSticky() { return isSticky; }
	
	public void bringToFront() {
		popup.getParent().setComponentZOrder(popup, 0);
	}
	
	protected class ShowTimerAction implements java.awt.event.ActionListener
	{
		protected boolean showTimer;
		
		protected ShowTimerAction(boolean showTimer) {
			this.showTimer = showTimer;
		}
		
		public void actionPerformed(java.awt.event.ActionEvent e) {
			if(showTimer) {
				PrefuseTooltip.this.startShowingImmediately();
			} else {
				PrefuseTooltip.this.stopShowingImmediately();
			}
		}
	}
	
	protected class PrefuseTooltipListener extends java.awt.event.MouseAdapter {
		public void mouseEntered(MouseEvent e) {
			stopShowingTimer.stop();
		}
		
		public void mouseExited(MouseEvent e) {
			if(((java.awt.Component)e.getSource()).getMousePosition() != null) {
				// don't stop showing if we are still inside the contents.
				// this is to fix the "feature" where mouseExited is fired when
				// the cursor is moved over a child component of contents. eg,
				// when the cursor is moved onto a JButton that is inside a
				// JPanel contents box, etc.
				return;
			}
			PrefuseTooltip.this.stopShowing();
		}
	}
	
	// override this method when you implement a PrefuseTooltip
	abstract protected java.awt.Component getContents();
}

class HoverTooltip extends ControlAdapter {
	PrefuseTooltip activeTooltip;

	public void itemExited(VisualItem item, MouseEvent e) {
		if(activeTooltip != null) {
			activeTooltip.stopShowing();
		}
	}
	
	public void itemEntered(VisualItem item, MouseEvent e) {
		if(item instanceof Node) {
			showNodeTooltip(item, e);
		} else if(item instanceof Edge) {
			showEdgeTooltip(item, e);
		}
	}
	
	public void itemPressed(VisualItem item, java.awt.event.MouseEvent e) {
		if(activeTooltip != null) {
			activeTooltip.stopShowingImmediately();
		}
	}
	
	public void itemReleased(VisualItem item, java.awt.event.MouseEvent e) {
		if(item instanceof Node) {
		showNodeTooltip(item, e);
		} else if(item instanceof Edge) {
			showEdgeTooltip(item, e);
		}
	}
	
	protected void showNodeTooltip(VisualItem item, java.awt.event.MouseEvent e) {
		Visualization v = item.getVisualization();
		
		showTooltip(new GraphViewNodeTooltip(
					(Display)e.getSource(),
					((prefuse.data.Node)v.getSourceTuple(item)).getString("name"),
					((prefuse.data.Node)v.getSourceTuple(item)).getString("info")),
					item,
					e);
	}
	
	protected void showEdgeTooltip(VisualItem item, java.awt.event.MouseEvent e) {
		/*
		Visualization v = item.getVisualization();
		Edge edge = (prefuse.data.Edge)v.getSourceTuple(item);
		String connector;
		
		if(edge.isDirected()) {
			connector = " -> ";
		} else {
			connector = " - ";
		}
		
		showTooltip(new InterlinkenEdgeTooltip(
					(Display)e.getSource(),
					edge.getSourceNode().getString("name") + connector + edge.getTargetNode().getString("name")),
					item,
					e);
		*/
	}
	
	protected void showTooltip(PrefuseTooltip ptt, VisualItem item, java.awt.event.MouseEvent e) {
		if(activeTooltip != null) {
			activeTooltip.stopShowingImmediately();
		}
		
		activeTooltip = ptt;

		activeTooltip.startShowing(e.getX(), e.getY());
	}
}

class GraphViewNodeTooltip extends PrefuseTooltip {
	protected javax.swing.JLabel nameLabel;
	protected javax.swing.JLabel infoLabel;
	protected javax.swing.JPanel contentsPanel;
	protected String info;
	
	GraphViewNodeTooltip(javax.swing.JComponent owner, String name, String info) {
		super(owner);
		
		this.info = info;
		
		contentsPanel = new javax.swing.JPanel(new GridLayout(0,1));
		nameLabel = new javax.swing.JLabel();
		nameLabel.setText(name);
		infoLabel = new javax.swing.JLabel();
		infoLabel.setText(this.info);
		
	
		contentsPanel.add(nameLabel);
		contentsPanel.add(infoLabel);
		//contentsPanel.add(new javax.swing.JButton(new javax.swing.AbstractAction("Context Menu") {
        //    public void actionPerformed (java.awt.event.ActionEvent e) {
        //    	System.out.println("Gender for " + GraphViewNodeTooltip.this.nameLabel.getText() + ": " + GraphViewNodeTooltip.this.gender);
        //    }
		//}
		//));

		contentsPanel.setBorder(BorderFactory.createLineBorder(Color.gray));
		contentsPanel.setBackground(new Color(255, 250, 205));
	}
	
	public java.awt.Component getContents() {
		return contentsPanel;
	}
}