// Uses FILIUS' own text editor window (GUIApplicationTextEditorWindow on a // GUIDesktopPanel) on mail-lan's first computer as a person does: typing, // its buttons „Neu“, „Öffnen“, „Speichern“, „Speichern unter“, and its file // dialog (DMTNFileChooser: the folder's list, a double click on a folder or // a file, „Ordner aufwärts“, the name field, the dialog's own button), with // the command line beside it changing the files. After each step it writes // what the window shows (its title, its text), what the dialog showed, and // the computer's files. The reference for the editor's text editor // (packages/editor/test/textEditor.test.ts). // // Usage: java -cp filius.jar:lib/*:classes TextEditorWindowRun import filius.gui.anwendungssicht.GUIApplicationTextEditorWindow; import filius.gui.anwendungssicht.GUIApplicationWindow; import filius.gui.anwendungssicht.GUIDesktopPanel; import filius.software.lokal.Terminal; import filius.software.system.Betriebssystem; import filius.software.system.Datei; import java.awt.Component; import java.awt.Container; import java.awt.Point; import java.awt.Window; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.FileOutputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import javax.swing.AbstractButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultMutableTreeNode; public class TextEditorWindowRun { static final String CLIENT = "00000000-0000-0000-0000-000000000201"; static final List steps = new ArrayList<>(); static GUIApplicationTextEditorWindow window; static GUIDesktopPanel desktop; static Betriebssystem system; static Terminal terminal; /** What the last dialog showed, for the next step. */ static List shown = new ArrayList<>(); static Object field(Class owner, Object object, String name) throws Exception { Field field = owner.getDeclaredField(name); field.setAccessible(true); return field.get(object); } @SuppressWarnings("unchecked") static T find(Container container, Class kind) { for (Component component : container.getComponents()) { if (kind.isInstance(component)) return (T) component; if (component instanceof Container) { T found = find((Container) component, kind); if (found != null) return found; } } return null; } static List all(Container container, Class kind, List found) { for (Component component : container.getComponents()) { if (kind.isInstance(component)) found.add(kind.cast(component)); if (component instanceof Container) all((Container) component, kind, found); } return found; } static AbstractButton button(Container container, String text) { for (AbstractButton each : all(container, AbstractButton.class, new ArrayList<>())) if (text.equals(each.getText())) return each; throw new IllegalStateException("no button " + text); } static void onEdt(Runnable action) throws Exception { SwingUtilities.invokeLater(action); Thread.sleep(400); } static JTextArea editor() throws Exception { return (JTextArea) field(GUIApplicationTextEditorWindow.class, window, "editorField"); } /** The file dialog Java shows (modal, so the button's press waits for it). */ static JDialog dialog() throws Exception { long until = System.currentTimeMillis() + 5000; while (System.currentTimeMillis() < until) { for (Window each : Window.getWindows()) { if (each instanceof JDialog && each.isVisible() && find((Container) each, JList.class) != null) return (JDialog) each; } Thread.sleep(100); } throw new IllegalStateException("no file dialog"); } /** What the dialog shows: its title, its folder's label and its list. */ static void look(JDialog dialog) { Map look = new LinkedHashMap<>(); look.put("title", dialog.getTitle()); List labels = new ArrayList<>(); for (JLabel label : all(dialog.getContentPane(), JLabel.class, new ArrayList<>())) labels.add(label.getText()); look.put("labels", labels); List entries = new ArrayList<>(); ListModel model = find(dialog.getContentPane(), JList.class).getModel(); for (int index = 0; index < model.getSize(); index++) entries.add(String.valueOf(model.getElementAt(index))); look.put("entries", entries); look.put("name", find(dialog.getContentPane(), JTextField.class).getText()); List buttons = new ArrayList<>(); for (AbstractButton each : all(dialog.getContentPane(), AbstractButton.class, new ArrayList<>())) buttons.add(each.getText()); look.put("buttons", buttons); shown.add(look); } /** A double click on the list's entry, as Java's dialog listens (mouseClicked, two clicks). */ static void doubleClick(JDialog dialog, String entry) throws Exception { JList list = find(dialog.getContentPane(), JList.class); int index = -1; for (int each = 0; each < list.getModel().getSize(); each++) if (entry.equals(String.valueOf(list.getModel().getElementAt(each)))) index = each; if (index < 0) throw new IllegalStateException("no entry " + entry); int found = index; onEdt(() -> { Point at = list.indexToLocation(found); MouseEvent event = new MouseEvent(list, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, at.x + 2, at.y + 2, 2, false, MouseEvent.BUTTON1); for (MouseListener listener : list.getMouseListeners()) listener.mouseClicked(event); }); look(dialog); } /** * A file dialog after the editor's button: what it shows, then the * person's moves (">name" a double click on an entry, "^" folder up, * "=name" the name typed), then its own button ("!") or „Abbrechen“ ("x"). */ static void inDialog(String editorButton, String... moves) throws Exception { onEdt(button(window, editorButton)::doClick); JDialog dialog = dialog(); look(dialog); for (String move : moves) { if (move.startsWith(">")) doubleClick(dialog, move.substring(1)); else if (move.equals("^")) { onEdt(button(dialog.getContentPane(), "Ordner aufwärts")::doClick); look(dialog); } else if (move.startsWith("=")) { JTextField name = find(dialog.getContentPane(), JTextField.class); onEdt(() -> name.setText(move.substring(1))); } else if (move.equals("!")) { String action = editorButton.equals("Öffnen") ? "Öffnen" : "Speichern"; onEdt(button(dialog.getContentPane(), action)::doClick); } else if (move.equals("x")) onEdt(button(dialog.getContentPane(), "Abbrechen")::doClick); } Thread.sleep(300); } static void type(String text) throws Exception { JTextArea editor = editor(); onEdt(() -> editor.setText(text)); } /** GUIApplicationTerminalWindow on Enter: the line handed to the program. */ static void command(String line) throws Exception { StringTokenizer tokens = new StringTokenizer(line, " "); String name = tokens.nextToken(); String[] parameters = new String[3 + tokens.countTokens()]; Arrays.fill(parameters, ""); for (int index = 0; tokens.hasMoreTokens(); index++) parameters[index] = tokens.nextToken(); terminal.terminalEingabeAuswerten(name, parameters); Thread.sleep(500); } /** The computer's files, each folder's entries in their order: path, type, content (an image's only its length). */ static List> files() { List> files = new ArrayList<>(); walk(system.getDateisystem().getRoot(), "", files); return files; } static void walk(DefaultMutableTreeNode folder, String path, List> files) { for (Enumeration each = folder.children(); each.hasMoreElements();) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) each.nextElement(); if (node.getUserObject() instanceof Datei) { Datei datei = (Datei) node.getUserObject(); String content = datei.getDateiInhalt(); boolean text = "text".equals(datei.getDateiTyp()); files.add(Arrays.asList(path + "/" + datei.getName(), String.valueOf(datei.getDateiTyp()), text ? content : "(" + (content == null ? 0 : content.length()) + " characters)")); } else { files.add(Arrays.asList(path + "/" + node + "/", "folder", "")); walk(node, path + "/" + node, files); } } } static void step(String name) throws Exception { Map result = new LinkedHashMap<>(); result.put("step", name); result.put("title", ((JLabel) field(GUIApplicationWindow.class, window, "titleLabel")).getText()); String text = editor().getText(); result.put("text", text.length() > 200 ? text.substring(0, 200) + "… (" + text.length() + " characters)" : text); if (!shown.isEmpty()) result.put("dialog", shown); shown = new ArrayList<>(); result.put("files", files()); steps.add(result); } public static void main(String[] args) throws Exception { try { run(args); } catch (Throwable error) { error.printStackTrace(); System.exit(1); } System.exit(0); } static void run(String[] args) throws Exception { ScenarioNetwork network = ScenarioNetwork.read(args[0]); system = (Betriebssystem) network.nodes.get(CLIENT).getSystemSoftware(); system.installApp("filius.software.lokal.TextEditor", false); system.installApp("filius.software.lokal.Terminal", false); system.installApp("filius.software.www.WebServer", false); network.start(); Thread.sleep(1000); terminal = (Terminal) system.holeSoftware("filius.software.lokal.Terminal"); JFrame frame = new JFrame(); desktop = new GUIDesktopPanel(system); frame.getContentPane().add(desktop); window = (GUIApplicationTextEditorWindow) desktop.starteAnwendung("filius.software.lokal.TextEditor"); SwingUtilities.invokeAndWait(() -> { frame.pack(); frame.setVisible(true); }); Thread.sleep(500); step("open"); // A new text saved: „Speichern“ without a file asks for a name. type("Erste Zeile\nZweite Zeile"); inDialog("Speichern", "=notiz.txt", "!"); step("save new as notiz.txt"); type("Erste Zeile\nZweite Zeile\nDritte Zeile"); onEdt(button(window, "Speichern")::doClick); step("save"); // Another program changes the open file: the editor takes it while nothing is typed, keeps its own text after typing. Datei notiz = system.getDateisystem().holeDatei("/notiz.txt"); onEdt(() -> notiz.setDateiInhalt("von außen")); step("changed outside, nothing typed"); type("getippt"); onEdt(() -> notiz.setDateiInhalt("wieder von außen")); step("changed outside after typing"); // Folders in the dialog: a double click into one, a file chosen there, folder up. command("mkdir ordner"); command("touch ordner/b.txt"); inDialog("Öffnen", ">Ordner;ordner", ">Datei;b.txt", "!"); step("open ordner/b.txt by double clicks"); inDialog("Öffnen", ">Ordner;ordner", "^", "x"); step("dialog: folder and up, cancelled"); inDialog("Öffnen", "=NOTIZ.TXT", "!"); step("open NOTIZ.TXT"); inDialog("Öffnen", "=ordner/b.txt", "!"); step("open ordner/b.txt by name"); inDialog("Öffnen", "=fehlt.txt", "!"); step("open fehlt.txt"); inDialog("Speichern", "x"); step("save without a file, cancelled"); inDialog("Öffnen", "=", "!"); step("open without a name"); // An image opened as text. inDialog("Öffnen", ">Ordner;webserver", ">Datei;splashscreen-mini.png", "!"); step("open the web server's image"); // „Speichern unter“ onto a file there (in other case), then „Speichern“ again. inDialog("Öffnen", "=notiz.txt", "!"); type("vor dem Speichern unter"); inDialog("Speichern unter", "=NOTIZ.TXT", "!"); step("save as NOTIZ.TXT over notiz.txt"); type("nach dem Speichern unter"); onEdt(button(window, "Speichern")::doClick); step("save after save as over a file"); // „Speichern unter“ a new name in a folder, then „Speichern“ again. type("neu im Ordner"); inDialog("Speichern unter", ">Ordner;ordner", "=c.txt", "!"); step("save as ordner/c.txt"); type("neu im Ordner, geändert"); onEdt(button(window, "Speichern")::doClick); step("save after save as a new file"); // The open file removed by the command line, then saved. command("rm ordner/c.txt"); step("open file removed"); type("nach dem Löschen"); onEdt(button(window, "Speichern")::doClick); step("save after the file was removed"); // A name with a folder in it, and none. inDialog("Speichern unter", "=ordner/d.txt", "!"); step("save as ordner/d.txt"); inDialog("Speichern unter", "=", "!"); step("save as without a name"); onEdt(button(window, "Neu")::doClick); step("new"); Map output = new LinkedHashMap<>(); output.put("steps", steps); try (FileOutputStream out = new FileOutputStream(args[1])) { out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8)); } network.stop(); System.out.println("text editor window: " + steps.size() + " steps"); } }