All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.script.client.view.ScriptClientView Maven / Gradle / Ivy

There is a newer version: 2.0.27
Show newest version
package org.bidib.wizard.script.client.view;

import java.awt.Component;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.apache.commons.lang3.StringUtils;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.api.model.NodeProvider;
import org.bidib.wizard.api.script.ScriptCommand;
import org.bidib.wizard.api.script.ScriptEngineListener;
import org.bidib.wizard.api.script.ScriptStatus;
import org.bidib.wizard.api.script.Scripting;
import org.bidib.wizard.api.service.console.ConsoleService;
import org.bidib.wizard.client.common.component.ColorPane;
import org.bidib.wizard.client.common.text.WizardComponentFactory;
import org.bidib.wizard.client.common.view.BasicPopupMenu;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.script.DefaultScriptContext;
import org.bidib.wizard.common.script.engine.ScriptEngine;
import org.bidib.wizard.common.script.switching.SwitchFunctionsScriptCommandFactory;
import org.bidib.wizard.common.utils.ImageUtils;
import org.bidib.wizard.core.dialog.FileDialog;
import org.bidib.wizard.script.client.controller.listener.ScriptClientControllerListener;
import org.bidib.wizard.script.client.model.ScriptClientLogModel;
import org.bidib.wizard.script.client.model.ScriptClientLogModel.ConsoleColor;
import org.bidib.wizard.script.client.model.ScriptClientLogModel.ConsoleLine;
import org.bidib.wizard.script.client.model.ScriptClientModel;
import org.oxbow.swingbits.dialog.task.TaskDialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.value.ValueHolder;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.builder.FormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.Paddings;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.event.DockableSelectionListener;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;

public class ScriptClientView implements Dockable {

    private static final Logger LOGGER = LoggerFactory.getLogger(ScriptClientView.class);

    private static final String ENCODED_DIALOG_COLUMN_SPECS =
        "pref, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, fill:pref:grow";

    private static final String ENCODED_DIALOG_ROW_SPECS =
        "pref, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, fill:50dlu:grow";

    private static final String WORKING_DIR_SCRIPT_CLIENT_VIEW_KEY = "scriptClientView";

    private static final String SCRIPT_EXTENSION = "bidibt";

    private final FileFilter scriptFilter;

    private JComponent contentPanel;

    private final DockingDesktop desktop;

    private final WizardSettingsInterface wizardSettings;

    private final DockableStateChangeListener dockableStateChangeListener;

    private DockableSelectionListener dockableSelectionListener;

    private final ScriptClientControllerListener controllerListener;

    private ImageIcon executionPendingIcon;

    private ImageIcon executionRunningIcon;

    private ImageIcon executionFinishedIcon;

    private ImageIcon executionErrorIcon;

    private final ValueModel selectedScriptModel = new ValueHolder();

    private final ValueModel checkRepeatingModel = new ValueHolder();

    private final AtomicBoolean scriptRepeating = new AtomicBoolean(false);

    private JLabel executionStateIconLabel;

    private JButton startScript;

    private JButton stopScript;

    private JButton startStepScript;

    private JButton nextStepScript;

    private final DefaultScriptContext scriptContext;

    private ScriptEngine scriptEngine;

    private final ColorPane coloredTextPane;

    private final ScriptClientModel scriptClientModel;

    private final ScriptClientLogModel logModel;

    private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");

    private PropertyChangeListener pcsContent;

    private PropertyChangeListener pcsContentSize;

    private AbstractAction startScriptAction;

    private AbstractAction stepScriptAction;

    private AbstractAction nextStepScriptAction;

    private final ConsoleService consoleService;

