1// BEGIN LICENSE BLOCK 2// Version: CMPL 1.1 3// 4// The contents of this file are subject to the Cisco-style Mozilla Public 5// License Version 1.1 (the "License"); you may not use this file except 6// in compliance with the License. You may obtain a copy of the License 7// at www.eclipse-clp.org/license. 8// 9// Software distributed under the License is distributed on an "AS IS" 10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 11// the License for the specific language governing rights and limitations 12// under the License. 13// 14// The Original Code is The ECLiPSe Constraint Logic Programming System. 15// The Initial Developer of the Original Code is Cisco Systems, Inc. 16// Portions created by the Initial Developer are 17// Copyright (C) 2006 Cisco Systems, Inc. All Rights Reserved. 18// 19// Contributor(s): 20// 21// END LICENSE BLOCK 22 23package com.parctechnologies.eclipse.visualisation; 24 25 26import java.util.*; 27import java.util.List; 28import java.io.*; 29import java.awt.*; 30import java.awt.geom.*; 31import java.beans.*; 32import java.awt.event.*; 33import java.awt.print.*; 34import javax.swing.*; 35import javax.swing.event.*; 36import javax.swing.table.*; 37import javax.swing.filechooser.*; 38import com.parctechnologies.eclipse.*; 39import com.parctechnologies.eclipse.visualisation.*; 40import com.parctechnologies.eclipse.visualisation.viewers.*; 41import att.grappa.*; 42 43/** 44 * A graph viewer based on GrappaPanel from the GraphViz project. 45 */ 46public class GraphViewer 47 extends ContainerViewer 48 implements ZoomableViewer, Printable 49{ 50 51 public static final int DESKTOP_TYPE = 0; 52 public static final int NETWORK_TYPE = 1; 53 public static final double STEADY_GRAPH_SOFT_MARGIN = 100.0; 54 55 protected static final String DIRECTED_TB="directed (Top->Bottom)"; 56 protected static final String DIRECTED_LR="directed (Left->Right)"; 57 protected static final String UNDIRECTED ="undirected"; 58 protected static final String RADIAL ="radial"; 59 protected static final String REROUTE_STRAIGHT ="re-route edges (straight)"; 60 protected static final String REROUTE_SPLINE ="re-route edges (splines)"; 61 62 protected int type; 63 64 protected int imageCounter; 65 66 protected GrappaPanel graphPanel; 67 protected SteadyGraph graph; 68 protected JScrollPane scrollPane; 69 70 protected Map layoutActionMap; 71 72 //private ScrollingViewletGrid scrollingViewletGrid; 73 protected boolean trackExpansions = false; 74 // private ViewletTracker viewletTracker; 75 76 protected Map elementToViewletType; 77 78 79 /** The object to render a background image */ 80 protected GraphViewBacker backer; 81 82 /** Set to true means the user can move nodes **/ 83 protected boolean moveable; 84 85 public GraphViewer(ViewletType viewletType, 86 VisClientStateModel stateModel, 87 Viewable viewable, 88 int type) { 89 super(stateModel, viewable, viewletType); 90 this.type = type; 91 this.elementToViewletType = new HashMap(); 92 this.imageCounter = 0; 93 this.moveable = true; 94 this.layoutActionMap = new HashMap(); 95 layoutActionMap.put(DIRECTED_TB, 96 new LayoutAction(this,DIRECTED_TB,"dot -Geclipse -Grankdir=TB")); 97 layoutActionMap.put(DIRECTED_LR, 98 new LayoutAction(this,DIRECTED_LR,"dot -Geclipse -Grankdir=LR")); 99 layoutActionMap.put(UNDIRECTED, 100 new LayoutAction(this,UNDIRECTED,"neato -Geclipse")); 101 layoutActionMap.put(RADIAL, 102 new LayoutAction(this,RADIAL,"twopi -Geclipse")); 103 layoutActionMap.put(REROUTE_STRAIGHT, 104 new LayoutAction(this,REROUTE_STRAIGHT,"neato -Geclipse -s -n")); 105 layoutActionMap.put(REROUTE_SPLINE, 106 new LayoutAction(this,REROUTE_SPLINE,"neato -Geclipse -s -n -Gsplines")); 107 initialiseMenu(); 108 } 109 110 public ViewletData getViewletAt(java.util.List index) 111 { 112 return viewletDataStore.getViewletDataAt(index); 113 } 114 115 /** 116 * This implements the abstract getViewletsAt method required by the 117 * ContainerViewer class. 118 */ 119 public Collection getViewletDataAt(java.util.List index) 120 { 121 Collection result = new LinkedList(); 122 result.add(getViewletAt(index)); 123 return(result); 124 } 125 126 /** 127 * Returns the mnemonic to use for a given menuTitle 128 */ 129 protected int getMenuMnemonic(String menuTitle) { 130 if ("Graph".equals(menuTitle)) return KeyEvent.VK_G; 131 if ("Insert".equals(menuTitle)) return KeyEvent.VK_I; 132 return super.getMenuMnemonic(menuTitle); 133 } 134 135 protected void initialiseMenu() 136 { 137 //JCheckBoxMenuItem trackExpansionsItem = new JCheckBoxMenuItem("Track expansions"); 138 //trackExpansionsItem.setModel(new BooleanPropertyModel("trackExpansions", 139 // this, getPropertyChangeSupport())); 140 //addMenuItem("Options", trackExpansionsItem); 141 addMenuAndPopupMenuItem("View",null); 142 addMenuAndPopupMenuItem("View",new GrappaAntiAliasToggleAction()); 143 144 addMenuAndPopupMenuItem("Graph", new SetBackgroundAction(this, false)); 145 addMenuAndPopupMenuItem("Graph", new SetBackgroundAction(this, true)); 146 addMenuAndPopupMenuItem("Graph", null); 147 if (type == NETWORK_TYPE) { 148 // only add "directed" and "undirected" layout actions for the 149 // network viewer 150 addMenuAndPopupMenuItem("Graph", 151 (LayoutAction)layoutActionMap.get(DIRECTED_TB)); 152 addMenuAndPopupMenuItem("Graph", 153 (LayoutAction)layoutActionMap.get(DIRECTED_LR)); 154 addMenuAndPopupMenuItem("Graph", 155 (LayoutAction)layoutActionMap.get(UNDIRECTED)); 156 } 157 addMenuAndPopupMenuItem("Graph", 158 (LayoutAction)layoutActionMap.get(RADIAL)); 159 if (type == NETWORK_TYPE) { 160 // add "edge re-layout actions" 161 addMenuAndPopupMenuItem("Graph", 162 null); 163 addMenuAndPopupMenuItem("Graph", 164 (LayoutAction)layoutActionMap.get(REROUTE_STRAIGHT)); 165 addMenuAndPopupMenuItem("Graph", 166 (LayoutAction)layoutActionMap.get(REROUTE_SPLINE)); 167 } 168 169 addMenuAndPopupMenuItem("Insert", new AddImageAction(this)); 170 addMenuAndPopupMenuItem("Insert", null); 171 if (type == DESKTOP_TYPE) { 172 // only add "Insert XXX" actions for the desktop viewer 173 for(Iterator it = ((MultiViewletType)viewletType).getViewletTypeCollection().iterator(); it.hasNext();) { 174 ViewletType type = (ViewletType)it.next(); 175 addMenuAndPopupMenuItem("Insert", new AddViewletAction(this, type)); 176 } 177 } 178 } 179 180 protected Action getZoomToFitWidthAction() 181 { 182 return(new ZoomToFitWidthAction(this)); 183 } 184 185 protected Action getZoomToFitHeightAction() 186 { 187 return(new ZoomToFitHeightAction(this)); 188 } 189 190 191 192 public void prepareForEvent(VisEvent event) 193 { 194 if(event instanceof CreateEvent) 195 { 196 prepareForCreate((CreateEvent) event); 197 } 198 if(event instanceof ExpandEvent) 199 { 200 prepareForExpand((ExpandEvent) event); 201 } 202 203 super.prepareForEvent(event); 204 } 205 206 /** 207 * To prepare for the creation event we initialise the viewlet array 208 */ 209 protected void prepareForCreate(CreateEvent createEvent) 210 { 211 switch(type) { 212 case NETWORK_TYPE: 213 { 214 // for network, the entire viewable is queried in advance 215 viewletDataStore = 216 new ViewletArray(createEvent.getViewableSize(), 217 ((ViewableType.ArrayType)createEvent.getViewableType()).getFixityList(), 218 getViewable(), 219 (ViewletFactory)viewletType); 220 viewletDataStore.setSymRef(new SymRef(viewletDataStore, 221 this.getSymRef(), 222 "store")); 223 break; 224 } 225 default: 226 { 227 // for most graphs the viewlets will be added by need 228 viewletDataStore = 229 new SparseViewletStore(createEvent.getViewableSize(), 230 ((ViewableType.ArrayType)createEvent.getViewableType()).getFixityList(), 231 getViewable()); 232 viewletDataStore.setSymRef(new SymRef(viewletDataStore, 233 this.getSymRef(), 234 "store")); 235 } 236 } 237 viewletDataStore.addViewletDataStoreListener(new StoreListener()); 238 } 239 240 /** 241 * To prepare for the expand event we forward the expansion to the viewlet 242 * array, so that it can create the new viewlets from which we extract 243 * preBuild goals. 244 */ 245 protected void prepareForExpand(ExpandEvent expandEvent) 246 { 247 viewletDataStore.startExpandDimension(expandEvent.getExpandingDimension()); 248 } 249 250 251 252 253 public void startEvent(VisEvent event, java.util.List goalResults) 254 { 255 if(event instanceof UpdateEvent) 256 { 257 super.startEvent(event, goalResults); 258 return; 259 } 260 261 // for create event, initialise the component 262 if(event instanceof CreateEvent) 263 { 264 super.startEvent(event, goalResults); 265 266 initialiseGraph(); 267 initialiseComponent(); 268 return; 269 } 270 271 if(event instanceof ExpandEvent) 272 { 273 super.startEvent(event, goalResults); 274 275 // if the "track expansions" option is on, scroll to the expanded bit of 276 // the grid. 277 //if(trackExpansions) 278 // scrollToTrackExpansion((ExpandEvent) event); 279 return; 280 } 281 if(event instanceof ContractEvent) 282 { 283 super.startEvent(event, goalResults); 284 // remove all nodes which are no-longer in the viewlet data store 285 removeContractedElements(((ContractEvent)event).getViewableSize()); 286 return; 287 } 288 289 290 super.startEvent(event, goalResults); 291 } 292 293 void removeContractedElements(java.util.List newSize) { 294 for(GraphIterator enumer = graph.elements(); 295 enumer.hasNext(); ) { 296 // scan through all elements 297 Element elem = enumer.nextGraphElement(); 298 java.util.List index; 299 try { 300 index = stringToIndexList(elem.getName()); 301 } catch(NumberFormatException nfe) { 302 // not an indexed node 303 continue; 304 } 305 // mark those which fall outside the new dimensions 306 boolean contracted = false; 307 for(int dim = 0; dim < newSize.size(); dim++) { 308 int i = ((Integer)index.get(dim)).intValue(); 309 int newS = ((Integer)newSize.get(dim)).intValue(); 310 if (i > newS) { 311 contracted = true; 312 } 313 } 314 if (contracted) { 315 removeElement(index); 316 } 317 } 318 } 319 320 /** 321 * Map an element index to the viewlet type which represents it 322 **/ 323 protected ViewletType elementToViewletType(java.util.List index) { 324 return (ViewletType)(this.elementToViewletType.get(index.toString())); 325 } 326 327 328 /** 329 * configure all the viewlets prior to having the graph drawn for 330 * the first time. 331 */ 332 void customizeViewlets() { 333 for(Iterator it = viewletDataStore.getEntireViewletRange().iterator(); 334 it.hasNext(); ) { 335 java.util.List index = (java.util.List)it.next(); 336 Element element = getElement(index); 337 if (element != null) { 338 ViewletData data = viewletDataStore.getViewletDataAt(index);; 339 element.object = data; 340 elementToViewletType(index).customizeElement(viewletDataStore, index, element); 341 } 342 } 343 } 344 345 /** 346 * Removes an existing element 347 */ 348 void removeElement(java.util.List index) { 349 // does a node with this index already exist? 350 Element element = getElement(index); 351 if (element != null) { 352 // remove the old element 353 if (element.isNode()) { 354 graph.removeNode(index.toString()); 355 } else if (element.isEdge()) { 356 graph.removeEdge(index.toString()); 357 } 358 elementToViewletType.remove(index.toString()); 359 } 360 } 361 362 /** 363 * Create and insert a node into the graph 364 */ 365 Node insertNode(ViewletDataStore store, 366 java.util.List index, 367 ViewletData data, 368 ViewletType type) { 369 // remove any existing element 370 removeElement(index); 371 // create the node as a sub-node of this graph 372 Node node = new Node(graph, index.toString()); 373 // store this data in the node 374 node.object = data; 375 // configure the visual representation of the node 376 type.customizeElement(store, index, node); 377 // store the type of this viewlet data 378 elementToViewletType.put(index.toString(),type); 379 return node; 380 } 381 382 /** 383 * Create and insert an edge into the graph 384 */ 385 Edge insertEdge(ViewletDataStore store, 386 java.util.List index, 387 Node fromNode, Node toNode, 388 ViewletData data, ViewletType type) { 389 // remove any existing element 390 removeElement(index); 391 // create and edge 392 Edge edge = new Edge(graph, 393 fromNode, 394 toNode, 395 index.toString()); 396 // store this data in the node 397 edge.object = data; 398 // configure the visual representation of the node 399 type.customizeElement(store, index, edge); 400 // store the type of this viewlet data 401 elementToViewletType.put(index.toString(),type); 402 return edge; 403 } 404 405 /** 406 * Interprets the attributes string as follows... 407 * "key=value key=value key=value..." 408 * 409 */ 410 void setAttributes(Element element, String attributes) { 411 // strip off the first and last char 412 if (DebuggingSupport.logMessages) { 413 DebuggingSupport.logMessage(this, "element="+element+" adding attributes="+attributes); 414 } 415 StringTokenizer st = 416 new StringTokenizer(attributes.substring(1,attributes.length()-1),"= "); 417 while(st.hasMoreTokens()) { 418 String key = st.nextToken(); 419 String value = st.nextToken(); 420 if (DebuggingSupport.logMessages) { 421 DebuggingSupport.logMessage(this, "key="+key+" value="+value); 422 } 423 element.setAttribute(key,value); 424 } 425 426 } 427 428 protected void initialiseNetwork() { 429 Map nodeNumberToNode = new HashMap(); 430 int numElements = ((Integer)(viewletDataStore.getSize().get(0))).intValue(); 431 int numColumns = ((Integer)(viewletDataStore.getSize().get(1))).intValue(); 432 ArrayList index = new ArrayList(2); 433 index.add(new Integer(1)); 434 index.add(new Integer(1)); 435 for(int i = 1; i < numElements+1; i++) { 436 ViewletData data; 437 Integer integerI = new Integer(i); 438 index.set(0,integerI); 439 440 index.set(1,new Integer(1)); 441 data = viewletDataStore.getViewletDataAt(index); 442 String fromNode = data.toString(); 443 444 index.set(1,new Integer(2)); 445 data = viewletDataStore.getViewletDataAt(index); 446 String toNode = data.toString(); 447 448 index.set(1,new Integer(3)); 449 data = viewletDataStore.getViewletDataAt(index); 450 String info = data.toString(); 451 452 String attributes = null; 453 if (numColumns > 3) { 454 // read the position from the final column 455 ArrayList index2 = (ArrayList)index.clone(); 456 index2.set(1, new Integer(4)); 457 attributes = viewletDataStore.getViewletDataAt(index2).toString(); 458 } 459 if (DebuggingSupport.logMessages) { 460 DebuggingSupport.logMessage(this,"fromNode="+fromNode+" toNode="+toNode+" info="+info); 461 } 462 boolean isNode = true; 463 try { 464 isNode = (Integer.parseInt(fromNode) < 0); 465 } catch(NumberFormatException nfe) { 466 // do nothing 467 } 468 if (isNode) { 469 // anything that is not a positive integer indicates this is a 470 // node descriptor 471 472 //Node node = new Node(graph, index.toString()); 473 Node node; 474 // set the shape of node to use as specified in the "fromNode" string 475 if ("-1".equals(fromNode)) { 476 // default renderer 477 node = insertNode(viewletDataStore, index, data, viewletType); 478 } else { 479 node = insertNode(viewletDataStore, index, data, viewletType); 480 node.setAttribute("shape",fromNode); 481 } 482 if (attributes != null) { 483 // interpret the attributes field as a list of key=value 484 // pairs and use these to set element attributes 485 setAttributes(node, attributes); 486 //node.setAttribute("pos", pos); 487 } 488 // record node with its node number so that it can be looked 489 // up when creating edges 490 nodeNumberToNode.put(toNode, node); 491 } else { 492 // otherwise an edge descriptor 493// Edge edge = new Edge(graph, 494// (Node)(nodeNumberToNode.get(fromNode)), 495// (Node)(nodeNumberToNode.get(toNode)), 496// index.toString()); 497 Edge edge = insertEdge(viewletDataStore, 498 index, 499 (Node)(nodeNumberToNode.get(fromNode)), 500 (Node)(nodeNumberToNode.get(toNode)), 501 data, 502 viewletType); 503 } 504 } 505 } 506 507 /** 508 * Construct graph structures 509 */ 510 protected void initialiseGraph() { 511 graph = new SteadyGraph(getViewable().getNameString(), 512 true, 513 false, 514 STEADY_GRAPH_SOFT_MARGIN); 515 if (type == NETWORK_TYPE) { 516 // interpret the viewable as a network 517 initialiseNetwork(); 518 } 519 } 520 521 /** 522 * Layout the graph and set the new graph bounds 523 */ 524 void externalLayoutGraph(String command) { 525 final String EXTERNAL_SOLVERS_MESSAGE= 526 "\nEnsure that the GraphViz tools are installed correctly and are"+ 527 "\navailable on your default path. These tools can be downloaded"+ 528 "\nfrom http://www.research.att.com/sw/tools/graphviz/"; 529 Object connector = null; 530 try { 531 connector = Runtime.getRuntime().exec(command); 532 } catch(Exception ex) { 533 try { 534 // try pre-prending the eclipse library path, since this is where the 535 // tools that come with ECLiPSe may live 536 String newCommand = getStateModel().getEclipseLibDir()+File.separator+command; 537 connector = Runtime.getRuntime().exec(newCommand); 538 } catch(Exception ex2) { 539 JOptionPane.showMessageDialog(null, 540 new String[] {"Problem calling external layout program: " + ex2.getMessage(), EXTERNAL_SOLVERS_MESSAGE}); 541 return; 542 } 543 } 544 if(!GrappaSupport.filterGraph(graph,connector)) { 545 JOptionPane.showMessageDialog(null, 546 new String[] {"Problem during execution of external layout program.",EXTERNAL_SOLVERS_MESSAGE}); 547 return; 548 } 549 if(connector instanceof Process) { 550 try { 551 int code = ((Process)connector).waitFor(); 552 if(code != 0) { 553 JOptionPane.showMessageDialog(null, 554 new String[] {"External layout program exited with code "+code,EXTERNAL_SOLVERS_MESSAGE}); 555 //return; 556 } 557 } catch(InterruptedException ex) { 558 JOptionPane.showMessageDialog(null, 559 new String[] {"Problem whilst exiting external layout program.", EXTERNAL_SOLVERS_MESSAGE}); 560 ex.printStackTrace(System.err); 561 } 562 } 563 connector = null; 564 graph.oldBounds = null; 565 customizeViewlets(); 566 graph.repaint(); 567 } 568 569 /** 570 * Layout the graph as a matrix based on the first two dimensions of 571 * the viewlet indices */ 572 void layoutGraphAsMatrix() { 573 // 574 } 575 576 577 protected void initialiseComponent() 578 { 579 // disable the "trackUpdates" feature 580 trackUpdatesMenuItem.setEnabled(false); 581 582 scrollPane = new JScrollPane(); 583 scrollPane.getViewport().setBackingStoreEnabled(true); 584 585 backer = new GraphViewBacker(); 586 graphPanel = new GrappaPanel(graph, backer); 587 // set no-antialiasing for grappa graphs by default 588 Grappa.useAntiAliasing = false; 589 // set node and edge label cut-off 590 Grappa.nodeLabelsScaleCutoff = 0.1; 591 Grappa.edgeLabelsScaleCutoff = 0.1; 592 Grappa.subgLabelsScaleCutoff = 0.1; 593 graphPanel.addGrappaListener(new GraphViewListener(this)); 594 graphPanel.setScaleToFit(false); 595 596 java.awt.Rectangle bbox = graph.getBoundingBox().getBounds(); 597 598 599 600 // addMenuAndPopupMenuItem("Insert", new AddViewletAction(this, viewletType)); 601 // addMenuAndPopupMenuItem("Insert", new AddViewletAction(this, new TextViewletType())); 602 603 //scrollingViewletGrid = 604 // new ScrollingViewletGrid(getViewletArray(), this); 605 //viewletTracker = new ViewletTracker(scrollingViewletGrid); 606 MouseViewletMenuUpPopper mouseViewletMenuUpPopper = 607 new MouseViewletMenuUpPopper(this, graphPanel); 608 // create the selection for this table 609 //selection = new ViewletSelectionSpreadSheet((SpreadSheet)table, this); 610 // set the row height to equal the column width 611 scrollPane.setViewportView(graphPanel); 612 scrollPane.setPreferredSize(new Dimension(430, 200)); 613 } 614 615 616 public String getToolTip(java.util.List index) { 617 return viewletDataStore.getViewletDataAt(index).toString(); 618 } 619 620 public Component getComponent() 621 { 622 return scrollPane; 623 } 624 625 public void setTrackExpansions(boolean newValue) 626 { 627 //(new ViewerSetBooleanPropertyCommand(this, "trackExpansions", newValue)).issue(); 628 } 629 630 public void setTrackExpansionsPrivate(boolean newValue) 631 { 632 //boolean oldValue = trackExpansions; 633 //trackExpansions = newValue; 634 //this.getPropertyChangeSupport(). 635 // firePropertyChange("trackExpansions", oldValue, newValue); 636 } 637 638 public boolean getTrackExpansions() 639 { 640 return false; 641 //return(trackExpansions); 642 } 643 644 645 public void zoomToLevel(float zoomLevel) 646 { 647 // scale the graph 648 graphPanel.setScaleToFit(false); 649 graphPanel.setScaleToSize(null); 650 graphPanel.resetZoom(); 651 graphPanel.multiplyScaleFactor(zoomLevel); 652 //table.setColumnWidth((int)(DEFAULT_COLUMN_WIDTH*zoomLevel)); 653 //table.setRowHeight((int)(DEFAULT_ROW_HEIGHT*zoomLevel)); 654 //table.revalidate(); 655 } 656 657 public void zoomInByRatio(float zoomRatio) 658 { 659 graphPanel.setScaleToFit(false); 660 graphPanel.setScaleToSize(null); 661 graphPanel.multiplyScaleFactor(zoomRatio); 662 } 663 664 // move the scrollbars so that the new row/column comes into view. 665 protected void scrollToTrackExpansion(ExpandEvent event) 666 { 667 //scrollingViewletGrid.scrollToTrackExpansion(event.getExpandingDimension()); 668 } 669 670// protected ViewletTracker getViewletTracker() 671// { 672// return(viewletTracker); 673// } 674 675 676 protected class ZoomToFitWidthAction extends AbstractAction 677 { 678 private GraphViewer viewer; 679 ZoomToFitWidthAction(GraphViewer viewer) 680 { 681 super("Zoom to fit width"); 682 this.viewer = viewer; 683 } 684 685 public void actionPerformed(ActionEvent event) 686 { 687 (new ZoomableViewerZoomToFitWidthCommand(viewer)).issue(); 688 } 689 } 690 691 public void zoomToFitWidth() 692 { 693 double graphWidth = graph.getBoundingBox().getWidth(); 694 GrappaSize margins = (GrappaSize)(graph.getAttributeValue(Grappa.MARGIN_ATTR)); 695 if(margins != null) { 696 double x_margin = graphPanel.PointsPerInch * margins.width; 697 graphWidth += 2.0 * x_margin; 698 } 699 double paneWidth = scrollPane.getViewport().getExtentSize().getWidth(); 700 zoomToLevel((float)(paneWidth / graphWidth)); 701 } 702 703 protected class ZoomToFitHeightAction extends AbstractAction 704 { 705 private GraphViewer viewer; 706 ZoomToFitHeightAction(GraphViewer viewer) 707 { 708 super("Zoom to fit height"); 709 this.viewer = viewer; 710 } 711 712 public void actionPerformed(ActionEvent event) 713 { 714 (new ZoomableViewerZoomToFitHeightCommand(viewer)).issue(); 715 } 716 } 717 718 public void zoomToFitHeight() 719 { 720 double graphHeight = graph.getBoundingBox().getHeight(); 721 GrappaSize margins = (GrappaSize)(graph.getAttributeValue(Grappa.MARGIN_ATTR)); 722 if(margins != null) { 723 double y_margin = graphPanel.PointsPerInch * margins.height; 724 graphHeight += 2.0 * y_margin; 725 } 726 double paneHeight = scrollPane.getViewport().getExtentSize().getHeight(); 727 zoomToLevel((float)(paneHeight / graphHeight)); 728 } 729 730 731 protected class LayoutAction extends AbstractAction 732 { 733 private GraphViewer viewer; 734 private String command; 735 LayoutAction(GraphViewer viewer, String name, String command) 736 { 737 super("Layout graph:"+name); 738 this.viewer = viewer; 739 this.command = command; 740 } 741 742 public void actionPerformed(ActionEvent event) 743 { 744 String newCommand = (String)JOptionPane. 745 showInputDialog(null, 746 "The graph layout will be preformed by the following command", 747 "Layout command", 748 JOptionPane.PLAIN_MESSAGE, 749 null, 750 null, 751 command); 752 command = newCommand; 753 new ExternalLayoutCommand(viewer, command).issue(); 754 //viewer.externalLayoutGraph(command); 755 } 756 } 757 758 public static class ExternalLayoutCommand extends ViewerCommand { 759 String command; 760 public ExternalLayoutCommand(GraphViewer viewer, String command) { 761 super(viewer); 762 this.command = command; 763 } 764 765 public void postRecordIssue() { 766 ((GraphViewer)getViewer()).externalLayoutGraph(command); 767 } 768 } 769 770 public static class MoveNodeCommand extends ViewerCommand { 771 String nodeName; 772 double dx; 773 double dy; 774 775 public MoveNodeCommand(GraphViewer viewer, Node node, 776 double dx, double dy) { 777 super(viewer); 778 this.nodeName = node.getName(); 779 this.dx = dx; 780 this.dy = dy; 781 } 782 783 public void postRecordIssue() { 784 GraphViewer graphViewer = (GraphViewer)getViewer(); 785 graphViewer.moveNode((Node)graphViewer.graph.findNodeByName(nodeName), 786 dx, dy); 787 } 788 } 789 790 // public void setSelection(ViewletRange newSelection) { 791 //(new ViewerSetSelectionCommand(this, newSelection)).issue(); 792 // } 793 794 795 protected void clearSelectionPrivate() { 796 // clear the selection 797 if(graph.currentSelection != null) { 798 if(graph.currentSelection instanceof Element) { 799 ((Element)(graph.currentSelection)).highlight &= ~GrappaConstants.HIGHLIGHT_MASK; 800 } else { 801 Vector vec = ((Vector)(graph.currentSelection)); 802 for(int i = 0; i < vec.size(); i++) { 803 ((Element)(vec.elementAt(i))).highlight &= ~GrappaConstants.HIGHLIGHT_MASK; 804 } 805 } 806 graph.currentSelection = null; 807 graph.repaint(); 808 } 809 } 810 811 /** 812 * Called by the ViewerSetSelectionCommand 813 */ 814 protected void setSelectionPrivate(ViewletRange newSelection) { 815 DebuggingSupport. 816 logMessage(this, 817 "setSelectionPrivate called with newSelection=" + 818 newSelection); 819 clearSelectionPrivate(); 820 if (!newSelection.isEmpty()) { 821 graph.currentSelection = new Vector(); 822 for(Iterator it = newSelection.iterator(); 823 it.hasNext();) { 824 java.util.List index = (java.util.List) it.next(); 825 Element element = getElement(index); 826 if (element != null) { 827 // highlight element 828 element.highlight |= GrappaConstants.SELECTION_MASK; 829 ((Vector)graph.currentSelection).add(element); 830 } 831 } 832 } 833 } 834 835 836 protected Element getElement(java.util.List index) { 837 Element element = graph.findNodeByName(index.toString()); 838 if (element == null) { 839 element = graph.findEdgeByName(index.toString()); 840 } 841 return element; 842 } 843 844 845 /** 846 * Returns a copy of the current selection 847 */ 848 public ViewletRange getSelection() { 849 //return new SpreadSheetSelectionViewletRange(table); 850 // scan the graph, looking for nodes which are selected 851 ViewletRangeCollection range = new ViewletRangeCollection(); 852 for(Iterator it = viewletDataStore.getEntireViewletRange().iterator(); 853 it.hasNext();) { 854 java.util.List index = (java.util.List) it.next(); 855 Element element = getElement(index); 856 if (element != null) { 857 if ((element.highlight & Grappa.SELECTION_MASK) == Grappa.SELECTION_MASK) { 858 range.add(index); 859 } 860 } 861 } 862 return range; 863 } 864 865 866 /** 867 * Return a collection of actions which can be applied to viewlets 868 * in this table 869 */ 870 public Collection getCompoundActions(ViewletRange selected) { 871 return viewletType.getActions(viewletDataStore,selected); 872 // Collection ll = new LinkedList(); 873 // ll.add((new ToggleHoldAction()).createCompoundAction(selected)); 874 // return ll; 875 } 876 877// private class ToggleHoldAction extends ViewletAction 878// { 879// ToggleHoldAction() 880// { 881// super("Hold on updates"); 882// putValue(Action.NAME, "Hold on updates"); 883// putValue(Action.LONG_DESCRIPTION, 884// "Change whether control is held by the visualisation client during element updates"); 885// putValue(Action.SHORT_DESCRIPTION, 886// "Change whether control is held on updates"); 887// putValue(Action.SMALL_ICON, new HoldIcon(20, 20)); 888 889// } 890 891// public void actionPerformed(ActionEvent e) 892// { 893// // do nothing 894// } 895 896// /** 897// * If all viewlets in the collection have hold set to true, then the 898// * compound version sets it to false. If any of them have it set to false 899// * then the compound version sets all to true. 900// */ 901// public ViewletAction createCompoundAction(Collection viewlets) 902// { 903// boolean allHold = true; 904// ViewletImpl currentViewlet; 905// Iterator viewletsIterator = viewlets.iterator(); 906// while(viewletsIterator.hasNext()) 907// { 908// currentViewlet = (ViewletImpl) viewletsIterator.next(); 909// if(!currentViewlet.getHoldsOnUpdates()) 910// { 911// allHold = false; 912// break; 913// } 914// } 915// return(new CompoundToggleHoldAction(!allHold, viewlets)); 916// } 917// } 918 919// private class CompoundToggleHoldAction extends ToggleHoldAction 920// { 921// private boolean newValue; 922// private Collection viewlets; 923// CompoundToggleHoldAction(boolean newValue, Collection viewlets) 924// { 925// super(); 926// this.newValue = newValue; 927// this.viewlets = viewlets; 928// } 929 930// public void actionPerformed(ActionEvent e) 931// { 932// ViewletImpl currentViewlet; 933// Iterator viewletsIterator = viewlets.iterator(); 934// while(viewletsIterator.hasNext()) 935// { 936// currentViewlet = (ViewletImpl) viewletsIterator.next(); 937// currentViewlet.setHoldsOnUpdates(newValue); 938// } 939// // trigger the jtable to update as a whole bunch of viewlets have just 940// // changed 941// ((AbstractTableModel)table.getModel()).fireTableDataChanged(); 942// } 943 944// } 945 946 /** 947 * The default text cell render 948 */ 949// private class MyRenderer extends DefaultTableCellRenderer { 950// public MyRenderer() { 951// super(); 952// setHorizontalAlignment(SwingConstants.CENTER); 953// } 954 955// public Component getTableCellRendererComponent(JTable table, 956// Object value, 957// boolean isSelected, 958// boolean hasFocus, 959// int row, 960// int column) { 961// JLabel result = 962// (JLabel)(super.getTableCellRendererComponent(table, 963// value, 964// isSelected, 965// hasFocus, 966// row, 967// column)); 968// ViewletImpl viewlet = (ViewletImpl)value; 969// String updating = viewlet.getUpdating(); 970// Color col = Color.white; 971// if ("no".equals(updating)) { 972// // do nothing 973// } else if ("updatingForward".equals(updating)) { 974// col = Color.green; 975// } else /* updatingBack */ { 976// col = Color.red; 977// } 978// if (viewlet.getHoldsOnUpdates()) { 979// col = col.darker(); 980// } 981// if (isSelected) { 982// col = col.darker().darker(); 983// } 984// result.setBackground(col); 985// return result; 986// } 987 988 989// public void setValue(Object value) { 990// super.setValue(value); 991// ViewletImpl viewlet = (ViewletImpl)value; 992// setToolTipText(viewlet.getToolTipText()); 993// } 994// } 995 996 997 /** 998 * This implements the abstract getViewletsAt method required by the 999 * ContainerViewer class. 1000 */ 1001 protected ViewletRange getAllViewletData() 1002 { 1003 return(viewletDataStore.getEntireViewletRange()); 1004 } 1005 1006 /** Returns any actions or sub-menus that should be available via a 1007 right-click popup menu */ 1008 public Collection getViewerPopupMenuCollection() { 1009 Collection result = super.getViewerPopupMenuCollection(); 1010 result.add(getPopupMenu("Graph")); 1011 result.add(getPopupMenu("Insert")); 1012 result.add(new PrintAction(this)); 1013 return result; 1014 } 1015 1016 1017 void insertNewViewletAt(ViewletType newViewletType, 1018 java.util.List index, 1019 int col, 1020 int row 1021 ) { 1022 ViewletData newViewletData = ((ViewletFactory)newViewletType).build(); 1023// Object elementReference = new CompoundTermImpl("element", index); 1024// newViewlet.setElementReference(elementReference); 1025 MultiViewletType.Data multiData = 1026 (MultiViewletType.Data)(((ViewletFactory)viewletType).build()); 1027 multiData.setViewletType(newViewletType); 1028 multiData.setViewletData(newViewletData); 1029 1030 try { 1031 viewletDataStore.setViewletDataAt(index, multiData); 1032 ViewletRange range = new ViewletRangeCollection(); 1033 range.add( index ); 1034 BatchGoal preBuildGoal = 1035 viewletType.collectPreBuildGoal(this, 1036 viewletDataStore, 1037 range); 1038 java.util.List goalResults; 1039 try { 1040 if (DebuggingSupport.logMessages) { 1041 DebuggingSupport.logMessage(this, 1042 "Executing preBuild goal for new viewlet"); 1043 } 1044 1045 goalResults = getStateModel(). 1046 executeMultitaskBatchGoal(preBuildGoal); 1047 1048 if (DebuggingSupport.logMessages) { 1049 DebuggingSupport.logMessage(this, 1050 "Completed preBuild goal for new viewlet"); 1051 } 1052 } catch(EclipseException ee) { 1053 throw(new RuntimeException("EclipseException "+ee+" thrown while running "+ 1054 "viewlet's pre-build goal")); 1055 } catch(IOException ioe) { 1056 throw(new RuntimeException("IOException "+ioe+" thrown while running "+ 1057 "viewlet's pre-build goal")); 1058 } 1059 this.viewletType.startBuild(this, viewletDataStore, range, goalResults); 1060// List existingViewlets = (List) indexToViewlets.get(index); 1061// if(existingViewlets == null) 1062// { 1063// existingViewlets = new LinkedList(); 1064// } 1065// List identifier = new LinkedList(index); 1066// // distinguish this viewlet from others at the same index 1067// identifier.add(new Integer(existingViewlets.size())); 1068// newViewlet.setSymRef(new SymRef(newViewlet, getSymRef(), identifier)); 1069// existingViewlets.add(newViewlet); 1070// allViewlets.add(newViewlet); 1071// indexToViewlets.put(index, existingViewlets); 1072 1073// if (DebuggingSupport.logMessages) { 1074// DebuggingSupport.logMessage(this,"set of viewlets for index "+index+ 1075// " is now "+existingViewlets); 1076// } 1077 } catch(RuntimeException re) { 1078 // re-throw the exception after cleaning up the state by 1079 // removing the MultiViewletData from the viewletDataStore (it 1080 // was inserted so that the "collectPreBuildGoal" could be 1081 // called on it) 1082 viewletDataStore.setViewletDataAt(index, null); 1083 throw re; 1084 } 1085 Node node = insertNode(viewletDataStore, index, multiData, viewletType); 1086 //node.setAttribute("color",Color.white); 1087 //node.setAttribute("style","filled"); 1088 //node.setAttribute("label",newViewletData.toString()); 1089 moveNodeToVirtualTable(node, col, row); 1090 // addNewViewletComponent(newViewlet, col, row); 1091 } 1092 1093 /** 1094 * Sets the position attribute for thisnode to be at the given 1095 * coordinates in a virtual table whose top-left corner is in the 1096 * top-left of the viewer window. 1097 * @param Node The node to position 1098 * @param col Column in virtual table (counting from 0) 1099 * @param row Row in virtual table (counting from 0) 1100 * */ 1101 protected void moveNodeToVirtualTable(Node node, 1102 int col, 1103 int row) { 1104 1105 double x, y; 1106 1107 Rectangle2D prefSize = node.getGrappaNexus().getBounds2D(); 1108 x = prefSize.getWidth()*col; 1109 y = prefSize.getHeight()*row; 1110 1111 // clearSelection(); 1112 // addToSelection(viewlet); 1113 1114 if (DebuggingSupport.logMessages) { 1115 DebuggingSupport.logMessage(this, 1116 "Adding node at (row,col)=("+row+","+col+ 1117 ") and (x,y)=("+x+","+y+")"); 1118 } 1119 node.setAttribute("pos",x+","+y); 1120 1121 } 1122 1123 1124 1125 /** 1126 * Called from the InsertViewletCommand command class, this 1127 * function actualy adds the given viewlets. 1128 * @param viewletType The type of viewlet to use for creating the viewlets 1129 * @param indices A 2D array where the first dimension is the 1130 * viewable element dimension and the second contains the values 1131 * that need to be instatiated in that dimension. All combinations 1132 * of the given dimensions must be inserted. 1133 */ 1134 void insertNewViewlet(ViewletType viewletType, int[][] indices) 1135 { 1136 java.util.List index = new ArrayList(indices.length); 1137 int[] counters = new int[indices.length]; 1138 // record the index of the first non-unit dimension 1139 int columnDimensionIndex = 0; 1140 // record the index of the second non-unit dimension 1141 int rowDimensionIndex = -1; 1142 int nonUnitDimensions = 0; 1143 for(int i = 0 ; i < indices.length; i++) { 1144 counters[i] = indices[i].length-1; 1145 if (indices[i].length > 1) { 1146 nonUnitDimensions++; 1147 switch(nonUnitDimensions) { 1148 case 1: { 1149 columnDimensionIndex = i; 1150 break; 1151 } 1152 case 2: { 1153 rowDimensionIndex = i; 1154 break; 1155 } 1156 default:{ 1157 // do nothing for higher dimensions at the moment 1158 } 1159 } 1160 } 1161 } 1162 int sp = indices.length ; // stack pointer 1163 int count = 0; 1164 int sum; 1165 // Collect any indices for which an error occured whilst trying to 1166 // insert them 1167 Vector failedIndices = new Vector(); 1168 do { 1169 if (sp > indices.length-1) { 1170 int row = 0; // hold the row for this index 1171 int col = 0; // hold the column for this index 1172 // construct the list of indices 1173 index.clear(); 1174 for(int i = 0; i < indices.length; i++) { 1175 index.add(new Integer(indices[i][counters[i]])); 1176 if (i == columnDimensionIndex) { 1177 col = counters[i]; 1178 } 1179 if (i == rowDimensionIndex) { 1180 row = counters[i]; 1181 } 1182 } 1183 if (DebuggingSupport.logMessages) { 1184 DebuggingSupport. 1185 logMessage(this, 1186 "insert viewlet index=" + index + 1187 " col=" + col + 1188 " row=" + row); 1189 } 1190 try { 1191 insertNewViewletAt(viewletType, new ArrayList(index), col, row); 1192 } catch(RuntimeException e) { 1193 // store a copy of the index (must be a copy because the 1194 // variable 'index' is destructively modified) 1195 failedIndices.add(new ArrayList(index)); 1196 } 1197 count++; 1198 sp--; 1199 } 1200 1201 sum = 0 ; 1202 for (int i = 0 ; i <indices.length; i++ ) { 1203 sum += counters[i]; 1204 } 1205 if (sum == 0) { 1206 break; 1207 } 1208 1209 counters[sp]--; 1210 1211 if (counters[sp] < 0) { 1212 counters[sp] = indices[sp].length; 1213 sp--; 1214 } else { 1215 sp++; 1216 } 1217 1218 } while(sum > 0); 1219 if (DebuggingSupport.logMessages) { 1220 DebuggingSupport. 1221 logMessage(this,"number of combinations= "+count + 1222 " number of failed insertions="+failedIndices.size()); 1223 } 1224 if (!failedIndices.isEmpty()) { 1225 // inform the user that some insertions failed 1226 JList jlist = new JList(failedIndices); 1227 JOptionPane.showMessageDialog(null, 1228 new Object[] {"Viewlets could not be created for the following indices: ", new JScrollPane(jlist)}); 1229 } 1230 } 1231 1232 1233 1234 protected class AddViewletAction extends AbstractAction implements PropertyChangeListener 1235 { 1236 ViewletType viewletType; 1237 GraphViewer viewer; 1238 1239 /** record the fact that the viewer is being destroyed */ 1240 boolean destroyed; 1241 1242 public AddViewletAction(GraphViewer viewer, ViewletType viewletType) 1243 { 1244 super("Insert "+viewletType.getDescription().toLowerCase()); 1245 this.destroyed = false ; 1246 this.viewletType = viewletType; 1247 this.viewer = viewer; 1248 setEnabled(!getStateModel().getCanPerformRPC()); 1249 getStateModel().getPropertyChangeSupport(). 1250 addPropertyChangeListener("canPerformRPC", this); 1251 getPropertyChangeSupport(). 1252 addPropertyChangeListener("destroyEventIssued", this); 1253 } 1254 1255 public void propertyChange(PropertyChangeEvent event) 1256 { 1257 if (event.getPropertyName().equals("destroyEventIssued")) { 1258 destroyed = true; 1259 } 1260 if((destroyed) || 1261 !((Boolean) event.getNewValue()).booleanValue()) 1262 { 1263 setEnabled(false); 1264 } 1265 else 1266 { 1267 setEnabled(true); 1268 } 1269 } 1270 1271 public void actionPerformed(ActionEvent event) 1272 { 1273 java.util.List locationNamesList = new LinkedList(); 1274 for(int i = 1; i <= getSize().size(); i++) 1275 { 1276 locationNamesList.add(getLocationNames(i)); 1277 } 1278 JDialog dialog = 1279 new IndexChoosingDialog(getViewable().getNameAtom(), locationNamesList, 1280 viewletType, viewer); 1281 dialog.show(); 1282 1283 } 1284 } 1285 1286 protected class IndexChoosingDialog extends JDialog 1287 { 1288 private ViewletType viewletType; 1289 private GraphViewer viewer; 1290 private InsertAction insertAction; 1291 1292 private JList[] locationSelection; 1293 1294 1295 // second parameter is list of lists of location names 1296 public IndexChoosingDialog(Object viewableName, 1297 java.util.List locationNamesList, 1298 ViewletType viewletType, 1299 GraphViewer viewer) 1300 { 1301 super(); 1302 setModal(true); 1303 this.setTitle("New "+ 1304 viewletType.getDescription().toLowerCase()); 1305 this.viewletType = viewletType; 1306 this.viewer = viewer; 1307 insertAction = new InsertAction(); 1308 initialiseLocationSelectors(locationNamesList); 1309 getContentPane().setLayout(new GridBagLayout()); 1310 GridBagConstraints gbc = new GridBagConstraints(); 1311 gbc.fill = GridBagConstraints.BOTH; 1312 gbc.weighty = 1; 1313 gbc.weightx = 1; 1314 gbc.gridy = 1; 1315 gbc.insets = new Insets(10,10,10,10); 1316 getContentPane().add(locationSelectorPanel(), gbc); 1317 gbc.fill = GridBagConstraints.NONE; 1318 gbc.gridy = 0; 1319 getContentPane().add(new JLabel("Select viewable element location"), 1320 gbc); 1321 gbc.gridy = 2; 1322 JPanel buttonPanel = new JPanel(); 1323 buttonPanel.add(new ActionButton(insertAction)); 1324 buttonPanel.add(new ActionButton(new CancelAction())); 1325 getContentPane().add(buttonPanel, gbc); 1326 pack(); 1327 } 1328 1329 private void addViewlets() 1330 { 1331 // hold all the selected indices in an array 1332 int[][] indices = new int[locationSelection.length][]; 1333 1334 for(int i = 0 ; i < locationSelection.length; i++) 1335 { 1336 indices[i] = locationSelection[i].getSelectedIndices(); 1337 for(int j = 0; j < indices[i].length; j++ ) { 1338 // increment the selected value to get the viewable index 1339 // which counts from 1 instead of 0 1340 indices[i][j] ++; 1341 } 1342 } 1343 //index.add(new Integer(locationSelection[i].getMinSelectionIndex()+1)); 1344 (new InsertViewletCommand(viewer, 1345 indices, viewletType)).issue(); 1346 } 1347 1348 private void initialiseLocationSelectors(java.util.List locationNamesList) 1349 { 1350 Iterator i = locationNamesList.iterator(); 1351 locationSelection = new JList[locationNamesList.size()]; 1352 java.util.List locationNames; 1353 int j = 0; 1354 while(i.hasNext()) 1355 { 1356 locationNames = (java.util.List) i.next(); 1357 locationSelection[j] = new JList(new Vector(locationNames)); 1358 locationSelection[j]. 1359 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 1360 locationSelection[j].setVisibleRowCount(6); 1361 locationSelection[j].addListSelectionListener(insertAction); 1362 if (locationNames.size() == 1) { 1363 // if there's only one dimension then select it 1364 locationSelection[j].setSelectedIndex(0); 1365 locationSelection[j].setEnabled(false); 1366 } 1367 j++; 1368 } 1369 } 1370 1371 private JPanel locationSelectorPanel() 1372 { 1373 GridBagLayout layout = new GridBagLayout(); 1374 JPanel panel = new JPanel(layout); 1375 GridBagConstraints gbc = new GridBagConstraints(); 1376 gbc.weightx = 1; 1377 gbc.insets = new Insets(10, 10, 10, 10); 1378 gbc.fill = GridBagConstraints.HORIZONTAL; 1379 gbc.anchor = GridBagConstraints.NORTH; 1380 gbc.gridx = 0; 1381 for(int i = 0; i < locationSelection.length; i++) 1382 { 1383 gbc.weighty = 0; 1384 gbc.gridy = 0; 1385 panel.add(new JLabel("Dimension "+(i+1)), gbc); 1386 gbc.weighty = 1; 1387 gbc.gridy = 1; 1388 panel.add(new JScrollPane(locationSelection[i]), gbc); 1389 gbc.gridx++; 1390 } 1391 return(panel); 1392 1393 } 1394 1395 private class InsertAction 1396 extends AbstractAction 1397 implements ListSelectionListener 1398 { 1399 InsertAction() 1400 { 1401 super("Insert"); 1402 this.setEnabled(false); 1403 } 1404 public void valueChanged(ListSelectionEvent event) 1405 { 1406 boolean allDimensionsHaveSelection = true; 1407 for(int i = 0; i < locationSelection.length; i++) 1408 { 1409 if((locationSelection[i] == null) || 1410 (locationSelection[i].getMinSelectionIndex() < 0)) 1411 { 1412 allDimensionsHaveSelection = false; 1413 break; 1414 } 1415 } 1416 if(allDimensionsHaveSelection) 1417 { 1418 this.setEnabled(true); 1419 } 1420 } 1421 public void actionPerformed(ActionEvent event) 1422 { 1423 addViewlets(); 1424 dispose(); 1425 } 1426 } 1427 1428 private class CancelAction extends AbstractAction 1429 { 1430 CancelAction() 1431 { 1432 super("Cancel"); 1433 } 1434 public void actionPerformed(ActionEvent event) 1435 { 1436 dispose(); 1437 } 1438 } 1439 1440 } 1441 1442 1443 /** 1444 * Command to insert a viewlet on the desktop. 1445 */ 1446 public static class InsertViewletCommand extends ViewerCommand { 1447 1448 private SymRef viewletType; 1449 private int[][] index; 1450 1451 public InsertViewletCommand(GraphViewer viewer, 1452 int[][] index, 1453 ViewletType viewletType) { 1454 super(viewer); 1455 this.viewletType = viewletType.getSymRef(); 1456 this.index = index; 1457 } 1458 1459 private ViewletType getViewletType() { 1460 if (DebuggingSupport.logMessages) { 1461 DebuggingSupport.logMessage(this, "InsertViewletCommand getting ViewletType from SymRef="+viewletType); 1462 } 1463 return((ViewletType) SymRef.get(viewletType)); 1464 } 1465 1466 public void postRecordIssue(){ 1467 ((GraphViewer) getViewer()).insertNewViewlet(getViewletType(), index); 1468 } 1469 } 1470 1471 /** 1472 * Configure graphics options for viewer 1473 Grappa.antiAliasText = false; 1474 Grappa.useAntiAliasing = false; 1475 Grappa.useFractionalMetrics = false; 1476 */ 1477 protected class GrappaAntiAliasToggleAction extends AbstractAction { 1478 public GrappaAntiAliasToggleAction() { 1479 super("Toggle Hi-Quality"); 1480 } 1481 public void actionPerformed(ActionEvent e) { 1482 boolean newState = !Grappa.useAntiAliasing; 1483 Grappa.useAntiAliasing = newState; 1484 //Grappa.useFractionalMetrics = newState; 1485 //Grappa.antiAliasText = newState; 1486 graph.repaint(); 1487 } 1488 } 1489 1490 protected void addImage(String filename) { 1491 Node node = new Node(graph, "image-"+imageCounter++); 1492 node.setAttribute("shape","box"); 1493 node.setAttribute("label",""); 1494 node.setAttribute("image",filename); 1495 graph.resetBoundingBox(); 1496 graph.repaint(); 1497 } 1498 1499 protected class AddImageCommand extends ViewerCommand { 1500 String filename; 1501 1502 public AddImageCommand(GraphViewer viewer, String filename) { 1503 super(viewer); 1504 this.filename = filename; 1505 } 1506 1507 public void postRecordIssue() { 1508 ((GraphViewer)getViewer()).addImage(filename); 1509 } 1510 } 1511 1512 /** 1513 * Action to query the user for an image and then insert a node with 1514 * the "image" attribute set to this image 1515 **/ 1516 protected class AddImageAction extends AbstractAction { 1517 protected GraphViewer viewer; 1518 1519 protected File defaultImageDirectory; 1520 1521 public AddImageAction(GraphViewer viewer) { 1522 super("Image"); 1523 this.viewer = viewer; 1524 } 1525 1526 public void actionPerformed(ActionEvent event) { 1527 JFileChooser fileChooser = new ImageFileChooser(); 1528 if(defaultImageDirectory != null) 1529 { 1530 fileChooser.setCurrentDirectory(defaultImageDirectory); 1531 } 1532 int returnVal = fileChooser.showOpenDialog(getComponent()); 1533 if(returnVal == JFileChooser.APPROVE_OPTION) 1534 { 1535 1536 if (DebuggingSupport.logMessages) { 1537 DebuggingSupport.logMessage(this, "image: "+ 1538 fileChooser.getSelectedFile()+ 1539 " approved"); 1540 } 1541 1542 if(fileChooser.getSelectedFile().exists()) 1543 { 1544// (new DesktopViewerImportBackgroundCommand( 1545// desktopViewer, 1546// fileChooser.getSelectedFile())).issue(); 1547 new AddImageCommand(viewer, 1548 fileChooser.getSelectedFile().toString()).issue(); 1549 } 1550 else 1551 { 1552 JOptionPane. 1553 showMessageDialog(getComponent(), 1554 "File \n"+fileChooser.getSelectedFile().toString()+"\ndoes not exist", 1555 "Error in Visualisation Client", JOptionPane.ERROR_MESSAGE); 1556 } 1557 1558 } 1559 defaultImageDirectory = fileChooser.getCurrentDirectory(); 1560 1561 } 1562 } 1563 1564 1565 protected class ImageFileFilter extends javax.swing.filechooser.FileFilter 1566 { 1567 public String getDescription() 1568 { 1569 return("image file"); 1570 } 1571 public boolean accept(File f) 1572 { 1573 return(f.toString().endsWith(".gif") || f.toString().endsWith(".GIF")|| 1574 f.toString().endsWith(".png") || f.toString().endsWith(".PNG")|| 1575 f.toString().endsWith(".jpg") || f.toString().endsWith(".JPG")|| 1576 f.toString().endsWith(".jpeg") || f.toString().endsWith(".JPEG")|| 1577 f.isDirectory()); // so directories will be displayed. 1578 } 1579 } 1580 1581 protected class ImageFileChooser extends JFileChooser implements PropertyChangeListener 1582 { 1583 ImageFileChooser() 1584 { 1585 super(); 1586 removeChoosableFileFilter(getAcceptAllFileFilter()); 1587 // This means that if a directory is opened it is navigated to. 1588 setFileSelectionMode(FILES_ONLY); 1589 setFileFilter(new ImageFileFilter()); 1590 // This means that when the directory is opened its name is not retained 1591 // as the selected file. 1592 addPropertyChangeListener(DIRECTORY_CHANGED_PROPERTY, this); 1593 } 1594 public void propertyChange(PropertyChangeEvent event) 1595 { 1596 setSelectedFile(null); 1597 } 1598 1599 } 1600 1601 1602 /** 1603 * Class to synchronize the graph elements when changes happen to 1604 * the ViewletData. */ 1605 protected class StoreListener implements ViewletDataStoreListener { 1606 public StoreListener() { 1607 } 1608 1609 public void rangeUpdated(ViewletDataStore store, 1610 ViewletRange range) { 1611 for(Iterator it = range.iterator(); it.hasNext(); ) { 1612 java.util.List index = (java.util.List)(it.next()); 1613 Element element = getElement(index); 1614 // configure the renderer 1615 ViewletType type = elementToViewletType(index); 1616// ViewletData data = 1617// (ViewletData)(store.getViewletDataAt(index)); 1618 type.customizeElement(store, index, element); 1619// node.setAttribute("label",data.toString()); 1620// java.awt.Component component = 1621// viewletType.getTableCellRenderer().getTableCellRendererComponent(null, data, false, false, 1, 1); 1622// node.setAttribute("color",component.getBackground()); 1623 } 1624 graph.repaint(); 1625 } 1626 } 1627 1628 /** 1629 * A graph whose getBoundingBox() method will return the same bounds 1630 * even if the underlying graph has changed, so long as the change 1631 * does not exceed the specified amount. 1632 * */ 1633 protected static class SteadyGraph extends Graph { 1634 double softMargin; 1635 1636 java.awt.geom.Rectangle2D oldBounds; 1637 1638 public SteadyGraph(String name, 1639 boolean directed, 1640 boolean strict, 1641 double softMargin) { 1642 super(name, directed, strict); 1643 this.softMargin = softMargin; 1644 } 1645 1646 public java.awt.geom.Rectangle2D resetBoundingBox() { 1647 oldBounds = null; 1648 return super.resetBoundingBox(); 1649 } 1650 1651 public void repaint() { 1652 if (DebuggingSupport.logMessages) { 1653 DebuggingSupport.logMessage(this, "SteadyGraph repaint call on thread="+Thread.currentThread()); 1654 } 1655 super.repaint(); 1656 if (DebuggingSupport.logMessages) { 1657 DebuggingSupport.logMessage(this, "SteadyGraph repaint finishing on thread="+Thread.currentThread()); 1658 } 1659 } 1660 1661 public java.awt.geom.Rectangle2D getBoundingBox() { 1662 java.awt.geom.Rectangle2D newBounds = super.getBoundingBox(); 1663 if (oldBounds == null) { 1664 oldBounds = newBounds; 1665 } else { 1666 double dx = Math.abs(newBounds.getWidth()-oldBounds.getWidth()); 1667 double dy = Math.abs(newBounds.getHeight()-oldBounds.getHeight()); 1668 if (DebuggingSupport.logMessages) { 1669 DebuggingSupport.logMessage(this, "SteadyGraph dx="+dx+" dy="+dy); 1670 } 1671 if ((dx > softMargin) || (dy > softMargin)) { 1672 oldBounds = newBounds; 1673 } 1674 } 1675 return oldBounds; 1676 } 1677 } 1678 1679 /** 1680 * Internal method to setup the background 1681 */ 1682 void setBackground(File file) { 1683 backer.setFile(file); 1684 graph.repaint(); 1685 } 1686 1687 /** 1688 * Action to load/clear a background image 1689 */ 1690 protected class SetBackgroundAction extends AbstractAction { 1691 protected GraphViewer viewer; 1692 1693 protected File defaultImageDirectory; 1694 /** If set to false, then clear the background, else load*/ 1695 protected boolean load; 1696 1697 /** 1698 * An action to load/clear the background of the viewer 1699 * @param viewer The GraphViewer to set the background of 1700 * @param load if "true" then query the user for a file to load, 1701 * if "false" clear the background. 1702 **/ 1703 public SetBackgroundAction(GraphViewer viewer, boolean load) { 1704 super((load)?"Import background image":"Clear background"); 1705 this.viewer = viewer; 1706 this.load = load; 1707 } 1708 1709 public void actionPerformed(ActionEvent event) { 1710 if (!load) { 1711 (new SetBackgroundCommand(viewer,null)).issue(); 1712 return; 1713 } 1714 JFileChooser fileChooser = new ImageFileChooser(); 1715 if(defaultImageDirectory != null) 1716 { 1717 fileChooser.setCurrentDirectory(defaultImageDirectory); 1718 } 1719 int returnVal = fileChooser.showOpenDialog(getComponent()); 1720 if(returnVal == JFileChooser.APPROVE_OPTION) 1721 { 1722 1723 if (DebuggingSupport.logMessages) { 1724 DebuggingSupport.logMessage(this, "image: "+ 1725 fileChooser.getSelectedFile()+ 1726 " approved"); 1727 } 1728 1729 if(fileChooser.getSelectedFile().exists()) 1730 { 1731 (new SetBackgroundCommand(viewer, 1732 fileChooser.getSelectedFile())).issue(); 1733 } 1734 else 1735 { 1736 JOptionPane. 1737 showMessageDialog(getComponent(), 1738 "File \n"+fileChooser.getSelectedFile().toString()+"\ndoes not exist", 1739 "Error in Visualisation Client", JOptionPane.ERROR_MESSAGE); 1740 } 1741 1742 } 1743 defaultImageDirectory = fileChooser.getCurrentDirectory(); 1744 1745 } 1746 } 1747 1748 /** 1749 * Command to set the background 1750 */ 1751 public static class SetBackgroundCommand extends ViewerCommand { 1752 File file; 1753 public SetBackgroundCommand(GraphViewer viewer, File file) { 1754 super(viewer); 1755 this.file = file;; 1756 } 1757 1758 public void postRecordIssue() { 1759 ((GraphViewer)getViewer()).setBackground(file); 1760 } 1761 } 1762 1763 1764 /** 1765 * Draws a background image behind the graph 1766 */ 1767 protected class GraphViewBacker implements GrappaBacker, GrappaConstants { 1768 Image image; 1769 1770 public GraphViewBacker() { 1771 image = null; 1772 } 1773 1774 public void setFile(File file) { 1775 if (DebuggingSupport.logMessages) { 1776 DebuggingSupport.logMessage(this, "loading image: "+file); 1777 } 1778 if ( file == null) { 1779 image = null; 1780 return; 1781 } 1782 1783 Image source = Toolkit.getDefaultToolkit().getImage(file.toString()); 1784 MediaTracker tracker = new MediaTracker(graphPanel); 1785 tracker.addImage(source, 0); 1786 try { 1787 tracker.waitForID(0); 1788 } catch(InterruptedException e) { 1789 JOptionPane.showMessageDialog(null, 1790 "Problem loading background image:"+e); 1791 return; 1792 } 1793// BufferedImage bsource = null; 1794 1795// try 1796// { 1797// bsource = 1798// new BufferedImage(source.getWidth(viewletDesktop), 1799// source.getHeight(viewletDesktop), 1800// BufferedImage.TYPE_INT_BGR); 1801// } 1802// catch(Exception e) 1803// { 1804// JOptionPane. 1805// showMessageDialog(getComponent(), 1806// "Image could not be loaded from file \n"+imageFile.toString(), 1807// "Error in Visualisation Client", JOptionPane.ERROR_MESSAGE); 1808// return; 1809// } 1810// bsource.createGraphics().drawImage(source, 0,0, viewletDesktop); 1811 if (tracker.isErrorID(0)) { 1812 JOptionPane.showMessageDialog(null, 1813 "Unknown error occurred loading background image."); 1814 return; 1815 } 1816 image = source; 1817 } 1818 1819 public void drawBackground(java.awt.Graphics2D g2d, 1820 Graph graph, 1821 java.awt.geom.Rectangle2D bbox, 1822 java.awt.Shape clip) { 1823 if (image != null) { 1824 Shape oldClip = g2d.getClip(); 1825 g2d.setClip(clip); 1826 g2d.drawImage(image, 1827 (int)bbox.getX(), 1828 (int)bbox.getY(), 1829 (int)bbox.getWidth(), 1830 (int)bbox.getHeight(), 1831 null); 1832 g2d.setClip(oldClip); 1833 } 1834 } 1835 } 1836 1837 1838 /** 1839 * Implement the Printable interface 1840 */ 1841 public int print(Graphics graphics, 1842 PageFormat pageFormat, 1843 int pageIndex) throws PrinterException { 1844 int result = graphPanel.print(graphics, pageFormat, pageIndex); 1845 JViewport headerViewport=scrollPane.getColumnHeader(); 1846 if (headerViewport!=null) { 1847 Component header = headerViewport.getView(); 1848 if (header!=null) { 1849 if (header instanceof Printable) { 1850 ((Printable)header).print(graphics, pageFormat, pageIndex); 1851 } 1852 } 1853 } 1854 return result; 1855 } 1856 1857 /** 1858 * Print the graph 1859 */ 1860 protected class PrintAction extends AbstractAction { 1861 private GraphViewer viewer; 1862 1863 PrintAction(GraphViewer viewer) 1864 { 1865 super("Print"); 1866 this.viewer = viewer; 1867 } 1868 1869 public void actionPerformed(ActionEvent event) 1870 { 1871 PageFormat pf = new PageFormat(); 1872 Rectangle2D bb = viewer.graph.getBoundingBox(); 1873 if(bb.getWidth() > bb.getHeight()) 1874 pf.setOrientation(PageFormat.LANDSCAPE); 1875 try { 1876 PrinterJob printJob = PrinterJob.getPrinterJob(); 1877 printJob.setPrintable(viewer, pf); 1878 if (printJob.printDialog()) { 1879 printJob.print(); 1880 } 1881 } catch (Exception ex) { 1882 Grappa.displayException(ex, "Problem with print request"); 1883 } 1884 } 1885 } 1886 1887 1888 1889 /** 1890 * Move the given node and all edges associated with it 1891 */ 1892 void moveNode(Node node, double dx, double dy) { 1893 GrappaPoint oldPos = (GrappaPoint)node.getAttributeValue("pos"); 1894 GrappaPoint newPos = new GrappaPoint(oldPos.x + dx, oldPos.y + dy); 1895 node.setAttribute("pos", newPos); 1896 // loop through all incoming edges 1897 for(Iterator<Edge> en = node.inEdgeElements(); en.hasNext(); ) { 1898 Edge edge = en.next(); 1899 GrappaLine line = (GrappaLine)edge.getAttributeValue("pos"); 1900 String lineStr = line.toAttributeString(); 1901 if (DebuggingSupport.logMessages) { 1902 DebuggingSupport.logMessage(this, "opdPos="+oldPos+" newPos=" + newPos + " edge="+ edge +" line="+lineStr + " edge.goesForward()="+edge.goesForward()); 1903 } 1904 // replace the e,X,Y substring with e,X+dx,Y+dy 1905 StringTokenizer st = new StringTokenizer(lineStr,", "); 1906 String points = lineStr.substring(lineStr.indexOf(' ')); 1907 // skip the first token 1908 String firstToken = st.nextToken(); 1909 String newLineStr = lineStr; 1910 if ("e".equals(firstToken)) { 1911 double newX = Double.parseDouble(st.nextToken()) + dx; 1912 double newY =(Double.parseDouble(st.nextToken()) - dy); 1913 newLineStr = "e"+","+ newX + "," + newY + points; 1914 } else if ("s".equals(firstToken)) { 1915 double newX = Double.parseDouble(st.nextToken()) + dx; 1916 double newY =(Double.parseDouble(st.nextToken()) - dy); 1917 newLineStr = "s"+","+ newX + "," + newY + points; 1918 } 1919 edge.setAttribute("pos", newLineStr); 1920 } 1921 // loop through all outgoing edges 1922 for(Iterator<Edge> en = node.outEdgeElements(); en.hasNext(); ) { 1923 Edge edge = en.next(); 1924 GrappaLine line = (GrappaLine)edge.getAttributeValue("pos"); 1925 String lineStr = line.toAttributeString(); 1926 StringTokenizer st = new StringTokenizer(lineStr,", "); 1927 if (DebuggingSupport.logMessages) { 1928 DebuggingSupport.logMessage(this, "opdPos="+oldPos+" newPos=" + newPos + " edge="+ edge +" line="+lineStr); 1929 } 1930 String newLineStr = lineStr; 1931 if (lineStr == null || lineStr.length()==0) { 1932 break; 1933 } 1934 if (lineStr.charAt(0) == 's') { 1935 // replace the final location on the line 1936 int tailPos = lineStr.lastIndexOf(' '); 1937 String firstPart = lineStr.substring(0,tailPos); 1938 int lastComma = lineStr.lastIndexOf(','); 1939 double newX = Double.parseDouble(lineStr.substring(tailPos,lastComma)) + dx; 1940 double newY =(Double.parseDouble(lineStr.substring(lastComma+1)) - dy); 1941 newLineStr = firstPart + ' ' + newX + ',' + newY; 1942 } else if (lineStr.charAt(0) == 'e') { 1943 String head = lineStr.substring(0, lineStr.indexOf(' ')); 1944 int tailStart = lineStr.indexOf(' ', head.length()+1); 1945 String tail = ""; 1946 if (tailStart >= 0) { 1947 tail = lineStr.substring(tailStart); 1948 } 1949 // skip the first three token 1950 st.nextToken(); 1951 st.nextToken(); 1952 st.nextToken(); 1953 // replace the point after the arrow location 1954 double newX = Double.parseDouble(st.nextToken()) + dx; 1955 double newY =(Double.parseDouble(st.nextToken()) - dy); 1956 newLineStr = head + " " +newX + "," + newY + tail; 1957 } 1958 edge.setAttribute("pos", newLineStr); 1959 } 1960 graph.resetBoundingBox(); 1961 graph.repaint(); 1962 } 1963 1964 /** 1965 * Converts an element name (ie the string form of the Index) into a 1966 * java.util.List object 1967 **/ 1968 java.util.List stringToIndexList(String s) { 1969 java.util.List l = new LinkedList(); 1970 StringTokenizer st = new StringTokenizer(s.substring(1,s.length()-1),", "); 1971 while(st.hasMoreTokens()) { 1972 String t = st.nextToken(); 1973 l.add(Integer.valueOf(t)); 1974 } 1975 return l; 1976 } 1977 1978 1979 /** 1980 * Listens to click and selection events on the graph pane 1981 */ 1982 protected static class GraphViewListener implements GrappaListener, GrappaConstants { 1983 1984 /** holds the viewer for which this listener is listening */ 1985 protected GraphViewer viewer; 1986 1987 /** holds the previous drag point. Used to calculate incremental 1988 position updates to selected elements when moving them.*/ 1989 protected GrappaPoint previousDragPoint; 1990 1991 /** 1992 * Setup the listener 1993 */ 1994 public GraphViewListener(GraphViewer viewer) { 1995 this.viewer = viewer; 1996 this.previousDragPoint = null; 1997 } 1998 1999 /** 2000 * The method called when a single mouse click occurs on a 2001 * displayed subgraph. 2002 * 2003 * @param subg displayed subgraph where action occurred 2004 * @param elem subgraph element in which action occurred 2005 * @param pt the point where the action occurred (graph coordinates) 2006 * @param modifiers mouse modifiers in effect 2007 * @param panel specific panel where the action occurred 2008 **/ 2009 public void grappaClicked(Subgraph subg, 2010 Element elem, 2011 GrappaPoint pt, 2012 int modifiers, 2013 int clickCount, 2014 GrappaPanel panel) { 2015 if((modifiers&InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) { 2016 if(clickCount == 1) { 2017 // looks like Java has a single click occur on the way to a 2018 // multiple click, so this code always executes (which is 2019 // not necessarily a bad thing) 2020 if(subg.getGraph().isSelectable()) { 2021 if(modifiers == InputEvent.BUTTON1_MASK) { 2022 // select element 2023 if(elem == null) { 2024 if(subg.currentSelection != null) { 2025 if(subg.currentSelection instanceof Element) { 2026 ((Element)(subg.currentSelection)).highlight &= ~HIGHLIGHT_MASK; 2027 } else { 2028 Vector vec = ((Vector)(subg.currentSelection)); 2029 for(int i = 0; i < vec.size(); i++) { 2030 ((Element)(vec.elementAt(i))).highlight &= ~HIGHLIGHT_MASK; 2031 } 2032 } 2033 subg.currentSelection = null; 2034 subg.getGraph().repaint(); 2035 } 2036 } else { 2037 if(subg.currentSelection != null) { 2038 if(subg.currentSelection == elem) return; 2039 if(subg.currentSelection instanceof Element) { 2040 ((Element)(subg.currentSelection)).highlight &= ~HIGHLIGHT_MASK; 2041 } else { 2042 Vector vec = ((Vector)(subg.currentSelection)); 2043 for(int i = 0; i < vec.size(); i++) { 2044 ((Element)(vec.elementAt(i))).highlight &= ~HIGHLIGHT_MASK; 2045 } 2046 } 2047 subg.currentSelection = null; 2048 } 2049 if (elem != subg) { 2050 // do not allow selection of the whole 2051 // (sub)graph 2052 elem.highlight |= SELECTION_MASK; 2053 subg.currentSelection = elem; 2054 } 2055 subg.getGraph().repaint(); 2056 } 2057 } else if(modifiers == (InputEvent.BUTTON1_MASK|InputEvent.CTRL_MASK)) { 2058 // adjust selection 2059 if(elem != null) { 2060 if((elem.highlight&SELECTION_MASK) == SELECTION_MASK) { 2061 // unselect element 2062 elem.highlight &= ~SELECTION_MASK; 2063 if(subg.currentSelection == null) { 2064 // something got messed up somewhere 2065 throw new InternalError("currentSelection improperly maintained"); 2066 } else if(subg.currentSelection instanceof Element) { 2067 if(((Element)(subg.currentSelection)) != elem) { 2068 // something got messed up somewhere 2069 throw new InternalError("currentSelection improperly maintained"); 2070 } 2071 subg.currentSelection = null; 2072 } else { 2073 Vector vec = ((Vector)(subg.currentSelection)); 2074 boolean problem = true; 2075 for(int i = 0; i < vec.size(); i++) { 2076 if(((Element)(vec.elementAt(i))) == elem) { 2077 vec.removeElementAt(i); 2078 problem = false; 2079 break; 2080 } 2081 } 2082 if(problem) { 2083 // something got messed up somewhere 2084 throw new InternalError("currentSelection improperly maintained"); 2085 } 2086 } 2087 } else { 2088 // select element 2089 elem.highlight |= SELECTION_MASK; 2090 if(subg.currentSelection == null) { 2091 subg.currentSelection = elem; 2092 } else if(subg.currentSelection instanceof Element) { 2093 Object obj = subg.currentSelection; 2094 subg.currentSelection = new Vector(); 2095 ((Vector)(subg.currentSelection)).add(obj); 2096 ((Vector)(subg.currentSelection)).add(elem); 2097 } else { 2098 ((Vector)(subg.currentSelection)).add(elem); 2099 } 2100 } 2101 subg.getGraph().repaint(); 2102 } 2103 } 2104 } 2105 } else { 2106 // multiple clicks 2107 // this code executes for each click beyond the first 2108 //System.err.println("clickCount="+clickCount); 2109 } 2110 } 2111 } 2112 2113 /** 2114 * 2115 * The method called when a mouse press occurs on a displayed subgraph. 2116 * 2117 * @param subg displayed subgraph where action occurred 2118 * @param elem subgraph element in which action occurred 2119 * @param pt the point where the action occurred (graph coordinates) 2120 * @param modifiers mouse modifiers in effect 2121 * @param panel specific panel where the action occurred 2122 **/ 2123 public void grappaPressed(Subgraph subg, 2124 Element elem, 2125 GrappaPoint pt, 2126 int modifiers, 2127 GrappaPanel panel) { 2128 if(modifiers == InputEvent.BUTTON1_MASK && subg.getGraph().isSelectable()) { 2129 previousDragPoint = null; 2130 } 2131 } 2132 2133 /** 2134 * 2135 * The method called when a mouse release occurs on a displayed subgraph. 2136 * 2137 * @param subg displayed subgraph where action occurred 2138 * @param elem subgraph element in which action occurred 2139 * @param pt the point where the action occurred (graph coordinates) 2140 * @param modifiers mouse modifiers in effect 2141 * @param pressedElem subgraph element in which the most recent mouse press occurred 2142 * @param pressedPt the point where the most recent mouse press occurred (graph coordinates) 2143 * @param pressedModifiers mouse modifiers in effect when the most recent mouse press occurred 2144 * @param outline enclosing box specification from the previous drag position (for XOR reset purposes) 2145 * @param panel specific panel where the action occurred 2146 **/ 2147 public void grappaReleased(Subgraph subg, 2148 Element elem, 2149 GrappaPoint pt, 2150 int modifiers, 2151 Element pressedElem, 2152 GrappaPoint pressedPt, 2153 int pressedModifiers, 2154 GrappaBox outline, 2155 GrappaPanel panel) { 2156 if(modifiers == InputEvent.BUTTON1_MASK && subg.getGraph().isSelectable()) { 2157 if(outline != null) { 2158 boolean xorOutline = false; 2159 if(subg.currentSelection != null) { 2160 if(subg.currentSelection instanceof Element) { 2161 ((Element)(subg.currentSelection)).highlight = 0; 2162 } else { 2163 Vector vec = ((Vector)(subg.currentSelection)); 2164 for(int i = 0; i < vec.size(); i++) { 2165 ((Element)(vec.elementAt(i))).highlight = 0; 2166 } 2167 } 2168 subg.currentSelection = null; 2169 } 2170 List elems = GrappaSupport.findContainedElements(subg, outline); 2171 if(elems != null) { 2172 drillDown(subg, elems, Grappa.SELECTION_MASK, Grappa.HIGHLIGHT_ON); 2173 xorOutline = false; 2174 } 2175 if(!xorOutline) { 2176 subg.getGraph().paintImmediately(); 2177 } 2178 if(xorOutline) { 2179 Graphics2D g2d = (Graphics2D)(panel.getGraphics()); 2180 AffineTransform orig = g2d.getTransform(); 2181 g2d.setTransform(panel.getTransform()); 2182 g2d.setXORMode(Color.darkGray); 2183 g2d.draw(outline); 2184 g2d.setPaintMode(); 2185 g2d.setTransform(orig); 2186 } 2187 } 2188 } else if(modifiers == (InputEvent.BUTTON1_MASK|InputEvent.CTRL_MASK) && subg.getGraph().isSelectable()) { 2189 if(outline != null) { 2190 List elems = GrappaSupport.findContainedElements(subg, outline); 2191 if(elems != null) { 2192 drillDown(subg, elems, Grappa.SELECTION_MASK, Grappa.HIGHLIGHT_TOGGLE); 2193 subg.getGraph().repaint(); 2194 } else { 2195 Graphics2D g2d = (Graphics2D)(panel.getGraphics()); 2196 AffineTransform orig = g2d.getTransform(); 2197 g2d.setTransform(panel.getTransform()); 2198 g2d.setXORMode(Color.darkGray); 2199 g2d.draw(outline); 2200 g2d.setPaintMode(); 2201 g2d.setTransform(orig); 2202 } 2203 } 2204 } else if(modifiers == (InputEvent.BUTTON1_MASK|InputEvent.SHIFT_MASK) && subg.getGraph().isEditable()) { 2205 if(elem != null && pressedElem != null) { 2206 if(pressedModifiers == modifiers) { 2207 if(outline == null) { 2208 if(pressedElem == elem && pt.distance(pressedPt) < 5) { 2209 // [should we only allow elem.isSubgraph()?] 2210 // create node 2211 Attribute[] attrs = null; 2212 Attribute attr = subg.getNodeAttribute(Grappa.LABEL_ATTR); 2213 if(attr == null || attr.getValue().equals("\\N")) { 2214 attrs = new Attribute[] { new Attribute(Grappa.NODE, POS_ATTR, pt), new Attribute(Grappa.NODE, LABEL_ATTR, "Node" + subg.getGraph().getId(Grappa.NODE)) }; 2215 } else { 2216 attrs = new Attribute[] { new Attribute(Grappa.NODE, POS_ATTR, pt) }; 2217 } 2218 Element el = subg.createElement(Grappa.NODE,null,attrs); 2219 if(el != null) { 2220 el.buildShape(); 2221 subg.getGraph().repaint(); 2222 } 2223 subg.getGraph().repaint(); 2224 } else if(pressedElem != elem && pressedElem.isNode() && elem.isNode()) { 2225 // create edge 2226 Object[] info = new Object[] { elem, null, pressedElem }; 2227 Attribute[] attrs = new Attribute[] { new Attribute(Grappa.EDGE, POS_ATTR, new GrappaLine(new GrappaPoint[] { ((Node)pressedElem).getCenterPoint(), ((Node)elem).getCenterPoint() }, subg.getGraph().isDirected()?GrappaLine.TAIL_ARROW_EDGE:GrappaLine.NONE_ARROW_EDGE) ) }; 2228 Element el = subg.createElement(Grappa.EDGE,info,attrs); 2229 if(el != null) { 2230 el.buildShape(); 2231 subg.getGraph().repaint(); 2232 } 2233 } 2234 } 2235 } 2236 } 2237 } 2238 } 2239 2240 /** 2241 * 2242 * The method called when a mouse drag occurs on a displayed subgraph. 2243 * 2244 * @param subg displayed subgraph where action occurred 2245 * @param currentPt the current drag point 2246 * @param currentModifiers the current drag mouse modifiers 2247 * @param pressedElem subgraph element in which the most recent mouse press occurred 2248 * @param pressedPt the point where the most recent mouse press occurred (graph coordinates) 2249 * @param pressedModifiers mouse modifiers in effect when the most recent mouse press occurred 2250 * @param outline enclosing box specification from the previous drag position (for XOR reset purposes) 2251 * @param panel specific panel where the action occurred 2252 **/ 2253 public void grappaDragged(Subgraph subg, 2254 GrappaPoint currentPt, 2255 int currentModifiers, 2256 Element pressedElem, 2257 GrappaPoint pressedPt, 2258 int pressedModifiers, 2259 GrappaBox outline, 2260 GrappaPanel panel) { 2261 if((currentModifiers&InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) { 2262 if ((pressedElem != null) && (pressedElem != subg) && 2263 ((currentModifiers & InputEvent.CTRL_MASK) == 0) && 2264 viewer.moveable) { 2265 // move the selected elements 2266 double dx = 0.0; 2267 double dy = 0.0; 2268 if ( previousDragPoint != null) { 2269 dx = currentPt.x - previousDragPoint.x; 2270 dy = currentPt.y - previousDragPoint.y; 2271 } 2272 previousDragPoint = currentPt; 2273 if (DebuggingSupport.logMessages) { 2274 DebuggingSupport.logMessage(this, "Moving dx="+dx+" dy="+dy+" currentSelection="+subg.currentSelection); 2275 } 2276 if(subg.currentSelection != null) { 2277 if (subg.currentSelection instanceof Subgraph) { 2278 // move the whole sub-graph 2279 } else if(subg.currentSelection instanceof Element) { 2280 Element element = ((Element)(subg.currentSelection)); 2281// GrappaPoint oldPos = (GrappaPoint)element.getAttributeValue("pos"); 2282// GrappaPoint newPos = new GrappaPoint(oldPos.x + dx, oldPos.y + dy); 2283// element.setAttribute("pos", newPos); 2284 if (element instanceof Node) { 2285 new MoveNodeCommand(viewer, (Node)element, dx, dy).issue(); 2286 } 2287 } else { 2288 Vector vec = ((Vector)(subg.currentSelection)); 2289 for(int i = 0; i < vec.size(); i++) { 2290 Element element = ((Element)(vec.elementAt(i))); 2291 if (element instanceof Node) { 2292 new MoveNodeCommand(viewer, (Node)element, dx, dy).issue(); 2293 } 2294// GrappaPoint oldPos = (GrappaPoint)element.getAttributeValue("pos"); 2295// GrappaPoint newPos = new GrappaPoint(oldPos.x + dx, oldPos.y + dy); 2296// element.setAttribute("pos", newPos); 2297 } 2298 } 2299 //subg.getGraph().resetBoundingBox(); 2300 //subg.getGraph().repaint(); 2301 } 2302 } else if(currentModifiers == InputEvent.BUTTON1_MASK || currentModifiers == (InputEvent.BUTTON1_MASK|InputEvent.CTRL_MASK)) { 2303 // draw the selection box 2304 Graphics2D g2d = (Graphics2D)(panel.getGraphics()); 2305 AffineTransform orig = g2d.getTransform(); 2306 g2d.setTransform(panel.getTransform()); 2307 g2d.setXORMode(Color.darkGray); 2308 if(outline != null) { 2309 g2d.draw(outline); 2310 } 2311 GrappaBox box = GrappaSupport.boxFromCorners(pressedPt.x, pressedPt.y, currentPt.x, currentPt.y); 2312 g2d.draw(box); 2313 g2d.setPaintMode(); 2314 g2d.setTransform(orig); 2315 } 2316 } 2317 } 2318 2319 /** 2320 * 2321 * The method called when a element tooltip is needed. 2322 * 2323 * @param subg displayed subgraph where action occurred 2324 * @param elem subgraph element in which action occurred 2325 * @param pt the point where the action occurred (graph coordinates) 2326 * @param modifiers mouse modifiers in effect 2327 * @param panel specific panel where the action occurred 2328 * @return the tip to be displayed or null 2329 **/ 2330 public String grappaTip(Subgraph subg, 2331 Element elem, 2332 GrappaPoint pt, 2333 int modifiers, 2334 GrappaPanel panel) { 2335 if (elem==null) { 2336 return ""; 2337 } 2338 String name = elem.getName(); 2339 if (DebuggingSupport.logMessages) { 2340 DebuggingSupport.logMessage(this,"grappaTip name="+name); 2341 } 2342 java.util.List index = null; 2343 try { 2344 index = viewer.stringToIndexList(name); 2345 } catch(NumberFormatException nfe) { 2346 return null; 2347 } 2348 if (DebuggingSupport.logMessages) { 2349 DebuggingSupport.logMessage(this,"grappaTip index="+index); 2350 } 2351 String data = viewer.getToolTip(index); 2352 //ViewletData data = viewer.viewletDataStore.getViewletDataAt(index); 2353 if (data == null) { 2354 return ""; 2355 } 2356 return data.toString(); 2357 } 2358 2359 2360 protected void drillDown(Subgraph subg, List elems, int mode, int setting) { 2361 for(Object obj: elems) { 2362 if(obj instanceof Vector) { 2363 drillDown(subg, (Vector)obj, mode, setting); 2364 } else { 2365 GrappaSupport.setHighlight(((Element)obj), mode, setting); 2366 switch(setting) { 2367 case HIGHLIGHT_TOGGLE: 2368 if((((Element)obj).highlight&mode) == mode) { 2369 if(subg.currentSelection == null) { 2370 subg.currentSelection = obj; 2371 } else if(subg.currentSelection instanceof Element) { 2372 Object crnt = subg.currentSelection; 2373 subg.currentSelection = new Vector(); 2374 ((Vector)(subg.currentSelection)).add(crnt); 2375 ((Vector)(subg.currentSelection)).add(obj); 2376 } else { 2377 ((Vector)(subg.currentSelection)).add(obj); 2378 } 2379 } else { 2380 if(subg.currentSelection == obj) { 2381 subg.currentSelection = null; 2382 } else if(subg.currentSelection instanceof Vector) { 2383 ((Vector)(subg.currentSelection)).remove(obj); 2384 } 2385 } 2386 break; 2387 case HIGHLIGHT_ON: 2388 if(subg.currentSelection == null) { 2389 subg.currentSelection = obj; 2390 } else if(subg.currentSelection instanceof Element) { 2391 Object crnt = subg.currentSelection; 2392 subg.currentSelection = new Vector(); 2393 ((Vector)(subg.currentSelection)).add(crnt); 2394 ((Vector)(subg.currentSelection)).add(obj); 2395 } else { 2396 ((Vector)(subg.currentSelection)).add(obj); 2397 } 2398 break; 2399 case HIGHLIGHT_OFF: 2400 if(subg.currentSelection != null) { 2401 if(subg.currentSelection == obj) { 2402 subg.currentSelection = null; 2403 } else if(subg.currentSelection instanceof Vector) { 2404 ((Vector)(subg.currentSelection)).remove(obj); 2405 } 2406 } 2407 break; 2408 } 2409 } 2410 } 2411 } 2412 } 2413} 2414 2415