• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /barrelfish-2018-10-04/usr/eclipseclp/Visualisation/src/com/parctechnologies/eclipse/visualisation/
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