/* * Copyright (c) 2010-2016, Sikuli.org, sikulix.com * Released under the MIT License. * */ package org.sikuli.ide; import com.explodingpixels.macwidgets.MacUtils; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.AWTEventListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.InputEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; 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 java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.WindowConstants; import javax.swing.event.ChangeListener; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultEditorKit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; 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.android.ADBClient; import org.sikuli.android.ADBScreen; import org.sikuli.android.ADBTest; import org.sikuli.basics.Debug; import org.sikuli.basics.FileManager; import org.sikuli.basics.HotkeyEvent; import org.sikuli.basics.HotkeyListener; import org.sikuli.basics.HotkeyManager; import org.sikuli.basics.PreferencesUser; import org.sikuli.basics.Settings; import org.sikuli.idesupport.IDESplash; import org.sikuli.idesupport.IDESupport; import org.sikuli.idesupport.IIDESupport; import org.sikuli.script.*; import org.sikuli.script.Sikulix; import org.sikuli.scriptrunner.IScriptRunner; import org.sikuli.scriptrunner.ScriptingSupport; import org.sikuli.util.CommandArgs; import org.sikuli.util.CommandArgsEnum; import org.sikuli.util.EventObserver; import org.sikuli.util.EventSubject; import org.sikuli.util.OverlayCapturePrompt; import org.sikuli.util.SikulixFileChooser; 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 = null, _btnRunViz = null; 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 boolean showAbout = true; private boolean showPrefs = true; private boolean showQuit = true; IDESplash ideSplash = null; boolean idePause = false; int waitBeforeVisible = 0; private synchronized boolean setPause(Boolean state) { if (state != null) { idePause = state; } return idePause; } private boolean getPause() { return setPause(null); } private void waitPause() { if (getPause()) { ideSplash.setVisible(false); Sikulix.popup("No options yet!\nClick OK to continue!", String.format("%s-%s", runTime.getVersionShort(), runTime.sxBuildStamp)); ideSplash.showAction(" "); ideSplash.setVisible(true); waitBeforeVisible = 2; } } public static void showIDE() { Debug.log(3, "showIDE"); sikulixIDE.setVisible(true); } public static void hideIDE() { Debug.log(3, "hideIDE"); sikulixIDE.setVisible(false); RunTime.pause(0.5f); } public static void showAgain() { sikulixIDE.setVisible(true); EditorPane codePane = sikulixIDE.getCurrentCodePane(); codePane.requestFocus(); } 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 RunTime runTime; public static void run(String[] args) { start = (new Date()).getTime(); runTime = RunTime.get(RunTime.Type.IDE, args); 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); } runTime.setArgs(cmdArgs.getUserArgs(), cmdArgs.getSikuliArgs()); if (RunTime.shouldRunServer) { RunServer.run(null); System.exit(0); } if (cmdLine.hasOption("h")) { cmdArgs.printHelp(); System.exit(0); } 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"); ScriptingSupport.runscript(args); } getInstance(); log(3, "running with Locale: %s", SikuliIDEI18N.getLocaleShow()); sikulixIDE.initNativeSupport(); sikulixIDE.ideSplash = new IDESplash(runTime); if (cmdLine.hasOption(CommandArgsEnum.DEBUG.shortname())) { cmdValue = cmdLine.getOptionValue(CommandArgsEnum.DEBUG.longname()); if (cmdValue != null) { Debug.on(cmdValue); } } 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); } //TODO how to differentiate open and run for doubleclick/drop scripts if (macOpenFiles != null) { for (File f : macOpenFiles) { if (f.getName().endsWith(".sikuli") || f.getName().endsWith(".skl")) { ScriptingSupport.runscript(new String[]{"-r", f.getAbsolutePath()}); } } } runTime.printArgs(); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); //TODO UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception e) { log(-1, "Problem loading UIManager!\nError: %s", e.getMessage()); } sikulixIDE.initHotkeys(); sikulixIDE.ideSplash.showAction("Interrupt with " + HotkeyManager.getInstance().getHotKeyText("Abort")); sikulixIDE.ideSplash.showStep("Init ScriptingSupport"); ScriptingSupport.init(); IDESupport.initIDESupport(); sikulixIDE.initSikuliIDE(args); } private void initSikuliIDE(String[] args) { sikulixIDE.ideSplash.showStep("Reading Preferences"); prefs = PreferencesUser.getInstance(); //prefs.exportPrefs(new File(runTime.fUserDir, "SikulixIDEprefs.txt").getAbsolutePath()); if (prefs.getUserType() < 0) { prefs.setUserType(PreferencesUser.NEWBEE); prefs.setIdeSession(""); prefs.setDefaults(prefs.getUserType()); } _windowSize = prefs.getIdeSize(); _windowLocation = prefs.getIdeLocation(); Rectangle monitor = runTime.hasPoint(_windowLocation); if (monitor == null) { log(-1, "Remembered window not valid. Going to primary screen"); monitor = runTime.getMonitor(-1); _windowSize.width = 0; } if (_windowSize.width == 0) { _windowSize = new Dimension(1024, 700); _windowLocation = new Point(100, 50); } Rectangle win = monitor.intersection(new Rectangle(_windowLocation, _windowSize)); setSize(win.getSize()); setLocation(_windowLocation); sikulixIDE.ideSplash.showStep("Init Window"); 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(); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); initWindowListener(); initTooltip(); sikulixIDE.ideSplash.showStep("Check for Updates"); autoCheckUpdate(); try { getCurrentCodePane().requestFocus(); } catch (Exception e) { } waitPause(); sikulixIDE.ideSplash.showStep("Restore last Session"); restoreSession(0); if (tabPane.getTabCount() == 0) { (new FileAction()).doNew(null); } tabPane.setSelectedIndex(0); Debug.info("IDE startup: %4.1f seconds", (new Date().getTime() - start) / 1000.0); if (waitBeforeVisible > 0) { try { Thread.sleep(1000 * waitBeforeVisible); } catch (InterruptedException ex) { } } sikulixIDE.ideSplash.setVisible(false); sikulixIDE.ideSplash.dispose(); sikulixIDE.ideSplash = null; setVisible(true); _mainSplitPane.setDividerLocation(0.6); _inited = true; } private void initNativeSupport() { if (!Settings.isMac()) { return; } log(lvl, "initNativeSupport: starting"); if (System.getProperty("sikulix.asapp") != null) { Settings.isMacApp = true; } 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"); Class clOpenHandler = sysclass.forName("com.apple.eawt.OpenFilesHandler"); Object appHandler = Proxy.newProxyInstance( comAppleEawtApplication.getClassLoader(), new Class[]{clAboutHandler, clPreferencesHandler, clQuitHandler, clOpenHandler}, 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; m = comAppleEawtApplication.getMethod("setOpenFileHandler", new Class[]{clOpenHandler}); m.invoke(instApplication, new Object[]{appHandler}); } 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"); } private static List<File> macOpenFiles = null; @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 ("openFiles".equals(mName)) { log(lvl, "nativeSupport: should open files"); try { Method mOpenFiles = args[0].getClass().getMethod("getFiles", new Class[]{}); macOpenFiles = (List<File>) mOpenFiles.invoke(args[0], new Class[]{}); for (File f : macOpenFiles) { log(lvl, "nativeSupport: openFiles: %s", macOpenFiles); } } catch (Exception ex) { log(lvl, "NativeSupport: Quit: error: %s", ex.getMessage()); System.exit(1); } } 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(runTime.SikuliVersionIDE + " - " + 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(); if (session_str == null && loadScripts == null && macOpenFiles == null) { return; } List<File> filesToLoad = new ArrayList<File>(); if (macOpenFiles != null) { for (File f : macOpenFiles) { filesToLoad.add(f); restoreScriptFromSession(f); } } if (session_str != null) { String[] filenames = session_str.split(";"); for (int i = 0; i < filenames.length; i++) { if (filenames[i].isEmpty()) { continue; } File f = new File(filenames[i]); if (f.exists() && !filesToLoad.contains(f)) { Debug.log(3, "restore session: %s", f); filesToLoad.add(f); restoreScriptFromSession(f); } } } if (loadScripts != null) { for (int i = 0; i < loadScripts.length; i++) { if (loadScripts[i].isEmpty()) { continue; } File f = new File(loadScripts[i]); if (f.exists() && !filesToLoad.contains(f)) { Debug.log(3, "preload script: %s", f); filesToLoad.add(f); restoreScriptFromSession(f); } } } } private boolean restoreScriptFromSession(File file) { EditorPane ep = (new FileAction()).doNew(null, -1); ep.loadFile(file.getAbsolutePath()); if (ep.hasEditingFile()) { setCurrentFileTabTitle(file.getAbsolutePath()); 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 sName = new File(fName).getName(); int i = sName.lastIndexOf("."); if (i > 0) { tabPane.setTitleAt(tabIndex, sName.substring(0, i)); } else { tabPane.setTitleAt(tabIndex, sName); } this.setTitle(new File(fName).getAbsolutePath()); } 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(); runTime.resetProject(); 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 " + runTime.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: " + runTime.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 (Exception ex) { if (ex instanceof IOException) { log(-1, "Problem when trying to save %s\nError: %s", fname, ex.getMessage()); } else { log(-1, "A non-IOException-problem when trying to save %s\nError: %s", fname, ex.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 (Exception ex) { log(-1, "Problem when trying to save %s\nError: %s", fname, ex.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 (Exception ex) { log(-1, "doSaveAs: %s Error: %s", orgName, ex.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 (Exception ex) { Debug.info("Can't close this tab: %s", ex.getMessage()); } 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))); _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(); getCurrentCodePane().saveCaretPosition(); 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))); _toolMenu.add(createMenuItem(_I("menuToolAndroid"), null, new ToolAction(ToolAction.ANDROID))); } class ToolAction extends MenuAction { static final String EXTENSIONS = "extensions"; static final String ANDROID = "android"; ToolAction() { super(); } public ToolAction(String item) throws NoSuchMethodException { super(item); } public void extensions(ActionEvent ae) { showExtensionsFrame(); } public void android(ActionEvent ae) { androidSupport(); } } private 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); } } private static IScreen defaultScreen = null; public static IScreen getDefaultScreen() { return defaultScreen; } private void androidSupport() { final ADBScreen aScr = new ADBScreen(); String title = "Android Support - !!EXPERIMENTAL!!"; if (aScr.isValid()) { String warn = "Device found: " + aScr.getDeviceDescription() + "\n\n" + "click Check: a short test is run with the device\n" + "click Default: set device as default screen for capture\n" + "click Cancel: nothing is done (default screen is reset)\n" + "\nBE PREPARED: Feature is experimental - no guarantee ;-)"; String[] options = new String[3]; options[WARNING_DO_NOTHING] = "Check"; options[WARNING_ACCEPTED] = "Default"; 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) { defaultScreen = null; return; } if (ret == WARNING_DO_NOTHING) { SikuliIDE.hideIDE(); Thread test = new Thread() { @Override public void run() { androidSupportTest(aScr); } }; test.start(); } else if (ret == WARNING_ACCEPTED) { defaultScreen = aScr; return; } } else if (!ADBClient.isAdbAvailable) { Sikulix.popError("Package adb seems not to be available.\nIt must be installed for Android support.", title); } else { Sikulix.popError("No android device attached", title); } } private void androidSupportTest(ADBScreen aScr) { ADBTest.ideTest(aScr); SikuliIDE.showIDE(); } //</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://sikulix.com/quickstart/"); } 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 = runTime.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"), runTime.SikuliVersionIDE, 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().init(); JButton btnLocation = new ButtonLocation().init(); JButton btnOffset = new ButtonOffset().init(); JButton btnShow = new ButtonShow().init(); JButton btnShowIn = new ButtonShowIn().init(); toolbar.add(_btnCapture); toolbar.add(btnInsertImage); toolbar.add(btnSubregion); toolbar.add(btnLocation); toolbar.add(btnOffset); toolbar.add(btnShow); toolbar.add(btnShowIn); 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()) { JComponent jcSearchField = createSearchField(); toolbar.add(jcSearchField); // } 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 SikulixFileChooser(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 { String promptText; String buttonText; String iconFile; String buttonHint; public ButtonSubregion() { super(); promptText = SikuliIDE._I("msgCapturePrompt"); buttonText = "Region"; // SikuliIDE._I("btnRegionLabel"); iconFile = "/icons/region-icon.png"; buttonHint = SikuliIDE._I("btnRegionHint"); } public ButtonSubregion init() { URL imageURL = SikuliIDE.class.getResource(iconFile); setIcon(new ImageIcon(imageURL)); setText(buttonText); //setMaximumSize(new Dimension(26,26)); setToolTipText(buttonHint); addActionListener(this); return this; } @Override public void actionPerformed(ActionEvent ae) { if (shouldRun()) { sikulixIDE.setVisible(false); RunTime.pause(0.5f); Screen.doPrompt(promptText, this); } else { nothingTodo(); } } public void nothingTodo() { } public boolean shouldRun() { Debug.log(3, "TRACE: ButtonSubRegion triggered"); return true; } @Override public void update(EventSubject es) { OverlayCapturePrompt ocp = (OverlayCapturePrompt) es; ScreenImage simg = ocp.getSelection(); Screen.closePrompt(); Screen.resetPrompt(ocp); captureComplete(simg); updateAfter(); } public void updateAfter() { SikuliIDE.showAgain(); } public void captureComplete(ScreenImage simg) { int x, y, w, h; EditorPane codePane = getCurrentCodePane(); if (simg != null) { Rectangle roi = simg.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)); } } } } class ButtonLocation extends ButtonSubregion { public ButtonLocation() { super(); promptText = "Select a Location"; buttonText = "Location"; iconFile = "/icons/region-icon.png"; buttonHint = "Select location as center of selection"; } @Override public void captureComplete(ScreenImage simg) { int x, y, w, h; if (simg != null) { Rectangle roi = simg.getROI(); x = (int) (roi.getX() + roi.getWidth() / 2); y = (int) (roi.getY() + roi.getHeight() / 2); getCurrentCodePane().insertString(String.format("Location(%d, %d)", x, y)); } } } class ButtonOffset extends ButtonSubregion { public ButtonOffset() { super(); promptText = "Select an Offset"; buttonText = "Offset"; iconFile = "/icons/region-icon.png"; buttonHint = "Select offset as topLeft to buttomRight of selection"; } @Override public void captureComplete(ScreenImage simg) { int x, y, ox, oy; if (simg != null) { Rectangle roi = simg.getROI(); x = (int) roi.getX(); y = (int) roi.getY(); ox = (int) roi.getWidth(); oy = (int) roi.getHeight(); getCurrentCodePane().insertString(String.format("Region(%d, %d, %d, %d).asOffset()", x, y, ox, oy)); } } } class ButtonShow extends ButtonOnToolbar implements ActionListener { String buttonText; String iconFile; String buttonHint; public ButtonShow() { super(); buttonText = "Show"; iconFile = "/icons/region-icon.png"; buttonHint = "Show the item at the cursor"; } public ButtonShow init() { URL imageURL = SikuliIDE.class.getResource(iconFile); setIcon(new ImageIcon(imageURL)); setText(buttonText); //setMaximumSize(new Dimension(26,26)); setToolTipText(buttonHint); addActionListener(this); return this; } @Override public void actionPerformed(ActionEvent e) { String line = ""; EditorPane codePane = getCurrentCodePane(); line = codePane.getLineTextAtCaret(); String item = codePane.parseLineText(line); if (!item.isEmpty()) { String eval = ""; item = item.replaceAll("\"", "\\\""); if (item.startsWith("Region")) { if (item.contains(".asOffset()")) { item = item.replace(".asOffset()", ""); } eval = "Region.create" + item.substring(6) + ".highlight(2);"; } else if (item.startsWith("Location")) { eval = "new " + item + ".grow(10).highlight(2);"; } else if (item.startsWith("Pattern")) { eval = "m = Screen.all().exists(new " + item + ", 0); if (m != null) m.highlight(2); else print(m);"; } else if (item.startsWith("\"")) { eval = "m = Screen.all().exists(" + item + ", 0); if (m != null) m.highlight(2); else print(m);"; } if (!eval.isEmpty()) { Runner.runjsEval(eval); return; } } Sikulix.popup("Nothing to show"); } } class ButtonShowIn extends ButtonSubregion { String item = ""; public ButtonShowIn() { super(); buttonText = "Show in"; iconFile = "/icons/region-icon.png"; buttonHint = "Show the item at the cursor in the selected region"; } @Override public boolean shouldRun() { Debug.log(3, "TRACE: ButtonShowIn triggered"); EditorPane codePane = getCurrentCodePane(); String line = codePane.getLineTextAtCaret(); item = codePane.parseLineText(line); item = item.replaceAll("\"", "\\\""); if (item.startsWith("Pattern")) { item = "m = null; r = #region#; " + "if (r != null) m = r.exists(new " + item + ", 0); " + "if (m != null) m.highlight(2); else print(m);"; } else if (item.startsWith("\"")) { item = "m = null; r = #region#; " + "if (r != null) m = r.exists(" + item + ", 0); " + "if (m != null) m.highlight(2); else print(m);"; } return !item.isEmpty(); } @Override public void nothingTodo() { Sikulix.popup("Nothing to show"); } @Override public void captureComplete(ScreenImage simg) { if (simg != null) { Region reg = new Region(simg.getROI()); String itemReg = String.format("new Region(%d, %d, %d, %d)", reg.x, reg.y, reg.w, reg.h); item = item.replace("#region#", itemReg); Runner.runjsEval(item); } else { SikuliIDE.showAgain(); nothingTodo(); } } @Override public void updateAfter() { } } 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() { if (System.out.checkError()) { Sikulix.popError("System.out is broken (console output)!" + "\nYou will not see any messages anymore!" + "\nSave your work and restart the IDE!" + "\nYou may ignore this on your own risk!", "Fatal Error"); } 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); RunTime.pause(0.1f); sikulixIDE.setIsRunningScript(true); final IScriptRunner[] srunners = new IScriptRunner[]{null}; EditorPane codePane = getCurrentCodePane(); String cType = codePane.getContentType(); File scriptFile = null; if (codePane.isDirty()) { scriptFile = FileManager.createTempFile(Runner.typeEndings.get(cType)); if (scriptFile != null) { try { codePane.write(new BufferedWriter(new OutputStreamWriter( new FileOutputStream(scriptFile), "UTF8"))); } catch (Exception ex) { scriptFile = null; } } if (scriptFile == null) { log(-1, "runCurrentScript: temp file for running not available"); return; } } 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 = ScriptingSupport.getRunner(null, cType); if (srunner == null) { log(-1, "runCurrentScript: Could not load a script runner for: %s", cType); return; } addScriptCode(srunner); srunners[0] = srunner; ImagePath.reset(path.getAbsolutePath()); String tabtitle = tabPane.getTitleAt(tabPane.getSelectedIndex()); if (tabtitle.startsWith("*")) { tabtitle = tabtitle.substring(1); } final SubRun doRun = new SubRun(srunners, scriptFile, path, parent, tabtitle); _runningThread = new Thread(doRun); _runningThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { if (System.out.checkError()) { Sikulix.popError("System.out is broken (console output)!" + "\nYou will not see any messages anymore!" + "\nSave your work and restart the IDE!", "Fatal Error"); } log(lvl, "Scriptrun: cleanup in handler for uncaughtException: %s", e.toString()); doRun.hasFinished(true); doRun.afterRun(); } }); _runningThread.start(); } private class SubRun implements Runnable { private boolean finished = false; private int ret = 0; private IScriptRunner[] srunners = null; private File scriptFile = null; private File path = null; private String tabtitle = ""; private String parent; public SubRun(IScriptRunner[] srunners, File scriptFile, File path, String parent, String tabtitle) { this.srunners = srunners; this.scriptFile = scriptFile; this.path = path; this.tabtitle = tabtitle; this.parent = parent; } @Override public void run() { try { ret = srunners[0].runScript(scriptFile, path, runTime.getArgs(), new String[]{parent, tabtitle}); } catch (Exception ex) { log(-1, "(%s).runScript: Exception: %s", srunners[0], ex); } hasFinished(true); afterRun(); } public int getRet() { return ret; } public boolean hasFinished() { return hasFinished(false); } public synchronized boolean hasFinished(boolean state) { if (state) { finished = true; } return finished; } public void afterRun() { addErrorMark(ret); srunners[0].close(); srunners[0] = null; if (Image.getIDEshouldReload()) { EditorPane pane = sikulixIDE.getCurrentCodePane(); int line = pane.getLineNumberAtCaret(pane.getCaretPosition()); sikulixIDE.getCurrentCodePane().reparse(); sikulixIDE.getCurrentCodePane().jumpTo(line); } sikulixIDE.setIsRunningScript(false); sikulixIDE.setVisible(true); Sikulix.cleanUp(0); _runningThread = null; } } protected void addScriptCode(IScriptRunner srunner) { srunner.execBefore(null); srunner.execBefore(new String[]{"Settings.setShowActions(Settings.FALSE)"}); } public boolean isRunning() { return _runningThread != null; } public boolean stopRunScript() { if (_runningThread != null) { _runningThread.interrupt(); _runningThread.stop(); return true; } return false; } 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) { if (isInited()) { 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, "AbortKey was pressed"); boolean shouldCleanUp = true; if (_btnRun != null && _btnRun.isRunning()) { shouldCleanUp &= _btnRun.stopRunScript(); } if (_btnRunViz != null && _btnRunViz.isRunning()) { shouldCleanUp &= _btnRunViz.stopRunScript(); } if (_btnRun == null && _btnRunViz == null) { ideSplash.showAction("... accepted - please wait ..."); setPause(true); return; } if (shouldCleanUp) { org.sikuli.script.Sikulix.cleanUp(-1); this.setVisible(true); } else { Debug.log(3, "AbortKey was pressed, but nothing to stop here ;-)"); } } 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> }