/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2010 by Trifork
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package erjang.console;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import kilim.Pausable;
import erjang.EBinary;
import erjang.EHandle;
import erjang.EObject;
import erjang.EPID;
import erjang.ERT;
import erjang.EString;
import erjang.driver.EDriverInstance;
import erjang.driver.IO;
public class TTYTextAreaDriverControl extends EDriverInstance implements
KeyListener {
/* The various opcodes. */
static final int OP_PUTC = 0;
static final int OP_MOVE = 1;
static final int OP_INSC = 2;
static final int OP_DELC = 3;
static final int OP_BEEP = 4;
/* Control op */
static final int CTRL_OP_GET_WINSIZE = 100;
static final int CTRL_OP_GET_UNICODE_STATE = 101;
static final int CTRL_OP_SET_UNICODE_STATE = 102;
static final int CONTROL_TAG = 0x10000000; /*
* Control character, value in
* first position
*/
static final int ESCAPED_TAG = 0x01000000; /*
* Escaped character, value in
* first position
*/
static final int TAG_MASK = 0xFF000000;
static final int MAXSIZE = 1 << 16;
private JTextPane area;
private SimpleAttributeSet promptStyle;
private SimpleAttributeSet inputStyle;
private SimpleAttributeSet outputStyle;
private SimpleAttributeSet resultStyle;
private int startPos;
private boolean utf8_mode = true;
private Clipboard clipboard;
private SimpleAttributeSet errorStyle;
private static final int MAX_DOC_SIZE = 100000;
public TTYTextAreaDriverControl(TTYTextAreaDriver driver, JTextPane text,
String message) {
super(driver);
this.clipboard = text.getToolkit().getSystemClipboard();
this.area = text;
// inputJoin.send(Channel.EMPTY, null);
text.addKeyListener(this);
text.setAutoscrolls(true);
// No editing before startPos
if (text.getDocument() instanceof AbstractDocument)
((AbstractDocument) text.getDocument())
.setDocumentFilter(new DocumentFilter() {
public void insertString(
DocumentFilter.FilterBypass fb, int offset,
String string, AttributeSet attr)
throws BadLocationException {
if (offset >= startPos)
super.insertString(fb, offset, string, attr);
}
public void remove(DocumentFilter.FilterBypass fb,
int offset, int length)
throws BadLocationException {
if (offset >= startPos || offset == 0)
super.remove(fb, offset, length);
}
public void replace(DocumentFilter.FilterBypass fb,
int offset, int length, String text,
AttributeSet attrs) throws BadLocationException {
if (offset >= startPos)
super.replace(fb, offset, length, text, attrs);
}
});
promptStyle = new SimpleAttributeSet();
StyleConstants.setForeground(promptStyle, new Color(0xa4, 0x00, 0x00));
inputStyle = new SimpleAttributeSet();
StyleConstants.setForeground(inputStyle, new Color(0x20, 0x4a, 0x87));
outputStyle = new SimpleAttributeSet();
StyleConstants.setForeground(outputStyle, Color.darkGray);
errorStyle = new SimpleAttributeSet();
StyleConstants.setForeground(errorStyle, Color.red);
resultStyle = new SimpleAttributeSet();
StyleConstants.setItalic(resultStyle, true);
StyleConstants.setForeground(resultStyle, new Color(0x20, 0x4a, 0x87));
if (message != null) {
final MutableAttributeSet messageStyle = new SimpleAttributeSet();
StyleConstants.setBackground(messageStyle, text.getForeground());
StyleConstants.setForeground(messageStyle, text.getBackground());
append(message, messageStyle);
}
startPos = text.getDocument().getLength();
}
public InputStream getInputStream() {
return ERT.getInputStream();
}
public OutputStream getOutputStream() {
return new Output(outputStyle);
}
public OutputStream getErrorStream() {
return new Output(errorStyle);
}
class Output extends OutputStream {
private AttributeSet style;
public Output(AttributeSet style) {
this.style = style;
}
@Override
public void write(int b) throws IOException {
String data = new String(new byte[] { (byte) b }, IO.UTF8);
append(data, style);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
String data = new String(b,off,len,IO.UTF8);
append(data, style);
}
}
protected void append(final String toAppend, final AttributeSet style) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
Document doc = area.getDocument();
doc.insertString(doc.getLength(), toAppend, style);
area.setCaretPosition(doc.getLength());
// area.select(area.getCaretPosition(), area.getCaretPosition());
// Cut the document to fit into the MAX_DOC_SIZE.
// See JRUBY-4237.
int extra = doc.getLength() - MAX_DOC_SIZE;
if (extra > 0) {
int removeBytes = extra + MAX_DOC_SIZE / 10;
doc.remove(0, removeBytes);
startPos -= removeBytes;
}
} catch (BadLocationException e) {
}
}
});
}
@Override
protected void output(EHandle caller, ByteBuffer buf) throws IOException,
Pausable {
// if (lpos > MAXSIZE)
// put_chars("\n");
switch (buf.get()) {
case OP_PUTC:
put_chars(buf);
break;
case OP_MOVE:
move_rel(buf.getShort());
break;
case OP_INSC:
ins_chars(buf);
break;
case OP_DELC:
del_chars(buf.getShort());
break;
case OP_BEEP:
visible_beep();
break;
default:
/* Unknown op, just ignore. */
break;
}
return; /* TRUE; */
}
private void visible_beep() {
{
Color fg = area.getForeground();
Color bg = area.getBackground();
area.setForeground(bg);
area.setBackground(fg);
area.repaint();
Toolkit.getDefaultToolkit().beep();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
area.setForeground(fg);
area.setBackground(bg);
area.repaint();
}
}
private void del_chars(int i) {
try {
Document doc = area.getDocument();
int offs = area.getCaretPosition() + i;
doc.remove(offs, -i);
} catch (BadLocationException e) {
}
}
private void ins_chars(ByteBuffer buf) {
final String string = new String(buf.array(), buf.arrayOffset()
+ buf.position(), buf.remaining(), IO.UTF8);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
Document doc = area.getDocument();
int pos = area.getCaretPosition();
doc.insertString(pos, string, inputStyle);
area.setCaretPosition(pos + string.length());
area.select(area.getCaretPosition(), area.getCaretPosition());
// Cut the document to fit into the MAX_DOC_SIZE.
// See JRUBY-4237.
int extra = doc.getLength() - MAX_DOC_SIZE;
if (extra > 0) {
int removeBytes = extra + MAX_DOC_SIZE / 10;
doc.remove(0, removeBytes);
startPos -= removeBytes;
}
} catch (BadLocationException e) {
}
}
});
}
private void move_rel(final int delta_pos) {
if (delta_pos != 0) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int curr = area.getCaretPosition();
area.setCaretPosition(curr + delta_pos);
area.select(area.getCaretPosition(), area.getCaretPosition());
}
});
}
}
private void put_chars(ByteBuffer buf) {
String string = new String(buf.array(), buf.arrayOffset()
+ buf.position(), buf.remaining(), IO.UTF8);
append(string, outputStyle);
}
private void put_chars(String string) {
append(string, outputStyle);
}
@Override
public void keyTyped(KeyEvent e) {
e.consume();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent event) {
int code = event.getKeyCode();
if (code == KeyEvent.VK_COPY
|| (code == KeyEvent.VK_C && event.isMetaDown())) {
return;
}
if (code == KeyEvent.VK_PASTE
|| (code == KeyEvent.VK_V && event.isMetaDown())) {
Transferable clipData = clipboard.getContents(clipboard);
try {
if (clipData.isDataFlavorSupported(DataFlavor.stringFlavor)) {
String s = (String) (clipData
.getTransferData(DataFlavor.stringFlavor));
out(s);
}
} catch (Exception ufe) {
}
event.consume();
return;
}
if (code == KeyEvent.VK_CUT
|| (code == KeyEvent.VK_X && event.isMetaDown())) {
event.consume();
visible_beep();
return;
}
event.consume();
switch (code) {
case KeyEvent.VK_TAB:
out('\t');
return;
case KeyEvent.VK_ESCAPE:
out_byte((byte) 27);
return;
case KeyEvent.VK_LEFT:
out_ctrl('b');
return;
case KeyEvent.VK_DELETE:
out_ctrl('d');
return;
case KeyEvent.VK_RIGHT:
out_ctrl('f');
return;
case KeyEvent.VK_BACK_SPACE:
out_ctrl('h');
return;
case KeyEvent.VK_ENTER:
out_ctrl('j');
return;
case KeyEvent.VK_DOWN:
out_ctrl('n');
return;
case KeyEvent.VK_UP:
out_ctrl('p');
return;
}
if (event.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
// System.err.println("ignored key event: " + event);
return;
}
if ((event.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) {
// System.err.println("ctrl event: " + event);
if (code >= KeyEvent.VK_A && code <= KeyEvent.VK_Z) {
out_ctrl('a' + code - KeyEvent.VK_A);
}
} else {
out(event.getKeyChar());
}
}
private void out_ctrl(int i) {
if (i >= 'a' && i <= 'z') {
out_byte((byte) ((i - 'a') + 1));
}
}
private void out_byte(int b) {
ByteBuffer buf = ByteBuffer.wrap(new byte[] { (byte) b });
EObject out;
if (task.send_binary_data()) {
out = EBinary.make(buf);
} else {
out = EString.make(buf);
}
task.output_from_driver_b(out);
}
private void out(char ch) {
String string = new String(new char[] { ch });
out(string);
}
private void out(String string) {
byte[] bytes = string.getBytes(IO.UTF8);
ByteBuffer buf = ByteBuffer.wrap(bytes);
// driver_output(bb);
EObject out;
if (task.send_binary_data()) {
out = EBinary.make(buf);
} else {
out = EString.make(buf);
}
task.output_from_driver_b(out);
}
@Override
protected ByteBuffer control(EPID pid, int command, ByteBuffer cmd)
throws Pausable {
if (command == CTRL_OP_GET_WINSIZE) {
ByteBuffer rep = ByteBuffer.allocate(8);
rep.order(ByteOrder.nativeOrder());
rep.putInt(80);
rep.putInt(25);
return rep;
} else if (command == CTRL_OP_GET_UNICODE_STATE) {
ByteBuffer rep = ByteBuffer.allocate(1);
rep.put((byte) (utf8_mode ? 1 : 0));
return rep;
} else if (command == CTRL_OP_SET_UNICODE_STATE && cmd.remaining() == 1) {
ByteBuffer rep = ByteBuffer.allocate(1);
rep.put((byte) (utf8_mode ? 1 : 0));
utf8_mode = cmd.get() == 0 ? false : true;
return rep;
} else {
return null;
}
}
}