001/* The node controller for class definitions.
002
003 Copyright (c) 2003-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.Toolkit;
032import java.awt.event.ActionEvent;
033import java.awt.event.KeyEvent;
034import java.lang.ref.WeakReference;
035import java.util.Iterator;
036import java.util.List;
037
038import javax.swing.Action;
039import javax.swing.KeyStroke;
040
041import diva.canvas.CompositeFigure;
042import diva.canvas.Figure;
043import diva.canvas.toolbox.BasicFigure;
044import diva.graph.GraphController;
045import diva.graph.JGraph;
046import diva.gui.GUIUtilities;
047import ptolemy.kernel.InstantiableNamedObj;
048import ptolemy.kernel.util.Instantiable;
049import ptolemy.kernel.util.NamedObj;
050import ptolemy.moml.MoMLChangeRequest;
051import ptolemy.util.MessageHandler;
052import ptolemy.vergil.basic.OffsetMoMLChangeRequest;
053import ptolemy.vergil.toolbox.FigureAction;
054import ptolemy.vergil.toolbox.MenuActionFactory;
055
056///////////////////////////////////////////////////////////////////
057//// ClassDefinitionController
058
059/**
060 This class provides interaction with nodes that represent Ptolemy II
061 classes.  This extends the base class by providing mechanisms in the
062 context menu for creating an instance, creating a subclass,
063 and converting to an instance.
064 <p>
065 NOTE: There should be only one instance of this class associated with
066 a given GraphController. This is because this controller listens for
067 changes to the graph and re-renders the ports of any actor instance
068 in the graph when the graph changes. If there is more than one instance,
069 this rendering will be done twice, which can result in bugs like port
070 labels appearing twice.
071
072 @author Edward A. Lee and Steve Neuendorffer
073 @version $Id$
074 @since Ptolemy II 4.0
075 @Pt.ProposedRating Red (eal)
076 @Pt.AcceptedRating Red (johnr)
077 */
078public class ClassDefinitionController extends ActorController {
079    /** Create an actor instance controller associated with the
080     *  specified graph controller with full access.
081     *  @param controller The associated graph controller.
082     */
083    public ClassDefinitionController(GraphController controller) {
084        this(controller, FULL);
085    }
086
087    /** Create a controller associated with the specified graph
088     *  controller with the specified access.
089     *  @param controller The associated graph controller.
090     *  @param access The access level, one of FULL or PARTIAL.
091     */
092    public ClassDefinitionController(GraphController controller,
093            Access access) {
094        super(controller, access);
095
096        if (access == FULL) {
097            // Use a submenu.
098            Action[] actions = { _createInstanceAction, _createSubclassAction,
099                    _convertToInstanceAction };
100            _menuFactory.addMenuItemFactory(
101                    new MenuActionFactory(actions, "Class Actions"));
102        }
103    }
104
105    /** Add hot keys to the actions in the given JGraph.
106     *  @param jgraph The JGraph to which hot keys are to be added.
107     */
108    @Override
109    public void addHotKeys(JGraph jgraph) {
110        super.addHotKeys(jgraph);
111        // _convertToInstanceAction does not have a hot key.
112        // GUIUtilities.addHotKey(jgraph, _convertToInstanceAction);
113        GUIUtilities.addHotKey(jgraph, _createInstanceAction);
114        GUIUtilities.addHotKey(jgraph, _createSubclassAction);
115    }
116
117    ///////////////////////////////////////////////////////////////////
118    ////                         protected methods                 ////
119
120    /** Draw the node at its location. This overrides the base class
121     *  to highlight the actor to indicate that it is a class definition.
122     */
123    @Override
124    protected Figure _renderNode(Object node) {
125        Figure nf = super._renderNode(node);
126
127        if (nf instanceof CompositeFigure) {
128            // This cast should be safe...
129            CompositeFigure cf = (CompositeFigure) nf;
130            Figure backgroundFigure = cf.getBackgroundFigure();
131
132            // This might be null because the node is hidden.
133            if (backgroundFigure != null) {
134                BasicFigure bf = new BasicFigure(backgroundFigure.getBounds(),
135                        4.0f);
136                bf.setStrokePaint(_HIGHLIGHT_COLOR);
137                // Put the highlighting in the background,
138                // behind the actor label.
139                int index = cf.getFigureCount();
140                if (index < 0) {
141                    index = 0;
142                }
143                cf.add(index, bf);
144            }
145
146            return cf;
147        }
148
149        return nf;
150    }
151
152    ///////////////////////////////////////////////////////////////////
153    ////                         protected variables               ////
154
155    /** The action that handles converting a class to an instance.
156     */
157    protected ConvertToInstanceAction _convertToInstanceAction = new ConvertToInstanceAction(
158            "Convert to Instance");
159
160    /** The action that handles creating an instance from a class.
161     */
162    protected CreateInstanceAction _createInstanceAction = new CreateInstanceAction(
163            "Create Instance");
164
165    /** The action that handles creating a subclass from a class.
166     */
167    protected CreateSubclassAction _createSubclassAction = new CreateSubclassAction(
168            "Create Subclass");
169
170    ///////////////////////////////////////////////////////////////////
171    ////                         private methods                   ////
172
173    /** Create a change request to create an instance or a subclass
174     *  of the object. Do nothing if the specified object does not
175     *  implement Instantiable.
176     *  @see Instantiable
177     *  @param object The class to subclass or instantiate.
178     *  @param subclass True to create a subclass, false to create
179     *   an instance.
180     */
181    private void _createChangeRequest(NamedObj object, boolean subclass) {
182        if (!(object instanceof Instantiable)) {
183            return;
184        }
185        NamedObj container = object.getContainer();
186        StringBuffer moml = new StringBuffer();
187        moml.append("<group name=\"auto\">");
188
189        // FIXME: Can we adjust the location here?
190        // NOTE: This controller is expected to be used
191        // only for class definitions, which must be instances
192        // of InstantiableNamedObj, so this cast should be safe.
193        // However, the key bindings are active even if it's not
194        // a class, so if it's not a class, we just do nothing here.
195        if (((Instantiable) object).isClassDefinition()) {
196            if (subclass) {
197                moml.append("<class name=\"" + "SubclassOf" + object.getName()
198                        + "\" extends=\"" + object.getName() + "\"/>");
199            } else {
200                moml.append("<entity name=\"" + "InstanceOf" + object.getName()
201                        + "\" class=\"" + object.getName() + "\"/>");
202            }
203
204            moml.append("</group>");
205
206            MoMLChangeRequest request = new OffsetMoMLChangeRequest(this,
207                    container, moml.toString());
208            container.requestChange(request);
209        }
210    }
211
212    ///////////////////////////////////////////////////////////////////
213    ////                         private variables                 ////
214
215    /** A fourth argument would make this highlight translucent, which enables
216     * combination with other highlights. However, this forces printing
217     * to PDF to rasterize the image, which results in far lower quality.
218     */
219    private static Color _HIGHLIGHT_COLOR = new Color(150, 150, 255);
220
221    ///////////////////////////////////////////////////////////////////
222    ////                         inner classes                     ////
223    ///////////////////////////////////////////////////////////////////
224    //// ConvertToInstanceAction
225    // An action to convert a class to an instance.
226    @SuppressWarnings("serial")
227    private class ConvertToInstanceAction extends FigureAction {
228        public ConvertToInstanceAction(String commandName) {
229            super(commandName);
230        }
231
232        @Override
233        public void actionPerformed(ActionEvent e) {
234            // If access is not full, do nothing.
235            if (_access != FULL) {
236                return;
237            }
238
239            // Determine which entity was selected for the create instance action.
240            super.actionPerformed(e);
241
242            // NOTE: This cast should be safe because this controller is
243            // used for actors.
244            InstantiableNamedObj object = (InstantiableNamedObj) getTarget();
245            NamedObj container = object.getContainer();
246
247            // Assumes MoML parser will convert to instance.
248            if (!object.isClassDefinition()) {
249                // Object is already an instance. Do nothing.
250                return;
251            }
252
253            // If the class has objects that defer to it, then
254            // refuse to convert.
255            boolean hasDeferrals = false;
256            List deferred = object.getChildren();
257            StringBuffer names = new StringBuffer();
258
259            if (deferred != null) {
260                // List contains weak references, so it's not
261                // sufficient to just check the length.
262                Iterator deferrers = deferred.iterator();
263
264                while (deferrers.hasNext()) {
265                    WeakReference deferrer = (WeakReference) deferrers.next();
266                    NamedObj deferrerObject = (NamedObj) deferrer.get();
267
268                    if (deferrerObject != null) {
269                        hasDeferrals = true;
270
271                        if (names.length() > 0) {
272                            names.append(", ");
273                        }
274
275                        names.append(deferrerObject.getFullName());
276                    }
277                }
278            }
279
280            if (hasDeferrals) {
281                MessageHandler.error("Cannot convert to instance because "
282                        + "there are instances and/or subclasses:\n"
283                        + names.toString());
284                return;
285            }
286
287            String moml = "<entity name=\"" + object.getName() + "\"/>";
288            MoMLChangeRequest request = new MoMLChangeRequest(this, container,
289                    moml);
290            container.requestChange(request);
291        }
292    }
293
294    ///////////////////////////////////////////////////////////////////
295    //// CreateInstanceAction
296    // An action to instantiate a class.
297    @SuppressWarnings("serial")
298    private class CreateInstanceAction extends FigureAction {
299        public CreateInstanceAction(String commandName) {
300            super(commandName);
301            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
302                    KeyEvent.VK_I,
303                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
304        }
305
306        @Override
307        public void actionPerformed(ActionEvent e) {
308            // If access is not full, do nothing.
309            if (_access != FULL) {
310                return;
311            }
312
313            // Determine which entity was selected for the create instance action.
314            super.actionPerformed(e);
315
316            NamedObj object = getTarget();
317            _createChangeRequest(object, false);
318        }
319    }
320
321    ///////////////////////////////////////////////////////////////////
322    //// CreateSubclassAction
323    // An action to subclass a class.
324    @SuppressWarnings("serial")
325    private class CreateSubclassAction extends FigureAction {
326        public CreateSubclassAction(String commandName) {
327            super(commandName);
328            putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
329                    KeyEvent.VK_U,
330                    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
331        }
332
333        @Override
334        public void actionPerformed(ActionEvent e) {
335            // If access is not full, do nothing.
336            if (_access != FULL) {
337                return;
338            }
339
340            // Determine which entity was selected for the
341            // create subclass action.
342            super.actionPerformed(e);
343
344            NamedObj object = getTarget();
345            _createChangeRequest(object, true);
346        }
347    }
348}