001/* The node controller for objects with icons.
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 */
027package ptolemy.vergil.basic;
028
029// This file must work with Ptiny, so do not import packages
030// from ptolemy.domains, actor.lib, or org.
031
032import java.awt.Color;
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import diva.canvas.Figure;
039import diva.canvas.toolbox.SVGUtilities;
040import diva.graph.GraphController;
041import diva.graph.GraphModel;
042import diva.graph.NodeRenderer;
043import ptolemy.actor.ExecutionAttributes;
044import ptolemy.actor.gui.ColorAttribute;
045import ptolemy.kernel.Entity;
046import ptolemy.kernel.util.Attribute;
047import ptolemy.kernel.util.ChangeRequest;
048import ptolemy.kernel.util.Decorator;
049import ptolemy.kernel.util.DecoratorAttributes;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.InternalErrorException;
052import ptolemy.kernel.util.KernelException;
053import ptolemy.kernel.util.Locatable;
054import ptolemy.kernel.util.NameDuplicationException;
055import ptolemy.kernel.util.NamedObj;
056import ptolemy.kernel.util.StringAttribute;
057import ptolemy.vergil.icon.EditorIcon;
058import ptolemy.vergil.icon.XMLIcon;
059import ptolemy.vergil.kernel.AnimationRenderer;
060import ptolemy.vergil.kernel.AttributeController;
061import ptolemy.vergil.kernel.ShadowRenderer;
062
063///////////////////////////////////////////////////////////////////
064//// IconController
065
066/**
067 This class provides interaction with nodes that represent Ptolemy II
068 objects that are represented on screen as icons, such as attributes
069 and entities.   It provides a double click binding to edit the parameters
070 of the node, and a context menu containing a command to edit parameters
071 ("Configure"). This adds to the base class the ability to render an
072 icon for the object being controlled, where the icon is specified
073 by a contained attribute of class EditorIcon (typically, but not
074 necessarily named "_icon").
075
076 @author Steve Neuendorffer and Edward A. Lee
077 @version $Id$
078 @since Ptolemy II 2.0
079 @Pt.ProposedRating Red (eal)
080 @Pt.AcceptedRating Red (johnr)
081 */
082public class IconController extends ParameterizedNodeController {
083    /** Create a controller associated with the specified graph
084     *  controller.
085     *  @param controller The associated graph controller.
086     */
087    public IconController(GraphController controller) {
088        super(controller);
089        setNodeRenderer(new IconRenderer());
090    }
091
092    ///////////////////////////////////////////////////////////////////
093    ////                         private variables                 ////
094
095    /** Map used to keep track of icons that have been created
096     *  but not yet assigned to a container.
097     */
098    private static Map _iconsPendingContainer = new HashMap();
099
100    ///////////////////////////////////////////////////////////////////
101    ////                         inner classes                     ////
102
103    /** An icon renderer. */
104    public class IconRenderer implements NodeRenderer {
105        // FindBugs wants this static, but adding a constructor that takes
106        // a GraphModel does not work.  If you create a model that has
107        // a composite and then save it, dragging around the composite
108        // results in duplicate composites.
109
110        /**  Render a visual representation of the given node. If the
111         * StringAttribute _color of the node is set then use that color to
112         * highlight the node. If the StringAttribute _explanation of the node
113         * is set then use it to set the tooltip.
114         * @see diva.graph.NodeRenderer#render(java.lang.Object)
115         */
116        @Override
117        public Figure render(Object n) {
118            Locatable location = (Locatable) n;
119            final NamedObj object = location.getContainer();
120
121            // NOTE: this code is similar to that in PtolemyTreeCellRenderer
122            Figure result = null;
123
124            try {
125                List iconList = object.attributeList(EditorIcon.class);
126
127                // Check to see whether there is an icon that has been created,
128                // but not inserted.
129                if (iconList.size() == 0) {
130                    XMLIcon alreadyCreated = (XMLIcon) _iconsPendingContainer
131                            .get(object);
132
133                    if (alreadyCreated != null) {
134                        iconList.add(alreadyCreated);
135                    }
136                }
137
138                // If there are still no icons, then we need to create one.
139                if (iconList.size() == 0) {
140                    // NOTE: This used to directly create an XMLIcon within
141                    // the container "object". However, this is not cosher,
142                    // since we may not be able to get write access on the
143                    // workspace. We instead use a hack supported by XMLIcon
144                    // to create an XMLIcon with no container (this does not
145                    // require write access to the workspace), and specify
146                    // to it what the container will eventually be. Then
147                    // we queue a change request to make that the container.
148                    // Further, we have to make a record of the figure, indexed
149                    // by the object, in case some other change request is
150                    // executed before this gets around to setting the
151                    // container.  Otherwise, that second change request
152                    // will result in the creation of a second figure.
153
154                    final EditorIcon icon = XMLIcon
155                            .getXMLIcon(object.workspace(), "_icon");
156                    icon.setContainerToBe(object);
157                    icon.setPersistent(false);
158                    result = icon.createFigure();
159
160                    // NOTE: Make sure this is done before the change request
161                    // below is executed, which may be as early as when it is
162                    // requested.
163                    _iconsPendingContainer.put(object, icon);
164
165                    // NOTE: Make sure the source of this change request is
166                    // the graph model. Otherwise, this change request will
167                    // trigger a redraw of the entire graph, which will result
168                    // in another call to this very same method, which will
169                    // result in creation of yet another figure before this
170                    // method even returns!
171                    GraphController controller = IconController.this
172                            .getController();
173                    GraphModel graphModel = controller.getGraphModel();
174                    ChangeRequest request = new ChangeRequest(graphModel,
175                            "Set the container of a new XMLIcon.") {
176                        // NOTE: The KernelException should not be thrown,
177                        // but if it is, it will be handled properly.
178                        @Override
179                        protected void _execute() throws KernelException {
180                            _iconsPendingContainer.remove(object);
181
182                            // If the icon already has a container, do nothing.
183                            if (icon.getContainer() != null) {
184                                return;
185                            }
186
187                            // If the container already has an icon, do nothing.
188                            // MatlabWirelessSoundDetection.xml has some _icon properties,
189                            // so we check for them.
190                            List icons = object.attributeList(EditorIcon.class);
191                            if (icons != null && icons.size() > 0
192                                    || object.getAttribute("_icon") != null) {
193                                return;
194                            }
195
196                            icon.setContainer(object);
197                        }
198                    };
199
200                    request.setPersistent(false);
201                    object.requestChange(request);
202                } else if (iconList.size() >= 1) {
203                    // Use only the last icon in the list.
204                    EditorIcon icon = (EditorIcon) iconList
205                            .get(iconList.size() - 1);
206                    result = icon.createFigure();
207                }
208            } catch (KernelException ex) {
209                throw new InternalErrorException(null, ex,
210                        "Could not create icon " + "in " + object + " even "
211                                + "though one did not previously exist.");
212            }
213
214            if (result == null) {
215                throw new InternalErrorException("Failed to create icon.");
216            } else {
217                result.setToolTipText(object.getClassName());
218            }
219
220            // Check to see if it has
221            // attributes that specify its color or an explanation.
222            // Old way to specify a color.
223            try {
224                StringAttribute colorAttr = (StringAttribute) object
225                        .getAttribute("_color", StringAttribute.class);
226                if (colorAttr != null) {
227                    String color = colorAttr.getExpression();
228                    AnimationRenderer animationRenderer = new AnimationRenderer(
229                            SVGUtilities.getColor(color));
230                    animationRenderer.renderSelected(result);
231                }
232            } catch (IllegalActionException e) {
233                // Ignore
234            }
235
236            // New way to specify a highlight color.
237            AttributeController.renderHighlight(object, result);
238
239            try {
240                // clear highlighting
241                Attribute highlightColor = object
242                        .getAttribute("_decoratorHighlightColor");
243                if (highlightColor != null) {
244                    object.removeAttribute(highlightColor);
245                }
246
247                List<Decorator> decorators = new ArrayList();
248                decorators.addAll(object.decorators());
249
250                for (Decorator decorator : decorators) {
251                    DecoratorAttributes decoratorAttributes = object
252                            .getDecoratorAttributes(decorator);
253                    if (decoratorAttributes instanceof ExecutionAttributes) {
254                        if (decoratorAttributes.getDecorator() != null
255                                && ((ExecutionAttributes) decoratorAttributes)
256                                        .enabled()) {
257                            try {
258                                if (object.getAttribute(
259                                        "_decoratorHighlightColor") == null) {
260                                    highlightColor = new ColorAttribute(object,
261                                            "_decoratorHighlightColor");
262                                    Attribute attribute = ((NamedObj) decorator)
263                                            .getAttribute(
264                                                    "decoratorHighlightColor");
265                                    String colorExpression = "{0.5, 0.5, 0.5, 0.5}";
266                                    if (attribute != null) {
267                                        colorExpression = (((ColorAttribute) attribute)
268                                                .getToken()).toString();
269                                    }
270                                    ((ColorAttribute) highlightColor)
271                                            .setExpression(colorExpression);
272                                }
273                            } catch (NameDuplicationException e) {
274                                // Not gonna happen.
275                            }
276                        }
277                    }
278                }
279
280            } catch (IllegalActionException e1) {
281                // TODO Auto-generated catch block
282                e1.printStackTrace();
283            }
284
285            AttributeController.renderDecoratorHighlight(object, result);
286
287            // If a shadow is specified, render it now.
288            // The shadow attribute can be contained by the container
289            // so that it is applied to all icons corresponding to Entity
290            // objects (not attributes). This can be overridden for each
291            // object (including attributes) by providing an individual
292            // shadow specification. An empty color results in no shadow.
293            try {
294                // If the object itself has a shadow specification, use that.
295                ColorAttribute shadowAttribute = (ColorAttribute) object
296                        .getAttribute("_shadowColor", ColorAttribute.class);
297                if (shadowAttribute != null) {
298                    if (!shadowAttribute.getExpression().trim().equals("")) {
299                        Color color = shadowAttribute.asColor();
300                        // FIXME: How to set the size of the shadow?
301                        ShadowRenderer animationRenderer = new ShadowRenderer(
302                                color);
303                        animationRenderer.renderSelected(result);
304                    }
305                } else if (object instanceof Entity) {
306                    // If the container has a shadow specification, use that.
307                    NamedObj container = object.getContainer();
308                    if (container != null) {
309                        shadowAttribute = (ColorAttribute) container
310                                .getAttribute("_shadowColor",
311                                        ColorAttribute.class);
312                        if (shadowAttribute != null && !shadowAttribute
313                                .getExpression().trim().equals("")) {
314                            Color color = shadowAttribute.asColor();
315                            // FIXME: How to set the size of the shadow?
316                            ShadowRenderer animationRenderer = new ShadowRenderer(
317                                    color);
318                            animationRenderer.renderSelected(result);
319                        }
320                    }
321                }
322            } catch (IllegalActionException e) {
323                // Ignore.
324            }
325
326            try {
327                StringAttribute explanationAttribute = (StringAttribute) object
328                        .getAttribute("_explanation", StringAttribute.class);
329                if (explanationAttribute != null) {
330                    result.setToolTipText(explanationAttribute.getExpression());
331                }
332            } catch (IllegalActionException e) {
333                // Ignore.
334            }
335
336            return result;
337        }
338    }
339}