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 }