package de.uni_passau.fim.infosun.prophet.plugin.plugins; import java.awt.BorderLayout; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Scanner; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.border.Border; import de.uni_passau.fim.infosun.prophet.experimentViewer.EViewer; import de.uni_passau.fim.infosun.prophet.plugin.Plugin; import de.uni_passau.fim.infosun.prophet.util.VerticalLayout; import de.uni_passau.fim.infosun.prophet.util.qTree.Attribute; import de.uni_passau.fim.infosun.prophet.util.qTree.QTreeNode; import de.uni_passau.fim.infosun.prophet.util.settings.Setting; import de.uni_passau.fim.infosun.prophet.util.settings.SettingsList; import de.uni_passau.fim.infosun.prophet.util.settings.components.TextAreaSetting; import static de.uni_passau.fim.infosun.prophet.util.language.UIElementNames.getLocalized; import static de.uni_passau.fim.infosun.prophet.util.qTree.QTreeNode.Type.CATEGORY; import static javax.swing.border.TitledBorder.ABOVE_TOP; import static javax.swing.border.TitledBorder.CENTER; /** * This <code>Plugin</code> enables the experiment editor to define a list commands to be executed on the command * line. The commands will be represented as buttons in a separate <code>JFrame</code> the <code>Plugin</code> displays. * Clicking one of the buttons will execute the associated command. */ public class ExternalProgramsPlugin implements Plugin { private static final String KEY = "start_external_progs"; private static final String KEY_COMMANDS = "commands"; /** * A <code>JFrame</code> that displays a column of <code>JButton</code>s that start external programs on the * command line when clicked. */ public static class ProgramList extends JFrame { private final Map<File, Process> processes; private final Map<JButton, File> programs; private final ActionListener listener; private JPanel list; /** * Constructs a new <code>ProgramList</code>. */ public ProgramList() { setDefaultCloseOperation(DISPOSE_ON_CLOSE); String title = getLocalized("MENU_TAB_SETTINGS_EXTERNAL_PROGRAMS_TITLE"); Border emptyBorder = BorderFactory.createEmptyBorder(10, 20, 0, 20); list = new JPanel(); list.setBorder(BorderFactory.createTitledBorder(emptyBorder, title, CENTER, ABOVE_TOP)); list.setLayout(new VerticalLayout(5, VerticalLayout.STRETCH, VerticalLayout.TOP)); add(list, BorderLayout.CENTER); processes = new HashMap<>(); programs = new HashMap<>(); listener = event -> { Object btn = event.getSource(); if (!(btn instanceof JButton && programs.containsKey(btn))) { return; } File program = programs.get(btn); Process process = processes.get(program); if (process != null && process.isAlive()) { JOptionPane.showMessageDialog(null, getLocalized("MESSAGE_ONLY_ONE_INSTANCE")); } else { try { process = Runtime.getRuntime().exec(program.toString()); processes.replace(program, process); } catch (IOException e) { String message = getLocalized("MESSAGE_COULD_NOT_START_PROGRAM") + ": " + e.getMessage(); JOptionPane.showMessageDialog(null, message); } } }; } /** * Removes all buttons from this <code>ProgramList</code> and destroys all running sub-processes started * previously. The layout of this <code>JFrame</code> must be validated after this method was called. */ public void clear() { list.removeAll(); programs.clear(); processes.values().stream().filter(p -> p != null).forEach(Process::destroy); processes.clear(); } /** * Clears this <code>ProgramList</code> and then loads new programs from the given <code>Attribute</code>. * The programs (<code>String</code>s to be executed on the command line) are separated by line breaks. * The size of the <code>ProgramList</code> will be adjusted to fit the new number of buttons. * * @param attribute * the <code>Attribute</code> to load from */ public void load(Attribute attribute) { Scanner scanner = new Scanner(attribute.getValue()); JButton button; File program; clear(); while (scanner.hasNextLine()) { program = new File(scanner.nextLine()); button = new JButton(program.getName()); button.addActionListener(listener); programs.put(button, program); processes.put(program, null); list.add(button); } pack(); } } private ProgramList pList; private EViewer eViewer; private Point eViewerLoc; /** * Constructs a new <code>ExternalProgramsPlugin</code>. */ public ExternalProgramsPlugin() { pList = new ProgramList(); } @Override public Setting getSetting(QTreeNode node) { if (node.getType() != CATEGORY) { return null; } Attribute mainAttribute = node.getAttribute(KEY); SettingsList settingsList = new SettingsList(mainAttribute, getClass().getSimpleName(), true); settingsList.setCaption(getLocalized("MENU_TAB_SETTINGS_EXTERNAL_PROGRAMS")); Attribute subAttribute = mainAttribute.getSubAttribute(KEY_COMMANDS); Setting subSetting = new TextAreaSetting(subAttribute, null); subSetting.setCaption(getLocalized("MENU_TAB_SETTINGS_PATH_OF_EXTERNAL_PROGRAMS")); settingsList.addSetting(subSetting); return settingsList; } @Override public void experimentViewerRun(EViewer experimentViewer) { this.eViewer = experimentViewer; this.eViewerLoc = experimentViewer.getLocation(); this.eViewer.addComponentListener(new ComponentAdapter() { @Override public void componentMoved(ComponentEvent e) { super.componentMoved(e); eViewerLoc.setLocation(e.getComponent().getLocation()); } }); } @Override public boolean denyEnterNode(QTreeNode node) { return false; } @Override public void enterNode(QTreeNode node) { if (!enabled(node)) { return; } pList.load(node.getAttribute(KEY).getSubAttribute(KEY_COMMANDS)); Rectangle eViewerDim = eViewer.getBounds(); pList.setLocation(eViewerLoc.x + eViewerDim.width + 10, eViewerLoc.y); pList.setVisible(true); } @Override public String denyNextNode(QTreeNode currentNode) { return null; } @Override public void exitNode(QTreeNode node) { if (enabled(node)) { pList.clear(); pList.setVisible(false); } } @Override public String finishExperiment() { pList.dispose(); return null; } /** * Returns whether this <code>Plugin</code> is enabled for the given <code>node</code>. * * @param node * the <code>QTreeNode</code> to check * * @return <code>true</code> iff this <code>Plugin</code> is enabled for <code>node</code> */ private boolean enabled(QTreeNode node) { if (node.getType() != CATEGORY) { return false; } return node.containsAttribute(KEY) && Boolean.parseBoolean(node.getAttribute(KEY).getValue()); } }