001/* The node controller for states.
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.modal;
029
030import java.awt.Toolkit;
031import java.awt.event.ActionEvent;
032import java.awt.event.KeyEvent;
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import javax.swing.KeyStroke;
039
040import diva.canvas.Figure;
041import diva.graph.GraphController;
042import diva.graph.GraphModel;
043import diva.graph.NodeRenderer;
044import diva.gui.GUIUtilities;
045import ptolemy.actor.ExecutionAttributes;
046import ptolemy.actor.TypedActor;
047import ptolemy.actor.gui.ColorAttribute;
048import ptolemy.domains.modal.kernel.State;
049import ptolemy.kernel.util.Attribute;
050import ptolemy.kernel.util.ChangeRequest;
051import ptolemy.kernel.util.Decorator;
052import ptolemy.kernel.util.DecoratorAttributes;
053import ptolemy.kernel.util.IllegalActionException;
054import ptolemy.kernel.util.InternalErrorException;
055import ptolemy.kernel.util.KernelException;
056import ptolemy.kernel.util.Locatable;
057import ptolemy.kernel.util.NameDuplicationException;
058import ptolemy.kernel.util.NamedObj;
059import ptolemy.util.MessageHandler;
060import ptolemy.util.StringUtilities;
061import ptolemy.vergil.icon.EditorIcon;
062import ptolemy.vergil.icon.XMLIcon;
063import ptolemy.vergil.kernel.AttributeController;
064import ptolemy.vergil.kernel.AttributeWithIconController;
065import ptolemy.vergil.toolbox.FigureAction;
066
067///////////////////////////////////////////////////////////////////
068//// StateController
069
070/**
071 This class provides interaction with nodes that represent states in an
072 FSM graph.  It provides a double click binding to edit the parameters
073 of the state, and a context menu containing a commands to edit parameters
074 ("Configure"), rename, get documentation, and look inside.  The looks
075 inside command opens the refinement of the state, if it exists.
076
077 @author Steve Neuendorffer and Edward A. Lee
078 @version $Id$
079 @since Ptolemy II 8.0
080 @Pt.ProposedRating Red (eal)
081 @Pt.AcceptedRating Red (johnr)
082 */
083public class StateController extends AttributeWithIconController {
084
085    /** Create a state controller associated with the specified graph
086     *  controller.
087     *  @param controller The associated graph controller.
088     */
089    public StateController(GraphController controller) {
090        this(controller, FULL);
091    }
092
093    /** Create a state controller associated with the specified graph
094     *  controller.
095     *  @param controller The associated graph controller.
096     *  @param access The access level.
097     */
098    public StateController(GraphController controller, Access access) {
099        super(controller, access);
100
101        setNodeRenderer(new StateRenderer(controller.getGraphModel()));
102    }
103
104    ///////////////////////////////////////////////////////////////////
105    ////                         private variables                 ////
106
107    /** Map used to keep track of icons that have been created
108     *  but not yet assigned to a container.
109     */
110    private static Map _iconsPendingContainer = new HashMap();
111
112    ///////////////////////////////////////////////////////////////////
113    ////                         inner classes                     ////
114
115    /** An action to look inside a state at its refinement, if it has one.
116     *  NOTE: This requires that the configuration be non null, or it
117     *  will report an error with a fairly cryptic message.
118     */
119    @SuppressWarnings("serial")
120    protected class LookInsideAction extends FigureAction {
121        public LookInsideAction() {
122            super("Look Inside");
123
124            // If we are in an applet, so Control-L or Command-L will
125            // be caught by the browser as "Open Location", so we don't
126            // supply Control-L or Command-L as a shortcut under applets.
127            if (!StringUtilities.inApplet()) {
128                // For some inexplicable reason, the I key doesn't work here.
129                // So we use L.
130                putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
131                        KeyEvent.VK_L,
132                        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
133            }
134        }
135
136        @Override
137        public void actionPerformed(ActionEvent e) {
138            if (_configuration == null) {
139                MessageHandler
140                        .error("Cannot look inside without a configuration.");
141                return;
142            }
143
144            super.actionPerformed(e);
145
146            NamedObj target = getTarget();
147
148            // If the target is not an instance of State, do nothing.
149            if (target instanceof State) {
150                try {
151                    TypedActor[] refinements = ((State) target).getRefinement();
152
153                    if (refinements != null && refinements.length > 0) {
154                        for (TypedActor refinement : refinements) {
155                            // Open each refinement.
156                            _configuration.openInstance((NamedObj) refinement);
157                        }
158                    } else {
159                        MessageHandler.error("State has no refinement.");
160                    }
161                } catch (Exception ex) {
162                    MessageHandler.error("Look inside failed: ", ex);
163                }
164            }
165        }
166    }
167
168    /** Render the state as a circle, unless it has a custom icon.
169     */
170    public static class StateRenderer implements NodeRenderer {
171
172        /** Construct a state renderer.
173         *  @param model The GraphModel.
174         */
175        public StateRenderer(GraphModel model) {
176            super();
177            _model = model;
178        }
179
180        /** Render an object.
181         *  @param n The object to be rendered.  This object should
182         *  of type Locatable.
183         *  @return A Figure.
184         */
185        @Override
186        public Figure render(Object n) {
187            Locatable location = (Locatable) n;
188            final NamedObj object = location.getContainer();
189            EditorIcon icon;
190
191            try {
192                // In theory, there shouldn't be more than one
193                // icon, but if there are, use the last one.
194                List icons = object.attributeList(EditorIcon.class);
195
196                // Check to see whether there is an icon that has been created,
197                // but not inserted.
198                if (icons.size() == 0) {
199                    XMLIcon alreadyCreated = (XMLIcon) _iconsPendingContainer
200                            .get(object);
201
202                    if (alreadyCreated != null) {
203                        icons.add(alreadyCreated);
204                    }
205                }
206
207                if (icons.size() > 0) {
208                    icon = (EditorIcon) icons.get(icons.size() - 1);
209                } else {
210                    // NOTE: This code is the same as in
211                    // IconController.IconRenderer.
212                    // NOTE: This used to directly create an XMLIcon within
213                    // the container "object". However, this is not cosher,
214                    // since we may not be able to get write access on the
215                    // workspace. We instead use a hack supported by XMLIcon
216                    // to create an XMLIcon with no container (this does not
217                    // require write access to the workspace), and specify
218                    // to it what the container will eventually be. Then
219                    // we queue a change request to make that the container.
220                    // Further, we have to make a record of the figure, indexed
221                    // by the object, in case some other change request is
222                    // executed before this gets around to setting the
223                    // container.  Otherwise, that second change request
224                    // will result in the creation of a second figure.
225                    icon = new XMLIcon(object.workspace(), "_icon");
226                    icon.setContainerToBe(object);
227                    icon.setPersistent(false);
228
229                    // NOTE: Make sure this is done before the change request
230                    // below is executed, which may be as early as when it is
231                    // requested.
232                    _iconsPendingContainer.put(object, icon);
233
234                    // NOTE: Make sure the source of this change request is
235                    // the graph model. Otherwise, this change request will
236                    // trigger a redraw of the entire graph, which will result
237                    // in another call to this very same method, which will
238                    // result in creation of yet another figure before this
239                    // method even returns!
240                    final EditorIcon finalIcon = icon;
241                    ChangeRequest request = new ChangeRequest(_model,
242                            "Set the container of a new XMLIcon.") {
243                        // NOTE: The KernelException should not be thrown,
244                        // but if it is, it will be handled properly.
245                        @Override
246                        protected void _execute() throws KernelException {
247                            _iconsPendingContainer.remove(object);
248
249                            // If the icon already has a container, do nothing.
250                            if (finalIcon.getContainer() != null) {
251                                return;
252                            }
253
254                            // If the container already has an icon, do nothing.
255                            if (object.getAttribute("_icon") != null) {
256                                return;
257                            }
258
259                            finalIcon.setContainer(object);
260                        }
261                    };
262
263                    request.setPersistent(false);
264                    object.requestChange(request);
265                }
266            } catch (KernelException ex) {
267                throw new InternalErrorException(
268                        "could not create icon " + "in " + object + " even "
269                                + "though one did not exist");
270            }
271
272            Figure figure = icon.createFigure();
273            figure.setToolTipText(object.getName());
274
275            // New way to specify a highlight color.
276            AttributeController.renderHighlight(object, figure);
277
278            try {
279                // clear highlighting
280                Attribute highlightColor = object
281                        .getAttribute("_decoratorHighlightColor");
282                if (highlightColor != null) {
283                    object.removeAttribute(highlightColor);
284                }
285
286                List<Decorator> decorators = new ArrayList();
287                decorators.addAll(object.decorators());
288
289                for (Decorator decorator : decorators) {
290                    DecoratorAttributes decoratorAttributes = object
291                            .getDecoratorAttributes(decorator);
292                    if (decoratorAttributes instanceof ExecutionAttributes) {
293                        if (decoratorAttributes.getDecorator() != null
294                                && ((ExecutionAttributes) decoratorAttributes)
295                                        .enabled()) {
296                            try {
297                                if (object.getAttribute(
298                                        "_decoratorHighlightColor") == null) {
299                                    highlightColor = new ColorAttribute(object,
300                                            "_decoratorHighlightColor");
301                                    Attribute attribute = ((NamedObj) decorator)
302                                            .getAttribute(
303                                                    "decoratorHighlightColor");
304                                    String colorExpression = "{0.5, 0.5, 0.5, 0.5}";
305                                    if (attribute != null) {
306                                        colorExpression = (((ColorAttribute) attribute)
307                                                .getToken()).toString();
308                                    }
309                                    ((ColorAttribute) highlightColor)
310                                            .setExpression(colorExpression);
311                                }
312                            } catch (NameDuplicationException e) {
313                                // Not gonna happen.
314                            }
315                        }
316                    }
317                }
318
319            } catch (IllegalActionException e1) {
320                // TODO Auto-generated catch block
321                e1.printStackTrace();
322            }
323
324            AttributeController.renderDecoratorHighlight(object, figure);
325
326            return figure;
327        }
328
329        private GraphModel _model;
330    }
331}