package iiuf.swing.graph;

/*
  TODO
  - addContext menu doesn't work after setEditable(true)
*/

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.StringTokenizer;
import java.awt.Point;
import java.awt.Container;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.AWTEvent;
import java.awt.Cursor;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTarget;
import java.lang.reflect.Array;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JScrollPane;
import javax.swing.JComboBox;
import javax.swing.JCheckBox;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.JFileChooser;
import javax.swing.UIManager;
import javax.swing.ToolTipManager;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;

import iiuf.awt.Awt;
import iiuf.swing.Swing;
import iiuf.swing.ContextMenuEnabled;
import iiuf.swing.ContextMenuManager;
import iiuf.swing.SetSelectionModel;
import iiuf.swing.HexagonalBorder;
import iiuf.util.AttributeFactory;
import iiuf.util.Attributable;
import iiuf.util.Util;
import iiuf.util.EventListenerList;
import iiuf.util.graph.GraphNode;
import iiuf.util.graph.GraphModel;
import iiuf.util.graph.DefaultGraphModel;
import iiuf.util.graph.GraphPort;
import iiuf.util.graph.DefaultGraphNode;
import iiuf.util.graph.DefaultGraphEdge;
import iiuf.util.graph.GraphModelListener;

/**
   Graph visualizer class.<p>

   (c) 2000, 2001, IIUF, DIUF<p>

   @author $Author: ohitz $
   @version $Name:  $ $Revision: 1.1 $
*/
public class GraphPanel
  extends
  JPanel
  implements
  Scrollable,
  ContextMenuEnabled,
  GraphModelListener,
  DropTargetListener
{
  public static final String IS_EDITABLE              = "IS_EDITABLE";
  public static final String SELECTION_BOUNDS_CHANGED = "SELECTION_BOUNDS_CHANGED";
  
  static final int    SELECTION_MARK_SIZE   = 6;
  static final int    SELECTION_MARK_CENTER = SELECTION_MARK_SIZE / 2;
  static final String COMPONENT_TAG         = "__component__";
  static final String GRAPH_NODE_PORT_TAG   = "__graph_node_port__";
  static final String EDGE_TAG              = "__edge__";
  
  public int COMPONENT       = -1;
  public int GRAPH_EDGE      = -1;
  public int NODE_PROPERTIES = -1;
  public int GRAPH_NODE_PORT = -1;

  protected GraphPanelEditor editor;
  
  private GraphRouter                  router;
  private GraphModel                   graph;
  private AbstractNodeComponentFactory nodeFactory;
  private AbstractGraphEdgeFactory     edgeFactory;
  private AbstractPortFactory          portFactory;
  private AbstractPropertiesFactory    propertiesFactory;
  private GraphLayout                  layout;
  private boolean                      edgeAfter;
  private HashMap                      cmpToModelMap  = new HashMap();
  private HashMap                      edgeToModelMap = new HashMap();
  private GraphNode                    showPorts;
  private GraphPort                    preferredPort;
  private SelectionModel               selectionModel = new SelectionModel();
  private Rectangle                    selection      = new Rectangle();
  private Point                        dot            = new Point();
  private Point                        mark           = new Point();
  private int                          tolerance      = 5;
  private Color                        selectionColor; 
  private Rectangle                    tmpRect = new Rectangle();
  private ToolTipManager               toolTipManager;
  private Rectangle                    selectionBoundingBox;
  private Rectangle                    oldSelectionBoundingBox;
  int                                  layoutBlock;
  int                                  settingModel;
  boolean                              edit;
  
  public GraphPanel(AbstractNodeComponentFactory nodeFactory,
		    AbstractPortFactory          portFactory,
		    AbstractGraphEdgeFactory     edgeFactory,
		    NodeLayouter layouter,
		    GraphRouter  router) {
    this();
    setNodeComponentFactory(nodeFactory);
    nodeFactory.gp = this;
    setPortFactory(portFactory);
    setGraphEdgeFactory(edgeFactory);
    setRouter(router);
    setLayouter(layouter);
  }
  
  public GraphPanel() {
    toolTipManager = ToolTipManager.sharedInstance();

    setNodeComponentFactory(new AbstractNodeComponentFactory(GraphPanel.this) {
	protected Component newNodeComponent(GraphNode node, Object[] args) {
	  return new JButton("Node");
	}
      });
    
    setPortFactory(new AbstractPortFactory() {
	protected GraphNodePort newPort(GraphPort port, Object[] args) {
	  return null;
	}
      });
    
    setGraphEdgeFactory(new AbstractGraphEdgeFactory() {
	protected GraphEdge newGraphEdge(iiuf.util.graph.GraphEdge edge,
					 GraphNode fromNode, GraphPort fromPort, 
					 GraphNode toNode,   GraphPort toPort, Object[] args) {
	  return new GraphEdge((Component)fromNode.get(COMPONENT), (GraphNodePort)fromPort.get(GRAPH_NODE_PORT),
			       (Component)toNode.get(COMPONENT),   (GraphNodePort)toPort.get(GRAPH_NODE_PORT));
	}
      });
    
    setPropertiesFactory(new AbstractPropertiesFactory() {
	protected Component newPropertiesWindow(GraphNode node, Object[] args) {
	  return null;
	}
      });
    
    setRouter(new StraightLineRouter());
    
    editor = new GraphPanelEditor(this);
    editor.setAutoscrolls(true);
    editor.setSize(0 ,0);
    add(editor);
  
    layout = new GraphLayout(new DefaultNL());
    setLayout(layout);
    selectionModel.addChangeListener(new ChangeListener() {
	public void stateChanged(ChangeEvent e) {
	  repaint();
	}
      });
    setAutoscrolls(true);
    
    editor.addMouseMotionListener(new Scroller());
    addMouseMotionListener(new Scroller());

    setSelectionColor(UIManager.getColor("Tree.selectionBackground"));
  }
  
  static class Scroller extends MouseMotionAdapter {
    public void mouseDragged(MouseEvent e) {
      Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
      ((JComponent)e.getSource()).scrollRectToVisible(r);
    }
  }
  
  public Dimension getPreferredScrollableViewportSize() {return getPreferredSize(); }
  public boolean   getScrollableTracksViewportHeight() {
    return (getParent() instanceof JViewport) ? (((JViewport)getParent()).getHeight() > getPreferredSize().height) : false;
  }
  public boolean   getScrollableTracksViewportWidth()  {
    return (getParent() instanceof JViewport) ? (((JViewport)getParent()).getWidth() > getPreferredSize().width) : false;
  }
  public int       getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {return 8;}
  public int       getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
    int result = orientation == SwingConstants.VERTICAL ? visibleRect.height : visibleRect.width;
    if(result > 32) result -= 16;
    return result;
  }
  
  public void setSelectionColor(Color newColor) {
    selectionColor = newColor;
  }
  
  public Color getSelectionColor() {
    return selectionColor;
  }

  public void setEdgeSelectionTolerance(int tolerance_) {
    tolerance = tolerance_;
  }

  public int getEdgeSelectionTolerance() {
    return tolerance;
  }

  public GraphPanelEditor getEditor() {
    return editor;
  }

  public SetSelectionModel getSelectionModel() {
    return selectionModel;
  }
  
  public void setNodeComponentFactory(AbstractNodeComponentFactory nodeFactory_) {
    nodeFactory = nodeFactory_;
  }

  public void setPortFactory(AbstractPortFactory portFactory_) {
    portFactory = portFactory_;
  }
  
  public void setPropertiesFactory(AbstractPropertiesFactory propertiesFactory_) {
    propertiesFactory = propertiesFactory_;
  }

  public void setGraphEdgeFactory(AbstractGraphEdgeFactory edgeFactory_) {
    edgeFactory = edgeFactory_;
  }
  
  private GraphNode showPortsNodeCache;
  private GraphPort showPortsPrefferedPortCache;
  public void setShowPorts(GraphNode node, GraphPort preferredPort_) {
    if(showPortsNodeCache == node && showPortsPrefferedPortCache == preferredPort_)
      return;
    showPortsNodeCache          = node;
    showPortsPrefferedPortCache = preferredPort_;
    if(showPorts != null)
      repaint(((Component)showPorts.get(COMPONENT)).getBounds(tmpRect));
    showPorts     = node;
    preferredPort = preferredPort_;
    if(showPorts != null)
      repaint(((Component)showPorts.get(COMPONENT)).getBounds(tmpRect));
  }
  
  public GraphModel getModel() {
    return graph;
  }
  
  public void setModel(GraphModel graph_) {
    settingModel++;
    setShowPorts(null, null);
    selectionModel.clearSelection();
    if(graph != null) 
      graph.removeGraphModelListener(this);
    graph = graph_;
    graph.addGraphModelListener(this);
    
    COMPONENT       = graph.nodeAttribute(COMPONENT_TAG,       nodeFactory);
    NODE_PROPERTIES = graph.nodeAttribute("properties",        propertiesFactory);
    GRAPH_NODE_PORT = graph.portAttribute(GRAPH_NODE_PORT_TAG, portFactory);
    GRAPH_EDGE      = graph.edgeAttribute(EDGE_TAG,            edgeFactory);
    
    Component[]    cs = getComponents();
    for(int i = 0; i < cs.length; i++)
      if(cs[i] instanceof JComponent)
	toolTipManager.unregisterComponent((JComponent)cs[i]);
    removeAll();
    
    add(editor);
    
    GraphNode[] nodes = graph.nodesArray();
    for(int i = 0; i < nodes.length; i++)
      addNode(nodes[i]);
    
    edgeca = null;
    
    settingModel--;
    validate();
    repaint(0, 0, getWidth(), getHeight());
  }
  
  public Object viewToModel(Point p) {
    return viewToModel(p.x, p.y);
  }
  
  public Object viewToModel(int x, int y) {
    Object result = locationToObject(this, x, y);
    if(result instanceof GraphEdge)
      return viewToModel((GraphEdge)result);
    else
      return viewToModel((Component)result);
  }
  
  public GraphNode viewToModel(Component c) {
    return (GraphNode)cmpToModelMap.get(c);
  }

  public iiuf.util.graph.GraphEdge viewToModel(GraphEdge e) {
    return (iiuf.util.graph.GraphEdge)edgeToModelMap.get(e);
  }
  
  public void nodesAdded(GraphModel model, GraphNode[] nodes) {
    layoutBlock++;
    for(int i = 0; i < nodes.length; i++)
      addNode(nodes[i]);
    layoutBlock--;
    doLayout();
    repaint();
  }
  
  public void nodesRemoved(GraphModel model, GraphNode[] nodes) {
    layoutBlock++;
    for(int i = 0; i < nodes.length; i++)
      removeNode(nodes[i]);
    layoutBlock--;
    setShowPorts(null, null);
    doLayout();
    repaint();
  }
  
  public void edgesAdded(GraphModel model, iiuf.util.graph.GraphEdge[] edges) {
    layoutBlock++;
    edgeca = null;
    for(int i = 0; i < edges.length; i++) {
      GraphNode node = edges[i].getToNode();
      if(node instanceof ConnectingNode &&
	 editor.connectingNode != node) {
	Rectangle bounds = ((Component)edges[i].getFromNode().get(COMPONENT)).getBounds(tmpRect);
	((Component)node.get(COMPONENT)).setLocation(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
	break;
      }
    }
    layoutBlock--;
    doLayout();
    repaint();
  }

  public void edgesRemoved(GraphModel model, iiuf.util.graph.GraphEdge[] edges) {
    layoutBlock++;
    SetSelectionModel sm = getSelectionModel();
    for(int i = 0; i < edges.length; i++)
      sm.remove(edges[i]);
    edgeca = null;
    layoutBlock--;
    setShowPorts(null, null);
    doLayout();
    repaint();
  }
  
  public void setEditable(boolean state) {
    if(edit != state) {
      edit = state;
      if(edit) {
	editor.setSize(getSize());
	editor.start();
	if(ctxmgr != null)
	  ctxmgr.setComponent(editor);
      }
      else {
	selectionModel.clearSelection();
	editor.stop();
	editor.setSize(0, 0);
	if(ctxmgr != null)
	  ctxmgr.setComponent(this);
      }
      repaint();
      firePropertyChange(IS_EDITABLE, !state, state);      
    }
  }
  
  public boolean isEditable() {
    return edit;
  }
  
  public void setLayouter(NodeLayouter layouter) {
    layout.setLayouter(layouter);
    doLayout();
    repaint();
  }
  
  public NodeLayouter getLayouter() {
    return layout.getLayouter();
  }
  
  public void setRouter(GraphRouter router_) {
    router = router_;
    router.init();
    repaint();
  }

  public void setPaintEdgesAfterNodes(boolean state) {
    if(state != edgeAfter) {
      edgeAfter = state;
      repaint();
    }
  }
  
  private void addNode(GraphNode node) {
    Component cmp = (Component)node.get(COMPONENT);
    if(cmp instanceof GraphNodeComponent)
      ((GraphNodeComponent)cmp).addComponent(this, 1);
    else
      add(cmp, 1);
    cmpToModelMap.put(node.get(COMPONENT), node);
    nodeca = null;
  }
  
  private void removeNode(GraphNode node) {
    Component c = (Component)node.get(COMPONENT);
    
    if(c instanceof JComponent)
      toolTipManager.unregisterComponent((JComponent)c);
    if(c instanceof GraphNodeComponent)
      ((GraphNodeComponent)c).dispose();
    remove(c);
    cmpToModelMap.remove(node.get(COMPONENT));
    getSelectionModel().remove(node);
    nodeca = null;
  }
  
  public GraphNode findNodeAt(int x, int y) {
    return (GraphNode)cmpToModelMap.get(locationToComponent(x, y));
  }
  
  public GraphPort findPortAt(int x, int y) {
    GraphNode n = findNodeAt(x, y);
    return n == null ? null : findPortAt(n, x, y);
  }
  
  public GraphPort findPortAt(GraphNode node, int mx, int my) {
    GraphPort[] ports = node.getPorts();

    Component cmp = (Component)node.get(COMPONENT);
    
    int    mind = Integer.MAX_VALUE;
    int    min  = 0;
    int    cw   = cmp.getWidth();
    int    ch   = cmp.getHeight();
    int    cx   = cmp.getX();
    int    cy   = cmp.getY();
    for(int i = 0; i < ports.length; i++) {
      GraphNodePort port = (GraphNodePort)ports[i].get(GRAPH_NODE_PORT);
      if(port == null) return null;
      int x = mx - (int)(port.x * cw + cx);
      int y = my - (int)(port.y * ch + cy);
      int d = x * x + y * y;
      if(d < mind) {
	min  = i;
	mind = d;
      }
    }
    
    return ports[min];
  }
 
  GraphEdge[] edgeca;
  private GraphEdge[] getEdges() {
    if(graph == null) return new GraphEdge[0];
    if(edgeca == null) {
      edgeToModelMap = new HashMap();
      iiuf.util.graph.GraphEdge[] tmp = graph.edgesArray();
      edgeca = new GraphEdge[tmp.length];
      for(int i = 0; i < tmp.length; i++) {
	edgeca[i] = (GraphEdge)tmp[i].get(GRAPH_EDGE);
	edgeToModelMap.put(edgeca[i], tmp[i]);
      }
    }
    return edgeca;
  }

  Component[] nodeca;
  private Component[] getNodes() {
    if(graph == null) return new Component[0];
    if(nodeca == null) {
      GraphNode[] tmp = graph.nodesArray();
      nodeca = new Component[tmp.length];
      for(int i = 0; i < tmp.length; i++)
      nodeca[i] = (Component)tmp[i].get(COMPONENT);
    }
    return nodeca;
  }
  
  public Object locationToObject(Component component, Point location) {
    return locationToObject(component, location.x, location.y);
  }
  
  private Component locationToComponent(int x, int y) {
    synchronized (getTreeLock()) {
      Component[] components  = getComponents();
      
      for(int i = 0; i < components.length; i++) {
	Component comp = components[i];
	if(comp != null && comp != editor)
	  if (comp.contains(x - comp.getX(), y - comp.getY()))
	    return comp;
      }
    }
    return null;
  }
  
  public Object locationToObject(Component component, int x, int y) {
    if(!contains(x, y)) return null;
    
    Component cmp = locationToComponent(x, y);
    if(cmp != null) return cmp;
    
    GraphEdge[] edges = getEdges();
    for(int i = 0; i < edges.length; i++)
      if(edges[i].pointIsNear(x, y, tolerance))
	return edges[i];

    return this;
  }  
  
  public Component getComponent() {
    return this;
  }

 private ContextMenuManager ctxmgr;
  public void setContextMenuManager(ContextMenuManager manager) {
    ctxmgr = manager;
  }
  
  public ContextMenuManager getContextMenuManager() {
    return ctxmgr;
  }
  
  public ContextMenuManager addContextMenu() {
    if(ctxmgr == null)
      ctxmgr = new ContextMenuManager(this);
    return ctxmgr;
  }
  
  public Rectangle getDotMarkRectangle() {
    return selection;
  }
  
  public boolean isDragging(GraphNode node) {
    return editor.dragable == node || node instanceof ConnectingNode;
  }
  
  public void setDot(int x, int y) {
    dot.setLocation(x, y);
    setSelection(x, y);
  }
  
  public void setMark(int x, int y) {
    mark.setLocation(x, y);
    setSelection(x, y);
  }
  
  private void setSelection(int x, int y) {
    tmpRect = (Rectangle)selection.clone();
    int w = selection.width;
    int h = selection.height;
    selection.setBounds(x, y, 0, 0);
    selection.add(dot);
    if(selection.width != w || selection.height != h) {
      tmpRect.add(selection);
      tmpRect.width++;
      tmpRect.height++;
      repaint(tmpRect);
    }
  }
  
  public void repaint(int x, int y, int w, int h) {
    super.repaint(x - SELECTION_MARK_CENTER, y - SELECTION_MARK_CENTER, w + SELECTION_MARK_SIZE, h + SELECTION_MARK_SIZE);
  }
  
  public void paintChildren(Graphics g) {
    if(settingModel != 0) {
      repaint(100);
      return;
    }
    oldSelectionBoundingBox = selectionBoundingBox;
    if(!edgeAfter)
      paintEdges(g);
    super.paintChildren(g);
    if(edgeAfter)
      paintEdges(g);
    g.setColor(selectionColor);
    Graphics2D g2 = (Graphics2D)g;
    if(!selectionModel.isEmpty()) {
      selectionBoundingBox = null;
      Object[]  sel = selectionModel.getSelection();
      for(int i = 0; i < sel.length; i++) {
	if(!(sel[i] instanceof GraphNode)) continue;
	Rectangle bounds  = ((Component)((GraphNode)sel[i]).get(COMPONENT)).getBounds(tmpRect);
	g2.draw(bounds);
	if(selectionBoundingBox == null)
	  selectionBoundingBox = (Rectangle)bounds.clone();
	else
	  selectionBoundingBox.add(bounds);
      }
      if(selectionBoundingBox != null) {
	g2.draw(selectionBoundingBox);
	
	if(getLayouter().allowsNodeLocationChange()) {
	  int x  = selectionBoundingBox.x;
	  int y  = selectionBoundingBox.y;
	  int w  = selectionBoundingBox.width;
	  int wh = w / 2;
	  int h  = selectionBoundingBox.height;
	  int hh = h / 2;
	  
	  drawHandle(g, x,      y);
	  drawHandle(g, x + wh, y);
	  drawHandle(g, x + w,  y); 
	  
	  drawHandle(g, x,      y + hh);
	  drawHandle(g, x + w,  y + hh); 
	  
	  drawHandle(g, x,      y + h);
	  drawHandle(g, x + wh, y + h);
	  drawHandle(g, x + w,  y + h);
	}
      }
    } else
      selectionBoundingBox = null;
    if(selection.width != 0 && selection.height != 0)
      g2.draw(selection);
    if(showPorts != null) {
      GraphPort[]     ports = showPorts.getPorts();
      Component       cmp   = (Component)showPorts.get(COMPONENT);
      g.setColor(Color.red);
      for(int i = 0; i < ports.length; i++) {
	GraphNodePort port = (GraphNodePort)ports[i].get(GRAPH_NODE_PORT);
	if(port != null)
	  port.paint(cmp, g, ports[i] == preferredPort);
      }
    }
    
    
    if((oldSelectionBoundingBox == null && selectionBoundingBox != null) ||
       (oldSelectionBoundingBox != null && !oldSelectionBoundingBox.equals(selectionBoundingBox)))
      firePropertyChange(SELECTION_BOUNDS_CHANGED, oldSelectionBoundingBox, selectionBoundingBox);
  }
  
  private void paintEdges(Graphics g) {
    Graphics2D g2        = (Graphics2D)g;
    Stroke      svStroke = g2.getStroke();
    GraphEdge[] edges    = getEdges();
    router.setupEdges(this, edges, getNodes());
    for(int i = 0; i < edges.length; i++) {
      GraphEdge edge    = edges[i];
      Color     svColor = edge.color;
      edge.color = selectionModel.isSelected(edgeToModelMap.get(edges[i])) ? selectionColor : edges[i].color;      
      edge.paint(g);
      edge.color = svColor;
    }
    ((Graphics2D)g).setStroke(svStroke);
    for(int i = 0; i < edges.length; i++)
      edges[i].paintMarkers(g);
  }
  
  private transient Component targetLastEntered;
  private void trackMouseEnterExit(Component targetOver, MouseEvent e) {
    if(targetLastEntered == targetOver) return;
    
    if(targetLastEntered == null)
      retargetMouseEvent(targetOver, MouseEvent.MOUSE_ENTERED, e.getModifiers(), e);
    
    if(targetLastEntered != null) {
      retargetMouseEvent(targetLastEntered, MouseEvent.MOUSE_EXITED, e.getModifiers(), e);
      retargetMouseEvent(targetOver, MouseEvent.MOUSE_ENTERED, e.getModifiers(), e);
    }
    
    targetLastEntered = targetOver;
  }
  
  static void retargetMouseEvent(Component target, int id, int modifiers, MouseEvent e) {
    if(target == null) return;
    target.dispatchEvent(new MouseEvent(target,
					id, 
					e.getWhen(), 
					modifiers,
					e.getX() - target.getX(), 
					e.getY() - target.getY(), 
					e.getClickCount(), 
					e.isPopupTrigger()));
  }
  
  void handleMouseMoved(MouseEvent e) {
    int         x          = e.getX();
    int         y          = e.getY();
    Component[] c          = getComponents();
    Component   targetOver = null;
    for(int i = 0; i < c.length; i++) {
      if(c[i] == editor) continue;
      if(c[i].getBounds(tmpRect).contains(x, y)) {
      	targetOver = c[i];
        break;
      }
    }
    trackMouseEnterExit(targetOver, e);
    retargetMouseEvent(targetOver, e.getID(), e.getModifiers(), e);
  }
  
  public Rectangle getSelectionBounds() {
    return selectionBoundingBox;
  }

  int pointToSelectionArea(int px, int py) {
    if(selectionBoundingBox == null) return -1;
    int x  = selectionBoundingBox.x;
    int y  = selectionBoundingBox.y;
    int w  = selectionBoundingBox.width;
    int wh = w / 2;
    int h  = selectionBoundingBox.height;
    int hh = h / 2;
    
    if(px >= x - SELECTION_MARK_CENTER && px <= x + SELECTION_MARK_CENTER) {
      if(py >= y - SELECTION_MARK_CENTER && py <= y + SELECTION_MARK_CENTER)
	return SwingConstants.NORTH_WEST;
      if(py >= y - SELECTION_MARK_CENTER + hh && py <= y + SELECTION_MARK_CENTER + hh)
	return SwingConstants.WEST;
      if(py >= y - SELECTION_MARK_CENTER + h && py <= y + SELECTION_MARK_CENTER + h)
	return SwingConstants.SOUTH_WEST;
    }
    
    if(px >= x - SELECTION_MARK_CENTER + wh && px <= x + SELECTION_MARK_CENTER + wh) {
      if(py >= y - SELECTION_MARK_CENTER && py <= y + SELECTION_MARK_CENTER)
	return SwingConstants.NORTH;
      if(py >= y - SELECTION_MARK_CENTER + h && py <= y + SELECTION_MARK_CENTER + h)
	return SwingConstants.SOUTH;
    }
    
    if(px >= x - SELECTION_MARK_CENTER + w && px <= x + SELECTION_MARK_CENTER + w) {
      if(py >= y - SELECTION_MARK_CENTER && py <= y + SELECTION_MARK_CENTER)
	return SwingConstants.NORTH_EAST;
      if(py >= y - SELECTION_MARK_CENTER + hh && py <= y + SELECTION_MARK_CENTER + hh)
	return SwingConstants.EAST;
      if(py >= y - SELECTION_MARK_CENTER + h && py <= y + SELECTION_MARK_CENTER + h)
	return SwingConstants.SOUTH_EAST;
    }
    
    return selectionBoundingBox.contains(px, py) ? SwingConstants.CENTER : -1;
  }
  
  private void drawHandle(Graphics g, int x, int y) {
    Color svColor = g.getColor();
    g.setColor(Color.white);
    g.fillRect(x - SELECTION_MARK_CENTER, 
	       y - SELECTION_MARK_CENTER, 
	       SELECTION_MARK_SIZE, SELECTION_MARK_SIZE);
    g.setColor(svColor);
    g.drawRect(x - SELECTION_MARK_CENTER, 
	       y - SELECTION_MARK_CENTER, 
	       SELECTION_MARK_SIZE, SELECTION_MARK_SIZE);
  }

  //--- empty dnd implementation ---
  
  public void dragEnter(DropTargetDragEvent e)         {}
  public void dragExit(DropTargetEvent e)              {}
  public void dragOver(DropTargetDragEvent e)          {}
  public void dropActionChanged(DropTargetDragEvent e) {}
  public void drop(DropTargetDropEvent e)              {}

  //----------------------- test stuff -------------

  static int nodecnt = 0;
  static int MAXNODES = 3;
  static int MAXEDGES = 1;
  static int NAME;
  static GraphNode[] gnodes;
  
  static String[]       NLNAMES     = {"Tree",
				       "Force Directed",
				       "Distance Ordered",
				       "Free Move"};
  
  static String[]       ROUTERNAMES = {"Straight Line", "Orthogonal"};
  
  static String[]       GNAMES      = {"Random",
				       "Circle",
				       "FS Tree",
				       "Example 1",
				       "Example 2",
				       "Example 3",
				       "Example 4"};
  
  static String[]       GS          = {"",
				       "",
				       "",
				       "joe-food,joe-dog,joe-tea,joe-cat,joe-table,table-plate/50,plate-food/30,food-mouse/100,food-dog/100,mouse-cat/150,table-cup/30,cup-tea/30,dog-cat/80,cup-spoon/50,plate-fork,dog-flea1,dog-flea2,flea1-flea2/20,plate-knife",
				       "zero-one,one-two,two-three,three-four,four-five,five-six,six-seven,seven-zero",
				       "zero-one,zero-two,zero-three,zero-four,zero-five,zero-six,zero-seven,zero-eight,zero-nine,one-ten,two-twenty,three-thirty,four-fourty,five-fifty,six-sixty,seven-seventy,eight-eighty,nine-ninety,ten-twenty/80,twenty-thirty/80,thirty-fourty/80,fourty-fifty/80,fifty-sixty/80,sixty-seventy/80,seventy-eighty/80,eighty-ninety/80,ninety-ten/80,one-two/30,two-three/30,three-four/30,four-five/30,five-six/30,six-seven/30,seven-eight/30,eight-nine/30,nine-one/30",
				       "a1-a2,a2-a3,a3-a4,a4-a5,a5-a6,b1-b2,b2-b3,b3-b4,b4-b5,b5-b6,c1-c2,c2-c3,c3-c4,c4-c5,c5-c6,x-a1,x-b1,x-c1,x-a6,x-b6,x-c6"};
  
  private static ArrayList views = new ArrayList();
  
  private static GraphNode findNode(GraphModel g, String lbl) {
    Object nodes[] = g.nodes().toArray();
    
    for (int i = 0 ; i < nodes.length; i++)
      if(((DefaultGraphNode)nodes[i]).get(NAME).equals(lbl))
	return (DefaultGraphNode)nodes[i];
    
    DefaultGraphNode result = new DefaultGraphNode();
    result.set(NAME, lbl);
    g.add(result);
    return result;
  }
  
  private static GraphNode buildTree(GraphModel g, File f) {
    DefaultGraphNode node = new DefaultGraphNode();
    node.set(NAME, f.getName());
    g.add(node);
    if(f.isDirectory()) {
      File[] fs = f.listFiles();
      for(int i = 0; i < fs.length; i++) {
	GraphNode n = buildTree(g, fs[i]);
	iiuf.util.graph.DefaultGraphEdge e = new iiuf.util.graph.DefaultGraphEdge(node, n);
	e.setWeight(100);
	g.add(e);
      }
    }
    return node;
  }
  
  private static GraphNode startnode;

  private static GraphModel filetree(File root) {
    GraphModel result = new DefaultGraphModel();
    
    NAME = result.nodeAttribute("name", new AttributeFactory() {
	public Object newAttribute(Attributable attributable, Object[] args) {
	  return "";
	}
      });
    
    startnode = buildTree(result, root);
    
    GraphView[] va = getViews();
    
    for(int j = 0; j < va.length; j++)
      for(int i = 0; i < va[j].NLS.length; i++)
	if(va[j].NLS[i] instanceof TreeNL)
	  ((TreeNL)va[j].NLS[i]).setStart(startnode);
    
    return result;
  }

  private static GraphModel parse(String edges) {
    DefaultGraphModel result = new DefaultGraphModel();
    
    NAME = result.nodeAttribute("name", new AttributeFactory() {
	public Object newAttribute(Attributable attributable, Object[] args) {
	  return new String("" + nodecnt++);
	}
      });
    
    for(StringTokenizer t = new StringTokenizer(edges, ",") ; t.hasMoreTokens() ; ) {
      String str = t.nextToken();
      int i = str.indexOf('-');
      if (i > 0) {
	int len = 50;
	int j = str.indexOf('/');
	if (j > 0) {
	  len = Integer.valueOf(str.substring(j+1)).intValue();
	  str = str.substring(0, j);
	}
	
	iiuf.util.graph.DefaultGraphEdge edge = 
	  new iiuf.util.graph.DefaultGraphEdge(findNode(result, str.substring(0,i)), findNode(result, str.substring(i+1)));
	edge.setWeight(len);
	result.add(edge);
      }
    }
    
    return result;
  }

  private static GraphModel circle() {
    gnodes = new DefaultGraphNode[MAXNODES];
    
    DefaultGraphModel g = new DefaultGraphModel();
    
    NAME = g.nodeAttribute("name", new AttributeFactory() {
	public Object newAttribute(Attributable attributable, Object[] args) {
	  return new String("" + nodecnt++);
	}
      });
    
    for(int i = 0; i < MAXNODES; i++) {
      gnodes[i] = new DefaultGraphNode();
      g.add(gnodes[i]);
    }
    
    iiuf.util.graph.DefaultGraphEdge edge;
    
    for(int i = 0; i < MAXNODES - 1; i++) {
      edge = new iiuf.util.graph.DefaultGraphEdge(gnodes[i], gnodes[i + 1]);
      edge.setWeight(50);
      g.add(edge);
    }
    edge = new iiuf.util.graph.DefaultGraphEdge(gnodes[MAXNODES - 1], gnodes[0]);    
    edge.setWeight(50);
    g.add(edge);
    
    return g;
  }
  
  private static GraphModel randomize() {
    gnodes = new DefaultGraphNode[MAXNODES];

    DefaultGraphModel g = new DefaultGraphModel();

    NAME = g.nodeAttribute("name", new AttributeFactory() {
	public Object newAttribute(Attributable attributable, Object[] args) {
	  return new String("" + nodecnt++);
	}
      });
    
    for(int i = 0; i < MAXNODES; i++) {
      gnodes[i] = new DefaultGraphNode();
      g.add(gnodes[i]);
    }
    
    for(int i = 0; i < MAXEDGES; i++) {
      iiuf.util.graph.DefaultGraphEdge edge = 
	new iiuf.util.graph.DefaultGraphEdge(gnodes[Util.intRandom(MAXNODES)], gnodes[Util.intRandom(MAXNODES)]);
      edge.setWeight(50);
      g.add(edge);
    }
   
    return g;
  }
  
  static class GraphView
    extends 
    JFrame
  {
    JTextField  nodestf = new JTextField("" + MAXNODES);
    JTextField  edgestf = new JTextField("" + MAXEDGES);
    
    NodeLayouter[] NLS         = {new TreeNL(),
				  new ForceDirectedNL(),
				  new DistanceOrderedNL(),
				  new DefaultNL()};

    GraphRouter[]  ROUTERS     = {new StraightLineRouter(), new OrthogonalRouter()};
    
    EdgeMarker[][]   CAPTIONS = {{new EquilateralTriangleMarker(10, Math.PI / 6.0, true,  false)},
				 {new EquilateralTriangleMarker(10, Math.PI / 6.0, false, false)},
				 {new EquilateralTriangleMarker(10, Math.PI / 6.0, false, false, Color.blue, Color.green)},
				 {new EquilateralTriangleMarker(10, Math.PI / 6.0, false, false),
				  new EquilateralTriangleMarker(10, Math.PI / 6.0, false, true)},
				 {new EquilateralTriangleMarker(12, Math.PI / 4.0, false, false, Color.black, Color.white)},
				 {new LabelMarker("Hello World")},
				 {new LabelMarker("Hello World", HexagonalBorder.newBlackBorder())}};
    
    double[][]      CAPTIONSPOS = {{1.0},
				   {1.0},
				   {1.0},
				   {1.0, 0.0},
				   {0.5},
				   {0.5},
				   {0.5}};
    
    GraphPanel gp;
    
    GraphView(GraphModel model) {
      super("GraphView");
      
      gp = 
	new GraphPanel(
		       new AbstractNodeComponentFactory(gp) {
			   protected Component newNodeComponent(GraphNode node, Object[] args) {
			     return new JButton((String)node.get(NAME));
			   }
			 },
		       new AbstractPortFactory() {
			   protected GraphNodePort newPort(GraphPort port, Object[] args) {
			     return new GraphNodePort(0.5, 0.5, 0);
			   }
			 },
		       new AbstractGraphEdgeFactory() {
			   protected GraphEdge newGraphEdge(iiuf.util.graph.GraphEdge edge, 
							    GraphNode fromNode, GraphPort fromPort, 
							    GraphNode toNode,   GraphPort toPort,
							    Object[] args) {
			     return new GraphEdge((Component)fromNode.get(gp.COMPONENT), 
						  (GraphNodePort)fromPort.get(gp.GRAPH_NODE_PORT),
						  (Component)toNode.get(gp.COMPONENT),  
						  (GraphNodePort)toPort.get(gp.GRAPH_NODE_PORT));
			   }
			 },
		       NLS[0],
		       ROUTERS[0]);
      
      gp.setModel(model);
      gp.addContextMenu();
      
      JPanel params   = new JPanel();
      JPanel controls = new JPanel();
      JFrame frame = new JFrame("Graph Test");
      frame.setSize(800, 500);
      frame.setLocation(Awt.centerOnScreen(frame.getSize()));
      frame.getContentPane().setLayout(new BorderLayout());
      frame.getContentPane().add(new JScrollPane(gp), BorderLayout.CENTER);
      
      params.setLayout(new GridBagLayout());
      params.add(new JLabel("Nodes:"), Awt.constraints(false));
      nodestf = new JTextField("" + MAXNODES, 5);
      params.add(nodestf, Awt.constraints(false));    
      
      JComboBox nodenl = new JComboBox(NLNAMES);
      nodenl.addItemListener(new ItemListener() {
	  public void itemStateChanged(ItemEvent e) {
	    for(int i = 0; i < NLNAMES.length; i++)
	      if(NLNAMES[i].equals(e.getItem())) {
		if(startnode != null && NLS[i] instanceof TreeNL)
		  ((TreeNL)NLS[i]).setStart(startnode);
		gp.setLayouter(NLS[i]);
		break;
	      }
	  }
	});
      params.add(nodenl, Awt.constraints(true));
      
      params.add(new JLabel("Edges:"), Awt.constraints(false));
      edgestf = new JTextField("" + MAXEDGES, 5);
      params.add(edgestf, Awt.constraints(false));
      
      JComboBox edgert = new JComboBox(ROUTERNAMES);
      edgert.addItemListener(new ItemListener() {
	  public void itemStateChanged(ItemEvent e) {
	    if(e.getStateChange() != e.SELECTED) return;
	    for(int i = 0; i < ROUTERNAMES.length; i++)
	      if(ROUTERNAMES[i].equals(e.getItem())) {
		gp.setRouter(ROUTERS[i]);
		break;
	      }
	  }
	});
      params.add(edgert, Awt.constraints(true));
      
      params.add(new GeometryEditor(gp));
      params.add(new StyleEditor(gp));
      
      EdgeEditor ee = new EdgeEditor(gp);
      for(int i = 0; i < CAPTIONS.length; i++)
	ee.addMarkers(CAPTIONS[i], CAPTIONSPOS[i]);
      params.add(ee);

      controls.add(Swing.newCheckBox("Edit", 
				     new ItemListener() {
					 public void itemStateChanged(ItemEvent e) {
					   gp.setEditable(((JCheckBox)e.getItemSelectable()).isSelected());
					 }
				       }));
      
      controls.add(Swing.newCheckBox("Edge Over", 
				     new ItemListener() {
					 public void itemStateChanged(ItemEvent e) {
					   gp.setPaintEdgesAfterNodes(((JCheckBox)e.getItemSelectable()).isSelected());
					 }
				       }));
      
      
      JComboBox gshape = new JComboBox(GNAMES);
      gshape.addItemListener(new ItemListener() {
	  public void itemStateChanged(ItemEvent e) {
	    if(e.getStateChange() != e.SELECTED) return;
	    if(GNAMES[0].equals(e.getItem())) {
	      MAXNODES = Integer.parseInt(nodestf.getText());
	      MAXEDGES = Integer.parseInt(edgestf.getText());
	      setModel(randomize());
	    } else if(GNAMES[1].equals(e.getItem())) {
	      MAXNODES = Integer.parseInt(nodestf.getText());
	      setModel(circle());
	    } else if(GNAMES[2].equals(e.getItem())) {
	      fc.setFileSelectionMode(fc.DIRECTORIES_ONLY);
	      if(fc.showOpenDialog((Component)e.getItemSelectable()) == fc.APPROVE_OPTION)
		setModel(filetree(fc.getSelectedFile()));
	    } else { 
	      for(int i = 2; i < GNAMES.length; i++)
		if(GNAMES[i].equals(e.getItem())) {
		  setModel(parse(GS[i]));
		  break;
		}
	    }
	  }
	});
      controls.add(gshape);    
      
      controls.add(Swing.newButton("New View",
				   new ActionListener() {
				       public void actionPerformed(ActionEvent e) {
					 views.add(new GraphView(gp.getModel()));
				       }
				     }));
      
      controls.add(Swing.newButton("Quit",
				   new ActionListener() {
				       public void actionPerformed(ActionEvent e) {
					 System.exit(0);
				       }
				     }));
      
      
      frame.getContentPane().add(params, BorderLayout.NORTH);    
      frame.getContentPane().add(controls, BorderLayout.SOUTH);    
      frame.setVisible(true);
    }
    
    private void setModel(GraphModel model) {
      GraphView[] va = getViews();
      for(int i = 0; i < va.length; i++)
	va[i].gp.setModel(model);
    }
  }
   
  private static GraphView[] getViews() {
    return (GraphView[])views.toArray(new GraphView[views.size()]);
  }

  static JFileChooser fc = new JFileChooser();
  public static void main(String[] argv) {
    views.add(new GraphView(randomize()));    
  }
}

