/* Copyright or (C) or Copr. GET / ENST, Telecom-Paris, Ludovic Apvrille * * ludovic.apvrille AT enst.fr * * This software is a computer program whose purpose is to allow the * edition of TURTLE analysis, design and deployment diagrams, to * allow the generation of RT-LOTOS or Java code from this diagram, * and at last to allow the analysis of formal validation traces * obtained from external tools, e.g. RTL from LAAS-CNRS and CADP * from INRIA Rhone-Alpes. * * This software is governed by the CeCILL license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/ or redistribute the software under the terms of the CeCILL * license as circulated by CEA, CNRS and INRIA at the following URL * "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided only * with a limited warranty and the software's author, the holder of the * economic rights, and the successive licensors have only limited * liability. * * In this respect, the user's attention is drawn to the risks associated * with loading, using, modifying and/or developing or reproducing the * software by the user in light of its specific status of free software, * that may mean that it is complicated to manipulate, and that also * therefore means that it is reserved for developers and experienced * professionals having in-depth computer knowledge. Users are therefore * encouraged to load and test the software's suitability as regards their * requirements in conditions enabling the security of their systems and/or * data to be ensured and, more generally, to use and operate it in the * same conditions as regards security. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL license and that you accept its terms. */ package graph; import common.ConfigurationTTool; import common.SpecConfigTTool; import myutil.TraceManager; import org.graphstream.graph.Edge; import org.graphstream.graph.Node; import org.graphstream.graph.implementations.AbstractEdge; import org.graphstream.graph.implementations.MultiGraph; import org.graphstream.graph.implementations.MultiNode; import org.graphstream.ui.swing.SwingGraphRenderer; import org.graphstream.ui.swing_viewer.DefaultView; import org.graphstream.ui.swing_viewer.SwingViewer; import org.graphstream.ui.swing_viewer.ViewPanel; import org.graphstream.ui.view.Viewer; import org.graphstream.ui.view.ViewerListener; import org.graphstream.ui.view.ViewerPipe; import org.graphstream.ui.view.camera.Camera; import ui.file.PNGFilter; import ui.util.IconManager; import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.logging.Logger; /** * Class AUTGraphDisplay * Creation : 01/12/2016 * * @version 1.0 01/12/2016 * * @author Ludovic APVRILLE */ public class AUTGraphDisplay implements MouseListener, ViewerListener, Runnable { protected AUTGraph graph; protected SwingViewer viewer; protected MultiGraph vGraph; protected boolean loop; protected Node firstNode; protected ArrayList<Edge> edges; protected boolean exitOnClose = false; protected BasicFrame bf; protected String USED_STYLE_SHEET = ""; // see http://graphstream-project.org/doc/Advanced-Concepts/GraphStream-CSS-Reference/ protected static String STYLE_SHEET = "node {fill-color: #B1CAF1; text-color: black; size: 20px, 20px; text-size:14;} " + "edge {text-color: black; shape: cubic-curve; text-size:10;} " + "edge.defaultedge {text-size:10; text-color:black;} " + "edge.external {text-color:blue; text-size:14;} " + "node.deadlock {fill-color: red; text-color: white; size: 20px, 20px; text-size:16;} " + "node.init { fill-color: green; text-color: black; size: 20px, 20px; text-size:16;}"; protected static String STYLE_SHEET2 = "graph { canvas-color: white; fill-mode: gradient-vertical; fill-color: white, #004; padding: 20px; } " + "node { shape: circle; size-mode: dyn-size; size: 10px; fill-mode: gradient-radial; fill-color: #FFFC, #FFF0; stroke-mode: none; " + "shadow-mode: gradient-radial; shadow-color: #FFF5, #FFF0; shadow-width: 5px; shadow-offset: 0px, 0px; } " + "node: clicked { fill-color: #F00A, #F000; } node: selected { fill-color: #00FA, #00F0; } " + "edge { shape: angle; size: 1px; fill-color: red; fill-mode: plain; arrow-shape: circle; } " + "edge.defaultedge { shape: curve-cubic; size: 1px; fill-color: #FFF3; fill-mode: plain; arrow-shape: none; } " + "edge.external { shape: L-square-line; size: 3px; fill-color: #AAA3; fill-mode: plain; arrow-shape: circle; } " + "sprite { shape: circle; fill-mode: gradient-radial; fill-color: #FFF8, #FFF0; }"; /*public static String STYLE_SHEET = "node {" + " fill-color: #B1CAF1; text-color: black; size: 11px, 11px;" + "} " + "edge.default edge {" + "text-color: blue;" + " shape: cubic-curve; }" + "edge.external {" + //" arrow-shape: circle" + " text-style: bold;" + "} " + "node.deadlock {" + " fill-color: red; text-color: white; size: 15px, 15px;" + "} " + "node.init {" + " fill-color: green; text-color: black; size: 15px, 15px;" + "} ";*/ public AUTGraphDisplay(AUTGraph _graph, boolean _exitOnClose) { graph = _graph; exitOnClose = _exitOnClose; } public void display() { Node node; Edge edge; Logger l0 = Logger.getLogger(""); try { if (l0 != null) { l0.removeHandler(l0.getHandlers()[0]); } } catch (Exception e) { } System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer"); vGraph = new MultiGraph("TTool graph"); if ((ConfigurationTTool.RGStyleSheet != null) && (ConfigurationTTool.RGStyleSheet.trim().length() > 0)) { TraceManager.addDev("Adding stylesheet:" + ConfigurationTTool.RGStyleSheet + "\n\nvs default:" + STYLE_SHEET); vGraph.setAttribute("ui.stylesheet", ConfigurationTTool.RGStyleSheet); USED_STYLE_SHEET = ConfigurationTTool.RGStyleSheet; } else { vGraph.setAttribute("ui.stylesheet", STYLE_SHEET); USED_STYLE_SHEET = STYLE_SHEET; } //vGraph.setAttribute("ui.stylesheet", STYLE_SHEET); //vGraph.addAttribute("layout.weight", 0.5); int cpt = 0; graph.computeStates(); for (AUTState state : graph.getStates()) { node = vGraph.addNode("" + state.id); if (state.info == null) { node.setAttribute("ui.label", "" + state.id); } else { node.setAttribute("ui.label", "" + state.id + "/" + state.info ); } if (state.getNbOutTransitions() == 0) { node.setAttribute("ui.class", "deadlock"); } if ((cpt ==0) || (state.isOrigin)) { node.setAttribute("ui.class", "init"); firstNode = node; } cpt++; } cpt = 0; //TraceManager.addDev("Here we are!"); edges = new ArrayList<Edge>(graph.getTransitions().size()); HashSet<AUTTransition> transitionsMet = new HashSet<>(); for (AUTTransition transition : graph.getTransitions()) { edge = vGraph.addEdge("" + cpt, "" + transition.origin, "" + transition.destination, true); /*TraceManager.addDev("Transition=" + transition.transition); String tmp = Conversion.replaceAllChar(transition.transition, '(', "$"); tmp = Conversion.replaceAllChar(tmp, ')', "$"); TraceManager.addDev("Transition=" + tmp);*/ edge.setAttribute("ui.label", graph.removeOTime(graph.removeSameSignal(graph.getCompoundString(transition, transitionsMet)))); //edge.setAttribute("ui.style", "text-offset: -50, -50;"); //edge.addAttribute("ui.class", "edge"); //edge.addAttribute("shape", "cubic-curve"); //edge.addAttribute("arrow-shape", "circle"); edge.setAttribute("layout.weight", 0.4); if (!(transition.transition.startsWith("i("))) { edge.setAttribute("ui.class", "external"); } else { edge.setAttribute("ui.class", "defaultedge"); } edges.add(edge); cpt++; } //viewer = vGraph.display(); //viewer = new Viewer(vGraph, Viewer.ThreadingModel.GRAPH_IN_SWING_THREAD); viewer = new SwingViewer(vGraph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD); //SwingUtilities.invokeLater(new InitializeApplication(viewer, vGraph)); viewer.enableAutoLayout(); //View vi = viewer.addDefaultView(true); viewer.setCloseFramePolicy(Viewer.CloseFramePolicy.CLOSE_VIEWER); bf = new BasicFrame(this, viewer, vGraph, graph, edges, exitOnClose); //vi.addMouseListener(this); loop = true; Thread t = new Thread(this); t.start(); } public void sleep(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } public void run() { /*ViewerPipe fromViewer = viewer.newViewerPipe(); fromViewer.addViewerListener(this); fromViewer.addSink(vGraph);*/ // Then we need a loop to do our work and to wait for events. // In this loop we will need to call the // pump() method before each use of the graph to copy back events // that have already occurred in the viewer thread inside // our thread int cpt = 0; ViewerPipe pipeIn = viewer.newViewerPipe(); pipeIn.addSink(vGraph); pipeIn.addViewerListener(this); while (loop) { try { pipeIn.blockingPump(50); } catch (Exception e) { } if (vGraph.hasAttribute("ui.viewClosed")) { //TraceManager.addDev("View was closed"); loop = false; if (exitOnClose) { System.exit(1); } } bf.updateMe(); } //TraceManager.addDev("Starting loop:" + cpt); /*while (loop) { try { //TraceManager.addDev("beg of loop:" + cpt); fromViewer.blockingPump(); // or fromViewer.blockingPump(); in the nightly builds } catch (Exception e) {//TraceManager.addDev("Exception in pump:" + e); } if (vGraph.hasAttribute("ui.viewClosed")) { //TraceManager.addDev("View was closed"); loop = false; if (exitOnClose) { System.exit(1); } } */ // here your simulation code. //TraceManager.addDev("End of loop" + cpt); //cpt ++; // You do not necessarily need to use a loop, this is only an example. // as long as you call pump() before using the graph. pump() is non // blocking. If you only use the loop to look at event, use blockingPump() // to avoid 100% CPU usage. The blockingPump() method is only available from // the nightly builds. } public void buttonPushed(String id) { TraceManager.addDev("Button pushed on node " + id); } public void buttonReleased(String id) { TraceManager.addDev("Button released on node " + id); } public void viewClosed(String id) { //TraceManager.addDev("View closed and closed !"); loop = false; if (viewer != null) { viewer.close(); viewer.disableAutoLayout(); } viewer = null; vGraph.clear(); if (exitOnClose) { System.exit(1); } } public void mousePressed(MouseEvent e) { TraceManager.addDev("Mouse pressed; # of clicks: " + e.getClickCount()); } public void mouseReleased(MouseEvent e) { TraceManager.addDev("Mouse released; # of clicks: " + e.getClickCount()); } public void mouseEntered(MouseEvent e) { TraceManager.addDev("Mouse entered"); } public void mouseExited(MouseEvent e) { TraceManager.addDev("Mouse exited"); } public void mouseClicked(MouseEvent e) { TraceManager.addDev("Mouse clicked (# of clicks: " + e.getClickCount() + ")"); } public void mouseOver(java.lang.String id) { TraceManager.addDev("Mouse over: " + id); } public void mouseLeft(java.lang.String id) { TraceManager.addDev("Mouse over: " + id); } class InitializeApplication extends JFrame implements Runnable { private static final long serialVersionUID = -804177406404724792L; protected MultiGraph vGraph; protected SwingViewer viewer; protected AUTGraph graph; protected Node firstNode; public InitializeApplication(SwingViewer viewer, MultiGraph vGraph, AUTGraph autgraph) { this.viewer = viewer; this.vGraph = vGraph; this.graph = autgraph; } public void run() { int cpt = 0; Node node; Edge edge; for (AUTState state : graph.getStates()) { node = vGraph.addNode("" + state.id); node.setAttribute("ui.label", "" + state.id); if (state.getNbOutTransitions() == 0) { node.setAttribute("ui.class", "deadlock"); } if (cpt == 0) { node.setAttribute("ui.class", "init"); firstNode = node; } cpt++; } cpt = 0; // We must merge the transitions with the same starting and ending state HashSet<AUTTransition> transitionsMet = new HashSet<>(); for (AUTTransition transition : graph.getTransitions()) { edge = vGraph.addEdge("" + cpt, "" + transition.origin, "" + transition.destination, true); transitionsMet.add(transition); edge = vGraph.addEdge("" + cpt, "" + transition.origin, "" + transition.destination, true); /*TraceManager.addDev("Transition=" + transition.transition); String tmp = Conversion.replaceAllChar(transition.transition, '(', "$"); tmp = Conversion.replaceAllChar(tmp, ')', "$"); TraceManager.addDev("Transition=" + tmp);*/ edge.setAttribute("ui.label", transition.transition); edge.setAttribute("ui.class", "classic"); if (!(transition.transition.startsWith("i("))) { edge.setAttribute("ui.class", "external"); } cpt++; } viewer.enableAutoLayout(); DefaultView dv = new DefaultView(viewer, "Graph", new SwingGraphRenderer()); //ViewPanel vp = viewer.addDefaultView(true, new SwingGraphRenderer()); add(dv, BorderLayout.CENTER); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(800, 600); setVisible(true); } } class BasicFrame extends JFrame implements ActionListener { protected MultiGraph vGraph; protected SwingViewer viewer; protected JPanel viewerPanel; protected AUTGraph graph; protected ArrayList<Edge> edges; protected JButton close; protected JButton screenshot; protected JButton fontPlus, fontMinus; protected JCheckBox internalActions; protected JCheckBox readActions; protected JCheckBox higherQuality, antialiasing; protected JLabel help, info; protected boolean exitOnClose; private AUTGraphDisplay autD; private DefaultView dv; public BasicFrame(AUTGraphDisplay autD, SwingViewer viewer, MultiGraph vGraph, AUTGraph autgraph, ArrayList<Edge> _edges, boolean _exitOnClose) { this.autD = autD; this.viewer = viewer; this.vGraph = vGraph; this.graph = autgraph; edges = _edges; exitOnClose = _exitOnClose; makeComponents(); if (exitOnClose) { setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } } public void makeComponents() { dv = new DefaultView(viewer, "Graph", new SwingGraphRenderer()); //viewerPanel = viewer.addDefaultView(false); add(dv, BorderLayout.CENTER); //add(viewer, BorderLayout.CENTER ); close = new JButton("Close", IconManager.imgic27); close.addActionListener(this); screenshot = new JButton("Save in png", IconManager.imgic28); screenshot.addActionListener(this); fontPlus = new JButton("F+", IconManager.imgic28); fontPlus.addActionListener(this); fontMinus = new JButton("F-", IconManager.imgic28); fontMinus.addActionListener(this); close.addActionListener(this); help = new JLabel("Zoom with PageUp/PageDown, move with cursor keys"); info = new JLabel("Graph: " + graph.getNbOfStates() + " states, " + graph.getNbOfTransitions() + " transitions"); internalActions = new JCheckBox("Display internal actions", true); internalActions.addActionListener(this); readActions = new JCheckBox("Display read/write actions", true); readActions.addActionListener(this); higherQuality = new JCheckBox("Higher drawing quality", false); higherQuality.addActionListener(this); antialiasing = new JCheckBox("Anti aliasing", false); antialiasing.addActionListener(this); JPanel jp01 = new JPanel(); GridBagLayout gridbag01 = new GridBagLayout(); GridBagConstraints c01 = new GridBagConstraints(); jp01.setLayout(gridbag01); jp01.setBorder(new javax.swing.border.TitledBorder("Options")); //c01.gridwidth = 1; c01.gridheight = 1; c01.weighty = 1.0; c01.weightx = 1.0; c01.fill = GridBagConstraints.HORIZONTAL; c01.gridwidth = GridBagConstraints.REMAINDER; //end row jp01.add(screenshot); jp01.add(internalActions); jp01.add(readActions); jp01.add(higherQuality); jp01.add(antialiasing); jp01.add(fontMinus); jp01.add(fontPlus); JPanel infoPanel = new JPanel(new BorderLayout()); JPanel labelPanel = new JPanel(new BorderLayout()); labelPanel.add(help, BorderLayout.EAST); labelPanel.add(info, BorderLayout.WEST); infoPanel.add(labelPanel, BorderLayout.NORTH); infoPanel.add(close, BorderLayout.SOUTH); infoPanel.add(jp01, BorderLayout.CENTER); add(infoPanel, BorderLayout.SOUTH); //setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(1000, 700); setVisible(true); } public void actionPerformed(ActionEvent evt) { if (evt.getSource() == close) { closeFrame(); } else if (evt.getSource() == screenshot) { screenshot(); } else if (evt.getSource() == internalActions) { manageInternalActions(); } else if (evt.getSource() == readActions) { manageReadActions(); } else if (evt.getSource() == higherQuality) { manageHigherQuality(); } else if (evt.getSource() == antialiasing) { manageAntialiasing(); } else if (evt.getSource() == fontPlus) { fontPlus(); } else if (evt.getSource() == fontMinus) { fontMinus(); } } public void closeFrame() { if (autD != null) { autD.viewClosed("closed pushed"); } if (exitOnClose) { System.exit(1); } dispose(); } public void takeScreenshot(Component component, File file) { BufferedImage image = new BufferedImage( component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB ); // call the Component's paint method, using // the Graphics object of the image. component.paint(image.getGraphics()); // alternately use .printAll(..) try { // save captured image to PNG file ImageIO.write(image, "png", file); } catch (Exception e) { } } public void fontPlus() { setTextSize(1); /*if (edges == null) { return; } int cpt = 0; for (AUTTransition transition : graph.getTransitions()) { TraceManager.addDev("Attribute of edge:" + edges.get(cpt).getAttribute("text-size")); cpt++; }*/ } public void fontMinus() { setTextSize(-1); } private void setTextSize(int modifier) { String style = USED_STYLE_SHEET; if ((style == null) || (style.length() == 0)) { return; } // default.edge //TraceManager.addDev("Old style: " + style); style = modifyTextSize(modifier, style, "edge.defaultedge"); //TraceManager.addDev("New style default: " + style); // external style = modifyTextSize(modifier, style, "edge.external"); //TraceManager.addDev("New style external: " + style); vGraph.setAttribute("ui.stylesheet", style); USED_STYLE_SHEET = style; } public String modifyTextSize(int modifier, String input, String key) { String ret = input; int indexD = input.indexOf(key); if (indexD != -1) { String before = input.substring(0, indexD); String tmp = input.substring(indexD); int indexBB = tmp.indexOf("{"); int indexBE = tmp.indexOf("}"); int indexT = tmp.indexOf("text-size:"); if ((indexT > -1) && (indexBB >-1) && (indexBE > -1) && (indexT>indexBB) && (indexT < indexBE)) { String bef = tmp.substring(0, indexT+10); String textSize = tmp.substring(indexT+10); int indexVirg = textSize.indexOf(";"); if (indexVirg > -1) { String textValue = textSize.substring(0, indexVirg); String endf = textSize.substring(indexVirg); int val = Integer.decode(textValue); val += modifier; if (val < 1) { val = 1; } //TraceManager.addDev("Value=" + val); // Rebuild the string ret = before + bef + val + endf; } } } return ret; } public void screenshot() { TraceManager.addDev("Screenshot"); JFileChooser jfcggraph; if (SpecConfigTTool.GGraphPath.length() > 0) { jfcggraph = new JFileChooser(SpecConfigTTool.GGraphPath); } else { jfcggraph = new JFileChooser(); } PNGFilter filter = new PNGFilter(); jfcggraph.setFileFilter(filter); int returnVal = jfcggraph.showDialog(this, "Graph capture (in png)"); if (returnVal != JFileChooser.APPROVE_OPTION) { return; } File pngFile = jfcggraph.getSelectedFile(); TraceManager.addDev("Making the screenshot in " + pngFile.getAbsolutePath()); //vGraph.addAttribute("ui.screenshot", pngFile.getAbsolutePath()); //vGraph.addAttribute("ui.screenshot", "/homes/apvrille/tmp/toto.png"); takeScreenshot(dv, pngFile); /*FileSinkImages pic = new FileSinkImages(OutputType.PNG, Resolutions.UXGA); //pic.setQuality(); //pic.setLayoutPolicy(LayoutPolicy.COMPUTED_FULLY_AT_NEW_IMAGE); pic.setLayoutPolicy(LayoutPolicy.COMPUTED_IN_LAYOUT_RUNNER); try { pic.writeAll(vGraph, pngFile.getAbsolutePath()); } catch (IOException e) { TraceManager.addDev("Capture could not be performed: " + e.getMessage()); }*/ //vGraph.addAttribute("ui.screenshot", "/tmp/toto.png"); TraceManager.addDev("Screenshot performed"); } public synchronized void manageInternalActions() { if (edges == null) { return; } int cpt = 0; for (AUTTransition transition : graph.getTransitions()) { if (transition.transition.startsWith("i(")) { if (internalActions.isSelected()) { edges.get(cpt).setAttribute("ui.label", transition.transition); } else { edges.get(cpt).setAttribute("ui.label", ""); } } cpt++; } //dv.repaint(); } public synchronized void manageReadActions() { if (edges == null) { return; } int cpt = 0; for (AUTTransition transition : graph.getTransitions()) { if (transition.transition.contains("?")) { if (readActions.isSelected()) { edges.get(cpt).setAttribute("ui.label", transition.transition); } else { edges.get(cpt).setAttribute("ui.label", ""); } } cpt++; } //dv.repaint(); } public synchronized void manageHigherQuality() { //viewer.disableAutoLayout(); if (higherQuality.isSelected()) { vGraph.setAttribute("ui.quality"); } else { vGraph.removeAttribute("ui.quality"); } try { //viewer.enableAutoLayout(); //dv.repaint(); } catch (Exception e) { } } /*public void updateCameraView() { Camera camera = controller.getPanel().getCamera(); cameraAutoFitView.setSelected(autoFit); viewCenterX.setText(camera.getViewCenter().x+""); viewCenterY.setText(camera.getViewCenter().y+""); scale.setText(camera.getViewPercent()+""); }*/ public synchronized void manageAntialiasing() { //viewer.disableAutoLayout(); if (antialiasing.isSelected()) { vGraph.setAttribute("ui.antialias"); } else { vGraph.removeAttribute("ui.antialias"); } try { //viewer.enableAutoLayout(); //dv.repaint(); } catch (Exception e) { } } public synchronized void updateMe() { dv.repaint(); } } // Basic Frame } // Main class