/* * Copyright (C) 2014 Sergey Basalaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package alchemy.apps; import alchemy.io.TerminalInput; import alchemy.libs.ui.UiScreen; import alchemy.util.Strings; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import static javax.swing.JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED; import static javax.swing.JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED; /** * Terminal screen. * @author Sergey Basalaev */ public class TerminalScreen extends UiScreen { private final JPanel widget; private final JTextArea output; private final JLabel prompt; private final JTextField input; private final Box inputBox; private final Object sync = new Object(); private final JButton enter; final TerminalInputStream in = new TerminalInputStream(); final TerminalOutputStream out = new TerminalOutputStream(); final TerminalOutputStream err = out; private boolean withEOF = false; public TerminalScreen(String title) { super(title); widget = new JPanel(new BorderLayout()); output = new JTextArea(); output.setEditable(false); output.setLineWrap(true); JScrollPane outputPane = new JScrollPane(output, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED); outputPane.setPreferredSize(new Dimension(640, 480)); prompt = new JLabel(); input = new JTextField(); input.setEditable(false); input.setText("Running..."); input.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { synchronized (sync) { sync.notify(); } } } }); enter = new JButton("Enter"); enter.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { synchronized (sync) { sync.notify(); } } }); inputBox = Box.createHorizontalBox(); inputBox.add(prompt); inputBox.add(input); inputBox.add(enter); widget.add(outputPane, BorderLayout.CENTER); widget.add(inputBox, BorderLayout.SOUTH); } private String waitForInput() { input.setText(null); input.setEditable(true); enter.setVisible(true); input.requestFocus(); synchronized (sync) { try { sync.wait(); } catch (InterruptedException ie) { output.append("interrupted\n"); } } input.setEditable(false); String newInput = input.getText() + '\n'; input.setText("Running..."); enter.setVisible(false); synchronized (output) { output.append(prompt.getText()); output.append(" "); output.append(newInput); } return newInput; } @Override public JComponent getWidget() { return widget; } public void sendEOF() { withEOF = true; } private class TerminalInputStream extends InputStream implements TerminalInput { private ByteArrayInputStream buf = new ByteArrayInputStream(new byte[0]); public TerminalInputStream() { } @Override public int available() throws IOException { return buf.available(); } @Override public int read() throws IOException { int b = buf.read(); if (b == -1) { if (withEOF) { withEOF = false; return -1; } String data = waitForInput(); buf = new ByteArrayInputStream(Strings.utfEncode(data)); b = buf.read(); } return b; } @Override public void clear() { synchronized (output) { output.setText(null); } } @Override public String getPrompt() { return prompt.getText(); } @Override public void setPrompt(String text) { prompt.setText(text); } } void end(String name) throws InterruptedException { input.setEditable(false); input.setText("Process '" + name + "' ended."); enter.setText("Close"); enter.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { synchronized (sync) { sync.notify(); } } }); enter.setVisible(true); synchronized (sync) { sync.wait(); } } private class TerminalOutputStream extends OutputStream { private ByteArrayOutputStream buf; public TerminalOutputStream() { buf = new ByteArrayOutputStream(); } @Override public synchronized void write(int b) throws IOException { buf.write(b); if (b == '\n') flush(); } @Override public synchronized void write(byte[] b, int off, int len) throws IOException { int flushmark = off+len-1; while (flushmark >= off) { if (b[flushmark] == '\n') break; flushmark--; } if (flushmark >= off) { buf.write(b, off, off+flushmark+1); flush(); len -= flushmark-off+1; off = flushmark+1; } buf.write(b, off, len); } @Override public synchronized void flush() throws IOException { byte[] data = buf.toByteArray(); buf.reset(); synchronized (output) { output.append(Strings.utfDecode(data)); } } } }