001/* Base class for graph controllers in Ptolemy.
002
003 Copyright (c) 1999-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
027 */
028package ptolemy.vergil.basic;
029
030import java.awt.EventQueue;
031import java.awt.event.ActionEvent;
032import java.awt.geom.Point2D;
033import java.net.URL;
034import java.util.Iterator;
035import java.util.LinkedList;
036import java.util.List;
037
038import javax.swing.AbstractAction;
039import javax.swing.Action;
040import javax.swing.JMenu;
041import javax.swing.JToolBar;
042import javax.swing.SwingUtilities;
043
044import diva.canvas.Figure;
045import diva.canvas.connector.Connector;
046import diva.canvas.interactor.SelectionRenderer;
047import diva.graph.AbstractGraphController;
048import diva.graph.GraphController;
049import diva.graph.GraphModel;
050import diva.graph.GraphPane;
051import diva.graph.GraphUtilities;
052import diva.graph.JGraph;
053import diva.graph.NodeController;
054import diva.gui.toolbox.MenuCreator;
055import ptolemy.actor.gui.ColorAttribute;
056import ptolemy.actor.gui.Configuration;
057import ptolemy.actor.gui.DialogTableau;
058import ptolemy.data.expr.Parameter;
059import ptolemy.kernel.Entity;
060import ptolemy.kernel.InstantiableNamedObj;
061import ptolemy.kernel.util.Attribute;
062import ptolemy.kernel.util.ChangeRequest;
063import ptolemy.kernel.util.DebugEvent;
064import ptolemy.kernel.util.DebugListener;
065import ptolemy.kernel.util.IllegalActionException;
066import ptolemy.kernel.util.Locatable;
067import ptolemy.kernel.util.NameDuplicationException;
068import ptolemy.kernel.util.Nameable;
069import ptolemy.kernel.util.NamedObj;
070import ptolemy.kernel.util.Settable;
071import ptolemy.kernel.util.ValueListener;
072import ptolemy.util.FileUtilities;
073import ptolemy.util.MessageHandler;
074import ptolemy.util.StringUtilities;
075import ptolemy.vergil.actor.ActorGraphFrame;
076import ptolemy.vergil.toolbox.ConfigureAction;
077import ptolemy.vergil.toolbox.FigureAction;
078import ptolemy.vergil.toolbox.MenuActionFactory;
079import ptolemy.vergil.toolbox.PtolemyMenuFactory;
080import ptolemy.vergil.unit.UnitSolverDialog;
081
082///////////////////////////////////////////////////////////////////
083//// BasicGraphController
084
085/**
086 A base class for Ptolemy II graph controllers. This extends the base
087 class with an association with a configuration. The configuration is
088 central to a Ptolemy GUI, and is used by derived classes to perform
089 various functions such as opening models or their documentation.
090 The class also provides a strategy pattern interface for a controller
091 to add commands to the menu or toolbar of the frame it is controlling.
092
093 @author Steve Neuendorffer and Edward A. Lee
094 @version $Id$
095 @since Ptolemy II 2.0
096 @Pt.ProposedRating Red (eal)
097 @Pt.AcceptedRating Red (johnr)
098 */
099public abstract class BasicGraphController extends AbstractGraphController
100        implements DebugListener, ValueListener {
101    /** Create a new basic controller.
102     */
103    public BasicGraphController() {
104        super();
105    }
106
107    ///////////////////////////////////////////////////////////////////
108    ////                         public methods                    ////
109
110    /** Request a change that clears all the error highlights. */
111    public void clearAllErrorHighlights() {
112        ChangeRequest request = _getClearAllErrorHighlightsChangeRequest();
113        _frame.getModel().requestChange(request);
114    }
115
116    /** Highlight the specified object and all its containers to
117     *  indicate that it is the source of an error.
118     *  @param culprit The culprit.
119     */
120    public void highlightError(final Nameable culprit) {
121        if (culprit instanceof NamedObj) {
122            ChangeRequest request = new ChangeRequest(this,
123                    "Error Highlighter") {
124                @Override
125                protected void _execute() throws Exception {
126                    _addErrorHighlightIfNeeded(culprit);
127                    NamedObj container = culprit.getContainer();
128                    while (container != null) {
129                        _addErrorHighlightIfNeeded(container);
130                        container = container.getContainer();
131                    }
132                }
133            };
134            request.setPersistent(false);
135            ((NamedObj) culprit).requestChange(request);
136        }
137    }
138
139    /** Add commands to the specified menu and toolbar, as appropriate
140     *  for this controller.  In this base class, nothing is added.
141     *  @param menu The menu to add to, or null if none.
142     *  @param toolbar The toolbar to add to, or null if none.
143     */
144    public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) {
145        _addHotKeys(getFrame().getJGraph());
146    }
147
148    /** Clear any animation highlight that might currently be active.
149     */
150    public void clearAnimation() {
151        // Deselect previous one.
152        if (_animated != null && _animationRenderer != null) {
153            _animationRenderer.renderDeselected(_animated);
154        }
155    }
156
157    /** React to an event.  This base class does nothing.
158     *  @param event The debug event.
159     */
160    @Override
161    public void event(DebugEvent event) {
162    }
163
164    /** Get the time delay for animation.  After highlighting,
165     *  derived classes are expected to sleep for the specified amount
166     *  of time, in milliseconds.
167     *  @return The animation delay set by setAnimationDelay().
168     *  @see #setAnimationDelay(long)
169     */
170    public long getAnimationDelay() {
171        return _animationDelay;
172    }
173
174    /** Return the configuration that has been specified by setConfiguration(),
175     *  or null if none.
176     *  @return The configuration.
177     *  @see #setConfiguration(Configuration)
178     */
179    public Configuration getConfiguration() {
180        return _configuration;
181    }
182
183    /** Return the configuration menu factory.
184     *
185     *  @return The configuration menu factory.
186     */
187    public MenuActionFactory getConfigureMenuFactory() {
188        return _configureMenuFactory;
189    }
190
191    /** Get the graph frame, or null if there is none.  This is used by
192     *  some of the controllers to mark the modified bit of the frame
193     *  and to update any dependents.
194     *  @return The graph frame, or null if there is none.
195     *  @see #setFrame(BasicGraphFrame)
196     */
197    public BasicGraphFrame getFrame() {
198        return _frame;
199    }
200
201    /** Return the node controller appropriate for the given object.
202     *  In this base class, the method checks to see whether the object
203     *  is an instance of Locatable and contains a NodeControllerFactory
204     *  (which is an attribute).  If it does, then it invokes that factory
205     *  to create a node controller. Otherwise, it returns null.
206     *  @param object The object to get a controller for.
207     *  @return A custom node controller if there is one, and null otherwise.
208     */
209    @Override
210    public NodeController getNodeController(Object object) {
211        if (object instanceof Locatable) {
212            Object semanticObject = getGraphModel().getSemanticObject(object);
213
214            // Check to see whether
215            // this is a NamedObj that contains a NodeControllerFactory.
216            // If so, that should be used. If not, use the defaults
217            // below.  This allows any object in Ptolemy II to have
218            // its own controller, which means its own context menu
219            // and its own interactors.
220            if (semanticObject instanceof NamedObj) {
221                List factoryList = ((NamedObj) semanticObject)
222                        .attributeList(NodeControllerFactory.class);
223
224                // FIXME: This is creating a new node controller for each instance!!!
225                // This causes problems as indicated by the NOTE in ActorInstanceController.
226                if (factoryList.size() > 0) {
227                    NodeControllerFactory factory = (NodeControllerFactory) factoryList
228                            .get(0);
229                    NamedObjController controller = factory.create(this);
230                    controller.setConfiguration(getConfiguration());
231                    _initializeInteraction(controller);
232                    return controller;
233                }
234            }
235        }
236
237        return null;
238    }
239
240    /** React to a debug message.  This base class does nothing.
241     *  @param message The message.
242     */
243    @Override
244    public void message(String message) {
245    }
246
247    /** Set the time delay for animation.  After highlighting,
248     *  derived classes are expected to sleep for the specified amount
249     *  of time, in milliseconds.  If this method is not called, or
250     *  is called with argument 0, then no delay is introduced.
251     *  @param time Time to sleep, in milliseconds.
252     *  @see #getAnimationDelay()
253
254     */
255    public void setAnimationDelay(long time) {
256        _animationDelay = time;
257    }
258
259    /** Set the configuration.  This is used by some of the controllers
260     *  when opening files or URLs.
261     *  The configuration is checked for a "_getDocumentationActionDocPreference",
262     *  which, if present, is an integer that is passed to
263     *  {@link ptolemy.vergil.basic.GetDocumentationAction#GetDocumentationAction(int)}.
264     *  This attribute is used to select the Kepler-specific
265     *  KeplerDocumentationAttribute.
266     *  @param configuration The configuration.
267     *  @see #getConfiguration()
268     */
269    public void setConfiguration(Configuration configuration) {
270        _configuration = configuration;
271
272        if (configuration != null && _getDocumentationAction == null) {
273            int docPreference = 0;
274            String parameterName = "_getDocumentationActionDocPreference";
275            try {
276                Parameter getDocumentationActionDocPreference = (Parameter) configuration
277                        .getAttribute(parameterName, Parameter.class);
278                if (getDocumentationActionDocPreference != null) {
279                    // If you want KeplerDocumentationAttribute, set
280                    // _getDocumentationActionDocPreference to 1.
281                    docPreference = Integer
282                            .parseInt(getDocumentationActionDocPreference
283                                    .getExpression());
284                }
285            } catch (Exception ex) {
286                System.err.println("Warning, failed to parse " + parameterName);
287                ex.printStackTrace();
288            }
289            _getDocumentationAction = new GetDocumentationAction(docPreference);
290        }
291        if (_getDocumentationAction != null) {
292            _getDocumentationAction.setConfiguration(configuration);
293        }
294
295        if (_configuration != null && _menuFactory != null) {
296            // NOTE: The following requires that the configuration be
297            // non-null, or it will report an error.
298            _menuFactory.addMenuItemFactory(
299                    new MenuActionFactory(_openBaseClassAction));
300        }
301    }
302
303    ///////////////////////////////////////////////////////////////////
304    ////                         public methods                    ////
305
306    /** Set the figure associated with the given semantic object, and if
307     *  that semantic object is Settable, then set up a value listener
308     *  so that if its value changes, then the valueChanged() method
309     *  is invoked.
310     *  The semantic object is normally an attribute that implements
311     *  the Locatable interface, and the value indicates the location
312     *  of the object.
313     *  A null figure clears the association.
314     *  @param semanticObject The semantic object (normally a Locatable).
315     *  @param figure The figure.
316     */
317    @Override
318    public void setFigure(Object semanticObject, Figure figure) {
319        super.setFigure(semanticObject, figure);
320
321        if (semanticObject instanceof Settable) {
322            ((Settable) semanticObject).addValueListener(this);
323        }
324    }
325
326    /** Set the graph frame.  This is used by some of the controllers
327     *  to mark the modified bit of the frame and to update any dependents.
328     *  @param frame The graph frame, or null if there is none.
329     *  @see #getFrame()
330     */
331    public void setFrame(BasicGraphFrame frame) {
332        _frame = frame;
333    }
334
335    /** React to the fact that the specified Settable has changed.
336     *  If the specified Settable implements the Locatable interface,
337     *  then this method will move the figure and reroute any connections
338     *  to it. This is done immediately if the caller is in the Swing
339     *  event thread, but otherwise is deferred to the event thread.
340     *  @param settable The object that has changed value.
341     */
342    @Override
343    public void valueChanged(final Settable settable) {
344        if (settable instanceof Locatable && !_inValueChanged) {
345            // Have to defer this to the event thread, or repaint
346            // doesn't work properly.
347            Runnable action = new Runnable() {
348                @Override
349                public void run() {
350                    Locatable location = (Locatable) settable;
351                    Figure figure = getFigure(location);
352                    if (figure != null) {
353                        Point2D origin = figure.getOrigin();
354
355                        double originalUpperLeftX = origin.getX();
356                        double originalUpperLeftY = origin.getY();
357
358                        // NOTE: the following call may trigger an evaluation,
359                        // which results in another recursive call to this method.
360                        // Thus, we ignore the inside call and detect it with a
361                        // private variable.
362                        double[] newLocation;
363
364                        try {
365                            _inValueChanged = true;
366                            newLocation = location.getLocation();
367                        } finally {
368                            _inValueChanged = false;
369                        }
370
371                        double translationX = newLocation[0]
372                                - originalUpperLeftX;
373                        double translationY = newLocation[1]
374                                - originalUpperLeftY;
375
376                        if (translationX != 0.0 || translationY != 0.0) {
377                            // The translate method supposedly handles the required
378                            // repaint.
379                            figure.translate(translationX, translationY);
380
381                            // Reroute edges linked to this figure.
382                            GraphModel model = getGraphModel();
383                            Object userObject = figure.getUserObject();
384
385                            if (userObject != null) {
386                                Iterator inEdges = model.inEdges(userObject);
387
388                                while (inEdges.hasNext()) {
389                                    Figure connector = getFigure(
390                                            inEdges.next());
391
392                                    if (connector instanceof Connector) {
393                                        ((Connector) connector).reroute();
394                                    }
395                                }
396
397                                Iterator outEdges = model.outEdges(userObject);
398
399                                while (outEdges.hasNext()) {
400                                    Figure connector = getFigure(
401                                            outEdges.next());
402
403                                    if (connector instanceof Connector) {
404                                        ((Connector) connector).reroute();
405                                    }
406                                }
407
408                                if (model.isComposite(userObject)) {
409                                    Iterator edges = GraphUtilities
410                                            .partiallyContainedEdges(userObject,
411                                                    model);
412
413                                    while (edges.hasNext()) {
414                                        Figure connector = getFigure(
415                                                edges.next());
416
417                                        if (connector instanceof Connector) {
418                                            ((Connector) connector).reroute();
419                                        }
420                                    }
421                                }
422                            }
423                        }
424                    }
425                } /* end of run() method */
426            }; /* end of Runnable definition. */
427
428            if (EventQueue.isDispatchThread()) {
429                action.run();
430            } else {
431                SwingUtilities.invokeLater(action);
432            }
433        }
434    }
435
436    ///////////////////////////////////////////////////////////////////
437    ////                         protected methods                 ////
438
439    /** Add hot keys to the actions in the given JGraph.
440     *
441     *  @param jgraph The JGraph to which hot keys are to be added.
442     */
443    protected void _addHotKeys(JGraph jgraph) {
444    }
445
446    /** Create the controllers for nodes in this graph.
447     *  In this base class, nothing is created.
448     *  This is called by the constructor, so derived classes that
449     *  override this must be careful not to reference local variables
450     *  defined in the derived classes, because the derived classes
451     *  will not have been fully constructed by the time this is called.
452     */
453    protected void _createControllers() {
454    }
455
456    /** Return true if there are active highlights.
457     *  @return True if the list if error highlights is not empty.
458     */
459    protected boolean _areThereActiveErrorHighlights() {
460        return !_errorHighlights.isEmpty();
461    }
462
463    /**
464     * Return a change request that clears all the highlights.
465     * @return a change request that clears all the highlights.
466     */
467    protected ChangeRequest _getClearAllErrorHighlightsChangeRequest() {
468        ChangeRequest request = new ChangeRequest(this,
469                "Error Highlight Clearer", true) {
470            @Override
471            protected void _execute() throws Exception {
472                for (Attribute highlight : _errorHighlights) {
473                    highlight.setContainer(null);
474                }
475            }
476        };
477
478        // Mark the Error Highlight Clearer request as
479        // non-persistant so that we don't mark the model as being
480        // modified.  ptolemy/actor/lib/jni/test/Scale/Scale.xml
481        // required this change.
482        request.setPersistent(false);
483        return request;
484    }
485
486    /** Initialize interactions for the specified controller.  This
487     *  method is called when a new controller is constructed. This
488     *  base class does nothing, but derived classes may attach interactors
489     *  to the specified controller.
490     *  @param controller The controller for which to initialize interaction.
491     */
492    protected void _initializeInteraction(NamedObjController controller) {
493    }
494
495    // NOTE: The following method name does not have a leading underscore
496    // because it is a diva method.
497
498    /** Initialize all interaction on the graph pane. This method
499     *  is called by the setGraphPane() method of the superclass.
500     *  This initialization cannot be done in the constructor because
501     *  the controller does not yet have a reference to its pane
502     *  at that time.  Regrettably, the canvas is not yet associated
503     *  with the GraphPane, so you can't do any initialization that
504     *  involves the canvas.
505     */
506    @Override
507    protected void initializeInteraction() {
508        // Remove the existing menu if it has already been created by an earlier
509        // call of this method, because we may invoke this method multiple times
510        // but we don't want the same items to show up multiple times.
511        // -- tfeng (07/16/2009)
512        _menuFactory = null;
513
514        GraphPane pane = getGraphPane();
515        // Start Kepler code.
516        List configsList = Configuration.configurations();
517        Configuration config = _configuration;
518        if (config == null) {
519            // Is this really necessary?
520            for (Iterator it = configsList.iterator(); it.hasNext();) {
521                config = (Configuration) it.next();
522                if (config != null) {
523                    break;
524                }
525            }
526        }
527
528        // If a MenuFactory has been defined in the configuration, use this
529        // one; otherwise, use the default Ptolemy one:
530        if (config != null && _contextMenuFactoryCreator == null) {
531            _contextMenuFactoryCreator = (ContextMenuFactoryCreator) config
532                    .getAttribute("canvasContextMenuFactory");
533        }
534
535        // NOTE: by passing "this" to the menu factory, we are making it
536        // handle right-click menus for the canvas (only) - not the actors or
537        // relations; these are controlled by AttributeController and
538        // RelationController, respectively - MB - 2/14/06
539        if (_contextMenuFactoryCreator != null) {
540            try {
541                _menuFactory = (PtolemyMenuFactory) _contextMenuFactoryCreator
542                        .createContextMenuFactory(this);
543                // this is only done here, not for both MenuFactories, because
544                // SchematicContextMenuFactory already does this in its
545                // constructor:
546                // (Save _configureMenuFactory for use in sub classes)
547                _configureMenuFactory = new MenuActionFactory(_configureAction);
548                _menuFactory.addMenuItemFactory(_configureMenuFactory);
549            } catch (Throwable throwable) {
550                // do nothing - will default to ptii right-click menus
551                // System.out.println("Unable to use the alternative right-click menu "
552                // + "handler that was specified in the "
553                // + "configuration; defaulting to ptii handler. "
554                // + "Exception was: " + throwable);
555            }
556        }
557        // if the above has failed in any way, _menuFactory will still be null,
558        // in which case we should default to ptii context menus
559        if (_menuFactory == null) {
560            _menuFactory = new SchematicContextMenuFactory(this);
561        }
562        // End Kepler code.
563
564        _menuCreator = new MenuCreator(_menuFactory);
565        _menuCreator.setMouseFilter(new PopupMouseFilter());
566
567        // Note that the menuCreator cannot be an interactor, because
568        // it accepts all events.
569        // NOTE: The above is a very strange comment, since
570        // it is an interactor.  EAL 2/5/05.
571        pane.getBackgroundEventLayer().addInteractor(_menuCreator);
572        pane.getBackgroundEventLayer().setConsuming(false);
573
574        Action[] actions = { _getDocumentationAction,
575                new CustomizeDocumentationAction(),
576                new RemoveCustomDocumentationAction() };
577        _menuFactory.addMenuItemFactory(
578                new MenuActionFactory(actions, "Documentation"));
579
580        if (_configuration != null) {
581            // NOTE: The following requires that the configuration be
582            // non-null, or it will report an error.
583            _menuFactory.addMenuItemFactory(
584                    new MenuActionFactory(_openBaseClassAction));
585            _menuFactory.addMenuItemFactory(
586                    new MenuActionFactory(_unitSolverDialogAction));
587        }
588    }
589
590    ///////////////////////////////////////////////////////////////////
591    ////                         protected variables               ////
592
593    /** Currently animated state, if any. */
594    protected Figure _animated;
595
596    /** Renderer for animation. */
597    protected SelectionRenderer _animationRenderer;
598
599    /** The configure action. */
600    protected static ConfigureAction _configureAction = new ConfigureAction(
601            "Configure");
602
603    /** The submenu for configure actions. */
604    protected static MenuActionFactory _configureMenuFactory;
605
606    /** The interactor for creating context sensitive menus on the
607     *  graph itself.
608     */
609    protected MenuCreator _menuCreator;
610
611    /** The factory belonging to the menu creator. */
612    protected PtolemyMenuFactory _menuFactory;
613
614    /** The open base class action. */
615    protected OpenBaseClassAction _openBaseClassAction = new OpenBaseClassAction();
616
617    /** The UnitSolverDialog action. */
618    protected UnitSolverDialogAction _unitSolverDialogAction = new UnitSolverDialogAction();
619
620    ///////////////////////////////////////////////////////////////////
621    ////                         private methods                   ////
622
623    /** Add an error highlight color to the specified culprit if it is
624     *  not already present.
625     *  @param culprit The culprit to highlight.
626     *  @exception IllegalActionException If the highlight cannot be added.
627     *  @exception NameDuplicationException Should not be thrown.
628     */
629    private void _addErrorHighlightIfNeeded(Nameable culprit)
630            throws IllegalActionException, NameDuplicationException {
631        Attribute highlightColor = ((NamedObj) culprit)
632                .getAttribute("_highlightColor");
633        if (highlightColor == null) {
634            highlightColor = new ColorAttribute((NamedObj) culprit,
635                    "_highlightColor");
636            ((ColorAttribute) highlightColor)
637                    .setExpression("{1.0, 0.0, 0.0, 1.0}");
638            highlightColor.setPersistent(false);
639            ((ColorAttribute) highlightColor).setVisibility(Settable.EXPERT);
640            _errorHighlights.add(highlightColor);
641        }
642    }
643
644    ///////////////////////////////////////////////////////////////////
645    ////                         private variables                 ////
646
647    /** The time to sleep upon animation. */
648    private long _animationDelay = 0L;
649
650    // The configuration.
651    private Configuration _configuration;
652
653    /**
654     * A configurable object that allows a different MenuFactory to be specified
655     * instead of the default ptII one. The MenuFactory constructs the
656     * right-click context menus
657     */
658    private static ContextMenuFactoryCreator _contextMenuFactoryCreator;
659
660    /** List of error highlight attributes we have created. */
661    private List<Attribute> _errorHighlights = new LinkedList<Attribute>();
662
663    // The get documentation action.
664    private GetDocumentationAction _getDocumentationAction = new GetDocumentationAction();
665
666    // The graph frame, if there is one.
667    private BasicGraphFrame _frame;
668
669    // Flag to prevent double rendering upon setting location.
670    private boolean _inValueChanged = false;
671
672    ///////////////////////////////////////////////////////////////////
673    ////                         inner classes                     ////
674    ///////////////////////////////////////////////////////////////////
675    //// OpenBaseClassAction
676
677    /** An action that will open the base class of a subclass or the class
678     *  of an instance.
679     */
680    @SuppressWarnings("serial")
681    public class OpenBaseClassAction extends FigureAction {
682        /** Construct a new action.
683         */
684        public OpenBaseClassAction() {
685            super("Open Base Class");
686        }
687
688        ///////////////////////////////////////////////////////////////////
689        ////                         public methods                    ////
690
691        /** Open the base class of a subclass or the class of an instance.
692         *  @param e The event.
693         */
694        @Override
695        public void actionPerformed(ActionEvent e) {
696            if (_configuration == null) {
697                MessageHandler.error(
698                        "Cannot open base class without a configuration.");
699                return;
700            }
701
702            // Determine which entity was selected for the look inside action.
703            super.actionPerformed(e);
704
705            NamedObj target = getTarget();
706
707            if (target == null) {
708                return;
709            }
710
711            try {
712                if (target instanceof InstantiableNamedObj) {
713                    InstantiableNamedObj deferTo = (InstantiableNamedObj) ((InstantiableNamedObj) target)
714                            .getParent();
715
716                    if (deferTo != null) {
717                        _configuration.openModel(deferTo);
718                        return;
719                    }
720                }
721
722                String source = target.getSource();
723
724                if (source != null && !source.trim().equals("")) {
725                    // FIXME: Is there a more reasonable base directory
726                    // to give for the second argument?
727                    URL sourceURL = FileUtilities.nameToURL(source, null,
728                            target.getClass().getClassLoader());
729                    _configuration.openModel(null, sourceURL, source);
730                    return;
731                }
732
733                // Target does not defer and does not have a defined "source".
734                // Assume its base class is a Java class and open the source
735                // code.
736                String sourceFileName = StringUtilities
737                        .objectToSourceFileName(target);
738                URL sourceURL = target.getClass().getClassLoader()
739                        .getResource(sourceFileName);
740                _configuration.openModel(null, sourceURL,
741                        sourceURL.toExternalForm());
742            } catch (Exception ex) {
743                MessageHandler.error("Open base class failed.", ex);
744            }
745        }
746    }
747
748    ///////////////////////////////////////////////////////////////////
749    //// UnitSolverDialogAction
750
751    /** An action that will create a UnitSolverDialog.
752     */
753    @SuppressWarnings("serial")
754    public class UnitSolverDialogAction extends AbstractAction {
755        /** Construct an action that will create a UnitSolverDialog.
756         */
757        public UnitSolverDialogAction() {
758            super("UnitConstraints Solver");
759        }
760
761        /** Construct a UnitSolverDialog.
762         *  @param e The action event, ignored by this method.
763         */
764        @Override
765        public void actionPerformed(ActionEvent e) {
766            // Only makes sense if this is an ActorGraphFrame.
767            if (_frame instanceof ActorGraphFrame) {
768                DialogTableau dialogTableau = DialogTableau.createDialog(_frame,
769                        _configuration, ((ActorGraphFrame) _frame).getEffigy(),
770                        UnitSolverDialog.class,
771                        (Entity) ((ActorGraphFrame) _frame).getModel());
772
773                if (dialogTableau != null) {
774                    dialogTableau.show();
775                }
776            }
777        }
778    }
779
780    ///////////////////////////////////////////////////////////////////
781    //// SchematicContextMenuFactory
782
783    /** Factory for context menus. */
784    public static class SchematicContextMenuFactory extends PtolemyMenuFactory {
785        /** Create a new context menu factory associated with the
786         *  specified controller.
787         *  @param controller The controller.
788         */
789        public SchematicContextMenuFactory(GraphController controller) {
790            super(controller);
791            _configureMenuFactory = new MenuActionFactory(_configureAction);
792            addMenuItemFactory(_configureMenuFactory);
793        }
794
795        @Override
796        protected NamedObj _getObjectFromFigure(Figure source) {
797            // NOTE: Between Ptolemy 3.0 and 5.0, this would ignore
798            // the source argument, even if it was non-null.  Why?
799            // EAL 2/5/05.
800            if (source != null) {
801                Object object = source.getUserObject();
802                return (NamedObj) getController().getGraphModel()
803                        .getSemanticObject(object);
804            } else {
805                return (NamedObj) getController().getGraphModel().getRoot();
806            }
807        }
808    }
809}