/**************************************************************************** ** COPYRIGHT (C): 1997 Cay S. Horstmann. All Rights Reserved. ** PROJECT: Practical OO Development with C++ and Java ** FILE: GraphEditor.java ** PURPOSE: a generic graph framework ** VERSION 1.0 ** PROGRAMMERS: Cay Horstmann (CSH) ** RELEASE DATE: 3-15-97 (CSH) ** UPDATE HISTORY: ****************************************************************************/ import java.awt.Graphics; import java.awt.FlowLayout; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.Component; import java.awt.Canvas; import java.awt.Panel; import java.awt.Checkbox; import java.awt.CheckboxGroup; import java.awt.Dimension; import java.awt.Event; import java.applet.*; import java.util.*; import practicaloo.*; abstract class Node implements Cloneable /* PURPOSE: An abstract class for a node in a graph */ { public abstract void plot(Graphics g); public abstract Rectangle enclosing_rect(); public abstract boolean is_inside(Point p); public abstract Point boundary_point(Point exterior); public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { return null; } } public void move(Point p) { _center = p; } public Point center() { return _center; } private Point _center; } abstract class Edge implements Cloneable /* PURPOSE: An abstract class for an edge joining two nodes */ { public abstract void plot(Graphics g, Point f, Point t); public abstract boolean is_on(Point p, Point f, Point t); public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { return null; } } public void plot(Graphics g) { Point a = _from.center(); Point b = _to.center(); Point a1 = _from.boundary_point(b); Point b1 = _to.boundary_point(a); plot(g, a1, b1); } public boolean is_on(Point p) { Point a = _from.center(); Point b = _to.center(); Point a1 = _from.boundary_point(b); Point b1 = _to.boundary_point(a); return is_on(p, a1, b1); } public boolean meets(Node n) { return _from == n || _to == n; } public void connect(Node f, Node t) { _from = f; _to = t; } private Node _from; private Node _to; } class IconCanvas extends Canvas { IconCanvas() { resize((int)(2 * (RADIUS + OFFSET)), 0); } public boolean mouseDown(Event evt, int x, int y) { int c = (y - OFFSET) / (2 * RADIUS); if(c < node_types.size()) { ((GraphEditor)getParent()).setCurrentNode((Node)node_types.elementAt(c)); repaint(); } else { c = c - node_types.size(); if (c < edge_types.size()) ((GraphEditor)getParent()).setCurrentEdge((Edge)edge_types.elementAt(c)); repaint(); } return true; } public void add_node_type(Node n) /* PURPOSE: add a node to the iconcanvas RECEIVES: n - the node to add */ { node_types.addElement(n); } public void add_edge_type(Edge n) /* PURPOSE: add an edge to the iconcanvas RECEIVES: n - the edge to add */ { edge_types.addElement(n); } public void paint(Graphics g) { Dimension d = size(); g.drawRect(0, 0, d.width - 1, d.height -1); Point center = new Point(OFFSET + RADIUS, OFFSET + RADIUS); for(int i = 0; i < node_types.size(); i++) { Node n = (Node)node_types.elementAt(i); n.move(center); n.plot(g); if(n == ((GraphEditor)getParent()).current_node()) g.drawRect(center.get_x() - RADIUS, center.get_y() - RADIUS, 2 * RADIUS, 2 * RADIUS); center.move(0, 2 * RADIUS); } for(int j = 0; j < edge_types.size(); j++) { Edge e = (Edge)edge_types.elementAt(j); Point a = (Point)center.clone(); a.move(-(RADIUS - OFFSET) , -(RADIUS - OFFSET)); Point b = (Point)center.clone(); b.move(RADIUS - OFFSET, RADIUS - OFFSET); e.plot(g, a, b); if(e == ((GraphEditor)getParent()).current_edge()) g.drawRect(center.get_x() - RADIUS, center.get_y() - RADIUS, 2 * RADIUS, 2 * RADIUS); center.move(0, 2 * RADIUS); } } private Vector node_types = new Vector(); private Vector edge_types = new Vector(); private static final int RADIUS = 30; private static final int OFFSET = 3; } class GraphCanvas extends Canvas { public boolean mouseDown(Event evt, int x, int y) { int mode = ((GraphEditor)getParent()).mode(); Point mousePos = new Point(x, y); Node n = search_node(mousePos); if (mode == INSERT) { if (n != null) //want to insert edge { _current_node = n; _current_point = _current_node.center(); _rubberband_segment = new Segment(_current_point, mousePos); } else //want to insert node { Node t = ((GraphEditor)getParent()).current_node(); Node i = (Node)t.clone(); i.move(mousePos); _nodes.addElement(i); repaint(); } } else if(mode == MOVE) { if (n != null) { _current_node = n; _rubberband_rect = _current_node.enclosing_rect(); Graphics g = getGraphics(); g.setXORMode(getBackground()); _rubberband_rect.plot(g); _current_point = mousePos; } } else if(mode == ERASE) { if (n != null) remove_node(n); else remove_edge(mousePos); repaint(); } return true; } public boolean mouseDrag(Event evt, int x , int y) { if(_rubberband_rect != null) { Graphics g = getGraphics(); g.setXORMode(getBackground()); _rubberband_rect.plot(g); int r = x - _current_point.get_x(); int t = y - _current_point.get_y(); _rubberband_rect.move(r, t); _rubberband_rect.plot(g); _current_point = new Point(x, y); g.dispose(); } else if (_rubberband_segment != null) { Graphics g = getGraphics(); g.setXORMode(getBackground()); _rubberband_segment.plot(g); _current_point = new Point(x, y); _rubberband_segment = new Segment(_rubberband_segment.get_from(), _current_point); _rubberband_segment.plot(g); g.dispose(); } return true; } public boolean mouseUp(Event evt, int x, int y) { if(_rubberband_rect != null) { Graphics g = getGraphics(); g.setXORMode(getBackground()); _rubberband_rect.plot(g); g.dispose(); _rubberband_rect = null; _current_point = new Point(x, y); _current_node.move(_current_point); _current_node = null; repaint(); } else if (_rubberband_segment != null) { Graphics g = getGraphics(); g.setXORMode(getBackground()); _rubberband_segment.plot(g); g.dispose(); _rubberband_segment = null; _current_point = new Point(x, y); Node to = search_node(_current_point); if (to == null) return true; if (to == _current_node) return true; Edge e = ((GraphEditor)getParent()).current_edge(); Edge j = (Edge)e.clone(); j.connect(_current_node, to); _edges.addElement(j); _current_node = null; repaint(); } return true; } public Node search_node(Point p) /* PURPOSE: find the node containing a point RECEIVES: p - a point RETURNS: the node containing p, or null if none */ { for(int i = 0; i < _nodes.size(); i++) { Node n = (Node)_nodes.elementAt(i); if(n.is_inside(p)) return n; } return null; } public void remove_node(Node n) { if(n == null) return; for(int k = 0; k < _edges.size(); k++) { Edge e = (Edge)_edges.elementAt(k); if(e.meets(n)) { _edges.removeElementAt(k); k--; } _nodes.removeElement(n); } } public void remove_edge(Point p) { for(int i = 0; i < _edges.size(); i++) { if(((Edge)_edges.elementAt(i)).is_on(p)) { _edges.removeElementAt(i); //erase the edge return; } } } public void paint(Graphics g) { Dimension d = size(); g.drawRect(0, 0, d.width - 1, d.height - 1); for(int i = 0; i < _edges.size(); i++) { Edge e = (Edge)_edges.elementAt(i); e.plot(g); } for(int j = 0; j < _nodes.size(); j++) { Node n = (Node)_nodes.elementAt(j); n.plot(g); } } private Vector _nodes = new Vector(); private Vector _edges = new Vector(); private int mode; private Rectangle _rubberband_rect; private Segment _rubberband_segment; private Point _current_point; private Node _current_node; private static final int MOVE = 1; private static final int ERASE = 2; private static final int INSERT = 3; } abstract class GraphEditor extends Applet { abstract String[] get_types(); private void add(Component c, GridBagLayout gbl, GridBagConstraints gbc, int x, int y, int w, int h) { gbc.gridx = x; gbc.gridy = y; gbc.gridwidth = w; gbc.gridheight = h; gbl.setConstraints(c, gbc); add(c); } public void init() { GridBagLayout gbl = new GridBagLayout(); setLayout(gbl); Panel p = new Panel(); p.setLayout(new FlowLayout()); CheckboxGroup g = new CheckboxGroup(); p.add(move = new Checkbox("Move", g, false)); p.add(erase = new Checkbox("Erase", g, false)); p.add(insert = new Checkbox("Insert", g, true)); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.VERTICAL; gbc.weighty = 100; _icon = new IconCanvas(); add(_icon, gbl, gbc, 0, 0, 1, 4); gbc.weightx = 100; gbc.weighty = 100; gbc.fill = GridBagConstraints.BOTH; _graph = new GraphCanvas(); add(_graph, gbl, gbc, 1, 0, 3, 4); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.anchor = GridBagConstraints.CENTER; gbc.weightx = 100; gbc.weighty = 0; add(p, gbl, gbc, 0, 4, 4, 1); register(get_types()); } public void register(String[] typenames) { for (int i = 0; i < typenames.length; i++) { try { Class cls = Class.forName(typenames[i]); Object obj = cls.newInstance(); if (obj instanceof Node) { Node n = (Node)obj; if (_current_node == null) _current_node = n; _icon.add_node_type(n); } else if (obj instanceof Edge) { Edge e = (Edge)obj; if (_current_edge == null) _current_edge = e; _icon.add_edge_type(e); } } catch (Exception e) { showStatus("Cannot register " + typenames[i]); } } } public boolean action(Event evt, Object arg) { if(evt.target.equals(move)) _mode = MOVE; else if(evt.target.equals(erase)) _mode = ERASE; else if(evt.target.equals(insert)) _mode = INSERT; else return super.action(evt, arg); return true; } public void setCurrentNode(Node n) { _current_node = n; } public void setCurrentEdge(Edge e) { _current_edge = e; } public Node current_node() { return _current_node; } public Edge current_edge() { return _current_edge; } public int mode() { return _mode; } private IconCanvas _icon; private GraphCanvas _graph; private int _mode = INSERT; private Node _current_node; private Edge _current_edge; private Checkbox move; private Checkbox erase; private Checkbox insert; private static final int MOVE = 1; private static final int ERASE = 2; private static final int INSERT = 3; }