View Javadoc
1   package com.github.davidmoten.xuml;
2   
3   import java.awt.Color;
4   import java.awt.Dimension;
5   import java.awt.EventQueue;
6   import java.awt.Graphics;
7   import java.awt.GridLayout;
8   import java.awt.Shape;
9   import java.awt.print.PrinterException;
10  import java.io.File;
11  import java.io.IOException;
12  import java.io.InputStream;
13  import java.util.List;
14  
15  import javax.swing.JFileChooser;
16  import javax.swing.JFrame;
17  import javax.swing.JMenu;
18  import javax.swing.JMenuBar;
19  import javax.swing.JMenuItem;
20  import javax.swing.JPanel;
21  import javax.swing.filechooser.FileFilter;
22  
23  import java.awt.Rectangle;
24  
25  import com.google.common.base.Function;
26  
27  import edu.uci.ics.jung.algorithms.layout.FRLayout;
28  import edu.uci.ics.jung.algorithms.layout.Layout;
29  import edu.uci.ics.jung.graph.DirectedSparseGraph;
30  import edu.uci.ics.jung.graph.Graph;
31  import edu.uci.ics.jung.visualization.VisualizationViewer;
32  import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
33  import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
34  import edu.uci.ics.jung.visualization.decorators.EdgeShape;
35  import edu.uci.ics.jung.visualization.renderers.Renderer;
36  import xuml.tools.miuml.metamodel.jaxb.Class;
37  import xuml.tools.miuml.metamodel.jaxb.ModeledDomain;
38  import xuml.tools.model.compiler.Util;
39  
40  public class StateDiagramViewer {
41  
42      private final JFrame frame = new JFrame();
43      private final JPanel vvContainer = createVvContainer();
44      private volatile JMenu classMenu;
45  
46      private final static ThreadLocal<DrawingState> state = new ThreadLocal<>();
47  
48      private void open(ModeledDomain domain) {
49          if (classMenu == null)
50              throw new RuntimeException("must call start() before calling open(domain)");
51          classMenu.removeAll();
52          List<Class> classes = Util.getClasses(domain);
53          classes.stream().forEach(cls -> {
54              JMenuItem m = new JMenuItem(cls.getName());
55              m.setEnabled(cls.getLifecycle() != null);
56              m.addActionListener(evt -> show(cls));
57              classMenu.add(m);
58          });
59          // show the first class with a lifecyle
60          classes.stream().filter(cls -> cls.getLifecycle() != null).limit(1)
61                  .forEach(cls -> show(cls));
62      }
63  
64      public void show(Class c) {
65          System.out.println("showing " + c.getName());
66          Graph<String, Edge> g = new DirectedSparseGraph<String, Edge>();
67          if (c.getLifecycle() != null) {
68              c.getLifecycle().getState().stream().forEach(state -> g.addVertex(state.getName()));
69              c.getLifecycle().getTransition().stream().forEach(transition -> {
70                  String fromState = transition.getState();
71                  String toState = transition.getDestination();
72                  String eventName = c.getLifecycle().getEvent().stream().map(ev -> ev.getValue())
73                          .filter(event -> event.getID().equals(transition.getEventID()))
74                          .map(event -> event.getName()).findAny().get();
75                  g.addEdge(new Edge(eventName), fromState, toState);
76              });
77          }
78          if (!g.getVertices().isEmpty()) {
79              VisualizationViewer<String, Edge> vv = createVisualizationViewer(g);
80              EventQueue.invokeLater(() -> {
81                  vvContainer.removeAll();
82                  vvContainer.add(vv);
83                  vvContainer.setFocusable(true);
84                  vvContainer.repaint();
85                  vvContainer.revalidate();
86                  System.out.println("added vv");
87                  frame.setTitle(c.getName());
88              });
89          }
90      }
91  
92      private void start() {
93          EventQueue.invokeLater(() -> {
94              // Creates a menubar for a JFrame
95              JMenuBar menuBar = new JMenuBar();
96              // Add the menubar to the frame
97              frame.setJMenuBar(menuBar);
98              // Define and add two drop down menu to the menubar
99              JMenu fileMenu = new JMenu("File");
100             classMenu = new JMenu("Class");
101             JMenu editMenu = new JMenu("Edit");
102             menuBar.add(fileMenu);
103             menuBar.add(editMenu);
104             menuBar.add(classMenu);
105             JMenuItem openMenuItem = new JMenuItem("Open");
106             JMenuItem printMenuItem = new JMenuItem("Print...");
107             JMenuItem saveAsImageMenuItem = new JMenuItem("Save as PNG...");
108             JMenuItem exitMenuItem = new JMenuItem("Exit");
109             fileMenu.add(openMenuItem);
110             fileMenu.add(printMenuItem);
111             fileMenu.add(saveAsImageMenuItem);
112             fileMenu.add(exitMenuItem);
113             openMenuItem.addActionListener(System.out::println);
114             exitMenuItem.addActionListener(e -> System.exit(0));
115             printMenuItem.addActionListener(e -> print());
116             saveAsImageMenuItem.addActionListener(e -> saveAsImage());
117 
118             frame.getContentPane().add(vvContainer);
119             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
120             frame.setSize(new Dimension(1000, 800));
121             frame.setLocationRelativeTo(null);
122             frame.setVisible(true);
123         });
124 
125     }
126 
127     private void saveAsImage() {
128         JFileChooser fc = new JFileChooser();
129         fc.addChoosableFileFilter(new ImageFilter());
130         int returnVal = fc.showSaveDialog(vvContainer);
131         if (returnVal == JFileChooser.APPROVE_OPTION) {
132             File file = fc.getSelectedFile();
133             if (!file.getName().toUpperCase().endsWith(".PNG"))
134                 file = new File(file.getAbsolutePath() + ".png");
135             Color bg = vvContainer.getBackground();
136             try {
137                 vvContainer.setPreferredSize(vvContainer.getSize());
138                 vvContainer.setBackground(Color.white);
139                 Panels.saveImage(vvContainer, file);
140             } catch (RuntimeException e1) {
141                 e1.printStackTrace();
142             } finally {
143                 vvContainer.setBackground(bg);
144             }
145         }
146     }
147 
148     private void print() {
149         Color bg = vvContainer.getBackground();
150         try {
151             vvContainer.setPreferredSize(vvContainer.getSize());
152             vvContainer.setBackground(Color.white);
153             Panels.print(vvContainer);
154         } catch (PrinterException e1) {
155             e1.printStackTrace();
156         } finally {
157             vvContainer.setBackground(bg);
158         }
159     }
160 
161     private static JPanel createVvContainer() {
162         JPanel panel = new JPanel();
163         panel.setPreferredSize(new Dimension(800, 600));
164         // panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
165         panel.setLayout(new GridLayout(0, 1));
166         return panel;
167     }
168 
169     public static final class Edge {
170         final String name;
171 
172         Edge(String name) {
173             this.name = name;
174         }
175 
176         public static Edge of(String name) {
177             return new Edge(name);
178         }
179     }
180 
181     private static class DrawingState {
182 
183     }
184 
185     private static VisualizationViewer<String, Edge> createVisualizationViewer(
186             Graph<String, Edge> graph) {
187         FRLayout<String, Edge> layout = new FRLayout<String, Edge>(graph, new Dimension(800, 600));
188         while (!layout.done())
189             layout.step();
190 
191         VisualizationViewer<String, Edge> vv = new VisualizationViewer<String, Edge>(layout,
192                 new Dimension(800, 600)) {
193 
194             private static final long serialVersionUID = -3546358007558213875L;
195 
196             @Override
197             public void paintComponents(Graphics g) {
198                 try {
199                     state.set(new DrawingState());
200                     super.paintComponents(g);
201                 } finally {
202                     state.remove();
203                 }
204             }
205 
206         };
207         vv.setBackground(Color.white);
208         vv.getRenderContext().setVertexLabelTransformer(s -> s);
209         vv.getRenderContext().setVertexFillPaintTransformer(vertex -> vertex.equals("Created")
210                 ? Color.decode("#B5D9E6") : Color.decode("#FFF1BC"));
211         vv.getRenderContext().setVertexDrawPaintTransformer(vertex -> Color.black);
212         vv.getRenderContext().setVertexShapeTransformer(createVertexShapeTransformer(layout));
213         vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
214         vv.getRenderContext().setEdgeLabelTransformer(edge -> " " + edge.name + " ");
215         vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph));
216         vv.getRenderer().setEdgeLabelRenderer(new MyEdgeLabelRenderer<>(-10, Color.white));
217 
218         // The following code adds capability for mouse picking of
219         // vertices/edges. Vertices can even be moved!
220         final DefaultModalGraphMouse<String, Number> graphMouse = new DefaultModalGraphMouse<String, Number>();
221         vv.setGraphMouse(graphMouse);
222         graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
223         return vv;
224     }
225 
226     private static Function<String, Shape> createVertexShapeTransformer(
227             Layout<String, Edge> layout) {
228         return vertex -> {
229             int w = 150;
230             int margin = 5;
231             int ascent = 12;
232             int descent = 5;
233             Shape shape = new Rectangle(-w / 2 - margin, -ascent - margin, w + 2 * margin,
234                     (ascent + descent) + 2 * margin);
235             return shape;
236         };
237     }
238 
239     private static class ImageFilter extends FileFilter {
240 
241         // Accept all directories and all gif, jpg, tiff, or png files.
242         @Override
243         public boolean accept(File f) {
244             if (f.isDirectory()) {
245                 return true;
246             }
247             return f.getName().toUpperCase().endsWith(".PNG");
248         }
249 
250         // The description of this filter
251         @Override
252         public String getDescription() {
253             return "PNG files";
254         }
255     }
256 
257     public void start(String resource, String domainName) {
258         start(StateDiagramViewer.class.getResourceAsStream(resource), domainName);
259     }
260 
261     public void start(InputStream input, String domainName) {
262         try (InputStream is = input) {
263             start();
264             ModeledDomain domain = Util.getModeledDomain(is, domainName);
265             open(domain);
266         } catch (IOException e) {
267             throw new RuntimeException(e);
268         }
269     }
270 
271 }