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 }