org.sikuli.ide.SikuliIDE.java Source code

Java tutorial

Introduction

Here is the source code for org.sikuli.ide.SikuliIDE.java

Source

/*
 * Copyright 2010-2014, Sikuli.org, sikulix.com
 * Released under the MIT License.
 *
 * modified RaiMan 2014
 */
package org.sikuli.ide;

import com.explodingpixels.macwidgets.MacUtils;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import org.apache.commons.cli.CommandLine;
import org.jdesktop.swingx.JXCollapsiblePane;
import org.jdesktop.swingx.JXSearchField;
import org.jdesktop.swingx.JXTaskPane;
import org.jdesktop.swingx.JXTaskPaneContainer;
import org.sikuli.script.EventObserver;
import org.sikuli.script.EventSubject;
import org.sikuli.basics.HotkeyEvent;
import org.sikuli.basics.HotkeyListener;
import org.sikuli.basics.HotkeyManager;
import org.sikuli.script.Location;
import org.sikuli.script.OverlayCapturePrompt;
import org.sikuli.script.Screen;
import org.sikuli.script.ScreenImage;
import org.sikuli.basics.Debug;
import org.sikuli.basics.FileManager;
import org.sikuli.scriptrunner.IScriptRunner;
import org.sikuli.basics.PreferencesUser;
import org.sikuli.basics.Settings;
import org.sikuli.idesupport.IIDESupport;
import org.sikuli.script.ImagePath;
import org.sikuli.basics.ResourceLoader;
import org.sikuli.scriptrunner.ScriptRunner;
import org.sikuli.idesupport.IDESupport;
import org.sikuli.script.Key;
import org.sikuli.script.Sikulix;

public class SikuliIDE extends JFrame implements InvocationHandler {

    private static String me = "IDE: ";
    private static int lvl = 3;

    private static void log(int level, String message, Object... args) {
        Debug.logx(level, me + message, args);
    }

    final static boolean ENABLE_UNIFIED_TOOLBAR = true;
    final static Color COLOR_SEARCH_FAILED = Color.red;
    final static Color COLOR_SEARCH_NORMAL = Color.black;
    final static int WARNING_CANCEL = 2;
    final static int WARNING_ACCEPTED = 1;
    final static int WARNING_DO_NOTHING = 0;
    final static int IS_SAVE_ALL = 3;
    private Dimension _windowSize = null;
    private Point _windowLocation = null;
    private CloseableTabbedPane tabPane;
    private EditorLineNumberView lineNumberColumn;
    private JSplitPane _mainSplitPane;
    private JTabbedPane msgPane;
    private boolean msgPaneCollapsed = false;
    private EditorConsolePane _console;
    private JXCollapsiblePane _cmdList;
    private SikuliIDEStatusBar _status = null;
    private ButtonCapture _btnCapture;
    private ButtonRun _btnRun, _btnRunViz;
    private boolean ideIsRunningScript = false;
    private JXSearchField _searchField;
    private JMenuBar _menuBar = new JMenuBar();
    private JMenu _fileMenu = new JMenu(_I("menuFile"));
    private JMenu _editMenu = new JMenu(_I("menuEdit"));
    private UndoAction _undoAction = new UndoAction();
    private RedoAction _redoAction = new RedoAction();
    private FindAction _findHelper;
    private JMenu _runMenu = new JMenu(_I("menuRun"));
    private JMenu _viewMenu = new JMenu(_I("menuView"));
    private JMenu _toolMenu = new JMenu(_I("menuTool"));
    private JMenu _helpMenu = new JMenu(_I("menuHelp"));
    private JXCollapsiblePane _sidePane;
    private JCheckBoxMenuItem _chkShowUnitTest;
    private JMenuItem chkShowCmdList = null;
    private JCheckBoxMenuItem chkShowThumbs;
    //private UnitTestRunner _testRunner;
    private static CommandLine cmdLine;
    private static String cmdValue;
    private static String[] loadScripts = null;
    private static SikuliIDE sikulixIDE = null;
    private boolean _inited = false;
    private int restoredScripts = 0;
    private int alreadyOpenedTab = -1;
    private PreferencesUser prefs;
    private boolean ACCESSING_AS_FOLDER = false;
    private static long start;
    private static File isRunning = null;
    private static FileOutputStream isRunningFile = null;
    private boolean showAbout = true;
    private boolean showPrefs = true;
    private boolean showQuit = true;

    static {
    }

    private SikuliIDE() {
        super("SikuliX-IDE");
    }

    public static synchronized SikuliIDE getInstance() {
        if (sikulixIDE == null) {
            sikulixIDE = new SikuliIDE();
        }
        return sikulixIDE;
    }

    public static String _I(String key, Object... args) {
        try {
            return SikuliIDEI18N._I(key, args);
        } catch (Exception e) {
            Debug.log(3, "[I18N] " + key);
            return key;
        }
    }

