1 /* 2 * Copyright (c) 2001-2006, Gaudenz Alder 3 * Copyright (c) 2005-2006, David Benson 4 * 5 * All rights reserved. 6 * 7 * This file is licensed under the JGraph software license, a copy of which 8 * will have been provided to you in the file LICENSE at the root of your 9 * installation directory. If you are unable to locate this file please 10 * contact JGraph sales for another copy. 11 */ 12 package com.jgraph.example; 13 14 import java.awt.Color; 15 import java.awt.Container; 16 import java.awt.event.ActionEvent; 17 import java.awt.geom.Point2D; 18 import java.awt.geom.Rectangle2D; 19 import java.beans.BeanInfo; 20 import java.beans.DefaultPersistenceDelegate; 21 import java.beans.Encoder; 22 import java.beans.ExceptionListener; 23 import java.beans.Expression; 24 import java.beans.IntrospectionException; 25 import java.beans.Introspector; 26 import java.beans.PersistenceDelegate; 27 import java.beans.PropertyDescriptor; 28 import java.beans.XMLDecoder; 29 import java.beans.XMLEncoder; 30 import java.io.BufferedInputStream; 31 import java.io.BufferedOutputStream; 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.FileNotFoundException; 35 import java.io.FileOutputStream; 36 import java.io.ObjectInputStream; 37 import java.io.ObjectOutputStream; 38 import java.net.URL; 39 import java.util.ArrayList; 40 import java.util.HashSet; 41 import java.util.Hashtable; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 46 import javax.swing.AbstractAction; 47 import javax.swing.Action; 48 import javax.swing.ImageIcon; 49 import javax.swing.JFileChooser; 50 import javax.swing.JFrame; 51 import javax.swing.JOptionPane; 52 import javax.swing.JToolBar; 53 import javax.swing.JViewport; 54 import javax.swing.filechooser.FileFilter; 55 56 import org.jgraph.JGraph; 57 import org.jgraph.event.GraphSelectionEvent; 58 import org.jgraph.example.GraphEd; 59 import org.jgraph.graph.AbstractCellView; 60 import org.jgraph.graph.AttributeMap; 61 import org.jgraph.graph.BasicMarqueeHandler; 62 import org.jgraph.graph.DefaultCellViewFactory; 63 import org.jgraph.graph.DefaultEdge; 64 import org.jgraph.graph.DefaultGraphCell; 65 import org.jgraph.graph.DefaultGraphModel; 66 import org.jgraph.graph.DefaultPort; 67 import org.jgraph.graph.EdgeView; 68 import org.jgraph.graph.GraphConstants; 69 import org.jgraph.graph.GraphLayoutCache; 70 import org.jgraph.graph.GraphModel; 71 import org.jgraph.graph.ParentMap; 72 import org.jgraph.graph.PortView; 73 import org.jgraph.graph.VertexView; 74 75 /** 76 * An extension to GraphEd demonstrating more advanced JGraph features 77 */ 78 public class GraphEdX extends GraphEd { 79 80 /** 81 * JGraph Factory instance for random new graphs 82 */ 83 protected JGraphGraphFactory graphFactory = new JGraphGraphFactory(); 84 85 /** 86 * References the folding manager. 87 */ 88 protected JGraphFoldingManager foldingManager; 89 90 // Actions which Change State 91 protected Action hide, collapse, expand, expandAll, configure; 92 93 /** 94 * File chooser for loading and saving graphs. Note that it is lazily 95 * instaniated, always call initFileChooser before use. 96 */ 97 protected JFileChooser fileChooser = null; 98 99 static { 100 makeCellViewFieldsTransient(PortView.class); 101 makeCellViewFieldsTransient(VertexView.class); 102 makeCellViewFieldsTransient(EdgeView.class); 103 104 // For XML Encoding of the graph instance, we need to exclude 105 // the marquee handler explicitely. Being an inner class of 106 // GraphEd, it should not be part of the written file. 107 try { 108 BeanInfo info = Introspector.getBeanInfo(MyGraph.class); 109 PropertyDescriptor[] propertyDescriptors = info 110 .getPropertyDescriptors(); 111 for (int i = 0; i < propertyDescriptors.length; ++i) { 112 PropertyDescriptor pd = propertyDescriptors[i]; 113 if (pd.getName().equals("marqueeHandler")) { 114 pd.setValue("transient", Boolean.TRUE); 115 } 116 } 117 } catch (IntrospectionException e) { 118 e.printStackTrace(); 119 } 120 } 121 122 /** 123 * Constructs a new application 124 */ GraphEdX()125 public GraphEdX() { 126 // Overrides the global vertex renderer 127 VertexView.renderer = new JGraphGroupRenderer(); 128 129 // Prepares layout actions 130 setJMenuBar(new GraphEdXMenuBar(this, graphFactory)); 131 // Initializes actions states 132 valueChanged(null); 133 } 134 135 /** 136 * Show something interesting on start. 137 */ init()138 public void init() { 139 graphFactory.insertTreeSampleData(getGraph(), 140 createCellAttributes(new Point2D.Double(0, 0)), 141 createEdgeAttributes()); 142 } 143 144 // Override parent method createGraph()145 protected JGraph createGraph() { 146 // Creates a model that does not allow disconnections 147 GraphModel model = new MyGraphModel(); 148 GraphLayoutCache layoutCache = new GraphLayoutCache(model, 149 new DefaultCellViewFactory(), true); 150 return new MyGraph(model, layoutCache); 151 } 152 153 // Override parent method installListeners(JGraph graph)154 protected void installListeners(JGraph graph) { 155 super.installListeners(graph); 156 // Adds redirector for group collapse/expand 157 foldingManager = new JGraphFoldingManager(); 158 graph.addMouseListener(foldingManager); 159 } 160 uninstallListeners(JGraph graph)161 protected void uninstallListeners(JGraph graph) { 162 super.uninstallListeners(graph); 163 graph.removeMouseListener(foldingManager); 164 } 165 166 /** 167 * Updates buttons based on application state 168 */ valueChanged(GraphSelectionEvent e)169 public void valueChanged(GraphSelectionEvent e) { 170 super.valueChanged(e); 171 // Group Button only Enabled if a cell is selected 172 boolean enabled = !graph.isSelectionEmpty(); 173 // hide.setEnabled(enabled); 174 expand.setEnabled(enabled); 175 expandAll.setEnabled(enabled); 176 collapse.setEnabled(enabled); 177 } 178 179 /** 180 * Overrides the parent example group method to set the bounds of the 181 * collapsed group cell appropriately 182 */ group(Object[] children)183 public void group(Object[] children) { 184 // Order Cells by Model Layering 185 children = graph.order(children); 186 // If Any Cells in View 187 if (children != null && children.length > 0) { 188 double gs2 = 2 * graph.getGridSize(); 189 Rectangle2D collapsedBounds = graph.getCellBounds(children); 190 collapsedBounds.setFrame(collapsedBounds.getX(), collapsedBounds 191 .getY(), Math.max(collapsedBounds.getWidth() / 4, gs2), 192 Math.max(collapsedBounds.getHeight() / 2, gs2)); 193 graph.snap(collapsedBounds); 194 DefaultGraphCell group = createGroupCell(collapsedBounds); 195 if (group != null && children != null && children.length > 0) { 196 // Create the group structure 197 ParentMap pm = new ParentMap(); 198 for (int i = 0; i < children.length; i++) { 199 pm.addEntry(children[i], group); 200 } 201 graph.getGraphLayoutCache().insert(new Object[] { group }, 202 null, null, pm); 203 } 204 } 205 } 206 207 /** 208 * Hook from GraphEd to create a new group cell 209 */ createGroupCell(Rectangle2D collapsedBounds)210 protected DefaultGraphCell createGroupCell(Rectangle2D collapsedBounds) { 211 DefaultGraphCell group = super.createGroupCell(); 212 group.addPort(); 213 GraphConstants.setInset(group.getAttributes(), 10); 214 GraphConstants.setBackground(group.getAttributes(), new Color(240, 240, 215 255)); 216 GraphConstants.setBorderColor(group.getAttributes(), Color.black); 217 GraphConstants.setOpaque(group.getAttributes(), true); 218 GraphConstants.setBorder(group.getAttributes(), JGraphShadowBorder 219 .getSharedInstance()); 220 GraphConstants.setBounds(group.getAttributes(), collapsedBounds); 221 return group; 222 } 223 224 /** 225 * Hook from GraphEd to set attributes of a new cell 226 */ createCellAttributes(Point2D point)227 public Map createCellAttributes(Point2D point) { 228 Map map = super.createCellAttributes(point); 229 GraphConstants.setInset(map, 5); 230 GraphConstants.setGradientColor(map, new Color(200, 200, 255)); 231 return map; 232 } 233 234 /** 235 * Hook from GraphEd to set attributes of a new edge 236 */ createEdgeAttributes()237 public Map createEdgeAttributes() { 238 Map map = super.createEdgeAttributes(); 239 // Adds a parallel edge router 240 GraphConstants.setLineStyle(map, GraphConstants.STYLE_SPLINE); 241 if (GraphConstants.DEFAULTFONT != null) { 242 GraphConstants.setFont(map, GraphConstants.DEFAULTFONT 243 .deriveFont(10f)); 244 } 245 return map; 246 } 247 248 /** 249 * Hook from GraphEd to add action button to the tool bar 250 */ createToolBar()251 public JToolBar createToolBar() { 252 JToolBar toolbar = super.createToolBar(); 253 254 // Collapse 255 collapse = new AbstractAction() { 256 public void actionPerformed(ActionEvent e) { 257 graph.getGraphLayoutCache().setVisible(graph.getSelectionCells(), false); 258 } 259 }; 260 URL url = getClass().getClassLoader().getResource( 261 "org/jgraph/example/resources/collapse.gif"); 262 collapse.putValue(Action.SMALL_ICON, new ImageIcon(url)); 263 collapse.setEnabled(false); 264 toolbar.add(collapse); 265 266 // Expand 267 expand = new AbstractAction() { 268 public void actionPerformed(ActionEvent e) { 269 graph.getGraphLayoutCache().expand(graph.getSelectionCells()); 270 } 271 }; 272 url = getClass().getClassLoader().getResource( 273 "org/jgraph/example/resources/expand.gif"); 274 expand.putValue(Action.SMALL_ICON, new ImageIcon(url)); 275 expand.setEnabled(false); 276 toolbar.add(expand); 277 278 // ExpandAll 279 expandAll = new AbstractAction() { 280 public void actionPerformed(ActionEvent e) { 281 Object[] allCells = graph.getDescendants(graph.getRoots()); 282 Map nested = new Hashtable(); 283 for (int i=0; i < allCells.length; i++) { 284 Map attributeMap = new Hashtable(); 285 GraphConstants.setForeground(attributeMap, Color.BLACK); 286 nested.put(allCells[i], attributeMap); 287 } 288 graph.getModel().edit(nested, null, null, null); 289 } 290 }; 291 url = getClass().getClassLoader().getResource( 292 "org/jgraph/example/resources/expandAll.gif"); 293 expandAll.putValue(Action.SMALL_ICON, new ImageIcon(url)); 294 expandAll.setEnabled(false); 295 toolbar.add(expandAll); 296 return toolbar; 297 } 298 serializeGraph()299 public void serializeGraph() { 300 int returnValue = JFileChooser.CANCEL_OPTION; 301 initFileChooser(); 302 returnValue = fileChooser.showSaveDialog(graph); 303 if (returnValue == JFileChooser.APPROVE_OPTION) { 304 Container parent = graph.getParent(); 305 BasicMarqueeHandler marquee = graph.getMarqueeHandler(); 306 graph.setMarqueeHandler(null); 307 try { 308 // Serializes the graph by removing it from the component 309 // hierarchy and removing all listeners from it. The marquee 310 // handler, begin an inner class of GraphEd, is not marked 311 // serializable and will therefore not be stored. This must 312 // be taken into account when deserializing a graph. 313 uninstallListeners(graph); 314 parent.remove(graph); 315 ObjectOutputStream out = new ObjectOutputStream( 316 new BufferedOutputStream(new FileOutputStream( 317 fileChooser.getSelectedFile()))); 318 out.writeObject(graph); 319 out.flush(); 320 out.close(); 321 } catch (Exception e) { 322 e.printStackTrace(); 323 JOptionPane.showMessageDialog(graph, e.getMessage(), "Error", 324 JOptionPane.ERROR_MESSAGE); 325 } finally { 326 // Adds the component back into the component hierarchy 327 graph.setMarqueeHandler(marquee); 328 if (parent instanceof JViewport) { 329 JViewport viewPort = (JViewport) parent; 330 viewPort.setView(graph); 331 } else { 332 // Best effort... 333 parent.add(graph); 334 } 335 // And reinstalls the listener 336 installListeners(graph); 337 } 338 } 339 } 340 deserializeGraph()341 public void deserializeGraph() { 342 int returnValue = JFileChooser.CANCEL_OPTION; 343 initFileChooser(); 344 returnValue = fileChooser.showOpenDialog(graph); 345 if (returnValue == JFileChooser.APPROVE_OPTION) { 346 Container parent = graph.getParent(); 347 BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler(); 348 try { 349 uninstallListeners(graph); 350 parent.remove(graph); 351 ObjectInputStream in = new ObjectInputStream( 352 new BufferedInputStream(new FileInputStream(fileChooser 353 .getSelectedFile()))); 354 graph = (JGraph) in.readObject(); 355 // Take the marquee handler from the original graph and 356 // use it in the new graph as well. 357 graph.setMarqueeHandler(marqueeHandler); 358 // Adds the component back into the component hierarchy 359 if (parent instanceof JViewport) { 360 JViewport viewPort = (JViewport) parent; 361 viewPort.setView(graph); 362 } else { 363 // Best effort... 364 parent.add(graph); 365 } 366 // graph.setMarqueeHandler(previousHandler); 367 // And reinstalls the listener 368 installListeners(graph); 369 } catch (Exception e) { 370 e.printStackTrace(); 371 JOptionPane.showMessageDialog(graph, e.getMessage(), "Error", 372 JOptionPane.ERROR_MESSAGE); 373 } 374 } 375 } 376 saveFile()377 public void saveFile() { 378 int returnValue = JFileChooser.CANCEL_OPTION; 379 initFileChooser(); 380 returnValue = fileChooser.showSaveDialog(graph); 381 if (returnValue == JFileChooser.APPROVE_OPTION) { 382 XMLEncoder encoder; 383 Container parent = graph.getParent(); 384 try { 385 uninstallListeners(graph); 386 parent.remove(graph); 387 encoder = new XMLEncoder(new BufferedOutputStream( 388 new FileOutputStream(fileChooser.getSelectedFile()))); 389 configureEncoder(encoder); 390 encoder.writeObject(graph); 391 encoder.close(); 392 } catch (Exception e) { 393 JOptionPane.showMessageDialog(graph, e.getMessage(), "Error", 394 JOptionPane.ERROR_MESSAGE); 395 } finally { 396 // Adds the component back into the component hierarchy 397 if (parent instanceof JViewport) { 398 JViewport viewPort = (JViewport) parent; 399 viewPort.setView(graph); 400 } else { 401 // Best effort... 402 parent.add(graph); 403 } 404 // And reinstalls the listener 405 installListeners(graph); 406 } 407 } 408 } 409 openFile()410 public void openFile() { 411 int returnValue = JFileChooser.CANCEL_OPTION; 412 initFileChooser(); 413 returnValue = fileChooser.showOpenDialog(graph); 414 if (returnValue == JFileChooser.APPROVE_OPTION) { 415 Container parent = graph.getParent(); 416 BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler(); 417 try { 418 uninstallListeners(graph); 419 parent.remove(graph); 420 XMLDecoder decoder = new XMLDecoder(new BufferedInputStream( 421 new FileInputStream(fileChooser.getSelectedFile()))); 422 423 // In case you need to debug XML decoding here is how 424 // to print a stack trace 425 /* 426 decoder.setExceptionListener(new ExceptionListener() 427 { 428 public void exceptionThrown(Exception exception) 429 { 430 exception.printStackTrace(); 431 } 432 }); 433 */ 434 435 graph = (JGraph) decoder.readObject(); 436 // Take the marquee handler from the original graph and 437 // use it in the new graph as well. 438 graph.setMarqueeHandler(marqueeHandler); 439 // Adds the component back into the component hierarchy 440 if (parent instanceof JViewport) { 441 JViewport viewPort = (JViewport) parent; 442 viewPort.setView(graph); 443 } else { 444 // Best effort... 445 parent.add(graph); 446 } 447 // graph.setMarqueeHandler(previousHandler); 448 // And reinstalls the listener 449 installListeners(graph); 450 } catch (FileNotFoundException e) { 451 JOptionPane.showMessageDialog(graph, e.getMessage(), "Error", 452 JOptionPane.ERROR_MESSAGE); 453 } 454 455 } 456 } 457 458 /** 459 * Utility method that ensures the file chooser is created. Start-up time 460 * is improved by lazily instaniating choosers. 461 * 462 */ initFileChooser()463 protected void initFileChooser() { 464 if (fileChooser == null) { 465 fileChooser = new JFileChooser(); 466 FileFilter fileFilter = new FileFilter() { 467 /** 468 * @see javax.swing.filechooser.FileFilter#accept(File) 469 */ 470 public boolean accept(File f) { 471 if (f == null) 472 return false; 473 if (f.getName() == null) 474 return false; 475 if (f.getName().endsWith(".xml")) 476 return true; 477 if (f.getName().endsWith(".ser")) 478 return true; 479 if (f.isDirectory()) 480 return true; 481 482 return false; 483 } 484 485 /** 486 * @see javax.swing.filechooser.FileFilter#getDescription() 487 */ 488 public String getDescription() { 489 return "GraphEd file (.xml, .ser)"; 490 } 491 }; 492 fileChooser.setFileFilter(fileFilter); 493 } 494 } 495 configureEncoder(XMLEncoder encoder)496 protected void configureEncoder(XMLEncoder encoder) { 497 // Better debugging output, in case you need it 498 encoder.setExceptionListener(new ExceptionListener() { 499 public void exceptionThrown(Exception e) { 500 e.printStackTrace(); 501 } 502 }); 503 504 encoder.setPersistenceDelegate(DefaultGraphModel.class, 505 new DefaultPersistenceDelegate(new String[] { "roots", 506 "attributes" })); 507 encoder.setPersistenceDelegate(MyGraphModel.class, 508 new DefaultPersistenceDelegate(new String[] { "roots", 509 "attributes" })); 510 511 // Note: In the static initializer the marquee handler of the 512 // MyGraph class is made transient to avoid being written out. 513 encoder.setPersistenceDelegate(MyGraph.class, 514 new DefaultPersistenceDelegate(new String[] { "model", 515 "graphLayoutCache" })); 516 encoder 517 .setPersistenceDelegate(GraphLayoutCache.class, 518 new DefaultPersistenceDelegate(new String[] { "model", 519 "factory", "cellViews", "hiddenCellViews", 520 "partial" })); 521 encoder.setPersistenceDelegate(DefaultGraphCell.class, 522 new DefaultPersistenceDelegate(new String[] { "userObject" })); 523 encoder.setPersistenceDelegate(DefaultEdge.class, 524 new DefaultPersistenceDelegate(new String[] { "userObject" })); 525 encoder.setPersistenceDelegate(DefaultPort.class, 526 new DefaultPersistenceDelegate(new String[] { "userObject" })); 527 encoder.setPersistenceDelegate(AbstractCellView.class, 528 new DefaultPersistenceDelegate(new String[] { "cell", 529 "attributes" })); 530 encoder.setPersistenceDelegate(DefaultEdge.DefaultRouting.class, 531 new PersistenceDelegate() { 532 protected Expression instantiate(Object oldInstance, 533 Encoder out) { 534 return new Expression(oldInstance, 535 GraphConstants.class, "getROUTING_SIMPLE", null); 536 } 537 }); 538 encoder.setPersistenceDelegate(DefaultEdge.LoopRouting.class, 539 new PersistenceDelegate() { 540 protected Expression instantiate(Object oldInstance, 541 Encoder out) { 542 return new Expression(oldInstance, 543 GraphConstants.class, "getROUTING_DEFAULT", 544 null); 545 } 546 }); 547 encoder.setPersistenceDelegate(JGraphShadowBorder.class, 548 new PersistenceDelegate() { 549 protected Expression instantiate(Object oldInstance, 550 Encoder out) { 551 return new Expression(oldInstance, 552 JGraphShadowBorder.class, "getSharedInstance", 553 null); 554 } 555 }); 556 encoder.setPersistenceDelegate(ArrayList.class, encoder 557 .getPersistenceDelegate(List.class)); 558 } 559 560 /** 561 * Main method 562 */ main(String[] args)563 public static void main(String[] args) { 564 try { 565 // Switch off D3D because of Sun XOR painting bug 566 // See http://www.jgraph.com/forum/viewtopic.php?t=4066 567 System.setProperty("sun.java2d.d3d", "false"); 568 // Construct Frame 569 JFrame frame = new JFrame("GraphEdX"); 570 // Set Close Operation to Exit 571 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 572 // Fetch URL to Icon Resource 573 URL jgraphUrl = GraphEdX.class.getClassLoader().getResource( 574 "org/jgraph/example/resources/jgraph.gif"); 575 // If Valid URL 576 if (jgraphUrl != null) { 577 // Load Icon 578 ImageIcon jgraphIcon = new ImageIcon(jgraphUrl); 579 // Use in Window 580 frame.setIconImage(jgraphIcon.getImage()); 581 } 582 // Add an Editor Panel 583 GraphEdX app = new GraphEdX(); 584 frame.getContentPane().add(app); 585 app.init(); 586 // Set Default Size 587 frame.setSize(640, 480); 588 // Show Frame 589 frame.setVisible(true); 590 } catch (Exception e) { 591 e.printStackTrace(); 592 } 593 } 594 595 /** 596 * Makes all fields but <code>cell</code> and <code>attributes</code> 597 * transient in the bean info of <code>clazz</code>. 598 * 599 * @param clazz 600 * The cell view class who fields should be made transient. 601 */ makeCellViewFieldsTransient(Class clazz)602 public static void makeCellViewFieldsTransient(Class clazz) { 603 try { 604 BeanInfo info = Introspector.getBeanInfo(clazz); 605 PropertyDescriptor[] propertyDescriptors = info 606 .getPropertyDescriptors(); 607 for (int i = 0; i < propertyDescriptors.length; ++i) { 608 PropertyDescriptor pd = propertyDescriptors[i]; 609 if (!pd.getName().equals("cell") 610 && !pd.getName().equals("attributes")) { 611 pd.setValue("transient", Boolean.TRUE); 612 } 613 } 614 } catch (IntrospectionException e) { 615 e.printStackTrace(); 616 } 617 } 618 619 /** 620 * Encodable graph model with related constructor. Note: This class must be 621 * static for the XML encoding to work. 622 */ 623 public static class MyGraphModel extends DefaultGraphModel { 624 MyGraphModel()625 public MyGraphModel() { 626 super(); 627 } 628 MyGraphModel(List roots, AttributeMap attributes)629 public MyGraphModel(List roots, AttributeMap attributes) { 630 super(roots, attributes); 631 } 632 acceptsSource(Object edge, Object port)633 public boolean acceptsSource(Object edge, Object port) { 634 return (port != null); 635 } 636 acceptsTarget(Object edge, Object port)637 public boolean acceptsTarget(Object edge, Object port) { 638 return (port != null); 639 } 640 } 641 642 } 643