001/* The graph controller for the vergil viewer
002
003 Copyright (c) 1999-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.Color;
031import java.awt.Font;
032import java.awt.geom.AffineTransform;
033import java.awt.geom.Rectangle2D;
034import java.util.Enumeration;
035import java.util.Iterator;
036import java.util.List;
037import java.util.Vector;
038
039import javax.swing.SwingConstants;
040
041import diva.canvas.CompositeFigure;
042import diva.canvas.Figure;
043import diva.canvas.interactor.SelectionDragger;
044import diva.canvas.toolbox.LabelFigure;
045import diva.graph.EdgeController;
046import diva.graph.GraphModel;
047import diva.graph.GraphPane;
048import diva.graph.JGraph;
049import diva.graph.NodeController;
050import diva.graph.basic.BasicLayoutTarget;
051import diva.graph.layout.AbstractGlobalLayout;
052import ptolemy.actor.Actor;
053import ptolemy.actor.FiringEvent;
054import ptolemy.actor.IOPort;
055import ptolemy.actor.gui.Configuration;
056import ptolemy.data.BooleanToken;
057import ptolemy.data.Token;
058import ptolemy.data.expr.Parameter;
059import ptolemy.data.expr.StringParameter;
060import ptolemy.data.expr.Variable;
061import ptolemy.kernel.Entity;
062import ptolemy.kernel.Port;
063import ptolemy.kernel.util.Attribute;
064import ptolemy.kernel.util.DebugEvent;
065import ptolemy.kernel.util.IllegalActionException;
066import ptolemy.kernel.util.InternalErrorException;
067import ptolemy.kernel.util.KernelException;
068import ptolemy.kernel.util.Locatable;
069import ptolemy.kernel.util.NamedObj;
070import ptolemy.kernel.util.Settable;
071import ptolemy.kernel.util.StringAttribute;
072import ptolemy.moml.Vertex;
073import ptolemy.util.MessageHandler;
074import ptolemy.vergil.basic.AbstractBasicGraphModel;
075import ptolemy.vergil.basic.LocatableNodeController;
076import ptolemy.vergil.basic.NamedObjController;
077import ptolemy.vergil.basic.RunnableGraphController;
078import ptolemy.vergil.kernel.AnimationRenderer;
079import ptolemy.vergil.kernel.AttributeController;
080import ptolemy.vergil.kernel.RelationController;
081import ptolemy.vergil.toolbox.PortSite;
082
083///////////////////////////////////////////////////////////////////
084//// ActorViewerGraphController
085
086/**
087 A graph controller for the Ptolemy II schematic viewer.
088 This controller contains a set of default node controllers for attributes,
089 entities, links, ports, and relations.  Those default controllers can
090 be overridden by attributes of type NodeControllerFactory.
091 The getNodeController() method determines which controller to return
092 for each node.
093 <p>
094 In addition, this controller provides graph-wide operations that allow
095 nodes to be moved and context menus to be created.  It does
096 not provide interaction for adding or removing nodes; those are provided
097 by a derived class.  If does provide toolbar buttons for executing
098 the model (or if this is not the top level, delegating to the top
099 level to execute). Right-clicking on the background will
100 create a context-sensitive menu for the graph.
101
102 @author Steve Neuendorffer, Contributor: Edward A. Lee
103 @version $Id$
104 @since Ptolemy II 2.0
105 @Pt.ProposedRating Red (eal)
106 @Pt.AcceptedRating Red (johnr)
107 */
108public class ActorViewerGraphController extends RunnableGraphController {
109    /** Create a new basic controller with default
110     *  terminal and edge interactors and default context menus.
111     */
112    public ActorViewerGraphController() {
113        _createControllers();
114
115        // The following is a fallback controller used when encountering
116        // instances of Locatable that have no container. Do not create
117        // this in _createControllers() because that is overridden by
118        // derived classes.
119        _locatableController = new LocatableNodeController(this);
120    }
121
122    ///////////////////////////////////////////////////////////////////
123    ////                         public methods                    ////
124
125    /** React to an event by highlighting the actor being iterated.
126     *  This effectively animates the execution.
127     *  @param event The debug event.
128     */
129    @Override
130    public void event(DebugEvent event) {
131        if (event instanceof FiringEvent) {
132            Actor actor = ((FiringEvent) event).getActor();
133
134            if (actor instanceof NamedObj) {
135                NamedObj objToHighlight = (NamedObj) actor;
136
137                // If the object is not contained by the associated
138                // composite, then find an object above it in the hierarchy
139                // that is.
140                AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) getGraphModel();
141                NamedObj toplevel = graphModel.getPtolemyModel();
142
143                while (objToHighlight != null
144                        && objToHighlight.getContainer() != toplevel) {
145                    objToHighlight = objToHighlight.getContainer();
146                }
147
148                if (objToHighlight == null) {
149                    return;
150                }
151
152                Object location = objToHighlight.getAttribute("_location");
153
154                if (location != null) {
155                    Figure figure = getFigure(location);
156
157                    if (figure != null) {
158                        if (_animationRenderer == null) {
159                            _animationRenderer = new AnimationRenderer();
160                        }
161
162                        FiringEvent.FiringEventType type = ((FiringEvent) event)
163                                .getType();
164
165                        if (type == FiringEvent.BEFORE_ITERATE
166                                || type == FiringEvent.BEFORE_FIRE) {
167                            _animationRenderer.renderSelected(figure);
168                            _animated = figure;
169
170                            long animationDelay = getAnimationDelay();
171
172                            if (animationDelay > 0) {
173                                try {
174                                    Thread.sleep(animationDelay);
175                                } catch (InterruptedException ex) {
176                                }
177                            }
178                        } else if (type == FiringEvent.AFTER_ITERATE
179                                || type == FiringEvent.AFTER_POSTFIRE) {
180                            if (_animated != null) {
181                                _animationRenderer.renderDeselected(_animated);
182                            }
183                        }
184                    }
185                }
186            }
187        }
188    }
189
190    /** Return the edge controller appropriate for the given node,
191     *  which in this case is the same link controller returned by
192     *  getLinkController().
193     *  @param edge The edge object.
194     *  @return the edge controller.
195     */
196    @Override
197    public EdgeController getEdgeController(Object edge) {
198        return _linkController;
199    }
200
201    /** Return the value of the entity controller.
202     *  Callers may add context menus by calling
203     *  <pre>
204     *  getEntityController.addMenuItemFactory(new XXXDialogFactory);
205     *  </pre>
206     *  @return the entity controller
207     */
208    public ActorController getEntityController() {
209        // Used by jni.ThalesGraphFrame to add jni.ArgumentDialogFactory
210        return _entityController;
211    }
212
213    /** Return the node controller appropriate for the given object.
214     *  If the object is an instance of Vertex, then return the
215     *  local relation controller.  If it implements Locatable,
216     *  then determine whether it is an Entity, Attribute, or Port,
217     *  and return the appropriate default controller.
218     *  If the argument is an instance of Port, then return the
219     *  local port controller.
220     *  @param object A Vertex, Locatable, or Port.
221     *  @return the node controller
222     */
223    @Override
224    public NodeController getNodeController(Object object) {
225        // Defer to the superclass if it can provide a controller.
226        NodeController result = super.getNodeController(object);
227
228        if (result != null) {
229            return result;
230        }
231
232        // Superclass cannot provide a controller. Use defaults.
233        if (object instanceof Vertex) {
234            return _relationController;
235        } else if (object instanceof Locatable) {
236            Object semanticObject = getGraphModel().getSemanticObject(object);
237
238            if (semanticObject instanceof Entity) {
239
240                boolean isActorOfInterest = false;
241
242                try {
243                    // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration?
244                    Configuration configuration = getConfiguration();
245                    if (configuration != null) {
246                        StringParameter actorInteractionAddonParameter;
247                        actorInteractionAddonParameter = (StringParameter) configuration
248                                .getAttribute("_actorInteractionAddon",
249                                        Parameter.class);
250
251                        if (actorInteractionAddonParameter != null) {
252                            String actorInteractionAddonClassName = actorInteractionAddonParameter
253                                    .stringValue();
254
255                            Class actorInteractionAddonClass = Class
256                                    .forName(actorInteractionAddonClassName);
257
258                            ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass
259                                    .newInstance();
260
261                            isActorOfInterest = actorInteractionAddon
262                                    .isActorOfInterestForAddonController(
263                                            (NamedObj) semanticObject);
264                        }
265                    }
266                } catch (Exception e) {
267                    e.printStackTrace();
268                }
269
270                // In the viewer, there will not be a class definition
271                // controller that is distinct from the entity controller.
272                // In the edit, there will be.
273                if (_classDefinitionController != null
274                        && ((Entity) semanticObject).isClassDefinition()) {
275                    return _classDefinitionController;
276                } else if (isActorOfInterest) {
277                    return _addonActorController;
278                } else {
279                    return _entityController;
280                }
281            } else if (semanticObject instanceof Attribute) {
282                return _attributeController;
283            } else if (semanticObject instanceof Port) {
284                return _portController;
285            } else {
286                return _locatableController;
287            }
288        } else if (object instanceof Port) {
289            return _entityPortController;
290        }
291
292        throw new RuntimeException(
293                "Node with unknown semantic object: " + object);
294    }
295
296    /** Set the configuration.  The configuration is used when
297     *  opening documentation files.
298     *  @param configuration The configuration.
299     */
300    @Override
301    public void setConfiguration(Configuration configuration) {
302        super.setConfiguration(configuration);
303        _attributeController.setConfiguration(configuration);
304        _classDefinitionController.setConfiguration(configuration);
305        _entityController.setConfiguration(configuration);
306        _entityPortController.setConfiguration(configuration);
307        _relationController.setConfiguration(configuration);
308        _linkController.setConfiguration(configuration);
309
310        try {
311            // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration?
312            if (configuration != null) {
313                StringParameter actorInteractionAddon;
314                actorInteractionAddon = (StringParameter) configuration
315                        .getAttribute("_actorInteractionAddon",
316                                Parameter.class);
317
318                if (actorInteractionAddon != null) {
319                    _addonActorController.setConfiguration(configuration);
320                }
321            }
322        } catch (Exception e) {
323            e.printStackTrace();
324        }
325    }
326
327    ///////////////////////////////////////////////////////////////////
328    ////                         protected methods                 ////
329
330    /** Add hot keys to the actions in the given JGraph.
331     *
332     *  @param jgraph The JGraph to which hot keys are to be added.
333     */
334    @Override
335    protected void _addHotKeys(JGraph jgraph) {
336        super._addHotKeys(jgraph);
337        _entityController.addHotKeys(jgraph);
338        _classDefinitionController.addHotKeys(jgraph);
339        _attributeController.addHotKeys(jgraph);
340
341        try {
342            // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration?
343            Configuration configuration = getConfiguration();
344            if (configuration != null) {
345                StringParameter actorInteractionAddon;
346                actorInteractionAddon = (StringParameter) configuration
347                        .getAttribute("_actorInteractionAddon",
348                                Parameter.class);
349
350                if (actorInteractionAddon != null) {
351                    _addonActorController.addHotKeys(jgraph);
352                }
353            }
354        } catch (Exception e) {
355            e.printStackTrace();
356        }
357    }
358
359    /** Create the controllers for nodes in this graph.
360     *  In this base class, controllers with PARTIAL access are created.
361     *  This is called by the constructor, so derived classes that
362     *  override this must be careful not to reference local variables
363     *  defined in the derived classes, because the derived classes
364     *  will not have been fully constructed by the time this is called.
365     */
366    @Override
367    protected void _createControllers() {
368        super._createControllers();
369        _attributeController = new AttributeController(this,
370                AttributeController.PARTIAL);
371
372        // NOTE: Use an ordinary ActorController rather than
373        // ClassDefinitionController because access is only PARTIAL.
374        _classDefinitionController = new ClassDefinitionController(this,
375                AttributeController.PARTIAL);
376
377        try {
378
379            // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration?
380            Configuration configuration = getConfiguration();
381            if (configuration != null) {
382                StringParameter actorInteractionAddonParameter;
383                actorInteractionAddonParameter = (StringParameter) configuration
384                        .getAttribute("_actorInteractionAddon",
385                                Parameter.class);
386
387                if (actorInteractionAddonParameter != null) {
388                    String actorInteractionAddonClassName = actorInteractionAddonParameter
389                            .stringValue();
390                    Class actorInteractionAddonClass = Class
391                            .forName(actorInteractionAddonClassName);
392
393                    ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass
394                            .newInstance();
395
396                    _addonActorController = actorInteractionAddon
397                            .getControllerInstance(this, false);
398                }
399            }
400        } catch (Exception e) {
401            e.printStackTrace();
402        }
403
404        _entityController = new ActorInstanceController(this,
405                AttributeController.PARTIAL);
406        _entityPortController = new IOPortController(this,
407                AttributeController.PARTIAL);
408        _relationController = new RelationController(this);
409        _linkController = new LinkController(this);
410    }
411
412    /** Initialize all interaction on the graph pane. This method
413     *  is called by the setGraphPane() method of the superclass.
414     *  This initialization cannot be done in the constructor because
415     *  the controller does not yet have a reference to its pane
416     *  at that time.
417     */
418    @Override
419    protected void initializeInteraction() {
420        GraphPane pane = getGraphPane();
421
422        // Create and set up the selection dragger
423        _selectionDragger = new SelectionDragger(pane);
424        _selectionDragger.addSelectionModel(getSelectionModel());
425
426        // If the selectionDragger is consuming, then popup menus don't
427        // disappear properly.
428        _selectionDragger.setConsuming(false);
429
430        super.initializeInteraction();
431    }
432
433    ///////////////////////////////////////////////////////////////////
434    ////                         protected variables               ////
435
436    /** The attribute controller. */
437    protected NamedObjController _attributeController;
438
439    /** The class definition controller. */
440    protected ActorController _classDefinitionController;
441
442    /** The controller for actors with addon gui behavior. */
443    protected ActorController _addonActorController;
444
445    /** The entity controller. */
446    protected ActorController _entityController;
447
448    /** The entity port controller. */
449    protected NamedObjController _entityPortController;
450
451    /** The link controller. */
452    protected LinkController _linkController;
453
454    /** The relation controller. */
455    protected NamedObjController _relationController;
456
457    ///////////////////////////////////////////////////////////////////
458    ////                         private variables                 ////
459
460    /** The default controller, used only for instances of Locatable
461     *  that have no container.
462     */
463    private LocatableNodeController _locatableController;
464
465    /** The selection interactor for drag-selecting nodes. */
466    private SelectionDragger _selectionDragger;
467
468    /** Font for port labels. */
469    private static Font _portLabelFont = new Font("SansSerif", Font.PLAIN, 8);
470
471    ///////////////////////////////////////////////////////////////////
472    ////                         inner classes                     ////
473
474    ///////////////////////////////////////////////////////////////////
475    //// EntityLayout
476
477    /**
478     * This layout algorithm is responsible for laying out the ports within an
479     * entity.
480     */
481    public class EntityLayout extends AbstractGlobalLayout {
482        /** Create a new layout manager. */
483        public EntityLayout() {
484            super(new BasicLayoutTarget(ActorViewerGraphController.this));
485        }
486
487        ///////////////////////////////////////////////////////////////
488        ////                     public methods                    ////
489
490        /**
491         * Layout the ports of the specified node.
492         *
493         * @param node
494         *            The node, which is assumed to be an entity.
495         */
496        @Override
497        public void layout(Object node) {
498            GraphModel model = ActorViewerGraphController.this.getGraphModel();
499
500            // System.out.println("layout = " + node);
501            // new Exception().printStackTrace();
502            Iterator nodes = model.nodes(node);
503            Vector westPorts = new Vector();
504            Vector eastPorts = new Vector();
505            Vector southPorts = new Vector();
506            Vector northPorts = new Vector();
507
508            while (nodes.hasNext()) {
509                Port port = (Port) nodes.next();
510                // Skip the port if it is hidden.
511                if (_isHidden(port)) {
512                    continue;
513                }
514                int portRotation = IOPortController.getCardinality(port);
515                int direction = IOPortController.getDirection(portRotation);
516                if (direction == SwingConstants.WEST) {
517                    westPorts.add(port);
518                } else if (direction == SwingConstants.NORTH) {
519                    northPorts.add(port);
520                } else if (direction == SwingConstants.EAST) {
521                    eastPorts.add(port);
522                } else {
523                    southPorts.add(port);
524                }
525            }
526
527            CompositeFigure figure = (CompositeFigure) getLayoutTarget()
528                    .getVisualObject(node);
529
530            _reOrderPorts(westPorts);
531            _placePortFigures(figure, westPorts, SwingConstants.WEST);
532            _reOrderPorts(eastPorts);
533            _placePortFigures(figure, eastPorts, SwingConstants.EAST);
534            _reOrderPorts(southPorts);
535            _placePortFigures(figure, southPorts, SwingConstants.SOUTH);
536            _reOrderPorts(northPorts);
537            _placePortFigures(figure, northPorts, SwingConstants.NORTH);
538        }
539
540        ///////////////////////////////////////////////////////////////
541        ////                     private methods                   ////
542
543        private LabelFigure _createPortLabelFigure(String string, Font font,
544                double x, double y, int direction) {
545            LabelFigure label;
546
547            if (direction == SwingConstants.SOUTH) {
548                // The 1.0 argument is the padding.
549                label = new LabelFigure(string, font, 1.0,
550                        SwingConstants.SOUTH_WEST);
551
552                // Shift the label down so it doesn't
553                // collide with ports.
554                label.translateTo(x, y + 5);
555
556                // Rotate the label.
557                AffineTransform rotate = AffineTransform
558                        .getRotateInstance(Math.PI / 2.0, x, y + 5);
559                label.transform(rotate);
560            } else if (direction == SwingConstants.EAST) {
561                // The 1.0 argument is the padding.
562                label = new LabelFigure(string, font, 1.0,
563                        SwingConstants.SOUTH_WEST);
564
565                // Shift the label right so it doesn't
566                // collide with ports.
567                label.translateTo(x + 5, y);
568            } else if (direction == SwingConstants.WEST) {
569                // The 1.0 argument is the padding.
570                label = new LabelFigure(string, font, 1.0,
571                        SwingConstants.SOUTH_EAST);
572
573                // Shift the label left so it doesn't
574                // collide with ports.
575                label.translateTo(x - 5, y);
576            } else { // Must be north.
577
578                // The 1.0 argument is the padding.
579                label = new LabelFigure(string, font, 1.0,
580                        SwingConstants.SOUTH_WEST);
581
582                // Shift the label right so it doesn't
583                // collide with ports. It will probably
584                // collide with the actor name.
585                label.translateTo(x, y - 5);
586
587                // Rotate the label.
588                AffineTransform rotate = AffineTransform
589                        .getRotateInstance(-Math.PI / 2.0, x, y - 5);
590                label.transform(rotate);
591            }
592
593            return label;
594        }
595
596        /** Return true if a property named "_hide" is set for
597         *  the specified object. A property is specified if the specified
598         *  object contains an attribute with the specified name and that
599         *  attribute is either not a boolean-valued parameter, or it is
600         *  a boolean-valued parameter with value true.
601         *  @param object The object.
602         *  @return True if the property is set.
603         */
604        private boolean _isHidden(NamedObj object) {
605            Attribute attribute = object.getAttribute("_hide");
606            if (attribute == null) {
607                return false;
608            }
609            if (attribute instanceof Parameter) {
610                try {
611                    Token token = ((Parameter) attribute).getToken();
612
613                    if (token instanceof BooleanToken) {
614                        if (!((BooleanToken) token).booleanValue()) {
615                            return false;
616                        }
617                    }
618                } catch (IllegalActionException e) {
619                    // Ignore, using default of true.
620                }
621            }
622            return true;
623        }
624
625        // re-order the ports according to _ordinal property
626        private void _reOrderPorts(Vector ports) {
627            int size = ports.size();
628            Enumeration enumeration = ports.elements();
629            Port port;
630            StringAttribute ordinal = null;
631            int number = 0;
632            int index = 0;
633
634            while (enumeration.hasMoreElements()) {
635                port = (Port) enumeration.nextElement();
636                ordinal = (StringAttribute) port.getAttribute("_ordinal");
637
638                if (ordinal != null) {
639                    number = Integer.parseInt(ordinal.getExpression());
640
641                    if (number >= size) {
642                        ports.remove(index);
643
644                        try {
645                            ordinal.setExpression(Integer.toString(size - 1));
646                        } catch (Exception e) {
647                            MessageHandler
648                                    .error("Error setting ordinal property", e);
649                        }
650
651                        ports.add(port);
652                    } else if (number < 0) {
653                        ports.remove(index);
654
655                        try {
656                            ordinal.setExpression(Integer.toString(0));
657                        } catch (Exception e) {
658                            MessageHandler
659                                    .error("Error setting ordinal property", e);
660                        }
661
662                        ports.add(0, port);
663                    } else if (number != index) {
664                        ports.remove(index);
665                        ports.add(number, port);
666                    }
667                }
668
669                index++;
670            }
671        }
672
673        // Place the ports.
674        private void _placePortFigures(CompositeFigure figure, List portList,
675                int direction) {
676            Iterator ports = portList.iterator();
677            int number = 0;
678            // Don't count ports that are hidden and not connected. (Sven Koehler)
679            // "Make hidden, unconnected ports not be rendered on the
680            // canvas and therefore would not displace other ports on
681            // an actor."
682            // This is used by Kepler.
683            int count = 0;
684            for (Object p : portList) {
685                Port port = (Port) p;
686                Attribute portHide = port.getAttribute("_hide");
687                try {
688                    if (!(portHide != null && portHide instanceof Variable
689                            && ((Variable) portHide).getToken()
690                                    .equals(BooleanToken.TRUE)
691                            && port.linkedRelationList().isEmpty())) {
692                        count++;
693                    }
694                } catch (IllegalActionException ex) {
695                    count = portList.size();
696                }
697            }
698            Figure background = figure.getBackgroundFigure();
699
700            if (background == null) {
701                // This could occur if the icon has a _hide parameter.
702                background = figure;
703            }
704
705            while (ports.hasNext()) {
706                Port port = (Port) ports.next();
707                Figure portFigure = ActorViewerGraphController.this
708                        .getFigure(port);
709                // If there is no figure, then ignore this port. This may
710                // happen if the port hasn't been rendered yet.
711                if (portFigure == null) {
712                    continue;
713                }
714
715                Attribute portHide = port.getAttribute("_hide");
716                // Skip ports that are hidden and not connected (Sven Koehler)
717                try {
718                    if (portHide != null && portHide instanceof Variable
719                            && ((Variable) portHide).getToken()
720                                    .equals(BooleanToken.TRUE)
721                            && port.linkedRelationList().isEmpty()) {
722                        continue;
723                    }
724                } catch (IllegalActionException ex) {
725                    throw new InternalErrorException(ex);
726                }
727
728                Rectangle2D portBounds = portFigure.getShape().getBounds2D();
729                PortSite site = new PortSite(background, port, number, count,
730                        direction);
731                number++;
732
733                // NOTE: previous expression for port location was:
734                // 100.0 * number / (count+1)
735                // But this leads to squished ports with uneven spacing.
736                // Note that we don't use CanvasUtilities.translateTo because
737                // we want to only get the bounds of the background of the
738                // port figure.
739                double x = site.getX() - portBounds.getCenterX();
740                double y = site.getY() - portBounds.getCenterY();
741                portFigure.translate(x, y);
742
743                // If the actor contains a variable named "_showRate",
744                // with value true, then visualize the rate information.
745                // NOTE: Showing rates only makes sense for IOPorts.
746                Attribute showRateAttribute = port.getAttribute("_showRate");
747
748                if (port instanceof IOPort
749                        && showRateAttribute instanceof Variable) {
750                    boolean showRate = false;
751
752                    try {
753                        showRate = ((Variable) showRateAttribute).getToken()
754                                .equals(BooleanToken.TRUE);
755                    } catch (Exception ex) {
756                        // Ignore.
757                        showRate = false;
758                    }
759
760                    if (showRate) {
761                        // Infer the rate. See DFUtilities.
762                        String rateString = "";
763                        Variable rateParameter = null;
764
765                        if (((IOPort) port).isInput()) {
766                            rateParameter = (Variable) port
767                                    .getAttribute("tokenConsumptionRate");
768
769                            if (rateParameter == null) {
770                                String altName = "_tokenConsumptionRate";
771                                rateParameter = (Variable) port
772                                        .getAttribute(altName);
773                            }
774                        } else if (((IOPort) port).isOutput()) {
775                            rateParameter = (Variable) port
776                                    .getAttribute("tokenProductionRate");
777
778                            if (rateParameter == null) {
779                                String altName = "_tokenProductionRate";
780                                rateParameter = (Variable) port
781                                        .getAttribute(altName);
782                            }
783                        }
784
785                        if (rateParameter != null) {
786                            try {
787                                rateString = rateParameter.getToken()
788                                        .toString();
789                            } catch (KernelException ex) {
790                                // Ignore.
791                            }
792                        }
793
794                        LabelFigure labelFigure = _createPortLabelFigure(
795                                rateString, _portLabelFont, x, y, direction);
796                        labelFigure.setFillPaint(Color.BLUE);
797                        figure.add(labelFigure);
798                    }
799                }
800
801                // If the port contains an attribute named "_showName",
802                // then render the name of the port as well. If the
803                // attribute is a boolean-valued parameter, then
804                // show the name only if the value is true.
805                Attribute showAttribute = port.getAttribute("_showName");
806                String toShow = null;
807                if (showAttribute != null) {
808                    boolean show = true;
809
810                    if (showAttribute instanceof Parameter) {
811                        try {
812                            Token token = ((Parameter) showAttribute)
813                                    .getToken();
814
815                            if (token instanceof BooleanToken) {
816                                show = ((BooleanToken) token).booleanValue();
817                            }
818                        } catch (IllegalActionException e) {
819                            // Ignore. Presence of the attribute will prevail.
820                        }
821                    }
822
823                    if (show) {
824                        toShow = port.getDisplayName();
825                    }
826                }
827                // In addition, if the port contains an attribute
828                // called "_showInfo", then if that attribute is
829                // a variable, then its value is shown. Otherwise,
830                // if it is a Settable, then its expression is shown.
831                Attribute showInfo = port.getAttribute("_showInfo");
832                try {
833                    if (showInfo instanceof Variable
834                            && !((Variable) showInfo).isStringMode()) {
835                        String value = ((Variable) showInfo).getToken()
836                                .toString();
837                        if (toShow != null && !value.trim().equals("")) {
838                            toShow += " (" + value + ")";
839                        } else {
840                            toShow = value;
841                        }
842                    } else if (showInfo instanceof Settable) {
843                        String value = ((Settable) showInfo).getExpression();
844                        if (toShow != null && !value.trim().equals("")) {
845                            toShow += " (" + value + ")";
846                        } else {
847                            toShow = ((Settable) showInfo).getExpression();
848                        }
849                    }
850                } catch (IllegalActionException e) {
851                    if (toShow == null) {
852                        toShow = e.getMessage();
853                    } else {
854                        toShow += e.getMessage();
855                    }
856                }
857
858                // Finally, if the port is an IOPort and it has a defaultValue,
859                // show that value. If there is already text to show, the insert
860                // a colon before the value.
861                if (port instanceof IOPort) {
862                    try {
863                        Token defaultValue = ((IOPort) port).defaultValue
864                                .getToken();
865                        if (defaultValue != null) {
866                            if (toShow == null) {
867                                toShow = defaultValue.toString();
868                            } else {
869                                toShow += ": " + defaultValue.toString();
870                            }
871                        }
872                    } catch (IllegalActionException e) {
873                        if (toShow == null) {
874                            toShow = e.getMessage();
875                        } else {
876                            toShow += e.getMessage();
877                        }
878                    }
879                }
880
881                if (toShow != null) {
882                    LabelFigure labelFigure = _createPortLabelFigure(toShow,
883                            _portLabelFont, x, y, direction);
884                    figure.add(labelFigure);
885                }
886            }
887        }
888    }
889}