001    package nl.cwi.sen1.gui.plugin;
002    
003    import java.awt.FontMetrics;
004    import java.awt.event.ActionEvent;
005    import java.awt.event.MouseEvent;
006    import java.util.HashMap;
007    import java.util.Map;
008    
009    import javax.swing.AbstractAction;
010    import javax.swing.ButtonGroup;
011    import javax.swing.JMenu;
012    import javax.swing.JMenuItem;
013    import javax.swing.JRadioButtonMenuItem;
014    import javax.swing.JSeparator;
015    
016    import nl.cwi.sen1.configapi.types.ActionDescriptionList;
017    import nl.cwi.sen1.configapi.types.Event;
018    import nl.cwi.sen1.graph.Factory;
019    import nl.cwi.sen1.graph.types.Graph;
020    import nl.cwi.sen1.gui.CloseAbortedException;
021    import nl.cwi.sen1.gui.Studio;
022    import nl.cwi.sen1.gui.StudioImplWithPredefinedLayout;
023    import nl.cwi.sen1.gui.StudioWithPredefinedLayout;
024    import nl.cwi.sen1.gui.component.StudioComponent;
025    import nl.cwi.sen1.gui.component.StudioComponentImpl;
026    import nl.cwi.sen1.util.DefaultPopupImpl;
027    import nl.cwi.sen1.util.MouseAdapter;
028    import nl.cwi.sen1.util.Preferences;
029    import prefuse.controls.ControlAdapter;
030    import prefuse.visual.VisualItem;
031    import aterm.ATerm;
032    import aterm.ATermFactory;
033    import aterm.pure.PureFactory;
034    
035    public class GraphPainter extends DefaultStudioPlugin implements
036                    GraphPainterTif {
037            private static final String TOOL_NAME = "graph-painter";
038    
039            private static final String RESOURCE_DIR = "/resources";
040    
041            private Studio studio;
042    
043            private Factory graphFactory;
044    
045            private nl.cwi.sen1.configapi.Factory configFactory;
046    
047            private Map<String, GraphPanel> graphs;
048    
049            private Map<String, StudioComponent> forcePanels;
050    
051            private Preferences preferences;
052    
053            private GraphPainterBridge bridge;
054    
055            public GraphPainter() {
056                    String propertyPath = new String(RESOURCE_DIR + '/' + TOOL_NAME
057                                    + ".properties");
058                    this.preferences = new Preferences(getClass().getResourceAsStream(
059                                    propertyPath));
060                    this.graphs = new HashMap<String, GraphPanel>();
061                    this.forcePanels = new HashMap<String, StudioComponent>();
062            }
063    
064            public void initStudioPlugin(Studio studio) {
065                    this.studio = studio;
066                    graphFactory = Factory.getInstance((PureFactory) studio
067                                    .getATermFactory());
068                    configFactory = nl.cwi.sen1.configapi.Factory
069                                    .getInstance((PureFactory) studio.getATermFactory());
070                    bridge = new GraphPainterBridge(studio.getATermFactory(), this);
071                    bridge.setLockObject(this);
072                    studio.connect(getName(), bridge);
073            }
074    
075            public void displayGraph(String graphType, ATerm graphId, ATerm graphTerm) {
076                    GraphPanel panel = getPanel(graphType, graphId.toString());
077                    if (panel != null) {
078                            Graph graph = graphFactory.GraphFromTerm(graphTerm);
079                            panel.setGraph(new GraphAdapter(graph));
080                    } else {
081                            System.err
082                                            .println("Graph not displayed because it was not created before");
083                    }
084            }
085    
086            public void updateGraph(String graphType, ATerm graphId, ATerm nodeId,
087                            ATerm key, ATerm value) {
088                    GraphPanel panel = getPanel(graphType, graphId.toString());
089                    if (panel != null) {
090                            panel.updateNode(nodeId.toString(), graphFactory
091                                            .AttributeFromTerm(value));
092                    }
093            }
094    
095            private boolean isTrue(ATerm bool) {
096                    return !bool.isEqual(bool.getFactory().parse("false"));
097            }
098    
099            /**
100             * create a panel for displaying a graph. Before sizing, or displaying a
101             * graph a graph panel must be created.
102             * 
103             * @param graphType
104             *            The type of graph shown, for identifying apropriate menu's
105             *            etc.
106             * @param graphId
107             *            The unique id of the graph (modulo graphId)
108             * @param shared
109             *            Reuse a panel if a panel with the same (graphId,graphId)
110             *            exists
111             * @param closable
112             *            Let the user be able to close the panel
113             */
114            public ATerm createPanel(String graphType, ATerm graphId, ATerm shared,
115                            ATerm closable) {
116                    createPanel(graphType, graphId, isTrue(shared), isTrue(closable));
117                    return shared.getFactory().parse("snd-value(panel-created)");
118            }
119    
120            private ATerm createEventId(String graphType, ATerm graphId, String nodeId) {
121                    ATerm termId = studio.getATermFactory().parse(nodeId);
122                    return studio.getATermFactory()
123                                    .make(TOOL_NAME + "(<str>,<term>,<term>)", graphType, graphId,
124                                                    termId);
125            }
126    
127            private void createPanel(final String graphType, final ATerm graphId,
128                            boolean shared, boolean close) {
129                    GraphPanel panel = getPanel(graphType, graphId.toString());
130    
131                    if (!shared && panel != null) {
132                            System.err
133                                            .println("Graph not created because a panel with the same id exists already.");
134                            return;
135                    }
136    
137                    if (panel == null) {
138                            panel = new GraphPanel(graphType, graphId.toString(), preferences,
139                                            close);
140                            final GraphPanel graphPanel = panel;
141                            StudioComponent comp = new StudioComponentImpl(panelKey(graphType,
142                                            graphId.toString()), panel) {
143                                    public void requestClose() throws CloseAbortedException {
144                                            if (!graphPanel.isClosable()) {
145                                                    throw new CloseAbortedException();
146                                            }
147                                            removePanel(graphType, graphId.toString());
148                                            studio.removeComponent(this);
149                                            bridge.postEvent(studio.getATermFactory().make(
150                                                            "panel-closed(<str>,<term>)", graphType, graphId));
151                                    }
152                            };
153    
154                            final Event popup = configFactory.makeEvent_Popup();
155                            final ATerm id = configFactory.getPureFactory().parse(TOOL_NAME);
156                            panel.addControlListener(new ControlAdapter() {
157                                    MouseAdapter ma = new MouseAdapter(id, bridge, popup);
158    
159                                    public void itemPressed(VisualItem item, MouseEvent e) {
160                                            String nodeId = item.getString(GraphConstants.ID);
161    
162                                            if (nodeId != null) {
163                                                    ma.setId(createEventId(graphType, graphId, nodeId));
164                                                    ma.mousePressed(e);
165                                            }
166                                    }
167    
168                                    public void itemReleased(VisualItem item, MouseEvent e) {
169                                            if (e.isPopupTrigger()) {
170                                                    itemPressed(item, e);
171                                            }
172                                    }
173                            });
174    
175                            panel.setGraphPanelListener(new GraphPanelListener() {
176                                    public void nodeSelected(String id) {
177                                            ATerm eventId = createEventId(graphType, graphId, id);
178                                            bridge.postEvent(studio.getATermFactory().make(
179                                                            "mouse-event(<term>,click([],BUTTON1))", eventId));
180                                    }
181                            });
182    
183                            setPanel(graphType, graphId.toString(), panel);
184                            ((StudioWithPredefinedLayout) studio).addComponent(comp,
185                                            StudioImplWithPredefinedLayout.TOP_RIGHT);
186                            studio.addComponentMenu(comp, createGraphMenu(panel, "Dot",
187                                            graphType, graphId.toString()));
188                            panel.setLayout("Dot");
189                    }
190            }
191    
192            private JMenu createGraphMenu(final GraphPanel panel, String initialLayout,
193                            String graphId, String id) {
194                    JMenu graph = new JMenu("Graph");
195                    JMenu layouts = createLayoutMenu(graph, panel, initialLayout);
196                    graph.add(new JSeparator());
197                    JMenu toggles = createTogglesMenu(graph, panel, graphId, id);
198    
199                    graph.add(layouts);
200                    graph.add(toggles);
201    
202                    JMenuItem export = new JMenuItem("Export");
203                    export.addActionListener(panel.getSaveImageAction());
204                    graph.add(export);
205    
206                    JMenuItem reset = new JMenuItem("Reset view");
207                    reset.addActionListener(new AbstractAction() {
208                            public void actionPerformed(ActionEvent e) {
209                                    panel.restoreZoomAndPan();
210                            }
211                    });
212                    graph.add(reset);
213    
214                    return graph;
215            }
216    
217            private JMenu createTogglesMenu(JMenu menu, final GraphPanel panel,
218                            final String graphId, final String id) {
219                    JMenuItem item;
220                    ButtonGroup edge = new ButtonGroup();
221    
222                    item = new JRadioButtonMenuItem("Curved edges");
223                    item.addActionListener(new AbstractAction() {
224                            public void actionPerformed(ActionEvent e) {
225                                    panel.setCurvedEdges();
226                            }
227                    });
228                    edge.add(item);
229                    menu.add(item);
230    
231                    item = new JRadioButtonMenuItem("Straight edges");
232                    item.addActionListener(new AbstractAction() {
233                            public void actionPerformed(ActionEvent e) {
234                                    panel.setStraightEdges();
235                            }
236                    });
237                    item.setSelected(true);
238                    edge.add(item);
239                    menu.add(item);
240    
241                    menu.add(new JSeparator());
242    
243                    ButtonGroup animation = new ButtonGroup();
244                    item = new JRadioButtonMenuItem("Linear animation");
245                    item.addActionListener(new AbstractAction() {
246                            public void actionPerformed(ActionEvent e) {
247                                    panel.setLinearAnimation();
248                            }
249                    });
250                    animation.add(item);
251                    menu.add(item);
252    
253                    item = new JRadioButtonMenuItem("Polar animation");
254                    item.addActionListener(new AbstractAction() {
255                            public void actionPerformed(ActionEvent e) {
256                                    panel.setPolarAnimation();
257                            }
258                    });
259                    animation.add(item);
260                    item.setSelected(true);
261                    menu.add(item);
262    
263                    item = new JRadioButtonMenuItem("No animation");
264                    item.addActionListener(new AbstractAction() {
265                            public void actionPerformed(ActionEvent e) {
266                                    panel.setNoAnimation();
267                            }
268                    });
269                    animation.add(item);
270                    menu.add(item);
271    
272                    menu.add(new JSeparator());
273    
274                    // item = new JCheckBoxMenuItem("Show force panel");
275                    // item.addActionListener(new AbstractAction() {
276                    // public void actionPerformed(ActionEvent e) {
277                    // JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
278                    // if (item.isSelected()) {
279                    // showForcePanel(graphId, id, true);
280                    // } else {
281                    // showForcePanel(graphId, id, false);
282                    // }
283                    // }
284                    // });
285                    // item.setSelected(false);
286                    // menu.add(item);
287    
288                    return menu;
289            }
290    
291            protected void showForcePanel(String graphType, String graphId, boolean show) {
292                    if (show) {
293                            GraphPanel graphPanel = getPanel(graphType, graphId);
294                            GraphForcePanel forcePanel = new GraphForcePanel(graphPanel
295                                            .getForceSimulator(), preferences);
296                            StudioComponent comp = new StudioComponentImpl("Forces for "
297                                            + graphType, forcePanel);
298                            ((StudioWithPredefinedLayout) studio).addComponent(comp,
299                                            StudioImplWithPredefinedLayout.BOTTOM_LEFT);
300                            setForcePanel(graphType, graphId, comp);
301                    } else {
302                            StudioComponent comp = getForcePanel(graphType, graphId);
303                            if (comp != null) {
304                                    studio.removeComponent(comp);
305                                    forcePanels.remove(graphType);
306                            }
307                    }
308            }
309    
310            private StudioComponent getForcePanel(String graphType, String graphId) {
311                    return forcePanels.get(panelKey(graphType, graphId));
312            }
313    
314            private void setForcePanel(String graphType, String graphId,
315                            StudioComponent comp) {
316                    forcePanels.put(panelKey(graphType, graphId), comp);
317            }
318    
319            private String panelKey(String graphType, String graphId) {
320                    return graphType + ":" + graphId;
321            }
322    
323            private JMenu createLayoutMenu(JMenu menu, final GraphPanel panel,
324                            String initial) {
325                    ButtonGroup group = new ButtonGroup();
326    
327                    final Object[] layouts = panel.getLayouts();
328    
329                    for (int i = 0; i < layouts.length; i++) {
330                            final String name = (String) layouts[i];
331                            JRadioButtonMenuItem random = new JRadioButtonMenuItem(name);
332                            random.addActionListener(new AbstractAction() {
333                                    public void actionPerformed(ActionEvent e) {
334                                            panel.setLayout(name);
335                                    }
336                            });
337    
338                            if (initial.equals(name)) {
339                                    random.setSelected(true);
340                            }
341                            group.add(random);
342                            menu.add(random);
343                    }
344    
345                    return menu;
346            }
347    
348            private GraphPanel getPanel(String graphType, String graphId) {
349                    return graphs.get(panelKey(graphType, graphId));
350            }
351    
352            private void setPanel(String graphType, String graphId, GraphPanel panel) {
353                    graphs.put(panelKey(graphType, graphId), panel);
354            }
355    
356            private void removePanel(String graphType, String graphId) {
357                    graphs.remove(panelKey(graphType, graphId));
358            }
359    
360            public String getName() {
361                    return TOOL_NAME;
362            }
363    
364            public void recTerminate(ATerm t0) {
365                    fireStudioPluginClosed();
366            }
367    
368            public void recAckEvent(ATerm t0) {
369            }
370    
371            public void selectNode(String graphType, ATerm graphId, ATerm nodeId) {
372                    GraphPanel panel = getPanel(graphType, graphId.toString());
373                    if (panel != null) {
374                            panel.setSelectedNode(nodeId.toString());
375                    }
376            }
377    
378            public void showPopup(final String graphType, final ATerm graphId,
379                            final ATerm nodeId, final ATerm menu) {
380                    DefaultPopupImpl popup = new DefaultPopupImpl(bridge);
381                    ATermFactory f = graphId.getFactory();
382                    nl.cwi.sen1.configapi.Factory cf = nl.cwi.sen1.configapi.Factory
383                                    .getInstance((PureFactory) graphId.getFactory());
384                    ActionDescriptionList list = cf.ActionDescriptionListFromTerm(menu);
385    
386                    popup.showPopup(f.make("graph-node(<str>,<term>,<term>)", graphType,
387                                    graphId, nodeId), list);
388            }
389    
390            public ATerm sizeGraph(String graphType, ATerm graphId, ATerm graphTerm) {
391                    GraphPanel panel = getPanel(graphType, graphId.toString());
392    
393                    if (panel != null) {
394                            Graph graph = graphFactory.GraphFromTerm(graphTerm);
395                            FontMetrics metrics = panel.getFontMetrics(preferences
396                                            .getFont(GraphConstants.NODE_FONT));
397                            graph = GraphAdapter.sizeGraph(metrics, preferences, graph);
398                            return graphFactory.getPureFactory().make(
399                                            "snd-value(sized-graph(<term>))", graph.toTerm());
400                    }
401                    System.err
402                                    .println("graph not sized, because a panel was not created yet");
403                    return graphFactory.getPureFactory().make(
404                                    "snd-value(sized-graph(<term>))", graphTerm);
405            }
406    }