bsh.util.JConsole Maven / Gradle / Ivy
The newest version!
/*
* #%L
* The AIBench Shell Plugin
* %%
* Copyright (C) 2006 - 2017 Daniel Glez-Peña and Florentino Fdez-Riverola
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
/*****************************************************************************
* *
* This file is part of the BeanShell Java Scripting distribution. *
* Documentation and updates may be found at http://www.beanshell.org/ *
* *
* Sun Public License Notice: *
* *
* The contents of this file are subject to the Sun Public License Version *
* 1.0 (the "License"); you may not use this file except in compliance with *
* the License. A copy of the License is available at http://www.sun.com *
* *
* The Original Code is BeanShell. The Initial Developer of the Original *
* Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
* (C) 2000. All Rights Reserved. *
* *
* GNU Public License Notice: *
* *
* Alternatively, the contents of this file may be used under the terms of *
* the GNU Lesser General Public License (the "LGPL"), in which case the *
* provisions of LGPL are applicable instead of those above. If you wish to *
* allow use of your version of this file only under the terms of the LGPL *
* and not to allow others to use your version of this file under the SPL, *
* indicate your decision by deleting the provisions above and replace *
* them with the notice and other provisions required by the LGPL. If you *
* do not delete the provisions above, a recipient may use your version of *
* this file under either the SPL or the LGPL. *
* *
* Patrick Niemeyer ([email protected]) *
* Author of Learning Java, O'Reilly & Associates *
* http://www.pat.net/~pat/ *
* *
*****************************************************************************/
package bsh.util;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.util.Vector;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
/**
* A JFC/Swing based console for the BeanShell desktop. This is a descendant of
* the old AWTConsole.
*
* Improvements by: Mark Donszelmann <[email protected]> including Cut &
* Paste
*
* Improvements by: Daniel Leuck including Color and Image support, key press
* bug workaround
*/
public class JConsole extends JScrollPane implements GUIConsoleInterface, Runnable, KeyListener, MouseListener, ActionListener, PropertyChangeListener {
private static final long serialVersionUID = 1L;
private final static String CUT = "Cut";
private final static String COPY = "Copy";
private final static String PASTE = "Paste";
private OutputStream outPipe;
private InputStream inPipe;
private InputStream in;
private PrintStream out;
public InputStream getInputStream() {
return in;
}
public Reader getIn() {
return new InputStreamReader(in);
}
public PrintStream getOut() {
return out;
}
public PrintStream getErr() {
return out;
}
private int cmdStart = 0;
private Vector history = new Vector();
private String startedLine;
private int histLine = 0;
private JPopupMenu menu;
private JTextPane text;
private DefaultStyledDocument doc;
NameCompletion nameCompletion;
final int SHOW_AMBIG_MAX = 10;
// hack to prevent key repeat for some reason?
private boolean gotUp = true;
public JConsole() {
this(null, null);
}
public JConsole(InputStream cin, OutputStream cout) {
super();
// Special TextPane which catches for cut and paste, both L&F
// keys and
// programmatic behaviour
text = new JTextPane(doc = new DefaultStyledDocument()) {
public void cut() {
if (text.getCaretPosition() < cmdStart) {
super.copy();
} else {
super.cut();
}
}
public void paste() {
forceCaretMoveToEnd();
super.paste();
}
};
Font font = new Font("Monospaced", Font.PLAIN, 14);
text.setText("");
text.setFont(font);
text.setMargin(new Insets(7, 5, 7, 5));
text.addKeyListener(this);
setViewportView(text);
// create popup menu
menu = new JPopupMenu("JConsole Menu");
menu.add(new JMenuItem(CUT)).addActionListener(this);
menu.add(new JMenuItem(COPY)).addActionListener(this);
menu.add(new JMenuItem(PASTE)).addActionListener(this);
text.addMouseListener(this);
// make sure popup menu follows Look & Feel
UIManager.addPropertyChangeListener(this);
outPipe = cout;
if (outPipe == null) {
outPipe = new PipedOutputStream();
try {
in = new PipedInputStream((PipedOutputStream) outPipe);
} catch (IOException e) {
print("Console internal error (1)...", Color.red);
}
}
inPipe = cin;
if (inPipe == null) {
PipedOutputStream pout = new PipedOutputStream();
out = new PrintStream(pout);
try {
inPipe = new BlockingPipedInputStream(pout);
} catch (IOException e) {
print("Console internal error: " + e);
}
}
// Start the inpipe watcher
new Thread(this).start();
requestFocus();
}
public void requestFocus() {
super.requestFocus();
text.requestFocus();
}
public void keyPressed(KeyEvent e) {
type(e);
gotUp = false;
}
public void keyTyped(KeyEvent e) {
type(e);
}
public void keyReleased(KeyEvent e) {
gotUp = true;
type(e);
}
private synchronized void type(KeyEvent e) {
switch (e.getKeyCode()) {
case (KeyEvent.VK_ENTER):
if (e.getID() == KeyEvent.KEY_PRESSED) {
if (gotUp) {
enter();
resetCommandStart();
text.setCaretPosition(cmdStart);
}
}
e.consume();
text.repaint();
break;
case (KeyEvent.VK_UP):
if (e.getID() == KeyEvent.KEY_PRESSED) {
historyUp();
}
e.consume();
break;
case (KeyEvent.VK_DOWN):
if (e.getID() == KeyEvent.KEY_PRESSED) {
historyDown();
}
e.consume();
break;
case (KeyEvent.VK_LEFT):
case (KeyEvent.VK_BACK_SPACE):
case (KeyEvent.VK_DELETE):
if (text.getCaretPosition() <= cmdStart) {
// This doesn't work for backspace.
// See default case for workaround
e.consume();
}
break;
case (KeyEvent.VK_RIGHT):
forceCaretMoveToStart();
break;
case (KeyEvent.VK_HOME):
text.setCaretPosition(cmdStart);
e.consume();
break;
case (KeyEvent.VK_U): // clear line
if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0) {
replaceRange("", cmdStart, textLength());
histLine = 0;
e.consume();
}
break;
case (KeyEvent.VK_ALT):
case (KeyEvent.VK_CAPS_LOCK):
case (KeyEvent.VK_CONTROL):
case (KeyEvent.VK_META):
case (KeyEvent.VK_SHIFT):
case (KeyEvent.VK_PRINTSCREEN):
case (KeyEvent.VK_SCROLL_LOCK):
case (KeyEvent.VK_PAUSE):
case (KeyEvent.VK_INSERT):
case (KeyEvent.VK_F1):
case (KeyEvent.VK_F2):
case (KeyEvent.VK_F3):
case (KeyEvent.VK_F4):
case (KeyEvent.VK_F5):
case (KeyEvent.VK_F6):
case (KeyEvent.VK_F7):
case (KeyEvent.VK_F8):
case (KeyEvent.VK_F9):
case (KeyEvent.VK_F10):
case (KeyEvent.VK_F11):
case (KeyEvent.VK_F12):
case (KeyEvent.VK_ESCAPE):
// only modifier pressed
break;
// Control-C
case (KeyEvent.VK_C):
if (text.getSelectedText() == null) {
if (((e.getModifiers() & InputEvent.CTRL_MASK) > 0) && (e.getID() == KeyEvent.KEY_PRESSED)) {
append("^C");
}
e.consume();
}
break;
case (KeyEvent.VK_TAB):
if (e.getID() == KeyEvent.KEY_RELEASED) {
String part = text.getText().substring(cmdStart);
doCommandCompletion(part);
}
e.consume();
break;
default:
if ((e.getModifiers() & (InputEvent.CTRL_MASK | InputEvent.ALT_MASK | InputEvent.META_MASK)) == 0) {
// plain character
forceCaretMoveToEnd();
}
/*
* The getKeyCode function always returns VK_UNDEFINED for keyTyped
* events, so backspace is not fully consumed.
*/
if (e.paramString().indexOf("Backspace") != -1) {
if (text.getCaretPosition() <= cmdStart) {
e.consume();
break;
}
}
break;
}
}
private void doCommandCompletion(String part) {
if (nameCompletion == null)
return;
int i = part.length() - 1;
// Character.isJavaIdentifierPart() How convenient for us!!
while (i >= 0 && (Character.isJavaIdentifierPart(part.charAt(i)) || part.charAt(i) == '.'))
i--;
part = part.substring(i + 1);
if (part.length() < 2) // reasonable completion length
return;
// System.out.println("completing part: "+part);
// no completion
String[] complete = nameCompletion.completeName(part);
if (complete.length == 0) {
java.awt.Toolkit.getDefaultToolkit().beep();
return;
}
// Found one completion (possibly what we already have)
if (complete.length == 1 && !complete.equals(part)) {
String append = complete[0].substring(part.length());
append(append);
return;
}
// Found ambiguous, show (some of) them
String line = text.getText();
String command = line.substring(cmdStart);
// Find prompt
for (i = cmdStart; line.charAt(i) != '\n' && i > 0; i--)
;
String prompt = line.substring(i + 1, cmdStart);
// Show ambiguous
StringBuffer sb = new StringBuffer("\n");
for (i = 0; i < complete.length && i < SHOW_AMBIG_MAX; i++)
sb.append(complete[i] + "\n");
if (i == SHOW_AMBIG_MAX)
sb.append("...\n");
print(sb, Color.gray);
print(prompt); // print resets command start
append(command); // append does not reset command start
}
private void resetCommandStart() {
cmdStart = textLength();
}
private void append(String string) {
int slen = textLength();
text.select(slen, slen);
text.replaceSelection(string);
}
private String replaceRange(Object s, int start, int end) {
String st = s.toString();
text.select(start, end);
text.replaceSelection(st);
// text.repaint();
return st;
}
private void forceCaretMoveToEnd() {
if (text.getCaretPosition() < cmdStart) {
// move caret first!
text.setCaretPosition(textLength());
}
text.repaint();
}
private void forceCaretMoveToStart() {
if (text.getCaretPosition() < cmdStart) {
// move caret first!
}
text.repaint();
}
private void enter() {
String s = getCmd();
if (s.length() == 0) // special hack for empty return!
s = ";\n";
else {
history.addElement(s);
s = s + "\n";
}
append("\n");
histLine = 0;
acceptLine(s);
text.repaint();
}
private String getCmd() {
String s = "";
try {
s = text.getText(cmdStart, textLength() - cmdStart);
} catch (BadLocationException e) {
// should not happen
System.out.println("Internal JConsole Error: " + e);
}
return s;
}
private void historyUp() {
if (history.size() == 0)
return;
if (histLine == 0) // save current line
startedLine = getCmd();
if (histLine < history.size()) {
histLine++;
showHistoryLine();
}
}
private void historyDown() {
if (histLine == 0)
return;
histLine--;
showHistoryLine();
}
private void showHistoryLine() {
String showline;
if (histLine == 0)
showline = startedLine;
else
showline = (String) history.elementAt(history.size() - histLine);
replaceRange(showline, cmdStart, textLength());
text.setCaretPosition(textLength());
text.repaint();
}
String ZEROS = "000";
private void acceptLine(String line) {
// Patch to handle Unicode characters
// Submitted by Daniel Leuck
StringBuffer buf = new StringBuffer();
int lineLength = line.length();
for (int i = 0; i < lineLength; i++) {
String val = Integer.toString(line.charAt(i), 16);
val = ZEROS.substring(0, 4 - val.length()) + val;
buf.append("\\u" + val);
}
line = buf.toString();
// End unicode patch
if (outPipe == null)
print("Console internal error: cannot output ...", Color.red);
else
try {
outPipe.write(line.getBytes());
outPipe.flush();
} catch (IOException e) {
outPipe = null;
throw new RuntimeException("Console pipe broken...");
}
// text.repaint();
}
public void println(Object o) {
print(String.valueOf(o) + "\n");
text.repaint();
}
public void print(final Object o) {
invokeAndWait(
new Runnable() {
public void run() {
append(String.valueOf(o));
resetCommandStart();
text.setCaretPosition(cmdStart);
}
}
);
}
/**
* Prints "\\n" (i.e. newline)
*/
public void println() {
print("\n");
text.repaint();
}
public void error(Object o) {
print(o, Color.red);
}
public void println(Icon icon) {
print(icon);
println();
text.repaint();
}
public void print(final Icon icon) {
if (icon == null)
return;
invokeAndWait(
new Runnable() {
public void run() {
text.insertIcon(icon);
resetCommandStart();
text.setCaretPosition(cmdStart);
}
}
);
}
public void print(Object s, Font font) {
print(s, font, null);
}
public void print(Object s, Color color) {
print(s, null, color);
}
public void print(final Object o, final Font font, final Color color) {
invokeAndWait(
new Runnable() {
public void run() {
AttributeSet old = getStyle();
setStyle(font, color);
append(String.valueOf(o));
resetCommandStart();
text.setCaretPosition(cmdStart);
setStyle(old, true);
}
}
);
}
public void print(Object s, String fontFamilyName, int size, Color color) {
print(s, fontFamilyName, size, color, false, false, false);
}
public void print(
final Object o, final String fontFamilyName, final int size, final Color color, final boolean bold, final boolean italic, final boolean underline
) {
invokeAndWait(
new Runnable() {
public void run() {
AttributeSet old = getStyle();
setStyle(fontFamilyName, size, color, bold, italic, underline);
append(String.valueOf(o));
resetCommandStart();
text.setCaretPosition(cmdStart);
setStyle(old, true);
}
}
);
}
private AttributeSet setStyle(Font font) {
return setStyle(font, null);
}
private AttributeSet setStyle(Color color) {
return setStyle(null, color);
}
private AttributeSet setStyle(Font font, Color color) {
if (font != null)
return setStyle(font.getFamily(), font.getSize(), color, font.isBold(), font.isItalic(), StyleConstants.isUnderline(getStyle()));
else
return setStyle(null, -1, color);
}
private AttributeSet setStyle(String fontFamilyName, int size, Color color) {
MutableAttributeSet attr = new SimpleAttributeSet();
if (color != null)
StyleConstants.setForeground(attr, color);
if (fontFamilyName != null)
StyleConstants.setFontFamily(attr, fontFamilyName);
if (size != -1)
StyleConstants.setFontSize(attr, size);
setStyle(attr);
return getStyle();
}
private AttributeSet setStyle(String fontFamilyName, int size, Color color, boolean bold, boolean italic, boolean underline) {
MutableAttributeSet attr = new SimpleAttributeSet();
if (color != null)
StyleConstants.setForeground(attr, color);
if (fontFamilyName != null)
StyleConstants.setFontFamily(attr, fontFamilyName);
if (size != -1)
StyleConstants.setFontSize(attr, size);
StyleConstants.setBold(attr, bold);
StyleConstants.setItalic(attr, italic);
StyleConstants.setUnderline(attr, underline);
setStyle(attr);
return getStyle();
}
private void setStyle(AttributeSet attributes) {
setStyle(attributes, false);
}
private void setStyle(AttributeSet attributes, boolean overWrite) {
text.setCharacterAttributes(attributes, overWrite);
}
private AttributeSet getStyle() {
return text.getCharacterAttributes();
}
public void setFont(Font font) {
super.setFont(font);
if (text != null)
text.setFont(font);
}
private void inPipeWatcher() throws IOException {
byte[] ba = new byte[256]; // arbitrary blocking factor
int read;
while ((read = inPipe.read(ba)) != -1) {
print(new String(ba, 0, read));
// text.repaint();
}
println("Console: Input closed...");
}
public void run() {
try {
inPipeWatcher();
} catch (IOException e) {
print("Console: I/O Error: " + e + "\n", Color.red);
}
}
public String toString() {
return "BeanShell console";
}
// MouseListener Interface
public void mouseClicked(MouseEvent event) {}
public void mousePressed(MouseEvent event) {
if (event.isPopupTrigger()) {
menu.show((Component) event.getSource(), event.getX(), event.getY());
}
}
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger()) {
menu.show((Component) event.getSource(), event.getX(), event.getY());
}
text.repaint();
}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
// property change
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals("lookAndFeel")) {
SwingUtilities.updateComponentTreeUI(menu);
}
}
// handle cut, copy and paste
public void actionPerformed(ActionEvent event) {
String cmd = event.getActionCommand();
if (cmd.equals(CUT)) {
text.cut();
} else if (cmd.equals(COPY)) {
text.copy();
} else if (cmd.equals(PASTE)) {
text.paste();
}
}
/**
* If not in the event thread run via SwingUtilities.invokeAndWait()
*/
private void invokeAndWait(Runnable run) {
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeLater(run); // Lipido
// patch!!,
// estaba
// invokeAndWait
} catch (Exception e) {
// shouldn't happen
e.printStackTrace();
}
} else {
run.run();
}
}
/**
* The overridden read method in this class will not throw "Broken pipe"
* IOExceptions; It will simply wait for new writers and data. This is used
* by the JConsole internal read thread to allow writers in different (and
* in particular ephemeral) threads to write to the pipe.
*
* It also checks a little more frequently than the original read().
*
* Warning: read() will not even error on a read to an explicitly closed
* pipe (override closed to for that).
*/
public static class BlockingPipedInputStream extends PipedInputStream {
boolean closed;
public BlockingPipedInputStream(PipedOutputStream pout) throws IOException {
super(pout);
}
public synchronized int read() throws IOException {
if (closed)
throw new IOException("stream closed");
while (super.in < 0) { // While no data */
notifyAll(); // Notify any writers to wake up
try {
wait(750);
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
// This is what the superclass does.
int ret = buffer[super.out++] & 0xFF;
if (super.out >= buffer.length)
super.out = 0;
if (super.in == super.out)
super.in = -1; /* now empty */
return ret;
}
public void close() throws IOException {
closed = true;
super.close();
}
}
public void setNameCompletion(NameCompletion nc) {
this.nameCompletion = nc;
}
public void setWaitFeedback(boolean on) {
if (on)
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
else
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
private int textLength() {
return text.getDocument().getLength();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy