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}