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.BorderLayout; 033import java.awt.Color; 034import java.awt.Cursor; 035import java.awt.event.InputEvent; 036import java.awt.event.KeyAdapter; 037import java.awt.event.KeyEvent; 038import java.awt.event.WindowAdapter; 039import java.awt.event.WindowEvent; 040import java.awt.event.WindowListener; 041import java.util.Vector; 042 043import javax.swing.BorderFactory; 044import javax.swing.JFrame; 045import javax.swing.JPanel; 046import javax.swing.JScrollPane; 047import javax.swing.JSplitPane; 048import javax.swing.JTextArea; 049import javax.swing.SwingUtilities; 050 051import ptolemy.gui.MessageHandler; 052import ptolemy.gui.ShellInterpreter; 053 054////////////////////////////////////////////////////////////////////////// 055//// DoubleShellTextArea 056 057/** 058 * A read-only text area showing the result of the previous step and a text area 059 * supporting shell-style interactions. 060 * 061 * @author Ilkay Altintas 062 * @version $Id: DoubleShellTextAreaPanel.java 11161 2005-11-01 20:39:16Z ruland 063 * $ 064 */ 065public class DoubleShellTextAreaPanel extends JPanel { 066 067 /** 068 * Create a new instance with no initial message. 069 */ 070 public DoubleShellTextAreaPanel() { 071 this(null); 072 } 073 074 /** 075 * Create a new instance with the specified initial message. 076 * 077 * @param initialMessage 078 * The initial message. 079 */ 080 public DoubleShellTextAreaPanel(String initialMessage) { 081 // Graphics 082 super(new BorderLayout()); 083 setBorder(BorderFactory.createTitledBorder(BorderFactory 084 .createLineBorder(Color.black), "")); 085 086 // FIXME: Border and shell size needs to be configurable. 087 shell1 = new JTextArea("", 20, 80); 088 JScrollPane jSP_up = new JScrollPane(shell1); 089 // add(jSP1, BorderLayout.NORTH); 090 shell2 = new JTextArea("", 20, 80); 091 JScrollPane jSP_down = new JScrollPane(shell2); 092 // add(jSP2, BorderLayout.SOUTH); 093 JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, jSP_up, 094 jSP_down); 095 pane.setDividerLocation(0.5); 096 add(pane, BorderLayout.CENTER); 097 098 shell2.addKeyListener(new ShellKeyListener()); 099 } 100 101 public static void main(String[] args) { 102 JFrame jFrame = new JFrame("ShellTextArea Example"); 103 WindowListener windowListener = new WindowAdapter() { 104 public void windowClosing(WindowEvent e) { 105 System.exit(0); 106 } 107 }; 108 jFrame.addWindowListener(windowListener); 109 110 final DoubleShellTextAreaPanel exec = new DoubleShellTextAreaPanel(); 111 jFrame.getContentPane().add(exec); 112 jFrame.setSize(600, 400); 113 jFrame.pack(); 114 jFrame.show(); 115 } 116 117 /** 118 * Override the base class to output the first prompt. We need to do this 119 * here because we can't write to the TextArea until the peer has been 120 * created. 121 */ 122 public void addNotify() { 123 super.addNotify(); 124 initialize(_initialMessage); 125 } 126 127 /** 128 * Set the associated text area editable (with a true argument) or not 129 * editable (with a false argument). This should be called in the swing 130 * event thread. 131 * 132 * @param editable 133 * True to make the text area editable, false to make it 134 * uneditable. 135 */ 136 public void setEditable() { 137 shell2.setEditable(true); 138 shell1.setEditable(false); 139 140 } 141 142 /** 143 * Append the specified text to the JTextArea and update the prompt cursor. 144 * The text will actually be appended in the swing thread, not immediately. 145 * This method immediately returns. 146 * 147 * @param text 148 * The text to append to the text area. 149 */ 150 public void appendShell2(final String text) { 151 Runnable doAppendSh2 = new Runnable() { 152 public void run() { 153 shell2.append(text); 154 // Scroll down as we generate text. 155 shell2.setCaretPosition(shell2.getText().length()); 156 // To prevent _promptCursor from being 157 // updated before the JTextArea is actually updated, 158 // this needs to be inside the Runnable. 159 _promptCursor += text.length(); 160 } 161 }; 162 SwingUtilities.invokeLater(doAppendSh2); 163 } 164 165 public void appendShell1(final String text) { 166 shell1.setEditable(true); 167 Runnable doAppendSh1 = new Runnable() { 168 public void run() { 169 shell1.append(text); 170 // Scroll down as we generate text. 171 shell1.setCaretPosition(shell1.getText().length()); 172 } 173 }; 174 SwingUtilities.invokeLater(doAppendSh1); 175 } 176 177 /** 178 * Clear the JTextArea and reset the prompt cursor. The clearing is done in 179 * the swing thread, not immediately. This method immediately returns. 180 */ 181 public void clearShell1() { 182 Runnable doClearShell1 = new Runnable() { 183 public void run() { 184 // shell1.setText(_greetSh1); 185 shell1.setCaretPosition(0); 186 } 187 }; 188 SwingUtilities.invokeLater(doClearShell1); 189 } 190 191 /** 192 * Clear the JTextArea and reset the prompt cursor. The clearing is done in 193 * the swing thread, not immediately. This method immediately returns. 194 */ 195 public void clearShell2() { 196 Runnable doClearShell2 = new Runnable() { 197 public void run() { 198 shell2.setText(""); 199 shell2.setCaretPosition(0); 200 _promptCursor = 0; 201 } 202 }; 203 SwingUtilities.invokeLater(doClearShell2); 204 } 205 206 /** 207 * Initialize the text area with the given starting message, followed by a 208 * prompt. If the argument is null or the empty string, then only a prompt 209 * is shown. 210 * 211 * @param initialMessage 212 * The initial message. 213 */ 214 public void initialize(String initialMessage) { 215 clearShell1(); 216 appendShell1(initialMessage + "\n" + _greetSh1 + "\n"); 217 clearShell2(); 218 appendShell2(mainPrompt); 219 shell1.setEditable(false); 220 } 221 222 /** 223 * Replace a range in the JTextArea. 224 */ 225 public void replaceRangeJTextArea(final String text, final int start, 226 final int end) { 227 Runnable doReplaceRangeJTextArea = new Runnable() { 228 public void run() { 229 shell2.replaceRange(text, start, end); 230 } 231 }; 232 SwingUtilities.invokeLater(doReplaceRangeJTextArea); 233 } 234 235 /** 236 * Return the result of a command evaluation. This method is used when it is 237 * impractical to insist on the result being returned by evaluateCommand() 238 * of a ShellInterpreter. For example, computing the result may take a 239 * while. 240 * 241 * @param result 242 * The result to return. 243 */ 244 public void returnResult(final String result) { 245 // Make the text area editable again. 246 Runnable doMakeEditable = new Runnable() { 247 public void run() { 248 setEditable(); 249 String toPrint = result + "\n" + _greetSh1; 250 appendShell1(toPrint); 251 appendShell2("\n" + mainPrompt); 252 } 253 }; 254 SwingUtilities.invokeLater(doMakeEditable); 255 } 256 257 /** 258 * Set the interpreter. 259 * 260 * @param interpreter 261 * The interpreter. 262 * @see #getInterpreter() 263 */ 264 public void setInterpreter(ShellInterpreter interpreter) { 265 _interpreter = interpreter; 266 } 267 268 /** 269 * Get the interpreter that has been registered with setInterpreter(). 270 * 271 * @return The interpreter, or null if none has been set. 272 * @see #setInterpreter(ShellInterpreter) 273 */ 274 public ShellInterpreter getInterpreter() { 275 return _interpreter; 276 } 277 278 // ///////////////////////////////////////////////////////////////// 279 // // public variables //// 280 281 public JTextArea shell1; 282 public JTextArea shell2; 283 284 /** Main prompt. */ 285 public String mainPrompt = ">> "; 286 287 /** Size of the history to keep. */ 288 public int historyLength = 20; 289 290 // ///////////////////////////////////////////////////////////////// 291 // // private methods //// 292 293 // Update the command history. 294 private void _updateHistory(String command) { 295 _historyCursor = 0; 296 if (_historyCommands.size() == historyLength) { 297 _historyCommands.removeElementAt(0); 298 } 299 _historyCommands.addElement(command); 300 } 301 302 // Evaluate the command so far, if possible, printing 303 // a continuation prompt if not. 304 // NOTE: This must be called in the swing event thread. 305 private void _evalCommand() { 306 String newtext = shell2.getText().substring(_promptCursor); 307 _promptCursor += newtext.length(); 308 if (_commandBuffer.length() > 0) { 309 _commandBuffer.append("\n"); 310 } 311 _commandBuffer.append(newtext); 312 String command = _commandBuffer.toString(); 313 314 if (_interpreter == null) { 315 appendShell2("\n" + mainPrompt); 316 } else { 317 if (_interpreter.isCommandComplete(command)) { 318 // Process it 319 appendShell2("\n"); 320 Cursor oldCursor = shell2.getCursor(); 321 shell2.setCursor(new Cursor(Cursor.WAIT_CURSOR)); 322 String result; 323 try { 324 result = _interpreter.evaluateCommand(command); 325 } catch (RuntimeException e) { 326 // RuntimeException are due to bugs in the expression 327 // evaluation code, so we make the stack trace available. 328 MessageHandler.error("Failed to evaluate expression", e); 329 result = "Internal error evaluating expression."; 330 throw e; 331 } catch (Exception e) { 332 result = e.getMessage(); 333 // NOTE: Not ideal here to print the stack trace, but 334 // if we don't, it will be invisible, which makes 335 // debugging hard. 336 // e.printStackTrace(); 337 } 338 if (result != null) { 339 if (result.trim().equals("")) { 340 appendShell2(mainPrompt); 341 } else { 342 appendShell2(result + "\n" + mainPrompt); 343 } 344 } else { 345 // Result is incomplete. 346 // Make the text uneditable to prevent further input 347 // until returnResult() is called. 348 // NOTE: We are assuming this called in the swing thread. 349 setEditable(); 350 } 351 _commandBuffer.setLength(0); 352 shell2.setCursor(oldCursor); 353 _updateHistory(command); 354 } else { 355 appendShell2("\n" + contPrompt); 356 } 357 } 358 } 359 360 // ///////////////////////////////////////////////////////////////// 361 // // private variables //// 362 363 // The initial message, if there is one. 364 private String _initialMessage = null; 365 private String _greetSh1 = "The outputs of the previous step.\nPlease double click on your selections to be sent to the next step\n"; 366 367 private int _promptCursor = 0; 368 369 /** Prompt to use on continuation lines. */ 370 public String contPrompt = ""; 371 372 // The command input 373 private StringBuffer _commandBuffer = new StringBuffer(); 374 375 // The interpreter. 376 private ShellInterpreter _interpreter; 377 378 // History 379 private int _historyCursor = 0; 380 private Vector _historyCommands = new Vector(); 381 382 // ///////////////////////////////////////////////////////////////// 383 // // inner classes //// 384 // The key listener 385 private class ShellKeyListener extends KeyAdapter { 386 public void keyTyped(KeyEvent keyEvent) { 387 switch (keyEvent.getKeyCode()) { 388 case KeyEvent.VK_UNDEFINED: 389 if (keyEvent.getKeyChar() == '\b') { 390 if (shell2.getCaretPosition() == _promptCursor) { 391 keyEvent.consume(); // don't backspace over prompt! 392 } 393 } 394 break; 395 396 case KeyEvent.VK_BACK_SPACE: 397 if (shell2.getCaretPosition() == _promptCursor) { 398 keyEvent.consume(); // don't backspace over prompt! 399 } 400 break; 401 default: 402 } 403 } 404 405 public void keyReleased(KeyEvent keyEvent) { 406 switch (keyEvent.getKeyCode()) { 407 case KeyEvent.VK_BACK_SPACE: 408 if (shell2.getCaretPosition() == _promptCursor) { 409 keyEvent.consume(); // don't backspace over prompt! 410 } 411 break; 412 default: 413 } 414 } 415 416 public void keyPressed(KeyEvent keyEvent) { 417 // Process keys 418 switch (keyEvent.getKeyCode()) { 419 case KeyEvent.VK_ENTER: 420 keyEvent.consume(); 421 _evalCommand(); 422 break; 423 case KeyEvent.VK_BACK_SPACE: 424 if (shell2.getCaretPosition() <= _promptCursor) { 425 // FIXME: Consuming the event is useless... 426 // The backspace still occurs. Why? Java bug? 427 keyEvent.consume(); // don't backspace over prompt! 428 } 429 break; 430 case KeyEvent.VK_LEFT: 431 if (shell2.getCaretPosition() == _promptCursor) { 432 keyEvent.consume(); 433 } 434 break; 435 case KeyEvent.VK_HOME: 436 shell2.setCaretPosition(_promptCursor); 437 keyEvent.consume(); 438 break; 439 default: 440 switch (keyEvent.getModifiers()) { 441 case InputEvent.CTRL_MASK: 442 switch (keyEvent.getKeyCode()) { 443 case KeyEvent.VK_A: 444 shell2.setCaretPosition(_promptCursor); 445 keyEvent.consume(); 446 break; 447 default: 448 } 449 break; 450 default: 451 // Otherwise we got a regular character. 452 // Don't consume it, and TextArea will 453 // take care of displaying it. 454 } 455 } 456 } 457 } 458}