001/* The graph controller for vergil.
002
003 Copyright (c) 1998-2016 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
027 */
028package ptolemy.vergil.actor;
029
030import java.awt.Toolkit;
031import java.awt.event.ActionEvent;
032import java.awt.event.InputEvent;
033import java.awt.event.KeyEvent;
034import java.awt.geom.AffineTransform;
035import java.awt.geom.NoninvertibleTransformException;
036import java.awt.geom.Point2D;
037import java.awt.geom.Rectangle2D;
038import java.lang.reflect.Constructor;
039import java.util.Iterator;
040
041import javax.swing.Action;
042import javax.swing.JMenu;
043import javax.swing.JMenuItem;
044import javax.swing.JToolBar;
045
046import diva.canvas.CanvasComponent;
047import diva.canvas.CanvasUtilities;
048import diva.canvas.Figure;
049import diva.canvas.FigureLayer;
050import diva.canvas.Site;
051import diva.canvas.connector.AutonomousSite;
052import diva.canvas.connector.Connector;
053import diva.canvas.connector.ConnectorManipulator;
054import diva.canvas.event.LayerEvent;
055import diva.canvas.event.MouseFilter;
056import diva.canvas.interactor.AbstractInteractor;
057import diva.canvas.interactor.ActionInteractor;
058import diva.canvas.interactor.CompositeInteractor;
059import diva.canvas.interactor.GrabHandle;
060import diva.canvas.interactor.Interactor;
061import diva.graph.GraphModel;
062import diva.graph.GraphPane;
063import diva.graph.JGraph;
064import diva.graph.NodeRenderer;
065import diva.graph.layout.GlobalLayout;
066import diva.graph.layout.IncrLayoutAdapter;
067import diva.graph.layout.IncrementalLayoutListener;
068import diva.gui.GUIUtilities;
069import diva.gui.toolbox.FigureIcon;
070import diva.gui.toolbox.JContextMenu;
071import diva.util.Filter;
072import diva.util.UserObjectContainer;
073import ptolemy.actor.PublisherPort;
074import ptolemy.actor.SubscriberPort;
075import ptolemy.actor.gui.Configuration;
076import ptolemy.data.expr.Parameter;
077import ptolemy.data.expr.StringParameter;
078import ptolemy.kernel.CompositeEntity;
079import ptolemy.kernel.Entity;
080import ptolemy.kernel.util.InternalErrorException;
081import ptolemy.kernel.util.Locatable;
082import ptolemy.kernel.util.NamedObj;
083import ptolemy.kernel.util.StringAttribute;
084import ptolemy.moml.MoMLChangeRequest;
085import ptolemy.util.MessageHandler;
086import ptolemy.vergil.basic.BasicGraphFrame;
087import ptolemy.vergil.basic.NamedObjController;
088import ptolemy.vergil.kernel.AttributeController;
089import ptolemy.vergil.kernel.Link;
090import ptolemy.vergil.kernel.ListenToAction;
091import ptolemy.vergil.kernel.PortDialogAction;
092import ptolemy.vergil.kernel.RelationController;
093import ptolemy.vergil.toolbox.FigureAction;
094import ptolemy.vergil.toolbox.MenuItemFactory;
095import ptolemy.vergil.toolbox.SnapConstraint;
096import ptolemy.vergil.unit.ConfigureUnitsAction;
097
098///////////////////////////////////////////////////////////////////
099//// ActorEditorGraphController
100
101/**
102 A Graph Controller for the Ptolemy II schematic editor.  In addition to the
103 interaction allowed in the viewer, this controller allows nodes to be
104 dragged and dropped onto its graph.  Relations can be created by
105 control-clicking on the background.  Links can be created by control-clicking
106 and dragging on a port or a relation.  In addition links can be created by
107 clicking and dragging on the ports that are inside an entity.
108 Anything can be deleted by selecting it and pressing
109 the delete key on the keyboard.
110
111 @author Steve Neuendorffer, Contributor: Edward A. Lee, Bert Rodiers
112 @version $Id$
113 @since Ptolemy II 2.0
114 @Pt.ProposedRating Red (eal)
115 @Pt.AcceptedRating Red (johnr)
116 */
117public class ActorEditorGraphController extends ActorViewerGraphController {
118    /** Create a new basic controller with default
119     *  terminal and edge interactors.
120     */
121    public ActorEditorGraphController() {
122        super();
123    }
124
125    ///////////////////////////////////////////////////////////////////
126    ////                         public methods                    ////
127
128    /** Add commands to the specified menu and toolbar, as appropriate
129     *  for this controller.  In this class, commands are added to create
130     *  ports and relations.
131     *  @param menu The menu to add to, or null if none.
132     *  @param toolbar The toolbar to add to, or null if none.
133     */
134    @Override
135    public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) {
136        super.addToMenuAndToolbar(menu, toolbar);
137        // Only include the port actions if there is an actor library.
138        // The ptinyViewer configuration uses this.
139        if (getConfiguration().getEntity("actor library") != null) {
140            diva.gui.GUIUtilities.addMenuItem(menu, _newInputPortAction);
141            diva.gui.GUIUtilities.addToolBarButton(toolbar,
142                    _newInputPortAction);
143            diva.gui.GUIUtilities.addMenuItem(menu, _newOutputPortAction);
144            diva.gui.GUIUtilities.addToolBarButton(toolbar,
145                    _newOutputPortAction);
146            diva.gui.GUIUtilities.addMenuItem(menu, _newInoutPortAction);
147            diva.gui.GUIUtilities.addToolBarButton(toolbar,
148                    _newInoutPortAction);
149            diva.gui.GUIUtilities.addMenuItem(menu, _newInputMultiportAction);
150            diva.gui.GUIUtilities.addToolBarButton(toolbar,
151                    _newInputMultiportAction);
152            diva.gui.GUIUtilities.addMenuItem(menu, _newOutputMultiportAction);
153            diva.gui.GUIUtilities.addToolBarButton(toolbar,
154                    _newOutputMultiportAction);
155            diva.gui.GUIUtilities.addMenuItem(menu, _newInoutMultiportAction);
156            diva.gui.GUIUtilities.addToolBarButton(toolbar,
157                    _newInoutMultiportAction);
158
159            menu.addSeparator();
160
161            // Add an item that adds new relations.
162            diva.gui.GUIUtilities.addMenuItem(menu, _newRelationAction);
163            diva.gui.GUIUtilities.addToolBarButton(toolbar, _newRelationAction);
164        }
165    }
166
167    /** Set the configuration.  The configuration is used when
168     *  opening documentation files.
169     *  @param configuration The configuration.
170     */
171    @Override
172    public void setConfiguration(Configuration configuration) {
173        super.setConfiguration(configuration);
174
175        if (_portDialogAction != null) {
176            _portDialogAction.setConfiguration(configuration);
177        }
178
179        if (_configureUnitsAction != null) {
180            _configureUnitsAction.setConfiguration(configuration);
181        }
182
183        if (_listenToActorFactory != null) {
184            _listenToActorFactory.setConfiguration(configuration);
185        }
186    }
187
188    ///////////////////////////////////////////////////////////////////
189    ////                         Public Inner Classes              ////
190
191    ///////////////////////////////////////////////////////////////////
192    //// NewRelationAction
193    /** An action to create a new relation. */
194    @SuppressWarnings("serial")
195    public class NewRelationAction extends FigureAction {
196        /** Create an action that creates a new relation.
197         */
198        public NewRelationAction() {
199            this(null);
200        }
201
202        /** Create an action that creates a new relation.
203         *  @param iconRoles A matrix of Strings, where each element
204         *  consists of two Strings, the absolute URL of the icon
205         *  and the key that represents the role of the icon.  The keys
206         *  are usually static fields from this class, such as
207         *  {@link diva.gui.GUIUtilities#LARGE_ICON},
208         *  {@link diva.gui.GUIUtilities#ROLLOVER_ICON},
209         *  {@link diva.gui.GUIUtilities#ROLLOVER_SELECTED_ICON} or
210         *  {@link diva.gui.GUIUtilities#SELECTED_ICON}.
211         *  If this parameter is null, then the icon comes from
212         *  the calling getNodeRenderer() on the {@link #_portController}.
213         *  @see diva.gui.GUIUtilities#addIcons(Action, String[][])
214         */
215        public NewRelationAction(String[][] iconRoles) {
216            super("New Relation");
217
218            if (iconRoles != null) {
219                GUIUtilities.addIcons(this, iconRoles);
220            } else {
221                // Standard toolbar icons are 25x25 pixels.
222                NodeRenderer renderer = _relationController.getNodeRenderer();
223                Figure figure = renderer.render(null);
224
225                FigureIcon icon = new FigureIcon(figure, 25, 25, 1, true);
226                putValue(diva.gui.GUIUtilities.LARGE_ICON, icon);
227            }
228            putValue("tooltip", "Control-click to create a new relation");
229            putValue(diva.gui.GUIUtilities.MNEMONIC_KEY,
230                    Integer.valueOf(KeyEvent.VK_R));
231        }
232
233        @Override
234        public void actionPerformed(ActionEvent e) {
235            super.actionPerformed(e);
236
237            double x;
238            double y;
239
240            // If you add a vertex on top of an existing link the vertex will
241            // be added to the link. If the link exists, link will be different
242            // from null.
243
244            Link link = null;
245
246            if (getSourceType() == TOOLBAR_TYPE
247                    || getSourceType() == MENUBAR_TYPE) {
248                // No location in the action, so put it in the middle.
249                BasicGraphFrame frame = ActorEditorGraphController.this
250                        .getFrame();
251                Point2D center;
252
253                if (frame != null) {
254                    // Put in the middle of the visible part.
255                    center = frame.getCenter();
256                    x = center.getX();
257                    y = center.getY();
258                } else {
259                    // Put in the middle of the pane.
260                    GraphPane pane = getGraphPane();
261                    center = pane.getSize();
262                    x = center.getX() / 2;
263                    y = center.getY() / 2;
264                }
265            } else {
266                // Transform
267                AffineTransform current = getGraphPane().getTransformContext()
268                        .getTransform();
269                AffineTransform inverse;
270
271                try {
272                    inverse = current.createInverse();
273                } catch (NoninvertibleTransformException ex) {
274                    throw new RuntimeException(ex.toString());
275                }
276
277                Point2D point = new Point2D.Double(getX(), getY());
278
279                inverse.transform(point, point);
280                x = point.getX();
281                y = point.getY();
282
283                // If you add a vertex on top of an existing link the vertex will
284                // be added to the link. In this code fragment we will find
285                // out whether the vertex is on top of a link.
286                {
287                    GraphPane pane = getGraphPane();
288                    FigureLayer foregroundLayer = pane.getForegroundLayer();
289
290                    double halo = foregroundLayer.getPickHalo();
291                    double width = halo * 2;
292
293                    // The rectangle in which we search for a Figure.
294                    Rectangle2D region = new Rectangle2D.Double(x - halo,
295                            y - halo, width, width);
296
297                    // Iterate through figures within the region.
298                    Iterator<?> foregroundFigures = foregroundLayer.getFigures()
299                            .getIntersectedFigures(region).figuresFromFront();
300                    Iterator<?> pickFigures = CanvasUtilities
301                            .pickIter(foregroundFigures, region);
302
303                    while (link == null && pickFigures.hasNext()) {
304                        CanvasComponent possibleFigure = (CanvasComponent) pickFigures
305                                .next();
306                        if (possibleFigure == null) {
307                            // Nothing to see here, move along - there is no Figure.
308                        } else if (possibleFigure instanceof UserObjectContainer) {
309                            // Work our way up the CanvasComponent parent tree
310                            // See EditorDropTarget for similar code.
311                            Object userObject = ((UserObjectContainer) possibleFigure)
312                                    .getUserObject();
313                            if (userObject instanceof Link) {
314                                link = (Link) userObject;
315                            }
316                        }
317                    }
318                }
319            }
320
321            ActorGraphModel graphModel = (ActorGraphModel) getGraphModel();
322            double[] point = _offsetVertex(SnapConstraint.constrainPoint(x, y));
323            final NamedObj toplevel = graphModel.getPtolemyModel();
324
325            if (!(toplevel instanceof CompositeEntity)) {
326                throw new InternalErrorException(
327                        "Cannot invoke NewRelationAction on an object "
328                                + "that is not a CompositeEntity.");
329            }
330
331            final String relationName = toplevel.uniqueName("relation");
332
333            StringBuffer moml = new StringBuffer();
334            if (link != null) {
335                // Add the vertex to an existing link.
336                moml.append("<group>\n");
337                StringBuffer failmoml = new StringBuffer();
338                graphModel.getLinkModel().addNewVertexToLink(moml, failmoml,
339                        (CompositeEntity) toplevel, link, relationName, x, y);
340                moml.append("</group>\n");
341            } else {
342                final String vertexName = "vertex1";
343
344                // Create the relation.
345                moml.append("<relation name=\"" + relationName + "\">\n");
346                moml.append("<vertex name=\"" + vertexName + "\" value=\"{");
347                moml.append(point[0] + ", " + point[1]);
348                moml.append("}\"/>\n");
349                moml.append("</relation>");
350            }
351            MoMLChangeRequest request = new MoMLChangeRequest(this, toplevel,
352                    moml.toString());
353            request.setUndoable(true);
354            toplevel.requestChange(request);
355        }
356
357        /** Offset a figure if another figure is already at that location.
358         *  @param point An array of two doubles (x and y)
359         *  @return An array of two doubles (x and y) that represents
360         *  either the original location or an offset location that
361         *  does not obscure an object of class <i>figure</i>.
362         */
363        protected double[] _offsetVertex(double[] point) {
364
365            GraphPane pane = getGraphPane();
366            FigureLayer foregroundLayer = pane.getForegroundLayer();
367
368            Rectangle2D visibleRectangle;
369            BasicGraphFrame frame = ActorEditorGraphController.this.getFrame();
370            if (frame != null) {
371                visibleRectangle = frame.getVisibleRectangle();
372            } else {
373                visibleRectangle = pane.getCanvas().getVisibleSize();
374            }
375            return offsetFigure(point[0], point[1], PASTE_OFFSET, PASTE_OFFSET,
376                    ptolemy.moml.Vertex.class, foregroundLayer,
377                    visibleRectangle);
378        }
379    }
380
381    ///////////////////////////////////////////////////////////////////
382    ////                         protected methods                 ////
383
384    /** Add hot keys to the actions in the given JGraph.
385     *
386     *  @param jgraph The JGraph to which hot keys are to be added.
387     */
388    @Override
389    protected void _addHotKeys(JGraph jgraph) {
390        super._addHotKeys(jgraph);
391
392        _classDefinitionController.addHotKeys(getFrame().getJGraph());
393
394        try {
395
396            StringParameter actorInteractionAddon;
397            actorInteractionAddon = (StringParameter) this.getConfiguration()
398                    .getAttribute("_actorInteractionAddon", Parameter.class);
399
400            if (actorInteractionAddon != null) {
401                _addonActorController.addHotKeys(getFrame().getJGraph());
402            }
403        } catch (Exception e) {
404            e.printStackTrace();
405        }
406
407    }
408
409    /** Create the controllers for nodes in this graph.
410     *  In this class, controllers with FULL access are created.
411     *  This is called by the constructor, so derived classes that
412     *  override this must be careful not to reference local variables
413     *  defined in the derived classes, because the derived classes
414     *  will not have been fully constructed by the time this is called.
415     */
416    @Override
417    protected void _createControllers() {
418        Configuration _config = Configuration.configurations().iterator()
419                .next();
420        String _alternateActorInstanceClassName = null;
421        _attributeController = new AttributeController(this,
422                AttributeController.FULL);
423
424        _classDefinitionController = new ClassDefinitionController(this);
425
426        if (_config != null) {
427
428            try {
429
430                StringParameter actorInteractionAddonParameter;
431                actorInteractionAddonParameter = (StringParameter) _config
432                        .getAttribute("_actorInteractionAddon",
433                                Parameter.class);
434
435                if (actorInteractionAddonParameter != null) {
436                    String actorInteractionAddonClassName = actorInteractionAddonParameter
437                            .stringValue();
438                    Class actorInteractionAddonClass = Class
439                            .forName(actorInteractionAddonClassName);
440
441                    ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass
442                            .newInstance();
443
444                    _addonActorController = actorInteractionAddon
445                            .getControllerInstance(this);
446
447                }
448            } catch (Exception e) {
449                e.printStackTrace();
450            }
451
452        }
453
454        if (_config != null) {
455            /*
456             * If _alternateActorInstanceController is set in the config, use that
457             * class as the _entityController instead of the default
458             * ActorInstanceController
459             */
460            StringAttribute _alternateActorInstanceAttribute = (StringAttribute) _config
461                    .getAttribute("_alternateActorInstanceController");
462            if (_alternateActorInstanceAttribute != null) {
463                _alternateActorInstanceClassName = _alternateActorInstanceAttribute
464                        .getExpression();
465            }
466        }
467
468        if (_alternateActorInstanceClassName == null) {
469            // Default to the normal ActorInstanceController.
470            _entityController = new ActorInstanceController(this);
471        } else {
472            try {
473                // Try to load the alternate class.
474                Class _alternateActorInstanceClass = Class
475                        .forName(_alternateActorInstanceClassName);
476                Class[] argsClass = new Class[] {
477                        diva.graph.GraphController.class };
478                Object[] args = new Object[] { this };
479                Constructor alternateActorInstanceConstructor = _alternateActorInstanceClass
480                        .getConstructor(argsClass);
481                _entityController = (ActorController) alternateActorInstanceConstructor
482                        .newInstance(args);
483            } catch (Exception e) {
484                System.out.println("The configuration has "
485                        + "_alternateActorInstanceController set, but the class "
486                        + _alternateActorInstanceClassName
487                        + " is not found.  Defaulting "
488                        + " to ActorInstanceController: " + e.getMessage());
489                e.printStackTrace();
490            }
491        }
492
493        // Set up a listener to lay out the ports when graph changes.
494        // NOTE: It is imperative that there be no more than one such
495        // listener!  If there is more than one instance, the
496        // ports will be laid out more than once. This manifests itself
497        // as a bug where port names are rendered twice, and for some
498        // inexplicable reason, are rendered in two different places!
499        // The filter for the layout algorithm of the ports within this
500        // entity. This returns true only if the candidate object is
501        // an instance of Locatable and the semantic object associated
502        // with it is an instance of Entity.
503        Filter portFilter = new Filter() {
504            @Override
505            public boolean accept(Object candidate) {
506                GraphModel model = getGraphModel();
507                Object semanticObject = model.getSemanticObject(candidate);
508
509                // For some strange reason, this used to avoid doing
510                // layout for class definitions, with the following clause:
511                //  && !((Entity) semanticObject).isClassDefinition()
512                if (candidate instanceof Locatable
513                        && semanticObject instanceof Entity) {
514                    return true;
515                } else {
516                    return false;
517                }
518            }
519        };
520
521        // Anytime we add a port to an entity, we want to layout all the
522        // ports within that entity.
523        GlobalLayout layout = new EntityLayout();
524        addGraphViewListener(
525                new IncrementalLayoutListener(new IncrLayoutAdapter(layout) {
526                    @Override
527                    public void nodeDrawn(Object node) {
528                        layout(node);
529                    }
530                }, portFilter));
531
532        _entityPortController = new IOPortController(this,
533                AttributeController.FULL);
534        _portController = new ExternalIOPortController(this,
535                AttributeController.FULL);
536        _relationController = new RelationController(this);
537        _linkController = new LinkController(this);
538    }
539
540    /** Initialize interactions for the specified controller.  This
541     *  method is called when a new controller is constructed. In this
542     *  class, this method attaches a link creator to the controller
543     *  if the controller is an instance of ExternalIOPortController,
544     *  IOPortController, or RelationController.
545     *  @param controller The controller for which to initialize interaction.
546     */
547    @Override
548    protected void _initializeInteraction(NamedObjController controller) {
549        super._initializeInteraction(controller);
550
551        if (controller instanceof ExternalIOPortController
552                || controller instanceof IOPortController
553                || controller instanceof RelationController) {
554            Interactor interactor = controller.getNodeInteractor();
555
556            if (interactor instanceof CompositeInteractor) {
557                ((CompositeInteractor) interactor).addInteractor(_linkCreator);
558            }
559        }
560    }
561
562    /** Initialize all interaction on the graph pane. This method
563     *  is called by the setGraphPane() method of the superclass.
564     *  This initialization cannot be done in the constructor because
565     *  the controller does not yet have a reference to its pane
566     *  at that time.
567     */
568    @Override
569    protected void initializeInteraction() {
570        // FIXME: why is this protected, but does not have a leading underscore
571        // how is this different from _initializeInteraction above?
572        super.initializeInteraction();
573
574        GraphPane pane = getGraphPane();
575
576        // Add a menu command to configure the ports.
577        _portDialogAction = new PortDialogAction("Ports");
578        _portDialogAction.setConfiguration(getConfiguration());
579
580        _configureMenuFactory.addAction(_portDialogAction, "Customize");
581        _configureUnitsAction = new ConfigureUnitsAction("Units Constraints");
582        _configureMenuFactory.addAction(_configureUnitsAction, "Customize");
583        _configureUnitsAction.setConfiguration(getConfiguration());
584
585        // Add a menu command to list to the actor.
586        _listenToActorFactory = new ListenToActorFactory();
587        _menuFactory.addMenuItemFactory(_listenToActorFactory);
588        _listenToActorFactory.setConfiguration(getConfiguration());
589
590        // Create listeners that creates new relations.
591        _relationCreator = new RelationCreator();
592        _relationCreator.setMouseFilter(_shortcutFilter);
593
594        pane.getBackgroundEventLayer().addInteractor(_relationCreator);
595
596        // Note that shift-click is already bound to the dragSelection
597        // interactor when adding things to a selection.
598        // Create the interactor that drags new edges.
599        _linkCreator = new LinkCreator();
600        _linkCreator.setMouseFilter(_shortcutFilter);
601
602        // NOTE: Do not use _initializeInteraction() because we are
603        // still in the constructor, and that method is overloaded in
604        // derived classes.
605        ((CompositeInteractor) _portController.getNodeInteractor())
606                .addInteractor(_linkCreator);
607        ((CompositeInteractor) _entityPortController.getNodeInteractor())
608                .addInteractor(_linkCreator);
609        ((CompositeInteractor) _relationController.getNodeInteractor())
610                .addInteractor(_linkCreator);
611
612        LinkCreator linkCreator2 = new LinkCreator();
613        linkCreator2
614                .setMouseFilter(new MouseFilter(InputEvent.BUTTON1_MASK, 0));
615        ((CompositeInteractor) _entityPortController.getNodeInteractor())
616                .addInteractor(linkCreator2);
617    }
618
619    /** Action for creating a new relation. */
620    protected Action _newRelationAction = new NewRelationAction(new String[][] {
621            { "/ptolemy/vergil/actor/img/relation.gif",
622                    GUIUtilities.LARGE_ICON },
623            { "/ptolemy/vergil/actor/img/relation_o.gif",
624                    GUIUtilities.ROLLOVER_ICON },
625            { "/ptolemy/vergil/actor/img/relation_ov.gif",
626                    GUIUtilities.ROLLOVER_SELECTED_ICON },
627            { "/ptolemy/vergil/actor/img/relation_on.gif",
628                    GUIUtilities.SELECTED_ICON } });
629
630    //   private LinkCreator _linkCreator2;  // For shift-click
631
632    ///////////////////////////////////////////////////////////////////
633    ////                         Protected Inner Classes           ////
634
635    /** This class is an interactor that interactively drags edges from
636     *  one terminal to another, creating a link to connect them.
637     */
638    protected class LinkCreator extends AbstractInteractor {
639        /** Create a new edge when the mouse is pressed. */
640        @Override
641        public void mousePressed(LayerEvent event) {
642            Figure source = event.getFigureSource();
643            NamedObj sourceObject = (NamedObj) source.getUserObject();
644
645            if (sourceObject instanceof PublisherPort
646                    || sourceObject instanceof SubscriberPort) {
647                // MessageHandler.error("Cannot connect directly to publish and subscribe ports.");
648                return;
649            }
650
651            // Create the new edge.
652            Link link = new Link();
653
654            // Set the tail, going through the model so the link is added
655            // to the list of links.
656            ActorGraphModel model = (ActorGraphModel) getGraphModel();
657            model.getLinkModel().setTail(link, sourceObject);
658
659            try {
660                // add it to the foreground layer.
661                FigureLayer layer = getGraphPane().getForegroundLayer();
662                Site headSite;
663                Site tailSite;
664
665                // Temporary sites.  One of these will get blown away later.
666                headSite = new AutonomousSite(layer, event.getLayerX(),
667                        event.getLayerY());
668                tailSite = new AutonomousSite(layer, event.getLayerX(),
669                        event.getLayerY());
670
671                // Render the edge.
672                Connector c = getEdgeController(link).render(link, layer,
673                        tailSite, headSite);
674
675                // get the actual attach site.
676                tailSite = getEdgeController(link).getConnectorTarget()
677                        .getTailSite(c, source, event.getLayerX(),
678                                event.getLayerY());
679
680                if (tailSite == null) {
681                    throw new RuntimeException("Invalid connector target: "
682                            + "no valid site found for tail of new connector.");
683                }
684
685                // And reattach the connector.
686                c.setTailSite(tailSite);
687
688                // Add it to the selection so it gets a manipulator, and
689                // make events go to the grab-handle under the mouse
690                getSelectionModel().addSelection(c);
691
692                ConnectorManipulator cm = (ConnectorManipulator) c.getParent();
693                GrabHandle gh = cm.getHeadHandle();
694                layer.grabPointer(event, gh);
695            } catch (Exception ex) {
696                MessageHandler.error("Drag connection failed:", ex);
697            }
698        }
699    }
700
701    /** An interactor for creating relations upon control clicking.
702     */
703    protected class RelationCreator extends ActionInteractor {
704        public RelationCreator() {
705            super();
706            setAction(_newRelationAction);
707        }
708    }
709
710    ///////////////////////////////////////////////////////////////////
711    ////                         private variables                 ////
712
713    private ConfigureUnitsAction _configureUnitsAction;
714
715    /** The interactors that interactively creates edges. */
716    private LinkCreator _linkCreator; // For control-click
717
718    /** Factory for listen to actor menu item. */
719    private ListenToActorFactory _listenToActorFactory;
720
721    /** Action for creating a new inout multiport. */
722    private Action _newInoutMultiportAction = new NewPortAction(
723            ExternalIOPortController._GENERIC_INOUT_MULTIPORT,
724            "New input/output multiport", KeyEvent.VK_T,
725            new String[][] {
726                    { "/ptolemy/vergil/actor/img/multi_inout.gif",
727                            GUIUtilities.LARGE_ICON },
728                    { "/ptolemy/vergil/actor/img/multi_inout_o.gif",
729                            GUIUtilities.ROLLOVER_ICON },
730                    { "/ptolemy/vergil/actor/img/multi_inout_ov.gif",
731                            GUIUtilities.ROLLOVER_SELECTED_ICON },
732                    { "/ptolemy/vergil/actor/img/multi_inout_on.gif",
733                            GUIUtilities.SELECTED_ICON } });
734
735    /** Action for creating a new input/output port. */
736    private Action _newInoutPortAction = new NewPortAction(
737            ExternalIOPortController._GENERIC_INOUT, "New input/output port",
738            KeyEvent.VK_P,
739            new String[][] {
740                    { "/ptolemy/vergil/actor/img/single_inout.gif",
741                            GUIUtilities.LARGE_ICON },
742                    { "/ptolemy/vergil/actor/img/single_inout_o.gif",
743                            GUIUtilities.ROLLOVER_ICON },
744                    { "/ptolemy/vergil/actor/img/single_inout_ov.gif",
745                            GUIUtilities.ROLLOVER_SELECTED_ICON },
746                    { "/ptolemy/vergil/actor/img/single_inout_on.gif",
747                            GUIUtilities.SELECTED_ICON } });
748
749    /** Action for creating a new input multiport. */
750    private Action _newInputMultiportAction = new NewPortAction(
751            ExternalIOPortController._GENERIC_INPUT_MULTIPORT,
752            "New input multiport", KeyEvent.VK_N,
753            new String[][] {
754                    { "/ptolemy/vergil/actor/img/multi_in.gif",
755                            GUIUtilities.LARGE_ICON },
756                    { "/ptolemy/vergil/actor/img/multi_in_o.gif",
757                            GUIUtilities.ROLLOVER_ICON },
758                    { "/ptolemy/vergil/actor/img/multi_in_ov.gif",
759                            GUIUtilities.ROLLOVER_SELECTED_ICON },
760                    { "/ptolemy/vergil/actor/img/multi_in_on.gif",
761                            GUIUtilities.SELECTED_ICON } });
762
763    /** Action for creating a new input port. */
764    private Action _newInputPortAction = new NewPortAction(
765            ExternalIOPortController._GENERIC_INPUT, "New input port",
766            KeyEvent.VK_I,
767            new String[][] {
768                    { "/ptolemy/vergil/actor/img/single_in.gif",
769                            GUIUtilities.LARGE_ICON },
770                    { "/ptolemy/vergil/actor/img/single_in_o.gif",
771                            GUIUtilities.ROLLOVER_ICON },
772                    { "/ptolemy/vergil/actor/img/single_in_ov.gif",
773                            GUIUtilities.ROLLOVER_SELECTED_ICON },
774                    { "/ptolemy/vergil/actor/img/single_in_on.gif",
775                            GUIUtilities.SELECTED_ICON } });
776
777    /** Action for creating a new output multiport. */
778    private Action _newOutputMultiportAction = new NewPortAction(
779            ExternalIOPortController._GENERIC_OUTPUT_MULTIPORT,
780            "New output multiport", KeyEvent.VK_U,
781            new String[][] {
782                    { "/ptolemy/vergil/actor/img/multi_out.gif",
783                            GUIUtilities.LARGE_ICON },
784                    { "/ptolemy/vergil/actor/img/multi_out_o.gif",
785                            GUIUtilities.ROLLOVER_ICON },
786                    { "/ptolemy/vergil/actor/img/multi_out_ov.gif",
787                            GUIUtilities.ROLLOVER_SELECTED_ICON },
788                    { "/ptolemy/vergil/actor/img/multi_out_on.gif",
789                            GUIUtilities.SELECTED_ICON } });
790
791    /** Action for creating a new output port. */
792    private Action _newOutputPortAction = new NewPortAction(
793            ExternalIOPortController._GENERIC_OUTPUT, "New output port",
794            KeyEvent.VK_O,
795            new String[][] {
796                    { "/ptolemy/vergil/actor/img/single_out.gif",
797                            GUIUtilities.LARGE_ICON },
798                    { "/ptolemy/vergil/actor/img/single_out_o.gif",
799                            GUIUtilities.ROLLOVER_ICON },
800                    { "/ptolemy/vergil/actor/img/single_out_ov.gif",
801                            GUIUtilities.ROLLOVER_SELECTED_ICON },
802                    { "/ptolemy/vergil/actor/img/single_out_on.gif",
803                            GUIUtilities.SELECTED_ICON } });
804
805    /** The port dialog factory. */
806    private PortDialogAction _portDialogAction;
807
808    /** The interactor for creating new relations. */
809    private RelationCreator _relationCreator;
810
811    /** The filter for shortcut operations.  This is used for creation
812     *  of relations and creation of links from relations. Under PC,
813     *  this is a control-1 click.  Under Mac OS X, the control key is
814     *  used for context menus and this corresponds to the command-1
815     *  click.  For details, see the Apple java archive
816     *  http://lists.apple.com/archives/java-dev User: archives,
817     *  passwd: archives
818     */
819    private MouseFilter _shortcutFilter = new MouseFilter(
820            InputEvent.BUTTON1_MASK,
821            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(),
822            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
823
824    ///////////////////////////////////////////////////////////////////
825    ////                         inner classes                     ////
826
827    ///////////////////////////////////////////////////////////////////
828    //// ListenToActorFactory
829    private class ListenToActorFactory implements MenuItemFactory {
830        /** Add an item to the given context menu that will open a listen
831         *  to actor window.
832         *  @param menu The context menu.
833         *  @param object The object whose ports are being manipulated.
834         */
835        @Override
836        public JMenuItem create(final JContextMenu menu, NamedObj object) {
837            String name = "Listen to Actor";
838            final NamedObj target = object;
839
840            _action = new ListenToAction(target,
841                    ActorEditorGraphController.this);
842            _action.setConfiguration(_configuration);
843            return menu.add(_action, name);
844        }
845
846        /** Set the configuration for use by the help screen.
847         *  @param configuration The configuration.
848         */
849        public void setConfiguration(Configuration configuration) {
850            _configuration = configuration;
851
852            if (_action != null) {
853                _action.setConfiguration(_configuration);
854            }
855        }
856
857        private ListenToAction _action;
858
859        private Configuration _configuration;
860    }
861}