001/* 002 * Copyright (c) 2004-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: welker $' 006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 007 * '$Revision: 24234 $' 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; 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.ExpressionShellEffigy; 045import ptolemy.actor.gui.ExpressionShellFrame; 046import ptolemy.actor.gui.ExpressionShellTableau; 047import ptolemy.actor.gui.Placeable; 048import ptolemy.actor.gui.Tableau; 049import ptolemy.actor.gui.TableauFactory; 050import ptolemy.actor.gui.TableauFrame; 051import ptolemy.actor.gui.WindowPropertiesAttribute; 052import ptolemy.data.StringToken; 053import ptolemy.data.type.BaseType; 054import ptolemy.gui.ShellInterpreter; 055import ptolemy.gui.ShellTextArea; 056import ptolemy.kernel.CompositeEntity; 057import ptolemy.kernel.util.IllegalActionException; 058import ptolemy.kernel.util.NameDuplicationException; 059import ptolemy.kernel.util.Nameable; 060import ptolemy.kernel.util.Workspace; 061 062////////////////////////////////////////////////////////////////////////// 063//// TextualInteractionShell 064/** 065 * This actor creates a command shell on the screen, sending commands that are 066 * typed by the user to its output port, and reporting strings received at its 067 * input by displaying them. Each time it fires, it reads the input, displays 068 * it, then displays a command prompt (which by default is ">>"), and waits 069 * for a command to be typed. The command is terminated by an enter or return 070 * character, which then results in the command being produced on the output. In 071 * a typical use of this actor, it will be preceded by a SampleDelay actor which 072 * will provide an initial welcome message or instructions. The output will then 073 * be routed to some subsystem for processing, and the result will be fed back 074 * to the input. 075 * <p> 076 * Note that because of complexities in Swing, if you resize the display window, 077 * then, unlike the plotters, the new size will not be persistent. That is, if 078 * you save the model and then re-open it, the new size is forgotten. The 079 * position, however, is persistent. 080 * 081 * @author Ilkay Altintas 082 * @version $Id: TextualInteractionShell.java 11161 2005-11-01 20:39:16Z ruland 083 * $ 084 * @category.name user interaction 085 * @category.name flow control 086 */ 087public class TextualInteractionShell extends TypedAtomicActor implements 088 Placeable, ShellInterpreter { 089 090 /** 091 * Construct an actor with the given container and name. 092 * 093 * @param container 094 * The container. 095 * @param name 096 * The name of this actor. 097 * @exception IllegalActionException 098 * If the actor cannot be contained by the proposed 099 * container. 100 * @exception NameDuplicationException 101 * If the container already has an actor with this name. 102 */ 103 public TextualInteractionShell(CompositeEntity container, String name) 104 throws IllegalActionException, NameDuplicationException { 105 super(container, name); 106 107 input = new TypedIOPort(this, "input", true, false); 108 input.setTypeEquals(BaseType.STRING); 109 110 output = new TypedIOPort(this, "output", false, true); 111 output.setTypeEquals(BaseType.STRING); 112 113 _windowProperties = new WindowPropertiesAttribute(this, 114 "_windowProperties"); 115 116 _attachText("_iconDescription", "<svg>\n" 117 + "<rect x=\"-20\" y=\"-20\" " + "width=\"40\" height=\"40\" " 118 + "style=\"fill:lightGrey\"/>\n" + "<rect x=\"-14\" y=\"-14\" " 119 + "width=\"28\" height=\"28\" " + "style=\"fill:white\"/>\n" 120 + "<polyline points=\"-10,-10, -5,-5, -10,0\" " 121 + "style=\"stroke:black\"/>\n" 122 + "<polyline points=\"-7,-10, -2,-5, -7,0\" " 123 + "style=\"stroke:black\"/>\n" + "</svg>\n"); 124 } 125 126 // ///////////////////////////////////////////////////////////////// 127 // // ports and parameters //// 128 129 /** 130 * @entity.description The input is a string. The actor will first display 131 * this in an interavice shell, than accept the 132 * expression and evaluate it. 133 */ 134 public TypedIOPort input; 135 136 /** 137 * @entity.description The output is a string that represents the output of 138 * the command that was entered in the upper shell. 139 */ 140 public TypedIOPort output; 141 142 /** The shell window object. */ 143 public ShellTextArea shell; 144 public ShellTextArea shell1; 145 146 public ShellTableau tableau; 147 public Effigy containerEffigy; 148 public Effigy midEffigy; 149 150 // ///////////////////////////////////////////////////////////////// 151 // // public methods //// 152 153 /** 154 * Clone the actor into the specified workspace. 155 * 156 * @param workspace 157 * The workspace for the new object. 158 * @return A new actor. 159 * @exception CloneNotSupportedException 160 * If a derived class has an attribute that cannot be cloned. 161 */ 162 public Object clone(Workspace workspace) throws CloneNotSupportedException { 163 TextualInteractionShell newObject = (TextualInteractionShell) super 164 .clone(workspace); 165 newObject.shell = null; 166 newObject.shell1 = null; 167 newObject._container = null; 168 newObject._frame = null; 169 return newObject; 170 } 171 172 /** 173 * Evaluate the specified command. 174 * 175 * @param command 176 * The command. 177 * @return The return value of the command, or null if there is none. 178 * @exception Exception 179 * If something goes wrong processing the command. 180 */ 181 public String evaluateCommand(String command) throws Exception { 182 // NOTE: This method is typically called in the swing event thread. 183 // Be careful to avoid locking up the UI. 184 setOutput(command); 185 // Return null to indicate that the command evaluation is not 186 // complete. This results in disabling editing on the text 187 // widget until returnResult() is called on it, which happens 188 // the next time fire() is called. 189 return null; 190 } 191 192 /** 193 * Read and display the input, then wait for user input and produce the user 194 * data on the output. If the user input is "quit" or "exit", then set a 195 * flag that causes postfire() to return false. 196 * 197 * @exception IllegalActionException 198 * If producing the output causes an exception. 199 */ 200 public void fire() throws IllegalActionException { 201 super.fire(); 202 if (input.numberOfSources() > 0 && input.hasToken(0)) { 203 String value = ((StringToken) input.get(0)).stringValue(); 204 // If window has been dismissed, there is nothing more to do. 205 if ((shell == null) || (shell1 == null)) 206 return; 207 if (_firstTime) { 208 _firstTime = false; 209 shell.initialize(value); 210 shell1 211 .initialize("Please enter the selected input from last step\'s output"); 212 } else { 213 shell.returnResult(value); 214 // shell1.returnResult(value); 215 } 216 } 217 shell.setEditable(false); 218 shell1.setEditable(true); 219 // FIXME: What to type if there is no input connected. 220 String userCommand = getOutput(); 221 if (userCommand.trim().equalsIgnoreCase("quit") 222 || userCommand.trim().equalsIgnoreCase("exit")) { 223 _returnFalseInPostfire = true; 224 } 225 output.broadcast(new StringToken(userCommand)); 226 } 227 228 /** 229 * Get the output string to be sent. This does not return until a value is 230 * entered on the shell by the user. 231 * 232 * @return The output string to be sent. 233 */ 234 public synchronized String getOutput() { 235 while (_outputValues.size() < 1 && !_stopRequested) { 236 try { 237 // NOTE: Do not call wait on this object directly! 238 // If another thread tries to get write access to the 239 // workspace, it will deadlock! This method releases 240 // all read accesses on the workspace before doing the 241 // wait. 242 workspace().wait(this); 243 } catch (InterruptedException ex) { 244 } 245 } 246 if (_stopRequested) { 247 return ("stop"); 248 } else { 249 return ((String) _outputValues.remove(0)); 250 } 251 } 252 253 /** 254 * If the shell has not already been created, create it. Then wait for user 255 * input and produce it on the output. 256 * 257 * @exception IllegalActionException 258 * If the parent class throws it. 259 */ 260 public void initialize() throws IllegalActionException { 261 super.initialize(); 262 containerEffigy = Configuration.findEffigy(toplevel()); 263 try { 264 midEffigy = new Effigy(containerEffigy, containerEffigy 265 .uniqueName("shell")); 266 } catch (NameDuplicationException ex) { 267 } 268 if (shell == null) { 269 // No container has been specified for the shell. 270 // Place the shell in its own frame. 271 // Need an effigy and a tableau so that menu ops work properly. 272 if (containerEffigy == null) { 273 throw new IllegalActionException(this, 274 "Cannot find effigy for top level: " 275 + toplevel().getFullName()); 276 } 277 try { 278 // ExpressionShellEffigy shellEffigy = new 279 // ExpressionShellEffigy( 280 ExpressionShellEffigy shellEffigy = new ExpressionShellEffigy( 281 // containerEffigy, containerEffigy.uniqueName("shell")); 282 midEffigy, midEffigy.uniqueName("shell")); 283 // The default identifier is "Unnamed", which is no good for 284 // two reasons: Wrong title bar label, and it causes a save-as 285 // to destroy the original window. 286 shellEffigy.identifier.setExpression(getFullName()); 287 // ShellTableau tableau = new ShellTableau( 288 tableau = new ShellTableau(shellEffigy, "tableau"); 289 _frame = tableau.frame; 290 shell = tableau.shell; 291 shell.setInterpreter(this); 292 // Prevent editing until the first firing. 293 shell.setEditable(false); 294 } catch (Exception ex) { 295 throw new IllegalActionException(this, null, ex, 296 "Error creating effigy and tableau"); 297 } 298 // _windowProperties.setProperties(_frame); 299 _frame.show(); 300 } else { 301 shell.clearJTextArea(); 302 } 303 if (shell1 == null) { 304 // No container has been specified for the shell. 305 // Place the shell in its own frame. 306 // Need an effigy and a tableau so that menu ops work properly. 307 // Effigy containerEffigy1 = Configuration.findEffigy(toplevel()); 308 // if (containerEffigy1 == null) { 309 if (containerEffigy == null) { 310 throw new IllegalActionException(this, 311 "Cannot find effigy for top level: " 312 + toplevel().getFullName()); 313 } 314 try { 315 ExpressionShellEffigy shellEffigy1 = new ExpressionShellEffigy( 316 // containerEffigy, containerEffigy.uniqueName("shell1")); 317 midEffigy, midEffigy.uniqueName("shell")); 318 // The default identifier is "Unnamed", which is no good for 319 // two reasons: Wrong title bar label, and it causes a save-as 320 // to destroy the original window. 321 shellEffigy1.identifier.setExpression(getFullName()); 322 ShellTableau tableau1 = new ShellTableau(shellEffigy1, 323 "tableau1"); 324 // tableau1.setContainer(shellEffigy); 325 326 _frame1 = tableau1.frame; 327 shell1 = tableau1.shell; 328 shell1.setInterpreter(this); 329 // Prevent editing until the first firing. 330 shell1.setEditable(false); 331 } catch (Exception ex) { 332 throw new IllegalActionException(this, null, ex, 333 "Error creating effigy and tableau"); 334 } 335 // _windowProperties.setProperties(_frame1); 336 _frame1.show(); 337 } else { 338 shell1.clearJTextArea(); 339 } 340 341 try { 342 TableauFactory factory = (TableauFactory) getAttribute("tableauFactory"); 343 Tableau midtableau = factory.createTableau(midEffigy); 344 midtableau.show(); 345 } catch (Exception ex) { 346 } 347 348 /* 349 * NOTE: This causes a bug where manual resizes of the window get 350 * overridden on re-run. if (_frame != null) { _frame.show(); } 351 */ 352 _firstTime = true; 353 _returnFalseInPostfire = false; 354 } 355 356 /** 357 * Return true if the specified command is complete (ready to be 358 * interpreted). 359 * 360 * @param command 361 * The command. 362 * @return True. 363 */ 364 public boolean isCommandComplete(String command) { 365 return true; 366 } 367 368 /** 369 * Specify the container into which this shell should be placed. This method 370 * needs to be called before the first call to initialize(). Otherwise, the 371 * shell will be placed in its own frame. The background of the plot is set 372 * equal to that of the container (unless it is null). 373 * 374 * @param container 375 * The container into which to place the shell, or null to 376 * specify that a new shell should be created. 377 */ 378 public void place(Container container) { 379 _container = container; 380 381 if (_container == null) { 382 // Dissociate with any container. 383 // NOTE: _remove() doesn't work here. Why? 384 if (_frame != null) 385 _frame.dispose(); 386 _frame = null; 387 _frame1 = null; 388 shell = null; 389 shell1 = null; 390 return; 391 } 392 shell = new ShellTextArea(); 393 shell.setInterpreter(this); 394 shell.clearJTextArea(); 395 shell.setEditable(false); 396 397 shell1 = new ShellTextArea(); 398 shell1.setInterpreter(this); 399 shell1.clearJTextArea(); 400 shell1.setEditable(false); 401 402 _container.add(shell); 403 _container.add(shell1); 404 // java.awt.Component.setBackground(color) says that 405 // if the color "parameter is null then this component 406 // will inherit the background color of its parent." 407 shell.setBackground(null); 408 shell1.setBackground(null); 409 } 410 411 /** 412 * Override the base class to return false if the user has typed "quit" or 413 * "exit". 414 * 415 * @return False if the user has typed "quit" or "exit". 416 * @exception IllegalActionException 417 * If the superclass throws it. 418 */ 419 public boolean postfire() throws IllegalActionException { 420 if (_returnFalseInPostfire) { 421 return false; 422 } 423 return super.postfire(); 424 } 425 426 /** 427 * Override the base class to remove the shell from its graphical container 428 * if the argument is null. 429 * 430 * @param container 431 * The proposed container. 432 * @exception IllegalActionException 433 * If the base class throws it. 434 * @exception NameDuplicationException 435 * If the base class throws it. 436 */ 437 public void setContainer(CompositeEntity container) 438 throws IllegalActionException, NameDuplicationException { 439 Nameable previousContainer = getContainer(); 440 super.setContainer(container); 441 if (container != previousContainer && previousContainer != null) { 442 _remove(); 443 } 444 } 445 446 /** 447 * Specify an output string to be sent. This method appends the specified 448 * string to a queue. Strings are retrieved from the queue by getOutput(). 449 * 450 * @see #getOutput() 451 * @param value 452 * An output string to be sent. 453 */ 454 public synchronized void setOutput(String value) { 455 _outputValues.add(value); 456 notifyAll(); 457 } 458 459 /** 460 * Override the base class to call notifyAll() to get out of any waiting. 461 */ 462 public void stop() { 463 super.stop(); 464 synchronized (this) { 465 notifyAll(); 466 } 467 } 468 469 /** 470 * Override the base class to make the shell uneditable. 471 * 472 * @exception IllegalActionException 473 * If the parent class throws it. 474 */ 475 public void wrapup() throws IllegalActionException { 476 super.wrapup(); 477 if (_returnFalseInPostfire && _frame != null) { 478 _frame.dispose(); 479 } else if (shell != null) { 480 shell.setEditable(false); 481 } 482 if (_returnFalseInPostfire && _frame1 != null) { 483 _frame1.dispose(); 484 } else if (shell1 != null) { 485 shell1.setEditable(false); 486 } 487 } 488 489 // ///////////////////////////////////////////////////////////////// 490 // // protected methods //// 491 492 /** 493 * Write a MoML description of the contents of this object. This overrides 494 * the base class to make sure that the current frame properties, if there 495 * is a frame, are recorded. 496 * 497 * @param output 498 * The output stream to write to. 499 * @param depth 500 * The depth in the hierarchy, to determine indenting. 501 * @exception IOException 502 * If an I/O error occurs. 503 */ 504 protected void _exportMoMLContents(Writer output, int depth) 505 throws IOException { 506 // Make sure that the current position of the frame, if any, 507 // is up to date. 508 if (_frame != null) { 509 _windowProperties.recordProperties(_frame); 510 } 511 super._exportMoMLContents(output, depth); 512 } 513 514 // ///////////////////////////////////////////////////////////////// 515 // // private members //// 516 517 /** Container into which this plot should be placed. */ 518 private Container _container; 519 520 /** Indicator of the first time through. */ 521 private boolean _firstTime = true; 522 523 /** Frame into which plot is placed, if any. */ 524 private TableauFrame _frame; 525 private TableauFrame _frame1; 526 private TableauFrame _frame2; 527 528 /** The list of strings to send to the output. */ 529 private List _outputValues = new LinkedList(); 530 531 /** Flag indicating that "exit" or "quit" has been entered. */ 532 private boolean _returnFalseInPostfire = false; 533 534 // A specification for the window properties of the frame. 535 private WindowPropertiesAttribute _windowProperties; 536 537 // ///////////////////////////////////////////////////////////////// 538 // // private methods //// 539 540 /** 541 * Remove the shell from the current container, if there is one. 542 */ 543 private void _remove() { 544 SwingUtilities.invokeLater(new Runnable() { 545 public void run() { 546 if (shell != null) { 547 if (_container != null) { 548 _container.remove(shell); 549 _container.invalidate(); 550 _container.repaint(); 551 } else if (_frame != null) { 552 _frame.dispose(); 553 } 554 } 555 } 556 }); 557 } 558 559 // ///////////////////////////////////////////////////////////////// 560 // // inner classes //// 561 562 /** 563 * Version of ExpressionShellTableau that records the size of the display 564 * when it is closed. 565 */ 566 public class ShellTableau extends ExpressionShellTableau { 567 568 /** 569 * Construct a new tableau for the model represented by the given 570 * effigy. 571 * 572 * @param container 573 * The container. 574 * @param name 575 * The name. 576 * @exception IllegalActionException 577 * If the container does not accept this entity (this 578 * should not occur). 579 * @exception NameDuplicationException 580 * If the name coincides with an attribute already in the 581 * container. 582 */ 583 public ShellTableau(ExpressionShellEffigy container, String name) 584 throws IllegalActionException, NameDuplicationException { 585 super(container, name); 586 frame = new ShellFrame(this); 587 setFrame(frame); 588 frame.setTableau(this); 589 } 590 } 591 592 /** 593 * The frame that is created by an instance of ShellTableau. 594 */ 595 public class ShellFrame extends ExpressionShellFrame { 596 597 /** 598 * Construct a frame to display the ExpressionShell window. Override the 599 * base class to handle window closing. After constructing this, it is 600 * necessary to call setVisible(true) to make the frame appear. This is 601 * typically accomplished by calling show() on enclosing tableau. 602 * 603 * @param tableau 604 * The tableau responsible for this frame. 605 * @exception IllegalActionException 606 * If the model rejects the configuration attribute. 607 * @exception NameDuplicationException 608 * If a name collision occurs. 609 */ 610 public ShellFrame(ExpressionShellTableau tableau) 611 throws IllegalActionException, NameDuplicationException { 612 super(tableau); 613 } 614 615 /** 616 * Overrides the base class to record the size and location of the 617 * frame. 618 * 619 * @return False if the user cancels on a save query. 620 */ 621 protected boolean _close() { 622 if (_frame != null) { 623 _windowProperties.setProperties(_frame); 624 } 625 // Return value can be ignored since there is no issue of saving. 626 super._close(); 627 place(null); 628 return true; 629 } 630 } 631}