// Uses FILIUS' own file explorer and image viewer windows // (GUIApplicationFileExplorerWindow, GUIApplicationImageViewerWindow on a // GUIDesktopPanel) on mail-lan's first computer as a person does: the // explorer's tree, its list with the menu of a right click (on an entry, on // the free space), Java's questions answered as given (a watcher types into // the input dialogs, says yes or no), "Aktualisieren", the import window; // the image viewer's "Öffnen" over the file dialog. The command line beside // them changes the files. After each step it writes what the windows show // (the tree's rows, the list, the menu, the dialogs' texts, the title, the // viewer's pictures) and the computer's files. The reference for the // editor's explorer and viewer (packages/editor/test/fileExplorer.test.ts). // // Usage: java -cp filius.jar:lib/*:classes FileExplorerWindowRun import filius.gui.anwendungssicht.GUIApplicationFileExplorerWindow; import filius.gui.anwendungssicht.GUIApplicationImageViewerWindow; 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.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; 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.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; public class FileExplorerWindowRun { static final String CLIENT = "00000000-0000-0000-0000-000000000201"; static final List steps = new ArrayList<>(); /** The answers to Java's next dialogs: a text typed into an input dialog, or "yes"/"no". */ static final Deque answers = new ArrayDeque<>(); /** The texts of the dialogs since the last step. */ static final List dialogs = new ArrayList<>(); static GUIApplicationFileExplorerWindow explorer; static GUIApplicationImageViewerWindow viewer; static GUIDesktopPanel desktop; static Betriebssystem system; static Terminal terminal; static List menu = null; 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); } static void setField(Class owner, Object object, String name, Object value) throws Exception { Field field = owner.getDeclaredField(name); field.setAccessible(true); field.set(object, value); } @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); } /** Answers each of Java's option dialogs from the answers given, writes down its text. */ static void watchDialogs() { Thread watcher = new Thread(() -> { while (true) { for (Window window : Window.getWindows()) { if (!(window instanceof JDialog) || !window.isVisible()) continue; JOptionPane pane = find((Container) window, JOptionPane.class); if (pane == null) continue; String answer; synchronized (dialogs) { dialogs.add(String.valueOf(pane.getMessage())); answer = answers.isEmpty() ? "yes" : answers.pop(); } if (pane.getWantsInput()) { pane.setInputValue(answer); pane.setValue(JOptionPane.OK_OPTION); } else { pane.setValue(answer.equals("no") ? JOptionPane.NO_OPTION : pane.getOptionType() == JOptionPane.DEFAULT_OPTION ? JOptionPane.OK_OPTION : JOptionPane.YES_OPTION); } window.setVisible(false); } try { Thread.sleep(50); } catch (InterruptedException error) { return; } } }); watcher.setDaemon(true); watcher.start(); } @SuppressWarnings("unchecked") static JList list() throws Exception { return (JList) field(GUIApplicationFileExplorerWindow.class, explorer, "dateiListe"); } static JTree tree() throws Exception { return (JTree) field(GUIApplicationFileExplorerWindow.class, explorer, "tv"); } /** A right click on the list: on the entry's row, or below the entries; the menu Java shows. */ static void rightClick(String entry) throws Exception { JList list = list(); int index = -1; for (int each = 0; each < list.getModel().getSize(); each++) if (list.getModel().getElementAt(each).endsWith(";" + entry)) index = each; if (entry != null && index < 0) throw new IllegalStateException("no entry " + entry); int at = index; List before = popups(); onEdt(() -> { Point point = at >= 0 ? list.indexToLocation(at) : new Point(5, list.getModel().getSize() * 16 + 40); MouseEvent event = new MouseEvent(list, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, point.x + 2, point.y + 2, 1, true, MouseEvent.BUTTON3); for (MouseListener listener : list.getMouseListeners()) listener.mouseClicked(event); }); JPopupMenu popup = null; for (JPopupMenu each : popups()) if (!before.contains(each) && each.isVisible()) popup = each; if (popup == null) throw new IllegalStateException("no menu"); menu = new ArrayList<>(); for (Component item : popup.getComponents()) if (item instanceof JMenuItem) menu.add(((JMenuItem) item).getText()); lastMenu = popup; } static JPopupMenu lastMenu; /** The menus Java shows (a shown menu lies in its frame's layer, not in the window that made it). */ static List popups() { List found = new ArrayList<>(); for (Window window : Window.getWindows()) { all(window, JPopupMenu.class, found); } return found; } /** A right click and the item chosen in its menu. */ static void choose(String entry, String item, String... typed) throws Exception { rightClick(entry); synchronized (dialogs) { answers.addAll(Arrays.asList(typed)); } JMenuItem chosen = null; for (Component each : lastMenu.getComponents()) if (each instanceof JMenuItem && item.equals(((JMenuItem) each).getText())) chosen = (JMenuItem) each; if (chosen == null) throw new IllegalStateException("no item " + item + " in " + menu); JMenuItem pressed = chosen; onEdt(() -> { lastMenu.setVisible(false); pressed.doClick(); }); Thread.sleep(400); } /** Chooses the tree's row of the path (its folders' names from the root), as a click on it does. */ static void select(String... names) throws Exception { JTree tree = tree(); onEdt(() -> { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot(); TreePath path = new TreePath(node); for (String name : names) { DefaultMutableTreeNode next = null; for (Enumeration each = node.children(); each.hasMoreElements();) { DefaultMutableTreeNode child = (DefaultMutableTreeNode) each.nextElement(); if (name.equals(child.getUserObject() instanceof Datei ? ((Datei) child.getUserObject()).getName() : child.toString())) next = child; } if (next == null) throw new IllegalStateException("no tree node " + name); node = next; path = path.pathByAddingChild(node); } tree.setSelectionPath(path); }); } 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); } 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 = String.valueOf(datei.getDateiTyp()).startsWith("text"); files.add(Arrays.asList(path + "/" + datei.getName(), String.valueOf(datei.getDateiTyp()), text ? content : "(" + (content == null ? 0 : content.length()) + " characters)")); // A node below a file (Java can make one): written as it is. if (node.getChildCount() > 0) walk(node, path + "/" + datei.getName(), files); } else { files.add(Arrays.asList(path + "/" + node + "/", "folder", "")); walk(node, path + "/" + node, files); } } } static String title(GUIApplicationWindow window) throws Exception { return ((JLabel) field(GUIApplicationWindow.class, window, "titleLabel")).getText(); } static void step(String name) throws Exception { Map result = new LinkedHashMap<>(); result.put("step", name); JTree tree = tree(); List rows = new ArrayList<>(); SwingUtilities.invokeAndWait(() -> { for (int row = 0; row < tree.getRowCount(); row++) tree.expandRow(row); for (int row = 0; row < tree.getRowCount(); row++) { TreePath path = tree.getPathForRow(row); rows.add(path.getPathCount() - 1 + ":" + path.getLastPathComponent()); } }); result.put("tree", rows); List entries = new ArrayList<>(); ListModel model = list().getModel(); for (int index = 0; index < model.getSize(); index++) entries.add(model.getElementAt(index)); result.put("list", entries); if (menu != null) result.put("menu", menu); menu = null; synchronized (dialogs) { result.put("dialogs", new ArrayList<>(dialogs)); dialogs.clear(); } if (!shown.isEmpty()) result.put("shown", shown); shown = new ArrayList<>(); result.put("files", files()); steps.add(result); } /** What the image viewer shows: its title and its pictures (their sizes; -1 when none could be read). */ static void viewerStep(String name) throws Exception { Map result = new LinkedHashMap<>(); result.put("step", name); result.put("title", title(viewer)); List pictures = new ArrayList<>(); for (JLabel label : all((JPanel) field(GUIApplicationImageViewerWindow.class, viewer, "backPanel"), JLabel.class, new ArrayList<>())) { ImageIcon icon = (ImageIcon) label.getIcon(); pictures.add(icon == null ? "none" : icon.getIconWidth() + "x" + icon.getIconHeight() + (label.isShowing() ? "" : " hidden") + " at " + label.getX() + "," + label.getY()); } result.put("pictures", pictures); if (!shown.isEmpty()) result.put("shown", shown); shown = new ArrayList<>(); steps.add(result); } static JDialog fileDialog() 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"); } /** The viewer's "Öffnen": the folders double clicked, the file's name typed, "Öffnen". */ static void viewerOpen(String folder, String name) throws Exception { onEdt(button(viewer, "Öffnen")::doClick); JDialog dialog = fileDialog(); if (folder != null) { JList list = find(dialog.getContentPane(), JList.class); int index = -1; for (int each = 0; each < list.getModel().getSize(); each++) if (("Ordner;" + folder).equals(String.valueOf(list.getModel().getElementAt(each)))) index = each; 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); }); } JTextField field = find(dialog.getContentPane(), JTextField.class); onEdt(() -> field.setText(name)); onEdt(button(dialog.getContentPane(), "Öffnen")::doClick); Thread.sleep(500); } 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.FileExplorer", false); system.installApp("filius.software.lokal.ImageViewer", 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"); watchDialogs(); JFrame frame = new JFrame(); desktop = new GUIDesktopPanel(system); frame.getContentPane().add(desktop); explorer = (GUIApplicationFileExplorerWindow) desktop.starteAnwendung("filius.software.lokal.FileExplorer"); SwingUtilities.invokeAndWait(() -> { frame.pack(); frame.setVisible(true); }); Thread.sleep(500); Map open = new LinkedHashMap<>(); open.put("step", "explorer title"); open.put("title", title(explorer)); open.put("buttons", Arrays.asList(button(explorer, "Aktualisieren").getText(), button(explorer, "Importieren").getText())); steps.add(open); step("open"); // The menu below the entries: a new folder, named in Java's input dialog. choose(null, "Neuer Ordner", "ordner"); step("new folder ordner"); // The command line changes the files: does the list follow by itself? command("mkdir extern"); command("touch notiz.txt"); step("command line made extern and notiz.txt"); onEdt(button(explorer, "Aktualisieren")::doClick); step("Aktualisieren"); // An entry's menu; copy, then the free space's menu has "Einfügen", once and again. rightClick("notiz.txt"); step("menu of an entry"); choose("notiz.txt", "Kopieren"); rightClick(null); step("menu after copy"); select("ordner"); choose(null, "Einfügen"); step("paste the copy into ordner"); select("extern"); rightClick(null); step("menu after the paste of a copy"); choose(null, "Einfügen"); step("paste the copy again, into extern"); // Cut and paste: the free space's menu has no "Einfügen" afterwards. select(); choose("notiz.txt", "Ausschneiden"); select("webserver"); choose(null, "Einfügen"); rightClick(null); step("cut notiz.txt and paste it into webserver, then the menu"); // Rename: Java's question, a name there already, a blank name, a new one. select(); choose("ordner", "Umbenennen", "extern"); step("rename ordner to extern"); choose("ordner", "Umbenennen", " "); step("rename ordner to blanks"); choose("ordner", "Umbenennen", "Ordner Neu"); step("rename ordner to Ordner Neu"); // Delete: Java asks; no, then yes. choose("extern", "Löschen", "no"); step("delete extern, no"); choose("extern", "Löschen", "yes"); step("delete extern, yes"); // A file chosen in the tree: the list is empty; a new folder there. select("webserver", "index.html"); step("a file chosen in the tree"); choose(null, "Neuer Ordner", "unter"); step("new folder in a file chosen in the tree"); // The import window: its texts, an import without a file, one with the fields as "Datei wählen" fills them. select(); onEdt(button(explorer, "Importieren")::doClick); List texts = new ArrayList<>(); JPanel layer = (JPanel) field(GUIDesktopPanel.class, desktop, "dialogLayer"); for (JLabel label : all(layer, JLabel.class, new ArrayList<>())) texts.add("label:" + label.getText()); for (AbstractButton each : all(layer, AbstractButton.class, new ArrayList<>())) texts.add("button:" + each.getText()); List fields = all(layer, JTextField.class, new ArrayList<>()); for (JTextField each : fields) texts.add("field:" + each.getText() + (each.isEditable() ? "" : " (read only)")); shown.add(texts); JTextArea output = find(layer, JTextArea.class); press(button(layer, "Datei importieren")); shown.add("output:" + output.getText()); java.io.File local = java.io.File.createTempFile("import", ".txt"); java.nio.file.Files.write(local.toPath(), "Zeile 1\nZeile 2".getBytes(StandardCharsets.UTF_8)); setField(GUIApplicationFileExplorerWindow.class, explorer, "datei", local.getName()); setField(GUIApplicationFileExplorerWindow.class, explorer, "pfad", local.getParent() + java.io.File.separator); onEdt(() -> { fields.get(0).setText(local.getPath()); fields.get(1).setText("importiert.txt"); }); press(button(layer, "Datei importieren")); shown.add("output:" + output.getText()); press(button(layer, "Schließen")); shown.add("layer visible:" + layer.isVisible()); step("import window"); // The image viewer. viewer = (GUIApplicationImageViewerWindow) desktop.starteAnwendung("filius.software.lokal.ImageViewer"); Thread.sleep(500); viewerStep("viewer open"); viewerOpen("webserver", "splashscreen-mini.png"); viewerStep("viewer: the web server's picture"); viewerOpen("webserver", "splashscreen-mini.png"); viewerStep("viewer: the picture again"); viewerOpen(null, "importiert.txt"); viewerStep("viewer: a text file"); viewerOpen(null, "fehlt.png"); viewerStep("viewer: no such file"); Map output2 = new LinkedHashMap<>(); output2.put("steps", steps); try (FileOutputStream out = new FileOutputStream(args[1])) { out.write((FlsOracle.Json.write(output2, "") + "\n").getBytes(StandardCharsets.UTF_8)); } network.stop(); System.out.println("file explorer window: " + steps.size() + " steps"); } /** A press with the mouse, as Java's import window listens (mousePressed). */ static void press(AbstractButton button) throws Exception { onEdt(() -> { MouseEvent event = new MouseEvent(button, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, 5, 5, 1, false); for (MouseListener listener : button.getMouseListeners()) listener.mousePressed(event); }); } }