class GraphLayout 
  implements
  LayoutManager2
{
  private Dimension    dimension = new Dimension();
  private NodeLayouter layouter;
  
  GraphLayout(NodeLayouter layouter) {
    setLayouter(layouter);
  }
  
  void setLayouter(NodeLayouter layouter_) {
    if(layouter != null) layouter.deactivate();
    layouter = layouter_;
    layouter.activate();
  }
  
  NodeLayouter getLayouter() {
    return layouter;
  }
  
  public void addLayoutComponent(Component comp, Object constraints) {
    // System.out.println("addLayoutComponent");
  }
  
  public float getLayoutAlignmentX(Container target) {
    // System.out.println("getLayoutAlignmentX");
    return 0;
  }
  
  public float getLayoutAlignmentY(Container target) {
    // System.out.println("getLayoutAlignmentY");
    return 0;
  }
  
  public void invalidateLayout(Container target) {
    // System.out.println("invalidateLayout");
  }
  
  public Dimension maximumLayoutSize(Container target) {
    return dimension;
  }
  
  public void addLayoutComponent(String name, Component comp) {
    layoutContainer(comp.getParent());
  }
  
  public void layoutContainer(Container parent_) {
    GraphPanel parent = (GraphPanel)parent_;
    if(parent.layoutBlock != 0) return;
    Dimension oldDim  = parent.getSize();
    dimension = layouter.layout(parent, parent.getModel());
    dimension = new Dimension(Math.max(dimension.width, parent.getWidth()), Math.max(dimension.height, parent.getHeight()));      
    if(parent.edit)
      parent.editor.setSize(dimension);
    if(oldDim.width  != dimension.width || oldDim.height != dimension.height) 
      parent.revalidate();
  }
  
  public Dimension minimumLayoutSize(Container parent) {
    return dimension;
  }

  public Dimension preferredLayoutSize(Container parent) {
    return dimension;
  }
  
  public void removeLayoutComponent(Component comp) {
    layoutContainer(comp.getParent());
  }
}

