package ij.plugin.frame;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.awt.datatransfer.*;																																																																																													
import ij.*;
import ij.gui.*;
import ij.util.Tools;
import ij.text.*;
import ij.macro.*;
import ij.plugin.MacroInstaller;
import ij.plugin.Commands;
import ij.plugin.Macro_Runner;
import ij.io.SaveDialog;

/** This is a simple TextArea based editor for editing and compiling plugins. */
public class Editor extends PlugInFrame implements ActionListener, ItemListener,
	TextListener, KeyListener, ClipboardOwner, MacroConstants, Runnable, Debugger {
	
	/** ImportPackage statements added in front of scripts. Contains no 
	newlines so that lines numbers in error messages are not changed. */
	public static String JavaScriptIncludes =
		"importPackage(Packages.ij);"+
		"importPackage(Packages.ij.gui);"+
		"importPackage(Packages.ij.process);"+
		"importPackage(Packages.ij.measure);"+
		"importPackage(Packages.ij.util);"+
		"importPackage(Packages.ij.plugin);"+
		"importPackage(Packages.ij.io);"+
		"importPackage(Packages.ij.plugin.filter);"+
		"importPackage(Packages.ij.plugin.frame);"+
		"importPackage(java.lang);"+
		"importPackage(java.awt);"+
		"importPackage(java.awt.image);"+
		"importPackage(java.awt.geom);"+
		"importPackage(java.util);"+
		"importPackage(java.io);"+
		"function print(s) {IJ.log(s);};";

	public static final int MAX_SIZE=28000, XINC=10, YINC=18;
	public static final int MONOSPACED=1, MENU_BAR=2;
	public static final int MACROS_MENU_ITEMS = 13;
	static final String FONT_SIZE = "editor.font.size";
	static final String FONT_MONO= "editor.font.mono";
	static final String CASE_SENSITIVE= "editor.case-sensitive";
	static final String DEFAULT_DIR= "editor.dir";
	static final String INSERT_SPACES= "editor.spaces";
	static final String TAB_INC= "editor.tab-inc";
	public static Editor currentMacroEditor;
	private TextArea ta;
	private String path;
	protected boolean changes;
	private static String searchString = "";
	private static boolean caseSensitive = Prefs.get(CASE_SENSITIVE, true);
	private static int lineNumber = 1;
	private static int xoffset, yoffset;
	private static int nWindows;
	private Menu fileMenu, editMenu;
	private Properties p = new Properties();
	private int[] macroStarts;
	private String[] macroNames;
	private MenuBar mb;
	private Menu macrosMenu;
	private int nMacros;
	private Program pgm;
	private int eventCount;
	private String shortcutsInUse;
	private int inUseCount;
	private MacroInstaller installer;
	private static String defaultDir = Prefs.get(DEFAULT_DIR, null);;
	private boolean dontShowWindow;
    private int[] sizes = {9, 10, 11, 12, 13, 14, 16, 18, 20, 24, 36, 48, 60, 72};
    private int fontSize = (int)Prefs.get(FONT_SIZE, 6); // defaults to 16-point
    private CheckboxMenuItem monospaced;
    private static boolean wholeWords;
    private boolean isMacroWindow;
    private int debugStart, debugEnd;
    private static TextWindow debugWindow;
    private boolean step;
    private int previousLine;
    private static Editor instance;
    private int runToLine;
    private String downloadUrl;
    private boolean downloading;
    private FunctionFinder functionFinder;
    private ArrayList undoBuffer = new ArrayList();
    private boolean performingUndo;
    private boolean checkForCurlyQuotes;
    private static int tabInc = (int)Prefs.get(TAB_INC, 3);
    private static boolean insertSpaces = Prefs.get(INSERT_SPACES, false);
    CheckboxMenuItem insertSpacesItem;

	
	public Editor() {
		this(24, 80, 0, MENU_BAR);
	}

	public Editor(int rows, int columns, int fontSize, int options) {
		super("Editor");
		WindowManager.addWindow(this);
		addMenuBar(options);	
		ta = new TextArea(rows, columns);
		ta.addTextListener(this);
		ta.addKeyListener(this);
		if (IJ.isLinux()) ta.setBackground(Color.white);
 		addKeyListener(IJ.getInstance());  // ImageJ handles keyboard shortcuts
		add(ta);
		pack();
		if (fontSize<0)
			fontSize = 0;
		if (fontSize>=sizes.length)
			fontSize = sizes.length-1;
		setFont();
		positionWindow();
		if (IJ.isJava16() && !IJ.isJava18() && !IJ.isLinux())
			insertSpaces = false;
	}
	
	void addMenuBar(int options) {
		mb = new MenuBar();
		if (Menus.getFontSize()!=0) ;
			mb.setFont(Menus.getFont());
		Menu m = new Menu("File");
		m.add(new MenuItem("New...", new MenuShortcut(KeyEvent.VK_N, true)));
		m.add(new MenuItem("Open...", new MenuShortcut(KeyEvent.VK_O)));
		m.add(new MenuItem("Save", new MenuShortcut(KeyEvent.VK_S)));
		m.add(new MenuItem("Save As..."));
		m.add(new MenuItem("Revert"));
		m.add(new MenuItem("Print..."));
		m.addActionListener(this);
		fileMenu = m;
		mb.add(m);
		
		m = new Menu("Edit");
		MenuItem item = new MenuItem("Undo",new MenuShortcut(KeyEvent.VK_Z));
		m.add(item);
		m.addSeparator();
		boolean shortcutsBroken = IJ.isWindows()
			&& (System.getProperty("java.version").indexOf("1.1.8")>=0
			||System.getProperty("java.version").indexOf("1.5.")>=0);
		shortcutsBroken = false;
		if (shortcutsBroken)
			item = new MenuItem("Cut  Ctrl+X");
		else
			item = new MenuItem("Cut",new MenuShortcut(KeyEvent.VK_X));
		m.add(item);
		if (shortcutsBroken)
			item = new MenuItem("Copy  Ctrl+C");
		else
			item = new MenuItem("Copy", new MenuShortcut(KeyEvent.VK_C));
		m.add(item);
		if (shortcutsBroken)
			item = new MenuItem("Paste  Ctrl+V");
		else
			item = new MenuItem("Paste",new MenuShortcut(KeyEvent.VK_V));
		m.add(item);
		m.addSeparator();
		m.add(new MenuItem("Find...", new MenuShortcut(KeyEvent.VK_F)));
		m.add(new MenuItem("Find Next", new MenuShortcut(KeyEvent.VK_G)));
		m.add(new MenuItem("Go to Line...", new MenuShortcut(KeyEvent.VK_L)));
		m.addSeparator();
		m.add(new MenuItem("Select All", new MenuShortcut(KeyEvent.VK_A)));
		m.add(new MenuItem("Balance", new MenuShortcut(KeyEvent.VK_B,false)));
		m.add(new MenuItem("Detab..."));
		insertSpacesItem = new CheckboxMenuItem("Tab Key Inserts Spaces");
		insertSpacesItem.addItemListener(this);
		insertSpacesItem.setState(insertSpaces);
		m.add(insertSpacesItem);
		m.add(new MenuItem("Zap Gremlins"));
		m.add(new MenuItem("Copy to Image Info"));
		m.addActionListener(this);
		mb.add(m);
		editMenu = m;
		if ((options&MENU_BAR)!=0)
			setMenuBar(mb);
		
		m = new Menu("Font");
		m.add(new MenuItem("Make Text Smaller", new MenuShortcut(KeyEvent.VK_N)));
		m.add(new MenuItem("Make Text Larger", new MenuShortcut(KeyEvent.VK_M)));
		m.addSeparator();
		monospaced = new CheckboxMenuItem("Monospaced Font", Prefs.get(FONT_MONO, false));
		if ((options&MONOSPACED)!=0) monospaced.setState(true);
		monospaced.addItemListener(this);
		m.add(monospaced);
		m.add(new MenuItem("Save Settings"));
		m.addActionListener(this);
		mb.add(m);
		
		m = Menus.getExamplesMenu(this);
		mb.add(m);
	}			
			
	public void positionWindow() {
		Dimension screen = IJ.getScreenSize();
		Dimension window = getSize();
		if (window.width==0)
			return;
		int left = screen.width/2-window.width/2;
		int top = screen.height/(IJ.isWindows()?6:5);
		if (IJ.isMacOSX())
			top = (screen.height-window.height)/4;
		if (top<0) top = 0;
		if (nWindows<=0 || xoffset>8*XINC)
			{xoffset=0; yoffset=0;}
		setLocation(left+xoffset, top+yoffset);
		xoffset+=XINC; yoffset+=YINC;
		nWindows++;
	}

	void setWindowTitle(String title) {
		Menus.updateWindowMenuItem(getTitle(), title);
		setTitle(title);
	}
	
	public void create(String name, String text) {
		ta.append(text);
		if (IJ.isMacOSX()) IJ.wait(25); // needed to get setCaretPosition() on OS X
		ta.setCaretPosition(0);
		setWindowTitle(name);
		boolean macroExtension = name.endsWith(".txt") || name.endsWith(".ijm");
		if (macroExtension || name.endsWith(".js") || name.endsWith(".bsh") || name.endsWith(".py") || name.indexOf(".")==-1) {
			macrosMenu = new Menu("Macros");			
			macrosMenu.add(new MenuItem("Run Macro", new MenuShortcut(KeyEvent.VK_R)));
			macrosMenu.add(new MenuItem("Evaluate Line", new MenuShortcut(KeyEvent.VK_Y)));
			macrosMenu.add(new MenuItem("Abort Macro"));
			macrosMenu.add(new MenuItem("Install Macros", new MenuShortcut(KeyEvent.VK_I)));
			macrosMenu.add(new MenuItem("Macro Functions...", new MenuShortcut(KeyEvent.VK_M, true)));
			macrosMenu.add(new MenuItem("Function Finder...", new MenuShortcut(KeyEvent.VK_F, true)));
			macrosMenu.addSeparator();
			macrosMenu.add(new MenuItem("Evaluate Macro"));
			macrosMenu.add(new MenuItem("Evaluate JavaScript", new MenuShortcut(KeyEvent.VK_J, false)));
			macrosMenu.add(new MenuItem("Evaluate BeanShell", new MenuShortcut(KeyEvent.VK_B, true)));
			macrosMenu.add(new MenuItem("Evaluate Python", new MenuShortcut(KeyEvent.VK_P, false)));
			macrosMenu.add(new MenuItem("Show Log Window", new MenuShortcut(KeyEvent.VK_L, true)));
			macrosMenu.addSeparator();
			// MACROS_MENU_ITEMS must be updated if items are added to this menu
			macrosMenu.addActionListener(this);
			mb.add(macrosMenu);
			if (!(name.endsWith(".js")||name.endsWith(".bsh")||name.endsWith(".py"))) {
				Menu debugMenu = new Menu("Debug");			
				debugMenu.add(new MenuItem("Debug Macro", new MenuShortcut(KeyEvent.VK_D)));
				debugMenu.add(new MenuItem("Step", new MenuShortcut(KeyEvent.VK_E)));
				debugMenu.add(new MenuItem("Trace", new MenuShortcut(KeyEvent.VK_T)));
				debugMenu.add(new MenuItem("Fast Trace", new MenuShortcut(KeyEvent.VK_T,true)));
				debugMenu.add(new MenuItem("Run"));
				debugMenu.add(new MenuItem("Run to Insertion Point", new MenuShortcut(KeyEvent.VK_E, true)));
				debugMenu.add(new MenuItem("Abort"));
				debugMenu.addActionListener(this);
				mb.add(debugMenu);
			}
		} else {
			fileMenu.addSeparator();
			fileMenu.add(new MenuItem("Compile and Run", new MenuShortcut(KeyEvent.VK_R)));
		}
		if (IJ.getInstance()!=null && !dontShowWindow)
			show();
		if (dontShowWindow) {
			dispose();
			dontShowWindow = false;
		}
		WindowManager.setWindow(this);
		checkForCurlyQuotes = true;
		changes = false;
	}

	public void createMacro(String name, String text) {
		create(name, text);
	}
	
	void installMacros(String text, boolean installInPluginsMenu) {
		String functions = Interpreter.getAdditionalFunctions();
		if (functions!=null && text!=null) {
			if (!(text.endsWith("\n") || functions.startsWith("\n")))
				text = text + "\n" + functions;
			else
				text = text + functions;
		}
		installer = new MacroInstaller();
		installer.setFileName(getTitle());
		int nShortcutsOrTools = installer.install(text, macrosMenu);
		if (installInPluginsMenu || nShortcutsOrTools>0)
			installer.install(null);
		dontShowWindow = installer.isAutoRunAndHide();
		currentMacroEditor = this;
	}
		
	/** Opens a file and replaces the text (if any) by the contents of the file. */
	public void open(String dir, String name) {
		path = dir+name;
		File file = new File(path);
		if (!file.exists()) {
			IJ.error("File not found: "+path);
			return;
		}
		try {
			StringBuffer sb = new StringBuffer(5000);
			BufferedReader r = new BufferedReader(new FileReader(file));
			while (true) {
				String s=r.readLine();
				if (s==null)
					break;
				else
					sb.append(s+"\n");
			}
			r.close();
			if (ta!=null && ta.getText().length()>0) {
				ta.setText("");  //delete previous contents (if any)
				eventCount = 0;
			}
			create(name, new String(sb));
			changes = false;
		}
		catch (Exception e) {
			IJ.handleException(e);
			return;
		}
	}

	public String getText() {
		if (ta==null)
			return "";
		else
			return ta.getText();
	}

	public TextArea getTextArea() {
		return ta;
	}

	public void display(String title, String text) {
		ta.selectAll();
		ta.replaceRange(text, ta.getSelectionStart(), ta.getSelectionEnd());
		ta.setCaretPosition(0);
		setWindowTitle(title);
		changes = false;
		if (IJ.getInstance()!=null)
			show();
		WindowManager.setWindow(this);
	}

	void save() {
		if (path==null) {
			saveAs(); 
			return;
		}
		File f = new File(path);
		if (f.exists() && !f.canWrite()) {
			IJ.showMessage("Editor", "Unable to save because file is write-protected. \n \n" + path);
			return;
		}
		String text = ta.getText();
		char[] chars = new char[text.length()];
		text.getChars(0, text.length(), chars, 0);
		try {
			BufferedReader br = new BufferedReader(new CharArrayReader(chars));
			BufferedWriter bw = new BufferedWriter(new FileWriter(path));
			while (true) {
				String s = br.readLine();
				if (s==null) break;
				bw.write(s, 0, s.length());
				bw.newLine();
			}
			bw.close();
			IJ.showStatus(text.length()+" chars saved to " + path);
			changes = false;
		} catch
			(IOException e) {}
	}

	void compileAndRun() {
		if (path==null)
			saveAs();
		if (path!=null) {
			save();
			String text = ta.getText();
			if (text.contains("implements PlugInFilter") && text.contains("IJ.run("))
				IJ.log("<>");
			IJ.runPlugIn("ij.plugin.Compiler", path);
		}
	}
	
	final void runMacro(boolean debug) {
		if (path!=null)
			Macro_Runner.setFilePath(path);
		if (getTitle().endsWith(".js"))
			{evaluateJavaScript(); return;}
		else if (getTitle().endsWith(".bsh"))
			{evaluateScript(".bsh"); return;}
		else if (getTitle().endsWith(".py"))
			{evaluateScript(".py"); return;}
		int start = ta.getSelectionStart();
		int end = ta.getSelectionEnd();
		String text;
		if (start==end)
			text = ta.getText();
		else
			text = ta.getSelectedText();
		Interpreter instance = Interpreter.getInstance();
		if (instance!=null) { // abort any currently running macro
			instance.abortMacro();
			long t0 = System.currentTimeMillis();
			while (Interpreter.getInstance()!=null && (System.currentTimeMillis()-t0)<3000L)
				IJ.wait(10);
		}
		if (checkForCurlyQuotes && text.contains("\u201D")) {
			// replace curly quotes with standard quotes
 			text = text.replaceAll("\u201C", "\""); 
			text = text.replaceAll("\u201D", "\"");
			if (start==end)
				ta.setText(text);
			else {
				String text2 = ta.getText();
 				text2 = text2.replaceAll("\u201C", "\""); 
				text2 = text2.replaceAll("\u201D", "\"");
				ta.setText(text2);
			}
			changes = true;
			checkForCurlyQuotes = false;
		}
		currentMacroEditor = this;
		new MacroRunner(text, debug?this:null);
	}
	
	void evaluateMacro() {
		String title = getTitle();
		if (title.endsWith(".js")||title.endsWith(".bsh")||title.endsWith(".py"))
			setTitle(title.substring(0,title.length()-3)+".ijm");
		runMacro(false);
	}

	void evaluateJavaScript() {
		if (!getTitle().endsWith(".js"))
			setTitle(SaveDialog.setExtension(getTitle(), ".js"));
		int start = ta.getSelectionStart();
		int end = ta.getSelectionEnd();
		String text;
		if (start==end)
			text = ta.getText();
		else
			text = ta.getSelectedText();
		if (text.equals(""))
			return;
		boolean strictMode = false;
		if (IJ.isJava18()) {
			// text.matches("^( |\t)*(\"use strict\"|'use strict')");
			String text40 = text.substring(0,Math.min(40,text.length()));
			strictMode =  text40.contains("'use strict'") || text40.contains("\"use strict\"");
		}
		text = getJSPrefix("") + text;
		if (IJ.isJava18()) {
			text = "load(\"nashorn:mozilla_compat.js\");" + text;
			if (strictMode)
				text = "'use strict';" + text;
		}
		if ((IJ.isJava16() && !(IJ.isMacOSX()&&!IJ.is64Bit()))) {
			// Use JavaScript engine built into Java 6 and later.
			IJ.runPlugIn("ij.plugin.JavaScriptEvaluator", text);
		} else {
			Object js = IJ.runPlugIn("JavaScript", text);
			if (js==null)
				download("/download/tools/JavaScript.jar");
		}
	}

	public void evaluateScript(String ext) {
		if (downloading) {
			IJ.beep();
			IJ.showStatus("Download in progress");
			return;
		}
		if (ext.endsWith(".js")) {
			evaluateJavaScript();
			return;
		}
		if (!getTitle().endsWith(ext))
			setTitle(SaveDialog.setExtension(getTitle(), ext));
		int start = ta.getSelectionStart();
		int end = ta.getSelectionEnd();
		String text;
		if (start==end)
			text = ta.getText();
		else
			text = ta.getSelectedText();
		if (text.equals("")) return;
		String plugin, url;
		if (ext.equals(".bsh")) {
			plugin = "bsh";
			url = "/plugins/bsh/BeanShell.jar";
		} else {
			// download Jython from http://imagej.nih.gov/ij/plugins/jython/
			plugin = "Jython";
			url = "/plugins/jython/Jython.jar";
		}
		Object obj = IJ.runPlugIn(plugin, text);
		if (obj==null)
			download(url);
	}
	
	private void download(String url) {
		this.downloadUrl = url;
		Thread thread = new Thread(this, "Downloader");
		thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY));
		thread.start();
	}

	void evaluateLine() {
		int start = ta.getSelectionStart();
		int end = ta.getSelectionEnd();
		if (end>start)
			{runMacro(false); return;}
		String text = ta.getText();
		while (start>0) {
			start--;
			if (text.charAt(start)=='\n')
				{start++; break;}
		}
		while (end pageHeight) {
						// New Page
						pageNum++;
						linesForThisPage = 0;
						pg.dispose();
						pg = pjob.getGraphics();
						if (pg != null)
							pg.setFont (helv);
						curHeight = topMargin;
					}
					curHeight += fontHeight;
					if (pg != null) {
						pg.drawString (nextLine, leftMargin, curHeight - fontDescent);
						linesForThisPage++;
						linesForThisJob++;
					} 
				}
			} while (nextLine != null);
		} catch (EOFException eof) {
	   // Fine, ignore
		} catch (Throwable t) { // Anything else
			IJ.handleException(t);
		}
	}
	
	String detabLine(String s) {
		if (s.indexOf('\t')<0)
			return s;
		int tabSize = 4;
		StringBuffer sb = new StringBuffer((int)(s.length()*1.25));
		char c;
		for (int i=0; i1) {
			undoBuffer.remove(undoBuffer.size()-1);
			String text = (String)undoBuffer.get(undoBuffer.size()-1);
			performingUndo = true;
			ta.setText(text);
			if (position<=text.length())
				ta.setCaretPosition(position);
			if (IJ.debugMode) IJ.log("Undo2: "+undoBuffer.size()+" "+text);
		}
	}
	
	boolean copy() { 
		String s; 
		s = ta.getSelectedText();
		Clipboard clip = getToolkit().getSystemClipboard();
		if (clip!=null) {
			StringSelection cont = new StringSelection(s);
			clip.setContents(cont,this);
			return true;
		} else
			return false;
	}
 
	  
	void cut() {
		if (copy()) {
			int start = ta.getSelectionStart();
			int end = ta.getSelectionEnd();
			ta.replaceRange("", start, end);
			if (IJ.isMacOSX())
				ta.setCaretPosition(start);
		}	
	}

	void paste() {
		String s;
		s = ta.getSelectedText();
		Clipboard clipboard = getToolkit( ). getSystemClipboard(); 
		Transferable clipData = clipboard.getContents(s);
		try {
			s = (String)(clipData.getTransferData(DataFlavor.stringFlavor));
		}
		catch  (Exception e)  {
			s  = e.toString( );
		}
		int start = ta.getSelectionStart( );
		int end = ta.getSelectionEnd( );
		ta.replaceRange(s, start, end);
		if (IJ.isMacOSX())
			ta.setCaretPosition(start+s.length());
		checkForCurlyQuotes = true;
	}

	void copyToInfo() { 
		ImagePlus imp = WindowManager.getCurrentImage();
		if (imp==null) {
			IJ.noImage();
			return;
		}
		int start = ta.getSelectionStart();
		int end = ta.getSelectionEnd();
		String text;
		if (start==end)
			text = ta.getText();
		else
			text = ta.getSelectedText();
		imp.setProperty("Info", text);
	}

	public void actionPerformed(ActionEvent e) {
		String what = e.getActionCommand();
		int flags = e.getModifiers();
		boolean altKeyDown = (flags & Event.ALT_MASK)!=0;
		
		if ("Save".equals(what))
			save();
		else if ("Compile and Run".equals(what))
				compileAndRun();
		else if ("Run Macro".equals(what)) {
			if (altKeyDown) {
				enableDebugging();
				runMacro(true);
			} else
				runMacro(false);
		} else if ("Debug Macro".equals(what)) {
				enableDebugging();
				runMacro(true);
		} else if ("Step".equals(what))
			setDebugMode(STEP);
		else if ("Trace".equals(what))
			setDebugMode(TRACE);
		else if ("Fast Trace".equals(what))
			setDebugMode(FAST_TRACE);
		else if ("Run".equals(what))
			setDebugMode(RUN_TO_COMPLETION);
		else if ("Run to Insertion Point".equals(what))
			runToInsertionPoint();
		else if ("Abort".equals(what) || "Abort Macro".equals(what)) {
			Interpreter.abort();
			IJ.beep();		
		} else if ("Evaluate Line".equals(what))
			evaluateLine();
		else if ("Install Macros".equals(what))
			installMacros(ta.getText(), true);
		else if ("Macro Functions...".equals(what))
			showMacroFunctions();
		else if ("Function Finder...".equals(what))
			functionFinder = new FunctionFinder(this);
		else if ("Evaluate Macro".equals(what))
			evaluateMacro();
		else if ("Evaluate JavaScript".equals(what))
			evaluateJavaScript();
		else if ("Evaluate BeanShell".equals(what))
			evaluateScript(".bsh");
		else if ("Evaluate Python".equals(what))
			evaluateScript(".py");
		else if ("Show Log Window".equals(what))
			showLogWindow();
		else if ("Revert".equals(what))
			revert();
		else if ("Print...".equals(what))
			print();
		else if (what.equals("Undo"))
		   undo();
		else if (what.equals("Paste"))
			paste();
		else if (what.equals("Copy"))
			copy();
		else if (what.equals("Cut"))
		   cut();
		else if ("Save As...".equals(what))
			saveAs();
		else if ("Select All".equals(what))
			selectAll();
		else if ("Find...".equals(what))
			find(null);
		else if ("Find Next".equals(what))
			find(searchString);
		else if ("Go to Line...".equals(what))
			gotoLine();
		else if ("Balance".equals(what))
			balance();
		else if ("Detab...".equals(what))
			detab();
		else if ("Zap Gremlins".equals(what))
			zapGremlins();
		else if ("Make Text Larger".equals(what))
			changeFontSize(true);
		else if ("Make Text Smaller".equals(what))
			changeFontSize(false);
		else if ("Save Settings".equals(what))
			saveSettings();
		else if ("New...".equals(what))
			IJ.run("Text Window");
		else if ("Open...".equals(what))
			IJ.open();
		else if (what.equals("Copy to Image Info"))
			copyToInfo();
		else if (what.endsWith(".ijm") || what.endsWith(".java") || what.endsWith(".js") || what.endsWith(".bsh") || what.endsWith(".py"))
			openExample(what);
		else {
			if (altKeyDown) {
				enableDebugging();
				installer.runMacro(what, this);
			} else
				installer.runMacro(what, null);
		}
	}
	
	/** Opens an example from the Help/Examples menu
		and runs if "Autorun Exampes" is checked. */
	public static boolean openExample(String name) {
		boolean isJava = name.endsWith(".java");
		boolean isJavaScript = name.endsWith(".js");
		boolean isBeanShell = name.endsWith(".bsh");
		boolean isPython = name.endsWith(".py");
		boolean isMacro = name.endsWith(".ijm");
		if (!(isMacro||isJava||isJavaScript||isBeanShell||isPython))
			return false;
		boolean run = !isJava && !name.contains("_Tool") && Prefs.autoRunExamples;
		int rows = 24;
		int columns = 70;
		int options = MENU_BAR;
		String text = null;
		Editor ed = new Editor(rows, columns, 0, options);
		String dir = "Macro/";
		if (isJava)
			dir = "Java/";
		else if (isJavaScript)
			dir = "JavaScript/";
		else if (isBeanShell)
			dir = "BeanShell/";
		else if (isPython)
			dir = "Python/";
		String url = "http://wsr.imagej.net/download/Examples/"+dir+name;
		text = IJ.openUrlAsString(url);
		if (text.startsWith("0) 
			url += "#" +selectedWords[0];//append selection as hash tag
		IJ.runPlugIn("ij.plugin.BrowserLauncher", IJ.URL+url);
	}

	final void runToInsertionPoint() {
		Interpreter interp = Interpreter.getInstance();
		if (interp==null)
			IJ.beep();
		else {
			runToLine = getCurrentLine();
			setDebugMode(RUN_TO_CARET);
		}
	}
		
	final int getCurrentLine() {
		int pos = ta.getCaretPosition();
		int currentLine = 0;
		String text = ta.getText();
		if (IJ.isWindows())
			text = text.replaceAll("\r\n", "\n");
		char[] chars = new char[text.length()];
		chars = text.toCharArray();
		int count=0;
		int start=0, end=0;
		int len = chars.length;
		for (int i=0; i=start && posend)
			currentLine = count;
		return currentLine;
	}

	final void enableDebugging() {
			step = true;
			int start = ta.getSelectionStart();
			int end = ta.getSelectionEnd();
			if (start==debugStart && end==debugEnd)
				ta.select(start, start);
	}
	
	final void setDebugMode(int mode) {
		step = true;
		Interpreter interp = Interpreter.getInstance();
		if (interp!=null) {
			if (interp.getDebugger()==null)
				fixLineEndings();
			interp.setDebugger(this);
			interp.setDebugMode(mode);
		}
	}

	public void textValueChanged(TextEvent e) {
		String text = ta.getText();
		//if (undo2==null || text.length()!=undo2.length()+1 || text.charAt(text.length()-1)=='\n')
		int length = 0;
		if (!performingUndo) {
			for (int i=0; i2 || !IJ.isMacOSX() && eventCount>1)
			changes = true;
		if (IJ.isMacOSX()) // screen update bug work around
			ta.setCaretPosition(ta.getCaretPosition());
	}
	
	public void keyPressed(KeyEvent e) { 
	} 
	
	public void keyReleased(KeyEvent e) {
		int pos = ta.getCaretPosition();
		if (insertSpaces && pos>0 && e.getKeyCode()==KeyEvent.VK_TAB) {
			String spaces = " ";
			for (int i=1; i=text.length()-1)
					{index=-1; break;}
			}
		} else
			index = text.indexOf(s, ta.getCaretPosition()+1);
		searchString = s2;
		if (index<0)
			{IJ.beep(); return;}
		ta.setSelectionStart(index);
		ta.setSelectionEnd(index+s.length());
	}
	
	boolean isWholeWordMatch(String text, String word, int index) {
		char c = index==0?' ':text.charAt(index-1);
		if (Character.isLetterOrDigit(c) || c=='_') return false;
		c = index+word.length()>=text.length()?' ':text.charAt(index+word.length());
		if (Character.isLetterOrDigit(c) || c=='_') return false;
		return true;
	}
	
	void gotoLine() {
		GenericDialog gd = new GenericDialog("Go to Line", this);
		gd.addNumericField("Go to line number: ", lineNumber, 0);
		gd.showDialog();
		if (gd.wasCanceled())
			return;
		int n = (int)gd.getNextNumber();
		if (n<1) return;
		String text = ta.getText();
		char[] chars = new char[text.length()];
		chars = text.toCharArray();
		int count=1, loc=0;
		for (int i=0; i= 0; i--) {
			char ch = chars[i];
			if ("({[]})".indexOf(ch) >= 0) {
				leftBows = ch + leftBows;
				leftBows = leftBows.replace("[]", "");//skip nested pairs
				leftBows = leftBows.replace("()", "");
				leftBows = leftBows.replace("{}", "");
				if (leftBows.equals ("[") || leftBows.equals ("{") || leftBows.equals ("(")) {
					start = i;
					break;
				}
			}
		}
		String rightBows = "";
		for (int i = position ; i < chars.length; i++) {
			char ch = chars[i];
			if ("({[]})".indexOf(ch) >= 0) {
				rightBows += ch;
				rightBows = rightBows.replace("[]", "");//skip nested pairs
				rightBows = rightBows.replace("()", "");
				rightBows = rightBows.replace("{}", "");
				String pair = leftBows + rightBows;
				if (pair.equals("[]") ||  pair.equals("{}") || pair.equals("()")) {
					stop = i;
					break;
				}
			}
		}
		if (start == -1 || stop == -1) {
			IJ.beep();
			return;
		}
		ta.setSelectionStart(start);
		ta.setSelectionEnd(stop + 1);
		IJ.showStatus(chars.length + " " + position + " " + start + " " + stop);
	}

	// replaces contents of comments with blanks
	private void maskComments(char[] chars) {
		int n = chars.length;
		boolean inSlashSlashComment = false;
		boolean inSlashStarComment = false;
		for (int i=0; i 0 && chars[i - 1] == '\\';
				if (chars[i] == '\n')
					inQuotes = false;
				if (chars[i] == quote && !escaped) {
					if (!inQuotes) {
						startMask = i;
						inQuotes = true;
					} else {
						stopMask = i;
						for (int jj = startMask; jj <= stopMask; jj++) {
							chars[jj] = ' ';
						}
						inQuotes = false;
					}
				}
			}
		}
	}
	
   //replaces contents of comments with blanks
	private void rmaskComments(char[] chars) {
		int n = chars.length;
		boolean inSlashSlashComment = false;
		boolean inSlashStarComment = false;
		for (int i=0; i127)) {
				count++;
				chars[i] = ' ';
			}
		}
		if (count>0) {
			text = new String(chars);
			ta.setText(text);
		}
		if (count>0)
			IJ.showMessage("Zap Gremlins", count+" invalid characters converted to spaces");
		else
			IJ.showMessage("Zap Gremlins", "No invalid characters found");
	}
	
	
	private void detab() {
		GenericDialog gd = new GenericDialog("Detab", this);
		gd.addNumericField("Spaces per tab: ", tabInc, 0);
		gd.addCheckbox("Tab key inserts spaces: ", insertSpaces);
		gd.showDialog();
		if (gd.wasCanceled())
			return;
		int tabInc2 = tabInc;
		tabInc = (int)gd.getNextNumber();
		if (tabInc<1) tabInc=1;
		if (tabInc>8) tabInc=8;
		if (tabInc!=tabInc2)
			Prefs.set(TAB_INC, tabInc);
		boolean insertSpaces2 = insertSpaces;
		insertSpaces = gd.getNextBoolean();
		if (insertSpaces!=insertSpaces2) {
			Prefs.set(INSERT_SPACES, insertSpaces);
			insertSpacesItem.setState(insertSpaces);
		}
		int nb = 0;
		int pos = 1;
		String text = ta.getText();
		if (text.indexOf('\t')<0)
			return;
		char[] chars = new char[text.length()];
		chars = text.toCharArray();
		StringBuffer sb = new StringBuffer((int)(chars.length*1.25));
		for (int i=0; i0) {
					sb.append(' ');
					++pos;
					--nb;
				}
			} else if (c=='\n') {
				sb.append(c);
				pos = 1;
			} else {
				sb.append(c);
				++pos;
			}
		}
		ta.setText(sb.toString());
	}

	void selectAll() {
		ta.selectAll();
	}
    
    void changeFontSize(boolean larger) {
        int in = fontSize;
        if (larger) {
            fontSize++;
            if (fontSize==sizes.length)
                fontSize = sizes.length-1;
        } else {
            fontSize--;
            if (fontSize<0)
                fontSize = 0;
        }
        IJ.showStatus(sizes[fontSize]+" point");
        setFont();
    }
    
    void saveSettings() {
		Prefs.set(FONT_SIZE, fontSize);
		Prefs.set(FONT_MONO, monospaced.getState());
		IJ.showStatus("Font settings saved (size="+sizes[fontSize]+", monospaced="+monospaced.getState()+")");
    }
    
    void setFont() {
        ta.setFont(new Font(getFontName(), Font.PLAIN, sizes[fontSize]));
    }
    
    String getFontName() {
    	return monospaced.getState()?"Monospaced":"SansSerif";
    }
	
	public void setFont(Font font) {
		ta.setFont(font);
	}

	public void append(String s) {
		ta.append(s);
	}

	public void setIsMacroWindow(boolean mw) {
		isMacroWindow = mw;
	}

	public static void setDefaultDirectory(String defaultDirectory) {
		defaultDir = defaultDirectory;
		if (defaultDir!=null && !(defaultDir.endsWith(File.separator)||defaultDir.endsWith("/")))
			defaultDir += File.separator;
	}
	
	public void lostOwnership (Clipboard clip, Transferable cont) {}
	
	public int debug(Interpreter interp, int mode) {
		if (IJ.debugMode)
			IJ.log("debug: "+interp.getLineNumber()+"  "+mode+"  "+interp);
		if (mode==RUN_TO_COMPLETION)
			return 0;
		int n = interp.getLineNumber();
		if (mode==RUN_TO_CARET) {
			if (n==runToLine) {
				mode = STEP;
				interp.setDebugMode(mode);
			} else
				return 0;
		}
		if (!isVisible()) { // abort macro if user closes window
			interp.abortMacro();
			return 0;
		}
		if (n==previousLine) {
			previousLine=0;
			return 0;
		}
		Window win = WindowManager.getActiveWindow();
		if (win!=this)
			IJ.wait(50);
		toFront();
		previousLine = n;
		String text = ta.getText();
		if (IJ.isWindows())
			text = text.replaceAll("\r\n", "\n");
		char[] chars = new char[text.length()];
		chars = text.toCharArray();
		int count=1;
		debugStart=0;
		int len = chars.length;
		debugEnd = len;
		for (int i=0; i 

Ads help maintain this website.