1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.io.graphml;
15  
16  import demo.view.DemoBase;
17  import demo.view.DemoDefaults;
18  import demo.view.flowchart.painters.FlowchartRealizerFactory;
19  import y.base.Edge;
20  import y.base.EdgeCursor;
21  import y.base.Node;
22  import y.base.NodeCursor;
23  import y.io.GraphMLIOHandler;
24  import y.io.graphml.output.GraphElementIdProvider;
25  import y.io.graphml.output.GraphMLWriteException;
26  import y.io.graphml.output.WriteEvent;
27  import y.io.graphml.output.WriteEventListenerAdapter;
28  import y.util.D;
29  import y.view.BevelNodePainter;
30  import y.view.EditMode;
31  import y.view.GenericNodeRealizer;
32  import y.view.Graph2D;
33  import y.view.Graph2DEvent;
34  import y.view.Graph2DListener;
35  import y.view.Graph2DUndoManager;
36  import y.view.Selections;
37  import y.view.Selections.SelectionStateObserver;
38  import y.view.ViewMode;
39  import y.view.hierarchy.HierarchyManager;
40  
41  import javax.swing.Action;
42  import javax.swing.BorderFactory;
43  import javax.swing.JButton;
44  import javax.swing.JLabel;
45  import javax.swing.JPanel;
46  import javax.swing.JScrollPane;
47  import javax.swing.JSplitPane;
48  import javax.swing.JTextArea;
49  import javax.swing.JToolBar;
50  import javax.swing.Timer;
51  import javax.swing.text.BadLocationException;
52  import javax.swing.text.DefaultHighlighter;
53  import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
54  import java.awt.BorderLayout;
55  import java.awt.Dimension;
56  import java.awt.EventQueue;
57  import java.awt.Font;
58  import java.awt.event.ActionEvent;
59  import java.awt.event.ActionListener;
60  import java.beans.PropertyChangeEvent;
61  import java.beans.PropertyChangeListener;
62  import java.io.ByteArrayInputStream;
63  import java.io.IOException;
64  import java.io.StringWriter;
65  import java.util.HashMap;
66  import java.util.Iterator;
67  import java.util.Locale;
68  import java.util.Map;
69  import java.util.regex.Matcher;
70  import java.util.regex.Pattern;
71  
72  
73  /**
74   * This shows the basic usage of GraphMLIOHandler to load and save in the GraphML file format.
75   *
76   * In addition, it shows the graphml representation of the current graph in the lower text pane.
77   * This representation is updated dynamically. Also, edits in the graphml text can be applied
78   * to the current graph by pressing the "Apply GraphML" button.
79   *
80   * A small list of predefined GraphML files can be accessed from the combobox in the toolbar.
81   *
82   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/graphml.html#graphml_extension">Section Reading and Writing Additional Data</a> in the yFiles for Java Developer's Guide
83   */
84  public class GraphMLDemo extends DemoBase {
85    private static final String BEVEL_NODE_CONFIGURATION = "BevelNodeConfig";
86  
87    protected GraphMLPane graphMLPane;
88    private Graph2DUndoManager undoManager;
89  
90    /**
91     * Creates a new instance of GraphMLDemo
92     */
93    public GraphMLDemo() {
94  
95      graphMLPane = new GraphMLPane();
96      graphMLPane.setPreferredSize(new Dimension(600, 350));
97      graphMLPane.setMinimumSize(new Dimension(0, 100));
98  
99      //plug the gui elements together and add them to the pane
100 
101     JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, view, graphMLPane);
102     splitPane.setBorder(BorderFactory.createEmptyBorder());
103     view.setPreferredSize(new Dimension(600, 350));
104     view.setMinimumSize(new Dimension(0, 200));
105     initGraph();
106     view.fitContent();
107     contentPane.add(splitPane, BorderLayout.CENTER);
108   }
109 
110   protected void initialize() {
111     final Graph2D graph = view.getGraph2D();
112 
113     //Create a hierarchy manager that allows us to read/write hierarchically structured graphs.
114     new HierarchyManager(graph);
115 
116     //Create a customized undo manager which triggers updates of the GraphML section.
117     undoManager = new Graph2DUndoManager(graph) {
118       /**
119        * Stores all edge realizer so their changes can be undone/redone and and informs the GraphML section about a
120        * potential change.
121        * @param graph The graph where the edges belong to.
122        * @param ec    The edges which realizers are stored.
123        */
124       public void backupRealizers(Graph2D graph, EdgeCursor ec) {
125         super.backupRealizers(graph, ec);
126         graphMLPane.firePotentialChange();
127       }
128 
129       /**
130        * Stores all node realizer so their changes can be undone/redone and and informs the GraphML section about a
131        * potential change.
132        * @param graph The graph where the nodes belong to.
133        * @param nc    The nodes which realizers are stored.
134        */
135       public void backupRealizers(Graph2D graph, NodeCursor nc) {
136         super.backupRealizers(graph, nc);
137         graphMLPane.firePotentialChange();
138       }
139     };
140     undoManager.setViewContainer(view);
141 
142     graph.setBackupRealizersHandler(undoManager);
143   }
144 
145   protected void configureDefaultRealizers() {
146     super.configureDefaultRealizers();
147 
148     final GenericNodeRealizer.Factory f = GenericNodeRealizer.getFactory();
149     if (!f.getAvailableConfigurations().contains(BEVEL_NODE_CONFIGURATION)) {
150       BevelNodePainter bevelNodePainter = new BevelNodePainter();
151       bevelNodePainter.setDrawShadow(true);
152       Map impls = f.createDefaultConfigurationMap();
153       impls.put(GenericNodeRealizer.Painter.class, bevelNodePainter);
154       impls.put(GenericNodeRealizer.ContainsTest.class, bevelNodePainter);
155       f.addConfiguration(BEVEL_NODE_CONFIGURATION, impls);
156     }
157 
158     // registers all flowchart configurations
159     FlowchartRealizerFactory.createData();
160   }
161 
162   private void initGraph() {
163     loadInitialGraph();
164     undoManager.resetQueue();
165   }
166 
167   protected void loadInitialGraph() {
168     loadGraph("resources/ygraph/visual_features.graphml");
169   }
170 
171   /**
172    * Overrides the base class method to add a sample file combo box.
173    */
174   protected JToolBar createToolBar() {
175     JToolBar jToolBar = super.createToolBar();
176 
177     //add undo action to toolbar
178     jToolBar.addSeparator();
179     Action action = undoManager.getUndoAction();
180     action.putValue(Action.SMALL_ICON, getIconResource("resource/undo.png"));
181     action.putValue(Action.SHORT_DESCRIPTION, "Undo");
182     jToolBar.add(action);
183 
184     //add redo action to toolbar
185     action = undoManager.getRedoAction();
186     action.putValue(Action.SMALL_ICON, getIconResource("resource/redo.png"));
187     action.putValue(Action.SHORT_DESCRIPTION, "Redo");
188     jToolBar.add(action);
189 
190     return jToolBar;
191   }
192 
193   /**
194    * Returns the list of sample files of this demo.
195    */
196   protected String[] getExampleResources() {
197     return new String[]{
198         "resources/ygraph/visual_features.graphml",
199         "resources/ygraph/problemsolving.graphml",
200         "resources/ygraph/simple.graphml",
201         "resources/ygraph/grouping.graphml",
202     };
203   }
204 
205   protected void loadGraph(Class aClass, String resourceString) {
206     try {
207       graphMLPane.setUpdating(true);
208       super.loadGraph(aClass, resourceString);
209       graphMLPane.setUpdating(false);
210       graphMLPane.showGraphMLText(view.getGraph2D());
211     }
212     finally {
213       graphMLPane.setUpdating(false);
214     }
215   }
216 
217   /**
218    * Launches this demo.
219    */
220   public static void main(String[] args) {
221     EventQueue.invokeLater(new Runnable() {
222       public void run() {
223         Locale.setDefault(Locale.ENGLISH);
224         initLnF();
225         new GraphMLDemo().start();
226       }
227     });
228   }
229 
230   class GraphMLPane extends JPanel {
231 
232     /**
233      * Re-entrance lock
234      */
235     protected boolean updating;
236     private boolean editable;
237 
238     private final JButton applyButton;
239     private Map elementIdMap;
240     private final JTextArea graphMLTextPane;
241     private int lastStartIndex;
242     Timer timer;
243 
244     GraphMLPane() {
245       editable = true;
246 
247       JPanel graphMLHeader = new JPanel();
248       JLabel graphMLLabel = new JLabel("GraphML representation");
249       graphMLLabel.setFont(graphMLLabel.getFont().deriveFont(Font.BOLD));
250       graphMLLabel.setFont(graphMLLabel.getFont().deriveFont(12.0f));
251       graphMLLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
252       setLayout(new BorderLayout());
253 
254       graphMLTextPane = new JTextArea();
255 
256       JScrollPane scrollPane = new JScrollPane(graphMLTextPane);
257       scrollPane.setPreferredSize(new Dimension(0, 350));
258 
259       graphMLHeader.setLayout(new BorderLayout());
260       graphMLHeader.add(graphMLLabel, BorderLayout.WEST);
261       applyButton = new JButton("Apply GraphML");
262 
263       applyButton.addActionListener(new ActionListener() {
264         public void actionPerformed(ActionEvent e) {
265           applyGraphMLText(view.getGraph2D());
266         }
267       });
268 
269       graphMLHeader.add(applyButton, BorderLayout.EAST);
270 
271       add(graphMLHeader, BorderLayout.NORTH);
272       add(scrollPane, BorderLayout.CENTER);
273       setEditable(editable);
274       registerView();
275     }
276 
277 
278     public boolean isEditable() {
279       return editable;
280     }
281 
282     public void setEditable(boolean editable) {
283       this.editable = editable;
284       graphMLTextPane.setEditable(editable);
285       applyButton.setVisible(editable);
286     }
287 
288     void registerView() {
289 
290       for(Iterator iter = view.getViewModes(); iter.hasNext();) {
291         ViewMode mode = (ViewMode) iter.next();
292         if(mode instanceof EditMode) {
293           EditMode editMode = (EditMode) mode;
294           PropertyChangeListener pcl = new PropertyChangeListener() {
295             public void propertyChange(PropertyChangeEvent evt) {
296               if (evt.getPropertyName().equals(ViewMode.EDITING_PROPERTY) && evt.getNewValue().equals(Boolean.FALSE)) {
297                 firePotentialChange();                
298               }
299             }
300           };
301           editMode.addPropertyChangeListener(pcl);
302           editMode.getHotSpotMode().addPropertyChangeListener(pcl);
303           editMode.getMovePortMode().addPropertyChangeListener(pcl);
304           editMode.getMoveLabelMode().addPropertyChangeListener(pcl);
305           editMode.getMoveSelectionMode().addPropertyChangeListener(pcl);
306           editMode.getMoveSelectionMode().addPropertyChangeListener(pcl);
307           break;
308         }
309       }
310 
311       SelectionStateObserver sto = new SelectionStateObserver() {
312         protected void updateSelectionState(Graph2D graph) {
313           firePotentialChange();
314         }
315       };
316       view.getGraph2D().addGraphListener(sto);
317       view.getGraph2D().addGraph2DSelectionListener(sto);
318 
319       view.getGraph2D().addGraph2DListener(new Graph2DListener() {
320         public void onGraph2DEvent(Graph2DEvent e) {
321           firePotentialChange();
322         }
323       });      
324     }
325 
326     
327     public void firePotentialChange() {
328       if(!isUpdating()) {
329         if(timer == null) {
330           timer = new Timer(100, new ActionListener() {
331             
332             public void actionPerformed(ActionEvent e) {
333               updateGraphMLText(view.getGraph2D());
334             }
335           });
336           timer.setRepeats(false);
337         }
338         timer.restart();
339       }
340     }
341     
342     private void setUpdating(boolean updating) {
343       this.updating = updating;
344     }
345 
346     private boolean isUpdating() {
347       return updating;
348     }
349 
350     public void updateGraphMLText(Graph2D graph) {
351       // show graphml text
352       showGraphMLText(view.getGraph2D());
353 
354       // scroll to selected node/edge in graphml text
355       if (!Selections.isNodeSelectionEmpty(graph)) {
356         scrollGraphMLTextTo(view.getGraph2D().selectedNodes().node());
357       } else if (!Selections.isEdgeSelectionEmpty(graph)) {
358         scrollGraphMLTextTo(view.getGraph2D().selectedEdges().edge());
359       }
360     }
361 
362     private void scrollGraphMLTextTo(Node node) {
363       if (elementIdMap != null && elementIdMap.get(node) instanceof String) {
364         scrollGraphMLTextTo("node", elementIdMap.get(node).toString());
365       }
366     }
367 
368     private void scrollGraphMLTextTo(Edge edge) {
369       if (elementIdMap != null && elementIdMap.get(edge) instanceof String) {
370         scrollGraphMLTextTo("edge", elementIdMap.get(edge).toString());
371       }
372     }
373 
374     private void scrollGraphMLTextTo(String tag, String elementId) {
375       String text = graphMLTextPane.getText();
376       Pattern pattern = Pattern.compile("<" + tag + " .*id=\"" + elementId + "\"");
377       int startIndex = 0;
378       Matcher matcher = pattern.matcher(text);
379       if (matcher.find()) {
380         startIndex = matcher.start();
381       }
382       int endIndex = text.indexOf("</" + tag + ">", startIndex) + (tag.length() + 3);
383 
384       DefaultHighlighter highlighter = new DefaultHighlighter();
385       DefaultHighlightPainter painter = new DefaultHighlightPainter(DemoDefaults.DEFAULT_CONTRAST_COLOR);
386       graphMLTextPane.setHighlighter(highlighter);
387       try {
388         highlighter.addHighlight(startIndex, endIndex, painter);
389         lastStartIndex = startIndex;
390       } catch (BadLocationException e1) {
391         lastStartIndex = 0;
392         e1.printStackTrace();
393       }
394 
395       graphMLTextPane.requestFocus();
396       graphMLTextPane.setCaretPosition(startIndex);
397       graphMLTextPane.moveCaretPosition(endIndex);
398       view.getCanvasComponent().requestFocus();
399 
400       try {
401         graphMLTextPane.scrollRectToVisible(graphMLTextPane.modelToView(startIndex));
402       } catch (BadLocationException e) {
403         e.printStackTrace();
404       }
405 
406     }
407 
408     private String createGraphMLTextAndUpdateGraphElementIdMap(final Graph2D graph) {
409       StringWriter buffer = new StringWriter();
410       if (graph != null) {
411         GraphMLIOHandler ioh = createGraphMLIOHandler();
412         ioh.getGraphMLHandler().addWriteEventListener(
413             new WriteEventListenerAdapter() {
414               public void onDocumentWritten(WriteEvent event)
415                   throws GraphMLWriteException {
416                 GraphElementIdProvider idProvider = (GraphElementIdProvider) event
417                     .getContext().lookup(GraphElementIdProvider.class);
418                 elementIdMap = new HashMap();
419                 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
420                   Node n = nc.node();
421                   elementIdMap.put(n, idProvider.getNodeId(n, event
422                       .getContext()));
423                 }
424                 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
425                   Edge e = ec.edge();
426                   elementIdMap.put(e, idProvider.getEdgeId(e, event
427                       .getContext()));
428                 }
429               }
430             });
431         try {
432           ioh.write(graph, buffer);
433         }catch(IOException ex) {
434          ex.printStackTrace();
435          return "";
436         }
437       }
438       buffer.flush();
439       return buffer.toString();
440     }
441     
442     /**
443      * Helper method that serializes the current graph content into a string which is shown in the graphml text pane.
444      */
445     protected void showGraphMLText(final Graph2D graph) {
446       if (!isUpdating()) {
447         setUpdating(true);
448         try {
449           graphMLTextPane.setText(createGraphMLTextAndUpdateGraphElementIdMap(graph));
450           graphMLTextPane.setCaretPosition(Math.min(lastStartIndex, graphMLTextPane.getText().length()));
451         }finally {
452           setUpdating(false);
453         }       
454       }
455     }
456 
457     /**
458      * Helper method that applies the text content of the graphml text pane to the current graph.
459      */
460     protected void applyGraphMLText(final Graph2D graph) {
461       if (graph != null) {
462 
463         Graph2D testGraph = (Graph2D) graph.createGraph();
464         new HierarchyManager(testGraph);
465         try {
466           byte[] input = graphMLTextPane.getText().getBytes("UTF-8");
467           
468           {
469             ByteArrayInputStream byteStream = new ByteArrayInputStream(input);
470             createGraphMLIOHandler().read(testGraph, byteStream);
471           }
472 
473           //seems to work well. try it with original graph then.          
474           {
475             setUpdating(true);
476             ByteArrayInputStream byteStream = new ByteArrayInputStream(input);
477             GraphMLIOHandler ioh = createGraphMLIOHandler();                                              
478             ioh.read(graph, byteStream);
479             
480             createGraphMLTextAndUpdateGraphElementIdMap(graph); 
481             
482             setUpdating(false);            
483            
484           }
485         } catch (Exception e) {
486           D.show(e);
487         }
488         finally {
489           graph.updateViews(); 
490         }
491       }
492     }
493 
494   }
495 }
496