class SelectionModel 
  implements
  SetSelectionModel 
{
  private HashSet           selection  = new HashSet();
  private EventListenerList listeners  = new EventListenerList();
  private ChangeEvent       EVENT;
  
  SelectionModel() {
    EVENT = new ChangeEvent(this);
  }
  
  public void add(Object node) {
    selection.add(node);
    fireChangeEvent();
  }

  public void addAll(Collection nodes) {
    selection.addAll(nodes);
    fireChangeEvent();
  }

  public void remove(Object node) {
    selection.remove(node);
    fireChangeEvent();
  }

  public void removeAll(Collection nodes) {
    selection.removeAll(nodes);
    fireChangeEvent();
  }

  public void clearSelection() {
    selection = new HashSet();
    fireChangeEvent();
  }

  public boolean isEmpty() {
    return selection.isEmpty();
  }
  
  public Object[] getSelection() {
    return selection.toArray();
  }
  
  public Object[] getSelection(Class cls) {
    HashSet  subsel = new HashSet();
    Object[] sel    = selection.toArray();
    for(int i = 0; i < sel.length; i++)
    if(cls.isAssignableFrom(sel[i].getClass()))
    subsel.add(sel[i]);
    
    Object[] result = (Object[])Array.newInstance(cls, subsel.size());
    int j = 0;
    for(Iterator i = subsel.iterator(); i.hasNext(); j++)
    result[j] = i.next();
    return result;
  }
  
  public boolean isSelected(Object o) {
    return selection.contains(o);
  }
  
  private synchronized void fireChangeEvent() {
    ChangeListener[] l = (ChangeListener[])listeners.getListeners(ChangeListener.class);
    for(int i = 0; i < l.length; i++)
      l[i].stateChanged(EVENT);
  }
  
  public synchronized void addChangeListener(ChangeListener listener) {
    listeners.add(ChangeListener.class, listener);
  }

  public synchronized void addChangeListener(ChangeListener listener, boolean weak) {
    listeners.add(ChangeListener.class, listener, weak);
  }
  
  public synchronized void removeChangeListener(ChangeListener listener) {
    listeners.remove(ChangeListener.class, listener);
  }
  
  public int size() {
    return selection.size();
  }
  
  public int size(Class cls) {
    return getSelection(cls).length;
  }
}

