001/* An attribute that produces a custom node controller that highlights
002 * downstream actors.
003
004 Copyright (c) 2007-2016 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.vergil.basic;
030
031import java.awt.event.ActionEvent;
032import java.util.Date;
033import java.util.HashSet;
034import java.util.Iterator;
035
036import diva.graph.GraphController;
037import ptolemy.actor.Actor;
038import ptolemy.actor.IOPort;
039import ptolemy.actor.Manager;
040import ptolemy.actor.gui.ColorAttribute;
041import ptolemy.actor.gui.Configuration;
042import ptolemy.actor.gui.DialogTableau;
043import ptolemy.actor.gui.Effigy;
044import ptolemy.data.BooleanToken;
045import ptolemy.data.expr.SingletonParameter;
046import ptolemy.kernel.Entity;
047import ptolemy.kernel.util.IllegalActionException;
048import ptolemy.kernel.util.KernelException;
049import ptolemy.kernel.util.Location;
050import ptolemy.kernel.util.NameDuplicationException;
051import ptolemy.kernel.util.NamedObj;
052import ptolemy.kernel.util.Settable;
053import ptolemy.moml.MoMLChangeRequest;
054import ptolemy.util.MessageHandler;
055import ptolemy.vergil.actor.ActorInstanceController;
056import ptolemy.vergil.icon.EditorIcon;
057import ptolemy.vergil.kernel.attributes.RectangleAttribute;
058import ptolemy.vergil.kernel.attributes.TextAttribute;
059import ptolemy.vergil.toolbox.FigureAction;
060import ptolemy.vergil.toolbox.MenuActionFactory;
061
062///////////////////////////////////////////////////////////////////
063//// DependencyHighlighter
064
065/**
066 This is an attribute that produces a custom node controller that adds
067 context menu commands to highlight dependents and prerequisites.
068 A dependent is a downstream actor, and a prerequisite is an upstream
069 actor. To use this, drop it onto any actor. The context menu (right click
070 or command click) acquires four additional commands to highlight or clear
071 highlights on dependents or prerequisites.
072
073 @author Edward A. Lee
074 @version $Id$
075 @since Ptolemy II 8.0
076 @Pt.ProposedRating Red (eal)
077 @Pt.AcceptedRating Red (johnr)
078 */
079public class DependencyHighlighter extends NodeControllerFactory {
080    /** Construct a new attribute with the given container and name.
081     *  @param container The container.
082     *  @param name The name.
083     *  @exception IllegalActionException If the attribute cannot be contained
084     *   by the proposed container.
085     *  @exception NameDuplicationException If the container already has an
086     *   attribute with this name.
087     */
088    public DependencyHighlighter(NamedObj container, String name)
089            throws NameDuplicationException, IllegalActionException {
090        super(container, name);
091
092        highlightColor = new ColorAttribute(this, "highlightColor");
093        // Red default.
094        highlightColor.setExpression("{1.0, 0.0, 0.0, 1.0}");
095
096        // Hide the name.
097        SingletonParameter _hideName = new SingletonParameter(this,
098                "_hideName");
099        _hideName.setToken(BooleanToken.TRUE);
100        _hideName.setVisibility(Settable.EXPERT);
101
102        // The icon.
103        EditorIcon _icon = new EditorIcon(this, "_icon");
104        RectangleAttribute rectangle = new RectangleAttribute(_icon,
105                "rectangle");
106        rectangle.width.setExpression("175.0");
107        rectangle.height.setExpression("20.0");
108        rectangle.fillColor.setExpression("{1.0, 0.7, 0.7, 1.0}");
109
110        Location _location = new Location(rectangle, "_location");
111        _location.setExpression("-5.0, -15.0");
112
113        TextAttribute text = new TextAttribute(_icon, "text");
114        text.text.setExpression("DependencyHighlighter");
115    }
116
117    ///////////////////////////////////////////////////////////////////
118    ////                         parameters                        ////
119
120    /** The highlight color. */
121    public ColorAttribute highlightColor;
122
123    ///////////////////////////////////////////////////////////////////
124    ////                         public methods                    ////
125
126    /** Return a new node controller.  This base class returns an
127     *  instance of IconController.  Derived
128     *  classes can return some other class to customize the
129     *  context menu.
130     *  @param controller The associated graph controller.
131     *  @return A new node controller.
132     */
133    @Override
134    public NamedObjController create(GraphController controller) {
135        return new DependencyController(controller);
136    }
137
138    ///////////////////////////////////////////////////////////////////
139    ////                         private methods                   ////
140
141    /** Add MoML for highlights for the specified actor to the specified buffer.
142     *  @param actor The actor.
143     *  @param moml The string buffer into which to add the MoML for the highlights.
144     *  @param visited The set of actors that have been visited.
145     *  @param forward True for dependents, false for prerequisites.
146     *  @param clear True to clear, false to highlight.
147     */
148    private void _addHighlights(NamedObj actor, StringBuffer moml,
149            HashSet<NamedObj> visited, boolean forward, boolean clear) {
150        if (visited.contains(actor)) {
151            return;
152        }
153        if (actor instanceof Actor) {
154            moml.append("<entity name=\"");
155            moml.append(actor.getFullName());
156            moml.append("\">");
157            if (!clear) {
158                moml.append(highlightColor.exportMoML("_highlightColor"));
159            } else {
160                if (actor.getAttribute("_highlightColor") != null) {
161                    moml.append("<deleteProperty name=\"_highlightColor\"/>");
162                }
163            }
164            moml.append("</entity>");
165
166            visited.add(actor);
167            Iterator ports;
168            if (forward) {
169                ports = ((Actor) actor).outputPortList().iterator();
170            } else {
171                ports = ((Actor) actor).inputPortList().iterator();
172            }
173            while (ports.hasNext()) {
174                IOPort port = (IOPort) ports.next();
175                Iterator connectedPorts = port.connectedPortList().iterator();
176                while (connectedPorts.hasNext()) {
177                    IOPort otherPort = (IOPort) connectedPorts.next();
178                    // Skip ports with the same polarity (input or output)
179                    // as the current port, or opposite polarity if the
180                    // container of the port is the same as the container
181                    // of the actor.
182                    if (otherPort.getContainer() == actor.getContainer()) {
183                        if (port.isInput() && !otherPort.isInput()
184                                || port.isOutput() && !otherPort.isOutput()) {
185                            continue;
186                        }
187                    } else {
188                        if (port.isInput() && !otherPort.isOutput()
189                                || port.isOutput() && !otherPort.isInput()) {
190                            continue;
191                        }
192                    }
193                    NamedObj higherActor = otherPort.getContainer();
194                    _addHighlights(higherActor, moml, visited, forward, clear);
195                }
196            }
197        }
198    }
199
200    ///////////////////////////////////////////////////////////////////
201    ////                         inner classes                     ////
202
203    /** The controller that adds commands to the context menu.
204     */
205    public/*static*/class DependencyController extends ActorInstanceController {
206        // Findbugs suggests making this static, but if this class is static,
207        // we can't reference the non-static HighlightDependents class here.
208
209        /** Create a DependencyController that is associated with a controller.
210         *  @param controller The controller.
211         */
212        public DependencyController(GraphController controller) {
213            super(controller);
214
215            HighlightDependents highlight = new HighlightDependents(
216                    "Highlight dependents", true, false, false);
217            _menuFactory.addMenuItemFactory(new MenuActionFactory(highlight));
218
219            // Only one menu choice for listing because the dialog has
220            // checkboxes to select between dependencies and prerequisites.
221            HighlightDependents listDependents = new HighlightDependents(
222                    "List dependents & prereqs.", true, false, true);
223            _menuFactory
224                    .addMenuItemFactory(new MenuActionFactory(listDependents));
225
226            HighlightDependents clear1 = new HighlightDependents(
227                    "Clear dependents", true, true, false);
228            _menuFactory.addMenuItemFactory(new MenuActionFactory(clear1));
229
230            HighlightDependents prerequisites = new HighlightDependents(
231                    "Highlight prerequisites", false, false, false);
232            _menuFactory
233                    .addMenuItemFactory(new MenuActionFactory(prerequisites));
234
235            HighlightDependents clear2 = new HighlightDependents(
236                    "Clear prerequisites", false, true, false);
237            _menuFactory.addMenuItemFactory(new MenuActionFactory(clear2));
238        }
239    }
240
241    /** The action for the commands added to the context menu.
242     */
243    @SuppressWarnings("serial")
244    private class HighlightDependents extends FigureAction {
245        /** Construct a HighlightsDependents action.
246         *  @param commandName The name that appears in the menu.
247         *  @param forward True if dependents are to be highlighted or listed.
248         *  If false, then the the prerequisites are highlighted or listed.
249         *  @param clear True if the highlight or prerequisite is to be cleared.
250         *  If false, then the dependency or prerequisite is highlighted.
251         *  @param list True if a list dialog is to be displayed.  If false,
252         *  then the dependents or prerequisites are highlighted.
253         */
254        public HighlightDependents(String commandName, boolean forward,
255                boolean clear, boolean list) {
256            super(commandName);
257            _forward = forward;
258            _clear = clear;
259            _list = list;
260        }
261
262        @Override
263        public void actionPerformed(ActionEvent e) {
264            // Determine which entity was selected for the create instance action.
265            super.actionPerformed(e);
266
267            NamedObj actor = getTarget();
268            // If the model has not been preinitialized since the last
269            // change to its structure, that must be done now for the result
270            // to be accurate. This is because higher-order components
271            // and Publisher and Subscriber connections may not have yet
272            // been created.
273            try {
274                BasicGraphFrame frame = BasicGraphFrame
275                        .getBasicGraphFrame(actor.toplevel());
276                if (frame == null) {
277                    throw new NullPointerException("The frame for "
278                            + actor.toplevel().getName() + " was null?");
279                } else {
280                    frame.report("Preinitializing");
281                    long startTime = new Date().getTime();
282                    Manager.preinitializeThenWrapup((Actor) actor);
283                    frame.report("Done Preinitializing: "
284                            + Manager.timeAndMemory(startTime));
285                }
286            } catch (KernelException ex) {
287                MessageHandler.error("Preinitialize failed.", ex);
288                return;
289            }
290
291            if (_list) {
292                // List the dependencies or prerequisites in a dialog
293                // This is similar to code in BasicGraphFrame for
294                // SearchResultDialog.
295                Effigy effigy = Configuration.findEffigy(actor.toplevel());
296                Configuration configuration = (Configuration) effigy.toplevel();
297
298                DialogTableau dialogTableau = DialogTableau.createDialog(
299                        BasicGraphFrame.getBasicGraphFrame(actor.toplevel()),
300                        configuration, effigy, DependencyResultsDialog.class,
301                        (Entity) actor);
302
303                if (dialogTableau != null) {
304                    dialogTableau.show();
305                }
306
307            } else {
308                // Highlight or clear the dependency or prerequisite.
309                StringBuffer moml = new StringBuffer("<group>");
310                HashSet<NamedObj> visited = new HashSet<NamedObj>();
311                _addHighlights(actor, moml, visited, _forward, _clear);
312                moml.append("</group>");
313                actor.requestChange(new MoMLChangeRequest(this,
314                        actor.getContainer(), moml.toString()));
315            }
316        }
317
318        private boolean _forward, _clear, _list;
319    }
320}