    public ScriptClientView(final DockingDesktop desktop, final WizardSettingsInterface wizardSettings,
        final ScriptClientControllerListener controllerListener, final ScriptClientModel scriptClientModel,
        final ScriptClientLogModel logModel, final ConsoleService consoleService,
        final DefaultScriptContext scriptContext) {
        this.desktop = desktop;
        this.wizardSettings = wizardSettings;
        this.controllerListener = controllerListener;
        this.scriptClientModel = scriptClientModel;
        this.logModel = logModel;
        this.consoleService = consoleService;

        this.coloredTextPane = new ColorPane();

        this.scriptContext = scriptContext;

        DockKeys.DOCKKEY_SCRIPT_CLIENT_VIEW.setName(Resources.getString(ScriptClientView.class, "title"));
        DockKeys.DOCKKEY_SCRIPT_CLIENT_VIEW.setFloatEnabled(true);
        DockKeys.DOCKKEY_SCRIPT_CLIENT_VIEW.setAutoHideEnabled(false);

        dockableStateChangeListener = new DockableStateChangeListener() {

            @Override
            public void dockableStateChanged(DockableStateChangeEvent event) {
                LOGGER
                    .info("The state has changed, newState: {}, prevState: {}", event.getNewState(),
                        event.getPreviousState());

                DockableState newState = event.getNewState();
                if (newState.getDockable().equals(ScriptClientView.this) && newState.isClosed()) {
                    LOGGER.info("The NodesClientView is closed.");
                    // we are closed
                    desktop.removeDockableStateChangeListener(dockableStateChangeListener);

                    if (ScriptClientView.this.dockableSelectionListener != null) {
                        desktop.removeDockableSelectionListener(ScriptClientView.this.dockableSelectionListener);

                        ScriptClientView.this.dockableSelectionListener = null;
                    }

                    if (controllerListener != null) {
                        LOGGER.info("Close the view.");

                        controllerListener.viewClosed();
                    }
                }
            }
        };
        this.desktop.addDockableStateChangeListener(dockableStateChangeListener);

        String scriptDescription = Resources.getString(ScriptClientView.class, "scriptDescription");
        scriptFilter = new FileNameExtensionFilter(scriptDescription, SCRIPT_EXTENSION);
    }

    public JComponent initComponents(final ValueModel currentCommandModel) {

        // Set the icon for accessory execution
        executionPendingIcon = ImageUtils.createImageIcon(ScriptClientView.class, "/icons/accessory-wait.png");
        executionRunningIcon = ImageUtils.createImageIcon(ScriptClientView.class, "/icons/accessory-wait.png");
        executionFinishedIcon = ImageUtils.createImageIcon(ScriptClientView.class, "/icons/accessory-successful.png");
        executionErrorIcon = ImageUtils.createImageIcon(ScriptClientView.class, "/icons/accessory-error.png");

        // create form builder
        boolean debugDialog = false;
        final JPanel panel = debugDialog ? new FormDebugPanel() : new JPanel();
        final FormBuilder formBuilder =
            FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        formBuilder.border(Paddings.DIALOG);

        final JButton btnHelpScript = new JButton(Resources.getString(ScriptClientView.class, "helpScript"));
        btnHelpScript.addActionListener(evt -> {
            prepareHelp();
        });

        formBuilder.add(btnHelpScript).xy(1, 1);

        final JButton btnSelectScript = new JButton(Resources.getString(ScriptClientView.class, "selectScript"));
        btnSelectScript.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                String storedWorkingDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_SCRIPT_CLIENT_VIEW_KEY);