/*
  $Log: GraphPanel.java,v $
  Revision 1.1  2002/07/11 12:09:52  ohitz
  Initial checkin

  Revision 1.15  2001/04/30 07:33:17  schubige
  added webcom to cvstree

  Revision 1.14  2001/03/21 19:34:06  schubige
  started with dom stuff

  Revision 1.13  2001/03/20 14:28:30  schubige
  enhanced sample soundlet, added format popup

  Revision 1.12  2001/03/19 16:13:26  schubige
  soundium without drag cursor

  Revision 1.11  2001/03/16 18:08:20  schubige
  improved orthogonal router

  Revision 1.10  2001/03/13 13:41:05  schubige
  Fixed some graph panel and soundium bugs

  Revision 1.9  2001/03/12 17:52:00  schubige
  Added version support to sourcewatch and enhanced soundium

  Revision 1.8  2001/03/11 17:59:38  schubige
  Fixed various soundium and iiuf.swing.graph bugs

  Revision 1.7  2001/03/09 21:24:58  schubige
  Added preferences to edge editor

  Revision 1.6  2001/03/09 15:30:51  schubige
  Added markers to graph panel

  Revision 1.5  2001/03/07 17:36:28  schubige
  soundium properties panel beta

  Revision 1.4  2001/03/05 17:55:07  schubige
  Still working on soundium properties panel

  Revision 1.3  2001/02/26 15:57:22  schubige
  Again changes in SoundEngine.x, added some todos to graph panel & co

  Revision 1.2  2001/02/23 17:23:11  schubige
  Added loop source to soundium and fxed some bugs along

  Revision 1.1  2001/02/17 09:54:21  schubige
  moved graph stuff to iiuf.swing.graph, started work on rotatable GraphNodeComponents

  Revision 1.18  2001/02/16 13:47:38  schubige
  Adapted soundium to new rtsp version

  Revision 1.17  2001/02/15 16:00:43  schubige
  Improved graph panel, fixed some soundium bugs

  Revision 1.16  2001/02/14 17:25:37  schubige
  implemented resizing, select all and key-shortcuts for graph panel

  Revision 1.15  2001/02/13 14:49:06  schubige
  started work on gui - engine connection

  Revision 1.14  2001/02/12 17:50:05  schubige
  still working on soundium gui

  Revision 1.13  2001/02/11 16:25:39  schubige
  working on soundium

  Revision 1.12  2001/01/12 08:26:20  schubige
  TJGUI update and some TreeView bug fixes

  Revision 1.11  2001/01/04 16:28:38  schubige
  Header update for 2001 and DIUF

  Revision 1.10  2001/01/04 12:12:36  schubige
  fixed bugs reported by iiuf.dev.java.Verify

  Revision 1.9  2001/01/04 09:58:49  schubige
  fixed bugs reported by iiuf.dev.java.Verify

  Revision 1.8  2001/01/03 15:23:50  schubige
  graph stuff beta

  Revision 1.7  2000/12/29 08:03:55  schubige
  SourceWatch beta debug iter 1

  Revision 1.6  2000/12/28 09:29:10  schubige
  SourceWatch beta

  Revision 1.5  2000/12/20 09:46:39  schubige
  TJGUI update

  Revision 1.4  2000/12/18 12:39:09  schubige
  Added ports to iiuf.util.graph

  Revision 1.3  2000/11/10 10:46:53  schubige
  iiuf tree cleanup iter 3

  Revision 1.2  2000/10/09 06:47:57  schubige
  Updated logger stuff

  Revision 1.1  2000/08/17 16:22:14  schubige
  Swing cleanup & TreeView added

  Revision 1.2  2000/07/28 12:06:54  schubige
  Graph stuff update

  Revision 1.1  2000/07/14 13:56:20  schubige
  Added graph view stuff
  
*/