    public static void main(String[] args) {

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                log(lvl, "final cleanup");
                if (isRunning != null) {
                    try {
                        isRunningFile.close();
                    } catch (IOException ex) {
                    }
                    isRunning.delete();
                }
                FileManager.cleanTemp();
            }
        });

        //    if (System.getProperty("sikuli.FromCommandLine") == null) {
        //      String[] userOptions = collectOptions("IDE", args);
        //      if (userOptions == null) {
        //        System.exit(0);
        //      }
        //      if (userOptions.length > 0) {
        //        for (String e : userOptions) {
        //          log(lvl, "arg: " + e);
        //        }
        //        args = userOptions;
        //      }
        //    }

        start = (new Date()).getTime();

        CommandArgs cmdArgs = new CommandArgs("IDE");
        cmdLine = cmdArgs.getCommandLine(CommandArgs.scanArgs(args));

        if (cmdLine == null) {
            Debug.error("Did not find any valid option on command line!");
            System.exit(1);
        }

        if (cmdLine.hasOption(CommandArgsEnum.DEBUG.shortname())) {
            cmdValue = cmdLine.getOptionValue(CommandArgsEnum.DEBUG.longname());
            if (cmdValue == null) {
                Debug.setDebugLevel(3);
                Debug.setLogFile("");
                Settings.LogTime = true;
            } else {
                Debug.setDebugLevel(cmdValue);
            }
        }

        if (cmdLine.hasOption("h")) {
            cmdArgs.printHelp();
            System.exit(0);
        }

        if (cmdLine.hasOption("c")) {
            System.setProperty("sikuli.console", "false");
        }

        if (cmdLine.hasOption(CommandArgsEnum.LOGFILE.shortname())) {
            cmdValue = cmdLine.getOptionValue(CommandArgsEnum.LOGFILE.longname());
            if (!Debug.setLogFile(cmdValue == null ? "" : cmdValue)) {
                System.exit(1);
            }
        }

        if (cmdLine.hasOption(CommandArgsEnum.USERLOGFILE.shortname())) {
            cmdValue = cmdLine.getOptionValue(CommandArgsEnum.USERLOGFILE.longname());
            if (!Debug.setUserLogFile(cmdValue == null ? "" : cmdValue)) {
                System.exit(1);
            }
        }

        if (cmdLine.hasOption(CommandArgsEnum.LOAD.shortname())) {
            loadScripts = cmdLine.getOptionValues(CommandArgsEnum.LOAD.longname());
            log(lvl, "requested to load: %s", loadScripts);
        }

        if (cmdLine.hasOption(CommandArgsEnum.RUN.shortname())
                || cmdLine.hasOption(CommandArgsEnum.TEST.shortname())
                || cmdLine.hasOption(CommandArgsEnum.INTERACTIVE.shortname())) {
            log(lvl, "Switching to ScriptRunner with option -r, -t or -i");
            ScriptRunner.runscript(args);
        }

        new File(Settings.BaseTempPath).mkdirs();
        isRunning = new File(Settings.BaseTempPath, "sikuli-ide-isrunning");
        try {
            isRunning.createNewFile();
            isRunningFile = new FileOutputStream(isRunning);
            if (null == isRunningFile.getChannel().tryLock()) {
                Sikulix.popError("Terminating on FatalError: IDE already running");
                System.exit(1);
            }
        } catch (Exception ex) {
            Sikulix.popError("Terminating on FatalError: cannot access IDE lock for/n" + isRunning);
            System.exit(1);
        }

        Settings.isRunningIDE = true;
        if (Settings.isMac()) {
            System.setProperty("apple.laf.useScreenMenuBar", "true");
            System.setProperty("com.apple.mrj.application.apple.menu.about.name", "SikuliX-IDE");
        }
        getInstance();

        Settings.setArgs(cmdArgs.getUserArgs(), cmdArgs.getSikuliArgs());
        Settings.showJavaInfo();
        Settings.printArgs();

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            //TODO UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        } catch (Exception e) {
            log(-1, "Problem loading UIManager!\nError: %s", e.getMessage());
        }

        ScriptRunner.initScriptingSupport();
        IDESupport.initIDESupport();
        sikulixIDE.initNativeSupport();
        sikulixIDE.initSikuliIDE(args);
    }

    private void initSikuliIDE(String[] args) {
        prefs = PreferencesUser.getInstance();
        if (prefs.getUserType() < 0) {
            prefs.setUserType(PreferencesUser.NEWBEE);
            prefs.setIdeSession("");
            prefs.setDefaults(prefs.getUserType());
        }

        ResourceLoader.get().check(Settings.SIKULI_LIB);

        _windowSize = prefs.getIdeSize();
        _windowLocation = prefs.getIdeLocation();
        Screen m = (Screen) (new Location(_windowLocation)).getScreen();
        if (m == null) {
            String em = "Remembered window not valid.\nGoing to primary screen";
            log(-1, em);
            Sikulix.popError(em, "IDE has problems ...");
            m = Screen.getPrimaryScreen();
            _windowSize.width = 0;
        }
        Rectangle s = m.getBounds();
        if (_windowSize.width == 0 || _windowSize.width > s.width || _windowSize.height > s.height) {
            if (s.width < 1025) {
                _windowSize = new Dimension(1024, 700);
                _windowLocation = new Point(0, 0);
            } else {
                _windowSize = new Dimension(s.width - 150, s.height - 100);
                _windowLocation = new Point(75, 0);
            }
        }
        setSize(_windowSize);
        setLocation(_windowLocation);

        Debug.log(3, "IDE: Adding components to window");
        initMenuBars(this);
        final Container c = getContentPane();
        c.setLayout(new BorderLayout());
        Debug.log(3, "IDE: creating tabbed editor");
        initTabPane();
        Debug.log(3, "IDE: creating message area");
        initMsgPane(prefs.getPrefMoreMessage() == PreferencesUser.HORIZONTAL);
        // RaiMan not used      initSidePane(); // IDE UnitTest

        Debug.log(3, "IDE: creating combined work window");
        JPanel codeAndUnitPane = new JPanel(new BorderLayout(10, 10));
        codeAndUnitPane.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
        codeAndUnitPane.add(tabPane, BorderLayout.CENTER);
        // RaiMan not used      codeAndUnitPane.add(_sidePane, BorderLayout.EAST);
        if (prefs.getPrefMoreMessage() == PreferencesUser.VERTICAL) {
            _mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, codeAndUnitPane, msgPane);
        } else {
            _mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, codeAndUnitPane, msgPane);
        }
        _mainSplitPane.setResizeWeight(0.6);
        _mainSplitPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

        Debug.log(3, "IDE: Putting all together");
        JPanel editPane = new JPanel(new BorderLayout(0, 0));

        JComponent cp = createCommandPane();

        if (PreferencesUser.getInstance().getPrefMoreCommandBar()) {
            editPane.add(cp, BorderLayout.WEST);
        }

        editPane.add(_mainSplitPane, BorderLayout.CENTER);
        c.add(editPane, BorderLayout.CENTER);

        JToolBar tb = initToolbar();
        c.add(tb, BorderLayout.NORTH); // the buttons

        c.add(initStatusbar(), BorderLayout.SOUTH);
        c.doLayout();

        initShortcutKeys();
        initHotkeys();
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        initWindowListener();
        initTooltip();

        autoCheckUpdate();

        _inited = true;
        try {
            getCurrentCodePane().requestFocus();
        } catch (Exception e) {
        }

        restoreSession(0);
        if (tabPane.getTabCount() == 0) {
            (new FileAction()).doNew(null);
        }
        tabPane.setSelectedIndex(0);

        Debug.log(3, "Sikuli-IDE startup: " + ((new Date()).getTime() - start));
        setVisible(true);
        _mainSplitPane.setDividerLocation(0.6);
        return; // as breakpoint
    }

    private void initNativeSupport() {
        if (!Settings.isMac()) {
            return;
        }
        log(lvl, "initNativeSupport: starting");
        try {
            //      com.apple.eawt.QuitResponse
            Class sysclass = URLClassLoader.class;
            Class comAppleEawtApplication = sysclass.forName("com.apple.eawt.Application");
            Method mGetApplication = comAppleEawtApplication.getDeclaredMethod("getApplication", null);
            Object instApplication = mGetApplication.invoke(null, null);

            Class clAboutHandler = sysclass.forName("com.apple.eawt.AboutHandler");
            Class clPreferencesHandler = sysclass.forName("com.apple.eawt.PreferencesHandler");
            Class clQuitHandler = sysclass.forName("com.apple.eawt.QuitHandler");

            Object appHandler = Proxy.newProxyInstance(comAppleEawtApplication.getClassLoader(),
                    new Class[] { clAboutHandler, clPreferencesHandler, clQuitHandler }, this);
            Method m = comAppleEawtApplication.getMethod("setAboutHandler", new Class[] { clAboutHandler });
            m.invoke(instApplication, new Object[] { appHandler });
            showAbout = false;
            m = comAppleEawtApplication.getMethod("setPreferencesHandler", new Class[] { clPreferencesHandler });
            m.invoke(instApplication, new Object[] { appHandler });
            showPrefs = false;
            m = comAppleEawtApplication.getMethod("setQuitHandler", new Class[] { clQuitHandler });
            m.invoke(instApplication, new Object[] { appHandler });
            showQuit = false;
        } catch (Exception ex) {
            String em = String.format("initNativeSupport: Mac: error:\n%s", ex.getMessage());
            log(-1, em);
            Sikulix.popError(em, "IDE has problems ...");
            System.exit(1);
        }
        log(lvl, "initNativeSupport: success");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String mName = method.getName();
        if ("handleAbout".equals(mName)) {
            sikulixIDE.doAbout();
        } else if ("handlePreferences".equals(mName)) {
            sikulixIDE.showPreferencesWindow();
        } else if ("handleQuitRequestWith".equals(mName)) {
            try {
                Class sysclass = URLClassLoader.class;
                Class comAppleEawtQuitResponse = sysclass.forName("com.apple.eawt.QuitResponse");
                Method mCancelQuit = comAppleEawtQuitResponse.getMethod("cancelQuit", null);
                Method mPerformQuit = comAppleEawtQuitResponse.getMethod("performQuit", null);
                Object resp = args[1];
                if (!sikulixIDE.quit()) {
                    mCancelQuit.invoke(resp, null);
                } else {
                    mPerformQuit.invoke(resp, null);
                }
            } catch (Exception ex) {
                log(lvl, "NativeSupport: Quit: error: %s", ex.getMessage());
                System.exit(1);
            }
        }
        return new Object();
    }

    @Override
    public void setTitle(String title) {
        super.setTitle(SikuliIDESettings.SikuliVersion + " - " + title);
    }

    public static ImageIcon getIconResource(String name) {
        URL url = SikuliIDE.class.getResource(name);
        if (url == null) {
            Debug.error("Warning: could not load \"" + name + "\" icon");
            return null;
        }
        return new ImageIcon(url);
    }

    //<editor-fold defaultstate="collapsed" desc="save / restore session">
    private boolean saveSession(int action, boolean quitting) {
        int nTab = tabPane.getTabCount();
        StringBuilder sbuf = new StringBuilder();
        for (int tabIndex = 0; tabIndex < nTab; tabIndex++) {
            try {
                EditorPane codePane = getPaneAtIndex(tabIndex);
                if (action == WARNING_DO_NOTHING) {
                    if (quitting) {
                        codePane.setDirty(false);
                    }
                    if (codePane.getCurrentFilename() == null) {
                        continue;
                    }
                } else if (codePane.isDirty()) {
                    if (!(new FileAction()).doSaveIntern(tabIndex)) {
                        if (quitting) {
                            codePane.setDirty(false);
                        }
                        continue;
                    }
                }
                if (action == IS_SAVE_ALL) {
                    continue;
                }
                File f = codePane.getCurrentFile();
                if (f != null) {
                    String bundlePath = codePane.getSrcBundle();
                    Debug.log(5, "save session: " + bundlePath);
                    if (tabIndex != 0) {
                        sbuf.append(";");
                    }
                    sbuf.append(bundlePath);
                }
            } catch (Exception e) {
                log(-1, "Problem while trying to save all changed-not-saved scripts!\nError: %s", e.getMessage());
                return false;
            }
        }
        PreferencesUser.getInstance().setIdeSession(sbuf.toString());
        return true;
    }

    private void restoreSession(int tabIndex) {
        String session_str = prefs.getIdeSession();
        String[] filenames = null;
        if (session_str == null && loadScripts == null) {
            return;
        }
        filenames = session_str.split(";");
        for (int i = 0; i < filenames.length; i++) {
            if (filenames[i].isEmpty()) {
                continue;
            }
            Debug.log(3, "restore session at %d: " + filenames[i], restoredScripts + 1);
            File f = new File(filenames[i]);
            if (f.exists()) {
                if (restoreScriptFromSession(filenames[i])) {
                    restoredScripts += 1;
                }
            }
        }
        if (loadScripts == null) {
            return;
        }
        int ao;
        for (int i = 0; i < loadScripts.length; i++) {
            if (loadScripts[i].isEmpty()) {
                continue;
            }
            File f = new File(loadScripts[i]);
            if (f.exists()) {
                if (restoreScriptFromSession(loadScripts[i])) {
                    ao = isAlreadyOpen(getCurrentCodePane().getCurrentSrcDir());
                    if (ao < 0) {
                        restoredScripts += 1;
                        Debug.log(3, "preload script at %d: " + loadScripts[i], restoredScripts + 1);
                    } else {
                        tabPane.remove(ao);
                        Debug.log(3, "preload session script at %d: " + loadScripts[i], restoredScripts + 1);
                    }
                }
            }
        }
    }

    private boolean restoreScriptFromSession(String file) {
        EditorPane ep = (new FileAction()).doNew(null, -1);
        ep.loadFile(file);
        if (ep.hasEditingFile()) {
            setCurrentFileTabTitle(file);
            return true;
        }
        log(-1, "restoreScriptFromSession: Can't load: %s", file);
        //    (new FileAction()).doCloseTab(null);
        return false;
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Support SikuliIDE">
    public static IIDESupport getIDESupport(String ending) {
        return IDESupport.ideSupporter.get(ending);
    }

    public JMenu getFileMenu() {
        return _fileMenu;
    }

    public JMenu getRunMenu() {
        return _runMenu;
    }

    public CloseableTabbedPane getTabPane() {
        return tabPane;
    }

    public EditorPane getCurrentCodePane() {
        if (tabPane.getSelectedIndex() == -1) {
            return null;
        }
        JScrollPane scrPane = (JScrollPane) tabPane.getSelectedComponent();
        EditorPane pane = (EditorPane) scrPane.getViewport().getView();
        return pane;
    }

    public EditorPane getPaneAtIndex(int index) {
        JScrollPane scrPane = (JScrollPane) tabPane.getComponentAt(index);
        EditorPane codePane = (EditorPane) scrPane.getViewport().getView();
        return codePane;
    }

    public void setCurrentFileTabTitle(String fname) {
        int tabIndex = tabPane.getSelectedIndex();
        setFileTabTitle(fname, tabIndex);
    }

    public String getCurrentFileTabTitle() {
        String fname = tabPane.getTitleAt(tabPane.getSelectedIndex());
        if (fname.startsWith("*")) {
            return fname.substring(1);
        } else {
            return fname;
        }
    }

    public void setCurrentFileTabTitleDirty(boolean isDirty) {
        int i = tabPane.getSelectedIndex();
        String title = tabPane.getTitleAt(i);
        if (!isDirty && title.startsWith("*")) {
            title = title.substring(1);
            tabPane.setTitleAt(i, title);
        } else if (isDirty && !title.startsWith("*")) {
            title = "*" + title;
            tabPane.setTitleAt(i, title);
        }
    }

    public void setFileTabTitle(String fname, int tabIndex) {
        String fullName = fname;
        if (fname.endsWith("/")) {
            fname = fname.substring(0, fname.length() - 1);
        }
        fname = fname.substring(fname.lastIndexOf("/") + 1);
        fname = fname.substring(0, fname.lastIndexOf("."));
        tabPane.setTitleAt(tabIndex, fname);
        this.setTitle(fullName);
    }

    public ArrayList<String> getOpenedFilenames() {
        int nTab = tabPane.getTabCount();
        File file = null;
        String filePath;
        ArrayList<String> filenames = new ArrayList<String>(0);
        if (nTab > 0) {
            for (int i = 0; i < nTab; i++) {
                EditorPane codePane = getPaneAtIndex(i);
                file = codePane.getCurrentFile(false);
                if (file != null) {
                    filePath = FileManager.slashify(file.getAbsolutePath(), false);
                    filePath = filePath.substring(0, filePath.lastIndexOf("/"));
                    filenames.add(filePath);
                } else {
                    filenames.add("");
                }
            }
        }
        return filenames;
    }

    public int isAlreadyOpen(String filename) {
        int aot = getOpenedFilenames().indexOf(filename);
        if (aot > -1 && aot < (tabPane.getTabCount() - 1)) {
            alreadyOpenedTab = aot;
            return aot;
        }
        return -1;
    }

    private void autoCheckUpdate() {
        PreferencesUser pref = PreferencesUser.getInstance();
        if (!pref.getCheckUpdate()) {
            return;
        }
        long last_check = pref.getCheckUpdateTime();
        long now = (new Date()).getTime();
        if (now - last_check > 1000 * 604800) {
            Debug.log(3, "autocheck update");
            (new HelpAction()).checkUpdate(true);
        }
        pref.setCheckUpdateTime();
    }

    public synchronized boolean isRunningScript() {
        return ideIsRunningScript;
    }

    public synchronized void setIsRunningScript(boolean state) {
        ideIsRunningScript = state;
    }

    protected boolean doBeforeRun() {
        int action;
        if (checkDirtyPanes()) {
            if (prefs.getPrefMoreRunSave()) {
                action = WARNING_ACCEPTED;
            } else {
                action = askForSaveAll("Run");
                if (action < 0) {
                    return false;
                }
            }
            saveSession(action, false);
        }
        Settings.ActionLogs = prefs.getPrefMoreLogActions();
        Settings.DebugLogs = prefs.getPrefMoreLogDebug();
        Settings.InfoLogs = prefs.getPrefMoreLogInfo();
        Settings.Highlight = prefs.getPrefMoreHighlight();
        Settings.OcrTextSearch = prefs.getPrefMoreTextSearch();
        Settings.OcrTextRead = prefs.getPrefMoreTextOCR();
        return true;
    }

    protected boolean doBeforeQuit() {
        if (checkDirtyPanes()) {
            int action = askForSaveAll("Quit");
            if (action < 0) {
                return false;
            }
            return saveSession(action, true);
        }
        return saveSession(WARNING_DO_NOTHING, true);
    }

    private int askForSaveAll(String typ) {
        //TODO I18N
        String warn = "Some scripts are not saved yet!";
        String title = SikuliIDEI18N._I("dlgAskCloseTab");
        String[] options = new String[3];
        options[WARNING_DO_NOTHING] = typ + " immediately";
        options[WARNING_ACCEPTED] = "Save all and " + typ;
        options[WARNING_CANCEL] = SikuliIDEI18N._I("cancel");
        int ret = JOptionPane.showOptionDialog(this, warn, title, 0, JOptionPane.WARNING_MESSAGE, null, options,
                options[2]);
        if (ret == WARNING_CANCEL || ret == JOptionPane.CLOSED_OPTION) {
            return -1;
        }
        return ret;
    }

    public void doAbout() {
        //TODO full featured About
        String info = "You are running " + Settings.SikuliVersionIDE + "\n\nNeed help? -> start with Help Menu\n\n"
                + "*** Have fun ;-)\n\n" + "Tsung-Hsiang Chang aka vgod\n" + "Tom Yeh\n"
                + "Raimund Hocke aka RaiMan\n\n" + "\n\nBuild: " + Settings.SikuliVersionBuild;
        JOptionPane.showMessageDialog(this, info, "Sikuli About", JOptionPane.PLAIN_MESSAGE);
    }

    private static String[] collectOptions(String type, String[] args) {
        List<String> resArgs = new ArrayList<String>();
        if (args != null) {
            resArgs.addAll(Arrays.asList(args));
        }
        String msg = "-----------------------   You might set some options    -----------------------";
        msg += "\n\n";
        msg += "-r name       ---   Run script name: foo[.sikuli] or foo.skl (no IDE window)";
        msg += "\n";
        msg += "-u [file]        ---   Write user log messages to file (default: <WorkingFolder>/UserLog.txt )";
        msg += "\n";
        msg += "-f [file]         ---   Write Sikuli log messages to file (default: <WorkingFolder>/SikuliLog.txt)";
        msg += "\n";
        msg += "-d n             ---   Set a higher level n for Sikuli's debug messages (default: 0)";
        msg += "\n";
        msg += "-- more         All space delimited entries after -- go to sys.argv";
        msg += "\n                           \"<some text>\" makes one parameter (may contain intermediate blanks)";
        msg += "\n\n";
        msg += "-------------------------------------------------------------------------";
        msg += "\n";
        msg += "-d                Special debugging option in case of mysterious errors:";
        msg += "\n";
        msg += "                    Debug level is set to 3 and debug output goes to <WorkingFolder>/SikuliLog.txt";
        msg += "\n";
        msg += "                    Content might be used to ask questions or report bugs";
        msg += "\n";
        msg += "-------------------------------------------------------------------------";
        msg += "\n";
        msg += "                    Just click OK to start IDE with no options - defaults will be used";

        String ret = JOptionPane.showInputDialog(null, msg, "SikuliX: collect runtime options",
                JOptionPane.QUESTION_MESSAGE);

        if (ret == null) {
            return null;
        }
        log(3, "collectOptions: returned [" + ret + "]");
        if (!ret.isEmpty()) {
            System.setProperty("sikuli.SIKULI_COMMAND", ret);
            resArgs.addAll(Arrays.asList(ret.split(" +")));
        }
        return resArgs.toArray(new String[0]);
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="isInited --- RaiMan not used">
    public boolean isInited() {
        return _inited;
    }
    //</editor-fold>

    private JMenuItem createMenuItem(JMenuItem item, KeyStroke shortcut, ActionListener listener) {
        if (shortcut != null) {
            item.setAccelerator(shortcut);
        }
        item.addActionListener(listener);
        return item;
    }

    private JMenuItem createMenuItem(String name, KeyStroke shortcut, ActionListener listener) {
        JMenuItem item = new JMenuItem(name);
        return createMenuItem(item, shortcut, listener);
    }

    class MenuAction implements ActionListener {

        protected Method actMethod = null;
        protected String action;

        public MenuAction() {
        }

        public MenuAction(String item) throws NoSuchMethodException {
            Class[] paramsWithEvent = new Class[1];
            try {
                paramsWithEvent[0] = Class.forName("java.awt.event.ActionEvent");
                actMethod = this.getClass().getMethod(item, paramsWithEvent);
                action = item;
            } catch (ClassNotFoundException cnfe) {
                log(-1, "Can't find menu action: " + cnfe);
            }
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (actMethod != null) {
                try {
                    Debug.log(3, "MenuAction." + action);
                    Object[] params = new Object[1];
                    params[0] = e;
                    actMethod.invoke(this, params);
                } catch (Exception ex) {
                    log(-1, "Problem when trying to invoke menu action %s\nError: %s", action, ex.getMessage());
                }
            }
        }
    }

    private static JMenu recentMenu = null;
    private static Map<String, String> recentProjects = new HashMap<String, String>();
    private static java.util.List<String> recentProjectsMenu = new ArrayList<String>();
    private static int recentMax = 10;
    private static int recentMaxMax = recentMax + 10;

    //<editor-fold defaultstate="collapsed" desc="Init FileMenu">
    private void initFileMenu() throws NoSuchMethodException {
        JMenuItem jmi;
        int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        _fileMenu.setMnemonic(java.awt.event.KeyEvent.VK_F);

        if (showAbout) {
            _fileMenu.add(
                    createMenuItem("About SikuliX", KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, scMask),
                            new FileAction(FileAction.ABOUT)));
            _fileMenu.addSeparator();
        }

        _fileMenu.add(createMenuItem(_I("menuFileNew"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_N, scMask), new FileAction(FileAction.NEW)));

        jmi = _fileMenu.add(createMenuItem(_I("menuFileOpen"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, scMask), new FileAction(FileAction.OPEN)));
        jmi.setName("OPEN");

        recentMenu = new JMenu(_I("menuRecent"));

        if (Settings.experimental) {
            _fileMenu.add(recentMenu);
        }

        if (Settings.isMac() && !Settings.handlesMacBundles) {
            _fileMenu.add(createMenuItem("Open folder.sikuli ...", null,
                    //            KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, scMask),
                    new FileAction(FileAction.OPEN_FOLDER)));
        }

        jmi = _fileMenu.add(createMenuItem(_I("menuFileSave"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, scMask), new FileAction(FileAction.SAVE)));
        jmi.setName("SAVE");

        jmi = _fileMenu.add(createMenuItem(_I("menuFileSaveAs"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, InputEvent.SHIFT_MASK | scMask),
                new FileAction(FileAction.SAVE_AS)));
        jmi.setName("SAVE_AS");

        if (Settings.isMac() && !Settings.handlesMacBundles) {
            _fileMenu.add(createMenuItem(_I("Save as folder.sikuli ..."),
                    //            KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S,
                    //            InputEvent.SHIFT_MASK | scMask),
                    null, new FileAction(FileAction.SAVE_AS_FOLDER)));
        }

        //TODO    _fileMenu.add(createMenuItem(_I("menuFileSaveAll"),
        _fileMenu.add(createMenuItem("Save all",
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, InputEvent.CTRL_MASK | scMask),
                new FileAction(FileAction.SAVE_ALL)));

        _fileMenu.add(createMenuItem(_I("menuFileExport"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_E, InputEvent.SHIFT_MASK | scMask),
                new FileAction(FileAction.EXPORT)));

        jmi = _fileMenu.add(
                createMenuItem(_I("menuFileCloseTab"), KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, scMask),
                        new FileAction(FileAction.CLOSE_TAB)));
        jmi.setName("CLOSE_TAB");

        if (showPrefs) {
            _fileMenu.addSeparator();
            _fileMenu.add(createMenuItem(_I("menuFilePreferences"),
                    KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_P, scMask),
                    new FileAction(FileAction.PREFERENCES)));
        }

        if (showQuit) {
            _fileMenu.addSeparator();
            _fileMenu.add(createMenuItem(_I("menuFileQuit"), null, new FileAction(FileAction.QUIT)));
        }
    }

    public FileAction getFileAction(int tabIndex) {
        return new FileAction(tabIndex);
    }

    class FileAction extends MenuAction {

        static final String ABOUT = "doAbout";
        static final String NEW = "doNew";
        static final String INSERT = "doInsert";
        static final String OPEN = "doLoad";
        static final String RECENT = "doRecent";
        static final String OPEN_FOLDER = "doLoadFolder";
        static final String SAVE = "doSave";
        static final String SAVE_AS = "doSaveAs";
        static final String SAVE_AS_FOLDER = "doSaveAsFolder";
        static final String SAVE_ALL = "doSaveAll";
        static final String EXPORT = "doExport";
        static final String CLOSE_TAB = "doCloseTab";
        static final String PREFERENCES = "doPreferences";
        static final String QUIT = "doQuit";
        static final String ENTRY = "doRecent";
        private int targetTab = -1;

        public FileAction() {
            super();
        }

        public FileAction(int tabIndex) {
            super();
            targetTab = tabIndex;
        }

        public FileAction(String item) throws NoSuchMethodException {
            super(item);
        }

        public void doAbout(ActionEvent ae) {
            sikulixIDE.doAbout();
        }

        public void doQuit(ActionEvent ae) {
            log(lvl, "doQuit requested");
            if (!doBeforeQuit()) {
                return;
            }
            while (true) {
                EditorPane codePane = sikulixIDE.getCurrentCodePane();
                if (codePane == null) {
                    break;
                }
                if (!sikulixIDE.closeCurrentTab()) {
                    return;
                }
            }
            Sikulix.cleanUp(0);
            System.exit(0);
        }

        public void doPreferences(ActionEvent ae) {
            sikulixIDE.showPreferencesWindow();
        }

        public void doNew(ActionEvent ae) {
            EditorPane ep = doNew(ae, -1);
            ep.getSrcBundle();
            ep.initBeforeLoad(null);
        }

        public EditorPane doNew(ActionEvent ae, int tabIndex) {
            log(lvl, "doNew: create new tab at: %d", tabIndex);
            EditorPane codePane = new EditorPane(sikulixIDE);
            JScrollPane scrPane = new JScrollPane(codePane);
            lineNumberColumn = new EditorLineNumberView(codePane);
            scrPane.setRowHeaderView(lineNumberColumn);
            if (ae == null) {
                if (tabIndex < 0 || tabIndex >= tabPane.getTabCount()) {
                    tabPane.addTab(_I("tabUntitled"), scrPane);
                } else {
                    tabPane.addTab(_I("tabUntitled"), scrPane, tabIndex);
                }
                tabPane.setSelectedIndex(tabIndex < 0 ? tabPane.getTabCount() - 1 : tabIndex);
            } else {
                tabPane.addTab(_I("tabUntitled"), scrPane, 0);
                tabPane.setSelectedIndex(0);
            }
            //      codePane.getSrcBundle();
            codePane.requestFocus();
            return codePane;
        }

        public void doInsert(ActionEvent ae) {
            doLoad(null);
        }

        public void doLoad(ActionEvent ae) {
            boolean accessingAsFile = false;
            if (Settings.isMac()) {
                accessingAsFile = !ACCESSING_AS_FOLDER;
                ACCESSING_AS_FOLDER = false;
            }
            alreadyOpenedTab = tabPane.getSelectedIndex();
            String fname = tabPane.getLastClosed();
            try {
                EditorPane codePane = doNew(null, targetTab);
                if (ae != null || fname == null) {
                    codePane.isSourceBundleTemp();
                    fname = codePane.loadFile(accessingAsFile);
                } else {
                    codePane.loadFile(fname);
                    if (codePane.hasEditingFile()) {
                        setCurrentFileTabTitle(fname);
                    } else {
                        fname = null;
                    }
                }
                if (fname != null) {
                    sikulixIDE.setCurrentFileTabTitle(fname);
                } else {
                    if (ae != null) {
                        doCloseTab(null);
                    }
                    tabPane.setSelectedIndex(alreadyOpenedTab);
                }
                doRecentAdd(getCurrentCodePane());
            } catch (IOException eio) {
                log(-1, "Problem when trying to load %s\nError: %s", fname, eio.getMessage());
            }
        }

        private void doRecentAdd(EditorPane codePane) {
            String fPath = new File(codePane.getSrcBundle()).getAbsolutePath();
            if (Settings.experimental) {
                log(3, "doRecentAdd: %s", fPath);
                String fName = new File(fPath).getName();
                if (recentProjectsMenu.contains(fName)) {
                    recentProjectsMenu.remove(fName);
                } else {
                    recentProjects.put(fName, fPath);
                    if (recentProjectsMenu.size() == recentMaxMax) {
                        String fObsolete = recentProjectsMenu.remove(recentMax - 1);
                        recentProjects.remove(fObsolete);
                    }
                }
                recentProjectsMenu.add(0, fName);
                recentMenu.removeAll();
                for (String entry : recentProjectsMenu.subList(1, recentProjectsMenu.size())) {
                    if (isAlreadyOpen(recentProjects.get(entry)) > -1) {
                        continue;
                    }
                    try {
                        recentMenu.add(createMenuItem(entry, null, new FileAction(FileAction.ENTRY)));
                    } catch (NoSuchMethodException ex) {
                    }
                }
            }
        }

        public void doRecent(ActionEvent ae) {
            log(3, "doRecent: menuOpenRecent: %s", ae.getActionCommand());
        }

        public void doLoadFolder(ActionEvent ae) {
            Debug.log(3, "IDE: doLoadFolder requested");
            ACCESSING_AS_FOLDER = true;
            doLoad(ae);
        }

        public void doSave(ActionEvent ae) {
            String fname = null;
            try {
                EditorPane codePane = getCurrentCodePane();
                fname = codePane.saveFile();
                if (fname != null) {
                    fname = codePane.getSrcBundle();
                    setCurrentFileTabTitle(fname);
                    tabPane.setLastClosed(fname);
                }
            } catch (IOException eio) {
                log(-1, "Problem when trying to save %s\nError: %s", fname, eio.getMessage());
            }
        }

        public boolean doSaveIntern(int tabIndex) {
            int currentTab = tabPane.getSelectedIndex();
            tabPane.setSelectedIndex(tabIndex);
            boolean retval = true;
            EditorPane codePane = getPaneAtIndex(tabIndex);
            String fname = null;
            try {
                fname = codePane.saveFile();
                if (fname != null) {
                    setFileTabTitle(fname, tabIndex);
                } else {
                    retval = false;
                }
            } catch (IOException eio) {
                log(-1, "Problem when trying to save %s\nError: %s", fname, eio.getMessage());
                retval = false;
            }
            tabPane.setSelectedIndex(currentTab);
            return retval;
        }

        public void doSaveAs(ActionEvent ae) {
            boolean accessingAsFile = false;
            if (Settings.isMac()) {
                accessingAsFile = !ACCESSING_AS_FOLDER;
                ACCESSING_AS_FOLDER = false;
            }
            String fname = null;
            EditorPane codePane = getCurrentCodePane();
            String orgName = codePane.getCurrentShortFilename();
            log(lvl, "doSaveAs requested: %s", orgName);
            try {
                fname = codePane.saveAsFile(accessingAsFile);
                if (fname != null) {
                    setCurrentFileTabTitle(fname);
                } else {
                    log(-1, "doSaveAs: %s not completed", orgName);
                }
            } catch (IOException eio) {
                log(-1, "doSaveAs: %s Error: %s", orgName, eio.getMessage());
            }
        }

        public void doSaveAsFolder(ActionEvent ae) {
            log(lvl, "doSaveAsFolder requested");
            ACCESSING_AS_FOLDER = true;
            doSaveAs(ae);
        }

        public void doSaveAll(ActionEvent ae) {
            log(lvl, "doSaveAll requested");
            if (!checkDirtyPanes()) {
                return;
            }
            saveSession(IS_SAVE_ALL, false);
        }

        public void doExport(ActionEvent ae) {
            EditorPane codePane = getCurrentCodePane();
            String orgName = codePane.getCurrentShortFilename();
            log(lvl, "doExport requested: %s", orgName);
            String fname = null;
            try {
                fname = codePane.exportAsZip();
            } catch (Exception ex) {
                log(-1, "Problem when trying to save %s\nError: %s", fname, ex.getMessage());
            }
        }

        public void doCloseTab(ActionEvent ae) {
            if (ae == null) {
                tabPane.remove(tabPane.getSelectedIndex());
                return;
            }
            EditorPane codePane = getCurrentCodePane();
            String orgName = codePane.getCurrentShortFilename();
            log(lvl, "doCloseTab requested: %s", orgName);
            try {
                if (codePane.close()) {
                    tabPane.remove(tabPane.getSelectedIndex());
                }
            } catch (IOException e) {
                Debug.info("Can't close this tab: " + e.getStackTrace());
            }
            codePane = getCurrentCodePane();
            if (codePane != null) {
                codePane.requestFocus();
            } else if (ae != null) {
                (new FileAction()).doNew(null);
            }
        }
    }

    public void showPreferencesWindow() {
        PreferencesWin pwin = new PreferencesWin();
        pwin.setAlwaysOnTop(true);
        pwin.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        if (!Settings.isJava7()) {
            pwin.setLocation(getLocation());
        }
        pwin.setVisible(true);
    }

    public boolean closeCurrentTab() {
        EditorPane pane = getCurrentCodePane();
        (new FileAction()).doCloseTab(null);
        if (pane == getCurrentCodePane()) {
            return false;
        }
        return true;
    }

    protected boolean quit() {
        (new FileAction()).doQuit(null);
        if (getCurrentCodePane() == null) {
            return true;
        } else {
            return false;
        }
    }

    protected boolean checkDirtyPanes() {
        for (int i = 0; i < tabPane.getTabCount(); i++) {
            try {
                EditorPane codePane = getPaneAtIndex(i);
                if (codePane.isDirty()) {
                    //RaiMan not used: getRootPane().putClientProperty("Window.documentModified", true);
                    return true;
                }
            } catch (Exception e) {
                Debug.error("checkDirtyPanes: " + e.getMessage());
            }
        }
        //RaiMan not used: getRootPane().putClientProperty("Window.documentModified", false);
        return false;
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Init EditMenu">
    private void initEditMenu() throws NoSuchMethodException {
        int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        _editMenu.setMnemonic(java.awt.event.KeyEvent.VK_E);
        JMenuItem undoItem = _editMenu.add(_undoAction);
        undoItem.setAccelerator(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, scMask));
        JMenuItem redoItem = _editMenu.add(_redoAction);
        redoItem.setAccelerator(
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, scMask | InputEvent.SHIFT_MASK));
        _editMenu.addSeparator();

        _editMenu.add(createMenuItem(_I("menuEditCut"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, scMask), new EditAction(EditAction.CUT)));
        _editMenu.add(createMenuItem(_I("menuEditCopy"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_C, scMask), new EditAction(EditAction.COPY)));
        _editMenu.add(createMenuItem(_I("menuEditPaste"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_V, scMask), new EditAction(EditAction.PASTE)));
        _editMenu.add(createMenuItem(_I("menuEditSelectAll"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, scMask),
                new EditAction(EditAction.SELECT_ALL)));

        if (!Settings.isMac10()) {
            _editMenu.addSeparator();
            JMenu findMenu = new JMenu(_I("menuFind"));
            _findHelper = new FindAction();
            findMenu.setMnemonic(KeyEvent.VK_F);
            findMenu.add(createMenuItem(_I("menuFindFind"),
                    KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F, scMask), new FindAction(FindAction.FIND)));
            findMenu.add(createMenuItem(_I("menuFindFindNext"),
                    KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_G, scMask),
                    new FindAction(FindAction.FIND_NEXT)));
            findMenu.add(createMenuItem(_I("menuFindFindPrev"),
                    KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_G, scMask | InputEvent.SHIFT_MASK),
                    new FindAction(FindAction.FIND_PREV)));
            _editMenu.add(findMenu);
        }

        _editMenu.addSeparator();
        _editMenu.add(createMenuItem(_I("menuEditIndent"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_TAB, 0), new EditAction(EditAction.INDENT)));
        _editMenu.add(createMenuItem(_I("menuEditUnIndent"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_TAB, InputEvent.SHIFT_MASK),
                new EditAction(EditAction.UNINDENT)));
    }

    class EditAction extends MenuAction {

        static final String CUT = "doCut";
        static final String COPY = "doCopy";
        static final String PASTE = "doPaste";
        static final String SELECT_ALL = "doSelectAll";
        static final String INDENT = "doIndent";
        static final String UNINDENT = "doUnindent";

        public EditAction() {
            super();
        }

        public EditAction(String item) throws NoSuchMethodException {
            super(item);
        }

        private void performEditorAction(String action, ActionEvent ae) {
            EditorPane pane = getCurrentCodePane();
            pane.getActionMap().get(action).actionPerformed(ae);
        }

        public void doCut(ActionEvent ae) {
            //TODO delete current line if no selection
            performEditorAction(DefaultEditorKit.cutAction, ae);
        }

        public void doCopy(ActionEvent ae) {
            //TODO copy current line if no selection
            performEditorAction(DefaultEditorKit.copyAction, ae);
        }

        public void doPaste(ActionEvent ae) {
            performEditorAction(DefaultEditorKit.pasteAction, ae);
        }

        public void doSelectAll(ActionEvent ae) {
            performEditorAction(DefaultEditorKit.selectAllAction, ae);
        }

        public void doIndent(ActionEvent ae) {
            EditorPane pane = getCurrentCodePane();
            (new SikuliEditorKit.InsertTabAction()).actionPerformed(pane);
        }

        public void doUnindent(ActionEvent ae) {
            EditorPane pane = getCurrentCodePane();
            (new SikuliEditorKit.DeindentAction()).actionPerformed(pane);
        }
    }

    class OpenRecent extends MenuAction {

        public OpenRecent() {
            super();
        }

        public void openRecent(ActionEvent ae) {
            log(lvl, "openRecent: %s", ae.getActionCommand());
        }
    }

    class FindAction extends MenuAction {

        static final String FIND = "doFind";
        static final String FIND_NEXT = "doFindNext";
        static final String FIND_PREV = "doFindPrev";

        public FindAction() {
            super();
        }

        public FindAction(String item) throws NoSuchMethodException {
            super(item);
        }

        public void doFind(ActionEvent ae) {
            _searchField.selectAll();
            _searchField.requestFocus();
        }

        public void doFindNext(ActionEvent ae) {
            findNext(_searchField.getText());
        }

        public void doFindPrev(ActionEvent ae) {
            findPrev(_searchField.getText());
        }

        private boolean _find(String str, int begin, boolean forward) {
            if (str == "!") {
                return false;
            }
            EditorPane codePane = getCurrentCodePane();
            int pos = codePane.search(str, begin, forward);
            Debug.log(7, "find \"" + str + "\" at " + begin + ", found: " + pos);
            if (pos < 0) {
                return false;
            }
            return true;
        }

        public boolean findStr(String str) {
            if (getCurrentCodePane() != null) {
                return _find(str, getCurrentCodePane().getCaretPosition(), true);
            }
            return false;
        }

        public boolean findPrev(String str) {
            if (getCurrentCodePane() != null) {
                return _find(str, getCurrentCodePane().getCaretPosition(), false);
            }
            return false;
        }

        public boolean findNext(String str) {
            if (getCurrentCodePane() != null) {
                return _find(str, getCurrentCodePane().getCaretPosition() + str.length(), true);
            }
            return false;
        }

        public void setFailed(boolean failed) {
            Debug.log(7, "search failed: " + failed);
            _searchField.setBackground(Color.white);
            if (failed) {
                _searchField.setForeground(COLOR_SEARCH_FAILED);
            } else {
                _searchField.setForeground(COLOR_SEARCH_NORMAL);
            }
        }
    }

    class UndoAction extends AbstractAction {

        public UndoAction() {
            super(_I("menuEditUndo"));
            setEnabled(false);
        }

        public void updateUndoState() {
            if (getCurrentCodePane() != null && getCurrentCodePane().getUndoManager().canUndo()) {
                setEnabled(true);
            } else {
                setEnabled(false);
            }
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            EditorUndoManager undo = getCurrentCodePane().getUndoManager();
            try {
                undo.undo();
            } catch (CannotUndoException ex) {
            }
            updateUndoState();
            _redoAction.updateRedoState();
        }
    }

    class RedoAction extends AbstractAction {

        public RedoAction() {
            super(_I("menuEditRedo"));
            setEnabled(false);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            EditorUndoManager undo = getCurrentCodePane().getUndoManager();
            try {
                undo.redo();
            } catch (CannotRedoException ex) {
            }
            updateRedoState();
            _undoAction.updateUndoState();
        }

        protected void updateRedoState() {
            if (getCurrentCodePane() != null && getCurrentCodePane().getUndoManager().canRedo()) {
                setEnabled(true);
            } else {
                setEnabled(false);
            }
        }
    }

    public void updateUndoRedoStates() {
        _undoAction.updateUndoState();
        _redoAction.updateRedoState();
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Init Run Menu">
    private void initRunMenu() throws NoSuchMethodException {
        JMenuItem item;
        int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        _runMenu.setMnemonic(java.awt.event.KeyEvent.VK_R);
        item = _runMenu.add(createMenuItem(_I("menuRunRun"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_R, scMask), new RunAction(RunAction.RUN)));
        item.setName("RUN");
        item = _runMenu.add(createMenuItem(_I("menuRunRunAndShowActions"),
                KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_R, InputEvent.ALT_MASK | scMask),
                new RunAction(RunAction.RUN_SHOW_ACTIONS)));
        item.setName("RUN_SLOWLY");

        PreferencesUser pref = PreferencesUser.getInstance();
        item = createMenuItem(_I("menuRunStop"),
                KeyStroke.getKeyStroke(pref.getStopHotkey(), pref.getStopHotkeyModifiers()),
                new RunAction(RunAction.RUN_SHOW_ACTIONS));
        item.setEnabled(false);
        _runMenu.add(item);
    }

    class RunAction extends MenuAction {

        static final String RUN = "run";
        static final String RUN_SHOW_ACTIONS = "runShowActions";

        public RunAction() {
            super();
        }

        public RunAction(String item) throws NoSuchMethodException {
            super(item);
        }

        public void run(ActionEvent ae) {
            doRun(_btnRun);
        }

        public void runShowActions(ActionEvent ae) {
            doRun(_btnRunViz);
        }

        private void doRun(ButtonRun btn) {
            btn.runCurrentScript();
        }
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Init View Menu">
    private void initViewMenu() throws NoSuchMethodException {
        int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        _viewMenu.setMnemonic(java.awt.event.KeyEvent.VK_V);

        if (prefs.getPrefMoreCommandBar()) {
            chkShowCmdList = new JCheckBoxMenuItem(_I("menuViewCommandList"), true);
            _viewMenu.add(
                    createMenuItem(chkShowCmdList, KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_L, scMask),
                            new ViewAction(ViewAction.CMD_LIST)));
        }

        chkShowThumbs = new JCheckBoxMenuItem(_I("menuViewShowThumbs"), false);
        _viewMenu.add(createMenuItem(chkShowThumbs, KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_T, scMask),
                new ViewAction(ViewAction.SHOW_THUMBS)));

        //TODO Message Area clear
        //TODO Message Area LineBreak
    }

    class ViewAction extends MenuAction {

        static final String UNIT_TEST = "toggleUnitTest";
        static final String CMD_LIST = "toggleCmdList";
        static final String SHOW_THUMBS = "toggleShowThumbs";

        public ViewAction() {
            super();
        }

        public ViewAction(String item) throws NoSuchMethodException {
            super(item);
        }

        public void toggleCmdList(ActionEvent ae) {
            _cmdList.setCollapsed(!_cmdList.isCollapsed());
        }

        public void toggleShowThumbs(ActionEvent ae) {
            getCurrentCodePane().showThumbs = chkShowThumbs.getState();
            if (!getCurrentCodePane().reparse()) {
                chkShowThumbs.setState(!chkShowThumbs.getState());
                getCurrentCodePane().showThumbs = chkShowThumbs.getState();
            }
        }

        public void toggleUnitTest(ActionEvent ae) {
            if (_chkShowUnitTest.getState()) {
                _sidePane.setCollapsed(false);
            } else {
                _sidePane.setCollapsed(true);
            }
        }
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Init ToolMenu">
    private void initToolMenu() throws NoSuchMethodException {
        int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        _toolMenu.setMnemonic(java.awt.event.KeyEvent.VK_T);

        _toolMenu.add(createMenuItem(_I("menuToolExtensions"), null, new ToolAction(ToolAction.EXTENSIONS)));
    }

    class ToolAction extends MenuAction {

        static final String EXTENSIONS = "extensions";

        public ToolAction() {
            super();
        }

        public ToolAction(String item) throws NoSuchMethodException {
            super(item);
        }

        public void extensions(ActionEvent ae) {
            showExtensionsFrame();
        }
    }

    public void showExtensionsFrame() {
        //    String warn = "You might proceed, if you\n"
        //            + "- have some programming skills\n"
        //            + "- read the docs about extensions\n"
        //            + "- know what you are doing\n\n"
        //            + "Otherwise you should press Cancel!";
        String warn = "Not available yet - click what you like ;-)";
        String title = "Need your attention!";
        String[] options = new String[3];
        options[WARNING_DO_NOTHING] = "OK";
        options[WARNING_ACCEPTED] = "Be quiet!";
        options[WARNING_CANCEL] = "Cancel";
        int ret = JOptionPane.showOptionDialog(this, warn, title, 0, JOptionPane.WARNING_MESSAGE, null, options,
                options[2]);
        if (ret == WARNING_CANCEL || ret == JOptionPane.CLOSED_OPTION) {
            return;
        }
        if (ret == WARNING_ACCEPTED) {
            //TODO set prefs to be quiet on extensions warning
        }
        ;
        ExtensionManagerFrame extmg = ExtensionManagerFrame.getInstance();
        if (extmg != null) {
            extmg.setVisible(true);
        }
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Init Help Menu">
    private void initHelpMenu() throws NoSuchMethodException {
        int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        _helpMenu.setMnemonic(java.awt.event.KeyEvent.VK_H);

        _helpMenu.add(createMenuItem(_I("menuHelpQuickStart"), null, new HelpAction(HelpAction.QUICK_START)));
        _helpMenu.addSeparator();

        _helpMenu.add(createMenuItem(_I("menuHelpGuide"), null, new HelpAction(HelpAction.OPEN_DOC)));
        _helpMenu.add(createMenuItem(_I("menuHelpDocumentations"), null, new HelpAction(HelpAction.OPEN_GUIDE)));
        _helpMenu.add(createMenuItem(_I("menuHelpFAQ"), null, new HelpAction(HelpAction.OPEN_FAQ)));
        _helpMenu.add(createMenuItem(_I("menuHelpAsk"), null, new HelpAction(HelpAction.OPEN_ASK)));
        _helpMenu.add(createMenuItem(_I("menuHelpBugReport"), null, new HelpAction(HelpAction.OPEN_BUG_REPORT)));

        //    _helpMenu.add(createMenuItem(_I("menuHelpTranslation"),
        //            null, new HelpAction(HelpAction.OPEN_TRANSLATION)));
        _helpMenu.addSeparator();
        _helpMenu.add(createMenuItem(_I("menuHelpHomepage"), null, new HelpAction(HelpAction.OPEN_HOMEPAGE)));

        _helpMenu.addSeparator();
        _helpMenu.add(createMenuItem(_I("menuHelpCheckUpdate"), null, new HelpAction(HelpAction.CHECK_UPDATE)));
    }

    class HelpAction extends MenuAction {

        static final String CHECK_UPDATE = "doCheckUpdate";
        static final String QUICK_START = "openQuickStart";
        static final String OPEN_DOC = "openDoc";
        static final String OPEN_GUIDE = "openTutor";
        static final String OPEN_FAQ = "openFAQ";
        static final String OPEN_ASK = "openAsk";
        static final String OPEN_BUG_REPORT = "openBugReport";
        static final String OPEN_TRANSLATION = "openTranslation";
        static final String OPEN_HOMEPAGE = "openHomepage";

        public HelpAction() {
            super();
        }

        public HelpAction(String item) throws NoSuchMethodException {
            super(item);
        }

        public void openQuickStart(ActionEvent ae) {
            FileManager.openURL("http://www.sikulix.com/quickstart.html");
        }

        public void openDoc(ActionEvent ae) {
            FileManager.openURL("http://sikulix-2014.readthedocs.org/en/latest/index.html");
        }

        public void openTutor(ActionEvent ae) {
            FileManager.openURL("http://www.sikuli.org/videos.html");
        }

        public void openFAQ(ActionEvent ae) {
            FileManager.openURL("https://answers.launchpad.net/sikuli/+faqs");
        }

        public void openAsk(ActionEvent ae) {
            String title = "SikuliX - Ask a question";
            String msg = "If you want to ask a question about SikuliX\n%s\n" + "\nplease do the following:"
                    + "\n- after having clicked yes" + "\n   the page on Launchpad should open in your browser."
                    + "\n- You should first check using Launchpad's search funktion,"
                    + "\n   wether similar questions have already been asked."
                    + "\n- If you decide to ask a new question," + "\n   try to enter a short but speaking title"
                    + "\n- In a new questions's text field first paste using ctrl/cmd-v"
                    + "\n   which should enter the SikuliX version/system/java info"
                    + "\n   that was internally stored in the clipboard before"
                    + "\n\nIf you do not want to ask a question now: click No";
            askBugOrAnswer(msg, title, "https://answers.launchpad.net/sikuli");
        }

        public void openBugReport(ActionEvent ae) {
            String title = "SikuliX - Report a bug";
            String msg = "If you want to report a bug for SikuliX\n%s\n" + "\nplease do the following:"
                    + "\n- after having clicked yes" + "\n   the page on Launchpad should open in your browser"
                    + "\n- fill in a short but speaking bug title and create the bug"
                    + "\n- in the bug's text field first paste using ctrl/cmd-v"
                    + "\n   which should enter the SikuliX version/system/java info"
                    + "\n   that was internally stored in the clipboard before"
                    + "\n\nIf you do not want to report a bug now: click No";
            askBugOrAnswer(msg, title, "https://bugs.launchpad.net/sikuli/+filebug");
        }

        private void askBugOrAnswer(String msg, String title, String url) {
            String si = Settings.getSystemInfo();
            System.out.println(si);
            msg = String.format(msg, si);
            if (Sikulix.popAsk(msg, title)) {
                Clipboard clb = Toolkit.getDefaultToolkit().getSystemClipboard();
                StringSelection sic = new StringSelection(si.toString());
                clb.setContents(sic, sic);
                FileManager.openURL(url);
            }
        }

        public void openTranslation(ActionEvent ae) {
            FileManager.openURL("https://translations.launchpad.net/sikuli/sikuli-x/+translations");
        }

        public void openHomepage(ActionEvent ae) {
            FileManager.openURL("http://sikulix.com");
        }

        public void doCheckUpdate(ActionEvent ae) {
            if (!checkUpdate(false)) {
                JOptionPane.showMessageDialog(null, _I("msgNoUpdate"), SikuliIDESettings.SikuliVersion,
                        JOptionPane.INFORMATION_MESSAGE);
            }
        }

        public boolean checkUpdate(boolean isAutoCheck) {
            JFrame f = null;
            String ver = "";
            String details;
            AutoUpdater au = new AutoUpdater();
            if (!isAutoCheck) {
                //TODO replace this hack: wait update check
                f = au.showUpdateFrame("Checking for new version ... please wait!",
                        "Checking for new version ... please wait! Checking for new version ... please wait!", -1);
            }
            PreferencesUser pref = PreferencesUser.getInstance();
            Debug.log(3, "being asked to check update");
            int whatUpdate = au.checkUpdate();
            if (f != null) {
                f.dispose();
            }
            if (whatUpdate >= AutoUpdater.SOMEBETA) {
                //TODO add Prefs wantBeta check
                whatUpdate -= AutoUpdater.SOMEBETA;
            }
            if (whatUpdate > 0) {
                if (whatUpdate == AutoUpdater.BETA) {
                    ver = au.getBeta();
                    details = au.getBetaDetails();
                } else {
                    ver = au.getVersion();
                    details = au.getDetails();
                }
                if (isAutoCheck && pref.getLastSeenUpdate().equals(ver)) {
                    return false;
                }
                au.showUpdateFrame(_I("dlgUpdateAvailable", ver), details, whatUpdate);
                PreferencesUser.getInstance().setLastSeenUpdate(ver);
                return true;
            }
            return false;
        }
    }

    private void initMenuBars(JFrame frame) {
        try {
            initFileMenu();
            initEditMenu();
            initRunMenu();
            initViewMenu();
            initToolMenu();
            initHelpMenu();
        } catch (NoSuchMethodException e) {
            log(-1, "Problem when initializing menues\nError: %s", e.getMessage());
        }

        _menuBar.add(_fileMenu);
        _menuBar.add(_editMenu);
        _menuBar.add(_runMenu);
        _menuBar.add(_viewMenu);
        _menuBar.add(_toolMenu);
        _menuBar.add(_helpMenu);
        frame.setJMenuBar(_menuBar);
    }

    //<editor-fold defaultstate="collapsed" desc="Init LeftBar Commands">
    private String[] getCommandCategories() {
        String[] CommandCategories = { _I("cmdListFind"), _I("cmdListMouse"), _I("cmdListKeyboard"),
                _I("cmdListObserver") };
        return CommandCategories;
    }

    private String[][] getCommandsOnToolbar() {
        String[][] CommandsOnToolbar = { { "find" }, { "PATTERN" }, { _I("cmdFind") }, { "findAll" }, { "PATTERN" },
                { _I("cmdFindAll") }, { "wait" }, { "PATTERN", "[timeout]" }, { _I("cmdWait") }, { "waitVanish" },
                { "PATTERN", "[timeout]" }, { _I("cmdWaitVanish") }, { "exists" }, { "PATTERN", "[timeout]" },
                { _I("cmdExists") }, { "----" }, {}, {}, { "click" }, { "PATTERN", "[modifiers]" },
                { _I("cmdClick") }, { "doubleClick" }, { "PATTERN", "[modifiers]" }, { _I("cmdDoubleClick") },
                { "rightClick" }, { "PATTERN", "[modifiers]" }, { _I("cmdRightClick") }, { "hover" }, { "PATTERN" },
                { _I("cmdHover") }, { "dragDrop" }, { "PATTERN", "PATTERN", "[modifiers]" }, { _I("cmdDragDrop") },
                /* RaiMan not used
                 * {"drag"}, {"PATTERN"},
                 * {"dropAt"}, {"PATTERN", "[delay]"},
                 * RaiMan not used */
                { "----" }, {}, {}, { "type" }, { "_text", "[modifiers]" }, { _I("cmdType") }, { "type" },
                { "PATTERN", "_text", "[modifiers]" }, { _I("cmdType2") }, { "paste" }, { "_text", "[modifiers]" },
                { _I("cmdPaste") }, { "paste" }, { "PATTERN", "_text", "[modifiers]" }, { _I("cmdPaste2") },
                { "----" }, {}, {}, { "onAppear" }, { "PATTERN", "_hnd" }, { _I("cmdOnAppear") }, { "onVanish" },
                { "PATTERN", "_hnd" }, { _I("cmdOnVanish") }, { "onChange" }, { "_hnd" }, { _I("cmdOnChange") },
                { "observe" }, { "[time]", "[background]" }, { _I("cmdObserve") }, };
        return CommandsOnToolbar;
    }

    private JComponent createCommandPane() {
        JXTaskPaneContainer con = new JXTaskPaneContainer();

        PreferencesUser pref = PreferencesUser.getInstance();
        JCheckBox chkAutoCapture = new JCheckBox(_I("cmdListAutoCapture"), pref.getAutoCaptureForCmdButtons());
        chkAutoCapture.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(javax.swing.event.ChangeEvent e) {
                boolean flag = ((JCheckBox) e.getSource()).isSelected();
                PreferencesUser pref = PreferencesUser.getInstance();
                pref.setAutoCaptureForCmdButtons(flag);
            }
        });
        JXTaskPane setPane = new JXTaskPane();
        setPane.setTitle(_I("cmdListSettings"));
        setPane.add(chkAutoCapture);
        setPane.setCollapsed(true);
        con.add(setPane);
        int cat = 0;
        JXTaskPane taskPane = new JXTaskPane();
        taskPane.setTitle(getCommandCategories()[cat++]);
        con.add(taskPane);
        String[][] CommandsOnToolbar = getCommandsOnToolbar();
        boolean collapsed;
        for (int i = 0; i < CommandsOnToolbar.length; i++) {
            String cmd = CommandsOnToolbar[i++][0];
            String[] params = CommandsOnToolbar[i++];
            String[] desc = CommandsOnToolbar[i];
            //TODO: more elegeant way, to handle special cases
            if (cmd.equals("----")) {
                if (cat == 2) {
                    collapsed = true;
                } else {
                    collapsed = false;
                }
                if (cat == 3) {
                    if (prefs.getUserType() == PreferencesUser.NEWBEE) {
                        break;
                    } else {
                        collapsed = true;
                    }
                }
                taskPane = new JXTaskPane();
                taskPane.setTitle(getCommandCategories()[cat++]);
                con.add(taskPane);
                taskPane.setCollapsed(collapsed);
            } else {
                taskPane.add(new ButtonGenCommand(cmd, desc[0], params));
            }
        }
        Dimension conDim = con.getSize();
        con.setPreferredSize(new Dimension(250, 1000));
        _cmdList = new JXCollapsiblePane(JXCollapsiblePane.Direction.LEFT);
        _cmdList.setMinimumSize(new Dimension(0, 0));
        _cmdList.add(new JScrollPane(con));
        _cmdList.setCollapsed(false);
        return _cmdList;
    }

    //<editor-fold defaultstate="collapsed" desc="RaiMan obsolete">
    private JToolBar initCmdToolbar() {
        JToolBar toolbar = new JToolBar(JToolBar.VERTICAL);
        toolbar.add(createCommandPane());
        return toolbar;
    }
    //</editor-fold>
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Init ToolBar Buttons">
    private JToolBar initToolbar() {
        if (ENABLE_UNIFIED_TOOLBAR) {
            MacUtils.makeWindowLeopardStyle(this.getRootPane());
        }

        JToolBar toolbar = new JToolBar();
        JButton btnInsertImage = new ButtonInsertImage();
        _btnCapture = new ButtonCapture();
        JButton btnSubregion = new ButtonSubregion();
        toolbar.add(_btnCapture);
        toolbar.add(btnInsertImage);
        toolbar.add(btnSubregion);
        toolbar.add(Box.createHorizontalGlue());
        _btnRun = new ButtonRun();
        toolbar.add(_btnRun);
        _btnRunViz = new ButtonRunViz();
        toolbar.add(_btnRunViz);
        toolbar.add(Box.createHorizontalGlue());

        //TODO get it working for OSX 10.10
        if (!Settings.isMac10()) {
            toolbar.add(createSearchField());
        }

        toolbar.add(Box.createRigidArea(new Dimension(7, 0)));
        toolbar.setFloatable(false);
        //toolbar.setMargin(new Insets(0, 0, 0, 5));
        return toolbar;
    }

    class ButtonInsertImage extends ButtonOnToolbar implements ActionListener {

        public ButtonInsertImage() {
            super();
            URL imageURL = SikuliIDE.class.getResource("/icons/insert-image-icon.png");
            setIcon(new ImageIcon(imageURL));
            setText(SikuliIDE._I("btnInsertImageLabel"));
            //setMaximumSize(new Dimension(26,26));
            setToolTipText(SikuliIDE._I("btnInsertImageHint"));
            addActionListener(this);
        }

        @Override
        public void actionPerformed(ActionEvent ae) {
            EditorPane codePane = getCurrentCodePane();
            File file = new SikuliIDEFileChooser(sikulixIDE).loadImage();
            if (file == null) {
                return;
            }
            String path = FileManager.slashify(file.getAbsolutePath(), false);
            Debug.info("load image: " + path);
            EditorPatternButton icon;
            String img = codePane.copyFileToBundle(path).getAbsolutePath();
            if (prefs.getDefaultThumbHeight() > 0) {
                icon = new EditorPatternButton(codePane, img);
                codePane.insertComponent(icon);
            } else {
                codePane.insertString("\"" + (new File(img)).getName() + "\"");
            }
        }
    }

    class ButtonSubregion extends ButtonOnToolbar implements ActionListener, EventObserver {

        public ButtonSubregion() {
            super();
            URL imageURL = SikuliIDE.class.getResource("/icons/region-icon.png");
            setIcon(new ImageIcon(imageURL));
            setText(SikuliIDE._I("btnRegionLabel"));
            //setMaximumSize(new Dimension(26,26));
            setToolTipText(SikuliIDE._I("btnRegionHint"));
            addActionListener(this);
        }

        @Override
        public void actionPerformed(ActionEvent ae) {
            sikulixIDE.setVisible(false);
            OverlayCapturePrompt prompt = new OverlayCapturePrompt(null, this);
            prompt.prompt(SikuliIDE._I("msgCapturePrompt"), 500);
        }

        @Override
        public void update(EventSubject s) {
            if (s instanceof OverlayCapturePrompt) {
                complete((OverlayCapturePrompt) s);
            }
        }

        public void complete(OverlayCapturePrompt cp) {
            int x, y, w, h;
            EditorPane codePane = getCurrentCodePane();
            ScreenImage r = cp.getSelection();
            cp.close();
            if (r != null) {
                Rectangle roi = r.getROI();
                x = (int) roi.getX();
                y = (int) roi.getY();
                w = (int) roi.getWidth();
                h = (int) roi.getHeight();
                sikulixIDE.setVisible(false);
                if (codePane.showThumbs) {
                    if (prefs.getPrefMoreImageThumbs()) {
                        codePane.insertComponent(new EditorRegionButton(codePane, x, y, w, h));
                    } else {
                        codePane.insertComponent(new EditorRegionLabel(codePane,
                                new EditorRegionButton(codePane, x, y, w, h).toString()));
                    }
                } else {
                    codePane.insertString(codePane.getRegionString(x, y, w, h));
                }
            }
            sikulixIDE.setVisible(true);
            codePane.requestFocus();
        }
    }

    class ButtonRun extends ButtonOnToolbar implements ActionListener {

        private Thread _runningThread = null;

        public ButtonRun() {
            super();

            URL imageURL = SikuliIDE.class.getResource("/icons/run_big_green.png");
            setIcon(new ImageIcon(imageURL));
            initTooltip();
            addActionListener(this);
            setText(_I("btnRunLabel"));
            //setMaximumSize(new Dimension(45,45));
        }

        @Override
        public void actionPerformed(ActionEvent ae) {
            runCurrentScript();
        }

        public void runCurrentScript() {
            SikuliIDE.getStatusbar().setMessage("... PLEASE WAIT ... checking IDE state before running script");
            if (ideIsRunningScript || sikulixIDE.getCurrentCodePane().getDocument().getLength() == 0
                    || !sikulixIDE.doBeforeRun()) {
                return;
            }
            SikuliIDE.getStatusbar().resetMessage();
            sikulixIDE.setVisible(false);
            Sikulix.pause(0.1f);
            sikulixIDE.setIsRunningScript(true);
            final IScriptRunner[] srunners = new IScriptRunner[] { null };
            _runningThread = new Thread() {
                @Override
                public void run() {
                    EditorPane codePane = getCurrentCodePane();
                    String cType = codePane.getContentType();
                    File scriptFile = null;
                    try {
                        if (codePane.isDirty()) {
                            scriptFile = FileManager.createTempFile(ScriptRunner.typeEndings.get(cType));
                            if (scriptFile == null) {
                                log(-1, "runCurrentScript: temp file for running not available");
                                return;
                            }
                            BufferedWriter bw = new BufferedWriter(
                                    new OutputStreamWriter(new FileOutputStream(scriptFile), "UTF8"));
                            codePane.write(bw);
                        } else {
                            scriptFile = codePane.getCurrentFile();
                        }
                        _console.clear();
                        resetErrorMark();
                        String parent = null;
                        File path = new File(getCurrentBundlePath());
                        if (path != null && !codePane.isSourceBundleTemp()) {
                            parent = path.getParent();
                        }
                        IScriptRunner srunner = ScriptRunner.getRunner(null, cType);
                        if (srunner == null) {
                            log(-1, "runCurrentScript: Could not load a script runner for: %s", cType);
                            return;
                        }
                        addScriptCode(srunner);
                        srunners[0] = srunner;
                        try {
                            ImagePath.reset(path.getAbsolutePath());
                            String tabtitle = tabPane.getTitleAt(tabPane.getSelectedIndex());
                            if (tabtitle.startsWith("*")) {
                                tabtitle = tabtitle.substring(1);
                            }
                            int ret = srunner.runScript(scriptFile, path, Settings.getArgs(),
                                    new String[] { parent, tabtitle });
                            addErrorMark(ret);
                            srunner.close();
                            srunners[0] = null;
                        } catch (Exception e) {
                            srunner.close();
                            srunners[0] = null;
                            throw e;
                        }
                    } catch (Exception e) {
                        e.getMessage();
                    } finally {
                        sikulixIDE.setIsRunningScript(false);
                        sikulixIDE.setVisible(true);
                        _runningThread = null;
                        Sikulix.cleanUp(0);
                    }
                }
            };
            _runningThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    log(-1, "Jython UncaughtExceptionHandler: trying to cleanup.\n%s", e.getMessage());
                    if (srunners[0] != null) {
                        srunners[0].close();
                        srunners[0] = null;
                    }
                    sikulixIDE.setIsRunningScript(false);
                    sikulixIDE.setVisible(true);
                    _runningThread = null;
                    Sikulix.cleanUp(0);
                }
            });
            _runningThread.start();
        }

        protected void addScriptCode(IScriptRunner srunner) {
            srunner.execBefore(null);
            srunner.execBefore(new String[] { "Settings.setShowActions(Settings.FALSE)" });
        }

        public void stopRunning() {
            if (_runningThread != null) {
                _runningThread.interrupt();
                _runningThread.stop();
            }
        }

        private void initTooltip() {
            PreferencesUser pref = PreferencesUser.getInstance();
            String strHotkey = Key.convertKeyToText(pref.getStopHotkey(), pref.getStopHotkeyModifiers());
            String stopHint = _I("btnRunStopHint", strHotkey);
            setToolTipText(_I("btnRun", stopHint));
        }

        public void addErrorMark(int line) {
            if (line < 0) {
                line *= -1;
            } else {
                return;
            }
            JScrollPane scrPane = (JScrollPane) tabPane.getSelectedComponent();
            EditorLineNumberView lnview = (EditorLineNumberView) (scrPane.getRowHeader().getView());
            lnview.addErrorMark(line);
            EditorPane codePane = SikuliIDE.this.getCurrentCodePane();
            codePane.jumpTo(line);
            codePane.requestFocus();
        }

        public void resetErrorMark() {
            JScrollPane scrPane = (JScrollPane) tabPane.getSelectedComponent();
            EditorLineNumberView lnview = (EditorLineNumberView) (scrPane.getRowHeader().getView());
            lnview.resetErrorMark();
        }
    }

    class ButtonRunViz extends ButtonRun {

        public ButtonRunViz() {
            super();
            URL imageURL = SikuliIDE.class.getResource("/icons/run_big_yl.png");
            setIcon(new ImageIcon(imageURL));
            setToolTipText(_I("menuRunRunAndShowActions"));
            setText(_I("btnRunSlowMotionLabel"));
        }

        @Override
        protected void addScriptCode(IScriptRunner srunner) {
            srunner.execBefore(null);
            srunner.execBefore(new String[] { "Settings.setShowActions(Settings.TRUE)" });
        }
    }

    protected String getCurrentBundlePath() {
        EditorPane pane = getCurrentCodePane();
        return pane.getBundlePath();
    }

    private JComponent createSearchField() {
        _searchField = new JXSearchField("Find");
        _searchField.setUseNativeSearchFieldIfPossible(true);
        //_searchField.setLayoutStyle(JXSearchField.LayoutStyle.MAC);
        _searchField.setMinimumSize(new Dimension(220, 30));
        _searchField.setPreferredSize(new Dimension(220, 30));
        _searchField.setMaximumSize(new Dimension(380, 30));
        _searchField.setMargin(new Insets(0, 3, 0, 3));
        _searchField
                .setToolTipText("Search is case sensitive - " + "start with ! to make search not case sensitive");

        _searchField.setCancelAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                getCurrentCodePane().requestFocus();
                _findHelper.setFailed(false);
            }
        });
        _searchField.setFindAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                //FIXME: On Linux the found selection disappears somehow
                if (!Settings.isLinux()) //HACK
                {
                    _searchField.selectAll();
                }
                boolean ret = _findHelper.findNext(_searchField.getText());
                _findHelper.setFailed(!ret);
            }
        });
        _searchField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(java.awt.event.KeyEvent ke) {
                boolean ret;
                if (ke.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) {
                    //FIXME: On Linux the found selection disappears somehow
                    if (!Settings.isLinux()) //HACK
                    {
                        _searchField.selectAll();
                    }
                    ret = _findHelper.findNext(_searchField.getText());
                } else {
                    ret = _findHelper.findStr(_searchField.getText());
                }
                _findHelper.setFailed(!ret);
            }
        });
        return _searchField;
    }
    //</editor-fold>

    private void initTabPane() {
        tabPane = new CloseableTabbedPane();
        tabPane.setUI(new AquaCloseableTabbedPaneUI());
        tabPane.addCloseableTabbedPaneListener(new CloseableTabbedPaneListener() {
            @Override
            public boolean closeTab(int i) {
                EditorPane codePane;
                try {
                    codePane = getPaneAtIndex(i);
                    tabPane.setLastClosed(codePane.getSrcBundle());
                    Debug.log(4, "close tab " + i + " n:" + tabPane.getComponentCount());
                    boolean ret = codePane.close();
                    Debug.log(4, "after close tab n:" + tabPane.getComponentCount());
                    if (ret && tabPane.getTabCount() < 2) {
                        (new FileAction()).doNew(null);
                    }
                    return ret;
                } catch (Exception e) {
                    log(-1, "Problem closing tab %d\nError: %s", i, e.getMessage());
                    return false;
                }
            }
        });

        tabPane.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(javax.swing.event.ChangeEvent e) {
                EditorPane codePane = null;
                JTabbedPane tab = (JTabbedPane) e.getSource();
                int i = tab.getSelectedIndex();
                if (i >= 0) {
                    codePane = getPaneAtIndex(i);
                    String fname = codePane.getCurrentSrcDir();
                    if (fname == null) {
                        SikuliIDE.this.setTitle(tab.getTitleAt(i));
                    } else {
                        ImagePath.setBundlePath(fname);
                        SikuliIDE.this.setTitle(fname);
                    }
                    SikuliIDE.this.chkShowThumbs.setState(SikuliIDE.this.getCurrentCodePane().showThumbs);
                }
                updateUndoRedoStates();
                if (codePane != null) {
                    SikuliIDE.getStatusbar()
                            .setCurrentContentType(SikuliIDE.this.getCurrentCodePane().getSikuliContentType());
                }
            }
        });

    }

    private void initMsgPane(boolean atBottom) {
        msgPane = new JTabbedPane();
        _console = new EditorConsolePane();
        msgPane.addTab(_I("paneMessage"), null, _console, "DoubleClick to hide/unhide");
        if (Settings.isWindows() || Settings.isLinux()) {
            msgPane.setBorder(BorderFactory.createEmptyBorder(5, 8, 5, 8));
        }
        msgPane.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent me) {
                if (me.getClickCount() < 2) {
                    return;
                }
                if (msgPaneCollapsed) {
                    _mainSplitPane.setDividerLocation(_mainSplitPane.getLastDividerLocation());
                    msgPaneCollapsed = false;
                } else {
                    int pos = _mainSplitPane.getWidth() - 35;
                    if (prefs.getPrefMoreMessage() == PreferencesUser.HORIZONTAL) {
                        pos = _mainSplitPane.getHeight() - 35;
                    }
                    _mainSplitPane.setDividerLocation(pos);
                    msgPaneCollapsed = true;
                }
            }
            //<editor-fold defaultstate="collapsed" desc="mouse events not used">

            @Override
            public void mousePressed(MouseEvent me) {
            }

            @Override
            public void mouseReleased(MouseEvent me) {
            }

            @Override
            public void mouseEntered(MouseEvent me) {
            }

            @Override
            public void mouseExited(MouseEvent me) {
            }
            //</editor-fold>
        });
    }

    public Container getMsgPane() {
        return msgPane;
    }

    private SikuliIDEStatusBar initStatusbar() {
        _status = new SikuliIDEStatusBar();
        return _status;
    }

    public static SikuliIDEStatusBar getStatusbar() {
        if (sikulixIDE == null) {
            return null;
        } else {
            return sikulixIDE._status;
        }
    }

    private void initWindowListener() {
        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                SikuliIDE.this.quit();
            }
        });
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                PreferencesUser.getInstance().setIdeSize(SikuliIDE.this.getSize());
            }

            @Override
            public void componentMoved(ComponentEvent e) {
                PreferencesUser.getInstance().setIdeLocation(SikuliIDE.this.getLocation());
            }
        });
    }

    private void initTooltip() {
        ToolTipManager tm = ToolTipManager.sharedInstance();
        tm.setDismissDelay(30000);
    }

    //<editor-fold defaultstate="collapsed" desc="Init ShortCuts HotKeys">
    private void nextTab() {
        int i = tabPane.getSelectedIndex();
        int next = (i + 1) % tabPane.getTabCount();
        tabPane.setSelectedIndex(next);
    }

    private void prevTab() {
        int i = tabPane.getSelectedIndex();
        int prev = (i - 1 + tabPane.getTabCount()) % tabPane.getTabCount();
        tabPane.setSelectedIndex(prev);
    }

    private void initShortcutKeys() {
        final int scMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
            private boolean isKeyNextTab(java.awt.event.KeyEvent ke) {
                if (ke.getKeyCode() == java.awt.event.KeyEvent.VK_TAB
                        && ke.getModifiers() == InputEvent.CTRL_MASK) {
                    return true;
                }
                if (ke.getKeyCode() == java.awt.event.KeyEvent.VK_CLOSE_BRACKET
                        && ke.getModifiers() == (InputEvent.META_MASK | InputEvent.SHIFT_MASK)) {
                    return true;
                }
                return false;
            }

            private boolean isKeyPrevTab(java.awt.event.KeyEvent ke) {
                if (ke.getKeyCode() == java.awt.event.KeyEvent.VK_TAB
                        && ke.getModifiers() == (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)) {
                    return true;
                }
                if (ke.getKeyCode() == java.awt.event.KeyEvent.VK_OPEN_BRACKET
                        && ke.getModifiers() == (InputEvent.META_MASK | InputEvent.SHIFT_MASK)) {
                    return true;
                }
                return false;
            }

            public void eventDispatched(AWTEvent e) {
                java.awt.event.KeyEvent ke = (java.awt.event.KeyEvent) e;
                //Debug.log(ke.toString());
                if (ke.getID() == java.awt.event.KeyEvent.KEY_PRESSED) {
                    if (isKeyNextTab(ke)) {
                        nextTab();
                    } else if (isKeyPrevTab(ke)) {
                        prevTab();
                    }
                }
            }
        }, AWTEvent.KEY_EVENT_MASK);

    }

    public void removeCaptureHotkey() {
        HotkeyManager.getInstance().removeHotkey("Capture");
    }

    public void installCaptureHotkey() {
        HotkeyManager.getInstance().addHotkey("Capture", new HotkeyListener() {
            @Override
            public void hotkeyPressed(HotkeyEvent e) {
                if (!isRunningScript()) {
                    onQuickCapture();
                }
            }
        });
    }

    public void onQuickCapture() {
        onQuickCapture(null);
    }

    public void onQuickCapture(String arg) {
        Debug.log(3, "QuickCapture");
        _btnCapture.capture(0);
    }

    public void removeStopHotkey() {
        HotkeyManager.getInstance().removeHotkey("Abort");
    }

    public void installStopHotkey() {
        HotkeyManager.getInstance().addHotkey("Abort", new HotkeyListener() {
            @Override
            public void hotkeyPressed(HotkeyEvent e) {
                onStopRunning();
            }
        });
    }

    public void onStopRunning() {
        Debug.log(3, "StopRunning after AbortKey");
        _btnRun.stopRunning();
        _btnRunViz.stopRunning();
        org.sikuli.script.Sikulix.cleanUp(-1);
        this.setVisible(true);
    }

    private void initHotkeys() {
        installCaptureHotkey();
        installStopHotkey();
    }
    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="IDE Unit Testing --- RaiMan not used">
    /*
     private void initSidePane() {
     initUnitPane();
     _sidePane = new JXCollapsiblePane(JXCollapsiblePane.Direction.RIGHT);
     _sidePane.setMinimumSize(new Dimension(0, 0));
     CloseableTabbedPane tabPane = new CloseableTabbedPane();
     _sidePane.getContentPane().add(tabPane);
     tabPane.setMinimumSize(new Dimension(0, 0));
     tabPane.addTab(_I("tabUnitTest"), _unitPane);
     tabPane.addCloseableTabbedPaneListener(new CloseableTabbedPaneListener() {
     @Override
     public boolean closeTab(int tabIndexToClose) {
     _sidePane.setCollapsed(true);
     _chkShowUnitTest.setState(false);
     return false;
     }
     });
     _sidePane.setCollapsed(true);
     }
        
     private void initUnitPane() {
     _testRunner = new UnitTestRunner();
     _unitPane = _testRunner.getPanel();
     _chkShowUnitTest.setState(false);
     addAuxTab(_I("paneTestTrace"), _testRunner.getTracePane());
     }
     */
    public void addAuxTab(String tabName, JComponent com) {
        msgPane.addTab(tabName, com);
    }

    public void jumpTo(String funcName) throws BadLocationException {
        EditorPane pane = getCurrentCodePane();
        pane.jumpTo(funcName);
        pane.grabFocus();
    }

    public void jumpTo(int lineNo) throws BadLocationException {
        EditorPane pane = getCurrentCodePane();
        pane.jumpTo(lineNo);
        pane.grabFocus();
    }
    //</editor-fold>
}