| Functions |

    package ij.macro;
    import ij.*;
    import ij.process.*;
    import ij.gui.*;
    import ij.measure.*;
    import ij.plugin.*;
    import ij.plugin.filter.*;
    import ij.plugin.frame.*;
    import ij.text.*;
    import ij.io.*;
    import ij.util.Tools;
    import java.awt.*;
    import java.awt.image.*;
    import java.util.*;
    import java.io.*;
    import java.awt.event.KeyEvent;
    import java.lang.reflect.*;
    import java.net.URL;
    import java.awt.datatransfer.*;

    /** This class implements the built-in macro functions. */
    public class Functions implements MacroConstants, Measurements {
        Interpreter interp;
        Program pgm;
        boolean updateNeeded;
        boolean autoUpdate = true;
        ImagePlus defaultImp;
        ImageProcessor defaultIP;
        int imageType;
        boolean colorSet, fontSet;
        Color defaultColor;
        double defaultValue = Double.NaN;
        Plot plot;
        static int plotID;
        int justification = ImageProcessor.LEFT_JUSTIFY;
        Font font;
        GenericDialog gd;
        PrintWriter writer;
        boolean altKeyDown, shiftKeyDown;
        boolean antialiasedText;
        StringBuffer buffer;

        boolean saveSettingsCalled;
        boolean usePointerCursor, hideProcessStackDialog;
        float divideByZeroValue;
        int jpegQuality;
        int lineWidth;
        boolean doScaling;
        boolean weightedColor;
        double[] weights;
        boolean interpolateScaledImages, open100Percent, blackCanvas;
        boolean useJFileChooser,debugMode;
        Color foregroundColor, backgroundColor, roiColor;
        boolean pointAutoMeasure, requireControlKey, useInvertingLut;
        boolean doubleBuffer, disablePopup;
        int measurements;
        int decimalPlaces;
        boolean blackBackground;

        Functions(Interpreter interp, Program pgm) {
            this.interp = interp;
            this.pgm = pgm;
        }

        void doFunction(int type) {
            switch (type) {
                case RUN: doRun(); break;
                case SELECT: IJ.selectWindow(getStringArg()); resetImage(); break;
                case WAIT: IJ.wait((int)getArg()); break;
                case BEEP: interp.getParens(); IJ.beep(); break;
                case RESET_MIN_MAX: interp.getParens(); IJ.resetMinAndMax(); resetImage(); break;
                case RESET_THRESHOLD: interp.getParens(); IJ.resetThreshold(); resetImage(); break;
                case PRINT: print(); break;
                case WRITE: IJ.write(getStringArg()); break;
                case DO_WAND: IJ.doWand((int)getFirstArg(), (int)getLastArg()); resetImage(); break;
                case SET_MIN_MAX: IJ.setMinAndMax(getFirstArg(), getLastArg()); resetImage(); break;
                case SET_THRESHOLD: setThreshold(); break;
                case SET_TOOL: IJ.setTool((int)getArg()); break;
                case SET_FOREGROUND: setForegroundColor(); break;
                case SET_BACKGROUND: setBackgroundColor(); break;
                case SET_COLOR: setColor(); break;
                case MAKE_LINE: makeLine(); break;
                case MAKE_OVAL: makeOval(); break;
                case MAKE_RECTANGLE: makeRectangle(); break;
                case DUMP: interp.dump(); break;
                case LINE_TO: lineTo(); break;
                case MOVE_TO: moveTo(); break;
                case DRAW_LINE: drawLine(); break;
                case REQUIRES: requires(); break;
                case AUTO_UPDATE: autoUpdate = getBooleanArg(); break;
                case UPDATE_DISPLAY: interp.getParens(); updateDisplay(); break;
                case DRAW_STRING: drawString(); break;
                case SET_PASTE_MODE: IJ.setPasteMode(getStringArg()); break;
                case DO_COMMAND: doCommand(); break;
                case SHOW_STATUS: IJ.showStatus(getStringArg()); interp.statusUpdated=true; break;
                case SHOW_PROGRESS: showProgress(); break;
                case SHOW_MESSAGE: showMessage(false); break;
                case SHOW_MESSAGE_WITH_CANCEL: showMessage(true); break;
                case SET_PIXEL: case PUT_PIXEL: setPixel(); break;
                case SNAPSHOT: case RESET: case FILL: doIPMethod(type); break;
                case SET_LINE_WIDTH: getProcessor().setLineWidth((int)getArg()); break;
                case CHANGE_VALUES: changeValues(); break;
                case SELECT_IMAGE: selectImage(); break;
                case EXIT: exit(); break;
                case SET_LOCATION: setLocation(); break;
                case GET_CURSOR_LOC: getCursorLoc(); break;
                case GET_LINE: getLine(); break;
                case GET_VOXEL_SIZE: getVoxelSize(); break;
                case GET_HISTOGRAM: getHistogram(); break;
                case GET_BOUNDING_RECT: case GET_BOUNDS: getBounds(); break;
                case GET_LUT: getLut(); break;
                case SET_LUT: setLut(); break;
                case GET_COORDINATES: getCoordinates(); break;
                case MAKE_SELECTION: makeSelection(); break;
                case SET_RESULT: setResult(); break;
                case UPDATE_RESULTS: updateResults(); break;
                case SET_BATCH_MODE: setBatchMode(); break;
                case PLOT: doPlot(); break;
                case SET_JUSTIFICATION: setJustification(); break;
                case SET_Z_COORDINATE: setZCoordinate(); break;
                case GET_THRESHOLD: getThreshold(); break;
                case GET_PIXEL_SIZE: getPixelSize(); break;
                case SETUP_UNDO: interp.getParens(); Undo.setup(Undo.TRANSFORM, getImage()); break;
                case SAVE_SETTINGS: saveSettings(); break;
                case RESTORE_SETTINGS: restoreSettings(); break;
                case SET_KEY_DOWN: setKeyDown(); break;
                case OPEN: open(); break;
                case SET_FONT: setFont(); break;
                case GET_MIN_AND_MAX: getMinAndMax(); break;
                case CLOSE: close(); break;
                case SET_SLICE: setSlice(); break;
                case NEW_IMAGE: newImage(); break;
                case SAVE: IJ.save(getStringArg()); break;
                case SAVE_AS: saveAs(); break;
                case SET_AUTO_THRESHOLD: setAutoThreshold(); break;
                case RENAME: resetImage(); getImage().setTitle(getStringArg()); break;
                case GET_STATISTICS: getStatistics(true); break;
                case GET_RAW_STATISTICS: getStatistics(false); break;
                case FLOOD_FILL: floodFill(); break;
                case RESTORE_PREVIOUS_TOOL: restorePreviousTool(); break;
                case SET_VOXEL_SIZE: setVoxelSize(); break;
                case GET_LOCATION_AND_SIZE: getLocationAndSize(); break;
                case GET_DATE_AND_TIME: getDateAndTime(); break;
                case SET_METADATA: setMetadata(); break;
                case CALCULATOR: imageCalculator(); break;
                case SET_RGB_WEIGHTS: setRGBWeights(); break;
                case MAKE_POLYGON: makePolygon(); break;
                case SET_SELECTION_NAME: setSelectionName(); break;
                case DRAW_RECT: case FILL_RECT: case DRAW_OVAL: case FILL_OVAL: drawOrFill(type); break;
                case SET_OPTION: setOption(); break;
                case SHOW_TEXT: showText(); break;
                case SET_SELECTION_LOC: setSelectionLocation(); break;
                case GET_DIMENSIONS: getDimensions(); break;
            }
        }

        final double getFunctionValue(int type) {
            double value = 0.0;
            switch (type) {
                case GET_PIXEL: value = getPixel(); break;
                case ABS: case COS: case EXP: case FLOOR: case LOG: case ROUND:
                case SIN: case SQRT: case TAN: case ATAN: case ASIN: case ACOS:
                    value = math(type);
                    break;
                case MAX_OF: case MIN_OF: case POW: case ATAN2: value=math2(type); break;
                case GET_TIME: interp.getParens(); value=System.currentTimeMillis(); break;
                case GET_WIDTH: interp.getParens(); value=getImage().getWidth(); break;
                case GET_HEIGHT: interp.getParens(); value=getImage().getHeight(); break;
                case RANDOM: value=random(); break;
                case GET_COUNT: case NRESULTS: value=getResultsCount(); break;
                case GET_RESULT: value=getResult(); break;
                case GET_NUMBER: value=getNumber(); break;
                case NIMAGES: value=getImageCount(); break;
                case NSLICES: value=getStackSize(); break;
                case LENGTH_OF: value=lengthOf(); break;
                case GET_ID: interp.getParens(); resetImage(); value=getImage().getID(); break;
                case BIT_DEPTH: interp.getParens(); value = getImage().getBitDepth(); break;
                case SELECTION_TYPE: value=getSelectionType(); break;
                case IS_OPEN: value=isOpen(); break;
                case IS_ACTIVE: value=isActive(); break;
                case INDEX_OF: value=indexOf(); break;
                case LAST_INDEX_OF: value=getFirstString().lastIndexOf(getLastString()); break;
                case CHAR_CODE_AT: value=getFirstString().charAt((int)getLastArg()); break;
                case GET_BOOLEAN: value=getBoolean(); break;
                case STARTS_WITH: case ENDS_WITH: value = startsWithEndsWith(type); break;
                case IS_NAN: value = Double.isNaN(getArg())?1:0; break;
                case GET_ZOOM: value = getZoom(); break;
                case PARSE_FLOAT: value = parseDouble(getStringArg()); break;
                case PARSE_INT: value = parseInt(); break;
                case IS_KEY_DOWN: value=isKeyDown(); break;
                case GET_SLICE_NUMBER: interp.getParens(); value=getImage().getCurrentSlice(); break;
                case SCREEN_WIDTH: case SCREEN_HEIGHT: value = getScreenDimension(type); break;
                case CALIBRATE: value = getImage().getCalibration().getCValue(getArg()); break;
                case ROI_MANAGER: value = roiManager(); break;
                case TOOL_ID: interp.getParens(); value = Toolbar.getToolId(); break;
                case IS: value = is(); break;
                default:
                    interp.error("Numeric function expected");
            }
            return value;
        }

        String getStringFunction(int type) {
            String str;
            switch (type) {
                case D2S: str = d2s(); break;
                case TO_HEX: str = toString(16); break;
                case TO_BINARY: str = toString(2); break;
                case GET_TITLE: interp.getParens(); resetImage(); str=getImage().getTitle(); break;
                case GET_STRING: str = getStringDialog(); break;
                case SUBSTRING: str = substring(); break;
                case FROM_CHAR_CODE: str = fromCharCode(); break;
                case GET_INFO: str = getInfo(); break;
                case GET_IMAGE_INFO: interp.getParens(); str = getImageInfo(); break;
                case GET_DIRECTORY: str = getDirectory(); break;
                case GET_ARGUMENT: interp.getParens(); str=interp.argument!=null?interp.argument:""; break;
                case TO_LOWER_CASE: str = getStringArg().toLowerCase(Locale.US); break;
                case TO_UPPER_CASE: str = getStringArg().toUpperCase(Locale.US); break;
                case RUN_MACRO: str = runMacro(false); break;
                case EVAL: str = runMacro(true); break;
                case TO_STRING: str = getStringArg(); break;
                case REPLACE: str = replace(); break;
                case DIALOG: str = doDialog(); break;
                case GET_METADATA: str = getMetadata(); break;
                case FILE: str = doFile(); break;
                case SELECTION_NAME: str = selectionName(); break;
                case GET_VERSION: interp.getParens();  str = IJ.getVersion(); break;
                case GET_RESULT_LABEL: str = getResultLabel(); break;
                case CALL: str = call(); break;
                case STRING: str = doString(); break;
                default:
                    str="";
                    interp.error("String function expected");
            }
            return str;
        }

        Variable[] getArrayFunction(int type) {
            Variable[] array;
            switch (type) {
                case GET_PROFILE: array=getProfile(); break;
                case NEW_ARRAY: array = newArray(); break;
                case SPLIT: array = split(); break;
                case GET_FILE_LIST: array = getFileList(); break;
                case GET_FONT_LIST: array = getFontList(); break;
                case NEW_MENU: array = newMenu(); break;
                case GET_LIST: array = getList(); break;
                default:
                    array = null;
                    interp.error("Array function expected");
            }
            return array;
        }

        final double math(int type) {
            double arg = getArg();
            switch (type) {
                case ABS: return Math.abs(arg);
                case COS: return Math.cos(arg);
                case EXP: return Math.exp(arg);
                case FLOOR: return Math.floor(arg);
                case LOG: return Math.log(arg);
                case ROUND: return Math.round(arg);
                case SIN: return Math.sin(arg);
                case SQRT: return Math.sqrt(arg);
                case TAN: return Math.tan(arg);
                case ATAN: return Math.atan(arg);
                case ASIN: return Math.asin(arg);
                case ACOS: return Math.acos(arg);
                default: return 0.0;
            }
        }

        final double math2(int type) {
            double a1 = getFirstArg();
            double a2 = getLastArg();
            switch (type) {
                case MIN_OF: return Math.min(a1, a2);
                case MAX_OF: return Math.max(a1, a2);
                case POW: return Math.pow(a1, a2);
                case ATAN2: return Math.atan2(a1, a2);
                default: return 0.0;
            }
        }

        final String getString() {
            String str = interp.getStringTerm();
            while (true) {
                interp.getToken();
                if (interp.token=='+')
                    str += interp.getStringTerm();
                else {
                    interp.putTokenBack();
                    break;
                }
            };
            return str;
        }

        final boolean isStringFunction() {
            Symbol symbol = pgm.table[interp.tokenAddress];
            return symbol.type==D2S;
        }

        final double getArg() {
            interp.getLeftParen();
            double arg = interp.getExpression();
            interp.getRightParen();
            return arg;
        }

        final double getFirstArg() {
            interp.getLeftParen();
            return interp.getExpression();
        }

        final double getNextArg() {
            interp.getComma();
            return interp.getExpression();
        }

        final double getLastArg() {
            interp.getComma();
            double arg = interp.getExpression();
            interp.getRightParen();
            return arg;
        }

        String getStringArg() {
            interp.getLeftParen();
            String arg = getString();
            interp.getRightParen();
            return arg;
        }

        final String getFirstString() {
            interp.getLeftParen();
            return getString();
        }

        final String getNextString() {
            interp.getComma();
            return getString();
        }

        final String getLastString() {
            interp.getComma();
            String arg = getString();
            interp.getRightParen();
            return arg;
        }

        boolean getBooleanArg() {
            interp.getLeftParen();
            double arg = interp.getBooleanExpression();
            interp.checkBoolean(arg);
            interp.getRightParen();
            return arg==0?false:true;
        }

        final Variable getFirstVariable() {
            interp.getLeftParen();
            return getVariable();
        }

        final Variable getNextVariable() {
            interp.getComma();
            return getVariable();
        }

        final Variable getLastVariable() {
            interp.getComma();
            Variable v = getVariable();
            interp.getRightParen();
            return v;
        }

        final Variable getVariable() {
            interp.getToken();
            if (interp.token!=WORD)
                interp.error("Variable expected");
            Variable v = interp.lookupLocalVariable(interp.tokenAddress);
            if (v==null)
                    v = interp.push(interp.tokenAddress, 0.0, null, interp);
            Variable[] array = v.getArray();
            if (array!=null) {
                int index = interp.getIndex();
                checkIndex(index, 0, array.length-1);
                v = array[index];
            }
            return v;
        }

        final Variable getFirstArrayVariable() {
            interp.getLeftParen();
            return getArrayVariable();
        }

        final Variable getNextArrayVariable() {
            interp.getComma();
            return getArrayVariable();
        }

        final Variable getLastArrayVariable() {
            interp.getComma();
            Variable v = getArrayVariable();
            interp.getRightParen();
            return v;
        }

        final Variable getArrayVariable() {
            interp.getToken();
            if (interp.token!=WORD)
                interp.error("Variable expected");
            Variable v = interp.lookupLocalVariable(interp.tokenAddress);
            if (v==null)
                    v = interp.push(interp.tokenAddress, 0.0, null, interp);
            return v;
        }

        final double[] getFirstArray() {
            interp.getLeftParen();
            return getNumericArray();
        }

        final double[] getNextArray() {
            interp.getComma();
            return getNumericArray();
        }

        final double[] getLastArray() {
            interp.getComma();
            double[] a = getNumericArray();
            interp.getRightParen();
            return a;
        }

        double[] getNumericArray() {
            Variable[] a1 = getArray();
            double[] a2 = new double[a1.length];
            for (int i=0; i<a1.length; i++)
                a2[i] = a1[i].getValue();
            return a2;
        }

        String[] getStringArray() {
            Variable[] a1 = getArray();
            String[] a2 = new String[a1.length];
            for (int i=0; i<a1.length; i++) {
                String s = a1[i].getString();
                if (s==null) s = "" + a1[i].getValue();
                a2[i] = s;
            }
            return a2;
        }

        Variable[] getArray() {
            interp.getToken();
            boolean newArray = interp.token==ARRAY_FUNCTION && pgm.table[interp.tokenAddress].type==NEW_ARRAY;
            if (!(interp.token==WORD||newArray))
                interp.error("Array expected");
            Variable[] a;
            if (newArray)
                a = getArrayFunction(NEW_ARRAY);
            else {
                Variable v = interp.lookupVariable();
                a= v.getArray();
            }
            if (a==null)
                interp.error("Array expected");
            return a;
        }

        Color getColor() {
            String color = getString();
            color = color.toLowerCase(Locale.US);
            if (color.equals("black"))
                return Color.black;
            else if (color.equals("white"))
                return Color.white;
            else if (color.equals("red"))
                return Color.red;
            else if (color.equals("green"))
                return Color.green;
            else if (color.equals("blue"))
                return Color.blue;
            else if (color.equals("cyan"))
                return Color.cyan;
            else if (color.equals("darkgray"))
                return Color.darkGray;
            else if (color.equals("gray"))
                return Color.gray;
            else if (color.equals("lightgray"))
                return Color.lightGray;
            else if (color.equals("magenta"))
                return Color.magenta;
            else if (color.equals("orange"))
                return Color.orange;
            else if (color.equals("yellow"))
                return Color.yellow;
            else if (color.equals("pink"))
                return Color.pink;
            else
                interp.error("'red', 'green', etc. expected");
            return null;
        }

        void checkIndex(int index, int lower, int upper) {
            if (index<lower || index>upper)
                interp.error("Index ("+index+") is outside of the "+lower+"-"+upper+" range");
        }

        void doRun() {
            interp.getLeftParen();
            String arg1 = getString();
            interp.getToken();
            if (!(interp.token==')' || interp.token==','))
                interp.error("',' or ')'  expected");
            String arg2 = null;
            if (interp.token==',') {
                arg2 = getString();
                interp.getRightParen();
            }
            if (arg2!=null)
                IJ.run(arg1, arg2);
            else
                IJ.run(arg1);
            resetImage();
            IJ.setKeyUp(IJ.ALL_KEYS);
            shiftKeyDown = altKeyDown = false;
        }

        void setForegroundColor() {
            IJ.setForegroundColor((int)getFirstArg(), (int)getNextArg(), (int)getLastArg());
            resetImage();
            defaultColor = null;
            defaultValue = Double.NaN;
        }

        void setBackgroundColor() {
            IJ.setBackgroundColor((int)getFirstArg(), (int)getNextArg(), (int)getLastArg());
            resetImage();
        }

        void setColor() {
            colorSet = true;
            interp.getLeftParen();
            if (isStringArg()) {
                defaultColor = getColor();
                getProcessor().setColor(defaultColor);
                defaultValue = Double.NaN;
                interp.getRightParen();
                return;
            }
            double arg1 = (int)interp.getExpression();
            if (interp.nextToken()==')')
                {interp.getRightParen(); setColor(arg1); return;}
            int red=(int)arg1, green=(int)getNextArg(), blue=(int)getLastArg();
            if (red<0) red=0; if (green<0) green=0; if (blue<0) blue=0;
            if (red>255) red=255; if (green>255) green=255; if (blue>255) blue=255;
            defaultColor = new Color(red, green, blue);
            getProcessor().setColor(defaultColor);
            defaultValue = Double.NaN;
        }

        void setColor(double value) {
            ImageProcessor ip = getProcessor();
            ImagePlus imp = getImage();
            switch (imp.getBitDepth()) {
                case 8:
                    if (value<0 || value>255)
                        interp.error("Argument out of 8-bit range (0-255)");
                    ip.setValue(value);
                    break;
                case 16:
                    if (imp.getLocalCalibration().isSigned16Bit())
                        value += 32768;
                    if (value<0 || value>65535)
                        interp.error("Argument out of 16-bit range (0-65535)");
                    ip.setValue(value);
                    break;
                default:
                    ip.setValue(value);
                    break;
            }
            defaultValue = value;
            defaultColor = null;
        }

        void makeLine() {
            int x1 = (int)Math.round(getFirstArg());
            int y1 = (int)Math.round(getNextArg());
            int x2 = (int)Math.round(getNextArg());
            interp.getComma();
            int y2 = (int)Math.round(interp.getExpression());
            interp.getToken();
            if (interp.token==')')
                IJ.makeLine(x1, y1, x2, y2);
            else {
                int max = 200;
                int[] x = new int[max];
                int[] y = new int[max];
                x[0]=x1; y[0]=y1; x[1]=x2; y[1]=y2;
                int n = 2;
                while (interp.token==',' && n<max) {
                    x[n] = (int)Math.round(interp.getExpression());
                    if (n==2 && interp.nextToken()==')') {
                        interp.getRightParen();
                        Line.setWidth((int)x[n]);
                        IJ.makeLine(x1, y1, x2, y2);
                        return;
                    }
                    interp.getComma();
                    y[n] = (int)Math.round(interp.getExpression());
                    interp.getToken();
                    n++;
                }
                if (n==max && interp.token!=')')
                    interp.error("More than "+max+" points");
                getImage().setRoi(new PolygonRoi(x, y, n, Roi.POLYLINE));
            }
            resetImage();
        }

        void makeOval() {
            Roi previousRoi = getImage().getRoi();
            if (shiftKeyDown||altKeyDown) getImage().saveRoi();
            IJ.makeOval((int)getFirstArg(), (int)getNextArg(), (int)getNextArg(), (int)getLastArg());
            Roi roi = getImage().getRoi();
            if (previousRoi!=null && roi!=null)
                updateRoi(roi);
            resetImage();
        }

        void makeRectangle() {
            Roi previousRoi = getImage().getRoi();
            if (shiftKeyDown||altKeyDown) getImage().saveRoi();
            IJ.makeRectangle((int)getFirstArg(), (int)getNextArg(), (int)getNextArg(), (int)getLastArg());
            Roi roi = getImage().getRoi();
            if (previousRoi!=null && roi!=null)
                updateRoi(roi);
            resetImage();
        }

        ImagePlus getImage() {
            if (defaultImp==null)
                defaultImp = IJ.getImage();
            if (defaultImp==null)
                {interp.error("No image"); return null;}
            if (defaultImp.getWindow()==null && IJ.getInstance()!=null && !interp.isBatchMode() && WindowManager.getTempCurrentImage()==null)
                throw new RuntimeException(Macro.MACRO_CANCELED);
            return defaultImp;
        }

        void resetImage() {
            defaultImp = null;
            defaultIP = null;
            colorSet = fontSet = false;
        }

        ImageProcessor getProcessor() {
            if (defaultIP==null) {
                defaultImp = getImage();
                defaultIP = defaultImp.getProcessor();
            }
            return defaultIP;
        }

        int getType() {
            if (defaultImp==null)
                defaultImp = IJ.getImage();
            imageType = defaultImp.getType();
            return imageType;
        }

        double getPixel() {
            interp.getLeftParen();
            int a1 = (int)interp.getExpression();
            interp.getComma();
            int a2 = (int)interp.getExpression();
            interp.getRightParen();
            double value = 0.0;
            ImageProcessor ip = getProcessor();
            if (getType()==ImagePlus.GRAY32)
                value = ip.getPixelValue(a1, a2);
            else
                value = ip.getPixel(a1, a2);
            return value;
        }

        void setZCoordinate() {
            int z = (int)getArg();
            ImagePlus imp = getImage();
            ImageStack stack = imp.getStack();
            int size = stack.getSize();
            if (z<0 || z>=size)
                interp.error("Z coordinate ("+z+") is out of 0-"+(size-1)+ " range");
            this.defaultIP = stack.getProcessor(z+1);
        }

        void setPixel() {
            interp.getLeftParen();
            int a1 = (int)interp.getExpression();
            interp.getComma();
            int a2 = (int)interp.getExpression();
            interp.getComma();
            double a3 = interp.getExpression();
            interp.getRightParen();
            if (getType()==ImagePlus.GRAY32)
                getProcessor().putPixelValue(a1, a2, a3);
            else
                getProcessor().putPixel(a1, a2, (int)a3);
            updateNeeded = true;
        }

        void moveTo() {
            interp.getLeftParen();
            int a1 = (int)(interp.getExpression()+0.5);
            interp.getComma();
            int a2 = (int)(interp.getExpression()+0.5);
            interp.getRightParen();
            getProcessor().moveTo(a1, a2);
        }

        void lineTo() {
            interp.getLeftParen();
            int a1 = (int)(interp.getExpression()+0.5);
            interp.getComma();
            int a2 = (int)(interp.getExpression()+0.5);
            interp.getRightParen();
            ImageProcessor ip = getProcessor();
            if (!colorSet) setForegroundColor(ip);
            ip.lineTo(a1, a2);
            updateAndDraw(defaultImp);
        }

        void drawLine() {
            interp.getLeftParen();
            int x1 = (int)(interp.getExpression()+0.5);
            interp.getComma();
            int y1 = (int)(interp.getExpression()+0.5);
            interp.getComma();
            int x2 = (int)(interp.getExpression()+0.5);
            interp.getComma();
            int y2 = (int)(interp.getExpression()+0.5);
            interp.getRightParen();
            ImageProcessor ip = getProcessor();
            if (!colorSet) setForegroundColor(ip);
            ip.drawLine(x1, y1, x2, y2);
            updateAndDraw(defaultImp);
        }

        void setForegroundColor(ImageProcessor ip) {
            if (defaultColor!=null)
                ip.setColor(defaultColor);
            else if (!Double.isNaN(defaultValue))
                ip.setValue(defaultValue);
            else
                ip.setColor(Toolbar.getForegroundColor());
            colorSet = true;
        }

        void doIPMethod(int type) {
            interp.getParens();
            ImageProcessor ip = getProcessor();
            switch (type) {
                case SNAPSHOT: ip.snapshot(); break;
                case RESET:
                    ip.reset();
                    updateNeeded = true;
                    break;
                case FILL:
                    ImagePlus imp = getImage();
                    Roi roi = imp.getRoi();
                    if (!colorSet) setForegroundColor(ip);
                    if (roi==null) {
                        ip.resetRoi();
                        ip.fill();
                    } else {
                        ip.setRoi(roi);
                        ip.fill(ip.getMask());
                    }
                    updateAndDraw(imp);
                    break;
            }
        }

        void updateAndDraw(ImagePlus imp) {
            if (autoUpdate)
                imp.updateChannelAndDraw();
            else
                updateNeeded = true;
        }

        void updateDisplay() {
            if (updateNeeded && WindowManager.getImageCount()>0) {
                ImagePlus imp = getImage();
                imp.updateAndDraw();
                updateNeeded = false;
            }
        }

        void drawString() {
            interp.getLeftParen();
            String str = getString();
            interp.getComma();
            int x = (int)(interp.getExpression()+0.5);
            interp.getComma();
            int y = (int)(interp.getExpression()+0.5);
            interp.getRightParen();
            ImageProcessor ip = getProcessor();
            if (!colorSet)
                setForegroundColor(ip);
            setFont(ip);
            ip.setJustification(justification);
            ip.setAntialiasedText(antialiasedText);
            ip.drawString(str, x, y);
            updateAndDraw(defaultImp);
        }

        void setFont(ImageProcessor ip) {
            if (font!=null && !fontSet)
                ip.setFont(font);
            fontSet = true;
        }

        void setJustification() {
            String str = getStringArg().toLowerCase(Locale.US);
            int just = ImageProcessor.LEFT_JUSTIFY;
            if (str.equals("center"))
                just = ImageProcessor.CENTER_JUSTIFY;
            else if (str.equals("right"))
                just = ImageProcessor.RIGHT_JUSTIFY;
            justification = just;
        }

        void changeValues() {
            double darg1 = getFirstArg();
            double darg2 = getNextArg();
            double darg3 = getLastArg();
            ImagePlus imp = getImage();
            ImageProcessor ip = getProcessor();
            Roi roi = imp.getRoi();
            ImageProcessor mask = null;
            if (roi==null || !roi.isArea()) {
                ip.resetRoi();
                roi = null;
            } else {
                ip.setRoi(roi);
                mask = ip.getMask();
                if (mask!=null) ip.snapshot();
            }
            int xmin=0, ymin=0, xmax=imp.getWidth(), ymax=imp.getHeight();
            if (roi!=null) {
                Rectangle r = roi.getBounds();
                xmin=r.x; ymin=r.y; xmax=r.x+r.width; ymax=r.y+r.height;
            }
            boolean isFloat = getType()==ImagePlus.GRAY32;
            if (imp.getBitDepth()==24) {
                darg1 = (int)darg1&0xffffff;
                darg2 = (int)darg2&0xffffff;
            }
            double v;
            for (int y=ymin; y<ymax; y++) {
                for (int x=xmin; x<xmax; x++) {
                    v = isFloat?ip.getPixelValue(x,y):ip.getPixel(x,y)&0xffffff;
                    if (v>=darg1 && v<=darg2) {
                        if (isFloat)
                            ip.putPixelValue(x, y, darg3);
                        else
                            ip.putPixel(x, y, (int)darg3);
                    }
                }
            }
            if (mask!=null) ip.reset(mask);
            if (imp.getType()==ImagePlus.GRAY16 || imp.getType()==ImagePlus.GRAY32)
                ip.resetMinAndMax();
            imp.updateAndDraw();
            updateNeeded = false;
        }

        void requires() {
            if (IJ.versionLessThan(getStringArg()))
                interp.done = true;
        }

        Random ran;
        double random() {
            interp.getParens();
            if (ran==null)
                ran = new Random();
            return ran.nextDouble();
        }

        //void setSeed() {
        //  long seed = (long)getArg();
        //  if (ran==null)
        //      ran = new Random(seed);
        //  else
        //      ran.setSeed(seed);
        //}

        double getResult() {
            interp.getLeftParen();
            String column = getString();
            int row = -1;
            if (interp.nextNonEolToken()==',') {
                interp.getComma();
                row = (int)interp.getExpression();
            }
            interp.getRightParen();
            ResultsTable rt = Analyzer.getResultsTable();
            int counter = rt.getCounter();
            if (counter==0)
                interp.error("\"Results\" table empty");
            if (row==-1) row = counter-1;
            if (row<0 || row>=counter)
                interp.error("Row ("+row+") out of range");
            int col = rt.getColumnIndex(column);
            if (!rt.columnExists(col))
                return Double.NaN;
            else
                return rt.getValueAsDouble(col, row);
        }

        String getResultLabel() {
            int row = (int)getArg();
            ResultsTable rt = Analyzer.getResultsTable();
            int counter = rt.getCounter();
            if (counter==0)
                interp.error("\"Results\" table empty");
            if (row<0 || row>=counter)
                interp.error("Row ("+row+") out of range");
            String label = rt.getLabel(row);
            return label!=null?label:"";
        }

        void setResult() {
            interp.getLeftParen();
            String column = getString();
            interp.getComma();
            int row = (int)interp.getExpression();
            interp.getComma();
            double value = 0.0;
            String label = null;
            if (column.equals("Label"))
                label = getString();
            else
                value = interp.getExpression();
            interp.getRightParen();
            ResultsTable rt = Analyzer.getResultsTable();
            if (row<0 || row>rt.getCounter())
                interp.error("Row ("+row+") out of range");
            if (row==rt.getCounter())
                rt.incrementCounter();
            try {
                if (label!=null)
                    rt.setLabel(label, row);
                else
                    rt.setValue(column, row, value);
            } catch (Exception e) {
                interp.error(""+e.getMessage());
            }
        }

        void updateResults() {
            interp.getParens();
            ResultsTable rt = Analyzer.getResultsTable();
            rt.show("Results");
        }

        double getNumber() {
            String prompt = getFirstString();
            double defaultValue = getLastArg();
            String title = interp.macroName!=null?interp.macroName:"";
            if (title.endsWith(" Options"))
                title = title.substring(0, title.length()-8);
            GenericDialog gd = new GenericDialog(title);
            int decimalPlaces = (int)defaultValue==defaultValue?0:2;
            gd.addNumericField(prompt, defaultValue, decimalPlaces);
            gd.showDialog();
            if (gd.wasCanceled()) {
                interp.done = true;
                return defaultValue;
            }
            double v = gd.getNextNumber();
            if (gd.invalidNumber())
                return defaultValue;
            else
                return v;
        }

        double getBoolean() {
            String prompt = getStringArg();
            String title = interp.macroName!=null?interp.macroName:"";
            if (title.endsWith(" Options"))
                title = title.substring(0, title.length()-8);
            YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), title, prompt);
            if (d.cancelPressed()) {
                interp.done = true;
                return 0.0;
            } else if (d.yesPressed())
                return 1.0;
            else
                return 0.0;
        }

        double getBoolean2() {
            String prompt = getFirstString();
            interp.getComma();
            double defaultValue = interp.getBooleanExpression();
            interp.checkBoolean(defaultValue);
            interp.getRightParen();
            String title = interp.macroName!=null?interp.macroName:"";
            if (title.endsWith(" Options"))
                title = title.substring(0, title.length()-8);
            GenericDialog gd = new GenericDialog(title);
            gd.addCheckbox(prompt, defaultValue==1.0?true:false);
            gd.showDialog();
            if (gd.wasCanceled()) {
                interp.done = true;
                return 0.0;
            }
            return gd.getNextBoolean()?1.0:0.0;
        }

        String getStringDialog() {
            interp.getLeftParen();
            String prompt = getString();
            interp.getComma();
            String defaultStr = getString();
            interp.getRightParen();

            String title = interp.macroName!=null?interp.macroName:"";
            if (title.endsWith(" Options"))
                title = title.substring(0, title.length()-8);
            GenericDialog gd = new GenericDialog(title);
            gd.addStringField(prompt, defaultStr, 20);
            gd.showDialog();
            String str = "";
            if (gd.wasCanceled())
                interp.done = true;
            else
                str = gd.getNextString();
            return str;
        }

        String d2s() {
            return IJ.d2s(getFirstArg(), (int)getLastArg());
        }

        String toString(int base) {
            int arg = (int)getArg();
            if (base==2)
                return Integer.toBinaryString(arg);
            else
                return Integer.toHexString(arg);
        }

        double getStackSize() {
            interp.getParens();
            return getImage().getStackSize();
        }

        double getImageCount() {
            interp.getParens();
            return WindowManager.getImageCount();
        }

        double getResultsCount() {
            interp.getParens();
            return Analyzer.getResultsTable().getCounter();
        }

        void getCoordinates() {
            Variable xCoordinates = getFirstArrayVariable();
            Variable yCoordinates = getLastArrayVariable();
            resetImage();
            ImagePlus imp = getImage();
            Roi roi = imp.getRoi();
            if (roi==null)
                interp.error("Selection required");
            Polygon p = roi.getPolygon();
            Variable[] xa = new Variable[p.npoints];
            for (int i=0; i<p.npoints; i++)
                xa[i] = new Variable(p.xpoints[i]);
            xCoordinates.setArray(xa);
            Variable[] ya = new Variable[p.npoints];
            for (int i=0; i<p.npoints; i++)
                ya[i] = new Variable(p.ypoints[i]);
            yCoordinates.setArray(ya);
        }

        Variable[] getProfile() {
            interp.getParens();
            ImagePlus imp = getImage();
            ProfilePlot pp = new ProfilePlot(imp, IJ.altKeyDown());
            double[] array = pp.getProfile();
            if (array==null)
                {interp.done=true; return null;}
            else
                return new Variable(array).getArray();
        }

        Variable[] newArray() {
            interp.getLeftParen();
            int next = interp.nextNonEolToken();
            if (next==STRING_CONSTANT || interp.nextNextNonEolToken()==','
            || next=='-' || next==PI)
                return initNewArray();
            int size = (int)interp.getExpression();
            interp.getRightParen();
            Variable[] array = new Variable[size];
            for (int i=0; i<size; i++)
                array[i] = new Variable();
            return array;
        }

        Variable[] split() {
            String s1 = getFirstString();
            String s2 = null;
            if (interp.nextToken()==')')
                interp.getRightParen();
            else
                s2 = getLastString();
            if (s1==null) return null;
            String[] strings = (s2==null||s2.equals(""))?Tools.split(s1):Tools.split(s1, s2);
            Variable[] array = new Variable[strings.length];
            for (int i=0; i<strings.length; i++)
                array[i] = new Variable(0, 0.0, strings[i]);
            return array;
        }

        Variable[] getFileList() {
            String dir = getStringArg();
            File f = new File(dir);
            if (!f.exists() || !f.isDirectory())
                return new Variable[0];
            String[] list = f.list();
            if (list==null)
                return new Variable[0];
            if (System.getProperty("os.name").indexOf("Linux")!=-1)
                ij.util.StringSorter.sort(list);
            File f2;
            int hidden = 0;
            for (int i=0; i<list.length; i++) {
                if (list[i].startsWith(".")) {
                    list[i] = null;
                    hidden++;
                } else {
                    f2 = new File(dir, list[i]);
                    if (f2.isDirectory())
                        list[i] = list[i] + "/";
                }
            }
            int n = list.length-hidden;
            if (n<=0)
                return new Variable[0];
            if (hidden>0) {
                String[] list2 = new String[n];
                int j = 0;
                for (int i=0; i<list.length; i++) {
                    if (list[i]!=null)
                        list2[j++] = list[i];
                }
                list = list2;
            }
            Variable[] array = new Variable[n];
            for (int i=0; i<n; i++)
                array[i] = new Variable(0, 0.0, list[i]);
            return array;
        }

        Variable[] initNewArray() {
            Vector vector = new Vector();
            int size = 0;
            do {
                Variable v = new Variable();
                if (interp.nextNonEolToken()==STRING_CONSTANT)
                    v.setString(getString());
                else
                    v.setValue(interp.getExpression());
                vector.addElement(v);
                size++;
                interp.getToken();
            } while (interp.token==',');
            if (interp.token!=')')
                interp.error("';' expected");
            Variable[] array = new Variable[size];
            vector.copyInto((Variable[])array);
            return array;
        }

        String fromCharCode() {
            char[] chars = new char[100];
            int count = 0;
            interp.getLeftParen();
            while(interp.nextToken()!=')') {
                int value = (int)interp.getExpression();
                if (value<0 || value>65535)
                    interp.error("Value (" + value + ") out of 0-65535 range");
                chars[count++] = (char)value;
                if (interp.nextToken()==',')
                    interp.getToken();
            }
            interp.getRightParen();
            return new String(chars, 0, count);
        }

        String getInfo() {
            if (interp.nextNextNonEolToken()==STRING_CONSTANT
            || (interp.nextNonEolToken()=='('&&interp.nextNextNonEolToken()!=')'))
                return getInfo(getStringArg());
            else {
                interp.getParens();
                return getWindowContents();
            }
        }

        String getInfo(String key) {
                if (key.equals("image.subtitle")) {
                    ImagePlus imp = getImage();
                    ImageWindow win = imp.getWindow();
                    return win!=null?win.createSubtitle():"";
                } else if (key.equals("slice.label")) {
                    ImagePlus imp = getImage();
                    if (imp.getStackSize()==1) return "";
                    String label = imp.getStack().getShortSliceLabel(imp.getCurrentSlice());
                    return label!=null?label:"";
                } else if (key.equals("window.contents")) {
                    return getWindowContents();
                } else {
                    String value = "";
                    try {value = System.getProperty(key);}
                    catch (Exception e) {};
                    return value!=null?value:"";
                }
        }

        String getWindowContents() {
            Frame frame = WindowManager.getFrontWindow();
            if (frame!=null && frame instanceof TextWindow) {
                TextPanel tp = ((TextWindow)frame).getTextPanel();
                return tp.getText();
            } else if (frame!=null && frame instanceof Editor) {
                return ((Editor)frame).getText();
            } else
                return getImageInfo();
        }

        String getImageInfo() {
            ImagePlus imp = getImage();
            Info infoPlugin = new Info();
            return infoPlugin.getImageInfo(imp, getProcessor());
        }

        public String getDirectory() {
            String dir = IJ.getDirectory(getStringArg());
            if (dir==null) dir = "";
            return dir;
        }

        double getSelectionType() {
            interp.getParens();
            double type = -1;
            ImagePlus imp = getImage();
            Roi roi = imp.getRoi();
            if (roi!=null)
                type = roi.getType();
            return type;
        }

        void showMessage(boolean withCancel) {
            String message;
            interp.getLeftParen();
            String title = getString();
            if (interp.nextToken()==',') {
                interp.getComma();
                message = getString();
            } else {
                message = title;
                title = "";
            }
            interp.getRightParen();
            if (withCancel)
                IJ.showMessageWithCancel(title, message);
            else
                IJ.showMessage(title, message);
        }

        double lengthOf() {
            int length = 0;
            interp.getLeftParen();
            switch (interp.nextToken()) {
                case STRING_CONSTANT:
                case STRING_FUNCTION:
                case USER_FUNCTION:
                    length = getString().length();
                    break;
                case WORD:
                    if (pgm.code[interp.pc+2]=='[') {
                        length = getString().length();
                        break;
                    }
                    interp.getToken();
                    Variable v = interp.lookupVariable();
                    if (v==null) return 0.0;
                    String s = v.getString();
                    if (s!=null)
                        length = s.length();
                    else {
                        Variable[] array = v.getArray();
                        if (array!=null)
                            length = array.length;
                        else
                            interp.error("String or array expected");
                    }
                    break;
                default:
                    interp.error("String or array expected");
            }
            interp.getRightParen();
            return length;
        }

        void getCursorLoc() {
            Variable x = getFirstVariable();
            Variable y = getNextVariable();
            Variable z = getNextVariable();
            Variable flags = getLastVariable();
            ImagePlus imp = getImage();
            ImageCanvas ic = imp.getCanvas();
            if (ic==null) return;
            Point p = ic.getCursorLoc();
            x.setValue(p.x);
            y.setValue(p.y);
            z.setValue(imp.getCurrentSlice()-1);
            flags.setValue(ic.getModifiers());
        }

        void getLine() {
            Variable vx1 = getFirstVariable();
            Variable vy1 = getNextVariable();
            Variable vx2 = getNextVariable();
            Variable vy2 = getNextVariable();
            Variable lineWidth = getLastVariable();
            resetImage();
            ImagePlus imp = getImage();
            double x1=-1, y1=-1, x2=-1, y2=-1;
            Roi roi = imp.getRoi();
            if (roi!=null && roi.getType()==Roi.LINE) {
                Line line = (Line)roi;
                x1=line.x1d; y1=line.y1d; x2=line.x2d; y2=line.y2d;
            }
            vx1.setValue(x1);
            vy1.setValue(y1);
            vx2.setValue(x2);
            vy2.setValue(y2);
            lineWidth.setValue(Line.getWidth());
        }

        void getVoxelSize() {
            Variable width = getFirstVariable();
            Variable height = getNextVariable();
            Variable depth = getNextVariable();
            Variable unit = getLastVariable();
            resetImage();
            ImagePlus imp = getImage();
            Calibration cal = imp.getCalibration();
            width.setValue(cal.pixelWidth);
            height.setValue(cal.pixelHeight);
            depth.setValue(cal.pixelDepth);
            unit.setString(cal.getUnits());
        }

        void getHistogram() {
            interp.getLeftParen();
            Variable values = null;
            if (interp.nextToken()==NUMBER)
                interp.getExpression();
            else
                values = getArrayVariable();
            Variable counts = getNextArrayVariable();
            interp.getComma();
            int nBins = (int)interp.getExpression();
            ImagePlus imp = getImage();
            double histMin=0.0, histMax=0.0;
            boolean setMinMax = false;
            int bitDepth = imp.getBitDepth();
            if (interp.nextToken()==',') {
                histMin = getNextArg();
                histMax = getLastArg();
                if (bitDepth==8 || bitDepth==24)
                    interp.error("16 or 32-bit image required to set histMin and histMax");
                setMinMax = true;
            } else
                interp.getRightParen();
            if ((bitDepth==8||bitDepth==24) && nBins!=256)
                interp.error("Bin count ("+nBins+") must be 256 for 8-bit and RGB images");
            if (nBins==65536 && bitDepth==16) {
                Variable[] array = counts.getArray();
                int[] hist = getProcessor().getHistogram();
                if (array!=null && array.length==nBins) {
                    for (int i=0; i<nBins; i++)
                        array[i].setValue(hist[i]);
                } else
                    counts.setArray(new Variable(hist).getArray());
                return;
            }
            ImageStatistics stats;
            if (setMinMax)
                stats = imp.getStatistics(AREA+MEAN+MODE+MIN_MAX, nBins, histMin, histMax);
            else
                stats = imp.getStatistics(AREA+MEAN+MODE+MIN_MAX, nBins);
            if (values!=null) {
                Calibration cal = imp.getCalibration();
                double[] array = new double[nBins];
                double value = cal.getCValue(stats.histMin);
                double inc = 1.0;
                if (bitDepth==16 || bitDepth==32 || cal.calibrated())
                    inc = (cal.getCValue(stats.histMax) - cal.getCValue(stats.histMin))/stats.nBins;
                for (int i=0; i<nBins; i++) {
                    array[i] = value;
                    value += inc;
                }
                values.setArray(new Variable(array).getArray());
            }
            Variable[] array = counts.getArray();
            if (array!=null && array.length==nBins) {
                for (int i=0; i<nBins; i++)
                    array[i].setValue(stats.histogram[i]);
            } else
                counts.setArray(new Variable(stats.histogram).getArray());
        }

        void getLut() {
            Variable reds = getFirstArrayVariable();
            Variable greens = getNextArrayVariable();
            Variable blues = getLastArrayVariable();
            resetImage();
            ImageProcessor ip = getProcessor();
            if (ip instanceof ColorProcessor)
                interp.error("Non-RGB image expected");
            IndexColorModel cm = (IndexColorModel)ip.getColorModel();
            int mapSize = cm.getMapSize();
            byte[] rLUT = new byte[mapSize];
            byte[] gLUT = new byte[mapSize];
            byte[] bLUT = new byte[mapSize];
            cm.getReds(rLUT);
            cm.getGreens(gLUT);
            cm.getBlues(bLUT);
            reds.setArray(new Variable(rLUT).getArray());
            greens.setArray(new Variable(gLUT).getArray());
            blues.setArray(new Variable(bLUT).getArray());
        }

        void setLut() {
            double[] reds = getFirstArray();
            double[] greens = getNextArray();
            double[] blues = getLastArray();
            int length = reds.length;
            if (greens.length!=length || blues.length!=length)
                interp.error("Arrays are not the same length");
            resetImage();
            ImagePlus imp = getImage();
            if (imp.getBitDepth()==24)
                interp.error("Non-RGB image expected");
            ImageProcessor ip = getProcessor();
            byte[] r = new byte[length];
            byte[] g = new byte[length];
            byte[] b = new byte[length];
            for (int i=0; i<length; i++) {
                r[i] = (byte)reds[i];
                g[i] = (byte)greens[i];
                b[i] = (byte)blues[i];
            }
            ip.setColorModel(new IndexColorModel(8, length, r, g, b));
            imp.updateAndDraw();
            updateNeeded = false;
        }

        void getThreshold() {
            Variable lower = getFirstVariable();
            Variable upper = getLastVariable();
            ImagePlus imp = getImage();
            ImageProcessor ip = getProcessor();
            double t1 = ip.getMinThreshold();
            double t2 = ip.getMaxThreshold();
            if (t1==ImageProcessor.NO_THRESHOLD) {
                t1 = -1;
                t2 = -1;
            } else if (imp.getBitDepth()==16) {
                Calibration cal = imp.getCalibration();
                t1 = cal.getCValue(t1);
                t2 = cal.getCValue(t2);
            }

            lower.setValue(t1);
            upper.setValue(t2);
        }

        void getPixelSize() {
            Variable unit = getFirstVariable();
            Variable width = getNextVariable();
            Variable height = getNextVariable();
            Variable depth = null;
            if (interp.nextToken()==',')
                depth = getNextVariable();
            interp.getRightParen();
            Calibration cal = getImage().getCalibration();
            unit.setString(cal.getUnits());
            width.setValue(cal.pixelWidth);
            height.setValue(cal.pixelHeight);
            if (depth!=null)
            depth.setValue(cal.pixelDepth);
        }

        void makeSelection() {
            String type = null;
            int roiType = -1;
            interp.getLeftParen();
            if (isStringArg()) {
                type = getString().toLowerCase();
                roiType = Roi.POLYGON;
                if (type.indexOf("free")!=-1)
                    roiType = Roi.FREEROI;
                if (type.indexOf("traced")!=-1)
                    roiType = Roi.TRACED_ROI;
                if (type.indexOf("line")!=-1) {
                    if (type.indexOf("free")!=-1)
                        roiType = Roi.FREELINE;
                    else
                        roiType = Roi.POLYLINE;
                }
                if (type.indexOf("angle")!=-1)
                    roiType = Roi.ANGLE;
                if (type.indexOf("point")!=-1)
                    roiType = Roi.POINT;
            } else {
                roiType = (int)interp.getExpression();
                if (roiType<0 || roiType==Roi.COMPOSITE)
                    interp.error("Invalid selection type ("+roiType+")");
                if (roiType==Roi.RECTANGLE) roiType = Roi.POLYGON;
                if (roiType==Roi.OVAL) roiType = Roi.FREEROI;
            }
            double[] x = getNextArray();
            double[] y = getLastArray();
            int n = x.length;
            if (y.length!=n)
                interp.error("Arrays are not the same length");
            resetImage();
            ImagePlus imp = getImage();
            int[] xcoord = new int[n];
            int[] ycoord = new int[n];
            int height = imp.getHeight();
            for (int i=0; i<n; i++) {
                xcoord[i] = (int)Math.round(x[i]);
                ycoord[i] = (int)Math.round(y[i]);
            }
            Roi roi = null;
            if (roiType==Roi.LINE) {
                if (xcoord.length!=2)
                    interp.error("2 element arrays expected");
                roi = new Line(xcoord[0], ycoord[0], xcoord[1], ycoord[1]);
            } else if (roiType==Roi.POINT)
                roi = new PointRoi(xcoord, ycoord, n);
            else
                roi = new PolygonRoi(xcoord, ycoord, n, roiType);
            Roi previousRoi = imp.getRoi();
            if (shiftKeyDown||altKeyDown) imp.saveRoi();
            imp.setRoi(roi);
            if (roiType==Roi.POLYGON || roiType==Roi.FREEROI) {
                roi = imp.getRoi();
                if (previousRoi!=null && roi!=null)
                    updateRoi(roi);
            }
            updateNeeded = false;
        }

        void doPlot() {
            interp.getToken();
            if (interp.token!='.')
                interp.error("'.' expected");
            interp.getToken();
            if (!(interp.token==WORD || interp.token==PREDEFINED_FUNCTION))
                interp.error("Function name expected: ");
            String name = interp.tokenString;
            if (name.equals("create")) {
                newPlot();
                return;
            }
            if (plot==null)
                interp.error("No plot defined");
            if (name.equals("show")) {
                showPlot();
                return;
            } else if (name.equals("update")) {
                updatePlot();
                return;
            } else if (name.equals("setLimits")) {
                plot.setLimits(getFirstArg(), getNextArg(), getNextArg(), getLastArg());
                return;
            } else if (name.equals("addText") || name.equals("drawLabel")) {
                addPlotText();
                return;
            } else if (name.equals("setColor")) {
                setPlotColor();
                return;
            } else if (name.equals("add")) {
                String arg = getFirstString();
                arg = arg.toLowerCase(Locale.US);
                int what = Plot.CIRCLE;
                if (arg.indexOf("curve")!=-1 || arg.indexOf("line")!=-1)
                    what = Plot.LINE;
                else if (arg.indexOf("box")!=-1)
                    what = Plot.BOX;
                else if (arg.indexOf("triangle")!=-1)
                    what = Plot.TRIANGLE;
                else if (arg.indexOf("cross")!=-1)
                    what = Plot.CROSS;
                else if (arg.indexOf("dot")!=-1)
                    what = Plot.DOT;
                else if (arg.indexOf("x")!=-1)
                    what = Plot.X;
                else if (arg.indexOf("error")!=-1)
                    what = -1;
                addToPlot(what);
                return;
            } else if (name.startsWith("setLineWidth")) {
                plot.setLineWidth((int)getArg());
                return;
            } else if (name.startsWith("setJustification")) {
                doFunction(SET_JUSTIFICATION);
                return;
            } else
                interp.error("Unrecognized plot function");
        }

        void newPlot() {
            String title = getFirstString();
            String xLabel = getNextString();
            String yLabel = getNextString();
            double[] x, y;
            if (interp.nextToken()==')')
                x = y = null;
            else {
                x = getNextArray();
                if (interp.nextToken()==')') {
                    y = x;
                    x = new double[y.length];
                    for (int i=0; i<y.length; i++)
                        x[i] = i;
                } else
                    y = getNextArray();
            }
            interp.getRightParen();
            plot = new Plot(title, xLabel, yLabel, x, y);
        }

        void showPlot() {
            if (plot!=null) {
                PlotWindow plotWindow = plot.show();
                if (plotWindow!=null)
                    plotID = plotWindow.getImagePlus().getID();
            }
            plot = null;
            interp.getParens();
        }

        void updatePlot() {
            if (plot!=null) {
                ImagePlus plotImage = WindowManager.getImage(plotID);
                ImageWindow win = plotImage!=null?plotImage.getWindow():null;
                if (win!=null)
                    ((PlotWindow)win).drawPlot(plot);
                else {
                    PlotWindow plotWindow = plot.show();
                    plotID = plotWindow.getImagePlus().getID();
                }
            }
            plot = null;
            interp.getParens();
        }

        void addPlotText() {
            String str = getFirstString();
            double x = getNextArg();
            double y = getLastArg();
            plot.setJustification(justification);
            plot.addLabel(x, y, str);
        }

        void setPlotColor() {
            interp.getLeftParen();
            plot.setColor(getColor());
            interp.getRightParen();
        }

        void addToPlot(int what) {
            double[] x = getNextArray();
            double[] y;
            if (interp.nextToken()==')') {
                y = x;
                x = new double[y.length];
                for (int i=0; i<y.length; i++)
                    x[i] = i;
            } else {
                interp.getComma();
                y = getNumericArray();
            }
            interp.getRightParen();
            if (what==-1)
                plot.addErrorBars(y);
            else
                plot.addPoints(x, y, what);
        }

        void getBounds() {
            Variable x = getFirstVariable();
            Variable y = getNextVariable();
            Variable width = getNextVariable();
            Variable height = getLastVariable();
            resetImage();
            ImagePlus imp = getImage();
            Roi roi = imp.getRoi();
            if (roi!=null) {
                Rectangle r = roi.getBounds();
                x.setValue(r.x);
                y.setValue(r.y);
                width.setValue(r.width);
                height.setValue(r.height);
            } else {
                x.setValue(0);
                y.setValue(0);
                width.setValue(imp.getWidth());
                height.setValue(imp.getHeight());
            }
        }

        String substring() {
            String s = getFirstString();
            int index1 = (int)getNextArg();
            int index2 = (int)getLastArg();
            if (index1>index2)
                interp.error("beginIndex>endIndex");
            checkIndex(index1, 0, s.length());
            checkIndex(index2, 0, s.length());
            return s.substring(index1, index2);
        }

        int indexOf() {
            String s1 = getFirstString();
            String s2 = getNextString();
            int fromIndex = 0;
            if (interp.nextToken()==',') {
                fromIndex = (int)getLastArg();
                checkIndex(fromIndex, 0, s1.length()-1);
            } else
                interp.getRightParen();
            if (fromIndex==0)
                return s1.indexOf(s2);
            else
                return s1.indexOf(s2, fromIndex);
        }

        int startsWithEndsWith(int type) {
            String s1 = getFirstString();
            String s2 = getLastString();
            if (type==STARTS_WITH)
                return s1.startsWith(s2)?1:0;
            else
                return s1.endsWith(s2)?1:0;
        }

        double isActive() {
            int id = (int)getArg();
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp==null || imp.getID()!=id)
                return 0.0; //false
            else
                return 1.0; //true
        }

        double isOpen() {
            interp.getLeftParen();
            if (isStringArg()) {
                String title = getString();
                interp.getRightParen();
                boolean open = WindowManager.getFrame(title)!=null;
                if (open)
                    return 1.0;
                else if (Interpreter.isBatchMode() && Interpreter.imageTable!=null) {
                    for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
                        ImagePlus imp = (ImagePlus)en.nextElement();
                        if (imp!=null && imp.getTitle().equals(title))
                            return 1.0;
                    }
                }
                return 0.0;
            } else {
                int id = (int)interp.getExpression();
                interp.getRightParen();
                return WindowManager.getImage(id)==null?0.0:1.0;
            }
        }

        boolean isStringArg() {
            int nextToken = pgm.code[interp.pc+1];
            int tok = nextToken&0xff;
            if (tok==STRING_CONSTANT||tok==STRING_FUNCTION) return true;
            if (tok!=WORD) return false;
            Variable v = interp.lookupVariable(nextToken>>TOK_SHIFT);
            if (v==null) return false;
            int type = v.getType();
            if (type!=Variable.ARRAY)
                return v.getType()==Variable.STRING;
            Variable[] array = v.getArray();
            if (array.length==0) return false;
            return array[0].getType()==Variable.STRING;
        }

        void exit() {
            String msg = null;
            if (interp.nextToken()=='(') {
                interp.getLeftParen();
                if (isStringArg())
                    msg = getString();
                interp.getRightParen();
            }
            interp.finishUp();
            if (msg!=null)
                IJ.showMessage("Macro", msg);
            throw new RuntimeException(Macro.MACRO_CANCELED);
        }

        void showProgress() {
            ij.gui.ProgressBar progressBar = IJ.getInstance().getProgressBar();
            interp.getLeftParen();
            double arg1 = interp.getExpression();
            if (interp.nextToken()==',') {
                interp.getComma();
                double arg2 = interp.getExpression();
                if (progressBar!=null) progressBar.show((arg1+1.0)/arg2, true);
            } else
                if (progressBar!=null) progressBar.show(arg1, true);
            interp.getRightParen();
            interp.showingProgress = true;
        }

        void saveSettings() {
            interp.getParens();
            usePointerCursor = Prefs.usePointerCursor;
            hideProcessStackDialog = IJ.hideProcessStackDialog;
            divideByZeroValue = FloatBlitter.divideByZeroValue;
            jpegQuality = JpegWriter.getQuality();
            lineWidth = Line.getWidth();
            doScaling = ImageConverter.getDoScaling();
            weightedColor = Prefs.weightedColor;
            weights = ColorProcessor.getWeightingFactors();
            interpolateScaledImages = Prefs.interpolateScaledImages;
            open100Percent = Prefs.open100Percent;
            blackCanvas = Prefs.blackCanvas;
            useJFileChooser = Prefs.useJFileChooser;
            debugMode = IJ.debugMode;
            foregroundColor =Toolbar.getForegroundColor();
            backgroundColor =Toolbar.getBackgroundColor();
            roiColor = Roi.getColor();
            pointAutoMeasure = Prefs.pointAutoMeasure;
            requireControlKey = Prefs.requireControlKey;
            useInvertingLut = Prefs.useInvertingLut;
            doubleBuffer = Prefs.doubleBuffer;
            saveSettingsCalled = true;
            measurements = Analyzer.getMeasurements();
            decimalPlaces = Analyzer.getPrecision();
            blackBackground = Prefs.blackBackground;
        }

        void restoreSettings() {
            interp.getParens();
            if (!saveSettingsCalled)
                interp.error("saveSettings() not called");
            Prefs.usePointerCursor = usePointerCursor;
            IJ.hideProcessStackDialog = hideProcessStackDialog;
            FloatBlitter.divideByZeroValue = divideByZeroValue;
            JpegWriter.setQuality(jpegQuality);
            Line.setWidth(lineWidth);
            ImageConverter.setDoScaling(doScaling);
            if (weightedColor!=Prefs.weightedColor) {
                ColorProcessor.setWeightingFactors(weights[0], weights[1], weights[2]);
                Prefs.weightedColor = !(weights[0]==1d/3d && weights[1]==1d/3d && weights[2]==1d/3d);
            }
            Prefs.interpolateScaledImages = interpolateScaledImages;
            Prefs.open100Percent = open100Percent;
            Prefs.blackCanvas = blackCanvas;
            Prefs.useJFileChooser = useJFileChooser;
            Prefs.useInvertingLut = useInvertingLut;
            Prefs.doubleBuffer = doubleBuffer;
            IJ.debugMode = debugMode;
            Toolbar.setForegroundColor(foregroundColor);
            Toolbar.setBackgroundColor(backgroundColor);
            Roi.setColor(roiColor);
            Analyzer.setMeasurements(measurements);
            Analyzer.setPrecision(decimalPlaces);
            ColorProcessor.setWeightingFactors(weights[0], weights[1], weights[2]);
            Prefs.blackBackground = blackBackground;
        }

        void setKeyDown() {
            String keys = getStringArg();
            keys = keys.toLowerCase(Locale.US);
            altKeyDown = keys.indexOf("alt")!=-1;
            if (altKeyDown)
                IJ.setKeyDown(KeyEvent.VK_ALT);
            else
                IJ.setKeyUp(KeyEvent.VK_ALT);
            shiftKeyDown = keys.indexOf("shift")!=-1;
            if (shiftKeyDown)
                IJ.setKeyDown(KeyEvent.VK_SHIFT);
            else
                IJ.setKeyUp(KeyEvent.VK_SHIFT);
            if (keys.equals("space"))
                IJ.setKeyDown(KeyEvent.VK_SPACE);
            else
                IJ.setKeyUp(KeyEvent.VK_SPACE);
            if (keys.indexOf("esc")!=-1)
                abortPluginOrMacro();
            else
                interp.keysSet = true;
        }

        void abortPluginOrMacro() {
            Interpreter.abortPrevious();
            IJ.setKeyDown(KeyEvent.VK_ESCAPE);
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp!=null) {
                ImageWindow win = imp.getWindow();
                if (win!=null) {
                    win.running = false;
                    win.running2 = false;
                }
            }
            //Macro.abort();
        }

        void open() {
            interp.getLeftParen();
            if (interp.nextToken()==')') {
                interp.getRightParen();
                IJ.open();
            } else {
                String path = getString();
                interp.getRightParen();
                IJ.open(path);
            }
            resetImage();
        }

        double roiManager() {
            String cmd = getFirstString();
            cmd = cmd.toLowerCase();
            String path = null;
            int index=0;
            double countOrIndex=Double.NaN;
            boolean twoArgCommand = cmd.equals("open")||cmd.equals("save")||cmd.equals("rename");
            boolean select = cmd.equals("select");
            if (twoArgCommand)
                path = getLastString();
            else if (select)
                index = (int)getLastArg();
            else
                interp.getRightParen();
            if (RoiManager.getInstance()==null)
                IJ.run("ROI Manager...");
            RoiManager rm = RoiManager.getInstance();
            if (rm==null)
                interp.error("ROI Manager not found");
            if (twoArgCommand)
                rm.runCommand(cmd, path);
            else if (select) {
                int n = rm.getList().getItemCount();
                checkIndex(index, 0, n-1);
                if (shiftKeyDown || altKeyDown) {
                    rm.select(index, shiftKeyDown, altKeyDown);
                    shiftKeyDown = altKeyDown = false;
                } else
                    roiManagerSelect(rm, index);
            } else if (cmd.equals("count"))
                countOrIndex = rm.getList().getItemCount();
            else if (cmd.equals("index"))
                countOrIndex = rm.getList().getSelectedIndex();
            else {
                if (!rm.runCommand(cmd))
                    interp.error("Invalid ROI Manager command");
            }
            return countOrIndex;
        }

        void roiManagerSelect(RoiManager rm, int index) {
            int delay = 1;
            long start = System.currentTimeMillis();
            while (true) {
                rm.select(index);
                if (delay>1) IJ.wait(delay);
                if (rm.getList().isIndexSelected(index))
                    break;
                //IJ.log(index+" "+delay);
                rm.select(-1); // deselect all
                IJ.wait(delay);
                delay *= 2; if (delay>32) delay=32;
                if ((System.currentTimeMillis()-start)>1000L)
                    interp.error("Failed to select");
            }
        }

        void setFont() {
            String name = getFirstString();
            int size = (int)getNextArg();
            int style = 0;
            antialiasedText = false;
            if (interp.nextToken()==',') {
                String styles = getLastString().toLowerCase();
                if (styles.indexOf("bold")!=-1) style += Font.BOLD;
                if (styles.indexOf("italic")!=-1) style += Font.ITALIC;
                if (styles.indexOf("anti")!=-1) antialiasedText = true;
            } else
                interp.getRightParen();
            font = new Font(name, style, size);
            fontSet = false;
        }

        void getMinAndMax() {
            Variable min = getFirstVariable();
            Variable max = getLastVariable();
            ImagePlus imp = getImage();
            ImageProcessor ip = imp.getProcessor();
            double v1 = ip.getMin();
            double v2 = ip.getMax();
            if (imp.getBitDepth()==16) {
                Calibration cal = imp.getCalibration();
                v1 = cal.getCValue(v1);
                v2 = cal.getCValue(v2);
            }
            min.setValue(v1);
            max.setValue(v2);
        }

        void selectImage() {
            interp.getLeftParen();
            if (isStringArg()) {
                selectImage(getString());
                interp.getRightParen();
            } else {
                int id = (int)interp.getExpression();
                IJ.selectWindow(id);
                interp.getRightParen();
            }
            resetImage();
        }

        void selectImage(String title) {
            if (Interpreter.isBatchMode()) {
                if (Interpreter.imageTable!=null) {
                    for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
                        ImagePlus imp = (ImagePlus)en.nextElement();
                        if (imp!=null) {
                            if (imp.getTitle().equals(title)) {
                                ImagePlus imp2 = WindowManager.getCurrentImage();
                                if (imp2!=null && imp2!=imp) imp2.saveRoi();
                                WindowManager.setTempCurrentImage(imp);
                                return;
                            }
                        }
                    }
                }
                selectWindowManagerImage(title);
            } else
                selectWindowManagerImage(title);
        }

        void notFound(String title) {
            interp.error(title+" not found");
        }

        void selectWindowManagerImage(String title) {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis()-start<4000) { // 4 sec timeout
                int[] wList = WindowManager.getIDList();
                int len = wList!=null?wList.length:0;
                for (int i=0; i<len; i++) {
                    ImagePlus imp = WindowManager.getImage(wList[i]);
                    if (imp!=null) {
                        if (imp.getTitle().equals(title)) {
                            IJ.selectWindow(imp.getID());
                            return;
                        }
                    }
                }
                IJ.wait(10);
            }
            notFound(title);
        }

        void close() {
            interp.getParens();
            ImagePlus imp = getImage();
            ImageWindow win = imp.getWindow();
            if (win!=null) {
                imp.changes = false;
                win.close();
            } else {
                imp.saveRoi();
                WindowManager.setTempCurrentImage(null);
                interp.removeBatchModeImage(imp);
            }
            resetImage();
        }

        void setBatchMode() {
            boolean enterBatchMode = false;
            String sarg = null;
            interp.getLeftParen();
            if (isStringArg())
                sarg = getString();
            else {
                double arg = interp.getBooleanExpression();
                interp.checkBoolean(arg);
                enterBatchMode = arg==1.0;
            }
            interp.getRightParen();
            if (!interp.isBatchMode())
                interp.calledMacro = false;
            resetImage();
            if (enterBatchMode)  { // true
                interp.setBatchMode(true);
                ImagePlus tmp = WindowManager.getTempCurrentImage();
                if (tmp!=null)
                    Interpreter.addBatchModeImage(tmp);
                return;
            }
            IJ.showProgress(0, 0);
            ImagePlus imp2 = WindowManager.getCurrentImage();
            WindowManager.setTempCurrentImage(null);
            if (sarg==null) {  //false
                interp.setBatchMode(false);
                displayBatchModeImage(imp2);
            } else {
                Vector v = Interpreter.imageTable;
                if (v==null) return;
                interp.setBatchMode(false);
                for (int i=0; i<v.size(); i++) {
                    imp2 = (ImagePlus)v.elementAt(i);
                    if (imp2!=null)
                        displayBatchModeImage(imp2);
                }
            }
        }

        void displayBatchModeImage(ImagePlus imp2) {
            if (imp2!=null) {
                ImageWindow win = imp2.getWindow();
                if (win==null)
                    imp2.show();
                else {
                    if (!win.isVisible()) win.show();
                    imp2.updateAndDraw();
                }
                Roi roi = imp2.getRoi();
                if (roi!=null) imp2.setRoi(roi);
            }
        }

        void setLocation() {
            int x = (int)getFirstArg();
            int y = (int)getLastArg();
            ImagePlus imp = getImage();
            ImageWindow win =imp.getWindow();
            if (win!=null)
                win.setLocation(x, y);
        }

        void setSlice() {
            int n = (int)getArg();
            ImagePlus imp = getImage();
            int nSlices = imp.getStackSize();
            if (n==1 && nSlices==1)
                return;
            else if (n<1 || n>nSlices)
                interp.error("Argument must be >=1 and <="+nSlices);
            else
                imp.setSlice(n);
            resetImage();
        }

        void newImage() {
            String title = getFirstString();
            String type = getNextString();
            int width = (int)getNextArg();
            int height = (int)getNextArg();
            int depth = (int)getLastArg();
            IJ.newImage(title, type, width, height, depth);
            resetImage();
        }

        void saveAs() {
            String format = getFirstString();
            String path =  null;
            if (interp.nextNonEolToken()==',')
                path = getLastString();
            else
                interp.getRightParen();
            IJ.saveAs(format, path);
        }

        double getZoom() {
            interp.getParens();
            ImagePlus imp = getImage();
            ImageCanvas ic = imp.getCanvas();
            if (ic==null)
                {interp.error("Image not displayed"); return 0.0;}
            else
                return ic.getMagnification();
        }

        void setAutoThreshold() {
            interp.getParens();
            ImagePlus imp = getImage();
            ImageProcessor ip = getProcessor();
            double min=0.0, max=0.0;
            boolean notByteData = !(ip instanceof ByteProcessor);
            if (notByteData) {
                if (ip instanceof ColorProcessor)
                    interp.error("Non-RGB image expected");
                ip.resetMinAndMax();
                min = ip.getMin(); max = ip.getMax();
                ip = new ByteProcessor(ip.createImage());
            }
            ip.setRoi(imp.getRoi());
            ImageStatistics stats = ImageStatistics.getStatistics(ip, AREA+MIN_MAX+MODE, null);
            int threshold = ip.getAutoThreshold(stats.histogram);
            int count1=0, count2=0;
            for (int i=0; i<256; i++) {
                if (i<threshold)
                    count1 += stats.histogram[i];
                else
                    count2 += stats.histogram[i];
            }
            boolean unbalanced = (double)count1/count2>1.25 || (double)count2/count1>1.25;
            //IJ.log(unbalanced+"  "+count1+"  "+count2);
            double lower, upper;
            if (unbalanced) {
                if ((stats.max-stats.dmode)>(stats.dmode-stats.min))
                    {lower=threshold; upper=255.0;}
                else
                    {lower=0.0; upper=threshold;}
            } else {
                if (ip.isInvertedLut())
                    {lower=threshold; upper=255.0;}
                else
                    {lower=0.0; upper=threshold;}
            }
            if (notByteData) {
                if (max>min) {
                    lower = min + (lower/255.0)*(max-min);
                    upper = min + (upper/255.0)*(max-min);
                } else
                    lower = upper = min;
            }
            if (imp.getBitDepth()==16) {
                Calibration cal = imp.getCalibration();
                lower = cal.getCValue(lower);
                upper = cal.getCValue(upper);
            }
            IJ.setThreshold(lower, upper);
            resetImage();
        }

        double parseDouble(String s) {
                if (s==null) return 0.0;
                s = s.trim();
                if (s.indexOf(' ')!=-1) s = s.substring(0, s.indexOf(' '));
                return Tools.parseDouble(s);
        }

        double parseInt() {
            String s = getFirstString();
            int radix = 10;
            if (interp.nextToken()==',') {
                interp.getComma();
                radix = (int)interp.getExpression();
                if (radix<2||radix>36) radix = 10;
            }
            interp.getRightParen();
            double n;
            try {
                if (radix==10) {
                    n = parseDouble(s);
                    if (!Double.isNaN(n)) n = Math.round(n);
                } else
                    n = Integer.parseInt(s, radix);
            } catch (NumberFormatException e) {
                n = NaN;
            }
            return n;
        }

        void print() {
            interp.inPrint = true;
            String s = getFirstString();
            if (interp.nextNonEolToken()==',') {
                if (s.startsWith("[") && s.endsWith("]")) {
                    printToWindow(s);
                    return;
                } else if (s.equals("~0~")) {
                    if (writer==null)
                        interp.error("File not open");
                    String s2 = getLastString();
                    if (s2.endsWith("\n"))
                        writer.print(s2);
                    else
                        writer.println(s2);
                    interp.inPrint = false;
                    return;
                }
                StringBuffer sb = new StringBuffer(s);
                do {
                    sb.append(" ");
                    sb.append(getNextString());
                } while (interp.nextNonEolToken()==',');
                s = sb.toString();
            }
            interp.getRightParen();
            IJ.log(s);
            interp.inPrint = false;
        }

        void printToWindow(String s) {
            String title = s.substring(1, s.length()-1);
            String s2 = getLastString();
            boolean isCommand = s2.startsWith("\\");
            Frame frame = WindowManager.getFrame(title);
            if (frame==null) {
                if (isCommand) {
                    interp.done = true;
                    return;
                } else
                    interp.error("Window not found");
            }
            boolean isEditor = frame instanceof Editor;
            if (!(isEditor || frame instanceof TextWindow))
                interp.error("Window is not text window");
            if (isEditor) {
                Editor ed = (Editor)frame;
                ed.setIsMacroWindow(true);
                if (isCommand)
                    handleEditorCommand(ed, s2);
                else
                    ed.append(s2);
            } else {
                TextWindow tw = (TextWindow)frame;
                if (isCommand)
                    handleTextWindowCommand(tw, s2);
                else
                    tw.append(s2);
            }
        }

        void handleEditorCommand(Editor ed, String s) {
            if (s.startsWith("\\Update:")) {
                TextArea ta = ed.getTextArea();
                ta.setText(s.substring(8, s.length()));
                ta.setEditable(false);
            } else if (s.equals("\\Close"))
                ed.close();
            else
                ed.append(s);
        }

        void handleTextWindowCommand(TextWindow tw, String s) {
            TextPanel tp = tw.getTextPanel();
            if (s.startsWith("\\Update:")) {
                int n = tp.getLineCount();
                String s2 = s.substring(8, s.length());
                if (n==0)
                    tp.append(s2);
                else
                    tp.setLine(n-1, s2);
            } else if (s.startsWith("\\Update")) {
                int cindex = s.indexOf(":");
                if (cindex==-1)
                    {tp.append(s); return;}
                String nstr = s.substring(7, cindex);
                int line = (int)Tools.parseDouble(nstr, -1);
                if (line<0 || line>25)
                    {tp.append(s); return;}
                int count = tp.getLineCount();
                while (line>=count) {
                    tp.append("");
                    count++;
                }
                String s2 = s.substring(cindex+1, s.length());
                tp.setLine(line, s2);
            } else if (s.equals("\\Clear"))
                tp.clear();
            else if (s.equals("\\Close"))
                tw.close();
            else if (s.startsWith("\\Headings:"))
                tp.setColumnHeadings(s.substring(10));
            else
                tp.append(s);
        }

        double isKeyDown() {
            double value = 0.0;
            String key = getStringArg().toLowerCase(Locale.US);
            if (key.indexOf("alt")!=-1) value = IJ.altKeyDown()==true?1.0:0.0;
            else if (key.indexOf("shift")!=-1) value = IJ.shiftKeyDown()==true?1.0:0.0;
            else if (key.indexOf("space")!=-1) value = IJ.spaceBarDown()==true?1.0:0.0;
            else interp.error("Invalid key");
            return value;
        }

        String runMacro(boolean eval) {
            interp.getLeftParen();
            String name = getString();
            String arg = null;
            if (interp.nextNonEolToken()==',') {
                interp.getComma();
                arg = getString();
            }
            interp.getRightParen();
            if (eval)
                return IJ.runMacro(name, arg);
            else
                return IJ.runMacroFile(name, arg);
        }

        void setThreshold() {
            double lower = getFirstArg();
            double upper = getNextArg();
            String mode = null;
            if (interp.nextNonEolToken()==',') {
                interp.getComma();
                mode = getString();
            }
            interp.getRightParen();
            IJ.setThreshold(lower, upper, mode);
            resetImage();
        }

        void drawOrFill(int type) {
            int x = (int)getFirstArg();
            int y = (int)getNextArg();
            int width = (int)getNextArg();
            int height = (int)getLastArg();
            ImageProcessor ip = getProcessor();
            if (!colorSet) setForegroundColor(ip);
            switch (type) {
                case DRAW_RECT: ip.drawRect(x, y, width, height); break;
                case FILL_RECT: ip.setRoi(x, y, width, height); ip.fill(); break;
                case DRAW_OVAL: ip.drawOval(x, y, width, height); break;
                case FILL_OVAL: ip.fillOval(x, y, width, height); break;
            }
            updateAndDraw(defaultImp);
        }

        double getScreenDimension(int type) {
            interp.getParens();
            Dimension screen = IJ.getScreenSize();
            if (type==SCREEN_WIDTH)
                return screen.width;
            else
                return screen.height;
        }

        void getStatistics(boolean calibrated) {
            Variable count = getFirstVariable();
            Variable mean=null, min=null, max=null, std=null, hist=null;
            int params = AREA+MEAN+MIN_MAX;
            interp.getToken();
            int arg = 1;
            while (interp.token==',') {
                arg++;
                switch (arg) {
                    case 2: mean = getVariable(); break;
                    case 3: min = getVariable(); break;
                    case 4: max = getVariable(); break;
                    case 5: std = getVariable(); params += STD_DEV; break;
                    case 6: hist = getArrayVariable(); break;
                    default: interp.error("')' expected");
                }
                interp.getToken();
            }
            if (interp.token!=')') interp.error("')' expected");
            resetImage();
            ImagePlus imp = getImage();
            Calibration cal = calibrated?imp.getCalibration():null;
            ImageProcessor ip = getProcessor();
            ip.setRoi(imp.getRoi());
            ImageStatistics stats = ImageStatistics.getStatistics(ip, params, cal);
            if (calibrated)
                count.setValue(stats.area);
            else
                count.setValue(stats.pixelCount);
            if (mean!=null) mean.setValue(stats.mean);
            if (min!=null) min.setValue(stats.min);
            if (max!=null) max.setValue(stats.max);
            if (std!=null) std.setValue(stats.stdDev);
            if (hist!=null) {
                boolean is16bit = !calibrated && ip instanceof ShortProcessor;
                int[] histogram = is16bit?stats.histogram16:stats.histogram;
                int bins = is16bit?(int)(stats.max+1):histogram.length;
                Variable[] array = new Variable[bins];
                int hmax = is16bit?(int)stats.max:255;
                for (int i=0; i<=hmax; i++)
                    array[i] = new Variable(histogram[i]);
                hist.setArray(array);
            }
        }

        String replace() {
            String s1 = getFirstString();
            String s2 = getNextString();
            String s3 = getLastString();
            if (s2.length()==1 && s3.length()==1)
                return s1.replace(s2.charAt(0), s3.charAt(0));
            else if (IJ.isJava14()) {
                try {
                    // Call replaceAll() using reflection so this class can be compiled with Java 1.3
                    Class StringClass = s1.getClass();
                    Method replaceAll = StringClass.getDeclaredMethod("replaceAll", new Class[] {String.class, String.class});
                    Object arglist[]=new Object[2]; arglist[0]=s2; arglist[1]=s3;
                    return (String)replaceAll.invoke(s1, arglist);
                } catch (Exception e) {
                    interp.error("Regular expression error:\n \n"+e);
                }
            } else
                interp.error("Java 1.4 or later required for multi-character replace");
            return null;
        }

        void floodFill() {
            int x = (int)getFirstArg();
            int y = (int)getNextArg();
            boolean fourConnected = true;
            if (interp.nextNonEolToken()==',') {
                String s = getLastString();
                if (s.indexOf("8")!=-1)
                    fourConnected = false;
            } else
                interp.getRightParen();
            ImageProcessor ip = getProcessor();
            if (!colorSet) setForegroundColor(ip);
            FloodFiller ff = new FloodFiller(ip);
            if (fourConnected)
                ff.fill(x, y);
            else
                ff.fill8(x, y);
            updateAndDraw(defaultImp);
            if (Recorder.record && pgm.hasVars)
                Recorder.record("floodFill", x, y);
        }

        void restorePreviousTool() {
            interp.getParens();
            Toolbar tb = Toolbar.getInstance();
            if (tb!=null) tb.restorePreviousTool();
        }

        void setVoxelSize() {
            double width = getFirstArg();
            double height = getNextArg();
            double depth = getNextArg();
            String unit = getLastString();
            resetImage();
            ImagePlus imp = getImage();
            Calibration cal = imp.getCalibration();
            cal.pixelWidth = width;
            cal.pixelHeight = height;
            cal.pixelDepth = depth;
            cal.setUnit(unit);
            imp.repaintWindow();
        }

        void getLocationAndSize() {
            Variable v1 = getFirstVariable();
            Variable v2 = getNextVariable();
            Variable v3 = getNextVariable();
            Variable v4 = getLastVariable();
            ImagePlus imp = getImage();
            int x=0, y=0, w=0, h=0;
            ImageWindow win = imp.getWindow();
            if (win!=null) {
                Point loc = win.getLocation();
                Dimension size = win.getSize();
                x=loc.x; y=loc.y; w=size.width; h=size.height;
            }
            v1.setValue(x);
            v2.setValue(y);
            v3.setValue(w);
            v4.setValue(h);
        }

        String doDialog() {
            interp.getToken();
            if (interp.token!='.')
                interp.error("'.' expected");
            interp.getToken();
            if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION))
                interp.error("Function name expected: ");
            String name = interp.tokenString;
            try {
                if (name.equals("create")) {
                    gd = new GenericDialog(getStringArg());
                    return null;
                }
                if (gd==null) {
                    interp.error("No dialog created with Dialog.create()");
                    return null;
                }
                if (name.equals("addString")) {
                    String label = getFirstString();
                    String defaultStr = getNextString();
                    int columns = 8;
                    if (interp.nextNonEolToken()==',')
                        columns = (int)getNextArg();
                    interp.getRightParen();
                    gd.addStringField(label, defaultStr, columns);
                } else if (name.equals("addNumber")) {
                    int columns = 6;
                    String units = null;
                    String prompt = getFirstString();
                    double defaultNumber = getNextArg();
                    int decimalPlaces = (int)defaultNumber==defaultNumber?0:3;
                    if (interp.nextNonEolToken()==',') {
                        decimalPlaces = (int)getNextArg();
                        columns = (int)getNextArg();
                        units = getLastString();
                    } else
                        interp.getRightParen();
                    gd.addNumericField(prompt, defaultNumber, decimalPlaces, columns, units);
                } else if (name.equals("addCheckbox")) {
                    gd.addCheckbox(getFirstString(), getLastArg()==1?true:false);
                } else if (name.equals("addMessage")) {
                    gd.addMessage(getStringArg());
                } else if (name.equals("addChoice")) {
                    String prompt = getFirstString();
                    interp.getComma();
                    String[] choices = getStringArray();
                    String defaultChoice = null;
                    if (interp.nextNonEolToken()==',') {
                        interp.getComma();
                        defaultChoice = getString();
                    } else
                        defaultChoice = choices[0];
                    interp.getRightParen();
                    gd.addChoice(prompt, choices, defaultChoice);
                } else if (name.equals("show")) {
                    interp.getParens();
                    gd.showDialog();
                    if (gd.wasCanceled()) {
                        interp.finishUp();
                        throw new RuntimeException(Macro.MACRO_CANCELED);
                    }
                } else if (name.equals("getString")) {
                    interp.getParens();
                    return gd.getNextString();
                } else if (name.equals("getNumber")) {
                    interp.getParens();
                    return ""+gd.getNextNumber();
                } else if (name.equals("getCheckbox")) {
                    interp.getParens();
                    return gd.getNextBoolean()==true?"1":"0";
                } else if (name.equals("getChoice")) {
                    interp.getParens();
                    return gd.getNextChoice();
                } else
                    interp.error("Unrecognized Dialog function "+name);
            } catch (IndexOutOfBoundsException e) {
                interp.error("Dialog error");
            }
            return null;
        }

        void getDateAndTime() {
            Variable year = getFirstVariable();
            Variable month = getNextVariable();
            Variable dayOfWeek = getNextVariable();
            Variable dayOfMonth = getNextVariable();
            Variable hour = getNextVariable();
            Variable minute = getNextVariable();
            Variable second = getNextVariable();
            Variable millisecond = getLastVariable();
            Calendar date = Calendar.getInstance();
            year.setValue(date.get(Calendar.YEAR));
            month.setValue(date.get(Calendar.MONTH));
            dayOfWeek.setValue(date.get(Calendar.DAY_OF_WEEK)-1);
            dayOfMonth.setValue(date.get(Calendar.DAY_OF_MONTH));
            hour.setValue(date.get(Calendar.HOUR_OF_DAY));
            minute.setValue(date.get(Calendar.MINUTE));
            second.setValue(date.get(Calendar.SECOND));
            millisecond.setValue(date.get(Calendar.MILLISECOND));
        }

        void setMetadata() {
            String metadata = getStringArg();
            ImagePlus imp = getImage();
            boolean isImageMetaData = false;
            if (metadata.startsWith("Info:")) {
                metadata = metadata.substring(5);
                isImageMetaData = true;
            }
            if (imp.getStackSize()==1 || isImageMetaData)
                imp.setProperty("Info", metadata);
            else {
                imp.getStack().setSliceLabel(metadata, imp.getCurrentSlice());
                if (!Interpreter.isBatchMode())
                    imp.repaintWindow();
            }
        }

        String getMetadata() {
            interp.getParens();
            ImagePlus imp = getImage();
            String metadata;
            if (imp.getStackSize()==1)
                metadata = (String)imp.getProperty("Info");
            else
                metadata = imp.getStack().getSliceLabel(imp.getCurrentSlice());
            if (metadata==null) metadata = "";
            return metadata;
        }

        ImagePlus getImageArg() {
            ImagePlus img = null;
            if (isStringArg()) {
                String title = getString();
                img = WindowManager.getImage(title);
            } else {
                int id = (int)interp.getExpression();
                img = WindowManager.getImage(id);
            }
            if (img==null) interp.error("Image not found");
            return img;
        }

        void imageCalculator() {
            String operator = getFirstString();
            interp.getComma();
            ImagePlus img1 = getImageArg();
            interp.getComma();
            ImagePlus img2 = getImageArg();
            interp.getRightParen();
            ImageCalculator ic = new ImageCalculator();
            ic.calculate(operator, img1, img2);
        }

        void setRGBWeights() {
            double r = getFirstArg();
            double g = getNextArg();
            double b = getLastArg();
            if (interp.rgbWeights==null)
                interp.rgbWeights = ColorProcessor.getWeightingFactors();
            ColorProcessor.setWeightingFactors(r, g, b);
        }

        void makePolygon() {
            int max = 200;
            int[] x = new int[max];
            int[] y = new int[max];
            x[0] = (int)Math.round(getFirstArg());
            y[0] = (int)Math.round(getNextArg());
            interp.getToken();
            int n = 1;
            while (interp.token==',' && n<max) {
                x[n] = (int)Math.round(interp.getExpression());
                interp.getComma();
                y[n] = (int)Math.round(interp.getExpression());
                interp.getToken();
                n++;
            }
            if (n<3)
                interp.error("Fewer than 3 points");
            if (n==max && interp.token!=')')
                interp.error("More than "+max+" points");
            ImagePlus imp = getImage();
            Roi previousRoi = imp.getRoi();
            if (shiftKeyDown||altKeyDown) imp.saveRoi();
            imp.setRoi(new PolygonRoi(x, y, n, Roi.POLYGON));
            Roi roi = imp.getRoi();
            if (previousRoi!=null && roi!=null)
                updateRoi(roi);
            resetImage();
        }

        void updateRoi(Roi roi) {
            if (shiftKeyDown || altKeyDown)
                roi.update(shiftKeyDown, altKeyDown);
            shiftKeyDown = altKeyDown = false;
        }

        String doFile() {
            interp.getToken();
            if (interp.token!='.')
                interp.error("'.' expected");
            interp.getToken();
            if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION || interp.token==PREDEFINED_FUNCTION))
                interp.error("Function name expected: ");
            String name = interp.tokenString;
            if (name.equals("open"))
                return openFile();
            else if (name.equals("openAsString"))
                return openAsString();
            else if (name.equals("openUrlAsString"))
                return openUrlAsString();
            else if (name.equals("close"))
                return closeFile();
            else if (name.equals("separator")) {
                interp.getParens();
                return File.separator;
            } else if (name.equals("directory")) {
                interp.getParens();
                String lastDir = OpenDialog.getLastDirectory();
                return lastDir!=null?lastDir:"";
            } else if (name.equals("name")) {
                interp.getParens();
                String lastName = OpenDialog.getLastName();
                return lastName!=null?lastName:"";
            } else if (name.equals("rename")) {
                File f1 = new File(getFirstString());
                File f2 = new File(getLastString());
                if (isValid(f1) && isValid(f2))
                    return f1.renameTo(f2)?"1":"0";
                else
                    return "0";
            }
            File f = new File(getStringArg());
            if (name.equals("getLength")||name.equals("length"))
                return ""+f.length();
            else if (name.equals("getName"))
                return f.getName();
            else if (name.equals("getAbsolutePath"))
                return f.getAbsolutePath();
            else if (name.equals("getParent"))
                return f.getParent();
            else if (name.equals("exists"))
                return f.exists()?"1":"0";
            else if (name.equals("isDirectory"))
                return f.isDirectory()?"1":"0";
            else if (name.equals("makeDirectory")||name.equals("mkdir")) {
                f.mkdir(); return null;
            } else if (name.equals("lastModified"))
                return ""+f.lastModified();
            else if (name.equals("dateLastModified"))
                return (new Date(f.lastModified())).toString();
            else if (name.equals("delete")) {
                if (isValid(f))
                    f.delete();
                return null;
            } else
                interp.error("Unrecognized File function "+name);
            return null;
        }

        boolean isValid(File f) {
            String path = f.getPath();
            if (path.equals("0") || path.equals("NaN") )
                    interp.error("Invalid path");
            path = f.getAbsolutePath();
            if (!(path.indexOf("ImageJ")!=-1
            ||path.startsWith(System.getProperty("java.io.tmpdir"))
            ||path.startsWith(System.getProperty("user.home")))) {
                interp.error("File must be in ImageJ, home or tmp directory");
                return false;
            } else
                return true;
        }

        void setSelectionName() {
            Roi roi = getImage().getRoi();
            if (roi==null)
                interp.error("No selection");
            else
                roi.setName(getStringArg());
        }

        String selectionName() {
            Roi roi = getImage().getRoi();
            String name = null;
            if (roi==null)
                interp.error("No selection");
            else
                name = roi.getName();
            return name!=null?name:"";
        }

        String openFile() {
            String path = getFirstString();
            String defaultName = null;
            if (interp.nextToken()==')')
                interp.getRightParen();
            else
                defaultName = getLastString();
            if (path.equals("") || defaultName!=null) {
                String title = defaultName!=null?path:"openFile";
                defaultName = defaultName!=null?defaultName:"log.txt";
                SaveDialog sd = new SaveDialog(title, defaultName, ".txt");
                if(sd.getFileName()==null) return "";
                path = sd.getDirectory()+sd.getFileName();
            } else {
                File file = new File(path);
                if (file.exists() && !(path.endsWith(".txt")||path.endsWith(".java")||path.endsWith(".xls")||path.endsWith(".ijm")))
                    interp.error("File exists and suffix is not '.txt', '.java', etc.");
            }
            if (writer!=null) writer.close();
            writer = null;
            try {
                FileOutputStream fos = new FileOutputStream(path);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                writer = new PrintWriter(bos);
            }
            catch (IOException e) {
                interp.error("File open error \n\""+e.getMessage()+"\"\n");
                return "";
            }
            return "~0~";
        }

        String openAsString() {
            String path = getStringArg();
            if (path.equals("")) {
                OpenDialog od = new OpenDialog("OpenAsString...", "");
                String directory = od.getDirectory();
                String name = od.getFileName();
                if (name==null) return "";
                path = directory + name;
            }
            String str = "";
            File file = new File(path);
            if (!file.exists())
                interp.error("File not found");
            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();
                str = new String(sb);
            }
            catch (Exception e) {
                interp.error("File open error \n\""+e.getMessage()+"\"\n");
            }
            return str;
        }

        String closeFile() {
            String f = getStringArg();
            if (!f.equals("~0~"))
                interp.error("Invalid file variable");
            if (writer!=null) {
                writer.close();
                writer = null;
            }
            return null;
        }

        // Calls a public static method with an arbitrary number
        // of String parameters, returning a String.
        // Contributed by Johannes Schindelin
        String call() {
            // get class and method name
            String fullName = getFirstString();
            int dot = fullName.lastIndexOf('.');
            if(dot<0)
                interp.error("Expected 'classname.methodname'");
            String className = fullName.substring(0,dot);
            String methodName = fullName.substring(dot+1);

            // get optional string arguments
            Object[] args = null;
            if (interp.nextNonEolToken()==',') {
                Vector vargs = new Vector();
                do
                    vargs.add(getNextString());
                while (interp.nextNonEolToken()==',');
                args = vargs.toArray();
            }
            interp.getRightParen();
            if (args==null) args = new Object[0];

            // get the class
            Class c;
            try {
                c = IJ.getClassLoader().loadClass(className);
            } catch(Exception ex) {
                interp.error("Could not load class "+className);
                return null;
            }

            // get method
            Method m;
            try {
                Class[] argClasses = null;
                if(args.length>0) {
                    argClasses = new Class[args.length];
                    for(int i=0;i<args.length;i++)
                        argClasses[i] = args[i].getClass();
                }
                m = c.getMethod(methodName,argClasses);
            } catch(Exception ex) {
                interp.error("Could not find the method "+methodName+" with "+
                         args.length+" parameter(s) in class "+className);
                return null;
            }

            try {
                Object obj = m.invoke(null, args);
                return obj!=null?obj.toString():null;
            } catch(Exception e) {
                IJ.log("Call error ("+e+")");
                return null;
            }

        }

        Variable[] getFontList() {
            interp.getParens();
            String fonts[] = null;
            if (IJ.isJava2()) {
                GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                fonts = ge.getAvailableFontFamilyNames();
            } else
                fonts = Toolkit.getDefaultToolkit().getFontList();
            if (fonts==null) return null;
            Variable[] array = new Variable[fonts.length];
            for (int i=0; i<fonts.length; i++)
                array[i] = new Variable(0, 0.0, fonts[i]);
            return array;
        }

        String openUrlAsString() {
            String urlString = getStringArg();
            StringBuffer sb = null;
            try {
                URL url = new URL(urlString);
                InputStream in = url.openStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
                sb = new StringBuffer() ;
                String line;
                while ((line=br.readLine()) != null)
                    sb.append (line + "\n");
                in.close ();
            } catch (IOException e) {
                String msg = ""+e;
                if (msg.indexOf("UnknownHost")!=-1 || msg.indexOf("FileNotFound")!=-1)
                    return "";
                else
                    interp.error(msg);
                sb = null;
            }
            if (sb!=null)
                return new String(sb);
            else
                return null;
        }

        void setOption() {
            String arg1 = getFirstString();
            interp.getComma();
            double arg2 = interp.getBooleanExpression();
            interp.checkBoolean(arg2);
            interp.getRightParen();
            boolean state = arg2==0?false:true;
            arg1 = arg1.toLowerCase(Locale.US);
            if (arg1.equals("disablepopupmenu")) {
                ImageCanvas ic = getImage().getCanvas();
                if (ic!=null) ic.disablePopupMenu(state);
            } else if (arg1.equals("show all")) {
                ImagePlus img = getImage();
                ImageCanvas ic = img.getCanvas();
                if (ic!=null) {
                    boolean previousState = ic.getShowAllROIs();
                    ic.setShowAllROIs(state);
                    if (state!=previousState) img.draw();
                }
            } else if (arg1.equals("changes"))
                getImage().changes = state;
            else if (arg1.equals("debugmode"))
                IJ.debugMode = state;
            else if (arg1.equals("openusingplugins"))
                Opener.setOpenUsingPlugins(state);
            else if (arg1.equals("queuemacros"))
                pgm.queueCommands = state;
            else if (arg1.equals("disableundo"))
                Prefs.disableUndo = state;
            else
                interp.error("Invalid option");
        }

        void showText() {
            String title = getFirstString();
            String text = getLastString();
            Editor ed = new Editor();
            ed.setSize(350, 300);
            ed.create(title, text);
        }

        Variable[] newMenu() {
            String name = getFirstString();
            interp.getComma();
            String[] commands = getStringArray();
            interp.getRightParen();
            if (pgm.menus==null)
                pgm.menus = new Hashtable();
            pgm.menus.put(name, commands);
            Variable[] commands2 = new Variable[commands.length];
            for (int i=0; i<commands.length; i++)
                commands2[i] = new Variable(0, 0.0, commands[i]);
            return commands2;
        }

        void setSelectionLocation() {
            int x = (int)Math.round(getFirstArg());
            int y = (int)Math.round(getLastArg());
            resetImage();
            ImagePlus imp = getImage();
            Roi roi = imp.getRoi();
            if (roi==null)
                interp.error("Selection required");
            roi.setLocation(x, y);
            imp.draw();
        }

        double is() {
            double state = 0.0;
            String arg = getStringArg();
            arg = arg.toLowerCase(Locale.US);
            if (arg.equals("locked"))
                state = getImage().isLocked()?1.0:0.0;
            else if (arg.indexOf("invert")!=-1)
                state = getImage().isInvertedLut()?1.0:0.0;
            else
                interp.error("Argument must be 'locked' or 'Inverted LUT'");
            return state;
        }

        Variable[] getList() {
            String key = getStringArg();
            if (key.equals("java.properties")) {
                Properties props = System.getProperties();
                Vector v = new Vector();
                for (Enumeration en=props.keys(); en.hasMoreElements();)
                    v.addElement((String)en.nextElement());
                Variable[] array = new Variable[v.size()];
                for (int i=0; i<array.length; i++)
                    array[i] = new Variable(0, 0.0, (String)v.elementAt(i));
                return array;
            } else if (key.equals("window.titles")) {
                Frame[] list = WindowManager.getNonImageWindows();
                Variable[] array = new Variable[list.length];
                for (int i=0; i<list.length; i++) {
                    Frame frame = list[i];
                    array[i] = new Variable(0, 0.0, frame.getTitle());
                }
                return array;
            } else {
                interp.error("Unvalid key");
                return null;
            }
        }

        String doString() {
            interp.getToken();
            if (interp.token!='.')
                interp.error("'.' expected");
            interp.getToken();
            if (interp.token!=WORD)
                interp.error("Function name expected: ");
            String name = interp.tokenString;
            if (name.equals("append"))
                return appendToBuffer();
            else if (name.equals("copy"))
                return copyStringToClipboard();
            else if (name.equals("copyResults"))
                return copyResults();
            else if (name.equals("paste"))
                return getClipboardContents();
            else if (name.equals("resetBuffer"))
                return resetBuffer();
            else if (name.equals("buffer"))
                return getBuffer();
            else
                interp.error("Unrecognized String function");
            return null;
        }

        String appendToBuffer() {
            String text = getStringArg();
            if (buffer==null)
                buffer = new StringBuffer(256);
            buffer.append(text);
            return null;
        }

        String copyStringToClipboard() {
            String text = getStringArg();
            StringSelection ss = new StringSelection(text);
            java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(ss, null);
            return null;
        }

        String getClipboardContents() {
            interp.getParens();
            java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            Transferable data = clipboard.getContents(null);
            String s = null;
            try {s = (String)data.getTransferData(DataFlavor.stringFlavor);}
            catch (Exception e) {s = data.toString();}
            return s;
        }

        String copyResults() {
            interp.getParens();
            if (!IJ.isResultsWindow())
                interp.error("No results");
            TextPanel tp = IJ.getTextPanel();
            StringSelection ss = new StringSelection(tp.getText());
            java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            clipboard.setContents(ss, null);
            return null;
        }

        String resetBuffer() {
            interp.getParens();
            buffer = new StringBuffer(256);
            return null;
        }

        String getBuffer() {
            interp.getParens();
            if (buffer==null)
                buffer = new StringBuffer(256);
            return buffer.toString();
        }

        void doCommand() {
            String arg = getStringArg();
            if (arg.equals("Start Animation"))
                arg = "Start Animation [\\]";
            IJ.doCommand(arg);
        }

        void getDimensions() {
            Variable width = getFirstVariable();
            Variable height = getNextVariable();
            Variable channels = getNextVariable();
            Variable slices = getNextVariable();
            Variable frames = getLastVariable();
            ImagePlus imp = getImage();
            int[] dim = imp.getDimensions();
            width.setValue(dim[0]);
            height.setValue(dim[1]);
            channels.setValue(dim[2]);
            slices.setValue(dim[3]);
            frames.setValue(dim[4]);
        }

    } // class Functions

| Functions |