                // select the script file
                FileDialog dialog =
                    new FileDialog(ScriptClientView.this.contentPanel, FileDialog.OPEN, storedWorkingDirectory,
                        "*." + SCRIPT_EXTENSION, scriptFilter) {

                        @Override
                        public void approve(final String fileName) {
                            LOGGER.info("Load script: {}", fileName);
                            try {
                                File file = new File(fileName);
                                if (file.exists()) {
                                    LOGGER.info("The script file exists: {}", file);
                                    selectedScriptModel.setValue(file.getName());

                                    prepareScript(fileName);

                                    scriptClientModel.setScriptFilePath(fileName);

                                    startScript.setEnabled(true);
                                    startStepScript.setEnabled(true);
                                }
                                else {
                                    selectedScriptModel.setValue("no script selected");
                                    startScript.setEnabled(false);
                                    startStepScript.setEnabled(false);
                                }

                                final String workingDir = Paths.get(fileName).getParent().toString();
                                LOGGER.info("Save current workingDir: {}", workingDir);

                                wizardSettings.setWorkingDirectory(WORKING_DIR_SCRIPT_CLIENT_VIEW_KEY, workingDir);
                            }
                            catch (IllegalArgumentException | IOException ex) {
                                LOGGER.info("Load and process script file failed.", ex);
                                startScript.setEnabled(false);
                                startStepScript.setEnabled(false);

                                // show error
                                TaskDialogs
                                    .build(JOptionPane.getFrameForComponent(desktop),
                                        Resources
                                            .getString(ScriptClientView.class, "load-script-failed.instruction",
                                                ex.getMessage()),
                                        Resources.getString(ScriptClientView.class, "load-script-failed.text"))
                                    .error();
                            }
                        }
                    };
                dialog.showDialog();
            }
        });
        formBuilder.add(btnSelectScript).xy(3, 1);

        JLabel scriptLabel = WizardComponentFactory.createLabel(selectedScriptModel);
        formBuilder.add(scriptLabel).xyw(5, 1, 7);

        JCheckBox repeatingCheck =
            WizardComponentFactory
                .createCheckBox(checkRepeatingModel, Resources.getString(ScriptClientView.class, "repeating"));
        formBuilder.add(repeatingCheck).xyw(1, 3, 3);

        // buttons
        this.startScriptAction = new AbstractAction(Resources.getString(ScriptClientView.class, "startScript")) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("Start script performed.");
                startScript();
            }
        };
        startScript = new JButton(startScriptAction);
        startScript.setEnabled(false);
        startScript.setToolTipText(Resources.getString(ScriptClientView.class, "startScript.tooltip"));
        formBuilder.add(startScript).xy(1, 5);

        this.stepScriptAction = new AbstractAction(Resources.getString(ScriptClientView.class, "startStepScript")) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("Start step script performed.");
                startStepScript();
            }
        };
        startStepScript = new JButton(stepScriptAction);
        startStepScript.setEnabled(false);
        startStepScript.setToolTipText(Resources.getString(ScriptClientView.class, "startStepScript.tooltip"));
        formBuilder.add(startStepScript).xy(3, 5);

        this.nextStepScriptAction = new AbstractAction(Resources.getString(ScriptClientView.class, "nextStepScript")) {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("Next step script performed.");
                nextStepScript();
            }
        };
        nextStepScript = new JButton(nextStepScriptAction);
        nextStepScript.setEnabled(false);
        nextStepScript.setToolTipText(Resources.getString(ScriptClientView.class, "nextStepScript.tooltip"));
        formBuilder.add(nextStepScript).xy(5, 5);

        stopScript = new JButton(Resources.getString(ScriptClientView.class, "stopScript"));
        stopScript.setEnabled(false);
        stopScript.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                stopScript();
            }
        });
        formBuilder.add(stopScript).xy(7, 5);

        executionStateIconLabel = new JLabel();
        formBuilder.add(executionStateIconLabel).xy(9, 5);

        JLabel currentCommandLabel = WizardComponentFactory.createLabel(currentCommandModel);
        formBuilder.add(Resources.getString(ScriptClientView.class, "currentStep")).xy(1, 7);
        formBuilder.add(currentCommandLabel).xyw(3, 7, 9);

        checkRepeatingModel.addValueChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.info("Repeating has changed: {}", checkRepeatingModel.getValue());
                Boolean repeating = (Boolean) checkRepeatingModel.getValue();

                scriptRepeating.set(repeating);
                scriptEngine.setScriptRepeating(repeating);
            }
        });

        // log pane
        coloredTextPane.setEditable(false);

        final JScrollPane scrollPane = new JScrollPane(coloredTextPane);
        formBuilder.add(scrollPane).xyw(1, 9, 11);

        pcsContent = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.info("Property was changed: {}", evt);
                if (evt instanceof IndexedPropertyChangeEvent) {
                    IndexedPropertyChangeEvent ipce = (IndexedPropertyChangeEvent) evt;

                    ConsoleLine newLine = (ConsoleLine) ipce.getNewValue();

                    if (newLine != null) {
                        String now = dateFormat.format(new Date());
                        coloredTextPane.append(newLine.getColor(), now + " - " + newLine.getMessage() + "\n");
                    }
                }
            }
        };

        logModel.addPropertyChangeListener(ScriptClientLogModel.PROPERTY_CONSOLE_CONTENT, pcsContent);

        pcsContentSize = new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.info("Property was changed: {}", evt);

                if (evt.getNewValue() != null) {
                    Integer value = (Integer) evt.getNewValue();
                    if (value == 0) {
                        // clear the console
                        coloredTextPane.clear();
                    }
                }
            }
        };

        logModel.addPropertyChangeListener(ScriptClientLogModel.PROPERTY_CONSOLE_CONTENT_SIZE, pcsContentSize);

        JPopupMenu popupMenu = new BasicPopupMenu();
        JMenuItem clearConsole = new JMenuItem(Resources.getString(ScriptClientView.class, "clear_console"));
        clearConsole.addActionListener(evt -> fireClearLogPane());
        popupMenu.add(clearConsole);
        coloredTextPane.setComponentPopupMenu(popupMenu);

        // build the content panel
        this.contentPanel = formBuilder.build();

        // create the script engine
        scriptEngine = new ScriptEngine<>((Scripting) this.controllerListener, this.scriptContext);
        scriptEngine.addScriptEngineListener((ScriptEngineListener) this.controllerListener);

        return this.contentPanel;
    }

    @Override
    public DockKey getDockKey() {
        return DockKeys.DOCKKEY_SCRIPT_CLIENT_VIEW;
    }

    @Override
    public Component getComponent() {
        return this.contentPanel;
    }

    public void prepareModel(NodeProvider nodeProvider) {
        // TODO Auto-generated method stub

    }

    private void startScript() {
        LOGGER.info("Start the script.");

        if (!startScript.isEnabled() && scriptEngine.hasScriptCommands()) {
            LOGGER.warn(">>>> The script is running already!");
            return;
        }

        cleanupContext(scriptContext);

        try {
            String fileName = this.scriptClientModel.getScriptFilePath();
            prepareScript(fileName);
        }
        catch (IllegalArgumentException | IOException ex) {
            LOGGER.info("Load and process script file failed.", ex);
            startScript.setEnabled(false);
            startStepScript.setEnabled(false);

            // show error
            TaskDialogs
                .build(JOptionPane.getFrameForComponent(desktop),
                    Resources.getString(ScriptClientView.class, "load-script-failed.instruction", ex.getMessage()),
                    Resources.getString(ScriptClientView.class, "load-script-failed.text"))
                .error();
        }

        if (!startScript.isEnabled() || !scriptEngine.hasScriptCommands()) {
            this.logModel.addConsoleLine(ConsoleColor.red, ">> No script commands available. Load script!");
            return;
        }

        // make sure auto scroll is enabled
        coloredTextPane.setAutoscrolls(true);
        coloredTextPane.setCaretPosition(coloredTextPane.getDocument().getLength());

        executionStateIconLabel.setIcon(executionPendingIcon);

        this.logModel.addConsoleLine(ConsoleColor.black, ">> Start script execution.");

        scriptEngine.startScript();
    }

    private void cleanupContext(final DefaultScriptContext scriptContext) {
        scriptContext.unregister(Scripting.KEY_SCRIPT_ERRORS);

        // unregister the existing parameters
        scriptContext.unregister(DefaultScriptContext.KEY_PARAMETERS);
        // register the empty parameters map
        scriptContext.register(DefaultScriptContext.KEY_PARAMETERS, new HashMap());

        // unregister the existing options
        scriptContext.unregister(Scripting.KEY_OPTIONS);
        // register the empty options map
        scriptContext.register(Scripting.KEY_OPTIONS, new HashMap());
    }

    private void startStepScript() {
        LOGGER.info("Start step the script.");

        if (!startStepScript.isEnabled() && scriptEngine.hasScriptCommands()) {
            LOGGER.warn(">>>> The script is running already!");
            return;
        }

        cleanupContext(scriptContext);

        try {
            String fileName = this.scriptClientModel.getScriptFilePath();
            prepareScript(fileName);
        }
        catch (IllegalArgumentException | IOException ex) {
            LOGGER.info("Load and process script file failed.", ex);
            startScript.setEnabled(false);
            startStepScript.setEnabled(false);

            // show error
            TaskDialogs
                .build(JOptionPane.getFrameForComponent(desktop),
                    Resources.getString(ScriptClientView.class, "load-script-failed.instruction", ex.getMessage()),
                    Resources.getString(ScriptClientView.class, "load-script-failed.text"))
                .error();
        }

        if (!startStepScript.isEnabled() || !scriptEngine.hasScriptCommands()) {
            this.logModel.addConsoleLine(ConsoleColor.red, ">> No script commands available. Load script!");
            return;
        }

        // make sure auto scroll is enabled
        coloredTextPane.setAutoscrolls(true);
        coloredTextPane.setCaretPosition(coloredTextPane.getDocument().getLength());

        executionStateIconLabel.setIcon(executionPendingIcon);

        this.logModel.addConsoleLine(ConsoleColor.black, ">> Start step script execution.");

        scriptEngine.startStepScript(cmd -> {
            LOGGER.info("Execute command: {}", cmd);

            try {

                this.nextStepLatch = new CountDownLatch(1);

                SwingUtilities.invokeAndWait(() -> {
                    nextStepScript.setEnabled(true);
                });

                this.nextStepLatch.await();

                LOGGER.info("The next step latch has changed. Current count: {}", this.nextStepLatch.getCount());

                SwingUtilities.invokeAndWait(() -> {
                    nextStepScript.setEnabled(false);
                });

            }
            catch (InvocationTargetException | InterruptedException ex) {
                LOGGER.warn("Wait for next step failed.", ex);
                return Boolean.FALSE;
            }

            return Boolean.TRUE;
        });
    }

    private CountDownLatch nextStepLatch;

    private void nextStepScript() {
        LOGGER.info("Next step was performed.");

        if (!nextStepScript.isEnabled() && scriptEngine.hasScriptCommands()) {
            LOGGER.warn(">>>> The script is running already!");
            return;
        }

        nextStepScript.setEnabled(false);

        this.nextStepLatch.countDown();
    }

    private void stopScript() {
        LOGGER.info("Stop the script.");

        ScriptClientView.this.logModel.addConsoleLine(ConsoleColor.black, ">> Stop script execution.");
        nextStepScript.setEnabled(false);

        scriptEngine.stopScript();
    }

    private void prepareScript(String fileName) throws IOException {
        Path fFilePath = Paths.get(fileName);

        // create the factory to parse the script
        final SwitchFunctionsScriptCommandFactory factory =
            new SwitchFunctionsScriptCommandFactory(this.consoleService);
        final List> scriptCommands = new LinkedList<>();

        try (Scanner scanner = new Scanner(fFilePath, StandardCharsets.UTF_8)) {
            while (scanner.hasNextLine()) {
                processLine(scanner.nextLine().trim(), factory, scriptCommands);
            }
        }

        LOGGER.info("Prepared list of commands: {}", scriptCommands);

        scriptEngine.setScriptCommands(scriptCommands);
    }

    private void processLine(
        String line, SwitchFunctionsScriptCommandFactory factory, List> scriptCommands) {
        LOGGER.info("Process line: {}", line);

        if (line.startsWith("#") || StringUtils.isBlank(line)) {
            LOGGER.info("Skip comment or empty line.");
        }
        else {
            LOGGER.info("Current line: {}", line);

            try {
                ScriptCommand command = factory.parse(line);
                if (command != null) {
                    scriptCommands.add(command);
                }
            }
            catch (NoSuchElementException ex) {

                this.logModel.addConsoleLine(ConsoleColor.red, "Script error in line: " + line);
                throw new IllegalArgumentException("Script error detected.");
            }
            catch (IllegalArgumentException ex) {

                this.logModel.addConsoleLine(ConsoleColor.red, "Script error in line: " + line);
                throw ex;
            }
        }
    }

    private void fireClearLogPane() {
        LOGGER.info("clear the log pane.");

        logModel.clear();
        coloredTextPane.clear();
    }

    public void close() {
        LOGGER.info("Panel was closed. Cleanup property change listeners.");
        logModel.removePropertyChangeListener(ScriptClientLogModel.PROPERTY_CONSOLE_CONTENT, pcsContent);
        logModel.removePropertyChangeListener(ScriptClientLogModel.PROPERTY_CONSOLE_CONTENT_SIZE, pcsContentSize);
    }

    public void scriptStatusChanged(ScriptStatus scriptStatus) {

        SwingUtilities.invokeLater(() -> {
            switch (scriptStatus) {
                case RUNNING:
                    startScript.setEnabled(false);
                    startStepScript.setEnabled(false);
                    stopScript.setEnabled(true);
                    executionStateIconLabel.setIcon(executionRunningIcon);
                    break;
                case STOPPED:
                case FINISHED:
                    startScript.setEnabled(true);
                    startStepScript.setEnabled(true);
                    nextStepScript.setEnabled(false);
                    stopScript.setEnabled(false);
                    executionStateIconLabel.setIcon(executionFinishedIcon);
                    break;
                case FINISHED_WITH_ERRORS:
                case ABORTED:

                    startScript.setEnabled(true);
                    startStepScript.setEnabled(true);
                    nextStepScript.setEnabled(false);
                    stopScript.setEnabled(false);

                    // set the error icon
                    executionStateIconLabel.setIcon(executionErrorIcon);
                    break;
                default:
                    break;
            }
        });

    }

    public void initKeyBindings() {
        LOGGER.info("Init the key bindings.");

        KeyStroke ksF5 = KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0);
        this.contentPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksF5, "startScript");
        this.contentPanel.getActionMap().put("startScript", startScriptAction);

        KeyStroke ksF2 = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
        this.contentPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksF2, "stepScript");
        this.contentPanel.getActionMap().put("stepScript", stepScriptAction);

        KeyStroke ksF3 = KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0);
        this.contentPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ksF3, "nextStep");
        this.contentPanel.getActionMap().put("nextStep", nextStepScriptAction);

    }

    private void prepareHelp() {

        LOGGER.info("Prepare and show the script help in browser.");

        final SwitchFunctionsScriptCommandFactory factory =
            new SwitchFunctionsScriptCommandFactory(this.consoleService);
        List helpItems = factory.prepareHelp();

        final String fileName = "scriptClientHelp";

        try {
            File tempDir = new File(System.getProperty("java.io.tmpdir"));
            File tempFile = File.createTempFile(fileName, ".html", tempDir);

            try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, StandardCharsets.UTF_8, true))) {
                writer.write("\n");
                writer
                    .write("\n" //
                        + "\n" //
                        + "\n" //
                        + "\n");
                writer.write("\n");

                for (String str : helpItems) {
                    writer.write("");
                }
                writer.write("\n\n");
            }

            File htmlFile = tempFile;
            Desktop.getDesktop().browse(htmlFile.toURI());
        }
        catch (IOException ex) {
            LOGGER.warn("Create help file failed.", ex);

            // show error
            TaskDialogs
                .build(JOptionPane.getFrameForComponent(desktop),
                    Resources.getString(ScriptClientView.class, "show-script-help-failed.instruction", ex.getMessage()),
                    Resources.getString(ScriptClientView.class, "show-script-help-failed.text"))
                .error();

        }

    }

}










BiDiB-Wizard ScriptClient Commands
"); writer.write(str + System.lineSeparator()); writer.write("