/*
* Copyright (c) 2001-2006, Gaudenz Alder
* Copyright (c) 2005-2006, David Benson
*
* All rights reserved.
*
* This file is licensed under the JGraph software license, a copy of which
* will have been provided to you in the file LICENSE at the root of your
* installation directory. If you are unable to locate this file please
* contact JGraph sales for another copy.
*/
package com.jgraph.example;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.BeanInfo;
import java.beans.DefaultPersistenceDelegate;
import java.beans.Encoder;
import java.beans.ExceptionListener;
import java.beans.Expression;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PersistenceDelegate;
import java.beans.PropertyDescriptor;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.filechooser.FileFilter;
import org.jgraph.JGraph;
import org.jgraph.event.GraphSelectionEvent;
import org.jgraph.example.GraphEd;
import org.jgraph.graph.AbstractCellView;
import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.BasicMarqueeHandler;
import org.jgraph.graph.DefaultCellViewFactory;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.EdgeView;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphModel;
import org.jgraph.graph.ParentMap;
import org.jgraph.graph.PortView;
import org.jgraph.graph.VertexView;
/**
* An extension to GraphEd demonstrating more advanced JGraph features
*/
public class GraphEdX extends GraphEd {
/**
* JGraph Factory instance for random new graphs
*/
protected JGraphGraphFactory graphFactory = new JGraphGraphFactory();
/**
* References the folding manager.
*/
protected JGraphFoldingManager foldingManager;
// Actions which Change State
protected Action hide, collapse, expand, expandAll, configure;
/**
* File chooser for loading and saving graphs. Note that it is lazily
* instaniated, always call initFileChooser before use.
*/
protected JFileChooser fileChooser = null;
static {
makeCellViewFieldsTransient(PortView.class);
makeCellViewFieldsTransient(VertexView.class);
makeCellViewFieldsTransient(EdgeView.class);
// For XML Encoding of the graph instance, we need to exclude
// the marquee handler explicitely. Being an inner class of
// GraphEd, it should not be part of the written file.
try {
BeanInfo info = Introspector.getBeanInfo(MyGraph.class);
PropertyDescriptor[] propertyDescriptors = info
.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; ++i) {
PropertyDescriptor pd = propertyDescriptors[i];
if (pd.getName().equals("marqueeHandler")) {
pd.setValue("transient", Boolean.TRUE);
}
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
/**
* Constructs a new application
*/
public GraphEdX() {
// Overrides the global vertex renderer
VertexView.renderer = new JGraphGroupRenderer();
// Prepares layout actions
setJMenuBar(new GraphEdXMenuBar(this, graphFactory));
// Initializes actions states
valueChanged(null);
}
/**
* Show something interesting on start.
*/
public void init() {
graphFactory.insertTreeSampleData(getGraph(),
createCellAttributes(new Point2D.Double(0, 0)),
createEdgeAttributes());
}
// Override parent method
protected JGraph createGraph() {
// Creates a model that does not allow disconnections
GraphModel model = new MyGraphModel();
GraphLayoutCache layoutCache = new GraphLayoutCache(model,
new DefaultCellViewFactory(), true);
Set locals = new HashSet();
locals.add(GraphConstants.BOUNDS);
layoutCache.setLocalAttributes(locals);
return new MyGraph(model, layoutCache);
}
// Override parent method
protected void installListeners(JGraph graph) {
super.installListeners(graph);
// Adds redirector for group collapse/expand
foldingManager = new JGraphFoldingManager();
graph.addMouseListener(foldingManager);
}
protected void uninstallListeners(JGraph graph) {
super.uninstallListeners(graph);
graph.removeMouseListener(foldingManager);
}
/**
* Updates buttons based on application state
*/
public void valueChanged(GraphSelectionEvent e) {
super.valueChanged(e);
// Group Button only Enabled if a cell is selected
boolean enabled = !graph.isSelectionEmpty();
// hide.setEnabled(enabled);
expand.setEnabled(enabled);
expandAll.setEnabled(enabled);
collapse.setEnabled(enabled);
}
/**
* Overrides the parent example group method to set the bounds of the
* collapsed group cell appropriately
*/
public void group(Object[] children) {
// Order Cells by Model Layering
children = graph.order(children);
// If Any Cells in View
if (children != null && children.length > 0) {
double gs2 = 2 * graph.getGridSize();
Rectangle2D collapsedBounds = graph.getCellBounds(children);
collapsedBounds.setFrame(collapsedBounds.getX(), collapsedBounds
.getY(), Math.max(collapsedBounds.getWidth() / 4, gs2),
Math.max(collapsedBounds.getHeight() / 2, gs2));
graph.snap(collapsedBounds);
DefaultGraphCell group = createGroupCell(collapsedBounds);
if (group != null && children != null && children.length > 0) {
// Create the group structure
ParentMap pm = new ParentMap();
for (int i = 0; i < children.length; i++) {
pm.addEntry(children[i], group);
}
graph.getGraphLayoutCache().insert(new Object[] { group },
null, null, pm);
}
}
}
/**
* Hook from GraphEd to create a new group cell
*/
protected DefaultGraphCell createGroupCell(Rectangle2D collapsedBounds) {
DefaultGraphCell group = super.createGroupCell();
group.addPort();
GraphConstants.setInset(group.getAttributes(), 10);
GraphConstants.setBackground(group.getAttributes(), new Color(240, 240,
255));
GraphConstants.setBorderColor(group.getAttributes(), Color.black);
GraphConstants.setOpaque(group.getAttributes(), true);
GraphConstants.setBorder(group.getAttributes(), JGraphShadowBorder
.getSharedInstance());
GraphConstants.setBounds(group.getAttributes(), collapsedBounds);
return group;
}
/**
* Hook from GraphEd to set attributes of a new cell
*/
public Map createCellAttributes(Point2D point) {
Map map = super.createCellAttributes(point);
GraphConstants.setInset(map, 5);
GraphConstants.setGradientColor(map, new Color(200, 200, 255));
return map;
}
/**
* Hook from GraphEd to set attributes of a new edge
*/
public Map createEdgeAttributes() {
Map map = super.createEdgeAttributes();
// Adds a parallel edge router
GraphConstants.setLineStyle(map, GraphConstants.STYLE_SPLINE);
if (GraphConstants.DEFAULTFONT != null) {
GraphConstants.setFont(map, GraphConstants.DEFAULTFONT
.deriveFont(10f));
}
return map;
}
/**
* Hook from GraphEd to add action button to the tool bar
*/
public JToolBar createToolBar() {
JToolBar toolbar = super.createToolBar();
// Collapse
collapse = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
graph.getGraphLayoutCache().setVisible(graph.getSelectionCells(), false);
}
};
URL url = getClass().getClassLoader().getResource(
"org/jgraph/example/resources/collapse.gif");
collapse.putValue(Action.SMALL_ICON, new ImageIcon(url));
collapse.setEnabled(false);
toolbar.add(collapse);
// Expand
expand = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
graph.getGraphLayoutCache().expand(graph.getSelectionCells());
}
};
url = getClass().getClassLoader().getResource(
"org/jgraph/example/resources/expand.gif");
expand.putValue(Action.SMALL_ICON, new ImageIcon(url));
expand.setEnabled(false);
toolbar.add(expand);
// ExpandAll
expandAll = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
Object[] allCells = graph.getDescendants(graph.getRoots());
Map nested = new Hashtable();
for (int i=0; i < allCells.length; i++) {
Map attributeMap = new Hashtable();
GraphConstants.setForeground(attributeMap, Color.BLACK);
nested.put(allCells[i], attributeMap);
}
graph.getModel().edit(nested, null, null, null);
}
};
url = getClass().getClassLoader().getResource(
"org/jgraph/example/resources/expandAll.gif");
expandAll.putValue(Action.SMALL_ICON, new ImageIcon(url));
expandAll.setEnabled(false);
toolbar.add(expandAll);
return toolbar;
}
public void serializeGraph() {
int returnValue = JFileChooser.CANCEL_OPTION;
initFileChooser();
returnValue = fileChooser.showSaveDialog(graph);
if (returnValue == JFileChooser.APPROVE_OPTION) {
Container parent = graph.getParent();
BasicMarqueeHandler marquee = graph.getMarqueeHandler();
graph.setMarqueeHandler(null);
try {
// Serializes the graph by removing it from the component
// hierarchy and removing all listeners from it. The marquee
// handler, begin an inner class of GraphEd, is not marked
// serializable and will therefore not be stored. This must
// be taken into account when deserializing a graph.
uninstallListeners(graph);
parent.remove(graph);
ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(
fileChooser.getSelectedFile())));
out.writeObject(graph);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
} finally {
// Adds the component back into the component hierarchy
graph.setMarqueeHandler(marquee);
if (parent instanceof JViewport) {
JViewport viewPort = (JViewport) parent;
viewPort.setView(graph);
} else {
// Best effort...
parent.add(graph);
}
// And reinstalls the listener
installListeners(graph);
}
}
}
public void deserializeGraph() {
int returnValue = JFileChooser.CANCEL_OPTION;
initFileChooser();
returnValue = fileChooser.showOpenDialog(graph);
if (returnValue == JFileChooser.APPROVE_OPTION) {
Container parent = graph.getParent();
BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler();
try {
uninstallListeners(graph);
parent.remove(graph);
ObjectInputStream in = new ObjectInputStream(
new BufferedInputStream(new FileInputStream(fileChooser
.getSelectedFile())));
graph = (JGraph) in.readObject();
// Take the marquee handler from the original graph and
// use it in the new graph as well.
graph.setMarqueeHandler(marqueeHandler);
// Adds the component back into the component hierarchy
if (parent instanceof JViewport) {
JViewport viewPort = (JViewport) parent;
viewPort.setView(graph);
} else {
// Best effort...
parent.add(graph);
}
// graph.setMarqueeHandler(previousHandler);
// And reinstalls the listener
installListeners(graph);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
}
}
}
public void saveFile() {
int returnValue = JFileChooser.CANCEL_OPTION;
initFileChooser();
returnValue = fileChooser.showSaveDialog(graph);
if (returnValue == JFileChooser.APPROVE_OPTION) {
XMLEncoder encoder;
Container parent = graph.getParent();
try {
uninstallListeners(graph);
parent.remove(graph);
encoder = new XMLEncoder(new BufferedOutputStream(
new FileOutputStream(fileChooser.getSelectedFile())));
configureEncoder(encoder);
encoder.writeObject(graph);
encoder.close();
} catch (Exception e) {
JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
} finally {
// Adds the component back into the component hierarchy
if (parent instanceof JViewport) {
JViewport viewPort = (JViewport) parent;
viewPort.setView(graph);
} else {
// Best effort...
parent.add(graph);
}
// And reinstalls the listener
installListeners(graph);
}
}
}
public void openFile() {
int returnValue = JFileChooser.CANCEL_OPTION;
initFileChooser();
returnValue = fileChooser.showOpenDialog(graph);
if (returnValue == JFileChooser.APPROVE_OPTION) {
Container parent = graph.getParent();
BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler();
try {
uninstallListeners(graph);
parent.remove(graph);
XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(
new FileInputStream(fileChooser.getSelectedFile())));
graph = (JGraph) decoder.readObject();
// Take the marquee handler from the original graph and
// use it in the new graph as well.
graph.setMarqueeHandler(marqueeHandler);
// Adds the component back into the component hierarchy
if (parent instanceof JViewport) {
JViewport viewPort = (JViewport) parent;
viewPort.setView(graph);
} else {
// Best effort...
parent.add(graph);
}
// graph.setMarqueeHandler(previousHandler);
// And reinstalls the listener
installListeners(graph);
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
}
}
}
/**
* Utility method that ensures the file chooser is created. Start-up time
* is improved by lazily instaniating choosers.
*
*/
protected void initFileChooser() {
if (fileChooser == null) {
fileChooser = new JFileChooser();
FileFilter fileFilter = new FileFilter() {
/**
* @see javax.swing.filechooser.FileFilter#accept(File)
*/
public boolean accept(File f) {
if (f == null)
return false;
if (f.getName() == null)
return false;
if (f.getName().endsWith(".xml"))
return true;
if (f.getName().endsWith(".ser"))
return true;
if (f.isDirectory())
return true;
return false;
}
/**
* @see javax.swing.filechooser.FileFilter#getDescription()
*/
public String getDescription() {
return "GraphEd file (.xml, .ser)";
}
};
fileChooser.setFileFilter(fileFilter);
}
}
protected void configureEncoder(XMLEncoder encoder) {
// Better debugging output, in case you need it
encoder.setExceptionListener(new ExceptionListener() {
public void exceptionThrown(Exception e) {
e.printStackTrace();
}
});
encoder.setPersistenceDelegate(DefaultGraphModel.class,
new DefaultPersistenceDelegate(new String[] { "roots",
"attributes" }));
encoder.setPersistenceDelegate(MyGraphModel.class,
new DefaultPersistenceDelegate(new String[] { "roots",
"attributes" }));
// Note: In the static initializer the marquee handler of the
// MyGraph class is made transient to avoid being written out.
encoder.setPersistenceDelegate(MyGraph.class,
new DefaultPersistenceDelegate(new String[] { "model",
"graphLayoutCache" }));
encoder
.setPersistenceDelegate(GraphLayoutCache.class,
new DefaultPersistenceDelegate(new String[] { "model",
"factory", "cellViews", "hiddenCellViews",
"partial" }));
encoder.setPersistenceDelegate(DefaultGraphCell.class,
new DefaultPersistenceDelegate(new String[] { "userObject" }));
encoder.setPersistenceDelegate(DefaultEdge.class,
new DefaultPersistenceDelegate(new String[] { "userObject" }));
encoder.setPersistenceDelegate(DefaultPort.class,
new DefaultPersistenceDelegate(new String[] { "userObject" }));
encoder.setPersistenceDelegate(AbstractCellView.class,
new DefaultPersistenceDelegate(new String[] { "cell",
"attributes" }));
encoder.setPersistenceDelegate(DefaultEdge.DefaultRouting.class,
new PersistenceDelegate() {
protected Expression instantiate(Object oldInstance,
Encoder out) {
return new Expression(oldInstance,
GraphConstants.class, "getROUTING_SIMPLE", null);
}
});
encoder.setPersistenceDelegate(DefaultEdge.LoopRouting.class,
new PersistenceDelegate() {
protected Expression instantiate(Object oldInstance,
Encoder out) {
return new Expression(oldInstance,
GraphConstants.class, "getROUTING_DEFAULT",
null);
}
});
encoder.setPersistenceDelegate(JGraphShadowBorder.class,
new PersistenceDelegate() {
protected Expression instantiate(Object oldInstance,
Encoder out) {
return new Expression(oldInstance,
JGraphShadowBorder.class, "getSharedInstance",
null);
}
});
encoder.setPersistenceDelegate(ArrayList.class, encoder
.getPersistenceDelegate(List.class));
}
/**
* Main method
*/
public static void main(String[] args) {
try {
// Construct Frame
JFrame frame = new JFrame("GraphEdX");
// Set Close Operation to Exit
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Fetch URL to Icon Resource
URL jgraphUrl = GraphEdX.class.getClassLoader().getResource(
"org/jgraph/example/resources/jgraph.gif");
// If Valid URL
if (jgraphUrl != null) {
// Load Icon
ImageIcon jgraphIcon = new ImageIcon(jgraphUrl);
// Use in Window
frame.setIconImage(jgraphIcon.getImage());
}
// Add an Editor Panel
GraphEdX app = new GraphEdX();
frame.getContentPane().add(app);
app.init();
// Set Default Size
frame.setSize(640, 480);
// Show Frame
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Makes all fields but <code>cell</code> and <code>attributes</code>
* transient in the bean info of <code>clazz</code>.
*
* @param clazz
* The cell view class who fields should be made transient.
*/
public static void makeCellViewFieldsTransient(Class clazz) {
try {
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] propertyDescriptors = info
.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; ++i) {
PropertyDescriptor pd = propertyDescriptors[i];
if (!pd.getName().equals("cell")
&& !pd.getName().equals("attributes")) {
pd.setValue("transient", Boolean.TRUE);
}
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
/**
* Encodable graph model with related constructor. Note: This class must be
* static for the XML encoding to work.
*/
public static class MyGraphModel extends DefaultGraphModel {
public MyGraphModel() {
super();
}
public MyGraphModel(List roots, AttributeMap attributes) {
super(roots, attributes);
}
public boolean acceptsSource(Object edge, Object port) {
return (port != null);
}
public boolean acceptsTarget(Object edge, Object port) {
return (port != null);
}
}
}