// DirectedGraphCanvas.java
// Copyright (C) 2000 Linyuan Lu
// Code based on David Binger's GraphPanel.java 
// Copyright (C) 1997 David Binger
// 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.

// Version 0.1
import java.awt.*;
import java.awt.event.*;

class DirectedGraphCanvas extends Canvas
implements FocusListener,
           ActionListener,
           KeyListener,
           MouseListener,
           MouseMotionListener,
           ComponentListener 
{
 // graph contains all information about graph and labels.
  DirectedGraph graph = new DirectedGraph();
    
//mode control;
    private int mode=1; //edit mode
    private boolean isEditMode(){return mode==1;};
    private boolean isViewMode(){return mode==0;};

    //set edit mode;
    public void setEditMode(){mode=1;};
    public void setViewMode(){mode=0;};
       
  protected static final int EDIT=1;


 // These are used for off screen buffering.  
  public Image img = null;
  private Dimension imageSize = new Dimension(0,0);

  // When all objects are being moved, this gives the last base point.
  private transient Point moveAllFrom = null;
  
  // When a label is being moved, this is the offset of the from
  // the labelBase to the mouse point.
  Point labelOffset = null;

  // This is only used in hitEdge(int,int).
  // It is outside the method to improve speed.
  private List hits = new List();

 public DirectedGraphCanvas(){
     super();
     //   addNotify();
     //	setSize(600,400);
     //	show();
 try {initListen();} 
       catch(NullPointerException e){};
       };

 // Register this as the great Listener.
  private final void initListen() {
     addFocusListener((FocusListener)this);
    addKeyListener((KeyListener)this);
    addMouseListener((MouseListener)this);
    addMouseMotionListener((MouseMotionListener)this);
    addComponentListener((ComponentListener)this);
  }
 private final void paintParts(Graphics g) {
     //if (!(g instanceof PSGr)) {
      // Don't erase background when generating PostScript.
      g.setColor(Color.white);
      g.fillRect(0,0,imageSize.width,imageSize.height);
      // }
    graph.paint(g);
  }

 public final void paint(Graphics g) {
    if (img==null) {
      imageSize = getSize(); 
      img = createImage(imageSize.width,imageSize.height);
    }
    Graphics bg = img.getGraphics();
    paintParts(bg);
    g.drawImage(img,0,0,null);
  }
  
  public final void update(Graphics g) {
    paint(g);
    //   System.out.println(""+graph);
  }

  // FocusListener
  public void focusGained(FocusEvent e) { repaint(0); }
  public void focusLost(FocusEvent e){}

 // ActionListener
  public void actionPerformed(ActionEvent e){  }
// MouseListener
  public void mouseMoved(MouseEvent e) {}

  // Handle the possibility that we are dragging a Vertex or a Vertex label.
  public final boolean mouseDraggedVertex(int x,int y,MouseEvent e) {
      if(!isEditMode()) return false;
    if (graph.selected instanceof DirectedVertex) {
      DirectedVertex v = (DirectedVertex)(graph.selected);
      if (labelOffset==null) {
        v.move(x,y);
      }
      else v.moveLabel(x-labelOffset.x,y-labelOffset.y);
      repaint(0);
      return true;
    } else return false;
  }

// Handle the possibility that we are dragging an Edge or an Edge label.
  public final boolean mouseDraggedEdge(int x,int y,MouseEvent e) { 
      if(!isEditMode()) return false;
    if (graph.selected instanceof DirectedEdge) {
      DirectedEdge ed = (DirectedEdge)(graph.selected);
      DirectedVertex v = ed.end;
      if (labelOffset==null) {
        if (!graph.vertices.contains(v)) {
	    //   setCursor(Cursor.getPredefinedCursor(CROSSHAIR_CURSOR));
          v.move(x,y);
        }
        else return false;
      } else {
        ed.moveLabel(x-labelOffset.x,y-labelOffset.y);
      }
      repaint(0);
      return true;
    } else return false;
  }
  
  // MouseListener
  public void mouseDragged(MouseEvent e) { 
      if(!isEditMode()) return;
    int x = e.getX();
    int y = e.getY();
    if (moveAllDrag(x,y,e)) return;
    if (mouseDraggedVertex(x,y,e)) return;
    if (mouseDraggedEdge(x,y,e)) return;
  }

  // MouseListener
  public void mouseExited(MouseEvent e) { 
      if(!isEditMode()) return ;
    labelOffset=null;
    moveAllFrom=null;
    graph.selected=null;
  }

  // MouseListener
  public void mouseEntered(MouseEvent e) {}


// What Vertex, if any, is at the point x,y?
  public final DirectedVertex hitVertex(int x,int y) {
    DirectedVertex v;
    int n = graph.vertices.length;
    Object a[] = graph.vertices.elements;
    for (int j=0;j<n;j++) {
      v = (DirectedVertex)a[j];
      if (v.hitDisk(x,y)) return v;
    }
    return null;
  }

 
  // What Edge, if any, is at the point x,y?
  public final DirectedEdge hitEdge(int x,int y) {
    DirectedEdge e;
    int n = graph.edges.length;
    Object a[] = graph.edges.elements;
    hits.length = 0;
    int limit = 8;
    for (int j=0;j<n;j++) {
      e = (DirectedEdge)a[j];
      if (e.near(x,y,limit)) hits.push(e);
    }
    if (hits.length==0) return null;
    while (hits.length>1) {
	if(hits.length==2){//two direction of one edge.
            DirectedEdge e0= (DirectedEdge)hits.elements[0];
	    DirectedEdge e1= (DirectedEdge)hits.elements[1];
	    if(e0.start.equals(e1.end) && e1.start.equals(e0.end)){
		Position c=e0.start.position;
		Position b=e0.end.position;
		if((c.x-x)*(c.x-x)+(c.y-y)*(c.y-y)
		   >(b.x-x)*(b.x-x)+(b.y-y)*(b.y-y)) return e0;
		else return e1;
	    }
	}
      hits.length = 0;
      limit--;
      for (int j=0;j<n;j++) {
        e = (DirectedEdge)a[j];
        if (e.near(x,y,limit)) hits.push(e);
      }      
    }
    return (DirectedEdge)(hits.elements[0]);
  }

 // What Vertex, if any, has a label at the point x,y?
  public final DirectedVertex hitVertexLabel(int x,int y) {
    DirectedVertex v;
    int n = graph.vertices.length;
    Object a[] = graph.vertices.elements;
    for (int j=0;j<n;j++) {
      v = (DirectedVertex)a[j];
      if (v.hitLabel(x,y)) return v;
    }
    return null;
  }

 // What Edge, if any, has a label at the point x,y?
  public final DirectedEdge hitEdgeLabel(int x,int y) {
    DirectedEdge e;
    int n = graph.edges.length;
    Object a[] = graph.edges.elements;
    for (int j=0;j<n;j++) {
      e = (DirectedEdge)a[j];
      if (e.hitLabel(x,y)) return e;
    }
    return null;
  }

  // Make v be the selected item.
  private final void select(Selectable v) {
    graph.select(v);
  }

 // There was a mousePress on vertex v.
  public final boolean pressedOnVertex(DirectedVertex v,MouseEvent e) {
    if (v==null) return false;
    if (0!=(e.getModifiers() & e.ALT_MASK)) {
        v.alter();
        select(v);
        repaint(0);
        return true;
      } 
    if (v.equals(graph.selected)) {
      // Start edge.
      DirectedVertex v2 = new DirectedVertex(e.getX(),e.getY());
      v2.size = 1;  
      DirectedEdge ed = graph.addEdge(v,v2);
          select(ed);
            } 
    else {
      select(v);
    }
    repaint(0);
    return true;
  }

 // There was a mousePress on the label of v.
  public boolean pressedOnVertexLabel(DirectedVertex v,MouseEvent e) {
    if (v==null) return false;
    labelOffset = new Point(e.getX()-v.labelBase.x,
                    e.getY()-v.labelBase.y);
    select(v);
    repaint(0);
    return true;

  }

  // There was a mousePress on the label of ed.
  public boolean pressedOnEdgeLabel(DirectedEdge ed,MouseEvent e) {
    if (ed==null) return false;
    labelOffset = new Point(e.getX()-ed.labelBase.x,
                    e.getY()-ed.labelBase.y);
    select(ed);
    repaint(0);
    return true;
  }
  // There was a mousePress on ed.
  public boolean pressedOnEdge(DirectedEdge ed,MouseEvent e) {
    if (ed==null) return false;
    int x = e.getX();
    int y = e.getY();
    //   if (ed.nearStart(x,y)) ed.toggleStart();
    // if (ed.nearEnd(x,y)) ed.toggleEnd();
    select(ed);
    repaint(0);
    return true;
  }  
// Handle the possibility that this event should create
  // a new Vertex.
  public boolean newVertex(int x,int y,MouseEvent e) {
    if (e.getClickCount()>1) {
      select(graph.addVertex(x,y));
      repaint(0);
      return true;
    } else {
      graph.unselect();
      repaint(0);
      return false;
    }
  }
 // MouseListener
  public void mousePressed(MouseEvent e) { 
      if(!isEditMode()) return;
    int x = e.getX();
    int y = e.getY();
    if ((e.getModifiers() & (e.SHIFT_MASK | e.CTRL_MASK))!=0) {
      if (pressedOnVertex(hitVertex(x,y),e)) return;
      if (pressedOnEdge(hitEdge(x,y),e)) return;
    }
    if (pressedOnVertexLabel(hitVertexLabel(x,y),e)) return;
    if (pressedOnVertex(hitVertex(x,y),e)) return;
    if (pressedOnEdgeLabel(hitEdgeLabel(x,y),e)) return;
    if (pressedOnEdge(hitEdge(x,y),e)) return;
    if (newVertex(x,y,e)) return;
    if (moveAllStart(x,y,e)) return;
  }

  // Begin moving the entire graph.
  public boolean moveAllStart(int x,int y,MouseEvent e) { 
      if(!isEditMode()) return false;
    if (0!=(e.getModifiers() & e.CTRL_MASK)) {
      moveAllFrom = new Point(x,y);
      //      setCursor(Cursor.getPredefinedCursor(HAND_CURSOR));
      return true;
    } else return false;    
  }

  // Continue moving the entire graph.
  public boolean moveAllDrag(int x,int y,MouseEvent e) { 
      if(!isEditMode()) return false;
    if (moveAllFrom==null) return false;
    graph.moveRelative(x-moveAllFrom.x,y-moveAllFrom.y);
    moveAllFrom.x = x;
    moveAllFrom.y = y;
    repaint(0);
    return true;
  }
 // Stop moving the entire graph.
  public void moveAllEnd() {
    moveAllFrom = null;
    //    setCursor(Cursor.getPredefinedCursor(DEFAULT_CURSOR));
  }

  // MouseListener
  public void mouseClicked(MouseEvent e) { }

 // MouseListener
  public void mouseReleased(MouseEvent e) {
    DirectedVertex v = hitVertex(e.getX(),e.getY());
    if (labelOffset!=null) mouseDragged(e);
    labelOffset=null;
    if (moveAllFrom!=null) {
      mouseDragged(e);
      moveAllEnd();
      return;
    }
    if (graph.selected instanceof DirectedEdge) {
      DirectedEdge ed = (DirectedEdge)(graph.selected);
      // setCursor(Cursor.getPredefinedCursor(DEFAULT_CURSOR));
      if ((v!=null) && (v!=ed.start)) {
        ed.end = v;
        v.addOutEdge(ed);
        repaint(0);
      } else {
        if (!graph.vertices.contains(ed.end)) {
          graph.delEdge(ed);
          repaint(0);
        }
      }
   }
  }

  // Shorten the selected label.
  public void deleteChar() {
    if (graph.selected!=null) {
      graph.selected.shortenLabel();
      repaint(0);
    }
  }

  // Remove the selected item from the graph.
  public void deleteObject() {
    if (graph.selected!=null) {
      if (graph.selected instanceof DirectedEdge) {
        graph.delEdge((DirectedEdge)(graph.selected));
      } else if (graph.selected instanceof DirectedVertex) {
        graph.delVertex((DirectedVertex)(graph.selected));
      }
      repaint(0);
    }
    else {
	graph=new DirectedGraph();
	repaint(0);
    }
    
  }

  // Alter the size of the selected item by d.
  public void adjustSize(int d) {
    if (graph.selected!=null) {
      graph.selected.adjustSize(d);
      repaint(0);
    }
  }

 // Alter the size (by d) of all items of the
  // same type as the selected item.
  public void adjustSizes(int d) {
    if ((graph.selected==null) ||
        (graph.selected instanceof DirectedVertex)) {
      int n = graph.vertices.length;
      for (int j=0;j<n;j++) {
        graph.vertex(j).adjustSize(d);
      }
    }
    if ((graph.selected==null) ||
        (graph.selected instanceof DirectedEdge)) {
      int n = graph.edges.length;
      for (int j=0;j<n;j++) {
        graph.edge(j).adjustSize(d);
      }
    }
    repaint(0);
  }
  // Alter the font size of the selected.
  public void adjustFont(int d) {
    if (graph.selected!=null) {
      graph.selected.adjustFont(d);
    } else graph.adjustFont(d);
    repaint(0);
  }
// Alter the font size of all items of the same
  // type as the selected.
  public void adjustFonts(int d) {
    int n = graph.vertices.length;
    Object a[] = graph.vertices.elements;
    for (int j=0;j<n;j++) {
      ((DirectedVertex)a[j]).adjustFont(d);
    }
    n = graph.edges.length;
    a = graph.edges.elements;
    for (int j=0;j<n;j++) {
      ((DirectedEdge)a[j]).adjustFont(d);
    }
    repaint(0);
  }
 // KeyListener
  public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
      case 127: // delete
        if (e.getModifiers()==0) deleteChar();
        else deleteObject();
        break;
      case 37: // left
        if (e.getModifiers()==0) adjustSize(-1);
        else adjustSizes(-1);
        break;
      case 39: // right
        if (e.getModifiers()==0) adjustSize(+1);
        else adjustSizes(+1);
        break;
      case 38: // up
        if (e.getModifiers()==0) adjustFont(+1);
        else adjustFonts(+1);
        break;
      case 40: // down
        if (e.getModifiers()==0) adjustFont(-1);
        else adjustFonts(-1);
        break;
    }
  }

  // KeyListener
  public void keyReleased(KeyEvent e) {}

  // KeyListener
  public void keyTyped(KeyEvent e) {
    if (13==e.getKeyCode()) { // newline
      graph.unselect();
      repaint(0);
      return;
    } 
    if (0!=(e.getModifiers() & e.CTRL_MASK)) { return; }
    if (graph.selected!=null) { 
      graph.selected.extendLabel(e.getKeyChar());
      repaint(0);
    }
  }
  // ComponentListener
  public void componentResized(ComponentEvent e) { img = null; }
  public void componentMoved(ComponentEvent e) {}
  public void componentShown(ComponentEvent e){}
  public void componentHidden(ComponentEvent e) {}

}






