package nodebox.client;
import nodebox.ui.Theme;
import org.python.util.PythonInterpreter;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
public class Console extends JFrame implements WindowListener, FocusListener {
private static final Color PROMPT_COLOR = new Color(72, 134, 242);
private static final Color PROMPT_BORDER_TOP_COLOR = new Color(240, 240, 240);
private static final Color ERROR_COLOR = new Color(255, 0, 0);
private static final SimpleAttributeSet ATTRIBUTES_REGULAR = new SimpleAttributeSet();
private static final SimpleAttributeSet ATTRIBUTES_ERROR = new SimpleAttributeSet();
private static final SimpleAttributeSet ATTRIBUTES_COMMAND = new SimpleAttributeSet();
static {
ATTRIBUTES_COMMAND.addAttribute(StyleConstants.ColorConstants.Foreground, PROMPT_COLOR);
ATTRIBUTES_ERROR.addAttribute(StyleConstants.ColorConstants.Foreground, ERROR_COLOR);
}
private static Logger logger = Logger.getLogger("nodebox.client.Console");
private PythonInterpreter interpreter;
private ArrayList<String> history = new ArrayList<String>();
private int historyOffset = 0;
private String temporarySavedCommand = null;
private JTextField consolePrompt;
private Document messagesDocument;
public Console() {
super("Console");
JTextPane consoleMessages = new JTextPane();
consoleMessages.setMargin(new Insets(2, 20, 2, 5));
consoleMessages.setFont(Theme.EDITOR_FONT);
consoleMessages.setEditable(false);
consoleMessages.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
consolePrompt.requestFocus();
}
});
messagesDocument = consoleMessages.getDocument();
JScrollPane messagesScroll = new JScrollPane(consoleMessages, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
messagesScroll.setBorder(BorderFactory.createEmptyBorder());
consolePrompt = new JTextField();
consolePrompt.setFont(Theme.EDITOR_FONT);
Keymap defaultKeymap = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
Keymap keymap = JTextComponent.addKeymap(null, defaultKeymap);
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), new EnterAction());
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new HistoryUpAction());
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new HistoryDownAction());
consolePrompt.setKeymap(keymap);
consolePrompt.setBorder(new PromptBorder());
setLayout(new BorderLayout());
add(messagesScroll, BorderLayout.CENTER);
add(consolePrompt, BorderLayout.SOUTH);
interpreter = new PythonInterpreter();
consolePrompt.requestFocus();
addFocusListener(this);
addWindowListener(this);
}
private void addMessage(String s, AttributeSet attributes) {
try {
messagesDocument.insertString(messagesDocument.getLength(), s, attributes);
} catch (BadLocationException e) {
logger.log(Level.WARNING, "addMessage: bad location (" + s + ")", e);
}
}
private void addMessage(String s) {
addMessage(s, ATTRIBUTES_REGULAR);
}
private void addCommandMessage(String s) {
addMessage(s, ATTRIBUTES_COMMAND);
}
private void addErrorMessage(String s) {
addMessage(s, ATTRIBUTES_ERROR);
}
public void doEnter() {
String command = getCommand();
addCommandToHistory(command);
setCommand("");
addCommandMessage(command + "\n");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
interpreter.setOut(outputStream);
interpreter.setErr(errorStream);
// HACK Indirect way to access the current document.
NodeBoxDocument document = Application.getInstance().getCurrentDocument();
interpreter.set("document", document);
interpreter.set("root", document.getNodeLibrary().getRoot());
interpreter.set("parent", document.getActiveNetwork());
interpreter.set("node", document.getActiveNode());
interpreter.exec("from nodebox.node import *");
Exception pythonException = null;
try {
Object result = interpreter.eval(command);
if (result != null) {
addMessage(result.toString() + "\n");
}
} catch (Exception e) {
pythonException = e;
}
String os = outputStream.toString();
if (os.length() > 0) {
addMessage(os);
if (!os.endsWith("\n"))
addMessage("\n");
}
if (pythonException != null)
addErrorMessage(pythonException.toString());
}
public String getCommand() {
return consolePrompt.getText();
}
public void setCommand(String command) {
consolePrompt.setText(command);
}
public void moveBackInHistory() {
if (historyOffset == history.size())
return;
if (historyOffset == 0) {
temporarySavedCommand = getCommand();
}
historyOffset++;
setCommand(history.get(history.size() - historyOffset));
}
public void moveForwardInHistory() {
if (historyOffset == 0)
return;
historyOffset--;
if (historyOffset == 0) {
checkNotNull(temporarySavedCommand, "temporarySavedCommand is null.");
setCommand(temporarySavedCommand);
temporarySavedCommand = null;
} else {
setCommand(history.get(history.size() - historyOffset));
}
}
public void addCommandToHistory(String command) {
history.add(command);
historyOffset = 0;
}
public void focusGained(FocusEvent focusEvent) {
consolePrompt.requestFocus();
}
public void focusLost(FocusEvent focusEvent) {
}
private class EnterAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
doEnter();
}
}
private class HistoryUpAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
moveBackInHistory();
}
}
private class HistoryDownAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
moveForwardInHistory();
}
}
//// Window events ////
public void windowOpened(WindowEvent e) {
}
public void windowClosing(WindowEvent e) {
Application.getInstance().onHideConsole();
}
public void windowClosed(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowActivated(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
private class PromptBorder implements Border {
public void paintBorder(Component component, Graphics g, int x, int y, int width, int height) {
g.setColor(PROMPT_BORDER_TOP_COLOR);
g.drawLine(0, 0, width, 0);
g.setColor(PROMPT_COLOR);
g.drawString(">", 5, 14);
}
public Insets getBorderInsets(Component component) {
return new Insets(3, 20, 3, 0);
}
public boolean isBorderOpaque() {
return true;
}
}
}