// Medusa is a graph viewer which allows interactive editing of networks
// (edges and nodes) and also connects to databases.
//
// Copyright (C) 2006 Sean Hooper
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA 02111-1307 USA

package medusa.display;


import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.ProgressMonitor;
import javax.swing.ToolTipManager;
import medusa.*;
import medusa.graph.Graph;
import medusa.graph.Node;
import medusa.graph.UniqueEdge;


/**
 * A panel that handles graphs, loads and saves them (I may move this to a higher level soon) and displays them
 *
 * Most often, any extending classes will override the display methods, especially
 * paintNode and paintEdge.
 * @author Sean Hooper
 */
public class BasicGraphPanel extends BasicGraphRenderer
        implements MouseListener, MouseMotionListener,
        Runnable{
    
    
    
    /**
     * Starts the relax thread, applying repelling forces between nodes and attracting forces between nodes
     * connected by edges.
     */
    protected Thread relaxThread;
    
    
    // the graph object
    
    /** todo: move all load functions to the frame!!!
     * enough to set the graph
     */
    public void setGraph(Graph g){
        graph=g;
        updateNodes();
    }
    
    /**
     * Updates the nodes for quicker repainting
     */
    public void updateNodes(){
        calculateEdgeLength();
        //nodeArray=graph.nodeArray();
        //nnodes=graph.getNodeSize();
    }

    public Node getPick() {
        return pick;
    }

    public boolean isRunning() {
        return running;
    }
    
    // The State switches

    public boolean running=false;
    
    /**
     * If true, only the moving nodes will be displayed. This can be good if
     * you have a large graph, since repainting will occur.
     * @param trueOrFalse True if hide, False (default) if show all nodes while moving
     */
    public void setHideWhenMove(boolean trueOrFalse){
        hideWhenMove=trueOrFalse;
    }
    /**
     * Setting this to true results in a composite effect on edges based on the confidence level
     * of the edge.
     * @param trueOrFalse True if edges are displayed with alpha
     */
    public void setAlpha(boolean trueOrFalse){
        alpha=trueOrFalse;
    }
    
    // used for marking a node with a pointer
    Node marked = null;
    
    // the initial font
    
    // change font size
    /**
     * Set font size
     * @param size Font size
     */
    public void changeFont(int size){
        nodeFont= new Font("TimesNewRoman", Font.PLAIN,size);
        fontSize=size;
    }
    
    // Node size, was final, but we decided to make it settable
    
    /**
     * Set node size
     * @param nodeSize Node size in pixels
     */
    public void setNodeSize(int nodeSize){
        if (nodeSize<1)
            nodeSize=10;
        this.nodeSize=nodeSize;
        correct=(int)(nodeSize/2.);
        
    }
    
    // correction for node size
    //protected Color
    
    // the progress bar for use in spring
    ProgressMonitor energyBar;
    ActionListener updateEnergyBar;
    javax.swing.Timer timer;
    
    // popup menu for editing
    //PopupMenu popup;
    
    /**
     * Background color
     * @param c Color c
     */
    public void setBackgroundColor(Color c){
        setBackground(c);
    }
    
    
    /* Composite is used for fading effects
     */
    
    /* returns the color as a string for the save file
     */
    /**
     * Gets a color as a string
     * @param color Color color
     * @return String representation of color
     */
    public String colorToString(Color color){
        return color.getRed()+","+color.getGreen()+","+color.getBlue();
    }
    
    /* These settings can be toggled -----------------------*/
    /**
     * If pretty is selected, multiple edges will be drawn in colors. If not selected,
     * edges are drawn as unique edges and in grey.
     * @param trueOrFalse Boolean pretty
     */
    public void setPretty(boolean trueOrFalse){
        pretty=trueOrFalse;
    }
    
    /**
     * Set true if you want to show edges as directed.
     * @param trueOrFalse Boolean arrows or not
     */
    public void setArrows(boolean trueOrFalse){
        arrow=trueOrFalse;
    }
    
    /**
     * Show labels or hide them
     * @param trueOrFalse Boolean true if show labels
     */
    public void setLabel(boolean trueOrFalse){
        label=trueOrFalse;
    }
    /**
     * Show confidence of edges using either alpha or patterns
     * @param trueOrFalse true if confidence is to be shown
     */
    public void setConfidence(boolean trueOrFalse){
        showConfidence=trueOrFalse;
    }
    /**
     * Show annotation instead of node names
     * @param trueOrFalse true: shows annotation. false: shows node label
     */
    public void setAnnotation(boolean trueOrFalse){
        showAnnotation=trueOrFalse;
    }
    
    /**
     * Sets the basic edge color.
     * @param color The color that will be used when interactions are not shown
     */
    public void setBasicEdgeColor(Color color){
        basicEdgeColor=color;
    }
    /**
     * Sets the font color
     * @param color Font color
     */
    public void setFontColor(Color color){
        fontColor=color;
    }
    
    /**
     * Offset for drawing labels
     */
    public static final double offset = 0.3;
    
    // These functions communicate with the StringletPanelSettings object
    /**
     * Returns the color for interaction i
     * @param i Interaction type
     * @return The color as defined
     */
    public Color getColor(Integer i){
        return getStringSettings().getColor(i);
    }
    /**
     * Sets the color for interaction i in MedusaSettings.
     * This should really be handled at a higher level...
     * @param i Interaction type
     * @param color Edge color for interaction i
     */
    public void setColor(Integer i, Color color){
        getStringSettings().setColor(i, color);
    }
    
    
    
   
    
    double edgeLen; // the length for relaxation
        /**
     * Returns the current edge length
     * @return edge length
     */
    public double getEdgeLen(){
        return edgeLen;
    }
    
    // calculate the length
    public void calculateEdgeLength(){
        int nnodes=graph.getNodeSize();
        if (nnodes>0)
            edgeLen=Math.sqrt(getPanelWidth()*getPanelHeight()/nnodes);
    }
    
    /**
     * Manually sets the edge length.
     * This is the steady-state length between two nodes. Don't expect this
     * as an exact value, since most graphs are more complex than two nodes
     * connected by an edge.
     * @param newEdgeLen New edge length
     */
    public void setEdgeLen(double newEdgeLen){
        if (newEdgeLen>0)
            edgeLen=newEdgeLen;
        else
            calculateEdgeLength();
    }
    
    // scaler variable
    protected double scale=1.0;
    protected double absoluteScale=1.0;
    
    /**
     * Sets the scale of the graph. large scales will zoom in on the
     * graph, while scales < 1 zoom out.
     * @param scale scale factor
     */
    public void setScale(double scale){
        this.scale=scale;
    }
    public void setAbsoluteScale(double scale){
        this.absoluteScale=scale;
    }
    

    
    /**
     * Sets the x and y parameters for the drawing area
     * @param x width
     * @param y height
     */
    public void setTimeFrameXY(int x, int y){
        
        setTimeFrameXY(new Dimension(x,y));
    }
    
    /**
     *
     * @param d
     */
    public void setTimeFrameXY(Dimension d){
        dims=d;
        //setSize(d);
        setPanelWidth((int) (getPanelWidth() * scale));
        setPanelHeight((int) (getPanelHeight() * scale));
        panelWidth=(int)(d.getWidth() * scale);
        panelHeight=(int)(d.getHeight() * scale);
        rescaleNodes();
        calculateEdgeLength();
        // setPreferredSize(new Dimension(getPanelWidth(), getPanelHeight()));
        dims=new Dimension(panelWidth, panelHeight);
        setPreferredSize(new Dimension(panelWidth, panelHeight));
        setSize(new Dimension(panelWidth, panelHeight));
        //setMinimumSize(new Dimension(getPanelWidth(), getPanelHeight()));
    }
    
        // Tool tips, if labels are off
    public String getToolTipText(MouseEvent e){
        Node node = getClosest(e);
        if (node==null)
            return null;
        //System.out.println(label+" "+showAnnotation);
        /*if (!label) {
         
            return node.getLabel();
            }*/
        if (showAnnotation){
            return node.getLabel();
        }
        
        return node.getAnnotation();
    }
    
        // Set orientations, etc in one go
    /**
     * Inits edges, fixes orientation
     */
    public void initEdges(){
        autoFixOrientation();
        // was fixOrientation();
        calculateEdgeLength();
    }
    
    // adjust the node x and y according to the new panel sizes
    private void rescaleNodes(){
        graph.rescaleNodes(scale);
    }
    
    /**
     * Sets new MedusaSettings
     * @param ss new settings
     */
    public void updateStringSettings(MedusaSettings ss){
        setStringSettings(ss);
    }
    
    /*
     * Constructor
     */
    /**
     * Constructs a new BasicGraphPanel with settings
     * @param stringSettings settings
     */
    public BasicGraphPanel(MedusaSettings stringSettings){
        graph = new Graph();
        setBackground(Color.white);
        setOpaque(true);
        
        edgeLen=100.; //play safe!
        setPreferredSize(new Dimension(getPanelWidth(), getPanelHeight()));
        addMouseListener(this);
        setStringSettings(stringSettings);
        
        ToolTipManager.sharedInstance().setInitialDelay(0);
        ToolTipManager.sharedInstance().setDismissDelay(1000000);
        setToolTipText("");
        
    }
    
    /*
     * Constructor with default medusasettings
     */
    public BasicGraphPanel(){
        this((new MedusaSettings()));
        
    }
    
    // The run function for relaxThread
    public void run(){
        Thread myThread=Thread.currentThread();
        temperature=INITIAL_TEMP;
        resetDelta();
        //double rand=Math.random();
        //boolean grav=(rand<0.2);
        running=true;
        while (relaxThread==myThread){
            
            relax2();
            
            try{
                Thread.sleep(50);
            } catch (InterruptedException e){
                running=false;
                break; //back to work
            }
        }
        running=false;
        //repaint();
    }
    
    // thread is not running. start it here
    public void start(){
        if (relaxThread==null)
            relaxThread=new Thread(this);
        running=true;
        relaxThread.start();
        
    }
    
    // kill it
    public void stop(){
        if (relaxThread!=null){
            running=false;
            relaxThread=null;
        }
    }
    
       /*
        * Implement the FR algorithm. This uses the FRspring class
        *
     * Starts the automatic layout
     */
    public synchronized void energy(){
        //Dimension d = getSize();
        energyBar=new ProgressMonitor
                (this,"Fruchterman Rheingold Algorithm","",0,200);
        frSpring =
                new FRspring(graph, getPanelWidth(), getPanelHeight());
        timer=new javax.swing.Timer(500,runFR);
        energyBar.setProgress(0);
        energyBar.setMillisToDecideToPopup(1000);
        frSpring.start();
        // this timer will update the map
        timer.start();
        
        
    }
    protected FRspring frSpring;
    /*
     * Implement an actionPerformed that updates the progress bar
     */
    protected ActionListener runFR = new ActionListener(){
        public void actionPerformed(ActionEvent evt){
            energyBar.setProgress(frSpring.getCurrent());
            //System.out.println(frSpring.getCurrent());
            if (energyBar.isCanceled() || frSpring.isDone()){
                timer.stop();
                frSpring.stop();
                energyBar.close();
                //energyBar.setProgress(0);
                //nodes=frSpring.getNodes();
                setCursor(null);
                repaint();
                
            }
        }
    };
    /*
     * Paint the panel. is called with repaint.
     * Try without synch
     */
    
    // fix all or none
    /**
     * Fix or unfix all nodes
     * @param trueOrFalse true fixes all nodes. false unfixes all
     */
    public void setFix(boolean trueOrFalse){
        for (Iterator nodes = graph.nodesIterator(); nodes.hasNext();){
            Node n = (Node) nodes.next();
            n.setFixed(trueOrFalse);
        }
        repaint();
    }
    // Invert fixation
    public void invertFix(){
        for (Iterator nodes = graph.nodesIterator(); nodes.hasNext();){
            Node n = (Node)nodes.next();
            n.setFixed(!n.isFixed());
        }
        repaint();
    }
    protected void edgeAttract(){
        for (Iterator uniqueEdges = graph.uniqueEdgesIterator();uniqueEdges.hasNext();) {
            UniqueEdge e = (UniqueEdge) uniqueEdges.next();
            Node n1=graph.getNode(e.getFromName());
            Node n2=graph.getNode(e.getToName());
            double vx = n2.getX() - n1.getX();
            double vy = n2.getY() - n1.getY();
            double len = Math.sqrt(vx * vx + vy * vy);
            //double len = Point.distance(n1.getX(),n1.getY(), n2.getX(), n2.getY());
            len = (len == 0) ? .0001 : len;
            // try this (len * 3)
            double f = (edgeLen - len) / (len*3);
            double dx = f * vx;
            double dy = f * vy;
            
            n2.setDX(n2.getDX() + dx);
            n2.setDY(n2.getDY() + dy);
            n1.setDX(n1.getDX() + -dx);
            n1.setDY(n1.getDY() + -dy);
        }
    }
    
    // edges attract
    protected void edgeAttract2(){
        for (Iterator uniqueEdges = graph.uniqueEdgesIterator();uniqueEdges.hasNext();) {
            UniqueEdge e = (UniqueEdge) uniqueEdges.next();
            Node n1=graph.getNode(e.getFromName());
            Node n2=graph.getNode(e.getToName());
            double vx = n2.getX() - n1.getX();
            double vy = n2.getY() - n1.getY();
            double len = Math.sqrt(vx * vx + vy * vy);
            //double len = Point.distance(n1.getX(),n1.getY(), n2.getX(), n2.getY());
            len = (len == 0) ? .00001 : len;
            // try this (len * 3)
            //double f = (edgeLen - len);// / (len*3);
            //double dx = f * vx;
            //double dy = f * vy;
            double dx=-len*vx;
            double dy=-len*vy;
//            System.out.println("len: "+len);
//            System.out.println("vx: "+vx+" vy:"+vy);
//            System.out.println("dx: "+dx+" dy:"+dy);
            n2.setDX(n2.getDX() + dx);
            n2.setDY(n2.getDY() + dy);
            n1.setDX(n1.getDX() + -dx);
            n1.setDY(n1.getDY() + -dy);
        }
    }
    
    protected void nodesRepel2(final double critXDistance,
            final double critYDistance){
        
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            double dx = 0;
            double dy = 0;
            for(Iterator it2 = graph.nodesIterator(); it2.hasNext();) {
                Node node2 = (Node)it2.next();
                if(node.equals(node2)) {
                    continue;
                }
                double vx = node.getX() - node2.getX();
                double vy = node.getY() - node2.getY();
                
                
                // put in four cases here, corresponding to how the
                // distance is measured
                
                if (vx > critXDistance) { // repel over the x border right
                    vx = node.getX() - node2.getX();// - panelWidth;
                }
                if (vx < -critXDistance)  {// repel over the x border left
                    vx = node.getX() - node2.getX();// + panelWidth;
                }
                
                if (vy > critYDistance) {// repel over the y border down
                    vy = node.getY() - node2.getY();// - panelHeight;
                }
                if (vy < -critYDistance) {// repel over the y border up
                    vy = node.getY() - node2.getY();//+ panelHeight;
                }
                
                double len = Math.sqrt(vx * vx + vy * vy);
                
                // move to "move nodes"
                if (len == 0.0) { // don't allow nodes on top of eachother
                    dx += Math.random()/100;
                    dy += Math.random()/100;
                } else
                    // if (len 0.) {
            // was / 2
            //	dlen = Math.sqrt(dlen) / 3;
            //	node.setDX(node.getDX() + dx / dlen);
            //	node.setDY(node.getDY() + dy / dlen);
            // }
            node.setDX(node.getDX() + dx);
            node.setDY(node.getDY() + dy);
        }
    }
    
    protected void nodesRepel(final double critXDistance,
            final double critYDistance){
        
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            double dx = 0;
            double dy = 0;
            for(Iterator it2 = graph.nodesIterator(); it2.hasNext();) {
                Node node2 = (Node)it2.next();
                if(node.equals(node2)) {
                    continue;
                }
                double vx = node.getX() - node2.getX();
                double vy = node.getY() - node2.getY();
                
                
                // put in four cases here, corresponding to how the
                // distance is measured
                
                if (vx > critXDistance) { // repel over the x border right
                    vx = node.getX() - node2.getX() - getPanelWidth();
                }
                if (vx < -critXDistance)  {// repel over the x border left
                    vx = node.getX() - node2.getX() + getPanelWidth();
                }
                
                if (vy > critYDistance) {// repel over the y border down
                    vy = node.getY() - node2.getY() - getPanelHeight();
                }
                if (vy < -critYDistance) {// repel over the y border up
                    vy = node.getY() - node2.getY() + getPanelHeight();
                }
                
                double len = vx * vx + vy * vy;
                
                if (len == 0) { // don't allow nodes on top of eachother
                    dx += Math.random();
                    dy += Math.random();
                } else
                    if (len 0.) {
                // was / 2
                dlen = Math.sqrt(dlen) / 3;
                node.setDX(node.getDX() + dx / dlen);
                node.setDY(node.getDY() + dy / dlen);
            }
        }
    }
    
    /* This relax funtion repels nodes over borders
     * in order to get a fairly even distribution
     */
    protected synchronized void relax2() {
        int border=10;
        int labelPadding=10;
        double critXDistance=getPanelWidth()*3/4;
        double critYDistance=getPanelHeight()*3/4;
        
        // attraction from edges
        edgeAttract();
        
        // repellation from nodes
        nodesRepel(critXDistance, critYDistance);
        
        // move nodes
        moveNodes(border, labelPadding);
        
        repaint();
        if ((cool)&(coolToggle)){
            temperature-=0.02;
            temperature=Math.max(temperature,0.0);
        }
        
    }
    
    protected void moveNodes2(int border, int labelPadding){
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            // quick cool down if too small
            double limit=0.001;
            if (Math.abs(node.getDX()) < limit) {
                node.setDX(0);
            }
            if (Math.abs(node.getDY()) < limit) {
                node.setDY(0);
            }
            if ((cool) & (coolToggle)){
                node.setDX(node.getDX() * temperature);
                node.setDY(node.getDY() * temperature);
            }
            
            // move all unfixed nodes
            if (!node.isFixed()) {
                node.setX(node.getX() + node.getDX());
                node.setY(node.getY() + node.getDY());
            }
            
            
            
            node.setDX(node.getDX() / 2);
            node.setDY(node.getDY() / 2);
        }
        
    }
    
    protected void moveNodes(int border, int labelPadding){
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            // quick cool down if too small
            if (Math.abs(node.getDX()) < 1) {
                node.setDX(0);
            }
            if (Math.abs(node.getDY()) < 1) {
                node.setDY(0);
            }
            if ((cool) & (coolToggle)){
                node.setDX(node.getDX() * temperature);
                node.setDY(node.getDY() * temperature);
            }
            
            // move all unfixed nodes
            if (!node.isFixed()) {
                node.setX(node.getX() + Math.max(-5, Math.min(5, node.getDX())));
                node.setY(node.getY() + Math.max(-5, Math.min(5, node.getDY())));
            }
            
            // make sure they stay in their borders
            if (node.getX() < border) {
                node.setX(border);
            } else if (node.getX() > getPanelWidth()-2*border) {
                node.setX(getPanelWidth()-2*border);
            }
            if (node.getY() < border + labelPadding) {
                node.setY(border + labelPadding);
            } else if (node.getY() > getPanelHeight() - border) {
                node.setY(getPanelHeight() - border);
            }
            
            node.setDX(node.getDX() / 2);
            node.setDY(node.getDY() / 2);
        }
    }
    
    static final double INITIAL_TEMP=1.0;
    double temperature=INITIAL_TEMP;
    boolean cool=true;
    // settable option for cooling
    boolean coolToggle=false;
    
    /**
     * Allows cooling of relaxation. If cooling is on, attraction
     * and repellation energies will slow if the mouse is not dragging
     * nodes.
     * @param trueOrFalse true: cooling
     */
    public void setCool(boolean trueOrFalse){
        coolToggle=trueOrFalse;
    }
    
    private void resetDelta(){
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            node.setDY(0);
            node.setDX(0);
        }
    }
    
    protected synchronized void gravity() {
        // calculate dy
        double g=9.81;
        double dt=0.1;
        double temp =g*dt;
        double cooldown=0.9;
        int yLimit=600;
        // get velocity & move nodes
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            if (!node.isFixed()){
                node.setDY(node.getDY() + temp);
                node.setY(node.getY() + 0.5 * temp * dt + node.getDY() * dt);
                if (node.getY() > 600){
                    //n1.dy =- n1.dy * cooldown;
                    node.setDY(-node.getDY() * cooldown);
                    node.setY(600);
                }
            }
        }
        repaint();
    }
    
    
    
    /**
     * Rotates graph
     * @param t rotation factor
     */
    public void rotate(double t) {
        // loop through all nodes and rotate
        double x,y,cos,sin;
        double pw2=getPanelWidth()/2;
        double ph2=getPanelHeight()/2;
        cos=Math.cos(t);
        sin=Math.sin(t);
        //System.out.println(cos+ " "+sin);
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            x=node.getX()-pw2;
            y=node.getY()-ph2;
            node.setX(x*cos-y*sin+pw2);
            node.setY(x*sin+y*cos+ph2);
            //System.out.println(x+" "+y+" "+" "+node.getX());
        }
        
        //repaint();
    }
    
    /**
     * Mirrors graph vertically or horizontally (or both)
     * @param xFlip true if flip
     * @param yFlip true if flip
     */
    public void mirror(boolean xFlip, boolean yFlip) {
        // loop through all nodes and rotate
        double x,y;
        
        //flip x
        if (xFlip)
            for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            node.setX(getPanelWidth()-node.getX());
            }
        //flip y
        if (yFlip)
            for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            node.setY(getPanelWidth()-node.getY());
            }
        
        //repaint();
    }
    
    
    // change the color of the selected nodes
    public void changeNodeColor(int red, int green, int blue){
        Color color=new Color(red,green,blue);
        for (Iterator nodes = graph.nodesIterator(); nodes.hasNext() ;) {
            Node n = (Node) nodes.next();
            if (n.isFixed())
                n.setColor(getCurrentNodeColorSet(),color);
        }
        //graph.setNodeColor(
    }
    public void changeNodeColor(Color color){
        for (Iterator nodes = graph.nodesIterator(); nodes.hasNext() ;) {
            Node n = (Node) nodes.next();
            if (n.isFixed())
                n.setColor(getCurrentNodeColorSet(),color);
        }
        
    }
    
    
    /**
     * Saves graph as an image
     * @param path file to save as
     * @param param type of picture.
     * Currently, 0=jpg
     * 1=png
     */
    public void saveImage(String path, int param){
        // param = 0 for jpg, = 1 for png
        //setBorder(Border.)
        Component comp=(Component) this;
        //int w = panelWidth;
        //int h = panelHeight;
        Dimension d = getSize();
        int w = (int)d.getWidth();
        int h = (int)d.getHeight();
        BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        
        Graphics2D g2 = im.createGraphics();
        comp.paint(g2);
        g2.dispose();
        //im contains pixel data, now
        
        //standard code:
        try{
            File file = new File(path);
            // write to jpg
            if (param==0){
                javax.imageio.ImageIO.write(im, "jpg", file);
            }
            // write to png
            if (param==1){
                javax.imageio.ImageIO.write(im, "png", file);
            }
            
            im.flush();
        } catch (IOException e){
            e.printStackTrace();
        }
    }
    
    
    public void setNodeFixed(String lbl, boolean fixed){
        graph.getNode(lbl).setFixed(fixed);
    }
    
    // return the index of the closest node
    
    protected Node getClosest(MouseEvent e){
        int radialDistance=50;
        int index=-1;
        Node bestNode = null;
        double bestdist = Double.MAX_VALUE;
        int x = e.getX();
        int y = e.getY();
        
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            double dist = (node.getX() - x) * (node.getX() - x)
            + (node.getY() - y) * (node.getY() - y);
            
            if ((dist < radialDistance) & (dist < bestdist)) {
                //pick = n;
                bestdist = dist;
                //index=i;
                bestNode = node;
            }
        }
        return bestNode;
    }
    
    // Take care of events, listen to mouse actions
    Node pick;
    boolean pickfixed;
    public void mouseClicked(MouseEvent e){
        //System.out.println("click!");
        
    }
    
    // two ints for movement of clusters
    double oldX=0.;
    double oldY=0.;
    //boolean shift=false;
    
    // Set up Rectangle objects for drag-selection
    private Rectangle currentRect = null;
    Rectangle rectToDraw=null;
    Rectangle previousRectDrawn = new Rectangle();
    
    private boolean box; // is the user creating a box selection?
    public void mousePressed(MouseEvent e){
        if (e.getButton()==MouseEvent.BUTTON1){
            handlePressLeftButton(e);
        }
        
    }
    
    public void handlePressLeftButton(MouseEvent e){
        // If we have picked a node, we will not pick it again.
        if (pick != null){
            return;
        }
        Node node = getClosest(e);
        // Several actions are possible.
        //--------------------------
        // Check control to toggle pick
        if (e.isControlDown()){
            if (node!=null){
                node.setFixed((!node.isFixed()));
            }
            pick = null;
            e.consume();
            return;
        }
        
        
        if (e.isShiftDown()){
            addMouseMotionListener(this);
            if (node!=null){
                setPick(node, e);
            }
            e.consume();
            return;
        }
        
        
        //--------------------------
        // Check that we have no modifiers
        
        if (e.getModifiers()==MouseEvent.BUTTON1_MASK){
            addMouseMotionListener(this);
            // now check if we have clicked a node
            if (node!=null){
                
                // pick up this node
                setPick(node, e);
                
            } else{
                // we have no node. draw a selection box
                int x = e.getX();
                int y = e.getY();
                currentRect = new Rectangle(x, y, 0, 0);
                updateDrawableRect(getWidth(), getHeight());
                repaint();
            }
            e.consume();
            return;
        }
        
        
        
    }
    
    private void setPick(final Node node, final MouseEvent e) {
        // pick up this node
        pick = node;
        // if hideWhenMove is selected, set the visibleGraph
        if (hideWhenMove)
            visibleGraph=graph.subGraph(node);
        
        pickfixed = pick.isFixed();
        pick.setFixed(true);
        pick.setX(e.getX());
        pick.setY(e.getY());
        oldX = pick.getX();
        oldY = pick.getY();
        temperature = INITIAL_TEMP;
        cool = false;
    }
    
    public void handlePressLeftButton_backup(MouseEvent e){
        if (pick == null){
            // System.out.println(e.getButton());
            if (e.getButton()==MouseEvent.BUTTON1){
                //System.out.println("shift: "+InputEvent.SHIFT_DOWN_MASK);
                
                //System.out.println("modifier: "+e.getModifiers()+" "+e.getMouseModifiersText(e.getModifiers()));
                
                // sort out the actions once and for all...
                // first check for shift.
                //****** Shift: move all fixed nodes
                
                
                
                if ((e.getModifiers()==16) | (e.getModifiers()==17) | (e.getModifiers()==18) ){
                    // no modifier | or shift
                    // prepare to move nodes
                    //box=false;
                    addMouseMotionListener(this);
                    Node node = getClosest(e);
                    //int index=getClosest(e);
                    if (node != null ){
                        // the case where we want to toggle fix
                        if (e.getModifiers() == 18){
                            node.setFixed((!node.isFixed()));
                            pick = null;
                        } else{
                            // pick up the pressed node
                            
                            pick = node;
                            pickfixed = pick.isFixed();
                            pick.setFixed(true);
                            pick.setX(e.getX());
                            pick.setY(e.getY());
                            oldX = pick.getX();
                            oldY = pick.getY();
                            temperature = INITIAL_TEMP;
                            cool = false;
                        }
                        repaint();
                    } else { //
                        //System.out.println("start a new selection rectangle");
                        //box=true;
                        int x = e.getX();
                        int y = e.getY();
                        currentRect = new Rectangle(x, y, 0, 0);
                        updateDrawableRect(getWidth(), getHeight());
                        repaint();
                    }
                    
                    e.consume();
                }
            }
        }
        
    }
    
    /**
     * Finds the node matching the regular expression
     * @param expression pattern to match
     * @return true if nodes were found, false if no nodes matched the pattern
     */
    public boolean findLabel(final String expression){
        //marked = null;
        Pattern labelPattern = Pattern.compile(expression);
        Matcher matcher;
        // if selection is "", remove mark
        if (expression.compareTo("")==0){
            return false;
        }
        // Else, look for a match
        for(Iterator it = graph.nodesIterator(); it.hasNext();) {
            Node node = (Node)it.next();
            
            matcher = labelPattern.matcher(node.getLabel());
            node.setFixed(false);
            if (matcher.find()){
                //marked = node;
                node.setFixed(true);
                //return node;
            }
        }
        return true;
    }
    
    public void mouseReleased(MouseEvent e){
        handleReleaseLeftButton(e);
        //handleReleaseRightButton(e);
    }
    
    protected void handleReleaseLeftButton(MouseEvent e) {
        if (e.getButton()!=MouseEvent.BUTTON1)
            return;
        removeMouseMotionListener(this);
        //System.out.println("Released");
        //if (relaxThread!=null)
        //  relaxThread.interrupt();
        
        if (e.getModifiers()==16){ // no mod
            if (pick!=null) {
                //if ((e.getX() 

Ads help maintain this website.