Хотел написать для Андроида, но решил протестировать и доделать рабочий вариант для компа. Первый блин на Java, выбор пал на Swing поскольку инет завален ссылками по нему, а с JavaFX связываться не решился. Собственно, от интерфейса требуется минимализм, надежность и кроссплатформенность. Это РЕПЛ (рид-эвал-принт-лууп) одного языка, в нижнем окне набираются команды, в верхнем отображается результат (как он будет готов). Здесь только UI оболочка, парсинг и вычисление вынес в отдельные классы, здесь стоят заглушки. Собственно, хотел узнать - это так вообще делается? Прямо класс Main наследуется от JFrame и вся UI логика прописывается в нем? Или выделять отдельный класс для этого? Как сделать изменение мышкой относительных размеров окон ввода и вывода (сейчас они у меня в таблице, высота одинакова)? Хорошо ли из отдельного потока выполнения изменять элементы UI перед завершением потока? Если лучше делать это в основном потоке, то как организовать красивый надежный коллбэк по завершению потока вычислений? Ну и вообще, любая конструктивная критика/рекомендации приветствуются.
package com.company;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;
public class Main extends JFrame {
    private static JTextArea textArea;
    private static JEditorPane textAreaIn;
    private static DefaultListModel<InterThread> threadListModel =
            new DefaultListModel<InterThread> ();
    private static JList threadList = new JList<InterThread> (threadListModel);
    private static JToolBar buttonsPanel;
    public Main() {
        super("GUI REPL");
        textArea = new JTextArea(5, 30);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.setFont(new Font("Courier New", Font.PLAIN, 12));
        JScrollPane scrollPane = new JScrollPane(textArea);
        scrollPane.setPreferredSize(new Dimension(500, 300));
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        final StyleContext sctextAreaIn = new StyleContext();
        final DefaultStyledDocument doctextAreaIn = new DefaultStyledDocument(sctextAreaIn);
        textAreaIn = new JTextPane(doctextAreaIn);
        final Style style0 = sctextAreaIn.addStyle("emptyStyle", null);
        style0.addAttribute(StyleConstants.FontSize, 12);
        style0.addAttribute(StyleConstants.FontFamily, "Courier New");
        doctextAreaIn.setParagraphAttributes(0, 1, style0, true);
        JScrollPane scrollPaneIn = new JScrollPane(textAreaIn);
        scrollPaneIn.setPreferredSize(new Dimension(500, 300));
        scrollPaneIn.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        threadList.setCellRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(
                    JList<?> list, Object value, int index, boolean isSelected,
                    boolean cellHasFocus) {
                Component renderer = super.getListCellRendererComponent(
                        list, value, index, isSelected, cellHasFocus);
                if (renderer instanceof JLabel && value instanceof Thread) {
                    ((JLabel) renderer).setText(((Thread) value).getName());
                }
                return renderer;
            }
        });
        threadList.setFont(new Font("Courier New", Font.BOLD, 20));
        JScrollPane scrollPaneThreads = new JScrollPane(threadList);
        scrollPaneThreads.setPreferredSize(new Dimension(150, 300));
        scrollPaneThreads.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        JLabel lastLoadFileNameLabel = new JLabel();
        Action sendUserInputAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                sendUserInput();
            }
        };
        JButton sendUserInput = new JButton();
        sendUserInput.setAction(sendUserInputAction);
        sendUserInput.setText("send (CTRL+ENTER)");
        sendUserInput.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
                .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_MASK)
                        , "sendUserInputAction");
        sendUserInput.getActionMap().put("sendUserInputAction", sendUserInputAction);
        Action loadFileAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JFileChooser fileopen = new JFileChooser();
                fileopen.setFileFilter(new FileNameExtensionFilter("TXT files", "txt"));
                int ret = fileopen.showDialog(getParent(), "Выберите файл скрипта");
                if (ret == JFileChooser.APPROVE_OPTION) {
                    File file = fileopen.getSelectedFile();
                    try {
                        String s = new Scanner(file).useDelimiter("\\Z").next();
                        lastLoadFileNameLabel.setText(file.getAbsolutePath());
                        startNewThread(false, s);
                    } catch (IOException ex) {
                        cout(true, "problem accessing file " + file.getAbsolutePath());
                    }
                }
            }
        };
        JButton loadFile = new JButton();
        loadFile.setAction(loadFileAction);
        loadFile.setText("load file");
        Action reloadFileAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String fileAbsolutePath = lastLoadFileNameLabel.getText();
                try {
                    File file = new File(fileAbsolutePath);
                    String s = new Scanner(file).useDelimiter("\\Z").next();
                    startNewThread(false, s);
                } catch (IOException ex) {
                    cout(true, ex.getLocalizedMessage());
                }
            }
        };
        JButton reloadFile = new JButton();
        reloadFile.setAction(reloadFileAction);
        reloadFile.setText("reload file");
        Action showActiveThreadsAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
                ThreadGroup parent;
                while ((parent = threadGroup.getParent()) != null) {
                    threadGroup = parent;
                    Thread[] threadList = new Thread[threadGroup.activeCount()];
                    threadGroup.enumerate(threadList);
                    for (Thread thread : threadList)
                        cout(true, thread.getThreadGroup().getName()
                                + " " + thread.getPriority()
                                + " " + thread.getName());
                }
            }
        };
        JButton showActiveThreads = new JButton();
        showActiveThreads.setAction(showActiveThreadsAction);
        showActiveThreads.setText("active threads");
        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(scrollPaneThreads, BorderLayout.EAST);
        JPanel textsPanel = new JPanel(new GridLayout(2,1));
        textsPanel.add(scrollPane);
        textsPanel.add(scrollPaneIn);
        getContentPane().add(textsPanel, BorderLayout.CENTER);
        sendUserInput.setDefaultCapable(true);
        buttonsPanel = new JToolBar(SwingConstants.VERTICAL);
        buttonsPanel.add(sendUserInput);
        buttonsPanel.addSeparator();
        buttonsPanel.add(loadFile);
        buttonsPanel.add(reloadFile);
        buttonsPanel.add(showActiveThreads);
        getContentPane().add(buttonsPanel, BorderLayout.WEST);
        getContentPane().add(lastLoadFileNameLabel, BorderLayout.SOUTH);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public class Read {
        public Object read(String s) { return "(" + s + ")"; }
    }
    public class Eval {
        Eval() {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
        }
        public Object eval(Object o) { return "evaluated: " + o.toString(); }
    }
    public class InterThread extends Thread {
        public String expression;
        public boolean showEcho;
        InterThread(boolean _showEcho, String _exp) { showEcho = _showEcho; expression = _exp; }
        public void run() {
            try {
                Object ro = new Read().read(expression);
                if (showEcho) cout(true, ro.toString());
                cout(true, new Eval().eval(ro).toString());
            } catch (Throwable e) {
                cout(true, e.toString());
                Thread.currentThread().interrupt();
            }
            textAreaIn.setText("");
            if (threadListModel.contains(this))
                threadListModel.remove(threadListModel.indexOf(this));
            for (Component c : buttonsPanel.getComponents()) {
                if (c.getClass().getSimpleName().equals("InterThreadJButton")) {
                    if (((InterThreadJButton) c).thread.equals(this))
                        buttonsPanel.remove(c);
                }
            }
            buttonsPanel.updateUI(); // .revalidate();
        }
    }
    class InterThreadJButton extends JButton {
        public InterThread thread;
        InterThreadJButton(InterThread t) {
            thread = t;
            Action interruptAction = new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    cout(true, "interrupt");
                    thread.interrupt();
                    cout(true, thread.getName());
                }
            };
            this.setAction(interruptAction);
            this.setText(thread.getName());
        }
    }
    public void startNewThread(boolean showEcho, String exp) {
        if (exp == null || exp.trim().isEmpty()) return;
        InterThread it = new InterThread(showEcho, exp);
        it.start();
        threadListModel.addElement(it);
        buttonsPanel.add(new InterThreadJButton(it));
    }
    public static void cout(boolean ln, String s) {
        if (s == null) return;
        if (ln) textArea.append(s + "\n"); else textArea.append(s);
        textArea.setCaretPosition(textArea.getDocument().getLength());
    }
    public void sendUserInput() {
        startNewThread(true, textAreaIn.getText());
    }
    //--------------------------------- MAIN -----------------------------
    public void run() throws FileNotFoundException {
        cout(true, "Lets begin");
    }
    public static void main(String[] args) throws FileNotFoundException {
        Main application = new Main();
        application.setVisible(true);
        application.pack();
        application.run();
    }
}

