001/* The node controller for relations (and vertices)
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.kernel;
029
030import java.awt.Color;
031import java.awt.Font;
032import java.awt.geom.Line2D;
033import java.util.List;
034
035import javax.swing.SwingConstants;
036
037import diva.canvas.CompositeFigure;
038import diva.canvas.Figure;
039import diva.canvas.toolbox.BasicFigure;
040import diva.canvas.toolbox.LabelFigure;
041import diva.canvas.toolbox.SVGUtilities;
042import diva.graph.GraphController;
043import diva.graph.NodeRenderer;
044import diva.util.java2d.Polygon2D;
045import ptolemy.actor.IORelation;
046import ptolemy.actor.gui.ColorAttribute;
047import ptolemy.actor.gui.Configuration;
048import ptolemy.actor.gui.PtolemyPreferences;
049import ptolemy.data.DoubleToken;
050import ptolemy.data.Token;
051import ptolemy.kernel.Relation;
052import ptolemy.kernel.util.IllegalActionException;
053import ptolemy.kernel.util.InternalErrorException;
054import ptolemy.kernel.util.StringAttribute;
055import ptolemy.moml.Vertex;
056import ptolemy.vergil.actor.ActorGraphModel;
057import ptolemy.vergil.basic.GetDocumentationAction;
058import ptolemy.vergil.basic.ParameterizedNodeController;
059import ptolemy.vergil.toolbox.MenuActionFactory;
060
061///////////////////////////////////////////////////////////////////
062//// RelationController
063
064/**
065 This class provides interaction with nodes that represent Ptolemy II
066 relations.  It provides a double click binding to edit the parameters
067 of the relation, and a context menu containing a command to edit parameters
068 ("Configure"), and a command to get documentation.
069
070 @author Steve Neuendorffer and Edward A. Lee
071 @version $Id$
072 @since Ptolemy II 2.0
073 @Pt.ProposedRating Red (eal)
074 @Pt.AcceptedRating Red (johnr)
075 */
076public class RelationController extends ParameterizedNodeController {
077    /** Create a relation controller associated with the specified graph
078     *  controller.
079     *  @param controller The associated graph controller.
080     */
081    public RelationController(GraphController controller) {
082        super(controller);
083        setNodeRenderer(new RelationRenderer());
084        _getDocumentationAction = new GetDocumentationAction();
085
086        // Add to the context menu.
087        _menuFactory.addMenuItemFactory(
088                new MenuActionFactory(_getDocumentationAction));
089    }
090
091    ///////////////////////////////////////////////////////////////////
092    ////                         public methods                    ////
093
094    /** Set the configuration.  This is used in derived classes to
095     *  to open files (such as documentation).  The configuration is
096     *  is important because it keeps track of which files are already
097     *  open and ensures that there is only one editor operating on the
098     *  file at any one time.
099     *  @param configuration The configuration.
100     */
101    @Override
102    public void setConfiguration(Configuration configuration) {
103        super.setConfiguration(configuration);
104        _getDocumentationAction.setConfiguration(configuration);
105    }
106
107    ///////////////////////////////////////////////////////////////////
108    ////                     private members                       ////
109
110    /** The "get documentation" action. */
111    private GetDocumentationAction _getDocumentationAction;
112
113    /** The label font. */
114    private static Font _relationLabelFont = new Font("SansSerif", Font.PLAIN,
115            10);
116
117    ///////////////////////////////////////////////////////////////////
118    ////                         inner classes                     ////
119
120    /** The renderer for relation node.  This class creates a Figure that
121     *  looks like a black diamond.
122     */
123    public class RelationRenderer implements NodeRenderer {
124        /**
125         * Render a visual representation of the given node.
126         * @param node The node to render.
127         * @return The persistent object that is drawn on the screen.
128         */
129        @Override
130        public Figure render(Object node) {
131            // Default values.
132            double height = 12.0;
133            double width = 12.0;
134
135            Relation relation = null;
136
137            if (node != null) {
138                Vertex vertex = (Vertex) node;
139                relation = (Relation) vertex.getContainer();
140
141                // NOTE: The preferences mechanism may set this.
142                Token relationSize = PtolemyPreferences
143                        .preferenceValue(relation, "_relationSize");
144
145                if (relationSize instanceof DoubleToken) {
146                    height = ((DoubleToken) relationSize).doubleValue();
147                    width = height;
148                }
149            }
150
151            Polygon2D.Double polygon = new Polygon2D.Double();
152            polygon.moveTo(width / 2, 0);
153            polygon.lineTo(0, height / 2);
154            polygon.lineTo(-width / 2, 0);
155            polygon.lineTo(0, -height / 2);
156            polygon.closePath();
157
158            Figure figure = new BasicFigure(polygon, Color.black);
159
160            if (node != null) {
161                ActorGraphModel model = (ActorGraphModel) getController()
162                        .getGraphModel();
163                figure.setToolTipText(
164                        relation.getName(model.getPtolemyModel()));
165                // Old way to set the color.
166                try {
167                    StringAttribute colorAttr = (StringAttribute) relation
168                            .getAttribute("_color", StringAttribute.class);
169
170                    if (colorAttr != null) {
171                        String color = colorAttr.getExpression();
172                        ((BasicFigure) figure)
173                                .setFillPaint(SVGUtilities.getColor(color));
174                        ((BasicFigure) figure)
175                                .setStrokePaint(SVGUtilities.getColor(color));
176                    }
177                } catch (IllegalActionException e) {
178                    // Ignore.
179                }
180                // New way to set the color
181                List<ColorAttribute> colorAttributes = relation
182                        .attributeList(ColorAttribute.class);
183                if (colorAttributes != null && colorAttributes.size() > 0) {
184                    // Use the last color added.
185                    Color color = colorAttributes
186                            .get(colorAttributes.size() - 1).asColor();
187                    ((BasicFigure) figure).setFillPaint(color);
188                    ((BasicFigure) figure).setStrokePaint(color);
189                }
190            }
191
192            CompositeFigure result = new CompositeFigure(figure);
193
194            if (relation instanceof IORelation) {
195                IORelation ioRelation = (IORelation) relation;
196                try {
197                    boolean isWidthFixed = ioRelation.isWidthFixed();
198                    if ((isWidthFixed || !ioRelation.needsWidthInference())
199                            && ioRelation.getWidth() > 1) {
200                        // Restore width and height to the default to get a reasonable slash.
201                        width = 12.0;
202                        height = 12.0;
203
204                        Line2D.Double line = new Line2D.Double(-width / 2,
205                                height / 2, width / 2, -height / 2);
206                        Figure lineFigure = new BasicFigure(line, Color.black);
207                        result.add(lineFigure);
208                        String labelString = (isWidthFixed ? "" : "(")
209                                + ((IORelation) relation).getWidth()
210                                + (isWidthFixed ? "" : ")");
211
212                        LabelFigure label = new LabelFigure(labelString,
213                                _relationLabelFont, 0,
214                                SwingConstants.SOUTH_WEST);
215                        label.translateTo(width / 2 + 1.0, -height / 2 - 1.0);
216                        result.add(label);
217                    }
218                } catch (IllegalActionException ex) {
219                    throw new InternalErrorException(ex);
220                    // At this time IllegalActionExceptions are not allowed to happen.
221                    // No width inference should happen at this time
222                }
223            }
224
225            return result;
226        }
227    }
228}