001/* 002 * Copyright (c) 1998-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: brooks $' 006 * '$Date: 2012-06-18 00:36:48 +0000 (Mon, 18 Jun 2012) $' 007 * '$Revision: 29975 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.sdm.spa.gui; 031 032import java.awt.Container; 033import java.io.IOException; 034import java.io.Writer; 035import java.util.LinkedList; 036import java.util.List; 037 038import javax.swing.SwingUtilities; 039 040import ptolemy.actor.TypedAtomicActor; 041import ptolemy.actor.TypedIOPort; 042import ptolemy.actor.gui.Configuration; 043import ptolemy.actor.gui.Effigy; 044import ptolemy.actor.gui.Placeable; 045import ptolemy.actor.gui.TableauFrame; 046import ptolemy.actor.gui.WindowPropertiesAttribute; 047import ptolemy.data.StringToken; 048import ptolemy.data.type.BaseType; 049import ptolemy.gui.ShellInterpreter; 050import ptolemy.kernel.CompositeEntity; 051import ptolemy.kernel.util.IllegalActionException; 052import ptolemy.kernel.util.NameDuplicationException; 053import ptolemy.kernel.util.Nameable; 054import ptolemy.kernel.util.Workspace; 055 056////////////////////////////////////////////////////////////////////////// 057//// UserInteractiveShell 058/** 059 * This actor code is adopted from InteractiveShell.java by Edward A. Lee 060 * 061 * @author Ilkay Altintas 062 * @version $Id: UserInteractiveShell.java 29975 2012-06-18 00:36:48Z brooks $ 063 */ 064public class UserInteractiveShell extends TypedAtomicActor implements 065 Placeable, ShellInterpreter { 066 067 /** 068 * Construct an actor with the given container and name. 069 * 070 * @param container 071 * The container. 072 * @param name 073 * The name of this actor. 074 * @exception IllegalActionException 075 * If the actor cannot be contained by the proposed 076 * container. 077 * @exception NameDuplicationException 078 * If the container already has an actor with this name. 079 */ 080 public UserInteractiveShell(CompositeEntity container, String name) 081 throws IllegalActionException, NameDuplicationException { 082 super(container, name); 083 084 input = new TypedIOPort(this, "input", true, false); 085 input.setTypeEquals(BaseType.STRING); 086 087 output = new TypedIOPort(this, "output", false, true); 088 output.setTypeEquals(BaseType.STRING); 089 090 _windowProperties = new WindowPropertiesAttribute(this, 091 "_windowProperties"); 092 093 _attachText("_iconDescription", "<svg>\n" 094 + "<rect x=\"-20\" y=\"-20\" " + "width=\"40\" height=\"40\" " 095 + "style=\"fill:lightGrey\"/>\n" + "<rect x=\"-14\" y=\"-14\" " 096 + "width=\"28\" height=\"28\" " + "style=\"fill:white\"/>\n" 097 + "<polyline points=\"-10,-10, -5,-5, -10,0\" " 098 + "style=\"stroke:black\"/>\n" 099 + "<polyline points=\"-7,-10, -2,-5, -7,0\" " 100 + "style=\"stroke:black\"/>\n" + "</svg>\n"); 101 } 102 103 // ///////////////////////////////////////////////////////////////// 104 // // ports and parameters //// 105 106 /** The input port. */ 107 public TypedIOPort input; 108 109 /** The output port. */ 110 public TypedIOPort output; 111 112 /** The shell window object. */ 113 public DoubleShellTextAreaPanel shellPanel; 114 115 // ///////////////////////////////////////////////////////////////// 116 // // public methods //// 117 118 /** 119 * Clone the actor into the specified workspace. 120 * 121 * @param workspace 122 * The workspace for the new object. 123 * @return A new actor. 124 * @exception CloneNotSupportedException 125 * If a derived class has an attribute that cannot be cloned. 126 */ 127 public Object clone(Workspace workspace) throws CloneNotSupportedException { 128 UserInteractiveShell newObject = (UserInteractiveShell) super 129 .clone(workspace); 130 newObject.shellPanel = null; 131 newObject._container = null; 132 newObject._frame = null; 133 return newObject; 134 } 135 136 /** 137 * Evaluate the specified command. 138 * 139 * @param command 140 * The command. 141 * @return The return value of the command, or null if there is none. 142 * @exception Exception 143 * If something goes wrong processing the command. 144 */ 145 public String evaluateCommand(String command) throws Exception { 146 // NOTE: This method is typically called in the swing event thread. 147 // Be careful to avoid locking up the UI. 148 setOutput(command); 149 // Return null to indicate that the command evaluation is not 150 // complete. This results in disabling editing on the text 151 // widget until returnResult() is called on it, which happens 152 // the next time fire() is called. 153 return null; 154 } 155 156 /** 157 * Read and display the input, then wait for user input and produce the user 158 * data on the output. If the user input is "quit" or "exit", then set a 159 * flag that causes postfire() to return false. 160 * 161 * @exception IllegalActionException 162 * If producing the output causes an exception. 163 */ 164 public void fire() throws IllegalActionException { 165 super.fire(); 166 if (input.numberOfSources() > 0 && input.hasToken(0)) { 167 String value = ((StringToken) input.get(0)).stringValue(); 168 // If window has been dismissed, there is nothing more to do. 169 if (shellPanel == null) 170 return; 171 if (_firstTime) { 172 _firstTime = false; 173 shellPanel.initialize(value); 174 } else { 175 shellPanel.returnResult(value); 176 } 177 } 178 shellPanel.setEditable(); 179 // FIXME: What to type if there is no input connected. 180 String userCommand = getOutput(); 181 if (userCommand.trim().equalsIgnoreCase("quit") 182 || userCommand.trim().equalsIgnoreCase("exit")) { 183 _returnFalseInPostfire = true; 184 } 185 output.broadcast(new StringToken(userCommand)); 186 } 187 188 /** 189 * Get the output string to be sent. This does not return until a value is 190 * entered on the shell by the user. 191 * 192 * @return The output string to be sent. 193 */ 194 public synchronized String getOutput() { 195 while (_outputValues.size() < 1 && !_stopRequested) { 196 try { 197 // NOTE: Do not call wait on this object directly! 198 // If another thread tries to get write access to the 199 // workspace, it will deadlock! This method releases 200 // all read accesses on the workspace before doing the 201 // wait. 202 workspace().wait(this); 203 } catch (InterruptedException ex) { 204 } 205 } 206 if (_stopRequested) { 207 return ("stop"); 208 } else { 209 return ((String) _outputValues.remove(0)); 210 } 211 } 212 213 /** 214 * If the shell has not already been created, create it. Then wait for user 215 * input and produce it on the output. 216 * 217 * @exception IllegalActionException 218 * If the parent class throws it. 219 */ 220 public void initialize() throws IllegalActionException { 221 super.initialize(); 222 if (shellPanel == null) { 223 // No container has been specified for the shell. 224 // Place the shell in its own frame. 225 // Need an effigy and a tableau so that menu ops work properly. 226 Effigy containerEffigy = Configuration.findEffigy(toplevel()); 227 if (containerEffigy == null) { 228 throw new IllegalActionException(this, 229 "Cannot find effigy for top level: " 230 + toplevel().getFullName()); 231 } 232 try { 233 DoubleShellTAPEffigy shellEffigy = new DoubleShellTAPEffigy( 234 containerEffigy, containerEffigy.uniqueName("shell")); 235 // The default identifier is "Unnamed", which is no good for 236 // two reasons: Wrong title bar label, and it causes a save-as 237 // to destroy the original window. 238 shellEffigy.identifier.setExpression(getFullName()); 239 DoubleShellTAPTableau tableau = new DoubleShellTAPTableau( 240 shellEffigy, "tableau"); 241 _frame = tableau.frame; 242 shellPanel = tableau.shellPanel; 243 shellPanel.setInterpreter(this); 244 // Prevent editing until the first firing. 245 shellPanel.setEditable(); 246 } catch (Exception ex) { 247 throw new IllegalActionException(this, null, ex, 248 "Error creating effigy and tableau"); 249 } 250 _windowProperties.setProperties(_frame); 251 _frame.show(); 252 } else { 253 shellPanel.clearShell2(); 254 } 255 /* 256 * NOTE: This causes a bug where manual resizes of the window get 257 * overridden on re-run. if (_frame != null) { _frame.show(); } 258 */ 259 _firstTime = true; 260 _returnFalseInPostfire = false; 261 } 262 263 /** 264 * Return true if the specified command is complete (ready to be 265 * interpreted). 266 * 267 * @param command 268 * The command. 269 * @return True. 270 */ 271 public boolean isCommandComplete(String command) { 272 return true; 273 } 274 275 /** 276 * Specify the container into which this shell should be placed. This method 277 * needs to be called before the first call to initialize(). Otherwise, the 278 * shell will be placed in its own frame. The background of the plot is set 279 * equal to that of the container (unless it is null). 280 * 281 * @param container 282 * The container into which to place the shell, or null to 283 * specify that a new shell should be created. 284 */ 285 public void place(Container container) { 286 _container = container; 287 288 if (_container == null) { 289 // Dissociate with any container. 290 // NOTE: _remove() doesn't work here. Why? 291 if (_frame != null) 292 _frame.dispose(); 293 _frame = null; 294 shellPanel = null; 295 return; 296 } 297 shellPanel = new DoubleShellTextAreaPanel(); 298 shellPanel.setInterpreter(this); 299 shellPanel.shell1.setEditable(true); 300 // shellPanel.shell1.append(_greetSh1); 301 shellPanel.shell1.setEditable(false); 302 shellPanel.clearShell2(); 303 304 _container.add(shellPanel); 305 // java.awt.Component.setBackground(color) says that 306 // if the color "parameter is null then this component 307 // will inherit the background color of its parent." 308 shellPanel.setBackground(null); 309 } 310 311 /** 312 * Override the base class to return false if the user has typed "quit" or 313 * "exit". 314 * 315 * @return False if the user has typed "quit" or "exit". 316 * @exception IllegalActionException 317 * If the superclass throws it. 318 */ 319 public boolean postfire() throws IllegalActionException { 320 // if (_returnFalseInPostfire) { 321 _returnFalseInPostfire = true; 322 return false; 323 324 // } 325 // return super.postfire(); 326 } 327 328 /** 329 * Override the base class to remove the shell from its graphical container 330 * if the argument is null. 331 * 332 * @param container 333 * The proposed container. 334 * @exception IllegalActionException 335 * If the base class throws it. 336 * @exception NameDuplicationException 337 * If the base class throws it. 338 */ 339 public void setContainer(CompositeEntity container) 340 throws IllegalActionException, NameDuplicationException { 341 Nameable previousContainer = getContainer(); 342 super.setContainer(container); 343 if (container != previousContainer && previousContainer != null) { 344 _remove(); 345 } 346 } 347 348 /** 349 * Specify an output string to be sent. This method appends the specified 350 * string to a queue. Strings are retrieved from the queue by getOutput(). 351 * 352 * @see #getOutput() 353 * @param value 354 * An output string to be sent. 355 */ 356 public synchronized void setOutput(String value) { 357 _outputValues.add(value); 358 notifyAll(); 359 } 360 361 /** 362 * Override the base class to call notifyAll() to get out of any waiting. 363 */ 364 public void stop() { 365 super.stop(); 366 synchronized (this) { 367 notifyAll(); 368 } 369 } 370 371 /** 372 * Override the base class to make the shell uneditable. 373 * 374 * @exception IllegalActionException 375 * If the parent class throws it. 376 */ 377 public void wrapup() throws IllegalActionException { 378 super.wrapup(); 379 if (_returnFalseInPostfire && _frame != null) { 380 _frame.dispose(); 381 } else if (shellPanel != null) { 382 shellPanel.setEditable(); 383 } 384 } 385 386 // ///////////////////////////////////////////////////////////////// 387 // // protected methods //// 388 389 /** 390 * Write a MoML description of the contents of this object. This overrides 391 * the base class to make sure that the current frame properties, if there 392 * is a frame, are recorded. 393 * 394 * @param output 395 * The output stream to write to. 396 * @param depth 397 * The depth in the hierarchy, to determine indenting. 398 * @exception IOException 399 * If an I/O error occurs. 400 */ 401 protected void _exportMoMLContents(Writer output, int depth) 402 throws IOException { 403 // Make sure that the current position of the frame, if any, 404 // is up to date. 405 if (_frame != null) { 406 _windowProperties.recordProperties(_frame); 407 } 408 super._exportMoMLContents(output, depth); 409 } 410 411 // ///////////////////////////////////////////////////////////////// 412 // // private members //// 413 414 private String _greetSh1 = "The outputs of the previous step.\nPlease double click on your selections to be sent to the next step\n"; 415 416 /** Container into which this plot should be placed. */ 417 private Container _container; 418 419 /** Indicator of the first time through. */ 420 private boolean _firstTime = true; 421 422 /** Frame into which plot is placed, if any. */ 423 private TableauFrame _frame; 424 425 /** The list of strings to send to the output. */ 426 private List _outputValues = new LinkedList(); 427 428 /** Flag indicating that "exit" or "quit" has been entered. */ 429 private boolean _returnFalseInPostfire = false; 430 431 // A specification for the window properties of the frame. 432 private WindowPropertiesAttribute _windowProperties; 433 434 // ///////////////////////////////////////////////////////////////// 435 // // private methods //// 436 437 /** 438 * Remove the shell from the current container, if there is one. 439 */ 440 private void _remove() { 441 SwingUtilities.invokeLater(new Runnable() { 442 public void run() { 443 if (shellPanel != null) { 444 if (_container != null) { 445 _container.remove(shellPanel); 446 _container.invalidate(); 447 _container.repaint(); 448 } else if (_frame != null) { 449 _frame.dispose(); 450 } 451 } 452 } 453 }); 454 } 455 456 // ///////////////////////////////////////////////////////////////// 457 // // inner classes //// 458 459 /** 460 * Version of ExpressionShellTableau that records the size of the display 461 * when it is closed. 462 */ 463 public class ShellTableau extends DoubleShellTAPTableau { 464 465 /** 466 * Construct a new tableau for the model represented by the given 467 * effigy. 468 * 469 * @param container 470 * The container. 471 * @param name 472 * The name. 473 * @exception IllegalActionException 474 * If the container does not accept this entity (this 475 * should not occur). 476 * @exception NameDuplicationException 477 * If the name coincides with an attribute already in the 478 * container. 479 */ 480 public ShellTableau(DoubleShellTAPEffigy container, String name) 481 throws IllegalActionException, NameDuplicationException { 482 super(container, name); 483 frame = new ShellFrame(this); 484 setFrame(frame); 485 frame.setTableau(this); 486 } 487 } 488 489 /** 490 * The frame that is created by an instance of ShellTableau. 491 */ 492 public class ShellFrame extends DoubleShellTAPTableauFrame { 493 494 /** 495 * Construct a frame to display the ExpressionShell window. Override the 496 * base class to handle window closing. After constructing this, it is 497 * necessary to call setVisible(true) to make the frame appear. This is 498 * typically accomplished by calling show() on enclosing tableau. 499 * 500 * @param tableau 501 * The tableau responsible for this frame. 502 * @exception IllegalActionException 503 * If the model rejects the configuration attribute. 504 * @exception NameDuplicationException 505 * If a name collision occurs. 506 */ 507 public ShellFrame(DoubleShellTAPTableau tableau) 508 throws IllegalActionException, NameDuplicationException { 509 super(tableau); 510 } 511 512 /** 513 * Overrides the base class to record the size and location of the 514 * frame. 515 * 516 * @return False if the user cancels on a save query. 517 */ 518 protected boolean _close() { 519 if (_frame != null) { 520 _windowProperties.setProperties(_frame); 521 } 522 // Return value can be ignored since there is no issue of saving. 523 super._close(); 524 place(null); 525 return true; 526 } 527 } 528}