001/* A graph view for the case construct for Ptolemy models.
002
003 Copyright (c) 2006-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.Component;
031import java.awt.event.ActionEvent;
032import java.awt.event.KeyEvent;
033import java.util.HashSet;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Set;
037
038import javax.swing.JComponent;
039import javax.swing.JMenu;
040import javax.swing.JTabbedPane;
041import javax.swing.event.ChangeEvent;
042import javax.swing.event.ChangeListener;
043
044import diva.canvas.event.LayerAdapter;
045import diva.canvas.event.LayerEvent;
046import diva.graph.GraphModel;
047import diva.graph.GraphPane;
048import diva.graph.JGraph;
049import diva.gui.GUIUtilities;
050import ptolemy.actor.gui.Configuration;
051import ptolemy.actor.gui.Tableau;
052import ptolemy.actor.lib.hoc.Case;
053import ptolemy.actor.lib.hoc.MultiCompositeActor;
054import ptolemy.actor.lib.hoc.Refinement;
055import ptolemy.gui.ComponentDialog;
056import ptolemy.gui.Query;
057import ptolemy.kernel.Port;
058import ptolemy.kernel.util.Nameable;
059import ptolemy.kernel.util.NamedObj;
060import ptolemy.moml.LibraryAttribute;
061import ptolemy.moml.MoMLChangeRequest;
062import ptolemy.util.MessageHandler;
063import ptolemy.util.StringUtilities;
064import ptolemy.vergil.actor.ActorEditorGraphController;
065import ptolemy.vergil.actor.ActorGraphFrame;
066import ptolemy.vergil.actor.ActorGraphModel;
067import ptolemy.vergil.basic.EditorDropTarget;
068import ptolemy.vergil.toolbox.FigureAction;
069
070///////////////////////////////////////////////////////////////////
071//// CaseGraphFrame
072
073/**
074 This is a graph editor frame for ptolemy case models.
075
076 @author Edward A. Lee
077 @version $Id$
078 @since Ptolemy II 8.0
079 @Pt.ProposedRating Yellow (eal)
080 @Pt.AcceptedRating Red (johnr)
081 */
082@SuppressWarnings("serial")
083public class CaseGraphFrame extends ActorGraphFrame implements ChangeListener {
084    /** Construct a frame associated with the specified case actor.
085     *  After constructing this, it is necessary
086     *  to call setVisible(true) to make the frame appear.
087     *  This is typically done by calling show() on the controlling tableau.
088     *  This constructor results in a graph frame that obtains its library
089     *  either from the model (if it has one) or the default library defined
090     *  in the configuration.
091     *  @see Tableau#show()
092     *  @param entity The model to put in this frame.
093     *  @param tableau The tableau responsible for this frame.
094     */
095    public CaseGraphFrame(Case entity, Tableau tableau) {
096        this(entity, tableau, null);
097    }
098
099    /** Construct a frame associated with the specified case actor.
100     *  After constructing this, it is necessary
101     *  to call setVisible(true) to make the frame appear.
102     *  This is typically done by calling show() on the controlling tableau.
103     *  This constructor results in a graph frame that obtains its library
104     *  either from the model (if it has one), or the <i>defaultLibrary</i>
105     *  argument (if it is non-null), or the default library defined
106     *  in the configuration.
107     *  @see Tableau#show()
108     *  @param entity The model to put in this frame.
109     *  @param tableau The tableau responsible for this frame.
110     *  @param defaultLibrary An attribute specifying the default library
111     *   to use if the model does not have a library.
112     */
113    public CaseGraphFrame(Case entity, Tableau tableau,
114            LibraryAttribute defaultLibrary) {
115        super(entity, tableau, defaultLibrary);
116
117        _case = entity;
118        _addCaseAction = new AddCaseAction();
119        _removeCaseAction = new RemoveCaseAction();
120
121        // Override the default help file.
122        // FIXME
123        // helpFile = "ptolemy/configs/doc/vergilFsmEditorHelp.htm";
124    }
125
126    ///////////////////////////////////////////////////////////////////
127    ////                         public methods                    ////
128
129    /** Open the container, if any, of the entity.
130     *  If this entity has no container, then do nothing.
131     */
132    @Override
133    public void openContainer() {
134        // Method overridden since the parent will go from the refinement to
135        // the case, which is where we were in the first place.
136        if (_case != _case.toplevel()) {
137            try {
138                Configuration configuration = getConfiguration();
139                // FIXME: do what with the return value?
140                configuration.openInstance(_case.getContainer());
141            } catch (Throwable throwable) {
142                MessageHandler.error("Failed to open container", throwable);
143            }
144        }
145    }
146
147    /** React to a change in the state of the tabbed pane.
148     *  @param event The event.
149     */
150    @Override
151    public void stateChanged(ChangeEvent event) {
152        Object source = event.getSource();
153        if (source instanceof JTabbedPane) {
154            Component selected = ((JTabbedPane) source).getSelectedComponent();
155            if (selected instanceof JGraph) {
156                setJGraph((JGraph) selected);
157                selected.requestFocus();
158            }
159            if (_graphPanner != null) {
160                _graphPanner.setCanvas((JGraph) selected);
161                _graphPanner.repaint();
162            }
163        }
164    }
165
166    ///////////////////////////////////////////////////////////////////
167    ////                         protected methods                 ////
168
169    /** Create the menus that are used by this frame.
170     *  It is essential that _createGraphPane() be called before this.
171     */
172    @Override
173    protected void _addMenus() {
174        super._addMenus();
175        _caseMenu = new JMenu("Case");
176        _caseMenu.setMnemonic(KeyEvent.VK_C);
177        _menubar.add(_caseMenu);
178        GUIUtilities.addHotKey(_getRightComponent(), _addCaseAction);
179        GUIUtilities.addMenuItem(_caseMenu, _addCaseAction);
180        GUIUtilities.addHotKey(_getRightComponent(), _removeCaseAction);
181        GUIUtilities.addMenuItem(_caseMenu, _removeCaseAction);
182    }
183
184    /** Create a new graph pane. Note that this method is called in
185     *  constructor of the base class, so it must be careful to not reference
186     *  local variables that may not have yet been created.
187     *  This overrides the base class to create a specialized
188     *  graph controller (an inner class).
189     *  @param entity The object to be displayed in the pane.
190     *  @return The pane that is created.
191     */
192    @Override
193    protected GraphPane _createGraphPane(NamedObj entity) {
194        _controller = new CaseGraphController();
195        _controller.setConfiguration(getConfiguration());
196        _controller.setFrame(this);
197
198        // The cast is safe because the constructor only accepts
199        // CompositeEntity.
200        final ActorGraphModel graphModel = new ActorGraphModel(entity);
201        return new GraphPane(_controller, graphModel);
202    }
203
204    /** Create the component that goes to the right of the library.
205     *  NOTE: This is called in the base class constructor, before
206     *  things have been initialized. Hence, it cannot reference
207     *  local variables.
208     *  @param entity The entity to display in the component.
209     *  @return The component that goes to the right of the library.
210     */
211    @Override
212    protected JComponent _createRightComponent(NamedObj entity) {
213        if (!(entity instanceof Case)) {
214            return super._createRightComponent(entity);
215        }
216        _tabbedPane = new JTabbedPane();
217        _tabbedPane.addChangeListener(this);
218        Iterator<?> cases = ((Case) entity).entityList(Refinement.class)
219                .iterator();
220        boolean first = true;
221        while (cases.hasNext()) {
222            Refinement refinement = (Refinement) cases.next();
223            JGraph jgraph = _addTabbedPane(refinement, false);
224            // The first JGraph is the one with the focus.
225            if (first) {
226                first = false;
227                setJGraph(jgraph);
228            } else {
229                ((CaseGraphController) _controller)._addHotKeys(jgraph);
230            }
231        }
232        return _tabbedPane;
233    }
234
235    ///////////////////////////////////////////////////////////////////
236    ////                         protected variables               ////
237
238    /** The case menu. */
239    protected JMenu _caseMenu;
240
241    ///////////////////////////////////////////////////////////////////
242    ////                         private methods                   ////
243
244    /** Add a tabbed pane for the specified case.
245     *  @param refinement The case.
246     *  @param newPane True to add the pane prior to the last pane.
247     *  @return The pane.
248     */
249    private JGraph _addTabbedPane(Refinement refinement, boolean newPane) {
250        GraphPane pane = _createGraphPane(refinement);
251        pane.getForegroundLayer().setPickHalo(2);
252        pane.getForegroundEventLayer().setConsuming(false);
253        pane.getForegroundEventLayer().setEnabled(true);
254        pane.getForegroundEventLayer().addLayerListener(new LayerAdapter() {
255            /** Invoked when the mouse is pressed on a layer
256             * or figure.
257             */
258            @Override
259            public void mousePressed(LayerEvent event) {
260                Component component = event.getComponent();
261
262                if (!component.hasFocus()) {
263                    component.requestFocus();
264                }
265            }
266        });
267        JGraph jgraph = new JGraph(pane);
268        String name = refinement.getName();
269        jgraph.setName(name);
270        int index = _tabbedPane.getComponentCount();
271        // Put before the default pane, unless this is the default.
272        if (newPane) {
273            index--;
274        }
275        _tabbedPane.add(jgraph, index);
276        jgraph.setBackground(BACKGROUND_COLOR);
277        // Create a drop target for the jgraph.
278        // FIXME: Should override _setDropIntoEnabled to modify all the drop targets created.
279        new EditorDropTarget(jgraph);
280        return jgraph;
281    }
282
283    ///////////////////////////////////////////////////////////////////
284    ////                         private variables                 ////
285
286    /** The action to add a case. */
287    private AddCaseAction _addCaseAction;
288
289    /** The Case actor displayed by this frame. */
290    private Case _case;
291
292    /** The action to remove a case. */
293    private RemoveCaseAction _removeCaseAction;
294
295    /** The tabbed pane for cases. */
296    private JTabbedPane _tabbedPane;
297
298    ///////////////////////////////////////////////////////////////////
299    ////                     public inner classes                  ////
300
301    /** Class implementing the Add Case menu command. */
302    public class AddCaseAction extends FigureAction {
303
304        /** Create a case action with label "Add Case". */
305        public AddCaseAction() {
306            super("Add Case");
307            putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_A));
308        }
309
310        ///////////////////////////////////////////////////////////////////////////////
311        ////                            public methods                             ////
312
313        /** Perform the action. */
314        @Override
315        public void actionPerformed(ActionEvent e) {
316            super.actionPerformed(e);
317            // Dialog to ask for a case name.
318            Query query = new Query();
319            query.addLine("case", "Pattern that the control input must match",
320                    "");
321            ComponentDialog dialog = new ComponentDialog(CaseGraphFrame.this,
322                    "Add Case", query);
323            if (dialog.buttonPressed().equals("OK")) {
324                final String pattern = query.getStringValue("case");
325                // NOTE: We do not use a TransitionRefinement because we don't
326                // want the sibling input ports that come with output ports.
327                String moml = "<entity name=\""
328                        + StringUtilities.escapeForXML(pattern) + "\" class=\""
329                        + _case.refinementClassName() + "\"/>";
330
331                // The following is, regrettably, copied from ModalTransitionController.
332                MoMLChangeRequest change = new MoMLChangeRequest(this, _case,
333                        moml) {
334                    @Override
335                    protected void _execute() throws Exception {
336                        super._execute();
337
338                        // Mirror the ports of the container in the refinement.
339                        // Note that this is done here rather than as part of
340                        // the MoML because we have set protected variables
341                        // in the refinement to prevent it from trying to again
342                        // mirror the changes in the container.
343                        Refinement entity = (Refinement) _case
344                                .getEntity(pattern);
345
346                        // Get the initial port configuration from the container.
347                        Iterator<?> ports = _case.portList().iterator();
348
349                        Set<Port> portsToMirror = new HashSet<Port>();
350                        while (ports.hasNext()) {
351                            Port port = (Port) ports.next();
352
353                            // see if we should mirror the port
354                            if (port != _case.control.getPort()) {
355                                portsToMirror.add(port);
356                            }
357                        }
358
359                        MultiCompositeActor.mirrorContainerPortsInRefinement(
360                                entity, portsToMirror);
361
362                        JGraph jgraph = _addTabbedPane(entity, true);
363                        ((CaseGraphController) _controller)._addHotKeys(jgraph);
364                    }
365                };
366
367                _case.requestChange(change);
368            }
369        }
370    }
371
372    /** Specialized graph controller that handles multiple graph models. */
373    public class CaseGraphController extends ActorEditorGraphController {
374        /** Override the base class to select the graph model associated
375         *  with the selected pane.
376         */
377        @Override
378        public GraphModel getGraphModel() {
379            if (_tabbedPane != null) {
380                Component tab = _tabbedPane.getSelectedComponent();
381                if (tab instanceof JGraph) {
382                    GraphPane pane = ((JGraph) tab).getGraphPane();
383                    return pane.getGraphModel();
384                }
385            }
386            // Fallback position.
387            return super.getGraphModel();
388        }
389
390        /** Add hot keys to the actions in the given JGraph.
391         *
392         *  @param jgraph The JGraph to which hot keys are to be added.
393         */
394        @Override
395        protected void _addHotKeys(JGraph jgraph) {
396            super._addHotKeys(jgraph);
397        }
398    }
399
400    /** Class implementing the Remove Case menu command. */
401    public class RemoveCaseAction extends FigureAction {
402
403        /** Create a case action with label "Add Case". */
404        public RemoveCaseAction() {
405            super("Remove Case");
406            putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_R));
407        }
408
409        ///////////////////////////////////////////////////////////////////////////////
410        ////                            public methods                             ////
411
412        /** Perform the action. */
413        @Override
414        public void actionPerformed(ActionEvent e) {
415            super.actionPerformed(e);
416            // Dialog to ask for a case name.
417            Query query = new Query();
418            List<?> refinements = _case.entityList(Refinement.class);
419            if (refinements.size() < 2) {
420                MessageHandler.error("No cases to remove.");
421            } else {
422                String[] caseNames = new String[refinements.size() - 1];
423                Iterator<?> cases = refinements.iterator();
424                int i = 0;
425                while (cases.hasNext()) {
426                    String name = ((Nameable) cases.next()).getName();
427                    if (!name.equals("default")) {
428                        caseNames[i] = name;
429                        i++;
430                    }
431                }
432                query.addChoice("case", "Remove case", caseNames, caseNames[0]);
433                ComponentDialog dialog = new ComponentDialog(
434                        CaseGraphFrame.this, "Remove Case", query);
435                if (dialog.buttonPressed().equals("OK")) {
436                    final String name = query.getStringValue("case");
437                    String moml = "<deleteEntity name=\""
438                            + StringUtilities.escapeForXML(name) + "\"/>";
439
440                    // The following is, regrettably, copied from ModalTransitionController.
441                    MoMLChangeRequest change = new MoMLChangeRequest(this,
442                            _case, moml) {
443                        @Override
444                        protected void _execute() throws Exception {
445                            super._execute();
446                            // Find the tabbed pane that matches the name and remove it.
447                            int count = _tabbedPane.getTabCount();
448                            for (int i = 0; i < count; i++) {
449                                if (name.equals(_tabbedPane.getTitleAt(i))) {
450                                    _tabbedPane.remove(i);
451                                    break;
452                                }
453                            }
454                        }
455                    };
456                    _case.requestChange(change);
457                }
458            }
459        }
460    }
461}