001/* A text area for shell-style interactions.
002
003 Copyright (c) 1998-2018 The Regents of the University of California.
004 All rights reserved.
005
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
009 above copyright notice and the following two paragraphs appear in all
010 copies 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 */
028package ptolemy.gui;
029
030import java.awt.BorderLayout;
031import java.awt.Color;
032import java.awt.Font;
033import java.awt.event.KeyAdapter;
034import java.awt.event.KeyEvent;
035import java.awt.event.WindowAdapter;
036import java.awt.event.WindowEvent;
037import java.awt.event.WindowListener;
038
039import javax.swing.BorderFactory;
040import javax.swing.JFrame;
041import javax.swing.JPanel;
042import javax.swing.JScrollPane;
043import javax.swing.JTextArea;
044import javax.swing.SwingUtilities;
045
046import ptolemy.util.StringUtilities;
047
048///////////////////////////////////////////////////////////////////
049//// UserDialog
050
051/**
052 A panel with two text areas, one for user input, and one in which
053 to display responses.
054
055 @author Edward A. Lee
056 @version $Id$
057 @since Ptolemy II 11.0
058 @Pt.ProposedRating Red (cxh)
059 @Pt.AcceptedRating Red (cxh)
060 */
061@SuppressWarnings("serial")
062public class UserDialog extends JPanel {
063    /** Create a new instance with no initial message.
064     */
065    public UserDialog() {
066        this(null);
067    }
068
069    /** Create a new instance with the specified initial message.
070     *  @param initialMessage The initial message.
071     */
072    public UserDialog(String initialMessage) {
073        // Graphics
074        super(new BorderLayout());
075        _initialMessage = initialMessage;
076
077        // Create a one-line input text area.
078        // FIXME: Size needs to be configurable.
079        _userInputTextArea = new JTextArea("", 1, 80);
080        // Event handling
081        _userInputTextArea.addKeyListener(new ShellKeyListener());
082        add(_userInputTextArea, BorderLayout.PAGE_START);
083
084        // Now create the responses text area.
085        // FIXME: Size needs to be configurable.
086        _responseTextArea = new JTextArea("", 20, 80);
087        _responseTextArea.setEditable(false);
088        _jScrollPane = new JScrollPane(_responseTextArea);
089        add(_jScrollPane, BorderLayout.PAGE_END);
090
091        // Set the fonts.
092        _userInputTextArea.setFont(new Font("Monospaced", 0, 14));
093        _responseTextArea.setFont(new Font("Monospaced", 0, 14));
094
095        _userInputTextArea.setBorder(BorderFactory.createTitledBorder(
096                BorderFactory.createLineBorder(Color.black), ""));
097        _responseTextArea.setBorder(BorderFactory.createTitledBorder(
098                BorderFactory.createLineBorder(Color.black), ""));
099    }
100
101    ///////////////////////////////////////////////////////////////////
102    ////                         public methods                    ////
103
104    /** Override the base class to output the first prompt.
105     *  We need to do this here because we can't write to
106     *  the TextArea until the peer has been created.
107     */
108    @Override
109    public void addNotify() {
110        super.addNotify();
111        initialize(_initialMessage);
112    }
113
114    /** Append the specified text to the response text area with a newline at the end.
115     *  The text will actually be appended in the swing thread, not immediately.
116     *  This method immediately returns.
117     *  @param text The text to append to the text area.
118     */
119    public void appendText(final String text) {
120        Runnable doAppendJTextArea = new Runnable() {
121            @Override
122            public void run() {
123                _responseTextArea.append(text + "\n");
124                // Scroll down as we generate text.
125                _responseTextArea
126                        .setCaretPosition(_responseTextArea.getText().length());
127            }
128        };
129        SwingUtilities.invokeLater(doAppendJTextArea);
130    }
131
132    /** Get the interpreter that has been registered with setInterpreter().
133     *  @return The interpreter, or null if none has been set.
134     *  @see #setInterpreter(ShellInterpreter)
135     */
136    public ShellInterpreter getInterpreter() {
137        return _interpreter;
138    }
139
140    /** Initialize the text area with the given starting message.
141     *  This is just like appendText(), except that it clears the display first.
142     *  @param initialMessage The initial message.
143     */
144    public void initialize(final String initialMessage) {
145        if (_responseTextArea == null) {
146            _initialMessage = initialMessage;
147        } else {
148            _initialMessage = null;
149            Runnable doInitialize = new Runnable() {
150                @Override
151                public void run() {
152                    _userInputTextArea.setText("");
153                    _userInputTextArea.setCaretPosition(0);
154                    if (initialMessage != null) {
155                        _responseTextArea.setText(initialMessage + "\n");
156                    } else {
157                        _responseTextArea.setText("");
158                    }
159                }
160            };
161            SwingUtilities.invokeLater(doInitialize);
162        }
163    }
164
165    /** Main method used for testing. To run a simple test, use:
166     *  <pre>
167     *        java -classpath $PTII ptolemy.gui.UserDialog
168     *  </pre>
169     *  @param args Currently ignored.
170     */
171    public static void main(String[] args) {
172        try {
173            // Run this in the Swing Event Thread.
174            Runnable doActions = new Runnable() {
175                @Override
176                public void run() {
177                    try {
178                        JFrame jFrame = new JFrame("UserDialog Example");
179                        WindowListener windowListener = new WindowAdapter() {
180                            @Override
181                            public void windowClosing(WindowEvent e) {
182                                StringUtilities.exit(0);
183                            }
184                        };
185
186                        jFrame.addWindowListener(windowListener);
187
188                        final UserDialog exec = new UserDialog();
189                        jFrame.getContentPane().add(exec);
190                        jFrame.pack();
191                        jFrame.setVisible(true);
192                    } catch (Exception ex) {
193                        System.err.println(ex.toString());
194                        ex.printStackTrace();
195                    }
196                }
197            };
198            SwingUtilities.invokeAndWait(doActions);
199        } catch (Exception ex) {
200            System.err.println(ex.toString());
201            ex.printStackTrace();
202        }
203    }
204
205    /** Set the interpreter.
206     *  @param interpreter The interpreter.
207     *  @see #getInterpreter()
208     */
209    public void setInterpreter(ShellInterpreter interpreter) {
210        _interpreter = interpreter;
211    }
212
213    ///////////////////////////////////////////////////////////////////
214    ////                         private methods                   ////
215
216    // Evaluate the command.
217    // NOTE: This must be called in the swing event thread.
218    private void _evalCommand() {
219        String command = _userInputTextArea.getText();
220
221        if (_interpreter != null) {
222            // Process it.
223            // Clear the command text area.
224            _userInputTextArea.setText("");
225            appendText(command);
226
227            String result;
228
229            try {
230                result = _interpreter.evaluateCommand(command);
231                if (result != null && !result.trim().equals("")) {
232                    appendText(result);
233                }
234            } catch (Throwable e) {
235                appendText("ERROR: " + e.toString());
236            }
237        }
238    }
239
240    ///////////////////////////////////////////////////////////////////
241    ////                         private variables                 ////
242
243    /** The scroll pane containing the output text. */
244    private JScrollPane _jScrollPane;
245
246    // The initial message, if there is one.
247    private String _initialMessage = null;
248
249    // The interpreter.
250    private ShellInterpreter _interpreter;
251
252    // The TextArea widget for responses.
253    private JTextArea _responseTextArea;
254
255    // The TextArea widget for user input.
256    private JTextArea _userInputTextArea;
257
258    ///////////////////////////////////////////////////////////////////
259    ////                         inner classes                     ////
260
261    // The key listener
262    private class ShellKeyListener extends KeyAdapter {
263        @Override
264        public void keyPressed(KeyEvent keyEvent) {
265            // Process keys
266            switch (keyEvent.getKeyCode()) {
267            case KeyEvent.VK_ENTER:
268                // Consume the keypress so it is not displayed.
269                keyEvent.consume();
270                _evalCommand();
271                break;
272            }
273        }
274    }
275}