001/* A simple graph view for Ptolemy models
002
003 Copyright (c) 1998-2018 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 2
027 */
028package ptolemy.vergil.basic;
029
030import java.awt.BorderLayout;
031import java.awt.Color;
032import java.awt.Component;
033import java.awt.Cursor;
034import java.awt.Dimension;
035import java.awt.Event;
036import java.awt.FileDialog;
037import java.awt.FlowLayout;
038import java.awt.Frame;
039import java.awt.Graphics;
040import java.awt.GridBagConstraints;
041import java.awt.GridBagLayout;
042import java.awt.Toolkit;
043import java.awt.datatransfer.Clipboard;
044import java.awt.datatransfer.ClipboardOwner;
045import java.awt.datatransfer.DataFlavor;
046import java.awt.datatransfer.StringSelection;
047import java.awt.datatransfer.Transferable;
048import java.awt.event.ActionEvent;
049import java.awt.event.ActionListener;
050import java.awt.event.KeyEvent;
051import java.awt.event.MouseAdapter;
052import java.awt.event.MouseEvent;
053import java.awt.event.MouseListener;
054import java.awt.event.MouseMotionListener;
055import java.awt.event.MouseWheelEvent;
056import java.awt.event.MouseWheelListener;
057import java.awt.geom.AffineTransform;
058import java.awt.geom.NoninvertibleTransformException;
059import java.awt.geom.Point2D;
060import java.awt.geom.Rectangle2D;
061import java.awt.print.PageFormat;
062import java.awt.print.Printable;
063import java.awt.print.PrinterException;
064import java.io.File;
065import java.io.FileOutputStream;
066import java.io.FileWriter;
067import java.io.IOException;
068import java.io.OutputStream;
069import java.io.StringWriter;
070import java.io.Writer;
071import java.lang.reflect.Constructor;
072import java.lang.reflect.Method;
073import java.net.URL;
074import java.util.Arrays;
075import java.util.HashSet;
076import java.util.Iterator;
077import java.util.LinkedList;
078import java.util.List;
079import java.util.Locale;
080import java.util.Stack;
081
082import javax.swing.AbstractAction;
083import javax.swing.Action;
084import javax.swing.BorderFactory;
085import javax.swing.JComponent;
086import javax.swing.JFileChooser;
087import javax.swing.JLabel;
088import javax.swing.JMenu;
089import javax.swing.JMenuItem;
090import javax.swing.JPanel;
091import javax.swing.JScrollPane;
092import javax.swing.JSplitPane;
093import javax.swing.JTabbedPane;
094import javax.swing.JTextField;
095import javax.swing.JToolBar;
096import javax.swing.JTree;
097import javax.swing.KeyStroke;
098import javax.swing.filechooser.FileFilter;
099import javax.swing.tree.DefaultTreeCellRenderer;
100import javax.swing.tree.TreePath;
101
102import diva.canvas.CanvasComponent;
103import diva.canvas.CanvasUtilities;
104import diva.canvas.Figure;
105import diva.canvas.FigureLayer;
106import diva.canvas.JCanvas;
107import diva.canvas.event.EventLayer;
108import diva.canvas.event.LayerAdapter;
109import diva.canvas.event.LayerEvent;
110import diva.canvas.interactor.SelectionModel;
111import diva.graph.GraphController;
112import diva.graph.GraphEvent;
113import diva.graph.GraphModel;
114import diva.graph.GraphPane;
115import diva.graph.GraphUtilities;
116import diva.graph.JGraph;
117import diva.gui.GUIUtilities;
118import diva.gui.toolbox.JCanvasPanner;
119import diva.gui.toolbox.JContextMenu;
120import diva.util.Filter;
121import diva.util.UserObjectContainer;
122import diva.util.java2d.ShapeUtilities;
123import ptolemy.actor.Actor;
124import ptolemy.actor.DesignPatternGetMoMLAction;
125import ptolemy.actor.Director;
126import ptolemy.actor.IOPort;
127import ptolemy.actor.IORelation;
128import ptolemy.actor.QuasiTransparentDirector;
129import ptolemy.actor.gui.BrowserEffigy;
130import ptolemy.actor.gui.Configuration;
131import ptolemy.actor.gui.DialogTableau;
132import ptolemy.actor.gui.EditParametersDialog;
133import ptolemy.actor.gui.Effigy;
134import ptolemy.actor.gui.LevelSkippingTableauFactory;
135import ptolemy.actor.gui.PtolemyFrame;
136import ptolemy.actor.gui.PtolemyPreferences;
137import ptolemy.actor.gui.SizeAttribute;
138import ptolemy.actor.gui.Tableau;
139import ptolemy.actor.gui.UserActorLibrary;
140import ptolemy.actor.gui.WindowPropertiesAttribute;
141import ptolemy.actor.gui.properties.ToolBar;
142import ptolemy.actor.parameters.ParameterPort;
143import ptolemy.data.ArrayToken;
144import ptolemy.data.DoubleToken;
145import ptolemy.data.StringToken;
146import ptolemy.data.Token;
147import ptolemy.data.expr.ExpertParameter;
148import ptolemy.data.expr.Parameter;
149import ptolemy.data.expr.StringParameter;
150import ptolemy.gui.ComponentDialog;
151import ptolemy.gui.ExtensionFilenameFilter;
152import ptolemy.gui.ImageExportable;
153import ptolemy.gui.JFileChooserBugFix;
154import ptolemy.gui.MemoryCleaner;
155import ptolemy.gui.PtFileChooser;
156import ptolemy.gui.PtGUIUtilities;
157import ptolemy.gui.Query;
158import ptolemy.gui.Top;
159import ptolemy.kernel.ComponentEntity;
160import ptolemy.kernel.CompositeEntity;
161import ptolemy.kernel.Entity;
162import ptolemy.kernel.undo.RedoChangeRequest;
163import ptolemy.kernel.undo.UndoChangeRequest;
164import ptolemy.kernel.util.Attribute;
165import ptolemy.kernel.util.ChangeListener;
166import ptolemy.kernel.util.ChangeRequest;
167import ptolemy.kernel.util.IllegalActionException;
168import ptolemy.kernel.util.InternalErrorException;
169import ptolemy.kernel.util.KernelException;
170import ptolemy.kernel.util.Location;
171import ptolemy.kernel.util.NameDuplicationException;
172import ptolemy.kernel.util.NamedObj;
173import ptolemy.kernel.util.Settable;
174import ptolemy.kernel.util.StringAttribute;
175import ptolemy.kernel.util.Workspace;
176import ptolemy.moml.ErrorHandler;
177import ptolemy.moml.IconLoader;
178import ptolemy.moml.LibraryAttribute;
179import ptolemy.moml.MoMLChangeRequest;
180import ptolemy.moml.MoMLParser;
181import ptolemy.moml.MoMLVariableChecker;
182import ptolemy.moml.SimpleErrorHandler;
183import ptolemy.util.CancelException;
184import ptolemy.util.MessageHandler;
185import ptolemy.util.SimpleMessageHandler;
186import ptolemy.vergil.icon.DesignPatternIcon;
187import ptolemy.vergil.kernel.AttributeNodeModel;
188import ptolemy.vergil.modal.FSMGraphModel;
189import ptolemy.vergil.toolbox.MenuItemFactory;
190import ptolemy.vergil.toolbox.MoveAction;
191import ptolemy.vergil.tree.ClassAndEntityTreeModel;
192import ptolemy.vergil.tree.EntityTreeModel;
193import ptolemy.vergil.tree.PTree;
194import ptolemy.vergil.tree.PTreeMenuCreator;
195import ptolemy.vergil.tree.PtolemyTreeCellRenderer;
196import ptolemy.vergil.tree.VisibleTreeModel;
197
198///////////////////////////////////////////////////////////////////
199//// BasicGraphFrame
200
201/**
202 A simple graph view for ptolemy models.  This represents a level of
203 the hierarchy of a ptolemy model as a diva graph.  Cut, copy and
204 paste operations are supported using MoML.
205
206 @author  Steve Neuendorffer, Edward A. Lee, Contributors: Chad Berkeley (Kepler), Ian Brown (HSBC), Bert Rodiers, Christian Motika, Daniel Crawl
207 @version $Id$
208 @since Ptolemy II 2.0
209 @Pt.ProposedRating Red (neuendor)
210 @Pt.AcceptedRating Red (johnr)
211 */
212@SuppressWarnings("serial")
213public abstract class BasicGraphFrame extends PtolemyFrame implements Printable,
214        ClipboardOwner, ChangeListener, MouseWheelListener, MouseListener,
215        MouseMotionListener, ImageExportable, HTMLExportable {
216
217    /** Construct a frame associated with the specified Ptolemy II model
218     *  or object. After constructing this, it is necessary
219     *  to call setVisible(true) to make the frame appear.
220     *  This is typically done by calling show() on the controlling tableau.
221     *  This constructor results in a graph frame that obtains its library
222     *  either from the model (if it has one) or the default library defined
223     *  in the configuration.
224     *  @see Tableau#show()
225     *  @param entity The model or object to put in this frame.
226     *  @param tableau The tableau responsible for this frame.
227     */
228    public BasicGraphFrame(NamedObj entity, Tableau tableau) {
229        this(entity, tableau, null);
230    }
231
232    /** Construct a frame associated with the specified Ptolemy II model.
233     *  After constructing this, it is necessary
234     *  to call setVisible(true) to make the frame appear.
235     *  This is typically done by calling show() on the controlling tableau.
236     *  This constructor results in a graph frame that obtains its library
237     *  either from the model (if it has one), or the <i>defaultLibrary</i>
238     *  argument (if it is non-null), or the default library defined
239     *  in the configuration.
240     *  @see Tableau#show()
241     *  @param entity The model or object to put in this frame.
242     *  @param tableau The tableau responsible for this frame.
243     *  @param defaultLibrary An attribute specifying the default library
244     *   to use if the model does not have a library.   The <i>defaultLibrary</i>
245     *  attribute is only read if the model does not have a
246     *  {@link ptolemy.moml.LibraryAttribute} with the name
247     *  "<code>_library</code>", or if the LibraryAttribute cannot be
248     *  read.
249     */
250    public BasicGraphFrame(NamedObj entity, Tableau tableau,
251            LibraryAttribute defaultLibrary) {
252        super(entity, tableau);
253        _defaultLibrary = defaultLibrary;
254        _initBasicGraphFrame();
255    }
256
257    ///////////////////////////////////////////////////////////////////
258    ////                         public methods                    ////
259
260    /** React to the fact that a change has been successfully executed
261     *  by marking the data associated with this window modified.  This
262     *  will trigger a dialog when the window is closed, prompting the
263     *  user to save the data.
264     *  @param change The change that has been executed.
265     */
266    @Override
267    public void changeExecuted(ChangeRequest change) {
268        boolean persistent = true;
269
270        // If the change is null, do not mark the model modified,
271        // but do update the graph panner.
272        if (change != null) {
273            persistent = change.isPersistent();
274
275            // Note that we don't want to accidently reset to false here.
276            if (persistent) {
277                setModified(persistent);
278            }
279        }
280
281        if (_graphPanner != null) {
282            _graphPanner.repaint();
283        }
284    }
285
286    /** React to the fact that a change has triggered an error by
287     *  doing nothing (the effigy is also listening and will report
288     *  the error).
289     *  @param change The change that was attempted.
290     *  @param exception The exception that resulted.
291     */
292    @Override
293    public void changeFailed(ChangeRequest change, Exception exception) {
294        // Do not report if it has already been reported.
295        if (change == null) {
296            MessageHandler.error("Change failed", exception);
297        } else if (!change.isErrorReported()) {
298            change.setErrorReported(true);
299            MessageHandler.error("Change failed", exception);
300        }
301    }
302
303    /** Clear the selected objects in this frame.
304     */
305    public void clearSelection() {
306        GraphController controller = _getGraphController();
307        SelectionModel model = controller.getSelectionModel();
308        model.clearSelection();
309    }
310
311    /** Get the currently selected objects from this document, if any,
312     *  and place them on the clipboard in MoML format.
313     */
314    public void copy() {
315        HashSet<NamedObj> namedObjSet = _getSelectionSet();
316        StringWriter buffer = new StringWriter();
317
318        try {
319            NamedObj container = (NamedObj) _getGraphModel().getRoot();
320
321            // NOTE: The order in the model must be respected.
322            Iterator<NamedObj> elements = container
323                    .sortContainedObjects(namedObjSet).iterator();
324
325            while (elements.hasNext()) {
326                NamedObj element = elements.next();
327
328                // first level to avoid obnoxiousness with
329                // toplevel translations.
330                element.exportMoML(buffer, 0);
331            }
332
333            if (container instanceof CompositeEntity) {
334                buffer.write(((CompositeEntity) container).exportLinks(1,
335                        namedObjSet));
336            }
337
338            Clipboard clipboard = java.awt.Toolkit.getDefaultToolkit()
339                    .getSystemClipboard();
340
341            // The code below does not use a PtolemyTransferable,
342            // to work around
343            // a bug in the JDK that should be fixed as of jdk1.3.1.  The bug
344            // is that cut and paste through the system clipboard to native
345            // applications doesn't work unless you use string selection.
346            String momlToBeCopied = buffer.toString();
347            String variablesToBePrepended = "";
348            try {
349                MoMLVariableChecker variableChecker = new MoMLVariableChecker();
350                variablesToBePrepended = variableChecker
351                        .checkCopy(momlToBeCopied, container);
352            } catch (IllegalActionException ex) {
353                // Ignore, maybe the missing symbols will work out
354                // in the pasted context.
355            }
356            clipboard.setContents(new StringSelection(
357                    variablesToBePrepended + momlToBeCopied), this);
358
359        } catch (IOException ex) {
360            MessageHandler.error("Copy failed", ex);
361        }
362    }
363
364    /** Create a typed composite actor that contains the selected actors
365     *  and connections. The created typed composite actor is transparent.
366     *  The resulting topology is the same in the sense
367     *  of deep connections.
368     */
369    public void createHierarchy() {
370        GraphController controller = _getGraphController();
371        SelectionModel model = controller.getSelectionModel();
372        AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) controller
373                .getGraphModel();
374
375        List<Object> origSelection = Arrays.asList(model.getSelectionAsArray());
376        HashSet<Object> selection = new HashSet<Object>();
377        selection.addAll(origSelection);
378
379        // A set, because some objects may represent the same
380        // ptolemy object.
381        HashSet<NamedObj> namedObjSet = new HashSet<NamedObj>();
382        HashSet<Object> nodeSet = new HashSet<Object>();
383
384        StringBuffer newPorts = new StringBuffer();
385        StringBuffer extRelations = new StringBuffer();
386        StringBuffer extConnections = new StringBuffer();
387        StringBuffer intRelations = new StringBuffer();
388        StringBuffer intConnections = new StringBuffer();
389
390        HashSet<Object> selectedEdges = new HashSet<Object>();
391
392        // First get all the nodes.
393        try {
394            NamedObj container = (NamedObj) graphModel.getRoot();
395
396            if (!(container instanceof CompositeEntity)) {
397                // This is an internal error because a reasonable GUI should not
398                // provide access to this functionality.
399                throw new InternalErrorException(
400                        "Cannot create hierarchy if the container is not a CompositeEntity.");
401            }
402
403            CompositeEntity compositeActor = (CompositeEntity) container;
404
405            String compositeActorName = container.uniqueName("CompositeActor");
406
407            double[] location = new double[2];
408            boolean gotLocation = false;
409
410            for (Object selectedItem : origSelection) {
411                if (selectedItem instanceof Figure) {
412                    Object userObject = ((Figure) selectedItem).getUserObject();
413
414                    // We want to skip the selection of ports in composite actors since
415                    //  these should remain in the already existing composite actor.
416                    // When the wire has been selected the port will be correctly duplicated.
417                    if (userObject instanceof Location) {
418                        Location loc = (Location) userObject;
419                        NamedObj locationContainer = loc.getContainer();
420                        if (locationContainer != null
421                                && locationContainer instanceof IOPort) {
422                            NamedObj portContainer = locationContainer
423                                    .getContainer();
424                            if (portContainer != null
425                                    && portContainer instanceof CompositeEntity) {
426                                // Remove element from selection
427                                model.removeSelection(selectedItem);
428                                selection.remove(selectedItem);
429
430                                // Don't process this node.
431                                continue;
432                            }
433                        }
434
435                    }
436
437                    if (!gotLocation) {
438                        location[0] = ((Figure) selectedItem).getBounds()
439                                .getCenterX();
440                        location[1] = ((Figure) selectedItem).getBounds()
441                                .getCenterY();
442                        gotLocation = true;
443                    }
444
445                    if (graphModel.isNode(userObject)) {
446                        nodeSet.add(userObject);
447
448                        NamedObj actual = (NamedObj) graphModel
449                                .getSemanticObject(userObject);
450                        namedObjSet.add(actual);
451
452                        // We will now add all links from and to the ports of the actor
453                        //      as selected links since we don't want to lose links and relations when creating
454                        //      hierarchies
455
456                        if (actual instanceof Entity) {
457                            for (IOPort port : (List<IOPort>) ((Entity) actual)
458                                    .portList()) {
459                                Iterator<?> outEdges = graphModel
460                                        .outEdges(port);
461                                while (outEdges.hasNext()) {
462                                    Object obj = outEdges.next();
463                                    selectedEdges.add(obj);
464                                    selection.add(controller.getFigure(obj));
465                                }
466                                Iterator<?> inEdges = graphModel.inEdges(port);
467                                while (inEdges.hasNext()) {
468                                    Object obj = inEdges.next();
469                                    selectedEdges.add(obj);
470                                    selection.add(controller.getFigure(obj));
471                                }
472                            }
473                        }
474                    } else if (graphModel.isEdge(userObject)) {
475                        selectedEdges.add(userObject);
476                    }
477                }
478            }
479
480            int i = 0;
481            for (Object userObject : selectedEdges) {
482                assert graphModel.isEdge(userObject);
483                // Check to see if the head and tail are both being
484                // selected.
485                Object head = graphModel.getHead(userObject);
486
487                // System.out.println("head:" +((NamedObj)head).getName());
488                Object tail = graphModel.getTail(userObject);
489
490                // System.out.println("tail:" +((NamedObj)tail).getName());
491                boolean headOK = nodeSet.contains(head);
492                boolean tailOK = nodeSet.contains(tail);
493                Iterator<Object> objects = nodeSet.iterator();
494
495                while (!(headOK && tailOK) && objects.hasNext()) {
496                    Object object = objects.next();
497
498                    if (!headOK && GraphUtilities.isContainedNode(head, object,
499                            graphModel)) {
500                        headOK = true;
501                    }
502
503                    if (!tailOK && GraphUtilities.isContainedNode(tail, object,
504                            graphModel)) {
505                        tailOK = true;
506                    }
507                }
508
509                // For the edges at the boundary.
510                if (!headOK && tailOK || headOK && !tailOK) {
511
512                    LinkElementProperties headProperties = LinkElementProperties
513                            .extractLinkProperties(head);
514                    LinkElementProperties tailProperties = LinkElementProperties
515                            .extractLinkProperties(tail);
516
517                    if (headProperties.port == null
518                            && tailProperties.port != null) {
519                        //Swap head and tail
520                        LinkElementProperties temp = headProperties;
521                        headProperties = tailProperties;
522                        tailProperties = temp;
523                    }
524
525                    IORelation relation = null;
526
527                    boolean duplicateRelation = true;
528                    if (headProperties.type == ElementInLinkType.RELATION) {
529                        relation = (IORelation) graphModel
530                                .getSemanticObject(headProperties.element);
531                        duplicateRelation = false;
532                    } else if (tailProperties.type == ElementInLinkType.RELATION) {
533                        relation = (IORelation) graphModel
534                                .getSemanticObject(tailProperties.element);
535                        duplicateRelation = false;
536                    } else {
537                        relation = (IORelation) graphModel
538                                .getSemanticObject(userObject);
539                        duplicateRelation = true;
540                    }
541
542                    if (headProperties.port != null) {
543                        ComponentEntity entity = (ComponentEntity) headProperties.port
544                                .getContainer();
545                        String portName = "port_" + i;
546                        boolean isInput = headProperties.port.isInput();
547                        boolean isOutput = headProperties.port.isOutput();
548                        newPorts.append("<port name=\"" + portName
549                                + "\" class=\"ptolemy.actor.TypedIOPort"
550                                + "\">\n");
551
552                        if (headProperties.port.isMultiport()) {
553                            newPorts.append("<property name=\"multiport\"/>\n");
554                        }
555
556                        if (namedObjSet.contains(entity)) {
557                            // The port is inside the hierarchy.
558                            // The relation must be outside.
559                            // Create composite port.
560                            if (isInput) {
561                                newPorts.append("<property name=\"input\"/>");
562                            }
563
564                            if (isOutput) {
565                                newPorts.append("<property name=\"output\"/>");
566                            }
567
568                            newPorts.append("\n</port>\n");
569
570                            // Create internal relation and links.
571                            // Note we can only partially reuse
572                            // the relation name, one original relation
573                            // can be two internal relations.
574                            String relationName = relation.getName() + "_" + i;
575                            intRelations.append("<relation name=\""
576                                    + relationName + "\" class=\""
577                                    + "ptolemy.actor.TypedIORelation\"/>\n");
578                            intConnections.append(
579                                    "<link port=\"" + entity.getName() + "."
580                                            + headProperties.port.getName()
581                                            + "\" relation=\"" + relationName
582                                            + "\"/>\n");
583                            intConnections.append("<link port=\"" + portName
584                                    + "\" relation=\"" + relationName
585                                    + "\"/>\n");
586
587                            // Create external links.
588                            if (duplicateRelation) {
589                                extRelations.append("<relation name=\""
590                                        + relation.getName() + "\" class=\""
591                                        + "ptolemy.actor.TypedIORelation\"/>\n");
592
593                                ComponentEntity otherEntity = (ComponentEntity) tailProperties.port
594                                        .getContainer();
595
596                                if (otherEntity == container) {
597                                    // This is a boundary port at a higher level.
598                                    extConnections.append("<link port=\""
599                                            + tailProperties.port.getName()
600                                            + "\" relation=\""
601                                            + relation.getName() + "\"/>\n");
602                                } else {
603                                    extConnections.append("<link port=\""
604                                            + otherEntity.getName() + "."
605                                            + tailProperties.port.getName()
606                                            + "\" relation=\""
607                                            + relation.getName() + "\"/>\n");
608                                }
609                            }
610
611                            extConnections
612                                    .append("<link port=\"" + compositeActorName
613                                            + "." + portName + "\" relation=\""
614                                            + relation.getName() + "\"/>\n");
615                        } else {
616                            // The port is outside the hierarchy.
617                            // The relation must be inside.
618                            if (isInput
619                                    && headProperties.type == ElementInLinkType.PORT_IN_ACTOR
620                                    || isOutput
621                                            && headProperties.type == ElementInLinkType.STANDALONE_PORT) {
622                                newPorts.append("<property name=\"output\"/>");
623                            }
624
625                            if (isOutput
626                                    && headProperties.type == ElementInLinkType.PORT_IN_ACTOR
627                                    || isInput
628                                            && headProperties.type == ElementInLinkType.STANDALONE_PORT) {
629                                newPorts.append("<property name=\"input\"/>");
630                            }
631
632                            newPorts.append("\n</port>\n");
633
634                            String relationName = relation.getName() + "_" + i;
635                            extRelations.append("<relation name=\""
636                                    + relationName + "\" class=\""
637                                    + "ptolemy.actor.TypedIORelation\"/>\n");
638                            String entityPrefix = "";
639                            if (getModel() != entity) {
640                                entityPrefix = entity.getName() + ".";
641                            }
642                            extConnections.append("<link port=\"" + entityPrefix
643                                    + headProperties.port.getName()
644                                    + "\" relation=\"" + relationName
645                                    + "\"/>\n");
646                            extConnections
647                                    .append("<link port=\"" + compositeActorName
648                                            + "." + portName + "\" relation=\""
649                                            + relationName + "\"/>\n");
650
651                            // Create external links.
652                            if (duplicateRelation) {
653                                intRelations.append("<relation name=\""
654                                        + relation.getName() + "\" class=\""
655                                        + "ptolemy.actor.TypedIORelation\"/>\n");
656
657                                ComponentEntity otherEntity = (ComponentEntity) tailProperties.port
658                                        .getContainer();
659
660                                String otherEntityPrefix = "";
661                                if (getModel() != otherEntity) {
662                                    otherEntityPrefix = otherEntity.getName()
663                                            + ".";
664                                }
665
666                                intConnections.append("<link port=\""
667                                        + otherEntityPrefix
668                                        + tailProperties.port.getName()
669                                        + "\" relation=\"" + relation.getName()
670                                        + "\"/>\n");
671                            }
672
673                            intConnections.append("<link port=\"" + portName
674                                    + "\" relation=\"" + relation.getName()
675                                    + "\"/>\n");
676                        }
677                    }
678                    //                        } else if (!headOK && !tailOK) {
679                    //                            // We only selected an edge. Build one input
680                    //                            // port, one output port for it, and build
681                    //                            // a direct connection.
682                }
683                ++i;
684            }
685
686            // System.out.println(" new ports:" + newPorts);
687
688            // Create the MoML command.
689            StringBuffer moml = new StringBuffer();
690
691            // If the dropObj defers to something else, then we
692            // have to check the parent of the object
693            // for import attributes, and then we have to
694            // generate import statements.  Note that everything
695            // imported by the parent will be imported now by
696            // the object into which this is dropped.
697            moml.append("<group>\n");
698
699            // Copy the selection, then get it from the clipboard
700            // and insert its MoML description in the new composite.
701            // This must be done before the call to _deleteMoML(),
702            // which clears the selection.
703            String selectionMoML;
704            copy();
705            Clipboard clipboard = java.awt.Toolkit.getDefaultToolkit()
706                    .getSystemClipboard();
707            Transferable transferable = clipboard.getContents(this);
708            try {
709                selectionMoML = (String) transferable
710                        .getTransferData(DataFlavor.stringFlavor);
711            } catch (Exception ex) {
712                throw new InternalErrorException(null, ex,
713                        "Getting data from clipboard failed.");
714            }
715
716            // Generate the MoML to carry out the deletion.
717
718            moml.append(_deleteMoML(graphModel,
719                    selection.toArray(new Object[selection.size()]), model));
720
721            moml.append("<entity name=\"" + compositeActorName
722                    + "\" class=\"ptolemy.actor.TypedCompositeActor\">\n");
723            moml.append("\t<property name=\"_location\" class=\""
724                    + "ptolemy.kernel.util.Location\" value=\"" + location[0]
725                    + ", " + location[1] + "\">\n");
726            moml.append("\t</property>\n");
727            moml.append(newPorts);
728
729            moml.append(selectionMoML);
730
731            // Internal relations and connections.
732            moml.append(intRelations);
733            moml.append(intConnections);
734            moml.append("</entity>\n");
735
736            // External relations and connections.
737            moml.append(extRelations);
738            moml.append(extConnections);
739
740            moml.append("</group>\n");
741
742            // System.out.println(moml.toString());
743
744            MoMLChangeRequest request = null;
745            request = new MoMLChangeRequest(this, container, moml.toString());
746            request.setUndoable(true);
747
748            container.requestChange(request);
749            NamedObj newObject = compositeActor.getEntity(compositeActorName);
750            // Kepler wants a different icon.
751            IconLoader _iconLoader = MoMLParser.getIconLoader();
752            if (_iconLoader != null) {
753                _iconLoader.loadIconForClass(
754                        "ptolemy.actor.TypedCompositeActor", newObject);
755            }
756        } catch (Throwable throwable) {
757            MessageHandler.error("Creating hierarchy failed", throwable);
758        }
759    }
760
761    /** Remove the currently selected objects from this document, if any,
762     *  and place them on the clipboard.
763     */
764    public void cut() {
765        copy();
766        delete();
767    }
768
769    /** Delete the currently selected objects from this document.
770     */
771    public void delete() {
772        // Note that we previously a delete was handled at the model level.
773        // Now a delete is handled by generating MoML to carry out the delete
774        // and handing that MoML to the parser
775        GraphController controller = _getGraphController();
776        SelectionModel model = controller.getSelectionModel();
777        AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) controller
778                .getGraphModel();
779        Object[] selection = model.getSelectionAsArray();
780
781        // Used by Kepler's Comad.
782        selection = BasicGraphFrameExtension.filterDeletedObjects(graphModel,
783                selection);
784
785        // Generate the MoML to carry out the deletion
786        StringBuffer moml = _deleteMoML(graphModel, selection, model);
787
788        BasicGraphFrameExtension.filterDeleteMoml(graphModel, selection, moml);
789
790        // Next process the deletion MoML. This should be the large majority
791        // of most deletions.
792        try {
793            // Finally create and request the change
794            NamedObj container = graphModel.getPtolemyModel();
795            MoMLChangeRequest change = new MoMLChangeRequest(this, container,
796                    moml.toString());
797            change.setUndoable(true);
798            container.requestChange(change);
799            BasicGraphFrameExtension.alternateDelete(selection, graphModel,
800                    container);
801        } catch (Exception ex) {
802            MessageHandler.error("Delete failed, changeRequest was:" + moml,
803                    ex);
804        }
805
806        graphModel.dispatchGraphEvent(new GraphEvent(this,
807                GraphEvent.STRUCTURE_CHANGED, graphModel.getRoot()));
808    }
809
810    /** Dispose of this frame.
811     *     Override this dispose() method to unattach any listeners that may keep
812     *  this model from getting garbage collected.  This method calls
813     *  {@link #disposeSuper()}.
814     */
815    @Override
816    public void dispose() {
817        if (_debugClosing) {
818            System.out.println("BasicGraphFrame.dispose() : " + getName());
819        }
820
821        // Remove the association with the library. This is necessary to allow
822        // this frame, and the rest of the model to be properly garbage
823        // collected
824        if (_libraryModel != null) {
825            _libraryModel.setRoot(null);
826        }
827        _openGraphFrames.remove(this);
828
829        if (_jgraph != null) {
830            GraphPane pane = _jgraph.getGraphPane();
831            EventLayer foregroundEventLayer = pane.getForegroundEventLayer();
832            foregroundEventLayer.removeLayerListener(_mousePressedLayerAdapter);
833        }
834
835        if (_findInLibraryEntryBox != null) {
836            ActionListener[] listeners = _findInLibraryEntryBox
837                    .getActionListeners();
838            if (listeners != null) {
839                for (ActionListener listener : listeners) {
840                    //System.out.println("BGF.dispose(): _findInLibraryEntryBox: Removing " + listener);
841                    _findInLibraryEntryBox.removeActionListener(listener);
842                }
843            }
844        }
845        //int removed =
846        MemoryCleaner.removeActionListeners(_toolbar);
847        //System.out.println("BasicGraphFrame toolbar action listeners removed: " + removed);
848
849        NamedObj model = getModel();
850        if (model != null) {
851            // SaveAs of an ontology solver resulted in a NPE.
852            getModel().removeChangeListener(this);
853        }
854
855        if (_rightComponent != null) {
856            _rightComponent.removeMouseWheelListener(this);
857            _rightComponent.removeMouseMotionListener(this);
858            _rightComponent.removeMouseListener(this);
859
860            // Free up BasicGraphFrame$MoveToFrontAction.
861            MemoryCleaner.removeActionListeners(_rightComponent);
862        }
863
864        if (_libraryContextMenuCreator != null) {
865            _libraryContextMenuCreator.clear();
866        }
867
868        _mousePressedLayerAdapter = null;
869
870        if (_treeView != null) {
871            MouseListener mouseListeners[] = _treeView.getMouseListeners();
872            for (int i = 0; i < mouseListeners.length; i++) {
873                _treeView.removeMouseListener(mouseListeners[i]);
874            }
875
876            // Free up BasicGraphFrame$HierarchyTreeCellRenderer.
877            MemoryCleaner.removeActionListeners(_treeView);
878            _treeView.setCellRenderer(null);
879            // See http://stackoverflow.com/questions/4517931/java-swing-jtree-is-not-garbage-collected
880            _treeView.setModel(null);
881            _treeView.setUI(null);
882            _treeView = null;
883            // See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/MemoryLeaks#ManagerAgain_treeViewModel
884            _treeViewModel = null;
885        }
886
887        // Top.dispose() sets all the AbstractAction to null.
888        disposeSuper();
889    }
890
891    /** Invoke the dispose() method of the superclass,
892     *  {@link ptolemy.actor.gui.PtolemyFrame}.
893     */
894    public void disposeSuper() {
895        if (_debugClosing) {
896            System.out.println("BasicGraphFrame.disposeSuper() : " + getName());
897        }
898
899        // This method is used by Kepler for the tabbed pane interface.
900        super.dispose();
901    }
902
903    /** Expand all the rows of the library.
904     *  Expanding all the rows is useful for testing.
905     */
906    @Override
907    public void expandAllLibraryRows() {
908        for (int i = 0; i < _library.getRowCount(); i++) {
909            _library.expandRow(i);
910        }
911    }
912
913    /** Export the current submodel as a design pattern using a method similar to
914     *  Save As.
915     */
916    public void exportDesignPattern() {
917        StringAttribute alternateGetMoml = null;
918        DesignPatternIcon icon = null;
919        try {
920            NamedObj model = getModel();
921            try {
922                if (model.getAttribute("_alternateGetMomlAction") == null) {
923                    alternateGetMoml = new StringAttribute(model,
924                            "_alternateGetMomlAction");
925                    alternateGetMoml.setExpression(
926                            DesignPatternGetMoMLAction.class.getName());
927                }
928
929                if (model.getAttribute("_designPatternIcon") == null) {
930                    icon = new DesignPatternIcon(model, "_designPatternIcon");
931                }
932            } catch (Exception e) {
933                throw new InternalErrorException(null, e,
934                        "Fail to prepare " + "for exporting a design pattern.");
935            }
936
937            _prepareExportDesignPattern();
938            _saveAs();
939        } finally {
940            _finishExportDesignPattern();
941
942            if (alternateGetMoml != null) {
943                try {
944                    alternateGetMoml.setContainer(null);
945                } catch (KernelException e) {
946                    // Ignore. This shouldn't happen.
947                }
948            }
949            if (icon != null) {
950                try {
951                    icon.setContainer(null);
952                } catch (KernelException e) {
953                    // Ignore. This shouldn't happen.
954                }
955            }
956        }
957    }
958
959    /** Given a NamedObj, return the corresponding BasicGraphFrame.
960     *  @param model The NamedObj for the model. See
961     *  {@link ptolemy.actor.gui.ConfigurationApplication#openModel(String)}
962     *  for a static method that returns the model
963     *  @return The BasicGraphFrame that corresponds with the model or
964     *  null if the model argument was null, the effigy for the model
965     *  cannot be found or if the Effigy does not contain a Tableau.
966     */
967    public static BasicGraphFrame getBasicGraphFrame(NamedObj model) {
968        if (model == null) {
969            return null;
970        }
971        // See PtolemyLayoutAction for similar code.
972        Effigy effigy = Configuration.findEffigy(model);
973        return getBasicGraphFrame(effigy);
974    }
975
976    /** Given an Effigy, return the corresponding BasicGraphFrame, if any.
977     *  @param effigy The Effigy. To determine the Effigy of a
978     *  NamedObj, use {@link  ptolemy.actor.gui.Configuration#findEffigy(NamedObj)}.
979     *  @return The BasicGraphFrame that corresponds with the Effigy
980     *  or null if the Effigy does not contain a Tableau.
981     */
982    public static BasicGraphFrame getBasicGraphFrame(Effigy effigy) {
983        if (effigy == null) {
984            return null;
985        }
986        List entities = effigy.entityList(Tableau.class);
987        if (entities == null) {
988            return null;
989        }
990
991        BasicGraphFrame frame = null;
992        Iterator tableaux = entities.iterator();
993        while (tableaux.hasNext()) {
994            Tableau tableau = (Tableau) tableaux.next();
995            if (tableau.getFrame() instanceof BasicGraphFrame) {
996                frame = (BasicGraphFrame) tableau.getFrame();
997                break;
998            }
999        }
1000        return frame;
1001    }
1002
1003    /** Return the center location of the visible part of the pane.
1004     *  @return The center of the visible part.
1005     *  @see #setCenter(Point2D)
1006     */
1007    public Point2D getCenter() {
1008        return _getCenter(getJGraph());
1009    }
1010
1011    /** Return the size of the contents of this window.
1012     *  @return The size of the contents.
1013     */
1014    @Override
1015    public Dimension getContentSize() {
1016        return getJGraph().getSize();
1017    }
1018
1019    /** Return the figure that is an icon of a NamedObj and is
1020     *  under the specified point, or null if there is none.
1021     *  The point argument may need to be transformed, see
1022     *  {@link ptolemy.vergil.basic.EditorDropTargetListener#_getFigureUnder(Point2D)}.
1023     *
1024     *  @param pane The pane in which to search
1025     *  @param point The point in the graph pane.
1026     *  @param filteredFigures figures that are filtered from the object search
1027     *  @return The object under the specified point, or null if there
1028     *   is none or it is not a NamedObj.
1029     */
1030    public static Figure getFigureUnder(GraphPane pane, Point2D point,
1031            final Object[] filteredFigures) {
1032
1033        FigureLayer layer = pane.getForegroundLayer();
1034
1035        // Find the figure under the point.
1036        // NOTE: Unfortunately, FigureLayer.getCurrentFigure() doesn't
1037        // work with a drop target (I guess it hasn't seen the mouse events),
1038        // so we have to use a lower level mechanism.
1039        double halo = layer.getPickHalo();
1040        double width = halo * 2;
1041        Rectangle2D region = new Rectangle2D.Double(point.getX() - halo,
1042                point.getY() - halo, width, width);
1043        // Filter away all figures given by the filteredFigures array
1044        CanvasComponent figureUnderMouse = layer.pick(region, new Filter() {
1045            @Override
1046            public boolean accept(Object o) {
1047                for (Object filter : filteredFigures) {
1048                    CanvasComponent figure = (CanvasComponent) o;
1049                    while (figure != null) {
1050                        if (figure.equals(filter)) {
1051                            return false;
1052                        }
1053                        figure = figure.getParent();
1054                    }
1055                }
1056                return true;
1057            }
1058        });
1059
1060        // Find a user object belonging to the figure under the mouse
1061        // or to any figure containing it (it may be a composite figure).
1062        Object objectUnderMouse = null;
1063
1064        while (figureUnderMouse instanceof UserObjectContainer
1065                && objectUnderMouse == null) {
1066            objectUnderMouse = ((UserObjectContainer) figureUnderMouse)
1067                    .getUserObject();
1068
1069            if (objectUnderMouse instanceof NamedObj) {
1070                if (figureUnderMouse instanceof Figure) {
1071                    return (Figure) figureUnderMouse;
1072                }
1073            }
1074
1075            figureUnderMouse = figureUnderMouse.getParent();
1076        }
1077
1078        return null;
1079    }
1080
1081    /** The frame (window) being exported to HTML.
1082     *  @return This frame.
1083     */
1084    public PtolemyFrame getFrame() {
1085        return this;
1086    }
1087
1088    /** Return the JGraph instance that this view uses to represent the
1089     *  ptolemy model.
1090     *  @return the JGraph.
1091     *  @see #setJGraph(JGraph)
1092     */
1093    public JGraph getJGraph() {
1094        return _jgraph;
1095    }
1096
1097    /** Return the JCanvasPanner instance.
1098     *  @return the JCanvasPanner
1099     */
1100    public JCanvasPanner getGraphPanner() {
1101        return _graphPanner;
1102    }
1103
1104    /** Get the directory that was last accessed.
1105     *  @return The last directory
1106     *  @see #setLastDirectory(File)
1107     */
1108    public File getLastDirectory() {
1109        return _directory;
1110    }
1111
1112    /** Set the directory that was last accessed by this window.
1113     *  @see #getLastDirectory()
1114     *  @param directory The directory last accessed.
1115     */
1116    public void setLastDirectory(File directory) {
1117        // NOTE: This method is necessary because we wish to have
1118        // this accessed by inner classes, and there is a bug in
1119        // jdk1.2.2 where inner classes cannot access protected
1120        // static members.
1121        setDirectory(directory);
1122    }
1123
1124    /** Return a set of instances of NamedObj representing the objects
1125     *  that are currently selected.  This set has no particular order
1126     *  to it. If you need the selection objects in proper order, as
1127     *  defined by the container, then call sortContainedObjects()
1128     *  on the container to sort the result.
1129     *  @return The set of selected objects.
1130     */
1131    public HashSet<NamedObj> getSelectionSet() {
1132        return _getSelectionSet();
1133    }
1134
1135    /** Return the rectangle representing the visible part of the
1136     *  pane, transformed into canvas coordinates.  This is the range
1137     *  of locations that are visible, given the current pan and zoom.
1138     *  @return The rectangle representing the visible part.
1139     */
1140    public Rectangle2D getVisibleCanvasRectangle() {
1141        return _getVisibleCanvasRectangle(getJGraph());
1142    }
1143
1144    /** Return the rectangle representing the visible part of the
1145     *  pane, in pixel coordinates on the screen.
1146     *  @return A rectangle whose upper left corner is at (0, 0) and whose
1147     *  size is the size of the canvas component.
1148     */
1149    public Rectangle2D getVisibleRectangle() {
1150        return _getVisibleRectangle(getJGraph());
1151    }
1152
1153    /** Import a design pattern into the current design.
1154     */
1155    public void importDesignPattern() {
1156        JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix();
1157        Color background = null;
1158        PtFileChooser ptFileChooser = null;
1159
1160        try {
1161            background = jFileChooserBugFix.saveBackground();
1162            ptFileChooser = new PtFileChooser(this,
1163                    "Select a design pattern file.", JFileChooser.OPEN_DIALOG);
1164            //if (_fileFilter != null) {
1165            //    ptFileChooser.addChoosableFileFilter(_fileFilter);
1166            //}
1167
1168            ptFileChooser.setCurrentDirectory(_directory);
1169
1170            int returnVal = ptFileChooser.showDialog(this, "Import");
1171
1172            if (returnVal == JFileChooser.APPROVE_OPTION) {
1173                Top.setDirectory(ptFileChooser.getCurrentDirectory());
1174                NamedObj model = null;
1175                File file = null;
1176                try {
1177                    file = ptFileChooser.getSelectedFile().getCanonicalFile();
1178                    URL url = file.toURI().toURL();
1179                    MoMLParser parser = new MoMLParser();
1180                    MoMLParser.purgeModelRecord(url);
1181                    model = parser.parse(url, url);
1182                    MoMLParser.purgeModelRecord(url);
1183                } catch (Exception e) {
1184                    report(new IllegalActionException(getModel(), e,
1185                            "Error reading input file \"" + file + "\"."));
1186                }
1187                if (model != null) {
1188                    Attribute attribute = model
1189                            .getAttribute("_alternateGetMomlAction");
1190                    String className = DesignPatternGetMoMLAction.class
1191                            .getName();
1192                    if (attribute == null
1193                            || !(attribute instanceof StringAttribute)
1194                            || !((StringAttribute) attribute).getExpression()
1195                                    .equals(className)) {
1196                        report(new IllegalActionException("The model \"" + file
1197                                + "\" is not a design pattern."));
1198                    } else {
1199                        String moml = new DesignPatternGetMoMLAction()
1200                                .getMoml(model, model.getName());
1201                        NamedObj context = getModel();
1202                        MoMLChangeRequest request = new MoMLChangeRequest(this,
1203                                context, moml);
1204                        context.requestChange(request);
1205                    }
1206                }
1207            }
1208        } finally {
1209            jFileChooserBugFix.restoreBackground(background);
1210        }
1211    }
1212
1213    /** Do nothing.
1214     */
1215    @Override
1216    public void lostOwnership(Clipboard clipboard, Transferable transferable) {
1217    }
1218
1219    /** Open the container, if any, of the entity.
1220     *  If this entity has no container, then do nothing.
1221     */
1222    public void openContainer() {
1223        GraphModel model = _getGraphModel();
1224        NamedObj thisEntity = (NamedObj) model.getRoot();
1225        if (thisEntity != thisEntity.toplevel()) {
1226            // Not already at the top level.
1227            try {
1228                // See whether the container contains an instance of LevelSkippingTableauFactory.
1229                NamedObj container = thisEntity.getContainer();
1230                List<LevelSkippingTableauFactory> skip = container
1231                        .attributeList(LevelSkippingTableauFactory.class);
1232                while (skip != null && skip.size() > 0) {
1233                    container = container.getContainer();
1234                    if (container == null) {
1235                        // This should not occur.
1236                        return;
1237                    }
1238                    skip = container
1239                            .attributeList(LevelSkippingTableauFactory.class);
1240                }
1241                // If the container is a ModalModel, the also skip a level.
1242                // Note that it is not enough to just check whether the name of the
1243                // class matches ModalModel, because then subclasses of ModalModel
1244                // are not recognized. This unfortunately creates a new dependence
1245                // on the modal package.
1246                if (thisEntity instanceof Actor) {
1247                    Director director = ((Actor) thisEntity).getDirector();
1248                    if (director instanceof QuasiTransparentDirector) {
1249                        if (thisEntity.getName().equals("_Controller")) {
1250                            container = container.getContainer();
1251                            if (container == null) {
1252                                // This should not occur.
1253                                return;
1254                            }
1255                        }
1256                    }
1257                }
1258
1259                Configuration configuration = getConfiguration();
1260                // FIXME: do what with the return value?
1261                configuration.openInstance(container);
1262            } catch (Throwable throwable) {
1263                MessageHandler.error("Failed to open container", throwable);
1264            }
1265        }
1266    }
1267
1268    /** Opens the nearest composite actor above the target in the hierarchy
1269     *  and possibly change the zoom and centering to show the target.
1270     *  This method is useful for displaying search results and actors that
1271     *  cause errors.
1272     *  @param target The target.
1273     *  @param owner The frame that, per the user, is generating the dialog.
1274     */
1275    public static void openComposite(final Frame owner, final NamedObj target) {
1276        // This method is static so that it
1277        NamedObj container = target.getContainer();
1278        while (container != null && !(container instanceof CompositeEntity)) {
1279            container = container.getContainer();
1280        }
1281        if (container == null) {
1282            // Hmm.  Could not find container?
1283            container = target;
1284        }
1285        try {
1286            if (owner != null) {
1287                report(owner, "Opening " + container.getFullName());
1288            }
1289            Effigy effigy = Configuration.findEffigy(target.toplevel());
1290            if (effigy == null) {
1291                throw new IllegalActionException(target,
1292                        "Failed to find an " + "effigy for the toplevel "
1293                                + target.toplevel().getFullName());
1294            }
1295            Configuration configuration = (Configuration) effigy.toplevel();
1296            Tableau tableau = configuration.openInstance(container);
1297
1298            // Try to zoom and center on the target.
1299
1300            // Get the _location attribute.  If it is null, then maybe
1301            // this is a parameter in an actor so go up the hierarchy
1302            // until we get to the container we found above or we find
1303            // a non-null location attribute.
1304            Location locationAttribute = (Location) target
1305                    .getAttribute("_location", Location.class);
1306            if (locationAttribute == null) {
1307                NamedObj targetContainer = target.getContainer();
1308                while (targetContainer != null
1309                        && (locationAttribute = (Location) targetContainer
1310                                .getAttribute("_location",
1311                                        Location.class)) == null) {
1312                    // FindBugs: Load of known null value.  locationAttribute is always null here.
1313                    // The break is unnecessary as if locationAttribute is non-null, then
1314                    // the body of the while loop is not executed.
1315                    //if (locationAttribute != null
1316                    if (targetContainer.equals(container)) {
1317                        break;
1318                    }
1319                    targetContainer = targetContainer.getContainer();
1320                }
1321            }
1322            if (locationAttribute != null) {
1323                Frame frame = tableau.getFrame();
1324                if (frame instanceof BasicGraphFrame) {
1325                    BasicGraphFrame basicGraphFrame = (BasicGraphFrame) frame;
1326
1327                    double[] locationArray = locationAttribute.getLocation();
1328                    Point2D locationPoint2D = new Point2D.Double(
1329                            locationArray[0], locationArray[1]);
1330
1331                    GraphPane pane = basicGraphFrame.getJGraph().getGraphPane();
1332
1333                    // The value returned by Rectangle2D.outcode()
1334                    int outcode = 0;
1335                    Figure figure = BasicGraphFrame.getFigureUnder(pane,
1336                            locationPoint2D, new Object[] {});
1337                    if (figure == null) {
1338                        // If we can't find the figure, then force zoom and center.
1339                        // I'm not sure if this can ever happen, but it might help
1340                        outcode = 666;
1341                    } else {
1342                        Rectangle2D figureBounds = figure.getBounds();
1343                        Rectangle2D canvasBounds = basicGraphFrame
1344                                .getVisibleCanvasRectangle();
1345                        outcode = canvasBounds.outcode(figureBounds.getX(),
1346                                figureBounds.getY());
1347                        //basicGraphFrame.zoomFit(pane, figureBounds);
1348                        //basicGraphFrame.zoom(0.6);
1349                    }
1350
1351                    // Get the scale, assume that the scaling in the X
1352                    // and Y directions are the same.
1353                    AffineTransform current = pane.getCanvas().getCanvasPane()
1354                            .getTransformContext().getTransform();
1355                    double scale = current.getScaleX();
1356                    if (scale < 0.8 || scale > 2.0 || outcode != 0) {
1357                        // Only reset the zoom if the would be difficult to see
1358                        // the component or if the component is not visible.
1359                        basicGraphFrame.zoomReset();
1360                        basicGraphFrame.setCenter(locationPoint2D);
1361                    }
1362                }
1363            }
1364            if (owner != null) {
1365                report(owner, "Opened " + container.getFullName());
1366            }
1367        } catch (Throwable throwable) {
1368            MessageHandler.error("Failed to open container", throwable);
1369        }
1370    }
1371
1372    /** Assuming the contents of the clipboard is MoML code, paste it into
1373     *  the current model by issuing a change request.
1374     */
1375    public void paste() {
1376        Clipboard clipboard = java.awt.Toolkit.getDefaultToolkit()
1377                .getSystemClipboard();
1378        Transferable transferable = clipboard.getContents(this);
1379        GraphModel model = _getGraphModel();
1380
1381        if (transferable == null) {
1382            return;
1383        }
1384
1385        try {
1386            NamedObj container = (NamedObj) model.getRoot();
1387            StringBuffer moml = new StringBuffer();
1388
1389            // The pasted version will have the names generated by the
1390            // uniqueName() method of the container, to ensure that they
1391            // do not collide with objects already in the container.
1392            moml.append("<group name=\"auto\">\n");
1393            //moml.append("<group>\n");
1394            moml.append((String) transferable
1395                    .getTransferData(DataFlavor.stringFlavor));
1396
1397            // Needed by Kepler's Comad.
1398            BasicGraphFrameExtension.alternatePasteMomlModification(container,
1399                    moml);
1400
1401            moml.append("</group>\n");
1402
1403            MoMLChangeRequest change = new OffsetMoMLChangeRequest(this,
1404                    container, moml.toString());
1405            change.setUndoable(true);
1406            container.requestChange(change);
1407
1408            // Added by Lei Dou to update the signature for Kepler/Comad
1409            BasicGraphFrameExtension.alternatePaste(container, moml);
1410        } catch (Exception ex) {
1411            MessageHandler.error("Paste failed", ex);
1412        }
1413    }
1414
1415    /** Print the visible portion of the graph to a printer,
1416     *  which is represented by the specified graphics object.
1417     *  @param graphics The context into which the page is drawn.
1418     *  @param format The size and orientation of the page being drawn.
1419     *  @param index The zero based index of the page to be drawn.
1420     *  @return PAGE_EXISTS if the page is rendered successfully, or
1421     *   NO_SUCH_PAGE if pageIndex specifies a non-existent page.
1422     *  @exception PrinterException If the print job is terminated.
1423     */
1424    @Override
1425    public int print(Graphics graphics, PageFormat format, int index)
1426            throws PrinterException {
1427        if (getJGraph() != null) {
1428            Rectangle2D view = getVisibleRectangle();
1429            return getJGraph().print(graphics, format, index, view);
1430        } else {
1431            return NO_SUCH_PAGE;
1432        }
1433    }
1434
1435    /** Redo the last undone change on the model.
1436     *  @see #undo()
1437     */
1438    public void redo() {
1439        GraphModel model = _getGraphModel();
1440
1441        try {
1442            NamedObj toplevel = (NamedObj) model.getRoot();
1443            RedoChangeRequest change = new RedoChangeRequest(this, toplevel);
1444            toplevel.requestChange(change);
1445        } catch (Exception ex) {
1446            MessageHandler.error("Redo failed", ex);
1447        }
1448    }
1449
1450    //     /** Open a file browser and save the given entity in the file specified
1451    //      *  by the user.
1452    //      *  @param entity The entity to save.
1453    //      *  @exception Exception If there is a problem saving the component.
1454    //      *  @since Ptolemy 4.0
1455    //      */
1456    //     public void saveComponentInFile(Entity entity) throws Exception {
1457    //         // FIXME: This method is probably no
1458    //         // NOTE: This mirrors similar code in Top and TableauFrame, but
1459    //         // I can't find any way to re-use that code, since the details
1460    //         // are slightly different at each step here.
1461
1462    //         JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix();
1463    //         Color background = null;
1464    //         PtFileChooser ptFileChooser = null;
1465
1466    //         try {
1467    //             background = jFileChooserBugFix.saveBackground();
1468    //             ptFileChooser = new PtFileChooser(this,
1469    //                     "Save Component as...",
1470    //                     JFileChooser.SAVE_DIALOG);
1471    //             ptFileChooser.setCurrentDirectory(_directory);
1472    //             // Hmm, is getCurrentDirectory necessary here?
1473    //             ptFileChooser.setSelectedFile(new File(ptFileChooser.getCurrentDirectory(),
1474    //                             entity.getName() + ".xml"));
1475
1476    //             int returnVal = ptFileChooser.showDialog(this,
1477    //                     "Save");
1478    //             if (returnVal == JFileChooser.APPROVE_OPTION) {
1479    //                 // We set _directory below.
1480    //                 File file = ptFileChooser.getSelectedFile();
1481
1482    //                 if (!_confirmFile(entity, file)) {
1483    //                     return;
1484    //                 }
1485
1486    //                 // Record the selected directory.
1487    //                 _directory = ptFileChooser.getCurrentDirectory();
1488
1489    //                 java.io.FileWriter fileWriter = null;
1490
1491    //                 try {
1492    //                     fileWriter = new java.io.FileWriter(file);
1493
1494    //                     // Make sure the entity name saved matches the file name.
1495    //                     String name = entity.getName();
1496    //                     String filename = file.getName();
1497    //                     int period = filename.indexOf(".");
1498
1499    //                     if (period > 0) {
1500    //                         name = filename.substring(0, period);
1501    //                     } else {
1502    //                         name = filename;
1503    //                     }
1504
1505    //                     fileWriter.write("<?xml version=\"1.0\" standalone=\"no\"?>\n"
1506    //                             + "<!DOCTYPE " + entity.getElementName() + " PUBLIC "
1507    //                             + "\"-//UC Berkeley//DTD MoML 1//EN\"\n"
1508    //                             + "    \"http://ptolemy.eecs.berkeley.edu"
1509    //                             + "/xml/dtd/MoML_1.dtd\">\n");
1510
1511    //                     entity.exportMoML(fileWriter, 0, name);
1512    //                 } finally {
1513    //                     if (fileWriter != null) {
1514    //                         fileWriter.close();
1515    //                     }
1516    //                 }
1517    //             }
1518    //         } finally {
1519    //             jFileChooserBugFix.restoreBackground(background);
1520    //         }
1521    //     }
1522
1523    /** Report a message to either the status bar or message handler.
1524     *  @param owner The frame that, per the user, is generating the
1525     *  dialog.
1526     *  @param message The message.
1527     */
1528    public static void report(Frame owner, String message) {
1529        if (owner instanceof Top) {
1530            ((Top) owner).report(message);
1531        } else {
1532            MessageHandler.message(message);
1533        }
1534    }
1535
1536    /** Save the given entity in the user library in the given
1537     *  configuration.
1538     *  @param configuration The configuration.
1539     *  @param entity The entity to save.
1540     *  @since Ptolemy 2.1
1541     *  @deprecated Use {@link ptolemy.actor.gui.UserActorLibrary#saveComponentInLibrary(Configuration, Entity)}
1542     */
1543    @Deprecated
1544    public static void saveComponentInLibrary(Configuration configuration,
1545            Entity entity) {
1546        try {
1547            ptolemy.actor.gui.UserActorLibrary
1548                    .saveComponentInLibrary(configuration, entity);
1549        } catch (Exception ex) {
1550            // We catch exceptions here because this method used to
1551            // not throw Exceptions, and we don't want to break compatibility.
1552            MessageHandler
1553                    .error("Failed to save \"" + entity.getName() + "\".");
1554        }
1555    }
1556
1557    /** Set the center location of the visible part of the pane.
1558     *  This will cause the panner to center on the specified location
1559     *  with the current zoom factor.
1560     *  @param center The center of the visible part.
1561     *  @see #getCenter()
1562     */
1563    public void setCenter(Point2D center) {
1564        _setCenter(getJGraph(), center);
1565    }
1566
1567    /** Set the JGraph instance that this view uses to represent the
1568     *  ptolemy model.
1569     *  @param jgraph The JGraph.
1570     *  @see #getJGraph()
1571     */
1572    public void setJGraph(JGraph jgraph) {
1573        _jgraph = jgraph;
1574    }
1575
1576    /** Undo the last undoable change on the model.
1577     *  @see #redo()
1578     */
1579    public void undo() {
1580        GraphModel model = _getGraphModel();
1581
1582        try {
1583            NamedObj toplevel = (NamedObj) model.getRoot();
1584            UndoChangeRequest change = new UndoChangeRequest(this, toplevel);
1585            toplevel.requestChange(change);
1586        } catch (Exception ex) {
1587            MessageHandler.error("Undo failed", ex);
1588        }
1589    }
1590
1591    /** Update the size, zoom and position of the window.
1592     *  This method is typically called when closing the window
1593     *  or writing the moml file out.
1594     *  @exception IllegalActionException If there is a problem
1595     *  getting a parameter.
1596     *  @exception NameDuplicationException If there is a problem
1597     *  creating a parameter.
1598     */
1599    public void updateWindowAttributes()
1600            throws IllegalActionException, NameDuplicationException {
1601        // First, record size and position.
1602
1603        // See "composite window size & position not always saved"
1604        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5637
1605
1606        // Record the position of the top-level frame, assuming
1607        // there is one.
1608        Component component = _getRightComponent().getParent();
1609        Component parent = component.getParent();
1610
1611        while (parent != null && !(parent instanceof Frame)) {
1612            component = parent;
1613            parent = component.getParent();
1614        }
1615
1616        // Oddly, sometimes getModel returns null?  $PTII/bin/ptinvoke
1617        // ptolemy.vergil.basic.export.ExportModel -force htm -run
1618        // -openComposites -whiteBackground
1619        // ptolemy/actor/gt/demo/MapReduce/MapReduce.xml
1620        // $PTII/ptolemy/actor/gt/demo/MapReduce/MapReduce
1621        NamedObj model = getModel();
1622        if (model != null) {
1623            _updateWindowAttributes((Frame) parent, model, getJGraph());
1624        }
1625    }
1626
1627    /** Write an HTML page based on the current view of the model
1628     *  to the specified destination directory. The file will be
1629     *  named "index.html," and supporting files, including at
1630     *  least a gif image showing the contents currently visible in
1631     *  the graph frame, will be created. If there are any plot windows
1632     *  open or any composite actors open, then gif and/or HTML will
1633     *  be generated for those as well and linked to the gif image
1634     *  created for this frame.
1635     *  <p>
1636     *  The generated page has a header with the name of the model,
1637     *  a reference to a GIF image file with name equal to the name
1638     *  of the model with a ".gif" extension appended, and a script
1639     *  that reacts when the mouse is moved over an actor by
1640     *  displaying a table with the parameter values of the actor.
1641     *  The gif image is assumed to have been generated with the
1642     *  current view using the {@link #writeImage(OutputStream, String)}
1643     *  method.
1644     *  @param parameters The parameters that control the export.
1645     *  @param writer The writer to use the write the HTML. If this is null,
1646     *   then create an index.html file in the
1647     *   directory given by the directoryToExportTo field of the parameters.
1648     *  @exception IOException If unable to write associated files, or if the
1649     *   current configuration does not support it.
1650     *  @exception PrinterException If unable to write associated files.
1651     *  @exception IllegalActionException If something goes wrong accessing the model.
1652     */
1653    @Override
1654    public void writeHTML(ExportParameters parameters, Writer writer)
1655            throws PrinterException, IOException, IllegalActionException {
1656        if (_exportHTMLAction != null) {
1657            ((HTMLExportable) _exportHTMLAction).writeHTML(parameters, writer);
1658        } else {
1659            throw new IOException(
1660                    "Export to Web not supported.  Probably the configuration does not have a _exportHTMLActionClassName parameter or the class named by that parameter is not present.");
1661        }
1662    }
1663
1664    /** Write an image to the specified output stream in the specified format.
1665     *  Supported formats include at least "gif" and "png", standard image file formats.
1666     *  The image is a rendition of the current view of the model.
1667     *  <p>{@link ptolemy.vergil.basic.export.ExportModel} is a standalone class
1668     *  that exports an image of a model.
1669     *  @param stream The output stream to write to.
1670     *  @param format The image format to generate.
1671     *  @see #writeHTML(ExportParameters, Writer)
1672     *  @exception IOException If writing to the stream fails.
1673     *  @exception PrinterException  If the specified format is not supported.
1674     */
1675    @Override
1676    public void writeImage(OutputStream stream, String format)
1677            throws PrinterException, IOException {
1678        writeImage(stream, format, null);
1679    }
1680
1681    /** Write an image to the specified output stream in the specified format with
1682     *  the specified background color.
1683     *  Supported formats include at least "gif" and "png", standard image file formats.
1684     *  The image is a rendition of the current view of the model.
1685     *  <p>{@link ptolemy.vergil.basic.export.ExportModel} is a standalone class
1686     *  that exports an image of a model.
1687     *  @param stream The output stream to write to.
1688     *  @param format The image format to generate.
1689     *  @param background The background color, or null to use the current color.
1690     *  @see #writeHTML(ExportParameters, Writer)
1691     *  @exception IOException If writing to the stream fails.
1692     *  @exception PrinterException  If the specified format is not supported.
1693     */
1694    public void writeImage(OutputStream stream, String format, Color background)
1695            throws PrinterException, IOException {
1696        JCanvas canvas = getJGraph().getGraphPane().getCanvas();
1697        Color previousBackground = canvas.getBackground();
1698        try {
1699            if (background != null) {
1700                canvas.setBackground(background);
1701            }
1702            getJGraph().exportImage(stream, format);
1703        } finally {
1704            if (background != null) {
1705                canvas.setBackground(previousBackground);
1706            }
1707        }
1708    }
1709
1710    /** Zoom in or out to magnify by the specified factor, from the current
1711     *  magnification.
1712     *  @param factor The magnification factor (relative to 1.0).
1713     */
1714    public void zoom(double factor) {
1715        _zoom(getJGraph(), factor);
1716    }
1717
1718    /** Zoom to fit the current figures.
1719     */
1720    public void zoomFit() {
1721        GraphPane pane = getJGraph().getGraphPane();
1722        Rectangle2D bounds = pane.getForegroundLayer().getLayerBounds();
1723        zoomFit(pane, bounds);
1724    }
1725
1726    /** Zoom to fit the bounds.
1727     *  @param pane The pane.
1728     *  @param bounds The bound to zoom to.
1729     */
1730    public void zoomFit(GraphPane pane, Rectangle2D bounds) {
1731        if (bounds.isEmpty()) {
1732            // Empty diagram.
1733            return;
1734        }
1735
1736        Rectangle2D viewSize = getVisibleRectangle();
1737        Rectangle2D paddedViewSize = new Rectangle2D.Double(
1738                viewSize.getX() + _ZOOM_FIT_PADDING,
1739                viewSize.getY() + _ZOOM_FIT_PADDING,
1740                viewSize.getWidth() - 2 * _ZOOM_FIT_PADDING,
1741                viewSize.getHeight() - 2 * _ZOOM_FIT_PADDING);
1742        AffineTransform newTransform = CanvasUtilities
1743                .computeFitTransform(bounds, paddedViewSize);
1744        JCanvas canvas = pane.getCanvas();
1745        canvas.getCanvasPane().setTransform(newTransform);
1746
1747        if (_graphPanner != null) {
1748            _graphPanner.repaint();
1749        }
1750    }
1751
1752    /** Set zoom to the nominal.
1753     */
1754    public void zoomReset() {
1755        JCanvas canvas = getJGraph().getGraphPane().getCanvas();
1756        AffineTransform current = canvas.getCanvasPane().getTransformContext()
1757                .getTransform();
1758        current.setToIdentity();
1759        canvas.getCanvasPane().setTransform(current);
1760
1761        if (_graphPanner != null) {
1762            _graphPanner.repaint();
1763        }
1764    }
1765
1766    /**
1767     * Called when the mouse is clicked.
1768     * This base class does nothing when the mouse is clicked.
1769     * However, events _are_ handled by the components within this component.
1770     * @param event The mouse event.
1771     */
1772    @Override
1773    public void mouseClicked(MouseEvent event) {
1774        // Implementation of the MouseMotionListener interface.
1775    }
1776
1777    /** Transform the graph by the amount the mouse is dragged
1778     *  while the middle mouse button is held down.
1779     * @param event The drag event.
1780     */
1781    @Override
1782    public void mouseDragged(MouseEvent event) {
1783        // Implementation of the MouseMotionListener interface.
1784        // See https://chess.eecs.berkeley.edu/bugzilla/show_bug.cgi?id=73
1785
1786        if (event.isAltDown()) {
1787            // Only interested in middle button. (defined as the alt modifier)
1788            int deltaX = event.getX() - _previousMouseX;
1789            int deltaY = event.getY() - _previousMouseY;
1790
1791            AffineTransform newTransform = getJGraph().getCanvasPane()
1792                    .getTransformContext().getTransform();
1793            newTransform.translate(deltaX, deltaY);
1794            getJGraph().getCanvasPane().setTransform(newTransform);
1795
1796            _previousMouseX = event.getX();
1797            _previousMouseY = event.getY();
1798            event.consume();
1799        }
1800    }
1801
1802    /**
1803     * Called when the mouse enters this component.
1804     * This base class does nothing when the enters this component.
1805     * However, events _are_ handled by the components within this component.
1806     * @param event The mouse event.
1807     */
1808    @Override
1809    public void mouseEntered(MouseEvent event) {
1810        // Implementation of the MouseMotionListener interface.
1811    }
1812
1813    /**
1814     * Called when the mouse leaves this component.
1815     * This base class does nothing when the exits this component.
1816     * However, events _are_ handled by the components within this component.
1817     * @param event The mouse event.
1818     */
1819    @Override
1820    public void mouseExited(MouseEvent event) {
1821        // Implementation of the MouseMotionListener interface.
1822    }
1823
1824    /** Called when the mouse is moved.
1825     * This base class does nothing when the mouse is moved.
1826     * @param event Contains details of the movement event.
1827     * However, events _are_ handled by the components within this component.
1828     */
1829    @Override
1830    public void mouseMoved(MouseEvent event) {
1831        // Implementation of the MouseMotionListener interface.
1832    }
1833
1834    /** Store the location of the middle mouse event.
1835     * @param event The mouse event.
1836     */
1837    @Override
1838    public void mousePressed(MouseEvent event) {
1839        if (event.isAltDown()) {
1840            // Only interested in middle button. (defined as the alt modifier)
1841            _previousMouseX = event.getX();
1842            _previousMouseY = event.getY();
1843            event.consume();
1844        }
1845    }
1846
1847    /**
1848     * Called when the mouse is released.
1849     * This base class does nothing when the mouse is moved.
1850     * However, events _are_ handled by the components within this component.
1851     * @param event The mouse event.
1852     */
1853    @Override
1854    public void mouseReleased(MouseEvent event) {
1855        // Implementation of the MouseMotionListener interface.
1856    }
1857
1858    /** Scroll in when the mouse wheel is moved.
1859     * @param event The mouse wheel event.
1860     */
1861    @Override
1862    public void mouseWheelMoved(MouseWheelEvent event) {
1863        // Scrolling the wheel away from you zooms in. This is arbitrary and
1864        // should be configurable by the user.
1865        //
1866        // TODO: It would be nice to centre the zoom on where the
1867        // mouse is. That would mirror what apps like google earth do.
1868
1869        int notches = event.getWheelRotation();
1870        double zoomFactor = 1.25;
1871        if (notches > 0) {
1872            zoomFactor = 1.0 / zoomFactor;
1873        }
1874        zoom(zoomFactor);
1875    }
1876
1877    ///////////////////////////////////////////////////////////////////
1878    ////                         public variables                  ////
1879
1880    /** Default background color is a light grey. */
1881    public static final Color BACKGROUND_COLOR = new Color(0xe5e5e5);
1882
1883    /** The name of the user library.  The default value is
1884     *  "UserLibrary".  The value of this variable is what appears
1885     *  in the Vergil left hand tree menu.
1886     *  @deprecated Use {@link ptolemy.actor.gui.UserActorLibrary#USER_LIBRARY_NAME}
1887     */
1888    @Deprecated
1889    public static String VERGIL_USER_LIBRARY_NAME = UserActorLibrary.USER_LIBRARY_NAME;
1890
1891    ///////////////////////////////////////////////////////////////////
1892    ////                         protected methods                 ////
1893
1894    /** Add a layout menu.
1895     *  @param graphMenu The menu to which to add the layout menu.
1896     */
1897    protected void _addLayoutMenu(JMenu graphMenu) {
1898        // The layout action is created by BasicGraphFrame.
1899        if (_layoutAction != null) {
1900            // If we are running with -ptinyViewer, then the layout facility
1901            // might not be present.
1902            GUIUtilities.addHotKey(_getRightComponent(), _layoutAction);
1903            GUIUtilities.addMenuItem(graphMenu, _layoutAction);
1904            if (_layoutConfigDialogAction != null) {
1905                GUIUtilities.addMenuItem(graphMenu, _layoutConfigDialogAction);
1906            }
1907            graphMenu.addSeparator();
1908        }
1909    }
1910
1911    /** Create the menus that are used by this frame.
1912     */
1913    @Override
1914    protected void _addMenus() {
1915        super._addMenus();
1916
1917        _editMenu = new JMenu("Edit");
1918        _editMenu.setMnemonic(KeyEvent.VK_E);
1919        _menubar.add(_editMenu);
1920
1921        // Add the undo action, followed by a separator then the editing actions
1922        diva.gui.GUIUtilities.addHotKey(_getRightComponent(), _undoAction);
1923        diva.gui.GUIUtilities.addMenuItem(_editMenu, _undoAction);
1924        diva.gui.GUIUtilities.addHotKey(_getRightComponent(), _redoAction);
1925        diva.gui.GUIUtilities.addMenuItem(_editMenu, _redoAction);
1926        _editMenu.addSeparator();
1927        GUIUtilities.addHotKey(_getRightComponent(), _cutAction);
1928        GUIUtilities.addMenuItem(_editMenu, _cutAction);
1929        GUIUtilities.addHotKey(_getRightComponent(), _copyAction);
1930        GUIUtilities.addMenuItem(_editMenu, _copyAction);
1931        GUIUtilities.addHotKey(_getRightComponent(), _pasteAction);
1932        GUIUtilities.addMenuItem(_editMenu, _pasteAction);
1933
1934        _editMenu.addSeparator();
1935
1936        GUIUtilities.addHotKey(_getRightComponent(), _moveToBackAction);
1937        GUIUtilities.addMenuItem(_editMenu, _moveToBackAction);
1938        GUIUtilities.addHotKey(_getRightComponent(), _moveToFrontAction);
1939        GUIUtilities.addMenuItem(_editMenu, _moveToFrontAction);
1940
1941        _editMenu.addSeparator();
1942        GUIUtilities.addMenuItem(_editMenu, _editPreferencesAction);
1943
1944        // Hot key for configure (edit parameters).
1945        GUIUtilities.addHotKey(_getRightComponent(),
1946                BasicGraphController._configureAction);
1947
1948        // May be null if there are not multiple views in the configuration.
1949        if (_viewMenu == null) {
1950            _viewMenu = new JMenu("View");
1951            _viewMenu.setMnemonic(KeyEvent.VK_V);
1952            _menubar.add(_viewMenu);
1953        } else {
1954            _viewMenu.addSeparator();
1955        }
1956
1957        GUIUtilities.addHotKey(_getRightComponent(), _zoomInAction);
1958        GUIUtilities.addMenuItem(_viewMenu, _zoomInAction);
1959        GUIUtilities.addHotKey(_getRightComponent(), _zoomResetAction);
1960        GUIUtilities.addMenuItem(_viewMenu, _zoomResetAction);
1961        GUIUtilities.addHotKey(_getRightComponent(), _zoomFitAction);
1962        GUIUtilities.addMenuItem(_viewMenu, _zoomFitAction);
1963        GUIUtilities.addHotKey(_getRightComponent(), _zoomOutAction);
1964        GUIUtilities.addMenuItem(_viewMenu, _zoomOutAction);
1965
1966        _graphMenu = new JMenu("Graph");
1967        _graphMenu.setMnemonic(KeyEvent.VK_G);
1968        _menubar.add(_graphMenu);
1969        GUIUtilities.addHotKey(_getRightComponent(), _findAction);
1970        GUIUtilities.addMenuItem(_graphMenu, _findAction);
1971    }
1972
1973    /** Add a Reload Accessors menu choice.
1974     *  @param graphMenu The menu to which to add the Reload Accessors
1975     *  menu choice.
1976     */
1977    protected void _addReloadAccessorsMenu(JMenu graphMenu) {
1978        // The action is created by BasicGraphFrame.
1979        if (_reloadAccessorsAction != null) {
1980            GUIUtilities.addMenuItem(graphMenu, _reloadAccessorsAction);
1981            graphMenu.addSeparator();
1982        }
1983    }
1984
1985    /** Return true if any element of the specified list is implied.
1986     *  An element is implied if its getDerivedLevel() method returns
1987     *  anything smaller than Integer.MAX_VALUE.
1988     *  @param elements A list of instances of NamedObj.
1989     *  @return True if any element in the list is implied.
1990     *  @see NamedObj#getDerivedLevel()
1991     */
1992    protected boolean _checkForImplied(List<NamedObj> elements) {
1993        Iterator<NamedObj> elementIterator = elements.iterator();
1994
1995        while (elementIterator.hasNext()) {
1996            NamedObj element = elementIterator.next();
1997
1998            if (element.getDerivedLevel() < Integer.MAX_VALUE) {
1999                MessageHandler.error(
2000                        "Cannot change the position of " + element.getFullName()
2001                                + " because the position is set by the class.");
2002                return true;
2003            }
2004        }
2005
2006        return false;
2007    }
2008
2009    /** Override the base class to remove the listeners we have
2010     *  created when the frame closes.  Specifically,
2011     *  remove our panner-updating listener from the entity.
2012     *  Also remove the listeners our graph model has created.
2013     *  @return True if the close completes, and false otherwise.
2014     */
2015    @Override
2016    protected boolean _close() {
2017        if (_debugClosing) {
2018            System.out.println("BasicGraphFrame._close() : " + getName());
2019        }
2020
2021        // See "composite window size & position not always saved"
2022        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5637
2023
2024        // Don't update the _windowProperties attribute during _close()
2025        // For example, if the model is a large model and there is an
2026        // error and the user clicks on "Go To Actor", then the model
2027        // may be zoomed.  When the user closes the model, they will
2028        // be prompted to save.  Even worse, it appears that the size
2029        // and location of windows can be slightly different between
2030        // different platforms.
2031
2032        //         try {
2033        //             _updateWindowAttributes();
2034        //         } catch (KernelException ex) {
2035        //             // Ignore problems here.  Errors simply result in a default
2036        //             // size and location.
2037        //             System.out.println("While closing, failed to update size, position or zoom factor: " + ex);
2038        //         }
2039
2040        boolean result = super._close();
2041
2042        if (result) {
2043            AbstractBasicGraphModel graphModel = _getGraphModel();
2044            graphModel.removeListeners();
2045        }
2046
2047        return result;
2048    }
2049
2050    /** Create the default library to use if an entity has no
2051     *  LibraryAttribute.  Note that this is called in the
2052     *  constructor and therefore overrides in subclasses
2053     *  should not refer to any members that may not have been
2054     *  initialized. If no library is found in the configuration,
2055     *  then an empty one is created in the specified workspace.
2056     *  @param workspace The workspace in which to create
2057     *   the library, if one needs to be created.
2058     *  @return The new library, or null if there is no
2059     *   configuration.
2060     */
2061    protected CompositeEntity _createDefaultLibrary(Workspace workspace) {
2062        Configuration configuration = getConfiguration();
2063
2064        if (configuration != null) {
2065            CompositeEntity result = (CompositeEntity) configuration
2066                    .getEntity("actor library");
2067
2068            if (result == null) {
2069                // Create an empty library by default.
2070                result = new CompositeEntity(workspace);
2071
2072                try {
2073                    result.setName("topLibrary");
2074
2075                    // Put a marker in so that this is
2076                    // recognized as a library.
2077                    new Attribute(result, "_libraryMarker");
2078                } catch (Exception ex) {
2079                    throw new InternalErrorException(
2080                            "Library configuration failed: " + ex);
2081                }
2082            }
2083
2084            return result;
2085        } else {
2086            return null;
2087        }
2088    }
2089
2090    /** Create the items in the File menu's Export section
2091     *  This method adds a menu items to export images of the plot
2092     *  in GIF, PNG, and possibly PDF.
2093     *  @return The items in the File menu.
2094     */
2095    @Override
2096    protected JMenuItem[] _createFileMenuItems() {
2097        JMenuItem[] fileMenuItems = super._createFileMenuItems();
2098
2099        JMenu importMenu = (JMenu) fileMenuItems[_IMPORT_MENU_INDEX];
2100        importMenu.setEnabled(true);
2101
2102        JMenu exportMenu = (JMenu) fileMenuItems[_EXPORT_MENU_INDEX];
2103        exportMenu.setEnabled(true);
2104
2105        // Get the "export PDF" action classname from the configuration.
2106        // This may or many not be included because it depends on GPL'd code,
2107        // and hence cannot be included included in any pure BSD distribution.
2108        // NOTE: Cannot use getConfiguration() because the configuration is
2109        // not set when this method is called. Hence, we assume that there
2110        // is only one configuration, or that if there are multiple configurations
2111        // in this execution, that the first one will determine whether PDF
2112        // export is provided.
2113        Configuration configuration = Configuration.configurations().get(0);
2114        // NOTE: Configuration should not be null, but just in case:
2115        if (configuration != null) {
2116
2117            // Here, we get the _importActionClassNames from the configuration.
2118            // _importActionClassNames is an array of Strings where each element
2119            // names a class to that is an import action.
2120            // See also _classesToRemove in Configuration.java
2121            try {
2122                Parameter importActionClassNames = (Parameter) configuration
2123                        .getAttribute("_importActionClassNames",
2124                                Parameter.class);
2125                if (importActionClassNames != null) {
2126                    ArrayToken importActionClassNamesToken = (ArrayToken) importActionClassNames
2127                            .getToken();
2128                    for (int i = 0; i < importActionClassNamesToken
2129                            .length(); i++) {
2130                        String importActionClassName = ((StringToken) importActionClassNamesToken
2131                                .getElement(i)).stringValue();
2132                        try {
2133                            // Get the class, instantiate it and add it to the menu.
2134                            Class importActionClass = Class
2135                                    .forName(importActionClassName);
2136                            Constructor constructor = importActionClass
2137                                    .getDeclaredConstructor(
2138                                            new Class[] { Top.class });
2139                            AbstractAction importAction = (AbstractAction) constructor
2140                                    .newInstance(new Object[] { this });
2141                            JMenuItem importItem = new JMenuItem(importAction);
2142                            importMenu.add(importItem);
2143                        } catch (Throwable throwable) {
2144                            // We do not want to abort at this point because the worst
2145                            // case is that we will have no Import FMU in the menu.
2146                            // That is better than preventing the user from opening a model.
2147                            System.err.println(
2148                                    "Warning: Tried to create the an import menu item by looking for the "
2149                                            + importActionClassName
2150                                            + " Java class, but failed: "
2151                                            + throwable);
2152                        }
2153                    }
2154                }
2155            } catch (Throwable throwable) {
2156                if (!_printedImportActionClassNamesMessage) {
2157                    _printedImportActionClassNamesMessage = true;
2158                    System.err.println(
2159                            "Problem reading the _importActionClassNames parameter from "
2160                                    + "the configuration: " + throwable);
2161                }
2162            }
2163
2164            // PDF Action.
2165            try {
2166                _exportPDFAction = (AbstractAction) configuration
2167                        .getStringParameterAsClass("_exportPDFActionClassName",
2168                                new Class[] { Top.class },
2169                                new Object[] { this });
2170            } catch (Throwable throwable) {
2171                // We do not want to abort at this point because the worst
2172                // case is that we will have no Export PDF in the menu.
2173                // That is better than preventing the user from opening a model.
2174                //System.err
2175                //    .printlns("Warning: Tried to create the Export PDF menu item, but failed: "
2176                //            + throwable);
2177            }
2178
2179            // Deal with the HTML Action next.
2180            try {
2181                _exportHTMLAction = (AbstractAction) configuration
2182                        .getStringParameterAsClass("_exportHTMLActionClassName",
2183                                new Class[] { BasicGraphFrame.class },
2184                                new Object[] { this });
2185            } catch (Throwable throwable) {
2186                // We do not want to abort at this point because the worst
2187                // case is that we will have no Export to Web in the menu.
2188                // That is better than preventing the user from opening a model.
2189
2190                // We don't include the GPL'd iText PDF in the
2191                // release, so don't print a message if it is missing.
2192
2193                //System.err
2194                //        .println("Warning: Tried to create the Export to Web menu item, but failed: "
2195                //                + throwable);
2196            }
2197        }
2198
2199        // Uncomment the next block to have Export PDF *ALWAYS* enabled.
2200        // We don't want it always enabled because ptiny, the applets and
2201        // Web Start should not included this AGPL'd piece of software
2202
2203        // NOTE: Comment out the entire block with lines that begin with //
2204        // so that the test in adm notices that the block is commented out.
2205
2206        //                 if (_exportPDFAction == null) {
2207        //                     //String exportPDFActionClassName = exportPDFActionClassNameParameter.stringValue();
2208        //                     String exportPDFActionClassName = "ptolemy.vergil.basic.export.itextpdf.ExportPDFAction";
2209        //                     try {
2210        //                         Class exportPDFActionClass = Class
2211        //                                 .forName(exportPDFActionClassName);
2212        //                         Constructor exportPDFActionConstructor = exportPDFActionClass
2213        //                                 .getDeclaredConstructor(Top.class);
2214        //                         _exportPDFAction = (AbstractAction) exportPDFActionConstructor
2215        //                                 .newInstance(this);
2216        //                     } catch (Throwable throwable) {
2217        //                         new InternalErrorException(null, throwable,
2218        //                                 "Failed to construct export PDF class \""
2219        //                                         + exportPDFActionClassName
2220        //                                         + "\", which was read from the configuration.");
2221        //                     }
2222        //                 }
2223
2224        // End of block to uncomment.
2225
2226        if (_exportPDFAction != null) {
2227            // Insert the Export PDF item.
2228            JMenuItem exportItem = new JMenuItem(_exportPDFAction);
2229            exportMenu.add(exportItem);
2230        }
2231
2232        // Next do the export GIF action.
2233        if (_exportGIFAction == null) {
2234            _exportGIFAction = new ExportImageAction("GIF");
2235        }
2236        JMenuItem exportItem = new JMenuItem(_exportGIFAction);
2237        exportMenu.add(exportItem);
2238
2239        // Next do the export PNG action.
2240        if (_exportPNGAction == null) {
2241            _exportPNGAction = new ExportImageAction("PNG");
2242        }
2243        exportItem = new JMenuItem(_exportPNGAction);
2244        exportMenu.add(exportItem);
2245
2246        // Next do the export HTML action.
2247        if (_exportHTMLAction != null) {
2248            // Insert the Export to Web item.
2249            exportItem = new JMenuItem(_exportHTMLAction);
2250            exportMenu.add(exportItem);
2251        }
2252        return fileMenuItems;
2253    }
2254
2255    /** Create a new graph pane.  Subclasses will override this to change
2256     *  the pane that is created.  Note that this method is called in
2257     *  constructor, so derived classes must be careful to not reference
2258     *  local variables that may not have yet been created.
2259     *  @param entity The object to be displayed in the pane.
2260     *  @return The pane that is created.
2261     */
2262    protected abstract GraphPane _createGraphPane(NamedObj entity);
2263
2264    /** Create the component that goes to the right of the library.
2265     *  @param entity The entity to display in the component.
2266     *  @return The component that goes to the right of the library.
2267     */
2268    protected JComponent _createRightComponent(NamedObj entity) {
2269        GraphPane pane = _createGraphPane(entity);
2270
2271        FigureLayer fl = pane.getForegroundLayer();
2272        fl.setPickHalo(2);
2273
2274        EventLayer fel = pane.getForegroundEventLayer();
2275        fel.setConsuming(false);
2276        fel.setEnabled(true);
2277
2278        _mousePressedLayerAdapter = new MousePressedLayerAdapter();
2279        fel.addLayerListener(_mousePressedLayerAdapter);
2280
2281        JGraph graph = new JGraph(pane);
2282        setJGraph(graph);
2283        _dropTarget = new EditorDropTarget(_jgraph);
2284        return _jgraph;
2285    }
2286
2287    /** Create a SizeAttribute for the current model when it is being saved to
2288     *  a file. The size recorded in the SizeAttribute is the size of the
2289     *  current canvas.
2290     *  @return The SizeAttribute.
2291     *  @exception IllegalActionException If "_vergilSize" is found but is not
2292     *  an instance of SizeAttribute, or if a SizeAttribute is not accepted by
2293     *  the current model.
2294     *  @exception NameDuplicationException If the name "_vergilSize" is already
2295     *  used when trying to create the SizeAttribute.
2296     */
2297    protected SizeAttribute _createSizeAttribute()
2298            throws IllegalActionException, NameDuplicationException {
2299        return _createSizeAttribute(getModel());
2300    }
2301
2302    /** Create a SizeAttribute for a specific model when it is being saved to
2303     *  a file. The size recorded in the SizeAttribute is the size of the
2304     *  current canvas.
2305     *  @param model The model.
2306     *  @return The SizeAttribute.
2307     *  @exception IllegalActionException If "_vergilSize" is found but is not
2308     *  an instance of SizeAttribute, or if a SizeAttribute is not accepted by
2309     *  the current model.
2310     *  @exception NameDuplicationException If the name "_vergilSize" is already
2311     *  used when trying to create the SizeAttribute.
2312     */
2313    protected SizeAttribute _createSizeAttribute(NamedObj model)
2314            throws IllegalActionException, NameDuplicationException {
2315        // Have to also record the size of the JGraph because
2316        // setting the size of the frame is ignored if we don't
2317        // also set the size of the JGraph. Why? Who knows. Swing.
2318        if (model != null) {
2319            SizeAttribute size = (SizeAttribute) model
2320                    .getAttribute("_vergilSize", SizeAttribute.class);
2321
2322            if (size == null) {
2323                size = new SizeAttribute(model, "_vergilSize");
2324            }
2325
2326            size.recordSize(_getRightComponent());
2327            return size;
2328        }
2329        return null;
2330    }
2331
2332    /** Export the model into the writer with the given name. If
2333     *  the _query has a selected entry and it is true,
2334     *  then only the selected named objects are exported;
2335     *  otherwise, the whole model is exported with its exportMoML()
2336     *  method.
2337     *
2338     *  @param writer The writer.
2339     *  @param model The model to export.
2340     *  @param name The name of the exported model.
2341     *  @exception IOException If an I/O error occurs.
2342     */
2343    protected void _exportDesignPattern(Writer writer, NamedObj model,
2344            String name) throws IOException {
2345        if (_query != null && _query.hasEntry("selected")
2346                && _query.getBooleanValue("selected")) {
2347            try {
2348                model.workspace().getReadAccess();
2349                String elementName = model.getElementName();
2350                writer.write("<?xml version=\"1.0\" standalone=\"no\"?>\n"
2351                        + "<!DOCTYPE " + elementName + " PUBLIC "
2352                        + "\"-//UC Berkeley//DTD MoML 1//EN\"\n"
2353                        + "    \"http://ptolemy.eecs.berkeley.edu"
2354                        + "/xml/dtd/MoML_1.dtd\">\n");
2355
2356                writer.write("<" + elementName + " name=\"" + name
2357                        + "\" class=\"" + model.getClassName() + "\"");
2358
2359                if (model.getSource() != null) {
2360                    writer.write(" source=\"" + model.getSource() + "\">\n");
2361                } else {
2362                    writer.write(">\n");
2363                }
2364
2365                String[] attributeNames = { "_alternateGetMomlAction",
2366                        "_designPatternIcon", "_transformationBefore",
2367                        "_transformationAfter" };
2368                for (String attributeName : attributeNames) {
2369                    Attribute attribute = model.getAttribute(attributeName);
2370                    if (attribute != null) {
2371                        attribute.exportMoML(writer, 1);
2372                    }
2373                }
2374
2375                HashSet<NamedObj> namedObjSet = _getSelectionSet();
2376                NamedObj container = (NamedObj) _getGraphModel().getRoot();
2377                Iterator<NamedObj> elements = container
2378                        .sortContainedObjects(namedObjSet).iterator();
2379                while (elements.hasNext()) {
2380                    elements.next().exportMoML(writer, 1);
2381                }
2382
2383                if (model instanceof CompositeEntity) {
2384                    writer.write(((CompositeEntity) model).exportLinks(1,
2385                            namedObjSet));
2386                }
2387
2388                writer.write("</" + elementName + ">\n");
2389            } finally {
2390                model.workspace().doneReading();
2391            }
2392        } else {
2393            if (model.getContainer() != null) {
2394                writer.write("<?xml version=\"1.0\" standalone=\"no\"?>\n"
2395                        + "<!DOCTYPE " + model.getElementName() + " PUBLIC "
2396                        + "\"-//UC Berkeley//DTD MoML 1//EN\"\n"
2397                        + "    \"http://ptolemy.eecs.berkeley.edu"
2398                        + "/xml/dtd/MoML_1.dtd\">\n");
2399            }
2400            model.exportMoML(writer, 0, name);
2401        }
2402    }
2403
2404    /** Finish exporting a design pattern.
2405     */
2406    protected void _finishExportDesignPattern() {
2407    }
2408
2409    /** Return the center location of the visible part of the pane
2410     *  for a JGraph.
2411     *  @param graph The JGraph.
2412     *  @return The center of the visible part.
2413     *  @see #setCenter(Point2D)
2414     */
2415    protected Point2D _getCenter(JGraph graph) {
2416        Rectangle2D rect = _getVisibleCanvasRectangle(graph);
2417        return new Point2D.Double(rect.getCenterX(), rect.getCenterY());
2418    }
2419
2420    /** Return the rectangle representing the visible part of the
2421     *  pane for a JGraph, transformed into canvas coordinates.  This is the range
2422     *  of locations that are visible, given the current pan and zoom.
2423     *  @param graph The JGraph.
2424     *  @return The rectangle representing the visible part.
2425     */
2426    protected Rectangle2D _getVisibleCanvasRectangle(JGraph graph) {
2427        AffineTransform current = graph.getCanvasPane().getTransformContext()
2428                .getTransform();
2429
2430        AffineTransform inverse;
2431        try {
2432            inverse = current.createInverse();
2433        } catch (NoninvertibleTransformException e) {
2434            throw new RuntimeException(e.toString());
2435        }
2436
2437        Rectangle2D visibleRect = _getVisibleRectangle(graph);
2438        return ShapeUtilities.transformBounds(visibleRect, inverse);
2439    }
2440
2441    /** Return the rectangle representing the visible part of the
2442     *  pane for a JGraph, in pixel coordinates on the screen.
2443     *  @param graph The JGraph.
2444     *  @return A rectangle whose upper left corner is at (0, 0) and whose
2445     *  size is the size of the canvas component.
2446     */
2447    protected Rectangle2D _getVisibleRectangle(JGraph graph) {
2448        Dimension size = graph.getSize();
2449        return new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight());
2450    }
2451
2452    /** Get the directory that was last accessed by this window.
2453     *  @see #_setDirectory
2454     *  @return The directory last accessed.
2455     *  @deprecated Use {@link #getLastDirectory()} instead
2456     */
2457    @Deprecated
2458    protected File _getDirectory() {
2459        return _getCurrentDirectory();
2460    }
2461
2462    /** Return the graph controller associated with this frame.
2463     *  @return The graph controller associated with this frame.
2464     */
2465    protected GraphController _getGraphController() {
2466        GraphPane graphPane = getJGraph().getGraphPane();
2467        return graphPane.getGraphController();
2468    }
2469
2470    /** Return the graph model associated with this frame.
2471     *  @return The graph model associated with this frame.
2472     */
2473    protected AbstractBasicGraphModel _getGraphModel() {
2474        GraphController controller = _getGraphController();
2475        return (AbstractBasicGraphModel) controller.getGraphModel();
2476    }
2477
2478    /** Return the right component on which graph editing occurs.
2479     *  @return The JGraph on which graph editing occurs.
2480     */
2481    protected JComponent _getRightComponent() {
2482        return _rightComponent;
2483    }
2484
2485    /** Return a set of instances of NamedObj representing the objects
2486     *  that are currently selected.  This set has no particular order
2487     *  to it. If you need the selection objects in proper order, as
2488     *  defined by the container, then call sortContainedObjects()
2489     *  on the container to sort the result.
2490     *  @return The set of selected objects.
2491     */
2492    protected HashSet<NamedObj> _getSelectionSet() {
2493        GraphController controller = _getGraphController();
2494        GraphModel graphModel = controller.getGraphModel();
2495        SelectionModel model = controller.getSelectionModel();
2496        Object[] selection = model.getSelectionAsArray();
2497
2498        // A set, because some objects may represent the same
2499        // ptolemy object.
2500        HashSet<NamedObj> namedObjSet = new HashSet<NamedObj>();
2501        HashSet<Object> nodeSet = new HashSet<Object>();
2502
2503        // First get all the nodes.
2504        for (Object element : selection) {
2505            if (element instanceof Figure) {
2506                Object userObject = ((Figure) element).getUserObject();
2507
2508                if (graphModel.isNode(userObject)) {
2509                    NamedObj actual = (NamedObj) graphModel
2510                            .getSemanticObject(userObject);
2511                    //System.out.println("BasicGraphFrame._getSelectionSet() actual: " + actual.getClass().getName());
2512                    //if ( !(actual instanceof PortParameter)) {
2513                    nodeSet.add(userObject);
2514                    namedObjSet.add(actual);
2515                    //}
2516                }
2517            }
2518        }
2519
2520        for (Object element : selection) {
2521            if (element instanceof Figure) {
2522                Object userObject = ((Figure) element).getUserObject();
2523
2524                if (graphModel.isEdge(userObject)) {
2525                    // Check to see if the head and tail are both being
2526                    // copied.  Only if so, do we actually take the edge.
2527                    Object head = graphModel.getHead(userObject);
2528                    Object tail = graphModel.getTail(userObject);
2529                    boolean headOK = nodeSet.contains(head);
2530                    boolean tailOK = nodeSet.contains(tail);
2531                    Iterator<Object> objects = nodeSet.iterator();
2532
2533                    while (!(headOK && tailOK) && objects.hasNext()) {
2534                        Object object = objects.next();
2535
2536                        if (!headOK && GraphUtilities.isContainedNode(head,
2537                                object, graphModel)) {
2538                            headOK = true;
2539                        }
2540
2541                        if (!tailOK && GraphUtilities.isContainedNode(tail,
2542                                object, graphModel)) {
2543                            tailOK = true;
2544                        }
2545                    }
2546
2547                    if (headOK && tailOK) {
2548                        // Add the relation.
2549                        NamedObj actual = (NamedObj) graphModel
2550                                .getSemanticObject(userObject);
2551                        namedObjSet.add(actual);
2552                    }
2553                }
2554            }
2555        }
2556
2557        return namedObjSet;
2558    }
2559
2560    /**
2561     * Initialize this BasicGraphFrame.
2562     * Derived classes may call this method in their constructors.
2563     * Derived classes should call the various _initBasicGraphFrame*() methods
2564     * so as to avoid code duplication
2565     */
2566    protected void _initBasicGraphFrame() {
2567
2568        // WARNING: If you change this method, then Kepler will probably break.
2569        // kepler/gui/src/org/kepler/gui/KeplerGraphFrame.java extends BasicGraphFrame
2570        // and has an _initBasicGraphFrame() method.
2571
2572        // To build Kepler under Eclipse, see
2573        // https://kepler-project.org/developers/reference/kepler-and-eclipse
2574
2575        // This method calls a series of other protected methods whose
2576        // names start with _initBasicGraphFrame. These methods contain common
2577        // functionality between this class and KeplerGraphFrame so
2578        // that there is a chance that we avoid a ton of code
2579        // duplication.
2580
2581        // Code that is different between this class and KeplerGraphFrame
2582        // appears below.
2583
2584        // Eventually, perhaps the common functionality can be put into one
2585        // method.
2586
2587        _initBasicGraphFrameInitialization();
2588
2589        ActionListener deletionListener = new DeletionListener();
2590
2591        _rightComponent.registerKeyboardAction(deletionListener, "Delete",
2592                KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
2593                JComponent.WHEN_IN_FOCUSED_WINDOW);
2594        _rightComponent.registerKeyboardAction(deletionListener, "BackSpace",
2595                KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0),
2596                JComponent.WHEN_IN_FOCUSED_WINDOW);
2597
2598        _initBasicGraphFrameRightComponent();
2599
2600        // Background color is parameterizable by preferences.
2601        _setBackgroundColor(_rightComponent);
2602
2603        _initBasicGraphFrameRightComponentMouseListeners();
2604
2605        try {
2606            // The SizeAttribute property is used to specify the size
2607            // of the JGraph component. Unfortunately, with Swing's
2608            // mysterious and undocumented handling of component sizes,
2609            // there appears to be no way to control the size of the
2610            // JGraph from the size of the Frame, which is specified
2611            // by the WindowPropertiesAttribute.
2612            SizeAttribute size = (SizeAttribute) getModel()
2613                    .getAttribute("_vergilSize", SizeAttribute.class);
2614
2615            if (size != null) {
2616                size.setSize(_rightComponent);
2617            } else {
2618                // Set the default size.
2619                // Note that the location is of the frame, while the size
2620                // is of the scrollpane.
2621                _rightComponent.setMinimumSize(new Dimension(200, 200));
2622                _rightComponent.setPreferredSize(new Dimension(700, 500));
2623                _rightComponent.setSize(600, 450);
2624            }
2625            _rightComponent.setBorder(BorderFactory.createEtchedBorder());
2626
2627            _initBasicGraphFrameSetZoomAndPan();
2628        } catch (Throwable throwable) {
2629            // Ignore problems here.  Errors simply result in a default
2630            // size and location.
2631        }
2632
2633        // If we don't have a library, we might be trying to only show
2634        // models
2635        // FIXME: should we be checking for _library instead?
2636        Configuration configuration = getConfiguration();
2637        if (configuration != null && (CompositeEntity) configuration
2638                .getEntity("actor library") != null) {
2639            // Create the panner.
2640            _graphPanner = new JCanvasPanner(getJGraph());
2641            _graphPanner.setPreferredSize(new Dimension(200, 150));
2642            // _graphPanner.setMaximumSize(new Dimension(200, 450));
2643            _graphPanner.setSize(200, 150);
2644            // NOTE: Border causes all kinds of problems!
2645            _graphPanner.setBorder(BorderFactory.createEtchedBorder());
2646        }
2647
2648        // Create the library of actors, or use the one in the entity,
2649        // if there is one.
2650        // FIXME: How do we make changes to the library persistent?
2651        boolean gotLibrary = false;
2652
2653        try {
2654            LibraryAttribute libraryAttribute = (LibraryAttribute) getModel()
2655                    .getAttribute("_library", LibraryAttribute.class);
2656
2657            if (libraryAttribute != null) {
2658                // The model contains a library.
2659                try {
2660                    _topLibrary = libraryAttribute.getLibrary();
2661                    if (_topLibrary != null) {
2662                        gotLibrary = true;
2663                    }
2664                } catch (SecurityException ex) {
2665                    System.out.println("Warning: failed to parse "
2666                            + "_library attribute (running in an applet "
2667                            + "or sandbox always causes this)");
2668                }
2669            }
2670        } catch (Exception ex) {
2671            try {
2672                MessageHandler.warning("Invalid library in the model.", ex);
2673            } catch (CancelException e) {
2674            }
2675        }
2676
2677        if (!gotLibrary) {
2678            try {
2679                if (_defaultLibrary != null) {
2680                    // A default library has been specified.
2681                    _topLibrary = _defaultLibrary.getLibrary();
2682                    gotLibrary = true;
2683                }
2684            } catch (SecurityException ex) {
2685                // Ignore, we are in an applet or sandbox.
2686                // We already printed a message, why print it again?
2687            } catch (Exception ex) {
2688                try {
2689                    // FIXME: It seems wrong to call MessageHandler here,
2690                    // instead, we should throw an IllegalActionException?
2691                    MessageHandler.warning(
2692                            "Invalid default library for the frame.", ex);
2693                } catch (CancelException e) {
2694                }
2695            }
2696        }
2697
2698        if (!gotLibrary) {
2699            // Neither the model nor the argument have specified a library.
2700            // See if there is a default library in the configuration.
2701            _topLibrary = _createDefaultLibrary(getModel().workspace());
2702        }
2703
2704        // Only include the palettePane and panner if there is an actor library.
2705        // The ptinyViewer configuration uses this.
2706        if (configuration != null && (CompositeEntity) configuration
2707                .getEntity("actor library") != null) {
2708            _libraryModel = new VisibleTreeModel(_topLibrary);
2709            // Second arguments prevents parameter values from showing in the library.
2710            _library = new PTree(_libraryModel, false);
2711            _library.setRootVisible(false);
2712            _library.setBackground(BACKGROUND_COLOR);
2713
2714            // If you want to expand the top-level libraries, uncomment this.
2715            // Object[] path = new Object[2];
2716            // path[0] = _topLibrary;
2717            // Iterator libraries = _topLibrary.entityList().iterator();
2718            // while (libraries.hasNext()) {
2719            //     path[1] = libraries.next();
2720            //     _library.expandPath(new javax.swing.tree.TreePath(path));
2721            // }
2722
2723            _libraryContextMenuCreator = new PTreeMenuCreator();
2724            _libraryContextMenuCreator
2725                    .addMenuItemFactory(new OpenLibraryMenuItemFactory());
2726            _libraryContextMenuCreator
2727                    .addMenuItemFactory(new DocumentationMenuItemFactory());
2728            _library.addMouseListener(_libraryContextMenuCreator);
2729
2730            _libraryScrollPane = new JScrollPane(_library);
2731            // See _treeViewScrollPane below.
2732            _libraryScrollPane.setMinimumSize(new Dimension(200, 200));
2733            _libraryScrollPane.setPreferredSize(new Dimension(200, 300));
2734            _libraryScrollPane.setBorder(BorderFactory.createEtchedBorder());
2735            _setBackgroundColor(_library);
2736
2737            // create the palette on the left.
2738            _palettePane = new JPanel();
2739            _palettePane.setBorder(null);
2740            _palettePane.setLayout(new GridBagLayout());
2741
2742            // create a query for search.
2743            JPanel findPanel = new JPanel(new GridBagLayout());
2744
2745            // Put in the label.
2746            GridBagConstraints labelConstraints = new GridBagConstraints();
2747            labelConstraints.gridx = 0;
2748            labelConstraints.gridy = 0;
2749            JLabel label = new JLabel("Find:");
2750            findPanel.add(label, labelConstraints);
2751
2752            // Put in the entry box.
2753            _findInLibraryEntryBox = new JTextField(12);
2754            _findInLibraryEntryBox.addActionListener(new FindInLibraryAction());
2755            GridBagConstraints entryBoxConstraints = new GridBagConstraints();
2756            entryBoxConstraints.gridx = 1;
2757            entryBoxConstraints.gridy = 0;
2758            entryBoxConstraints.fill = GridBagConstraints.HORIZONTAL;
2759            entryBoxConstraints.weightx = 1.0;
2760            findPanel.add(_findInLibraryEntryBox, entryBoxConstraints);
2761
2762            // Put in the find panel.
2763            GridBagConstraints findPanelConstraints = new GridBagConstraints();
2764            findPanelConstraints.gridx = 0;
2765            findPanelConstraints.gridy = 0;
2766            findPanelConstraints.fill = GridBagConstraints.HORIZONTAL;
2767            _palettePane.add(findPanel, findPanelConstraints);
2768
2769            // The Hierarchy Tree browser for CompositeEntities.
2770            NamedObj model = getModel();
2771            if (!(model instanceof CompositeEntity)) {
2772                // EditIconFrame will have a EditorIcon as a model, not a CompositeEntity.
2773                _treeViewScrollPane = null;
2774            } else {
2775                _treeViewModel = new ClassAndEntityTreeModel(
2776                        getModel().toplevel());
2777
2778                // Second arguments prevents parameter values from showing in the library,
2779                // I'm not sure if that is relevant for the hierarchy tree browser.
2780                _treeView = new PTree(_treeViewModel, false);
2781                // Replaced by mouse listener.
2782                // _treeView.addTreeSelectionListener(new HierarchyTreeSelectionListener());
2783                _treeView.addMouseListener(new HierarchyTreeMouseAdapter());
2784                _treeView.setBackground(BACKGROUND_COLOR);
2785                _treeView.setCellRenderer(new HierarchyTreeCellRenderer());
2786
2787                _treeViewScrollPane = new JScrollPane(_treeView);
2788                // See _libraryScrollPane above.
2789                _treeViewScrollPane.setMinimumSize(new Dimension(200, 200));
2790                _treeViewScrollPane.setPreferredSize(new Dimension(200, 300));
2791                _treeViewScrollPane
2792                        .setBorder(BorderFactory.createEtchedBorder());
2793                _setBackgroundColor(_treeView);
2794
2795                // Make the Ptolemy model visible in the tree.
2796                TreePath modelTreePath = null;
2797                {
2798                    // Traverse the Ptolemy model hierarchy, create a list, reverse it,
2799                    // create an array and then a TreePath.
2800                    List<NamedObj> compositeList = new LinkedList<NamedObj>();
2801                    NamedObj composite = getModel();
2802                    while (composite != null) {
2803                        compositeList.add(composite);
2804                        composite = composite.getContainer();
2805                    }
2806                    java.util.Collections.reverse(compositeList);
2807                    Object[] composites = compositeList.toArray();
2808                    modelTreePath = new TreePath(composites);
2809                }
2810                _treeView.expandPath(modelTreePath);
2811                _treeView.makeVisible(modelTreePath);
2812                _treeView.scrollPathToVisible(modelTreePath);
2813            }
2814
2815            // Put in the tabbed pane that contains the hierarchy browser and the library
2816            JTabbedPane libraryTreeTabbedPane = new JTabbedPane();
2817            libraryTreeTabbedPane.add("Library", _libraryScrollPane);
2818
2819            if (_treeViewScrollPane != null) {
2820                libraryTreeTabbedPane.add("Tree", _treeViewScrollPane);
2821            }
2822
2823            GridBagConstraints tabbedPaneConstraints = new GridBagConstraints();
2824            tabbedPaneConstraints.gridx = 0;
2825            tabbedPaneConstraints.gridy = 1;
2826            tabbedPaneConstraints.fill = GridBagConstraints.BOTH;
2827            tabbedPaneConstraints.weightx = 1.0;
2828            tabbedPaneConstraints.weighty = 0.7;
2829            _palettePane.add(libraryTreeTabbedPane, tabbedPaneConstraints);
2830
2831            // Add the graph panner.
2832            if (_graphPanner != null) {
2833                GridBagConstraints pannerConstraints = new GridBagConstraints();
2834                pannerConstraints.gridx = 0;
2835                pannerConstraints.gridy = 2;
2836                pannerConstraints.weighty = 0.3;
2837                pannerConstraints.fill = GridBagConstraints.BOTH;
2838                _palettePane.add(_graphPanner, pannerConstraints);
2839            }
2840
2841            _splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
2842            _splitPane.setLeftComponent(_palettePane);
2843            _splitPane.setRightComponent(_rightComponent);
2844            getContentPane().add(_splitPane, BorderLayout.CENTER);
2845        } else {
2846            getContentPane().add(_rightComponent, BorderLayout.CENTER);
2847        }
2848
2849        _toolbar = new JToolBar();
2850        _toolbar.setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
2851        try {
2852            new ToolBar(getTableau(), "toolbar", _toolbar, BorderLayout.NORTH);
2853        } catch (Exception e) {
2854            throw new InternalErrorException(getTableau(), e,
2855                    "Unable to create tool bar.");
2856        }
2857
2858        GUIUtilities.addToolBarButton(_toolbar, _saveAction);
2859
2860        // Note that in Top we disable Print unless the class implements
2861        // the Printable or Pageable interfaces.  By definition, this class
2862        // implements the Printable interface
2863        GUIUtilities.addToolBarButton(_toolbar, _printAction);
2864
2865        _initBasicGraphFrameToolBarZoomButtons();
2866
2867        GUIUtilities.addToolBarButton(_toolbar, _openContainerAction);
2868        if (getModel() == getModel().toplevel()) {
2869            // If we are at the top level, disable.
2870            _openContainerAction.setEnabled(false);
2871        }
2872
2873        _initBasicGraphFrameActions();
2874
2875        // Add a weak reference to this to keep track of all
2876        // the graph frames that have been created.
2877        _openGraphFrames.add(this);
2878    }
2879
2880    /** Add the cut, copy, paste, move to front, mode to back
2881     *  actions.  Also add the EditPreferencesAction and initialize
2882     *  the layout gui.
2883     *  Derived classes usually call this at the end of
2884     *  _initBasicGraphFrame().
2885     */
2886    protected void _initBasicGraphFrameActions() {
2887        _cutAction = new CutAction();
2888        _copyAction = new CopyAction();
2889        _pasteAction = new PasteAction();
2890        _findAction = new FindAction();
2891
2892        // FIXME: vergil.kernel.AttributeController also defines context
2893        // menu choices that do the same thing.
2894        _moveToFrontAction = new MoveToFrontAction();
2895        _moveToBackAction = new MoveToBackAction();
2896
2897        _editPreferencesAction = new EditPreferencesAction();
2898
2899        _initLayoutGuiAction();
2900        _initReloadAccessorsAction();
2901    }
2902
2903    /** Set up the right component. */
2904    protected void _initBasicGraphFrameRightComponent() {
2905        _rightComponent.setRequestFocusEnabled(true);
2906        _rightComponent.setAlignmentX(1);
2907        _rightComponent.setAlignmentY(1);
2908    }
2909
2910    /** Add listeners to the right component. */
2911    protected void _initBasicGraphFrameRightComponentMouseListeners() {
2912        _rightComponent.addMouseWheelListener(this);
2913        _rightComponent.addMouseMotionListener(this);
2914        _rightComponent.addMouseListener(this);
2915    }
2916
2917    /** Common initialization for a BasicGraphFrame.
2918     *  Derived classes should call this method early in
2919     *  _initBasicGraphFrame().
2920     */
2921    protected void _initBasicGraphFrameInitialization() {
2922        getModel().addChangeListener(this);
2923        getContentPane().setLayout(new BorderLayout());
2924        _rightComponent = _createRightComponent(getModel());
2925    }
2926
2927    /** Add tool bar buttons.
2928     *  Derived classes should set _toolbar before calling
2929     *  this method.
2930     */
2931    protected void _initBasicGraphFrameToolBarZoomButtons() {
2932        GUIUtilities.addToolBarButton(_toolbar, _zoomInAction);
2933        GUIUtilities.addToolBarButton(_toolbar, _zoomResetAction);
2934        GUIUtilities.addToolBarButton(_toolbar, _zoomFitAction);
2935        GUIUtilities.addToolBarButton(_toolbar, _zoomOutAction);
2936    }
2937
2938    /** Set the zoom factor and the pan.
2939     *  @exception IllegalActionException If the zoom or pan parameters
2940     *  cannot be read.
2941     */
2942    protected void _initBasicGraphFrameSetZoomAndPan()
2943            throws IllegalActionException {
2944        _initBasicGraphFrameSetZoomAndPane(getModel(), getJGraph());
2945    }
2946
2947    /** Set the zoom factor and the pan for a specific model and JGraph.
2948     *  @param model The model.
2949     *  @param jgraph The JGraph.
2950     *  @exception IllegalActionException If the zoom or pan parameters
2951     *  cannot be read.
2952     */
2953    protected void _initBasicGraphFrameSetZoomAndPane(NamedObj model,
2954            JGraph jgraph) throws IllegalActionException {
2955
2956        // Set the zoom factor.
2957        Parameter zoom = (Parameter) model.getAttribute("_vergilZoomFactor",
2958                Parameter.class);
2959        if (zoom != null) {
2960            _zoom(jgraph, ((DoubleToken) zoom.getToken()).doubleValue());
2961            // Make sure the visibility is only expert.
2962            zoom.setVisibility(Settable.EXPERT);
2963        }
2964
2965        // Set the pan position.
2966        Parameter pan = (Parameter) model.getAttribute("_vergilCenter",
2967                Parameter.class);
2968
2969        if (pan != null) {
2970            ArrayToken panToken = (ArrayToken) pan.getToken();
2971            Point2D center = new Point2D.Double(
2972                    ((DoubleToken) panToken.getElement(0)).doubleValue(),
2973                    ((DoubleToken) panToken.getElement(1)).doubleValue());
2974            _setCenter(jgraph, center);
2975
2976            // Make sure the visibility is only expert.
2977            pan.setVisibility(Settable.EXPERT);
2978        }
2979
2980        // If we have neither zooming or panning info...
2981        if (zoom == null && pan == null) {
2982            // ...set the top left corner of the view to the top left corner of the model.
2983            // Note: This code only works for a zoom factor of 1.0, which is no problem at
2984            // this stage since that's the default and no zooming info was found in the model.
2985            GraphPane pane = jgraph.getGraphPane();
2986            Rectangle2D bounds = pane.getForegroundLayer().getLayerBounds();
2987            Rectangle2D visible = _getVisibleRectangle(jgraph);
2988
2989            double centerX = visible.getCenterX()
2990                    - (visible.getX() - bounds.getX());
2991            double centerY = visible.getCenterY()
2992                    - (visible.getY() - bounds.getY());
2993
2994            // Set the new center point, but add a little free space between model and border
2995            _setCenter(jgraph,
2996                    new Point2D.Double(centerX - 10.0, centerY - 10.0));
2997        }
2998    }
2999
3000    /** Initialize the layout gui. */
3001    protected void _initLayoutGuiAction() {
3002        // Try to create an advanced layout action.
3003        final IGuiAction layoutGuiAction = _createLayoutAction();
3004        if (layoutGuiAction != null) {
3005            _layoutAction = new AbstractAction("Automatic Layout") {
3006                @Override
3007                public void actionPerformed(ActionEvent e) {
3008                    layoutGuiAction.doAction(getModel());
3009                }
3010            };
3011            // The advanced layout action is available, so create the configuration
3012            // dialog for displaying layout parameters.
3013            _layoutConfigDialogAction = new LayoutConfigDialogAction();
3014        } else {
3015            // The advanced layout action is not available, so use the simple
3016            // Ptolemy layout algorithm.
3017            _layoutAction = new AbstractAction("Automatic Layout") {
3018                @Override
3019                public void actionPerformed(ActionEvent e) {
3020                    new PtolemyLayoutAction().doAction(getModel());
3021                }
3022            };
3023        }
3024        _layoutAction.putValue("tooltip", "Layout the graph (Ctrl+T)");
3025        _layoutAction.putValue(GUIUtilities.ACCELERATOR_KEY,
3026                KeyStroke.getKeyStroke(KeyEvent.VK_T,
3027                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
3028        _layoutAction.putValue(GUIUtilities.MNEMONIC_KEY,
3029                Integer.valueOf(KeyEvent.VK_L));
3030    }
3031
3032    /** Initialize the reload accessors menu choice. */
3033    protected void _initReloadAccessorsAction() {
3034        // If the JSAccessors.reloadAllAccessors(CompositeEntity)
3035        // method can be found, then add a menu choice.
3036        Method accessorReloader = null;
3037        try {
3038            Class accessorClass = Class
3039                    .forName("org.terraswarm.accessor.JSAccessor");
3040            accessorReloader = accessorClass.getDeclaredMethod(
3041                    "reloadAllAccessors",
3042                    new Class[] { CompositeEntity.class });
3043        } catch (Throwable throwable) {
3044            // Fail silently!
3045        }
3046        final Method accessorReloaderFinal = accessorReloader;
3047        if (accessorReloader != null) {
3048            _reloadAccessorsAction = new AbstractAction("Reload Accessors") {
3049                @Override
3050                public void actionPerformed(ActionEvent e) {
3051                    try {
3052                        accessorReloaderFinal.invoke(null,
3053                                (CompositeEntity) getModel());
3054                    } catch (Exception ex) {
3055                        MessageHandler.error(
3056                                "Failed to reload all the accessors.", ex);
3057                    }
3058                }
3059            };
3060        }
3061    }
3062
3063    /** Return true if this is a design pattern.
3064     *  @return true if the model corresponding to this object
3065     *  has a DesignPatternIcon attribute.
3066     */
3067    protected boolean _isDesignPattern() {
3068        NamedObj model = getModel();
3069        return !model.attributeList(DesignPatternIcon.class).isEmpty();
3070    }
3071
3072    /** Prepare to export a design pattern.
3073     *  In this base class, do nothing.
3074     */
3075    protected void _prepareExportDesignPattern() {
3076    }
3077
3078    /** Create and return a file dialog for the "Save As" command.
3079     *  This overrides the base class so that if this is a design pattern
3080     *  and items are selected, then the user is asked if they
3081     *  want to save only the selected objects.
3082     *  <p>If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns true
3083     *  then {@link ptolemy.gui.Top#_saveAs()} uses this method.  Otherwise,
3084     *  {@link #_saveAsJFileChooserComponent()} is used.</p>
3085     *  @return A file dialog for save as.
3086     */
3087    @Override
3088    protected FileDialog _saveAsFileDialogComponent() {
3089        FileDialog fileDialog = super._saveAsFileDialogComponent();
3090        if (_isDesignPattern()) {
3091            if (!_getSelectionSet().isEmpty()) {
3092                // FIXME: It is not clear to me when this code would be called.
3093                // File -> New -> Ptera Model, then opening DesignPatterns,
3094                // dragging in a ListenToInput, selecting it and doing Save As
3095                // does not do it.
3096
3097                _query = new Query();
3098                _query.addCheckBox("selected", "Selected objects only", true);
3099                // The problem here is that with FileDialog, we can't add the
3100                // query as an accessory like we can with JFileChooser.  So, we
3101                // pop up a check box dialog before bringing up the FileDialog.
3102                new ComponentDialog(this, "Save submodel only?", _query);
3103            }
3104        }
3105
3106        return fileDialog;
3107    }
3108
3109    /** Create and return a file dialog for the "Save As" command.
3110     *  This overrides the base class so that if this is a design pattern
3111     *  and items are selected, then the user is asked if they
3112     *  want to save only the selected objects.
3113     *  <p>If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns false
3114     *  then {@link ptolemy.gui.Top#_saveAs()} uses this method.  Otherwise,
3115     *  {@link #_saveAsFileDialogComponent()} is used.</p>
3116
3117     *  @return A file dialog for save as.
3118     */
3119    @Override
3120    protected JFileChooser _saveAsJFileChooserComponent() {
3121        JFileChooser fileChooser = super._saveAsJFileChooserComponent();
3122
3123        if (_isDesignPattern()) {
3124            if (_getSelectionSet().isEmpty()) {
3125                fileChooser.setAccessory(null);
3126            } else {
3127                _query = new Query();
3128                _query.addCheckBox("selected", "Selected objects only", true);
3129                fileChooser.setAccessory(_query);
3130            }
3131        }
3132
3133        return fileChooser;
3134    }
3135
3136    /** Set the background color of the specified component to the default,
3137     *  or if it is given, to the background color given in the preferences.
3138     *  @param component The component to set the color of.
3139     */
3140    protected void _setBackgroundColor(Component component) {
3141        Configuration configuration = getConfiguration();
3142        component.setBackground(BACKGROUND_COLOR);
3143        if (configuration != null) {
3144            try {
3145                PtolemyPreferences preferences = PtolemyPreferences
3146                        .getPtolemyPreferencesWithinConfiguration(
3147                                configuration);
3148                if (preferences != null) {
3149                    component.setBackground(
3150                            preferences.backgroundColor.asColor());
3151                }
3152            } catch (IllegalActionException e1) {
3153                // Ignore the exception and use the default color.
3154            }
3155        }
3156    }
3157
3158    /** Set the center location of the visible part of a specific pane.
3159     *  This will cause the panner to center on the specified location
3160     *  with the current zoom factor.
3161     *  @param jgraph The JGraph.
3162     *  @param center The center of the visible part.
3163     *  @see #getCenter()
3164     */
3165    protected void _setCenter(JGraph jgraph, Point2D center) {
3166        Rectangle2D visibleRect = _getVisibleCanvasRectangle(jgraph);
3167        AffineTransform newTransform = jgraph.getCanvasPane()
3168                .getTransformContext().getTransform();
3169
3170        newTransform.translate(visibleRect.getCenterX() - center.getX(),
3171                visibleRect.getCenterY() - center.getY());
3172
3173        jgraph.getCanvasPane().setTransform(newTransform);
3174
3175        /*
3176        System.out.println("set pan to " +
3177                center.getX() + " " + center.getY() + " " +
3178                "for " + ((AbstractBasicGraphModel) jgraph.getGraphPane().getGraphModel()).getPtolemyModel().getFullName());
3179         */
3180    }
3181
3182    /** Set the directory that was last accessed by this window.
3183     *  @see #getLastDirectory
3184     *  @param directory The directory last accessed.
3185     *  @deprecated Use {@link #setDirectory(File)} instead
3186     */
3187    @Deprecated
3188    protected void _setDirectory(File directory) {
3189        setDirectory(directory);
3190    }
3191
3192    /** Enable or disable drop into.
3193     *  @param enable False to disable.
3194     */
3195    protected void _setDropIntoEnabled(boolean enable) {
3196        _dropTarget.setDropIntoEnabled(enable);
3197    }
3198
3199    /**
3200     * Update the size, zoom and position of the window for a specific model
3201     * and JGraph. This method is typically called when closing the window or
3202     * writing the moml file out.
3203     * @param parent The parent frame.
3204     * @param model The model.
3205     * @param graph The JGraph.
3206     * @exception IllegalActionException If there is a problem getting a
3207     * parameter.
3208     * @exception NameDuplicationException If there is a problem creating a
3209     * parameter.
3210     */
3211    protected void _updateWindowAttributes(Frame parent, NamedObj model,
3212            JGraph graph)
3213            throws IllegalActionException, NameDuplicationException {
3214
3215        // If there is no parent that is a Frame, do nothing.
3216        // We know that: (parent == null) || (parent instanceof Frame)
3217        if (parent != null) {
3218            WindowPropertiesAttribute properties = (WindowPropertiesAttribute) model
3219                    .getAttribute("_windowProperties",
3220                            WindowPropertiesAttribute.class);
3221
3222            if (properties == null) {
3223                properties = new WindowPropertiesAttribute(model,
3224                        "_windowProperties");
3225            }
3226
3227            // This method uses MoMLChangeRequest
3228            properties.recordProperties(parent);
3229        }
3230
3231        _createSizeAttribute(model);
3232
3233        // Also record zoom and pan state.
3234        AffineTransform current = graph.getCanvasPane().getTransformContext()
3235                .getTransform();
3236
3237        // We assume the scaling in the X and Y directions are the same.
3238        double scale = current.getScaleX();
3239        Parameter zoom = (Parameter) model.getAttribute("_vergilZoomFactor",
3240                Parameter.class);
3241
3242        boolean updateValue = false;
3243        if (zoom == null || zoom.getToken() == null) {
3244            // NOTE: This will not propagate.
3245            zoom = new ExpertParameter(model, "_vergilZoomFactor");
3246            zoom.setToken("1.0");
3247            updateValue = true;
3248        } else {
3249            double oldZoom = ((DoubleToken) zoom.getToken()).doubleValue();
3250            if (oldZoom != scale) {
3251                updateValue = true;
3252            }
3253        }
3254
3255        if (updateValue) {
3256            // Don't call setToken(), instead use a MoMLChangeRequest so that
3257            // the model is marked modified so that any changes are preserved.
3258            //zoom.setToken(new DoubleToken(scale));
3259            String moml = "<property name=\"_vergilZoomFactor\" " + " value=\""
3260                    + scale + "\"/>";
3261            MoMLChangeRequest request = new MoMLChangeRequest(this, model,
3262                    moml);
3263            request.setUndoable(true);
3264            model.requestChange(request);
3265
3266            // Make sure the visibility is only expert.
3267            zoom.setVisibility(Settable.EXPERT);
3268
3269            //System.out.println("updating zoom to " + scale + " for " + model.getFullName());
3270        }
3271
3272        // Save the center, to record the pan state.
3273        Point2D center = _getCenter(graph);
3274        Parameter pan = (Parameter) model.getAttribute("_vergilCenter",
3275                Parameter.class);
3276
3277        updateValue = false;
3278        if (pan == null || pan.getToken() == null) {
3279            // NOTE: This will not propagate.
3280            pan = new ExpertParameter(model, "_vergilCenter");
3281            pan.setToken("{" + center.getX() + ", " + center.getY() + "}");
3282            updateValue = true;
3283        } else {
3284            Token[] oldCenter = ((ArrayToken) pan.getToken()).arrayValue();
3285            double oldCenterX = ((DoubleToken) oldCenter[0]).doubleValue();
3286            double oldCenterY = ((DoubleToken) oldCenter[1]).doubleValue();
3287            if (center.getX() != oldCenterX || center.getY() != oldCenterY) {
3288                updateValue = true;
3289            }
3290        }
3291
3292        if (updateValue) {
3293            //Token[] centerArray = new Token[2];
3294            //centerArray[0] = new DoubleToken(center.getX());
3295            //centerArray[1] = new DoubleToken(center.getY());
3296            //pan.setToken(new ArrayToken(centerArray));
3297
3298            String moml = "<property name=\"_vergilCenter\" " + " value=\"{"
3299                    + center.getX() + ", " + center.getY() + "}\"/>";
3300            MoMLChangeRequest request = new MoMLChangeRequest(this, model,
3301                    moml);
3302            request.setUndoable(true);
3303            model.requestChange(request);
3304
3305            // Make sure the visibility is only expert.
3306            pan.setVisibility(Settable.EXPERT);
3307
3308            /*
3309            System.out.println("saving pan as " +
3310                    center.getX() + " " +
3311                    center.getY() +
3312                    " for " + model.getFullName());
3313             */
3314        }
3315    }
3316
3317    /** Write the model to the specified file.  This overrides the base
3318     *  class to record the current size and position of the window
3319     *  in the model.
3320     *  @param file The file to write to.
3321     *  @exception IOException If the write fails.
3322     */
3323    @Override
3324    protected void _writeFile(File file) throws IOException {
3325        try {
3326            updateWindowAttributes();
3327        } catch (KernelException ex) {
3328            // Ignore problems here.  Errors simply result in a
3329            // default size and location.
3330            System.out.println(
3331                    "While writing, failed to save size, position or zoom factor: "
3332                            + ex);
3333        }
3334
3335        if (_isDesignPattern()) {
3336            FileWriter fileWriter = null;
3337
3338            try {
3339                fileWriter = new FileWriter(file);
3340                String name = getModel().getName();
3341                String filename = file.getName();
3342                int period = filename.indexOf(".");
3343                if (period > 0) {
3344                    name = filename.substring(0, period);
3345                } else {
3346                    name = filename;
3347                }
3348                _exportDesignPattern(fileWriter, getModel(), name);
3349            } finally {
3350                if (fileWriter != null) {
3351                    fileWriter.close();
3352                }
3353            }
3354        } else {
3355            super._writeFile(file);
3356        }
3357    }
3358
3359    /** Return the MoML to delete the specified selection objects.
3360     *  This has the side effect of unselecting the objects. It also
3361     *  deletes edges that are not fully connected (these deletions
3362     *  cannot be done through MoML, and cannot be undone).
3363     *  @param graphModel The graph model.
3364     *  @param selection The selection.
3365     *  @param model The selection model.
3366     *  @return The MoML to delete the selected objects.
3367     */
3368    protected StringBuffer _deleteMoML(AbstractBasicGraphModel graphModel,
3369            Object[] selection, SelectionModel model) {
3370
3371        // First collect selected objects into the userObjects array
3372        // and deselect them.
3373        Object[] userObjects = new Object[selection.length];
3374        for (int i = 0; i < selection.length; i++) {
3375            userObjects[i] = ((Figure) selection[i]).getUserObject();
3376            model.removeSelection(selection[i]);
3377        }
3378
3379        // Create a set to hold those elements whose deletion
3380        // does not go through MoML. This is only links that
3381        // are not connected to another port or a relation.
3382        HashSet<Object> edgeSet = new HashSet<Object>();
3383
3384        StringBuffer moml = new StringBuffer("<group>\n");
3385
3386        // Delete edges then nodes, since deleting relations may
3387        // result in deleting links to that relation.
3388        for (int i = 0; i < selection.length; i++) {
3389            Object userObject = userObjects[i];
3390
3391            if (graphModel.isEdge(userObject)) {
3392                NamedObj actual = (NamedObj) graphModel
3393                        .getSemanticObject(userObject);
3394
3395                // If there is no semantic object, then this edge is
3396                // not fully connected, so we can't go through MoML.
3397                if (actual == null) {
3398                    edgeSet.add(userObject);
3399                } else {
3400                    moml.append(graphModel.getDeleteEdgeMoML(userObject));
3401                }
3402            }
3403        }
3404
3405        // First, delete all the non-attributes.
3406        // This helps avoid deleting properties such as top level parameters
3407        // upon which the entities depend.
3408        // FIXME: what if we have a parameter that is used by both the selection
3409        // and the other parts of the model?
3410        for (int i = 0; i < selection.length; i++) {
3411            Object userObject = userObjects[i];
3412
3413            NamedObjNodeModel namedObjNodeModel = (NamedObjNodeModel) graphModel
3414                    .getNodeModel(userObject);
3415            if (graphModel.isNode(userObject)
3416                    && !(namedObjNodeModel instanceof AttributeNodeModel)) {
3417                NamedObj actual = (NamedObj) graphModel
3418                        .getSemanticObject(userObject);
3419                if (!(actual instanceof ParameterPort)) {
3420                    // We don't delete ParameterPorts here because if
3421                    // we drag a region around a ParmeterPort, then
3422                    // both the PortParameter and the ParameterPort
3423                    // are selected.  Deleting both results in an
3424                    // error.  If we just click (not drag) on a
3425                    // ParameterPort, then the PortParameter is only
3426                    // selected and deletion work ok.  See
3427                    // https://chess.eecs.berkeley.edu/bugzilla/show_bug.cgi?id=311
3428                    moml.append(graphModel.getDeleteNodeMoML(userObject));
3429                }
3430            }
3431        }
3432
3433        // Now delete attributes.
3434        for (int i = 0; i < selection.length; i++) {
3435            Object userObject = userObjects[i];
3436
3437            NamedObjNodeModel namedObjNodeModel = (NamedObjNodeModel) graphModel
3438                    .getNodeModel(userObject);
3439            if (graphModel.isNode(userObject)
3440                    && namedObjNodeModel instanceof AttributeNodeModel) {
3441                moml.append(graphModel.getDeleteNodeMoML(userObject));
3442            }
3443        }
3444
3445        moml.append("</group>\n");
3446
3447        // Have both MoML to perform deletion and set of objects whose
3448        // deletion does not go through MoML. This set of objects
3449        // should be very small and so far consists of only links that are not
3450        // connected to a relation
3451        try {
3452            // First manually delete any objects whose deletion does not go
3453            // through MoML and so are not undoable
3454            // Note that we turn off event dispatching so that each individual
3455            // removal does not trigger graph redrawing.
3456            graphModel.setDispatchEnabled(false);
3457
3458            Iterator<Object> edges = edgeSet.iterator();
3459
3460            while (edges.hasNext()) {
3461                Object nextEdge = edges.next();
3462
3463                if (graphModel.isEdge(nextEdge)) {
3464                    graphModel.disconnectEdge(this, nextEdge);
3465                }
3466            }
3467        } finally {
3468            graphModel.setDispatchEnabled(true);
3469        }
3470
3471        return moml;
3472    }
3473
3474    /** Zoom in or out to magnify by the specified factor, from the current
3475     *  magnification for a specific JGraph.
3476     *  @param jgraph The JGraph.
3477     *  @param factor The magnification factor (relative to 1.0).
3478     */
3479    protected void _zoom(JGraph jgraph, double factor) {
3480        try {
3481            _zoomFlag = true;
3482            JCanvas canvas = jgraph.getGraphPane().getCanvas();
3483            AffineTransform current = canvas.getCanvasPane()
3484                    .getTransformContext().getTransform();
3485
3486            // Save the center, so we remember what we were looking at.
3487            Point2D center = _getCenter(jgraph);
3488            current.scale(factor, factor);
3489            canvas.getCanvasPane().setTransform(current);
3490
3491            /*
3492            System.out.println("set zoom to " +
3493                    factor + " " +
3494                    "for " + ((AbstractBasicGraphModel) jgraph.getGraphPane().getGraphModel()).getPtolemyModel().getFullName());
3495             */
3496
3497            // Reset the center.
3498            _setCenter(jgraph, center);
3499
3500            // Only repaint the panner if it exists and is for the
3501            // same JGraph.
3502            if (_graphPanner != null && _graphPanner.getCanvas() == jgraph) {
3503                _graphPanner.repaint();
3504            }
3505        } finally {
3506            _zoomFlag = false;
3507        }
3508    }
3509
3510    ///////////////////////////////////////////////////////////////////
3511    ////                         protected variables               ////
3512
3513    /** The copy action. */
3514    protected Action _copyAction;
3515
3516    /** The cut action. */
3517    protected Action _cutAction;
3518
3519    /** The default Library. **/
3520    protected LibraryAttribute _defaultLibrary;
3521
3522    /** The instance of EditorDropTarget associated with the JGraph. */
3523    protected EditorDropTarget _dropTarget;
3524
3525    /** The edit menu. */
3526    protected JMenu _editMenu;
3527
3528    /** The action to edit preferences. */
3529    protected EditPreferencesAction _editPreferencesAction;
3530
3531    /** The export to GIF action. */
3532    protected Action _exportGIFAction;
3533
3534    /** The export HTML action. */
3535    protected Action _exportHTMLAction;
3536
3537    /** The export to PDF action. */
3538    protected Action _exportPDFAction;
3539
3540    /** The export to PNG action. */
3541    protected Action _exportPNGAction;
3542
3543    /** The find action. */
3544    protected Action _findAction;
3545
3546    /** The graph menu. */
3547    protected JMenu _graphMenu;
3548
3549    /** The panner. Note that this variable
3550     *  can be null if the configuration does not have an entity named
3551     *  "actor library".  For example, see $PTII/bin/vergil -ptinyViewer.
3552     */
3553    protected JCanvasPanner _graphPanner;
3554
3555    /** The instance of JGraph for this editor. */
3556    protected JGraph _jgraph;
3557
3558    /** The action for automatically laying out the graph.
3559     *  This can be either an advanced layout or the simple Ptolemy layout,
3560     *  depending on whether the better one is available.
3561     */
3562    protected Action _layoutAction;
3563
3564    /** The action for opening the layout configuration dialog.
3565     *  This reference can be {@code null}, since the dialog is only supported
3566     *  if advanced layout is available. In this case the action should not
3567     *  be shown in menus.
3568     */
3569    protected Action _layoutConfigDialogAction;
3570
3571    /** The library display widget. */
3572    protected JTree _library;
3573
3574    /** The library context menu creator. */
3575    protected PTreeMenuCreator _libraryContextMenuCreator;
3576
3577    /** The library model. */
3578    protected EntityTreeModel _libraryModel;
3579
3580    /** The library scroll pane. */
3581    protected JScrollPane _libraryScrollPane;
3582
3583    /** Action to move to the back. */
3584    protected MoveToBackAction _moveToBackAction;
3585
3586    /** Action to move to the front. */
3587    protected MoveToFrontAction _moveToFrontAction;
3588
3589    /** List of references to graph frames that are open. */
3590    protected static LinkedList<BasicGraphFrame> _openGraphFrames = new LinkedList<BasicGraphFrame>();
3591
3592    /** The library display panel. */
3593    protected JPanel _palettePane;
3594
3595    /** The paste action. */
3596    protected Action _pasteAction;
3597
3598    /** The action for reloading accessors. */
3599    protected Action _reloadAccessorsAction;
3600
3601    /** The right component for this editor. */
3602    protected JComponent _rightComponent;
3603
3604    /** The split pane for library and editor. Note that this variable
3605     *  can be null if the configuration does not have an entity named
3606     *  "actor library".  For example, see $PTII/bin/vergil -ptinyViewer.
3607     */
3608    protected JSplitPane _splitPane;
3609
3610    /** The toolbar. */
3611    protected JToolBar _toolbar;
3612
3613    /** The library. */
3614    protected CompositeEntity _topLibrary;
3615
3616    /** The tree view of the model, used for browsing large models. */
3617    protected PTree _treeView;
3618
3619    /** The tree view scroll pane. */
3620    protected JScrollPane _treeViewScrollPane;
3621
3622    /** The tree view  model. */
3623    protected ClassAndEntityTreeModel _treeViewModel;
3624
3625    /** Action for zoom fitting. */
3626    protected Action _zoomFitAction = new ZoomFitAction("Zoom Fit");
3627
3628    /** Action for zooming in. */
3629    protected Action _zoomInAction = new ZoomInAction("Zoom In");
3630
3631    /** Action for zooming out. */
3632    protected Action _zoomOutAction = new ZoomOutAction("Zoom Out");
3633
3634    /** Action for zoom reset. */
3635    protected Action _zoomResetAction = new ZoomResetAction("Zoom Reset");
3636
3637    /** True if we are inside zoom().  Used by derived classes with scrollbars.
3638     */
3639    protected boolean _zoomFlag = false;
3640
3641    ///////////////////////////////////////////////////////////////////
3642    ////                         private methods                   ////
3643
3644    /**
3645     * Create an action for advanced automatic layout, if possible.
3646     *
3647     * @return a layout action, or null if it cannot be created
3648     */
3649    private IGuiAction _createLayoutAction() {
3650        try {
3651            StringParameter layoutGraphActionParameter = (StringParameter) getConfiguration()
3652                    .getAttribute("_layoutGraphAction", StringParameter.class);
3653            if (layoutGraphActionParameter != null) {
3654                // Try to find the class given in the configuration.
3655                Class layoutGraphActionClass = Class
3656                        .forName(layoutGraphActionParameter.stringValue());
3657
3658                // Try to create an instance using the default constructor.
3659                Object object = layoutGraphActionClass.getDeclaredConstructor()
3660                        .newInstance();
3661
3662                if (object instanceof IGuiAction) {
3663                    // If the action is a filter and the model is set, ask the action
3664                    // whether is supports the model.
3665                    if (object instanceof Filter && getModel() != null) {
3666                        if (!((Filter) object).accept(getModel())) {
3667                            return null;
3668                        }
3669                    }
3670
3671                    return (IGuiAction) object;
3672                }
3673            }
3674        } catch (Throwable throwable) {
3675            // Fail silently!
3676        }
3677        return null;
3678    }
3679
3680    ///////////////////////////////////////////////////////////////////
3681    ////                         private variables                 ////
3682
3683    /** The entry box for find in library. */
3684    JTextField _findInLibraryEntryBox;
3685
3686    /** A layer adapter to handle the mousePressed event. */
3687    private MousePressedLayerAdapter _mousePressedLayerAdapter;
3688
3689    /** Action for opening the container, moving uplevel. */
3690    private Action _openContainerAction = new OpenContainerAction(
3691            "Open the container");
3692
3693    /** X coordinate of where we last processed a press or drag of the
3694     *  middle mouse button.
3695     */
3696    private int _previousMouseX = 0;
3697
3698    /** Y coordinate of where we last processed a press or drag of the
3699     *  middle mouse button.
3700     */
3701    private int _previousMouseY = 0;
3702
3703    /**  Action to print the model. */
3704    private Action _printAction = new PrintAction("Print");
3705
3706    /** True if the message about problems reading
3707     *  _importActionClassNames has been printed.
3708     */
3709    private static boolean _printedImportActionClassNamesMessage = false;
3710
3711    /** Action to redo the last undone MoML change. */
3712    private Action _redoAction = new RedoAction();
3713
3714    /**  Action to save the model. */
3715    private Action _saveAction = new SaveAction("Save");
3716
3717    /** Action to undo the last MoML change. */
3718    private Action _undoAction = new UndoAction();
3719
3720    private static double _ZOOM_FIT_PADDING = 5.0;
3721
3722    ///////////////////////////////////////////////////////////////////
3723    ////                         inner classes                     ////
3724
3725    /** A Layer Adapter to handle the mousePressed layer event. */
3726    protected static class MousePressedLayerAdapter extends LayerAdapter {
3727        // FindBugs indicates that this should be a static class.
3728
3729        /** Invoked when the mouse is pressed on a layer
3730         * or figure.
3731         */
3732        @Override
3733        public void mousePressed(LayerEvent event) {
3734            Component component = event.getComponent();
3735
3736            if (!component.hasFocus()) {
3737                component.requestFocus();
3738            }
3739        }
3740    }
3741
3742    ///////////////////////////////////////////////////////////////////
3743    //// CopyAction
3744
3745    /** Action to copy the current selection. */
3746    protected class CopyAction extends AbstractAction {
3747        /** Create a new action to copy the current selection. */
3748        public CopyAction() {
3749            super("Copy");
3750            putValue("tooltip",
3751                    "Copy the current selection onto the clipboard.");
3752            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
3753                    KeyEvent.VK_C,
3754                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
3755            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_C));
3756        }
3757
3758        /** Copy the current selection. */
3759        @Override
3760        public void actionPerformed(ActionEvent e) {
3761            copy();
3762        }
3763    }
3764
3765    ///////////////////////////////////////////////////////////////////
3766    //// CutAction
3767
3768    /** Action to copy and delete the current selection. */
3769    protected class CutAction extends AbstractAction {
3770        /** Create a new action to copy and delete the current selection. */
3771        public CutAction() {
3772            super("Cut");
3773            putValue("tooltip",
3774                    "Cut the current selection onto the clipboard.");
3775            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
3776                    KeyEvent.VK_X,
3777                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
3778            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_T));
3779        }
3780
3781        /** Copy and delete the current selection. */
3782        @Override
3783        public void actionPerformed(ActionEvent e) {
3784            cut();
3785        }
3786    }
3787
3788    ///////////////////////////////////////////////////////////////////
3789    //// DeletionListener
3790
3791    /** An ActionListener for handling deletion events. */
3792    private class DeletionListener implements ActionListener {
3793        /** Delete any nodes or edges from the graph that are
3794         *  currently selected.  In addition, delete any edges
3795         *  that are connected to any deleted nodes.
3796         */
3797        @Override
3798        public void actionPerformed(ActionEvent e) {
3799            delete();
3800        }
3801    }
3802
3803    ///////////////////////////////////////////////////////////////////
3804    //// DocumentationMenuItemFactory
3805
3806    /**
3807     *  Create a menu item that will show documentation
3808     */
3809    private class DocumentationMenuItemFactory implements MenuItemFactory {
3810        /**
3811         * Add an item to the given context menu that bring up the
3812         * documentation for the given object
3813         */
3814        @Override
3815        public JMenuItem create(final JContextMenu menu,
3816                final NamedObj object) {
3817            Action action = new GetDocumentationAction() {
3818                @Override
3819                public void actionPerformed(ActionEvent e) {
3820                    Configuration configuration = getConfiguration();
3821                    setConfiguration(configuration);
3822                    super.actionPerformed(e);
3823                }
3824            };
3825
3826            action.putValue("tooltip", "Get Documentation.");
3827            action.putValue(diva.gui.GUIUtilities.MNEMONIC_KEY,
3828                    Integer.valueOf(KeyEvent.VK_D));
3829            return menu.add(action, (String) action.getValue(Action.NAME));
3830        }
3831    }
3832
3833    ///////////////////////////////////////////////////////////////////
3834    //// EditPreferencesAction
3835
3836    /** Action to edit the preferences.
3837     */
3838    protected class EditPreferencesAction extends AbstractAction {
3839        public EditPreferencesAction() {
3840            super("Edit Preferences");
3841            putValue("tooltip", "Change the Vergil preferences");
3842            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_E));
3843        }
3844
3845        @Override
3846        public void actionPerformed(ActionEvent e) {
3847            Configuration configuration = getConfiguration();
3848            PtolemyPreferences preferences = null;
3849
3850            try {
3851                preferences = (PtolemyPreferences) configuration.getAttribute(
3852                        PtolemyPreferences.PREFERENCES_WITHIN_CONFIGURATION,
3853                        PtolemyPreferences.class);
3854            } catch (IllegalActionException ex) {
3855                MessageHandler.error("Preferences attribute found, "
3856                        + "but not of the right class.", ex);
3857            }
3858
3859            if (preferences == null) {
3860                MessageHandler
3861                        .message("No preferences given in the configuration.");
3862            } else {
3863                // Open a modal dialog to edit the parameters.
3864                new EditParametersDialog(BasicGraphFrame.this, preferences,
3865                        "Edit Ptolemy Preferences");
3866
3867                // Make the current global variables conform with the
3868                // new values.
3869                try {
3870                    preferences.setAsDefault();
3871                } catch (IllegalActionException ex) {
3872                    MessageHandler.error("Invalid expression.", ex);
3873                    actionPerformed(e);
3874                }
3875
3876                // If any parameter has changed, all open vergil
3877                // windows need to be notified.
3878                Iterator<BasicGraphFrame> frames = _openGraphFrames.iterator();
3879
3880                while (frames.hasNext()) {
3881                    BasicGraphFrame frame = frames.next();
3882                    GraphModel graphModel = frame._getGraphController()
3883                            .getGraphModel();
3884                    graphModel.dispatchGraphEvent(
3885                            new GraphEvent(this, GraphEvent.STRUCTURE_CHANGED,
3886                                    graphModel.getRoot()));
3887
3888                    if (frame._graphPanner != null) {
3889                        frame._graphPanner.repaint();
3890                    }
3891                }
3892
3893                // Make the changes persistent.
3894                try {
3895                    preferences.save();
3896                } catch (IOException ex) {
3897                    try {
3898                        MessageHandler.warning("Failed to save preferences.",
3899                                ex);
3900                    } catch (CancelException e1) {
3901                        // Ignore cancel.
3902                    }
3903                }
3904            }
3905        }
3906    }
3907
3908    ///////////////////////////////////////////////////////////////////
3909    //// ElementInLinkType
3910    /**
3911     * An enumerate to specifies what kind of element the element (head or tail) is in a link.
3912     */
3913    private enum ElementInLinkType {
3914        PORT_IN_ACTOR, STANDALONE_PORT, RELATION
3915    }
3916
3917    ///////////////////////////////////////////////////////////////////
3918    //// ExecuteSystemAction
3919
3920    /** An action to open a run control window. */
3921    //    private class ExecuteSystemAction extends AbstractAction {
3922    //        /** Construct an action to execute the model. */
3923    //        public ExecuteSystemAction() {
3924    //            super("Go");
3925    //            putValue("tooltip", "Execute The Model");
3926    //            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
3927    //                    KeyEvent.VK_G, Toolkit.getDefaultToolkit()
3928    //                            .getMenuShortcutKeyMask()));
3929    //            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_G));
3930    //        }
3931    //
3932    //        /** Open a run control window. */
3933    //        public void actionPerformed(ActionEvent e) {
3934    //            try {
3935    //                PtolemyEffigy effigy = (PtolemyEffigy) getTableau()
3936    //                        .getContainer();
3937    //                new RunTableau(effigy, effigy.uniqueName("tableau"));
3938    //            } catch (Exception ex) {
3939    //                MessageHandler.error("Execution Failed", ex);
3940    //            }
3941    //        }
3942    //    }
3943
3944    ///////////////////////////////////////////////////////////////////
3945    //// ExportImageAction
3946
3947    /** Export an image of the model. */
3948    public class ExportImageAction extends AbstractAction {
3949        /** Create a new action to export an image.
3950         *  @param formatName The image format to be exported,
3951         *  currently, "GIF" and "PNG" are supported.
3952         */
3953        public ExportImageAction(String formatName) {
3954            super("Export " + formatName);
3955            _formatName = formatName.toLowerCase(Locale.getDefault());
3956            putValue("tooltip", "Export " + formatName + " image to a file.");
3957            // putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_G));
3958        }
3959
3960        ///////////////////////////////////////////////////////////////////
3961        ////                         public methods                   ////
3962
3963        /** Export an image.
3964         *
3965         *  <p> Under Mac OS X, use java.awt.FileDialog.
3966         *  Under other OS's, use javax.swing.JFileChooser. Under Mac OS
3967         *  X, see {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} for
3968         *  how to select between the two.</p>
3969         *
3970         *  @param e The event that triggered this action.
3971         */
3972        @Override
3973        public void actionPerformed(ActionEvent e) {
3974            JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix();
3975            Color background = null;
3976            PtFileChooser ptFileChooser = null;
3977            try {
3978                background = jFileChooserBugFix.saveBackground();
3979
3980                ptFileChooser = new PtFileChooser(BasicGraphFrame.this,
3981                        "Specify a " + _formatName + " file to be written.",
3982                        JFileChooser.SAVE_DIALOG);
3983
3984                ptFileChooser.setSelectedFile(
3985                        new File(getModel().getName() + "." + _formatName));
3986                LinkedList extensions = new LinkedList();
3987                extensions.add(_formatName);
3988                ptFileChooser.addChoosableFileFilter(
3989                        new ExtensionFilenameFilter(extensions));
3990                ptFileChooser.setCurrentDirectory(_directory);
3991
3992                int returnVal = ptFileChooser.showDialog(BasicGraphFrame.this,
3993                        "Export "
3994                                + _formatName.toUpperCase(Locale.getDefault()));
3995
3996                if (returnVal == JFileChooser.APPROVE_OPTION) {
3997                    _directory = ptFileChooser.getCurrentDirectory();
3998                    File imageFile = ptFileChooser.getSelectedFile()
3999                            .getCanonicalFile();
4000
4001                    if (imageFile.getName().indexOf(".") == -1) {
4002                        // If the user has not given the file an extension, add it
4003                        imageFile = new File(imageFile.getAbsolutePath() + "."
4004                                + _formatName);
4005                    }
4006                    // The Mac OS X FileDialog will ask if we want to save before this point.
4007                    if (imageFile.exists() && !PtGUIUtilities.useFileDialog()) {
4008                        if (!MessageHandler.yesNoQuestion(
4009                                "Overwrite \"" + imageFile.getName() + "\"?")) {
4010                            return;
4011                        }
4012                    }
4013                    OutputStream out = null;
4014                    try {
4015                        out = new FileOutputStream(imageFile);
4016                        getJGraph().exportImage(out, _formatName);
4017                    } finally {
4018                        if (out != null) {
4019                            out.close();
4020                        }
4021                    }
4022
4023                    // Open the image file.
4024                    if (MessageHandler.yesNoQuestion(
4025                            "Open \"" + imageFile.getCanonicalPath()
4026                                    + "\" in a browser?")) {
4027                        Configuration configuration = getConfiguration();
4028                        try {
4029                            URL imageURL = new URL(
4030                                    imageFile.toURI().toURL().toString()
4031                                            + "#in_browser");
4032                            configuration.openModel(imageURL, imageURL,
4033                                    imageURL.toExternalForm(),
4034                                    BrowserEffigy.staticFactory);
4035                        } catch (Throwable throwable) {
4036                            MessageHandler.error("Failed to open \""
4037                                    + imageFile.getName() + "\".", throwable);
4038                        }
4039                    }
4040                }
4041            } catch (Exception ex) {
4042                MessageHandler.error("Export to "
4043                        + _formatName.toUpperCase(Locale.getDefault())
4044                        + " failed", ex);
4045            } finally {
4046                jFileChooserBugFix.restoreBackground(background);
4047            }
4048        }
4049
4050        private String _formatName;
4051    }
4052
4053    ///////////////////////////////////////////////////////////////////
4054    //// ExportMapAction
4055
4056    /** Accept only folders in a file browser. */
4057    static public class FolderFileFilter extends FileFilter {
4058        /** Accept only folders.
4059         *  @param fileOrDirectory The file or directory to be checked.
4060         *  @return true if the file is a directory.
4061         */
4062        @Override
4063        public boolean accept(File fileOrDirectory) {
4064            if (fileOrDirectory.isDirectory()) {
4065                return true;
4066            }
4067            return false;
4068        }
4069
4070        /**  The description of this filter. */
4071        @Override
4072        public String getDescription() {
4073            return "Choose a Folder";
4074        }
4075    }
4076
4077    ///////////////////////////////////////////////////////////////////
4078    //// FindAction
4079
4080    /** Action to search for text in a model. */
4081    protected class FindAction extends AbstractAction {
4082        /** Create a new action to search for text. */
4083        public FindAction() {
4084            super("Find");
4085            putValue("tooltip", "Find occurrences of specified text.");
4086            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
4087                    KeyEvent.VK_F,
4088                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
4089            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_F));
4090        }
4091
4092        /** Open a dialog to find the specified text. */
4093        @Override
4094        public void actionPerformed(ActionEvent e) {
4095            DialogTableau dialogTableau = DialogTableau.createDialog(
4096                    BasicGraphFrame.this, getConfiguration(), getEffigy(),
4097                    SearchResultsDialog.class, (Entity) getModel());
4098
4099            if (dialogTableau != null) {
4100                dialogTableau.show();
4101            }
4102        }
4103    }
4104
4105    ///////////////////////////////////////////////////////////////////
4106    //// FindInLibraryAction
4107
4108    /** An ActionListener for handling deletion events. */
4109    private class FindInLibraryAction implements ActionListener {
4110        /** Delete any nodes or edges from the graph that are
4111         *  currently selected.  In addition, delete any edges
4112         *  that are connected to any deleted nodes.
4113         */
4114        @Override
4115        public void actionPerformed(ActionEvent e) {
4116            _library.clearSelection();
4117            String text = _findInLibraryEntryBox.getText().trim()
4118                    .toLowerCase(Locale.getDefault());
4119            if (text.equals("")) {
4120                // Nothing to search for. Ignore.
4121                _previousText = null;
4122                return;
4123            }
4124            NamedObj root = (NamedObj) _libraryModel.getRoot();
4125            if (!text.equals(_previousText)) {
4126                // Restart the search from the beginning.
4127                if (_stack == null) {
4128                    _stack = new Stack<NamedObj>();
4129                    _indexes = new Stack<Integer>();
4130                } else {
4131                    _stack.clear();
4132                    _indexes.clear();
4133                }
4134                _stack.push(root);
4135                _indexes.push(Integer.valueOf(1));
4136            }
4137            _previousText = text;
4138            try {
4139                // Indicate that something is happening with the cursor.
4140                // setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
4141                _findInLibraryEntryBox.setCursor(
4142                        Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
4143                _library.setCursor(
4144                        Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
4145
4146                // Put a message in the progress bar at the bottom of the window.
4147                // FIXME: Sadly, this doesn't work.
4148                report("Opening Libraries ...");
4149
4150                // Traverse the model until we get a match.
4151                // _stack will be empty only when no further match is found.
4152                boolean foundOne = false;
4153                int index = 0;
4154                while (!_stack.isEmpty()) {
4155                    NamedObj parent = _stack.peek();
4156                    if (_findInSublibrary(text, parent, index)) {
4157                        // Found one. Stop the search.
4158                        foundOne = true;
4159                        break;
4160                    } else {
4161                        // Pop up one level and continue the search.
4162                        _stack.pop();
4163                        index = _indexes.pop();
4164                    }
4165                }
4166                if (!foundOne) {
4167                    // Reached the end of the library.
4168                    try {
4169                        if (MessageHandler.yesNoCancelQuestion(
4170                                "Reached the end of the library. Start search again from the top?")) {
4171                            // Restart the search.
4172                            _stack.clear();
4173                            _indexes.clear();
4174                            _stack.push(root);
4175                            _indexes.push(Integer.valueOf(0));
4176
4177                            actionPerformed(e);
4178                        }
4179                    } catch (CancelException e1) {
4180                        // Canceled by user.
4181                        return;
4182                    }
4183                }
4184            } finally {
4185                // Restore the cursor.
4186                // setCursor(Cursor.getDefaultCursor());
4187                _findInLibraryEntryBox.setCursor(Cursor.getDefaultCursor());
4188                _library.setCursor(Cursor.getDefaultCursor());
4189
4190                // Clear the message in the progress bar at the bottom of the window.
4191                report("");
4192            }
4193        }
4194
4195        /** Search the specified library for a name or class name that
4196         *  contains the specified text.
4197         *  @param text The text to search for.
4198         *  @param library The library to search.
4199         *  @param index The index at which to start the search.
4200         *  @return True if a match is found.
4201         */
4202        private boolean _findInSublibrary(String text, NamedObj library,
4203                int index) {
4204            int count = _libraryModel.getChildCount(library);
4205            for (int i = index; i < count; i++) {
4206                NamedObj candidate = (NamedObj) _libraryModel.getChild(library,
4207                        i);
4208                String name = candidate.getName();
4209                if (name.toLowerCase(Locale.getDefault()).contains(text)) {
4210                    // Found a match to the name.
4211                    _stack.push(candidate);
4212                    Object[] path = _stack.toArray();
4213                    TreePath pathToHit = new TreePath(path);
4214                    _library.makeVisible(pathToHit);
4215                    _library.scrollPathToVisible(pathToHit);
4216                    _library.addSelectionPath(pathToHit);
4217                    // Start the next search at the next item.
4218                    _indexes.push(Integer.valueOf(i + 1));
4219                    return true;
4220                }
4221                // Not a match. See whether any of its children are a match.
4222                int childCount = 0;
4223                ErrorHandler momlErrorHandler = MoMLParser.getErrorHandler();
4224                MoMLParser.setErrorHandler(new SimpleErrorHandler());
4225                MessageHandler messageHandler = MessageHandler
4226                        .getMessageHandler();
4227                MessageHandler.setMessageHandler(new SimpleMessageHandler());
4228                try {
4229                    childCount = _libraryModel.getChildCount(candidate);
4230                } catch (Throwable throwable) {
4231                    report("Skipping opening " + candidate.getName() + ": "
4232                            + throwable);
4233                } finally {
4234                    MoMLParser.setErrorHandler(momlErrorHandler);
4235                    MessageHandler.setMessageHandler(messageHandler);
4236                }
4237                if (!_libraryModel.isLeaf(candidate) && childCount > 0) {
4238                    _stack.push(candidate);
4239                    _indexes.push(Integer.valueOf(i + 1));
4240                    if (_findInSublibrary(text, candidate, 0)) {
4241                        return true;
4242                    } else {
4243                        _stack.pop();
4244                        _indexes.pop();
4245                    }
4246                }
4247            }
4248            return false;
4249        }
4250
4251        private String _previousText;
4252        private Stack<NamedObj> _stack;
4253        private Stack<Integer> _indexes;
4254    }
4255
4256    ///////////////////////////////////////////////////////////////////
4257    //// LinkElementProperties
4258    /**
4259     * A class that keeps stores basic properties of element (head, tail) in a link
4260     */
4261    static private class LinkElementProperties {
4262        /**
4263         * Create a LinkElementProperties from the element (head or tail), a port if one is available and the ElementInLinkType
4264         */
4265        LinkElementProperties(Object element, IOPort port,
4266                ElementInLinkType type) {
4267            this.element = element;
4268            this.port = port;
4269            this.type = type;
4270        }
4271
4272        /**
4273         * Extract the properties from an element (head or tail) in a link a return these as an ElementInLinkType
4274         */
4275        static LinkElementProperties extractLinkProperties(Object element) {
4276            IOPort elementPort = null;
4277            ElementInLinkType elementType = ElementInLinkType.PORT_IN_ACTOR;
4278            if (element instanceof IOPort) {
4279                //This is a port of an actor
4280                elementPort = (IOPort) element;
4281                elementType = ElementInLinkType.PORT_IN_ACTOR;
4282            } else if (element instanceof Location) {
4283                //Either a port (not one of an actor) or a relation
4284                NamedObj elementContainer = ((Location) element).getContainer();
4285                if (elementContainer instanceof IOPort) {
4286                    //This is a port
4287                    elementPort = (IOPort) elementContainer;
4288                    elementType = ElementInLinkType.STANDALONE_PORT;
4289                } else {
4290                    //This is a relation
4291                    assert elementContainer instanceof IORelation;
4292                    elementType = ElementInLinkType.RELATION;
4293                }
4294            }
4295            return new LinkElementProperties(element, elementPort, elementType);
4296        }
4297
4298        public final Object element;
4299        public final IOPort port;
4300        public final ElementInLinkType type;
4301    }
4302
4303    ///////////////////////////////////////////////////////////////////
4304    //// HierarchyTreeCellRenderer
4305
4306    /** Render a cell in the model hierarchy tree.  The model being
4307     *  displayed is highlighted.
4308     */
4309    class HierarchyTreeCellRenderer extends PtolemyTreeCellRenderer {
4310
4311        /** Create a new rendition for the given object.
4312         *  If the object is the same as the currently displayed Ptolemy
4313         *  model, then make it bold.
4314         */
4315        @Override
4316        public Component getTreeCellRendererComponent(JTree tree, Object value,
4317                boolean sel, boolean expanded, boolean leaf, int row,
4318                boolean hasFocus) {
4319
4320            DefaultTreeCellRenderer component = (DefaultTreeCellRenderer) super.getTreeCellRendererComponent(
4321                    tree, value, selected, expanded, leaf, row, hasFocus);
4322            NamedObj model = getModel();
4323            if (model != null && component != null && model.equals(value)) {
4324                component.setText(
4325                        "<html><b>" + component.getText() + "</b></html>");
4326            }
4327            return this;
4328        }
4329    }
4330
4331    ///////////////////////////////////////////////////////////////////
4332    //// HierarchyTreeSelectionListener
4333
4334    /** The user selected a node in the Hierarchy tree browser */
4335    // Replaced by mouse listener
4336    /*
4337    private class HierarchyTreeSelectionListener implements
4338            TreeSelectionListener {
4339        // The value of the selection in the model hierarchy tree
4340        // browser changed.
4341        @Override
4342        public void valueChanged(TreeSelectionEvent event) {
4343            // Returns the last path element of the selection.
4344            // This method is useful only when the selection model allows a single selection.
4345            Object lastSelectedPathComponent = _treeView
4346                    .getLastSelectedPathComponent();
4347            if (lastSelectedPathComponent instanceof NamedObj) {
4348                try {
4349                    getConfiguration().openInstance(
4350                            (NamedObj) lastSelectedPathComponent);
4351                } catch (Throwable throwable) {
4352                    MessageHandler.error("Could not open "
4353                            + lastSelectedPathComponent, throwable);
4354                }
4355            }
4356        }
4357    }
4358     */
4359
4360    ///////////////////////////////////////////////////////////////////
4361    //// HierarchyTreeMouseAdapter
4362
4363    /** Listen for clicks of the mouse on the tree.
4364     */
4365    private class HierarchyTreeMouseAdapter extends MouseAdapter {
4366        @Override
4367        public void mousePressed(MouseEvent e) {
4368            if (e.getClickCount() == 2) {
4369                // Returns the last path element of the selection.
4370                // This method is useful only when the selection model allows a single selection.
4371                Object lastSelectedPathComponent = _treeView
4372                        .getLastSelectedPathComponent();
4373                if (lastSelectedPathComponent instanceof NamedObj) {
4374                    try {
4375                        getConfiguration().openInstance(
4376                                (NamedObj) lastSelectedPathComponent);
4377                    } catch (Throwable throwable) {
4378                        MessageHandler.error(
4379                                "Could not open " + lastSelectedPathComponent,
4380                                throwable);
4381                    }
4382                }
4383            }
4384        }
4385    }
4386
4387    ///////////////////////////////////////////////////////////////////
4388    //// MoveToBackAction
4389    /** Action to move the current selection to the back (which corresponds
4390     *  to first in the ordered list).
4391     */
4392    protected class MoveToBackAction extends AbstractAction {
4393        public MoveToBackAction() {
4394            // Note that we also have "Send to Back" in
4395            // vergil/kernel/AttributeController.java
4396            super("Send to Back");
4397            putValue("tooltip", "Send to back of like objects");
4398            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
4399                    KeyEvent.VK_B,
4400                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
4401            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_B));
4402        }
4403
4404        @Override
4405        public void actionPerformed(ActionEvent e) {
4406            final NamedObj container = (NamedObj) _getGraphModel().getRoot();
4407
4408            // Get the selection objects.
4409            // NOTE: The order in the model must be respected.
4410            HashSet<NamedObj> namedObjSet = _getSelectionSet();
4411            final List<NamedObj> elements = container
4412                    .sortContainedObjects(namedObjSet);
4413
4414            // Return if any is a derived object.
4415            if (_checkForImplied(elements)) {
4416                return;
4417            }
4418
4419            // Issue a change request, since this requires write access.
4420            ChangeRequest request = new ChangeRequest(container,
4421                    "Send to back") {
4422                @Override
4423                protected void _execute() throws IllegalActionException {
4424                    MoveAction.move(elements, MoveAction.TO_FIRST, container);
4425                }
4426            };
4427
4428            container.requestChange(request);
4429        }
4430    }
4431
4432    ///////////////////////////////////////////////////////////////////
4433    //// MoveToFrontAction
4434
4435    /** Action to move the current selection to the back (which corresponds
4436     *  to first in the ordered list).
4437     */
4438    protected class MoveToFrontAction extends AbstractAction {
4439        public MoveToFrontAction() {
4440            // Note that we also have "Bring to Front" in
4441            // vergil/kernel/AttributeController.java
4442            super("Bring to Front");
4443            putValue("tooltip", "Bring to front of like objects");
4444            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
4445                    KeyEvent.VK_F,
4446                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
4447            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_F));
4448        }
4449
4450        @Override
4451        public void actionPerformed(ActionEvent e) {
4452            final NamedObj container = (NamedObj) _getGraphModel().getRoot();
4453
4454            // Get the selection objects.
4455            // NOTE: The order in the model must be respected.
4456            HashSet<NamedObj> namedObjSet = _getSelectionSet();
4457            final List<NamedObj> elements = container
4458                    .sortContainedObjects(namedObjSet);
4459
4460            // Return if any is a derived object.
4461            if (_checkForImplied(elements)) {
4462                return;
4463            }
4464
4465            // Issue a change request, since this requires write access.
4466            ChangeRequest request = new ChangeRequest(container,
4467                    "Bring to front") {
4468                @Override
4469                protected void _execute() throws IllegalActionException {
4470                    MoveAction.move(elements, MoveAction.TO_LAST, container);
4471                }
4472            };
4473
4474            container.requestChange(request);
4475        }
4476    }
4477
4478    ///////////////////////////////////////////////////////////////////
4479    //// PasteAction
4480
4481    /** Paste the current contents of the clipboard into the current model. */
4482    protected class PasteAction extends AbstractAction {
4483        /** Create a new action to paste the current contents of the
4484         *  clipboard into the current model.
4485         */
4486        public PasteAction() {
4487            super("Paste");
4488            putValue("tooltip", "Paste the contents of the clipboard.");
4489            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
4490                    KeyEvent.VK_V,
4491                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
4492            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_P));
4493        }
4494
4495        /** Paste the current contents of the clipboard into
4496         *  the current model.
4497         */
4498        @Override
4499        public void actionPerformed(ActionEvent e) {
4500            paste();
4501        }
4502    }
4503
4504    ///////////////////////////////////////////////////////////////////
4505    //// OpenContainerAction
4506
4507    /** An action to open the container of this entity. */
4508    private class OpenContainerAction extends AbstractAction {
4509        /** Construct an open container action.  This action opens
4510         *  the container of this class.  If this entity is the toplevel
4511         *  then the icon is disabled.
4512         *  @param description A string that describes the action.  Spaces are
4513         *  permitted, each word is usually capitalized.
4514         */
4515        public OpenContainerAction(String description) {
4516            super(description);
4517            // Load the image by using the absolute path to the gif.
4518            // Using a relative location should work, but it does not.
4519            // Use the resource locator of the class.
4520            // For more information, see
4521            // jdk1.3/docs/guide/resources/resources.html
4522            GUIUtilities.addIcons(this,
4523                    new String[][] {
4524                            { "/ptolemy/vergil/basic/img/up.gif",
4525                                    GUIUtilities.LARGE_ICON },
4526                            { "/ptolemy/vergil/basic/img/up_o.gif",
4527                                    GUIUtilities.ROLLOVER_ICON },
4528                            { "/ptolemy/vergil/basic/img/up_ov.gif",
4529                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4530                            { "/ptolemy/vergil/basic/img/up_on.gif",
4531                                    GUIUtilities.SELECTED_ICON } });
4532
4533            putValue("tooltip", description);
4534
4535        }
4536
4537        /** Open the parent container, if any.
4538         *  @param event The action event, ignored by this method.
4539         */
4540        @Override
4541        public void actionPerformed(ActionEvent event) {
4542            openContainer();
4543        }
4544    }
4545
4546    ///////////////////////////////////////////////////////////////////
4547    //// OpenLibraryMenuItemFactory
4548
4549    /**
4550     *  Create a menu item that will open a library in editable form.
4551     */
4552    private class OpenLibraryMenuItemFactory implements MenuItemFactory {
4553        /**
4554         * Add an item to the given context menu that will open the
4555         * given object as an editable model.
4556         */
4557        @Override
4558        public JMenuItem create(final JContextMenu menu,
4559                final NamedObj object) {
4560            Action action = new AbstractAction("Open for Editing") {
4561                @Override
4562                public void actionPerformed(ActionEvent e) {
4563                    try {
4564                        getConfiguration().openModel(object);
4565                    } catch (KernelException ex) {
4566                        MessageHandler.error("Open failed.", ex);
4567                    }
4568                }
4569            };
4570
4571            action.putValue("tooltip", "Open library for editing.");
4572            action.putValue(diva.gui.GUIUtilities.MNEMONIC_KEY,
4573                    Integer.valueOf(KeyEvent.VK_O));
4574            return menu.add(action, (String) action.getValue(Action.NAME));
4575        }
4576    }
4577
4578    ///////////////////////////////////////////////////////////////////
4579    //// PrintAction
4580
4581    /**
4582     *  Print the current model.
4583     */
4584    private class PrintAction extends AbstractAction {
4585        /** Construct a print action.
4586         *  @param description A string that describes the action.  Spaces are
4587         *  permitted, each word is usually capitalized.
4588         */
4589        public PrintAction(String description) {
4590            super(description);
4591            putValue("tooltip", description);
4592
4593            // Load the image by using the absolute path to the gif.
4594            // Using a relative location should work, but it does not.
4595            // Use the resource locator of the class.
4596            // For more information, see
4597            // jdk1.3/docs/guide/resources/resources.html
4598            GUIUtilities.addIcons(this,
4599                    new String[][] {
4600                            { "/ptolemy/vergil/basic/img/print.gif",
4601                                    GUIUtilities.LARGE_ICON },
4602                            { "/ptolemy/vergil/basic/img/print_o.gif",
4603                                    GUIUtilities.ROLLOVER_ICON },
4604                            { "/ptolemy/vergil/basic/img/print_ov.gif",
4605                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4606                            { "/ptolemy/vergil/basic/img/print_on.gif",
4607                                    GUIUtilities.SELECTED_ICON } });
4608        }
4609
4610        /** Print the current layout.
4611         *  @param event The action event, ignored by this method.
4612         */
4613        @Override
4614        public void actionPerformed(ActionEvent event) {
4615            _print();
4616        }
4617    }
4618
4619    ///////////////////////////////////////////////////////////////////
4620    //// RedoAction
4621
4622    /**
4623     *  Redo the last undone MoML change on the current current model.
4624     */
4625    private class RedoAction extends AbstractAction {
4626        /**
4627         *  Create a new action to paste the current contents of the clipboard
4628         *  into the current model.
4629         */
4630        public RedoAction() {
4631            super("Redo");
4632            putValue("tooltip", "Redo the last change undone.");
4633            putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY,
4634                    KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit
4635                            .getDefaultToolkit().getMenuShortcutKeyMask()));
4636            putValue(diva.gui.GUIUtilities.MNEMONIC_KEY,
4637                    Integer.valueOf(KeyEvent.VK_R));
4638        }
4639
4640        /**
4641         *  Redo the last undone MoML change on the current current model.
4642         *
4643         * @param e The event for the action.
4644         */
4645        @Override
4646        public void actionPerformed(ActionEvent e) {
4647            redo();
4648        }
4649    }
4650
4651    ///////////////////////////////////////////////////////////////////
4652    //// SaveAction
4653
4654    /**
4655     *  Save the current model.
4656     */
4657    private class SaveAction extends AbstractAction {
4658        /** Construct a save action.
4659         *  @param description A string that describes the action.  Spaces are
4660         *  permitted, each word is usually capitalized.
4661         */
4662        public SaveAction(String description) {
4663            super(description);
4664            putValue("tooltip", description);
4665            // Load the image by using the absolute path to the gif.
4666            // Using a relative location should work, but it does not.
4667            // Use the resource locator of the class.
4668            // For more information, see
4669            // jdk1.3/docs/guide/resources/resources.html
4670            GUIUtilities.addIcons(this,
4671                    new String[][] {
4672                            { "/ptolemy/vergil/basic/img/save.gif",
4673                                    GUIUtilities.LARGE_ICON },
4674                            { "/ptolemy/vergil/basic/img/save_o.gif",
4675                                    GUIUtilities.ROLLOVER_ICON },
4676                            { "/ptolemy/vergil/basic/img/save_ov.gif",
4677                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4678                            { "/ptolemy/vergil/basic/img/save_on.gif",
4679                                    GUIUtilities.SELECTED_ICON } });
4680            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_Z));
4681        }
4682
4683        /** Save the current layout.
4684         *  @param e The action event, ignored by this method.
4685         */
4686        @Override
4687        public void actionPerformed(ActionEvent e) {
4688            _save();
4689        }
4690    }
4691
4692    ///////////////////////////////////////////////////////////////////
4693    //// UndoAction
4694
4695    /**
4696     *  Undo the last undoable MoML change on the current current model.
4697     */
4698    private class UndoAction extends AbstractAction {
4699        /**
4700         *  Create a new action to paste the current contents of the clipboard
4701         *  into the current model.
4702         */
4703        public UndoAction() {
4704            super("Undo");
4705            putValue("tooltip", "Undo the last change.");
4706            putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY,
4707                    KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit
4708                            .getDefaultToolkit().getMenuShortcutKeyMask()));
4709            putValue(diva.gui.GUIUtilities.MNEMONIC_KEY,
4710                    Integer.valueOf(KeyEvent.VK_U));
4711        }
4712
4713        /**
4714         *  Undo the last undoable MoML change on the current current model.
4715         *
4716         * @param e The event for the action.
4717         */
4718        @Override
4719        public void actionPerformed(ActionEvent e) {
4720            undo();
4721        }
4722    }
4723
4724    ///////////////////////////////////////////////////////////////////
4725    //// ZoomInAction
4726    /** An action to zoom in. */
4727    private class ZoomInAction extends AbstractAction {
4728        /** Construct a zoom in action.
4729         *  @param description A string that describes the action.  Spaces are
4730         *  permitted, each word is usually capitalized.
4731         */
4732        public ZoomInAction(String description) {
4733            super(description);
4734
4735            // Load the image by using the absolute path to the gif.
4736            // Using a relative location should work, but it does not.
4737            // Use the resource locator of the class.
4738            // For more information, see
4739            // jdk1.3/docs/guide/resources/resources.html
4740            GUIUtilities.addIcons(this,
4741                    new String[][] {
4742                            { "/ptolemy/vergil/basic/img/zoomin.gif",
4743                                    GUIUtilities.LARGE_ICON },
4744                            { "/ptolemy/vergil/basic/img/zoomin_o.gif",
4745                                    GUIUtilities.ROLLOVER_ICON },
4746                            { "/ptolemy/vergil/basic/img/zoomin_ov.gif",
4747                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4748                            { "/ptolemy/vergil/basic/img/zoomin_on.gif",
4749                                    GUIUtilities.SELECTED_ICON } });
4750
4751            putValue("tooltip", description + " (Ctrl+Shift+=)");
4752
4753            // NOTE: The following assumes that the + key is the same
4754            // as the = key.  Unfortunately, the VK_PLUS key event doesn't
4755            // work, so we have to do it this way.
4756            putValue(GUIUtilities.ACCELERATOR_KEY,
4757                    KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS,
4758                            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
4759                                    | Event.SHIFT_MASK));
4760            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_Z));
4761        }
4762
4763        /** Zoom in by a factor of 1.25.
4764         *  @param e The action event, ignored by this method.
4765         */
4766        @Override
4767        public void actionPerformed(ActionEvent e) {
4768            zoom(1.25);
4769        }
4770    }
4771
4772    ///////////////////////////////////////////////////////////////////
4773    //// ZoomResetAction
4774    /** An action to reset zoom. */
4775    private class ZoomResetAction extends AbstractAction {
4776        /** Construct a zoom reset action.
4777         *  @param description A string that describes the action.  Spaces are
4778         *  permitted, each word is usually capitalized.
4779         */
4780        public ZoomResetAction(String description) {
4781            super(description);
4782
4783            // Load the image by using the absolute path to the gif.
4784            // Using a relative location should work, but it does not.
4785            // Use the resource locator of the class.
4786            // For more information, see
4787            // jdk1.3/docs/guide/resources/resources.html
4788            GUIUtilities.addIcons(this,
4789                    new String[][] {
4790                            { "/ptolemy/vergil/basic/img/zoomreset.gif",
4791                                    GUIUtilities.LARGE_ICON },
4792                            { "/ptolemy/vergil/basic/img/zoomreset_o.gif",
4793                                    GUIUtilities.ROLLOVER_ICON },
4794                            { "/ptolemy/vergil/basic/img/zoomreset_ov.gif",
4795                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4796                            { "/ptolemy/vergil/basic/img/zoomreset_on.gif",
4797                                    GUIUtilities.SELECTED_ICON } });
4798
4799            // Control-m is usually carriage return.  In this case, we use
4800            // it to mean "return the zoom to the original state".
4801            putValue("tooltip", description + " (Ctrl+M)");
4802            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
4803                    KeyEvent.VK_M,
4804                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
4805            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_M));
4806        }
4807
4808        /** Reset the zoom.
4809         *  @param e The action event, ignored by this method.
4810         */
4811        @Override
4812        public void actionPerformed(ActionEvent e) {
4813            zoomReset();
4814        }
4815    }
4816
4817    ///////////////////////////////////////////////////////////////////
4818    //// ZoomFitAction
4819    /** An action to zoom fit.*/
4820    private class ZoomFitAction extends AbstractAction {
4821        /** Construct a zoom fit action.
4822         *  @param description A string that describes the action.  Spaces are
4823         *  permitted, each word is usually capitalized.
4824         */
4825        public ZoomFitAction(String description) {
4826            super(description);
4827
4828            // Load the image by using the absolute path to the gif.
4829            // Using a relative location should work, but it does not.
4830            // Use the resource locator of the class.
4831            // For more information, see
4832            // jdk1.3/docs/guide/resources/resources.html
4833            GUIUtilities.addIcons(this,
4834                    new String[][] {
4835                            { "/ptolemy/vergil/basic/img/zoomfit.gif",
4836                                    GUIUtilities.LARGE_ICON },
4837                            { "/ptolemy/vergil/basic/img/zoomfit_o.gif",
4838                                    GUIUtilities.ROLLOVER_ICON },
4839                            { "/ptolemy/vergil/basic/img/zoomfit_ov.gif",
4840                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4841                            { "/ptolemy/vergil/basic/img/zoomfit_on.gif",
4842                                    GUIUtilities.SELECTED_ICON } });
4843
4844            putValue("tooltip", description + " (Ctrl+Shift+-)");
4845            putValue(GUIUtilities.ACCELERATOR_KEY,
4846                    KeyStroke.getKeyStroke(KeyEvent.VK_MINUS,
4847                            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
4848                                    | Event.SHIFT_MASK));
4849            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_F));
4850        }
4851
4852        /** Zoom so that the entire graph is visible.
4853         *  @param e The action event, ignored by this method.
4854         */
4855        @Override
4856        public void actionPerformed(ActionEvent e) {
4857            zoomFit();
4858        }
4859    }
4860
4861    ///////////////////////////////////////////////////////////////////
4862    //// ZoomOutAction
4863    /** An action to zoom out. */
4864    private class ZoomOutAction extends AbstractAction {
4865        /** Construct a zoom fit action.
4866         *  @param description A string that describes the action.  Spaces are
4867         *  permitted, each word is usually capitalized.
4868         */
4869        public ZoomOutAction(String description) {
4870            super(description);
4871
4872            // Load the image by using the absolute path to the gif.
4873            // Using a relative location should work, but it does not.
4874            // Use the resource locator of the class.
4875            // For more information, see
4876            // jdk1.3/docs/guide/resources/resources.html
4877            GUIUtilities.addIcons(this,
4878                    new String[][] {
4879                            { "/ptolemy/vergil/basic/img/zoomout.gif",
4880                                    GUIUtilities.LARGE_ICON },
4881                            { "/ptolemy/vergil/basic/img/zoomout_o.gif",
4882                                    GUIUtilities.ROLLOVER_ICON },
4883                            { "/ptolemy/vergil/basic/img/zoomout_ov.gif",
4884                                    GUIUtilities.ROLLOVER_SELECTED_ICON },
4885                            { "/ptolemy/vergil/basic/img/zoomout_on.gif",
4886                                    GUIUtilities.SELECTED_ICON } });
4887
4888            putValue("tooltip", description + " (Ctrl+-)");
4889            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
4890                    KeyEvent.VK_MINUS,
4891                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
4892            putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_U));
4893        }
4894
4895        /** Zoom out by a factor of 1/1.25.
4896         *  @param e The action event, ignored by this method.
4897         */
4898        @Override
4899        public void actionPerformed(ActionEvent e) {
4900            zoom(1.0 / 1.25);
4901        }
4902    }
4903
4904    ///////////////////////////////////////////////////////////////////
4905    //// LayoutConfigDialogAction
4906
4907    /** Action to display a dialog for setting layout options. */
4908    private class LayoutConfigDialogAction extends AbstractAction {
4909        /** Create a new action to show the layout configuration dialog. */
4910        public LayoutConfigDialogAction() {
4911            super("Configure Layout...");
4912            putValue("tooltip",
4913                    "Set parameters for controlling the layout algorithm");
4914        }
4915
4916        /** Show the layout configuration dialog. */
4917        @Override
4918        public void actionPerformed(ActionEvent e) {
4919            NamedObj model = getModel();
4920
4921            Attribute attribute = model.getAttribute("_layoutConfiguration");
4922            if (attribute == null) {
4923
4924                String layoutConfiguration = "ptolemy.vergil.basic.layout.ActorLayoutConfiguration";
4925                if (_getGraphModel() instanceof FSMGraphModel) {
4926                    layoutConfiguration = "ptolemy.vergil.basic.layout.ModalLayoutConfiguration";
4927                }
4928
4929                String momlChange = "<property name=\"_layoutConfiguration\" class=\""
4930                        + layoutConfiguration + "\"/>";
4931                model.requestChange(
4932                        new MoMLChangeRequest(this, model, momlChange, false));
4933                attribute = model.getAttribute("_layoutConfiguration");
4934                if (attribute == null) {
4935                    MessageHandler.error(
4936                            "Could not create the layout configuration attribute.");
4937                    return;
4938                }
4939            }
4940            new EditParametersDialog(BasicGraphFrame.this, attribute,
4941                    "Configure Layout Parameters");
4942        }
4943    }
4944
4945}