| RoiManager |

    package ij.plugin.frame;
    import java.awt.*;
    import java.awt.event.*;
    import java.io.*;
    import java.util.*;
    import java.awt.List;
    import java.util.zip.*;
    import ij.*;
    import ij.process.*;
    import ij.gui.*;
    import ij.io.*;
    import ij.plugin.filter.*;
    import ij.util.*;
    import ij.macro.*;
    import ij.measure.*;

    /** This plugin implements the Analyze/Tools/ROI Manager command. */
    public class RoiManager extends PlugInFrame implements ActionListener, ItemListener, MouseListener, MouseWheelListener {

        static final int BUTTONS = 10;
        static final int DRAW=0, FILL=1, LABEL=2;
        static final int MENU=0, COMMAND=1, MULTI=2;
        Panel panel;
        static Frame instance;
        java.awt.List list;
        Hashtable rois = new Hashtable();
        Roi roiCopy;
        boolean canceled;
        boolean macro;
        boolean ignoreInterrupts;
        PopupMenu pm;
        Button moreButton;
        static boolean measureAll = true;
        static boolean onePerSlice = true;

        public RoiManager() {
            super("ROI Manager");
            if (instance!=null) {
                instance.toFront();
                return;
            }
            instance = this;
            ImageJ ij = IJ.getInstance();
            addKeyListener(ij);
            addMouseListener(this);
            addMouseWheelListener(this);
            WindowManager.addWindow(this);
            setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
            int rows = 15;
            boolean allowMultipleSelections = true; //IJ.isMacintosh();
            list = new List(rows, allowMultipleSelections);
            list.add("012345678901234");
            list.addItemListener(this);
            list.addKeyListener(ij);
            list.addMouseListener(this);
            list.addMouseWheelListener(this);
            if (IJ.isLinux()) list.setBackground(Color.white);
            add(list);
            panel = new Panel();
            int nButtons = IJ.isJava2()?BUTTONS:BUTTONS-1;
            panel.setLayout(new GridLayout(nButtons, 1, 5, 0));
            addButton("Add [t]");
            addButton("Update");
            addButton("Delete");
            addButton("Rename");
            addButton("Open");
            addButton("Save");
            addButton("Measure");
            addButton("Deselect");
            addButton("Show All");
            addButton("More >>");
            add(panel);
            addPopupMenu();
            pack();
            list.remove(0);
            GUI.center(this);
            show();
        }

        void addButton(String label) {
            Button b = new Button(label);
            b.addActionListener(this);
            b.addKeyListener(IJ.getInstance());
            b.addMouseListener(this);
            if (label.equals("More >>")) moreButton = b;
            panel.add(b);
        }

        void addPopupMenu() {
            pm=new PopupMenu();
            //addPopupItem("Select All");
            addPopupItem("Draw");
            addPopupItem("Fill");
            addPopupItem("Label");
            pm.addSeparator();
            addPopupItem("Combine");
            addPopupItem("Split");
            addPopupItem("Add Particles");
            addPopupItem("Multi Measure");
            addPopupItem("Sort");
            addPopupItem("Specify...");
            addPopupItem("Remove Slice Info");
            addPopupItem("Help");
            add(pm);
        }

        void addPopupItem(String s) {
            MenuItem mi=new MenuItem(s);
            mi.addActionListener(this);
            pm.add(mi);
        }

        public void actionPerformed(ActionEvent e) {
            int modifiers = e.getModifiers();
            boolean altKeyDown = (modifiers&ActionEvent.ALT_MASK)!=0 || IJ.altKeyDown();
            boolean shiftKeyDown = (modifiers&ActionEvent.SHIFT_MASK)!=0 || IJ.shiftKeyDown();
            IJ.setKeyUp(KeyEvent.VK_ALT);
            IJ.setKeyUp(KeyEvent.VK_SHIFT);
            String label = e.getActionCommand();
            if (label==null)
                return;
            String command = label;
            if (command.equals("Add [t]"))
                add(shiftKeyDown, altKeyDown);
            else if (command.equals("Update"))
                update();
            else if (command.equals("Delete"))
                delete(false);
            else if (command.equals("Rename"))
                rename(null);
            else if (command.equals("Open"))
                open(null);
            else if (command.equals("Save"))
                save();
            else if (command.equals("Measure"))
                measure(MENU);
            else if (command.equals("Show All"))
                showAll();
            else if (command.equals("Draw"))
                drawOrFill(DRAW);
            else if (command.equals("Fill"))
                drawOrFill(FILL);
            else if (command.equals("Label"))
                drawOrFill(LABEL);
            else if (command.equals("Deselect"))
                select(-1);
            else if (command.equals("More >>")) {
                Point ploc = panel.getLocation();
                Point bloc = moreButton.getLocation();
                pm.show(this, ploc.x, bloc.y);
            } else if (command.equals("Select All"))
                selectAll();
            else if (command.equals("Combine"))
                combine();
            else if (command.equals("Split"))
                split();
            else if (command.equals("Add Particles"))
                addParticles();
            else if (command.equals("Multi Measure"))
                multiMeasure();
            else if (command.equals("Sort"))
                sort();
            else if (command.equals("Specify..."))
                specify();
            else if (command.equals("Remove Slice Info"))
                removeSliceInfo();
            else if (command.equals("Help"))
                help();
        }

        public void itemStateChanged(ItemEvent e) {
            //IJ.log("itemStateChanged: "+e.getItem().toString()+"  "+e+"  "+ignoreInterrupts);
            if (e.getStateChange()==ItemEvent.SELECTED && !ignoreInterrupts) {
                int index = 0;
                try {index = Integer.parseInt(e.getItem().toString());}
                catch (NumberFormatException ex) {}
                if (index<0) index = 0;
                if (!IJ.shiftKeyDown() && !IJ.isMacintosh()) {
                    int[] indexes = list.getSelectedIndexes();
                    for (int i=0; i<indexes.length; i++) {
                        if (indexes[i]!=index)
                            list.deselect(indexes[i]);
                    }
                }
                if (WindowManager.getCurrentImage()!=null) {
                    restore(index, true);
                    if (Recorder.record) Recorder.record("roiManager", "Select", index);
                }
            }
        }

        void add(boolean shiftKeyDown, boolean altKeyDown) {
            if (shiftKeyDown)
                addAndDraw(altKeyDown);
            else if (altKeyDown)
                add(true);
            else
                add(false);
        }

        boolean add(boolean promptForName) {
            ImagePlus imp = getImage();
            if (imp==null)
                return false;
            Roi roi = imp.getRoi();
            if (roi==null) {
                error("The active image does not have a selection.");
                return false;
            }
            int n = list.getItemCount();
            if (n>0) {
                // check for duplicate
                String label = list.getItem(n-1);
                Roi roi2 = (Roi)rois.get(label);
                if (roi2!=null) {
                    int slice2 = getSliceNumber(label);
                    if (roi.equals(roi2) && (slice2==-1||slice2==imp.getCurrentSlice()))
                        return false;
                }
            }
            String name = roi.getName();
            if (isStandardName(name))
                name = null;
            String label = name!=null?name:getLabel(imp, roi, -1);
            if (promptForName)
                label = promptForName(label);
            else
                label = getUniqueName(label);
            if (label==null) return false;
            list.add(label);
            roi.setName(label);
            roiCopy = (Roi)roi.clone();
            Calibration cal = imp.getCalibration();
            if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0) {
                Rectangle r = roiCopy.getBounds();
                roiCopy.setLocation(r.x-(int)cal.xOrigin, r.y-(int)cal.yOrigin);
            }
            rois.put(label, roiCopy);
            updateShowAll();
            if (Recorder.record) Recorder.record("roiManager", "Add");
            return true;
        }

        /** Adds the specified ROI to the list. The third argument ('n') will
            be used form the first part of the ROI lable if it is >= 0. */
        public void add(ImagePlus imp, Roi roi, int n) {
            if (roi==null) return;
            String label = getLabel(imp, roi, n);
            if (label==null) return;
            list.add(label);
            roi.setName(label);
            roiCopy = (Roi)roi.clone();
            Calibration cal = imp.getCalibration();
            if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0) {
                Rectangle r = roiCopy.getBounds();
                roiCopy.setLocation(r.x-(int)cal.xOrigin, r.y-(int)cal.yOrigin);
            }
            rois.put(label, roiCopy);
        }

        boolean isStandardName(String name) {
            if (name==null) return false;
            boolean isStandard = false;
            int len = name.length();
            if (len>=14 && name.charAt(4)=='-' && name.charAt(9)=='-' )
                isStandard = true;
            else if (len>=9 && name.charAt(4)=='-')
                isStandard = true;
            return isStandard;
        }

        String getLabel(ImagePlus imp, Roi roi, int n) {
            Rectangle r = roi.getBounds();
            int xc = r.x + r.width/2;
            int yc = r.y + r.height/2;
            if (n>=0)
                {xc = yc; yc=n;}
            if (xc<0) xc = 0;
            if (yc<0) yc = 0;
            int digits = 4;
            String xs = "" + xc;
            if (xs.length()>digits) digits = xs.length();
            String ys = "" + yc;
            if (ys.length()>digits) digits = ys.length();
            xs = "000000" + xc;
            ys = "000000" + yc;
            String label = ys.substring(ys.length()-digits) + "-" + xs.substring(xs.length()-digits);
            if (imp.getStackSize()>1) {
                String zs = "000000" + imp.getCurrentSlice();
                label = zs.substring(zs.length()-digits) + "-" + label;
            }
            return label;
        }

        void addAndDraw(boolean altKeyDown) {
            if (altKeyDown) {
                if (!add(true)) return;
            } else if (!add(false))
                return;
            ImagePlus imp = WindowManager.getCurrentImage();
            Undo.setup(Undo.COMPOUND_FILTER, imp);
            IJ.run("Draw");
            Undo.setup(Undo.COMPOUND_FILTER_DONE, imp);
            if (Recorder.record) Recorder.record("roiManager", "Add & Draw");
        }

        boolean delete(boolean replacing) {
            int count = list.getItemCount();
            if (count==0)
                return error("The list is empty.");
            int index[] = list.getSelectedIndexes();
            if (index.length==0 || (replacing&&count>1)) {
                String msg = "Delete all items on the list?";
                if (replacing)
                    msg = "Replace items on the list?";
                canceled = false;
                if (!IJ.macroRunning() && !macro) {
                    YesNoCancelDialog d = new YesNoCancelDialog(this, "ROI Manager", msg);
                    if (d.cancelPressed())
                        {canceled = true; return false;}
                    if (!d.yesPressed()) return false;
                }
                index = getAllIndexes();
            }
            for (int i=count-1; i>=0; i--) {
                boolean delete = false;
                for (int j=0; j<index.length; j++) {
                    if (index[j]==i)
                        delete = true;
                }
                if (delete) {
                    rois.remove(list.getItem(i));
                    list.remove(i);
                }
            }
            updateShowAll();
            if (Recorder.record) Recorder.record("roiManager", "Delete");
            return true;
        }

        boolean update() {
            ImagePlus imp = getImage();
            if (imp==null) return false;
            ImageCanvas ic = imp.getCanvas();
            boolean showingAll = ic!=null &&  ic.getShowAllROIs();
            Roi roi = imp.getRoi();
            if (roi==null) {
                error("The active image does not have a selection.");
                return false;
            }
            int index = list.getSelectedIndex();
            if (index<0 && !showingAll)
                return error("Exactly one item in the list must be selected.");
            if (index>=0) {
                String name = list.getItem(index);
                rois.remove(name);
                rois.put(name, roi);
            }
            if (Recorder.record) Recorder.record("roiManager", "Update");
            if (showingAll) imp.draw();
            return true;
        }

        boolean rename(String name2) {
            int index = list.getSelectedIndex();
            if (index<0)
                return error("Exactly one item in the list must be selected.");
            String name = list.getItem(index);
            if (name2==null) name2 = promptForName(name);
            if (name2==null) return false;
            Roi roi = (Roi)rois.get(name);
            rois.remove(name);
            roi.setName(name2);
            rois.put(name2, roi);
            list.replaceItem(name2, index);
            list.select(index);
            if (Recorder.record) Recorder.record("roiManager", "Rename", name2);
            return true;
        }

        String promptForName(String name) {
            GenericDialog gd = new GenericDialog("ROI Manager");
            gd.addStringField("Rename As:", name, 20);
            gd.showDialog();
            if (gd.wasCanceled())
                return null;
            String name2 = gd.getNextString();
            name2 = getUniqueName(name2);
            return name2;
        }

        boolean restore(int index, boolean setSlice) {
            String label = list.getItem(index);
            Roi roi = (Roi)rois.get(label);
            ImagePlus imp = getImage();
            if (imp==null || roi==null)
                return false;
            if (setSlice) {
                int slice = getSliceNumber(label);
                if (slice>=1 && slice<=imp.getStackSize())
                    imp.setSlice(slice);
            }
            Roi roi2 = (Roi)roi.clone();
            Calibration cal = imp.getCalibration();
            Rectangle r = roi2.getBounds();
            if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0)
                roi2.setLocation(r.x+(int)cal.xOrigin, r.y+(int)cal.yOrigin);
            int width= imp.getWidth(), height=imp.getHeight();
            if (r.x>=width || r.y>=height || (r.x+r.width)<=0 || (r.y+r.height)<=0)
                roi2.setLocation((width-r.width)/2, (height-r.height)/2);
            imp.setRoi(roi2);
            return true;
        }

        int getSliceNumber(String label) {
            int slice = -1;
            if (label.length()>4 && label.charAt(4)=='-' && label.length()>=14)
                slice = (int)Tools.parseDouble(label.substring(0,4),-1);
            return slice;
        }

        void open(String path) {
            Macro.setOptions(null);
            String name = null;
            if (path==null || path.equals("")) {
                OpenDialog od = new OpenDialog("Open Selection(s)...", "");
                String directory = od.getDirectory();
                name = od.getFileName();
                if (name==null)
                    return;
                path = directory + name;
            }
            if (Recorder.record) Recorder.record("roiManager", "Open", path);
            if (path.endsWith(".zip")) {
                openZip(path);
                return;
            }
            Opener o = new Opener();
            if (name==null) name = o.getName(path);
            Roi roi = o.openRoi(path);
            if (roi!=null) {
                if (name.endsWith(".roi"))
                    name = name.substring(0, name.length()-4);
                name = getUniqueName(name);
                list.add(name);
                rois.put(name, roi);
            }
            updateShowAll();
        }

        // Modified on 2005/11/15 by Ulrik Stervbo to only read .roi files and to not empty the current list
        void openZip(String path) {
            ZipInputStream in = null;
            ByteArrayOutputStream out;
            int nRois = 0;
            try {
                in = new ZipInputStream(new FileInputStream(path));
                byte[] buf = new byte[1024];
                int len;
                ZipEntry entry = in.getNextEntry();
                while (entry!=null) {
                    String name = entry.getName();
                    if (name.endsWith(".roi")) {
                        out = new ByteArrayOutputStream();
                        while ((len = in.read(buf)) > 0)
                            out.write(buf, 0, len);
                        out.close();
                        byte[] bytes = out.toByteArray();
                        RoiDecoder rd = new RoiDecoder(bytes, name);
                        Roi roi = rd.getRoi();
                        if (roi!=null) {
                            name = name.substring(0, name.length()-4);
                            name = getUniqueName(name);
                            list.add(name);
                            rois.put(name, roi);
                            nRois++;
                        }
                    }
                    entry = in.getNextEntry();
                }
                in.close();
            } catch (IOException e) {error(e.toString());}
            if(nRois==0)
                    error("This ZIP archive does not appear to contain \".roi\" files");
            updateShowAll();
        }

        String getUniqueName(String name) {
                String name2 = name;
                int n = 1;
                Roi roi2 = (Roi)rois.get(name2);
                while (roi2!=null) {
                    roi2 = (Roi)rois.get(name2);
                    if (roi2!=null) {
                        int lastDash = name2.lastIndexOf("-");
                        if (lastDash!=-1 && name2.length()-lastDash<5)
                            name2 = name2.substring(0, lastDash);
                        name2 = name2+"-"+n;
                        n++;
                    }
                    roi2 = (Roi)rois.get(name2);
                }
                return name2;
        }

        boolean save() {
            if (list.getItemCount()==0)
                return error("The selection list is empty.");
            int[] indexes = list.getSelectedIndexes();
            if (indexes.length==0)
                indexes = getAllIndexes();
            if (indexes.length>1)
                return saveMultiple(indexes, null);
            String name = list.getItem(indexes[0]);
            Macro.setOptions(null);
            SaveDialog sd = new SaveDialog("Save Selection...", name, ".roi");
            String name2 = sd.getFileName();
            if (name2 == null)
                return false;
            String dir = sd.getDirectory();
            Roi roi = (Roi)rois.get(name);
            rois.remove(name);
            if (!name2.endsWith(".roi")) name2 = name2+".roi";
            String newName = name2.substring(0, name2.length()-4);
            rois.put(newName, roi);
            roi.setName(newName);
            list.replaceItem(newName, indexes[0]);
            RoiEncoder re = new RoiEncoder(dir+name2);
            try {
                re.write(roi);
            } catch (IOException e) {
                IJ.error("ROI Manager", e.getMessage());
            }
            return true;
        }

        boolean saveMultiple(int[] indexes, String path) {
            Macro.setOptions(null);
            if (path==null) {
                SaveDialog sd = new SaveDialog("Save ROIs...", "RoiSet", ".zip");
                String name = sd.getFileName();
                if (name == null)
                    return false;
                if (!(name.endsWith(".zip") || name.endsWith(".ZIP")))
                    name = name + ".zip";
                String dir = sd.getDirectory();
                path = dir+name;
            }
            try {
                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(path));
                DataOutputStream out = new DataOutputStream(new BufferedOutputStream(zos));
                RoiEncoder re = new RoiEncoder(out);
                for (int i=0; i<indexes.length; i++) {
                    String label = list.getItem(indexes[i]);
                    Roi roi = (Roi)rois.get(label);
                    if (!label.endsWith(".roi")) label += ".roi";
                    zos.putNextEntry(new ZipEntry(label));
                    re.write(roi);
                    out.flush();
                }
                out.close();
            }
            catch (IOException e) {
                error(""+e);
                return false;
            }
            if (Recorder.record) Recorder.record("roiManager", "Save", path);
            return true;
        }

        boolean measure(int mode) {
            ImagePlus imp = getImage();
            if (imp==null)
                return false;
            int[] indexes = list.getSelectedIndexes();
            if (indexes.length==0)
                indexes = getAllIndexes();
            if (indexes.length==0) return false;

            int nLines = 0;
            boolean allSliceOne = true;
            for (int i=0; i<indexes.length; i++) {
                String label = list.getItem(indexes[i]);
                if (getSliceNumber(label)>1) allSliceOne = false;
                Roi roi = (Roi)rois.get(label);
                if (roi.isLine()) nLines++;
            }
            if (nLines>0 && nLines!=indexes.length) {
                error("All items must be areas or all must be lines.");
                return false;
            }

            int nSlices = 1;
            if (mode==MULTI)
                nSlices = imp.getStackSize();
            int measurements = Analyzer.getMeasurements();
            if (imp.getStackSize()>1)
                Analyzer.setMeasurements(measurements|Measurements.SLICE);
            int currentSlice = imp.getCurrentSlice();
            for (int slice=1; slice<=nSlices; slice++) {
                if (nSlices>1) imp.setSlice(slice);
                for (int i=0; i<indexes.length; i++) {
                    if (restore(indexes[i], nSlices==1&&!allSliceOne))
                        IJ.run("Measure");
                    else
                        break;
                }
            }
            imp.setSlice(currentSlice);
            Analyzer.setMeasurements(measurements);
            if (indexes.length>1)
                IJ.run("Select None");
            if (Recorder.record) Recorder.record("roiManager", "Measure");
            return true;
        }

        /* This method performs measurements for several ROI's in a stack
            and arranges the results with one line per slice.  By constast, the
            measure() method produces several lines per slice.  The results
            from multiMeasure() may be easier to import into a spreadsheet
            program for plotting or additional analysis. Based on the multi()
            method in Bob Dougherty's Multi_Measure plugin
            (http://www.optinav.com/Multi-Measure.htm).
        */
        boolean multiMeasure() {
            ImagePlus imp = getImage();
            if (imp==null) return false;
            int[] indexes = list.getSelectedIndexes();
            if (indexes.length==0)
                indexes = getAllIndexes();
            if (indexes.length==0) return false;
            int measurements = Analyzer.getMeasurements();

            int nSlices = imp.getStackSize();
            GenericDialog gd = new GenericDialog("Multi Measure");
            if (nSlices>1)
                gd.addCheckbox("Measure All "+nSlices+" Slices", measureAll);
            gd.addCheckbox("One Row Per Slice", onePerSlice);
            int columns = getColumnCount(imp, measurements)*indexes.length;
            String str = nSlices==1?"this option":"both options";
            gd.setInsets(10, 25, 0);
            gd.addMessage(
                "Enabling "+str+" will result\n"+
                "in a table with "+columns+" columns."
            );
            gd.showDialog();
            if (gd.wasCanceled()) return false;
            if (nSlices>1)
                measureAll = gd.getNextBoolean();
            onePerSlice = gd.getNextBoolean();
            if (!measureAll) nSlices = 1;
            int currentSlice = imp.getCurrentSlice();
            if (!onePerSlice)
                return measure(MULTI);

            Analyzer aSys = new Analyzer(); //System Analyzer
            ResultsTable rtSys = Analyzer.getResultsTable();
            ResultsTable rtMulti = new ResultsTable();
            Analyzer aMulti = new Analyzer(imp, measurements, rtMulti); //Private Analyzer

            for (int slice=1; slice<=nSlices; slice++) {
                int sliceUse = slice;
                if(nSlices == 1)sliceUse = currentSlice;
                imp.setSlice(sliceUse);
                rtMulti.incrementCounter();
                int roiIndex = 0;
                for (int i=0; i<indexes.length; i++) {
                    if (restore(indexes[i], false)) {
                        roiIndex++;
                        Roi roi = imp.getRoi();
                        ImageStatistics stats = imp.getStatistics(measurements);
                        aSys.saveResults(stats, roi); //Save measurements in system results table;
                        for (int j=0; j<=rtSys.getLastColumn(); j++){
                            float[] col = rtSys.getColumn(j);
                            String head = rtSys.getColumnHeading(j);
                            if (head!=null && col!=null && !head.equals("Slice"))
                                rtMulti.addValue(head+roiIndex,rtSys.getValue(j,rtSys.getCounter()-1));
                        }
                    } else
                        break;
                }
                aMulti.displayResults();
                aMulti.updateHeadings();
            }

            imp.setSlice(currentSlice);
            if (indexes.length>1)
                IJ.run("Select None");
            return true;
        }

        int getColumnCount(ImagePlus imp, int measurements) {
            ImageStatistics stats = imp.getStatistics(measurements);
            ResultsTable rt = new ResultsTable();
            Analyzer analyzer = new Analyzer(imp, measurements, rt);
            analyzer.saveResults(stats, null);
            int count = 0;
            for (int i=0; i<=rt.getLastColumn(); i++) {
                float[] col = rt.getColumn(i);
                String head = rt.getColumnHeading(i);
                if (head!=null && col!=null)
                    count++;
            }
            return count;
        }

        boolean drawOrFill(int mode) {
            int[] indexes = list.getSelectedIndexes();
            if (indexes.length==0)
                indexes = getAllIndexes();
            ImagePlus imp = WindowManager.getCurrentImage();
            imp.killRoi();
            ImageProcessor ip = imp.getProcessor();
            ip.setColor(Toolbar.getForegroundColor());
            ip.snapshot();
            Undo.setup(Undo.FILTER, imp);
            Filler filler = mode==LABEL?new Filler():null;
            int slice = imp.getCurrentSlice();
            for (int i=0; i<indexes.length; i++) {
                String name = list.getItem(i);
                Roi roi = (Roi)rois.get(name);
                int type = roi.getType();
                if (roi==null) continue;
                if (mode==FILL&&(type==Roi.POLYLINE||type==Roi.FREELINE||type==Roi.ANGLE))
                    mode = DRAW;
                int slice2 = getSliceNumber(name);
                if (slice2>=1 && slice2<=imp.getStackSize()) {
                    imp.setSlice(slice2);
                    ip = imp.getProcessor();
                    ip.setColor(Toolbar.getForegroundColor());
                    if (slice2!=slice) Undo.reset();
                }
                switch (mode) {
                    case DRAW: roi.drawPixels(ip); break;
                    case FILL: ip.fillPolygon(roi.getPolygon()); break;
                    case LABEL:
                        roi.drawPixels(ip);
                        filler.drawLabel(imp, ip, i+1, roi.getBounds());
                        break;
                }
            }
            ImageCanvas ic = imp.getCanvas();
            if (ic!=null) ic.setShowAllROIs(false);
            imp.updateAndDraw();
            String str=null;
            switch (mode) {
                case DRAW: str="Draw"; break;
                case FILL: str="Fill"; break;
                case LABEL: str="Label"; imp.updateAndDraw(); break;
            }
            if (Recorder.record) Recorder.record("roiManager", str);
            return true;
        }

        void combine() {
            ImagePlus imp = getImage();
            if (imp==null) return;
            int[] indexes = list.getSelectedIndexes();
            if (indexes.length==1) {
                error("More than one item must be selected, or none");
                return;
            }
            if (indexes.length==0)
                indexes = getAllIndexes();
            ShapeRoi s1=null, s2=null;
            for (int i=0; i<indexes.length; i++) {
                Roi roi = (Roi)rois.get(list.getItem(indexes[i]));
                if (roi.isLine() || roi.getType()==Roi.POINT)
                    continue;
                Calibration cal = imp.getCalibration();
                if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0) {
                    roi = (Roi)roi.clone();
                    Rectangle r = roi.getBounds();
                    roi.setLocation(r.x+(int)cal.xOrigin, r.y+(int)cal.yOrigin);
                }
                if (s1==null) {
                    if (roi instanceof ShapeRoi)
                        s1 = (ShapeRoi)roi;
                    else
                        s1 = new ShapeRoi(roi);
                    if (s1==null) return;
                } else {
                    if (roi instanceof ShapeRoi)
                        s2 = (ShapeRoi)roi;
                    else
                        s2 = new ShapeRoi(roi);
                    if (s2==null) continue;
                    if (roi.isArea())
                        s1.or(s2);
                }
            }
            if (s1!=null)
                imp.setRoi(s1);
            if (Recorder.record) Recorder.record("roiManager", "Combine");
        }

        void addParticles() {
            String err = IJ.runMacroFile("ij.jar:AddParticles", null);
            if (err!=null && err.length()>0)
                error(err);
        }

        void sort() {
            int n = rois.size();
            if (n==0) return;
            String[] labels = new String[n];
            int index = 0;
            for (Enumeration en=rois.keys(); en.hasMoreElements();)
                labels[index++] = (String)en.nextElement();
            list.removeAll();
            StringSorter.sort(labels);
            for (int i=0; i<labels.length; i++)
                list.add(labels[i]);
            if (Recorder.record) Recorder.record("roiManager", "Sort");
        }

        void specify() {
            try {IJ.run("Specify...");}
            catch (Exception e) {return;}
            runCommand("add");
        }

        void removeSliceInfo() {
            int[] indexes = list.getSelectedIndexes();
            if (indexes.length==0)
                indexes = getAllIndexes();
            for (int i=0; i<indexes.length; i++) {
                int index = indexes[i];
                String name = list.getItem(index);
                int n = getSliceNumber(name);
                if (n==-1) continue;
                String name2 = name.substring(5, name.length());
                name2 = getUniqueName(name2);
                Roi roi = (Roi)rois.get(name);
                rois.remove(name);
                roi.setName(name2);
                rois.put(name2, roi);
                list.replaceItem(name2, index);
            }
        }

        void help() {
            String macro = "run('URL...', 'url=http://rsb.info.nih.gov/ij/docs/menus/analyze.html#manager');";
            new MacroRunner(macro);
        }

        void split() {
            ImagePlus imp = getImage();
            if (imp==null) return;
            Roi roi = imp.getRoi();
            if (roi==null || roi.getType()!=Roi.COMPOSITE) {
                error("Image with composite selection required");
                return;
            }
            Roi[] rois = ((ShapeRoi)roi).getRois();
            for (int i=0; i<rois.length; i++) {
                imp.setRoi(rois[i]);
                add(false);
            }
            //if (Recorder.record) Recorder.record("roiManager", "Split");
        }

        void showAll() {
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp==null)
                {error("There are no images open."); return;}
            ImageCanvas ic = imp.getCanvas();
            if (ic==null) return;
            boolean showingROIs = ic.getShowAllROIs();
            ic.setShowAllROIs(!showingROIs);
            if (Recorder.record)
                Recorder.recordString("setOption(\"Show All\","+(showingROIs?"false":"true")+");\n");
            imp.draw();
        }

        void updateShowAll() {
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp==null) return;
            ImageCanvas ic = imp.getCanvas();
            if (ic!=null && ic.getShowAllROIs())
                imp.draw();
        }

        int[] getAllIndexes() {
            int count = list.getItemCount();
            int[] indexes = new int[count];
            for (int i=0; i<count; i++)
                indexes[i] = i;
            return indexes;
        }

        ImagePlus getImage() {
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp==null) {
                error("There are no images open.");
                return null;
            } else
                return imp;
        }

        boolean error(String msg) {
            new MessageDialog(this, "ROI Manager", msg);
            Macro.abort();
            return false;
        }

        public void processWindowEvent(WindowEvent e) {
            super.processWindowEvent(e);
            if (e.getID()==WindowEvent.WINDOW_CLOSING) {
                instance = null;
            }
            ignoreInterrupts = false;
        }

        /** Returns a reference to the ROI Manager
            or null if it is not open. */
        public static RoiManager getInstance() {
            return (RoiManager)instance;
        }

        /** Returns the ROI Hashtable.
            @see getCount
            @see getRoisAsArray
        */
        public Hashtable getROIs() {
            return rois;
        }

        /** Returns the selection list.
            @see getCount
            @see getRoisAsArray
        */
        public List getList() {
            return list;
        }

        /** Returns the ROI count. */
        public int getCount() {
            return list.getItemCount();
        }

        /** Returns the ROIs as an array. */
        public Roi[] getRoisAsArray() {
            int n = list.getItemCount();
            Roi[] array = new Roi[n];
            for (int i=0; i<n; i++) {
                String label = list.getItem(i);
                array[i] = (Roi)rois.get(label);
            }
            return array;
        }

        /** Returns the name of the selection with the specified index.
            Can be called from a macro using
            <pre>call("ij.plugin.frame.RoiManager.getName", index)</pre>
            Returns "null" if the Roi Manager is not open or index is
            out of range.
        */
        public static String getName(String index) {
            int i = (int)Tools.parseDouble(index, -1);
            RoiManager instance = getInstance();
            if (instance!=null && i>=0 && i<instance.list.getItemCount())
                return  instance.list.getItem(i);
            else
                return "null";
        }

        /** Executes the ROI Manager "Add", "Add & Draw", "Update", "Delete", "Measure", "Draw",
            "Fill", "Deselect", "Select All", "Combine", "Split" or "Sort" command. Returns false if <code>cmd</code>
            is not one of these strings. */
        public boolean runCommand(String cmd) {
            cmd = cmd.toLowerCase();
            macro = true;
            boolean ok = true;
            if (cmd.equals("add"))
                add(IJ.shiftKeyDown(), IJ.altKeyDown());
            else if (cmd.equals("add & draw"))
                addAndDraw(false);
            else if (cmd.equals("update"))
                update();
            else if (cmd.equals("delete"))
                delete(false);
            else if (cmd.equals("measure"))
                measure(COMMAND);
            else if (cmd.equals("draw"))
                drawOrFill(DRAW);
            else if (cmd.equals("fill"))
                drawOrFill(FILL);
            else if (cmd.equals("label"))
                drawOrFill(LABEL);
            else if (cmd.equals("combine"))
                combine();
            else if (cmd.equals("split"))
                split();
            else if (cmd.equals("sort"))
                sort();
            else if (cmd.equals("deselect")||cmd.indexOf("all")!=-1) {
                if (IJ.isMacOSX()) ignoreInterrupts = true;
                select(-1);
            } else if (cmd.equals("reset")) {
                list.removeAll();
                rois.clear();
            } else
                ok = false;
            macro = false;
            return ok;
        }

        /** Executes the ROI Manager "Open", "Save" or "Rename" command. Returns false if
        <code>cmd</code> is not "Open", "Save" or "Rename", or if an error occurs. */
        public boolean runCommand(String cmd, String name) {
            cmd = cmd.toLowerCase();
            macro = true;
            if (cmd.equals("open")) {
                open(name);
                macro = false;
                return true;
            } else if (cmd.equals("save")) {
                if (!name.endsWith(".zip") && !name.equals(""))
                    return error("Name must end with '.zip'");
                if (list.getItemCount()==0)
                    return error("The selection list is empty.");
                int[] indexes = getAllIndexes();
                boolean ok = false;
                if (name.equals(""))
                    ok = saveMultiple(indexes, null);
                else
                    ok = saveMultiple(indexes, name);
                macro = false;
                return ok;
            } else if (cmd.equals("rename")) {
                rename(name);
                macro = false;
                return true;
            }
            return false;
        }

        public void select(int index) {
            int n = list.getItemCount();
            if (index<0) {
                for (int i=0; i<n; i++)
                    if (list.isSelected(i)) list.deselect(i);
                return;
            }
            boolean mm = list.isMultipleMode();
            if (mm) list.setMultipleMode(false);
            if (index<n) {
                list.select(index);
                restore(index, true);
                if (!Interpreter.isBatchMode())
                    IJ.wait(10);
            }
            if (mm) list.setMultipleMode(true);
        }

        public void select(int index, boolean shiftKeyDown, boolean altKeyDown) {
            if (!(shiftKeyDown||altKeyDown))
                select(index);
            ImagePlus imp = IJ.getImage();
            if (imp==null) return;
            Roi previousRoi = imp.getRoi();
            if (previousRoi==null)
                {select(index); return;}
            Roi.previousRoi = (Roi)previousRoi.clone();
            String label = list.getItem(index);
            Roi roi = (Roi)rois.get(label);
            if (roi!=null) {
                roi.setImage(imp);
                roi.update(shiftKeyDown, altKeyDown);
            }
        }

        void selectAll() {
            boolean allSelected = true;
            int count = list.getItemCount();
            for (int i=0; i<count; i++) {
                if (!list.isIndexSelected(i))
                    allSelected = false;
            }
            if (allSelected)
                select(-1);
            else {
                for (int i=0; i<count; i++)
                    if (!list.isSelected(i)) list.select(i);
            }
        }

        /** Overrides PlugInFrame.close(). */
        public void close() {
            super.close();
            instance = null;
        }

        public void mousePressed (MouseEvent e) {
            int x=e.getX(), y=e.getY();
            if (e.isPopupTrigger() || e.isMetaDown())
                pm.show(e.getComponent(),x,y);
        }

        public void mouseWheelMoved(MouseWheelEvent event) {
            synchronized(this) {
                int index = list.getSelectedIndex();
                int rot = event.getWheelRotation();
                if (rot<-1) rot = -1;
                if (rot>1) rot = 1;
                index += rot;
                if (index<0) index = 0;
                if (index>=list.getItemCount()) index = list.getItemCount();
                //IJ.log(index+"  "+rot);
                select(index);
                if (IJ.isWindows())
                    list.requestFocusInWindow();
            }
        }

        public void mouseReleased (MouseEvent e) {}
        public void mouseClicked (MouseEvent e) {}
        public void mouseEntered (MouseEvent e) {}
        public void mouseExited (MouseEvent e) {}

    }

| RoiManager |