001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package bsh.util;
033:
034: import java.awt.*;
035: import java.awt.event.*;
036: import java.io.*;
037: import java.util.Vector;
038: import bsh.*;
039:
040: /*
041: This should go away eventually... Native AWT sucks.
042: Use JConsole and the desktop() environment.
043:
044: Notes: todo -
045: clean up the watcher thread, set daemon status
046: */
047:
048: /**
049: An old AWT based console for BeanShell.
050:
051: I looked everwhere for one, and couldn't find anything that worked.
052: I've tried to keep this as small as possible, no frills.
053: (Well, one frill - a simple history with the up/down arrows)
054: My hope is that this can be moved to a lightweight (portable) component
055: with JFC soon... but Swing is still very slow and buggy.
056:
057: Done: see JConsole.java
058:
059: The big Hack:
060:
061: The heinous, disguisting hack in here is to keep the caret (cursor)
062: at the bottom of the text (without the user having to constantly click
063: at the bottom). It wouldn't be so bad if the damned setCaretPostition()
064: worked as expected. But the AWT TextArea for some insane reason treats
065: NLs as characters... oh, and it refuses to let you set a caret position
066: greater than the text length - for which it counts NLs as *one* character.
067: The glorious hack to fix this is to go the TextComponent peer. I really
068: hate this.
069:
070: Out of date:
071:
072: This class is out of date. It does not use the special blocking piped
073: input stream that the jconsole uses.
074:
075: Deprecation:
076:
077: This file uses two deprecate APIs. We want to be a PrintStream so
078: that we can redirect stdout to our console... I don't see a way around
079: this. Also we have to use getPeer() for the big hack above.
080: */
081: public class AWTConsole extends TextArea implements ConsoleInterface,
082: Runnable, KeyListener {
083:
084: private OutputStream outPipe;
085: private InputStream inPipe;
086:
087: // formerly public
088: private InputStream in;
089: private PrintStream out;
090:
091: public Reader getIn() {
092: return new InputStreamReader(in);
093: }
094:
095: public PrintStream getOut() {
096: return out;
097: }
098:
099: public PrintStream getErr() {
100: return out;
101: }
102:
103: private StringBuffer line = new StringBuffer();
104: private String startedLine;
105: private int textLength = 0;
106: private Vector history = new Vector();
107: private int histLine = 0;
108:
109: public AWTConsole(int rows, int cols, InputStream cin,
110: OutputStream cout) {
111: super (rows, cols);
112: setFont(new Font("Monospaced", Font.PLAIN, 14));
113: setEditable(false);
114: addKeyListener(this );
115:
116: outPipe = cout;
117: if (outPipe == null) {
118: outPipe = new PipedOutputStream();
119: try {
120: in = new PipedInputStream((PipedOutputStream) outPipe);
121: } catch (IOException e) {
122: print("Console internal error...");
123: }
124: }
125:
126: // start the inpipe watcher
127: inPipe = cin;
128: new Thread(this ).start();
129:
130: requestFocus();
131: }
132:
133: public void keyPressed(KeyEvent e) {
134: type(e.getKeyCode(), e.getKeyChar(), e.getModifiers());
135: e.consume();
136: }
137:
138: public AWTConsole() {
139: this (12, 80, null, null);
140: }
141:
142: public AWTConsole(InputStream in, OutputStream out) {
143: this (12, 80, in, out);
144: }
145:
146: public void type(int code, char ch, int modifiers) {
147: switch (code) {
148: case (KeyEvent.VK_BACK_SPACE ):
149: if (line.length() > 0) {
150: line.setLength(line.length() - 1);
151: replaceRange("", textLength - 1, textLength);
152: textLength--;
153: }
154: break;
155: case (KeyEvent.VK_ENTER ):
156: enter();
157: break;
158: case (KeyEvent.VK_U ):
159: if ((modifiers & InputEvent.CTRL_MASK) > 0) {
160: int len = line.length();
161: replaceRange("", textLength - len, textLength);
162: line.setLength(0);
163: histLine = 0;
164: textLength = getText().length();
165: } else
166: doChar(ch);
167: break;
168: case (KeyEvent.VK_UP ):
169: historyUp();
170: break;
171: case (KeyEvent.VK_DOWN ):
172: historyDown();
173: break;
174: case (KeyEvent.VK_TAB ):
175: line.append(" ");
176: append(" ");
177: textLength += 4;
178: break;
179: /*
180: case ( KeyEvent.VK_LEFT ):
181: if (line.length() > 0) {
182: break;
183: */
184: // Control-C
185: case (KeyEvent.VK_C ):
186: if ((modifiers & InputEvent.CTRL_MASK) > 0) {
187: line.append("^C");
188: append("^C");
189: textLength += 2;
190: } else
191: doChar(ch);
192: break;
193: default:
194: doChar(ch);
195: }
196: }
197:
198: private void doChar(char ch) {
199: if ((ch >= ' ') && (ch <= '~')) {
200: line.append(ch);
201: append(String.valueOf(ch));
202: textLength++;
203: }
204: }
205:
206: private void enter() {
207: String s;
208: if (line.length() == 0) // special hack for empty return!
209: s = ";\n";
210: else {
211: s = line + "\n";
212: history.addElement(line.toString());
213: }
214: line.setLength(0);
215: histLine = 0;
216: append("\n");
217: textLength = getText().length(); // sync for safety
218: acceptLine(s);
219:
220: setCaretPosition(textLength);
221: }
222:
223: /*
224: Here's the really disguisting hack.
225: We have to get to the peer because TextComponent will refuse to
226: let us set us set a caret position greater than the text length.
227: Great. What a piece of crap.
228: */
229: public void setCaretPosition(int pos) {
230: ((java.awt.peer.TextComponentPeer) getPeer())
231: .setCaretPosition(pos + countNLs());
232: }
233:
234: /*
235: This is part of a hack to fix the setCaretPosition() bug
236: Count the newlines in the text
237: */
238: private int countNLs() {
239: String s = getText();
240: int c = 0;
241: for (int i = 0; i < s.length(); i++)
242: if (s.charAt(i) == '\n')
243: c++;
244: return c;
245: }
246:
247: private void historyUp() {
248: if (history.size() == 0)
249: return;
250: if (histLine == 0) // save current line
251: startedLine = line.toString();
252: if (histLine < history.size()) {
253: histLine++;
254: showHistoryLine();
255: }
256: }
257:
258: private void historyDown() {
259: if (histLine == 0)
260: return;
261:
262: histLine--;
263: showHistoryLine();
264: }
265:
266: private void showHistoryLine() {
267: String showline;
268: if (histLine == 0)
269: showline = startedLine;
270: else
271: showline = (String) history.elementAt(history.size()
272: - histLine);
273:
274: replaceRange(showline, textLength - line.length(), textLength);
275: line = new StringBuffer(showline);
276: textLength = getText().length();
277: }
278:
279: private void acceptLine(String line) {
280: if (outPipe == null)
281: print("Console internal error...");
282: else
283: try {
284: outPipe.write(line.getBytes());
285: outPipe.flush();
286: } catch (IOException e) {
287: outPipe = null;
288: throw new RuntimeException("Console pipe broken...");
289: }
290: }
291:
292: public void println(Object o) {
293: print(String.valueOf(o) + "\n");
294: }
295:
296: public void error(Object o) {
297: print(o, Color.red);
298: }
299:
300: // No color
301: public void print(Object o, Color c) {
302: print("*** " + String.valueOf(o));
303: }
304:
305: synchronized public void print(Object o) {
306: append(String.valueOf(o));
307: textLength = getText().length(); // sync for safety
308: }
309:
310: private void inPipeWatcher() throws IOException {
311: if (inPipe == null) {
312: PipedOutputStream pout = new PipedOutputStream();
313: out = new PrintStream(pout);
314: inPipe = new PipedInputStream(pout);
315: }
316: byte[] ba = new byte[256]; // arbitrary blocking factor
317: int read;
318: while ((read = inPipe.read(ba)) != -1)
319: print(new String(ba, 0, read));
320:
321: println("Console: Input closed...");
322: }
323:
324: public void run() {
325: try {
326: inPipeWatcher();
327: } catch (IOException e) {
328: println("Console: I/O Error...");
329: }
330: }
331:
332: public static void main(String args[]) {
333: AWTConsole console = new AWTConsole();
334: final Frame f = new Frame("Bsh Console");
335: f.add(console, "Center");
336: f.pack();
337: f.show();
338: f.addWindowListener(new WindowAdapter() {
339: public void windowClosing(WindowEvent e) {
340: f.dispose();
341: }
342: });
343:
344: Interpreter interpreter = new Interpreter(console);
345: interpreter.run();
346: }
347:
348: public String toString() {
349: return "BeanShell AWTConsole";
350: }
351:
352: // unused
353: public void keyTyped(KeyEvent e) {
354: }
355:
356: public void keyReleased(KeyEvent e) {
357: }
358: }
|