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) {}

}