Хотел написать для Андроида, но решил протестировать и доделать рабочий вариант для компа. Первый блин на 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();
}
}