| Menus |

    package ij;
    import ij.process.*;
    import ij.util.*;
    import ij.gui.ImageWindow;
    import ij.plugin.MacroInstaller;
    import java.awt.*;
    import java.awt.image.*;
    import java.awt.event.*;
    import java.util.*;
    import java.io.*;
    import java.applet.Applet;
    import java.awt.event.*;
    import java.util.zip.*;

    /**
    This class installs and updates ImageJ's menus. Note that menu labels,
    even in submenus, must be unique. This is because ImageJ uses a single
    hash table for all menu labels. If you look closely, you will see that
    File->Import->Text Image... and File->Save As->Text Image... do not use
    the same label. One of the labels has an extra space.

    @see ImageJ
    */

    public class Menus {

        public static final char PLUGINS_MENU = 'p';
        public static final char IMPORT_MENU = 'i';
        public static final char SAVE_AS_MENU = 's';
        public static final char SHORTCUTS_MENU = 'h'; // 'h'=hotkey
        public static final char ABOUT_MENU = 'a';
        public static final char FILTERS_MENU = 'f';
        public static final char TOOLS_MENU = 't';
        public static final char UTILITIES_MENU = 'u';

        public static final int WINDOW_MENU_ITEMS = 5; // fixed items at top of Window menu

        public static final int NORMAL_RETURN = 0;
        public static final int COMMAND_IN_USE = -1;
        public static final int INVALID_SHORTCUT = -2;
        public static final int SHORTCUT_IN_USE = -3;
        public static final int NOT_INSTALLED = -4;
        public static final int COMMAND_NOT_FOUND = -5;

        public static final int MAX_OPEN_RECENT_ITEMS = 15;

        private static MenuBar mbar;
        private static CheckboxMenuItem gray8Item,gray16Item,gray32Item,
                color256Item,colorRGBItem,RGBStackItem,HSBStackItem;
        private static PopupMenu popup;

        private static ImageJ ij;
        private static Applet applet;
        private static Hashtable demoImagesTable = new Hashtable();
        private static String pluginsPath, macrosPath;
        private static Menu pluginsMenu, importMenu, saveAsMenu, shortcutsMenu,
            aboutMenu, filtersMenu, toolsMenu, utilitiesMenu, macrosMenu, optionsMenu;
        private static Hashtable pluginsTable;

        static Menu window, openRecentMenu;
        int nPlugins, nMacros;
        private static Hashtable shortcuts = new Hashtable();
        private static Hashtable macroShortcuts;
        private static Vector pluginsPrefs = new Vector(); // commands saved in IJ_Prefs
        static int windowMenuItems2; // non-image windows listed in Window menu + separator
        private static String error;
        private String jarError;
        private String pluginError;
        private boolean isJarErrorHeading;
        private boolean installingJars, duplicateCommand;
        private static Vector jarFiles;  // JAR files in plugins folder with "_" in their name
        private static Vector macroFiles;  // Macro files in plugins folder with "_" in their name
        private int importCount, saveAsCount, toolsCount, optionsCount;
        private static Hashtable menusTable; // Submenus of Plugins menu
        private int userPluginsIndex; // First user plugin or submenu in Plugins menu
        private boolean addSorted;
        private  static int fontSize = Prefs.getInt(Prefs.MENU_SIZE, 14);
        private static Font menuFont;
        static boolean jnlp; // true when using Java WebStart

        Menus(ImageJ ijInstance, Applet appletInstance) {
            ij = ijInstance;
            applet = appletInstance;
        }

        String addMenuBar() {
            error = null;
            pluginsTable = new Hashtable();

            Menu file = new Menu("File");
            addSubMenu(file, "New");
            addPlugInItem(file, "Open...", "ij.plugin.Commands(\"open\")", KeyEvent.VK_O, false);
            addPlugInItem(file, "Open Next", "ij.plugin.NextImageOpener", KeyEvent.VK_O, true);
            addSubMenu(file, "Open Samples");
            addOpenRecentSubMenu(file);
            importMenu = addSubMenu(file, "Import");
            file.addSeparator();
            addPlugInItem(file, "Close", "ij.plugin.Commands(\"close\")", KeyEvent.VK_W, false);
            addPlugInItem(file, "Save", "ij.plugin.Commands(\"save\")", KeyEvent.VK_S, false);
            saveAsMenu = addSubMenu(file, "Save As");
            addPlugInItem(file, "Revert", "ij.plugin.Commands(\"revert\")", KeyEvent.VK_R,  false);
            file.addSeparator();
            addPlugInItem(file, "Page Setup...", "ij.plugin.filter.Printer(\"setup\")", 0, false);
            addPlugInItem(file, "Print...", "ij.plugin.filter.Printer(\"print\")", KeyEvent.VK_P, false);
            file.addSeparator();
            addPlugInItem(file, "Quit", "ij.plugin.Commands(\"quit\")", 0, false);

            Menu edit = new Menu("Edit");
            addPlugInItem(edit, "Undo", "ij.plugin.Commands(\"undo\")", KeyEvent.VK_Z, false);
            edit.addSeparator();
            addPlugInItem(edit, "Cut", "ij.plugin.Clipboard(\"cut\")", KeyEvent.VK_X, false);
            addPlugInItem(edit, "Copy", "ij.plugin.Clipboard(\"copy\")", KeyEvent.VK_C, false);
            addPlugInItem(edit, "Copy to System", "ij.plugin.Clipboard(\"scopy\")", 0, false);
            addPlugInItem(edit, "Paste", "ij.plugin.Clipboard(\"paste\")", KeyEvent.VK_V, false);
            addPlugInItem(edit, "Paste Control...", "ij.plugin.frame.PasteController", 0, false);
            edit.addSeparator();
            addPlugInItem(edit, "Clear", "ij.plugin.filter.Filler(\"clear\")", 0, false);
            addPlugInItem(edit, "Clear Outside", "ij.plugin.filter.Filler(\"outside\")", 0, false);
            addPlugInItem(edit, "Fill", "ij.plugin.filter.Filler(\"fill\")", KeyEvent.VK_F, false);
            addPlugInItem(edit, "Draw", "ij.plugin.filter.Filler(\"draw\")", KeyEvent.VK_D, false);
            addPlugInItem(edit, "Invert", "ij.plugin.filter.Filters(\"invert\")", KeyEvent.VK_I, true);
            edit.addSeparator();
            addSubMenu(edit, "Selection");
            optionsMenu = addSubMenu(edit, "Options");

            Menu image = new Menu("Image");
            Menu imageType = new Menu("Type");
                gray8Item = addCheckboxItem(imageType, "8-bit", "ij.plugin.Converter(\"8-bit\")");
                gray16Item = addCheckboxItem(imageType, "16-bit", "ij.plugin.Converter(\"16-bit\")");
                gray32Item = addCheckboxItem(imageType, "32-bit", "ij.plugin.Converter(\"32-bit\")");
                color256Item = addCheckboxItem(imageType, "8-bit Color", "ij.plugin.Converter(\"8-bit Color\")");
                colorRGBItem = addCheckboxItem(imageType, "RGB Color", "ij.plugin.Converter(\"RGB Color\")");
                imageType.add(new MenuItem("-"));
                RGBStackItem = addCheckboxItem(imageType, "RGB Stack", "ij.plugin.Converter(\"RGB Stack\")");
                HSBStackItem = addCheckboxItem(imageType, "HSB Stack", "ij.plugin.Converter(\"HSB Stack\")");
                image.add(imageType);

            image.addSeparator();
            addSubMenu(image, "Adjust");
            addPlugInItem(image, "Show Info...", "ij.plugin.filter.Info", KeyEvent.VK_I, false);
            addPlugInItem(image, "Properties...", "ij.plugin.filter.ImageProperties", KeyEvent.VK_P, true);
            //addSubMenu(image, "Benchmarks");
            addSubMenu(image, "Color");
            addSubMenu(image, "Stacks");
            image.addSeparator();
            addPlugInItem(image, "Crop", "ij.plugin.filter.Resizer(\"crop\")", KeyEvent.VK_X, true);
            addPlugInItem(image, "Duplicate...", "ij.plugin.filter.Duplicater", KeyEvent.VK_D, true);
            addPlugInItem(image, "Rename...", "ij.plugin.SimpleCommands(\"rename\")", 0, false);
            addPlugInItem(image, "Scale...", "ij.plugin.Scaler", KeyEvent.VK_E, false);
            addSubMenu(image, "Rotate");
            addSubMenu(image, "Zoom");
            image.addSeparator();
            addSubMenu(image, "Lookup Tables");

            Menu process = new Menu("Process");
            addPlugInItem(process, "Smooth", "ij.plugin.filter.Filters(\"smooth\")", KeyEvent.VK_S, true);
            addPlugInItem(process, "Sharpen", "ij.plugin.filter.Filters(\"sharpen\")", 0, false);
            addPlugInItem(process, "Find Edges", "ij.plugin.filter.Filters(\"edge\")", 0, false);
            addPlugInItem(process, "Enhance Contrast", "ij.plugin.ContrastEnhancer", 0, false);
            addSubMenu(process, "Noise");
            addSubMenu(process, "Shadows");
            addSubMenu(process, "Binary");
            addSubMenu(process, "Math");
            addSubMenu(process, "FFT");
            filtersMenu = addSubMenu(process, "Filters");
            process.addSeparator();
            addPlugInItem(process, "Image Calculator...", "ij.plugin.ImageCalculator", 0, false);
            addPlugInItem(process, "Subtract Background...", "ij.plugin.filter.BackgroundSubtracter", 0, false);
            addItem(process, "Repeat Command", KeyEvent.VK_R, true);

            Menu analyze = new Menu("Analyze");
            addPlugInItem(analyze, "Measure", "ij.plugin.filter.Analyzer", KeyEvent.VK_M, false);
            addPlugInItem(analyze, "Analyze Particles...", "ij.plugin.filter.ParticleAnalyzer", 0, false);
            addPlugInItem(analyze, "Summarize", "ij.plugin.filter.Analyzer(\"sum\")", 0, false);
            addPlugInItem(analyze, "Distribution...", "ij.plugin.Distribution", 0, false);
            addPlugInItem(analyze, "Label", "ij.plugin.filter.Filler(\"label\")", 0, false);
            addPlugInItem(analyze, "Clear Results", "ij.plugin.filter.Analyzer(\"clear\")", 0, false);
            addPlugInItem(analyze, "Set Measurements...", "ij.plugin.filter.Analyzer(\"set\")", 0, false);
            analyze.addSeparator();
            addPlugInItem(analyze, "Set Scale...", "ij.plugin.filter.ScaleDialog", 0, false);
            addPlugInItem(analyze, "Calibrate...", "ij.plugin.filter.Calibrator", 0, false);
            addPlugInItem(analyze, "Histogram", "ij.plugin.Histogram", KeyEvent.VK_H, false);
            addPlugInItem(analyze, "Plot Profile", "ij.plugin.filter.Profiler(\"plot\")", KeyEvent.VK_K, false);
            addPlugInItem(analyze, "Surface Plot...", "ij.plugin.SurfacePlotter", 0, false);
            addSubMenu(analyze, "Gels");
            toolsMenu = addSubMenu(analyze, "Tools");

            window = new Menu("Window");
            addPlugInItem(window, "Show All", "ij.plugin.WindowOrganizer(\"show\")", KeyEvent.VK_F, true);
            addPlugInItem(window, "Put Behind [tab]", "ij.plugin.Commands(\"tab\")", 0, false);
            addPlugInItem(window, "Cascade", "ij.plugin.WindowOrganizer(\"cascade\")", 0, false);
            addPlugInItem(window, "Tile", "ij.plugin.WindowOrganizer(\"tile\")", 0, false);
            window.addSeparator();

            Menu help = new Menu("Help");
            addPlugInItem(help, "ImageJ Website...", "ij.plugin.BrowserLauncher", 0, false);
            addPlugInItem(help, "ImageJ News...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/notes.html\")", 0, false);
            addPlugInItem(help, "Documentation...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/docs\")", 0, false);
            addPlugInItem(help, "Installation...", "ij.plugin.SimpleCommands(\"install\")", 0, false);
            addPlugInItem(help, "Search Website...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/search.html\")", 0, false);
            addPlugInItem(help, "List Archives...", "ij.plugin.BrowserLauncher(\"https://list.nih.gov/archives/imagej.html\")", 0, false);
            help.addSeparator();
            addPlugInItem(help, "Plugins...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/plugins\")", 0, false);
            addPlugInItem(help, "Macros...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/macros/\")", 0, false);
            addPlugInItem(help, "Source Code...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/developer/source/\")", 0, false);
            addPlugInItem(help, "Macro Language...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/developer/macro/macros.html\")", 0, false);
            addPlugInItem(help, "Macro Functions...", "ij.plugin.BrowserLauncher(\"http://rsb.info.nih.gov/ij/developer/macro/functions.html\")", 0, false);
            help.addSeparator();
            aboutMenu = addSubMenu(help, "About Plugins");
            addPlugInItem(help, "About ImageJ...", "ij.plugin.AboutBox", 0, false);

            addPluginsMenu();
            if (applet==null)
                installPlugins();

            mbar = new MenuBar();
            if (fontSize!=0)
                mbar.setFont(getFont());
            mbar.add(file);
            mbar.add(edit);
            mbar.add(image);
            mbar.add(process);
            mbar.add(analyze);
            mbar.add(pluginsMenu);
            mbar.add(window);
            mbar.setHelpMenu(help);
            if (ij!=null)
                ij.setMenuBar(mbar);

            if (pluginError!=null)
                error = error!=null?error+="\n"+pluginError:pluginError;
            if (jarError!=null)
                error = error!=null?error+="\n"+jarError:jarError;
            return error;
        }

        void addOpenRecentSubMenu(Menu menu) {
            openRecentMenu = new Menu("Open Recent");
            for (int i=0; i<MAX_OPEN_RECENT_ITEMS; i++) {
                String path = Prefs.getString("recent" + (i/10)%10 + i%10);
                if (path==null) break;
                MenuItem item = new MenuItem(path);
                openRecentMenu.add(item);
                item.addActionListener(ij);
            }
            menu.add(openRecentMenu);
        }

        void addItem(Menu menu, String label, int shortcut, boolean shift) {
            if (menu==null)
                return;
            MenuItem item;
            if (shortcut==0)
                item = new MenuItem(label);
            else {
                if (shift) {
                    item = new MenuItem(label, new MenuShortcut(shortcut, true));
                    shortcuts.put(new Integer(shortcut+200),label);
                } else {
                    item = new MenuItem(label, new MenuShortcut(shortcut));
                    shortcuts.put(new Integer(shortcut),label);
                }
            }
            if (addSorted) {
                if (menu==pluginsMenu)
                    addItemSorted(menu, item, userPluginsIndex);
                else
                    addOrdered(menu, item);
            } else
                menu.add(item);
            item.addActionListener(ij);
        }

        void addPlugInItem(Menu menu, String label, String className, int shortcut, boolean shift) {
            pluginsTable.put(label, className);
            nPlugins++;
            addItem(menu, label, shortcut, shift);
        }

        CheckboxMenuItem addCheckboxItem(Menu menu, String label, String className) {
            pluginsTable.put(label, className);
            nPlugins++;
            CheckboxMenuItem item = new CheckboxMenuItem(label);
            menu.add(item);
            item.addItemListener(ij);
            item.setState(false);
            return item;
        }

        Menu addSubMenu(Menu menu, String name) {
            String value;
            String key = name.toLowerCase(Locale.US);
            int index;
            Menu submenu=new Menu(name.replace('_', ' '));
            index = key.indexOf(' ');
            if (index>0)
                key = key.substring(0, index);
            for (int count=1; count<100; count++) {
                value = Prefs.getString(key + (count/10)%10 + count%10);
                if (value==null)
                    break;
                if (count==1)
                    menu.add(submenu);
                if (value.equals("-"))
                    submenu.addSeparator();
                else
                    addPluginItem(submenu, value);
            }
            if (name.equals("Lookup Tables") && applet==null)
                addLuts(submenu);
            return submenu;
        }

        void addLuts(Menu submenu) {
            String path = Prefs.getHomeDir()+File.separator;
            File f = new File(path+"luts");
            String[] list = null;
            if (applet==null && f.exists() && f.isDirectory())
                list = f.list();
            if (list==null) return;
            if (IJ.isLinux()) StringSorter.sort(list);
            submenu.addSeparator();
            for (int i=0; i<list.length; i++) {
                String name = list[i];
                if (name.endsWith(".lut")) {
                    name = name.substring(0,name.length()-4);
                    MenuItem item = new MenuItem(name);
                    submenu.add(item);
                    item.addActionListener(ij);
                    nPlugins++;
                }
            }
        }

        void addPluginItem(Menu submenu, String s) {
            if (s.startsWith("\"-\"")) {
                // add menu separator if command="-"
                addSeparator(submenu);
                return;
            }
            int lastComma = s.lastIndexOf(',');
            if (lastComma<=0)
                return;
            String command = s.substring(1,lastComma-1);
            int keyCode = 0;
            boolean shift = false;
            if (command.endsWith("]")) {
                int openBracket = command.lastIndexOf('[');
                if (openBracket>0) {
                    String shortcut = command.substring(openBracket+1,command.length()-1);
                    keyCode = convertShortcutToCode(shortcut);
                    boolean functionKey = keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12;
                    if (keyCode>0 && !functionKey)
                        command = command.substring(0,openBracket);
                    //IJ.write(command+": "+shortcut);
                }
            }
            if (keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12) {
                shortcuts.put(new Integer(keyCode),command);
                keyCode = 0;
            } else if (keyCode>=265 && keyCode<=290) {
                keyCode -= 200;
                shift = true;
            }
            addItem(submenu,command,keyCode,shift);
            while(s.charAt(lastComma+1)==' ' && lastComma+2<s.length())
                lastComma++; // remove leading spaces
            String className = s.substring(lastComma+1,s.length());
            //IJ.log(command+"  "+className);
            if (installingJars)
                duplicateCommand = pluginsTable.get(command)!=null;
            pluginsTable.put(command, className);
            nPlugins++;
        }

        void checkForDuplicate(String command) {
            if (pluginsTable.get(command)!=null) {
            }
        }

        void addPluginsMenu() {
            String value,label,className;
            int index;
            pluginsMenu = new Menu("Plugins");
            for (int count=1; count<100; count++) {
                value = Prefs.getString("plug-in" + (count/10)%10 + count%10);
                if (value==null)
                    break;
                char firstChar = value.charAt(0);
                if (firstChar=='-')
                    pluginsMenu.addSeparator();
                else if (firstChar=='>') {
                    String submenu = value.substring(2,value.length()-1);
                    Menu menu = addSubMenu(pluginsMenu, submenu);
                    if (submenu.equals("Shortcuts"))
                        shortcutsMenu = menu;
                    else if (submenu.equals("Utilities"))
                        utilitiesMenu = menu;
                    else if (submenu.equals("Macros"))
                        macrosMenu = menu;
                } else
                    addPluginItem(pluginsMenu, value);
            }
            userPluginsIndex = pluginsMenu.getItemCount();
            if (userPluginsIndex<0) userPluginsIndex = 0;
        }

        /** Install plugins using "pluginxx=" keys in IJ_Prefs.txt.
            Plugins not listed in IJ_Prefs are added to the end
            of the Plugins menu. */
        void installPlugins() {
            String value, className;
            char menuCode;
            Menu menu;
            String[] plugins = getPlugins();
            String[] plugins2 = null;
            Hashtable skipList = new Hashtable();
            for (int index=0; index<100; index++) {
                value = Prefs.getString("plugin" + (index/10)%10 + index%10);
                if (value==null)
                    break;
                menuCode = value.charAt(0);
                switch (menuCode) {
                    case PLUGINS_MENU: default: menu = pluginsMenu; break;
                    case IMPORT_MENU: menu = importMenu; break;
                    case SAVE_AS_MENU: menu = saveAsMenu; break;
                    case SHORTCUTS_MENU: menu = shortcutsMenu; break;
                    case ABOUT_MENU: menu = aboutMenu; break;
                    case FILTERS_MENU: menu = filtersMenu; break;
                    case TOOLS_MENU: menu = toolsMenu; break;
                    case UTILITIES_MENU: menu = utilitiesMenu; break;
                }
                String prefsValue = value;
                value = value.substring(2,value.length()); //remove menu code and coma
                className = value.substring(value.lastIndexOf(',')+1,value.length());
                boolean found = className.startsWith("ij.");
                if (!found && plugins!=null) { // does this plugin exist?
                    if (plugins2==null)
                        plugins2 = getStrippedPlugins(plugins);
                    for (int i=0; i<plugins2.length; i++) {
                        if (className.startsWith(plugins2[i])) {
                            found = true;
                            break;
                        }
                    }
                }
                if (found) {
                    addPluginItem(menu, value);
                    pluginsPrefs.addElement(prefsValue);
                    if (className.endsWith("\")")) { // remove any argument
                        int argStart = className.lastIndexOf("(\"");
                        if (argStart>0)
                            className = className.substring(0, argStart);
                    }
                    skipList.put(className, "");
                }
            }
            if (plugins!=null) {
                for (int i=0; i<plugins.length; i++) {
                    if (!skipList.containsKey(plugins[i]))
                        installUserPlugin(plugins[i]);
                }
            }
            installJarPlugins();
            installMacros();
        }

        /** Installs macro files that are located in the plugins folder and have a "_" in their name. */
        void installMacros() {
            if (macroFiles==null)
                return;
            for (int i=0; i<macroFiles.size(); i++) {
                String name = (String)macroFiles.elementAt(i);
                installMacro(name);
            }
        }

        /** Installs a macro in the Plugins menu, or submenu, with
            with underscores in the file name replaced by spaces. */
        void installMacro(String name) {
            Menu menu = pluginsMenu;
            String dir = null;
            int slashIndex = name.indexOf('/');
            if (slashIndex>0) {
                dir = name.substring(0, slashIndex);
                name = name.substring(slashIndex+1, name.length());
                menu = getPluginsSubmenu(dir);
            }
            String command = name.replace('_',' ');
            command = command.substring(0, command.length()-4); //remove ".txt" or ".ijm"
            command.trim();
            if (pluginsTable.get(command)!=null) // duplicate command?
                command = command + " Macro";
            MenuItem item = new MenuItem(command);
            addOrdered(menu, item);
            item.addActionListener(ij);
            String path = (dir!=null?dir+File.separator:"") + name;
            pluginsTable.put(command, "ij.plugin.Macro_Runner(\""+path+"\")");
            nMacros++;
        }

        /** Inserts 'item' into 'menu' in alphanumeric order. */
        void addOrdered(Menu menu, MenuItem item) {
            if (menu==pluginsMenu)
                {menu.add(item); return;}
            String label = item.getLabel();
            for (int i=0; i<menu.getItemCount(); i++) {
                if (label.compareTo(menu.getItem(i).getLabel())<0) {
                    menu.insert(item, i);
                    return;
                }
            }
            menu.add(item);
        }

        /** Install plugins located in JAR files. */
        void installJarPlugins() {
            if (jarFiles==null)
                return;
            installingJars = true;
            for (int i=0; i<jarFiles.size(); i++) {
                isJarErrorHeading = false;
                String jar = (String)jarFiles.elementAt(i);
                InputStream is = getConfigurationFile(jar);
                if (is==null) continue;
                LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is));
                try {
                    while(true) {
                        String s = lnr.readLine();
                        if (s==null) break;
                        installJarPlugin(jar, s);
                    }
                }
                catch (IOException e) {}
                finally {
                    try {if (lnr!=null) lnr.close();}
                    catch (IOException e) {}
                }
            }
        }

        /** Install a plugin located in a JAR file. */
        void installJarPlugin(String jar, String s) {
            //IJ.log(s);
            if (s.length()<3) return;
            char firstChar = s.charAt(0);
            if (firstChar=='#') return;
            addSorted = false;
            Menu menu;
            if (s.startsWith("Plugins>")) {
                int firstComma = s.indexOf(',');
                if (firstComma==-1 || firstComma<=8)
                    menu = null;
                else {
                    String name = s.substring(8, firstComma);
                    menu = getPluginsSubmenu(name);
                }
            } else if (firstChar=='"' || s.startsWith("Plugins")) {
                String name = getSubmenuName(jar);
                if (name!=null)
                    menu = getPluginsSubmenu(name);
                else
                    menu = pluginsMenu;
                addSorted = true;
            } else if (s.startsWith("File>Import")) {
                menu = importMenu;
                if (importCount==0) addSeparator(menu);
                importCount++;
            } else if (s.startsWith("File>Save")) {
                menu = saveAsMenu;
                if (saveAsCount==0) addSeparator(menu);
                saveAsCount++;
            } else if (s.startsWith("Analyze>Tools")) {
                menu = toolsMenu;
                if (toolsCount==0) addSeparator(menu);
                toolsCount++;
            } else if (s.startsWith("Help>About")) {
                menu = aboutMenu;
            } else if (s.startsWith("Edit>Options")) {
                menu = optionsMenu;
                if (optionsCount==0) addSeparator(menu);
                optionsCount++;
            } else {
                if (jarError==null) jarError = "";
                addJarErrorHeading(jar);
                jarError += "    Invalid menu: " + s + "\n";
                return;
            }
            int firstQuote = s.indexOf('"');
            if (firstQuote==-1)
                return;
            s = s.substring(firstQuote, s.length()); // remove menu
            if (menu!=null) {
                addPluginItem(menu, s);
                addSorted = false;
            }
            if (duplicateCommand) {
                if (jarError==null) jarError = "";
                addJarErrorHeading(jar);
                jarError += "    Duplicate command: " + s + "\n";
            }
            duplicateCommand = false;
        }

        void addJarErrorHeading(String jar) {
            if (!isJarErrorHeading) {
                    if (!jarError.equals(""))
                        jarError += " \n";
                    jarError += "Plugin configuration error: " + jar + "\n";
                    isJarErrorHeading = true;
                }
        }

        Menu getPluginsSubmenu(String submenuName) {
            if (menusTable!=null) {
                Menu menu = (Menu)menusTable.get(submenuName);
                if (menu!=null)
                    return menu;
            }
            Menu menu = new Menu(submenuName);
            //pluginsMenu.add(menu);
            addItemSorted(pluginsMenu, menu, userPluginsIndex);
            if (menusTable==null) menusTable = new Hashtable();
            menusTable.put(submenuName, menu);
            //IJ.log("getPluginsSubmenu: "+submenuName);
            return menu;
        }

        String getSubmenuName(String jarPath) {
            //IJ.log("getSubmenuName: \n"+jarPath+"\n"+pluginsPath);
            if (jarPath.startsWith(pluginsPath))
                jarPath = jarPath.substring(pluginsPath.length() - 1);
            int index = jarPath.lastIndexOf(File.separatorChar);
            if (index<0) return null;
            String name = jarPath.substring(0, index);
            index = name.lastIndexOf(File.separatorChar);
            if (index<0) return null;
            name = name.substring(index+1);
            if (name.equals("plugins")) return null;
            return name;
        }

        void addItemSorted(Menu menu, MenuItem item, int startingIndex) {
            String itemLabel = item.getLabel();
            int count = menu.getItemCount();
            boolean inserted = false;
            for (int i=startingIndex; i<count; i++) {
                MenuItem mi = menu.getItem(i);
                String label = mi.getLabel();
                //IJ.log(i+ "  "+itemLabel+"  "+label + "  "+(itemLabel.compareTo(label)));
                if (itemLabel.compareTo(label)<0) {
                    menu.insert(item, i);
                    inserted = true;
                    break;
                }
            }
            if (!inserted) menu.add(item);
        }

        void addSeparator(Menu menu) {
            menu.addSeparator();
        }

        /** Opens the configuration file ("plugins.txt") from a JAR file and returns it as an InputStream. */
        InputStream getConfigurationFile(String jar) {
            try {
                ZipFile jarFile = new ZipFile(jar);
                Enumeration entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = (ZipEntry) entries.nextElement();
                    if (entry.getName().endsWith("plugins.config"))
                        return jarFile.getInputStream(entry);
                }
            }
            catch (Exception e) {}
            return autoGenerateConfigFile(jar);
        }

        /** Creates a configuration file for JAR/ZIP files that do not have one. */
        InputStream autoGenerateConfigFile(String jar) {
            StringBuffer sb = null;
            try {
                ZipFile jarFile = new ZipFile(jar);
                Enumeration entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = (ZipEntry) entries.nextElement();
                    String name = entry.getName();
                    if (name.endsWith(".class") && name.indexOf("_")>0 && name.indexOf("$")==-1
                    && name.indexOf("/_")==-1 && !name.startsWith("_")) {
                        if (sb==null) sb = new StringBuffer();
                        String className = name.substring(0, name.length()-6);
                        int slashIndex = className.lastIndexOf('/');
                        String plugins = "Plugins";
                        if (slashIndex >= 0) {
                            plugins += ">" + className.substring(0, slashIndex).replace('/', '>').replace('_', ' ');
                            name = className.substring(slashIndex + 1);
                        } else
                            name = className;
                        name = name.replace('_', ' ');
                        className = className.replace('/', '.');
                        sb.append(plugins + ", \""+name+"\", "+className+"\n");
                    }
                }
            }
            catch (Exception e) {}
            //IJ.log(""+(sb!=null?sb.toString():"null"));
            if (sb==null)
                return null;
            else
                return new ByteArrayInputStream(sb.toString().getBytes());
        }

        /** Returns a list of the plugins with directory names removed. */
        String[] getStrippedPlugins(String[] plugins) {
            String[] plugins2 = new String[plugins.length];
            int slashPos;
            for (int i=0; i<plugins2.length; i++) {
                plugins2[i] = plugins[i];
                slashPos = plugins2[i].lastIndexOf('/');
                if (slashPos>=0)
                    plugins2[i] = plugins[i].substring(slashPos+1,plugins2[i].length());
            }
            return plugins2;
        }

        /** Returns a list of the plugins in the plugins menu. */
        public static synchronized String[] getPlugins() {
            String homeDir = Prefs.getHomeDir();
            if (homeDir==null)
                return null;
            if (homeDir.endsWith("plugins"))
                pluginsPath = homeDir+Prefs.separator;
            else {
                String property = System.getProperty("plugins.dir");
                if (property!=null && (property.endsWith("/")||property.endsWith("\\")))
                    property = property.substring(0, property.length()-1);
                String pluginsDir = property;
                if (pluginsDir==null)
                    pluginsDir = homeDir;
                else if (pluginsDir.equals("user.home")) {
                    pluginsDir = System.getProperty("user.home");
                    if (!(new File(pluginsDir+Prefs.separator+"plugins")).isDirectory())
                        pluginsDir = pluginsDir + Prefs.separator + "ImageJ";
                    property = null;
                    // needed to run plugins when ImageJ launched using Java WebStart
                    if (applet==null) System.setSecurityManager(null);
                    jnlp = true;
                }
                pluginsPath = pluginsDir+Prefs.separator+"plugins"+Prefs.separator;
                if (property!=null&&!(new File(pluginsPath)).isDirectory())
                    pluginsPath = pluginsDir + Prefs.separator;
                macrosPath = pluginsDir+Prefs.separator+"macros"+Prefs.separator;
            }
            File f = macrosPath!=null?new File(macrosPath):null;
            if (f!=null && !f.isDirectory())
                macrosPath = null;
            f = pluginsPath!=null?new File(pluginsPath):null;
            if (f==null || (f!=null && !f.isDirectory())) {
                //error = "Plugins folder not found at "+pluginsPath;
                pluginsPath = null;
                return null;
            }
            String[] list = f.list();
            if (list==null)
                return null;
            Vector v = new Vector();
            jarFiles = null;
            macroFiles = null;
            for (int i=0; i<list.length; i++) {
                String name = list[i];
                boolean isClassFile = name.endsWith(".class");
                boolean hasUnderscore = name.indexOf('_')>=0;
                if (hasUnderscore && isClassFile && name.indexOf('$')<0 ) {
                    name = name.substring(0, name.length()-6); // remove ".class"
                    v.addElement(name);
                } else if (hasUnderscore && (name.endsWith(".jar") || name.endsWith(".zip"))) {
                    if (jarFiles==null) jarFiles = new Vector();
                    jarFiles.addElement(pluginsPath + name);
                } else if (hasUnderscore && (name.endsWith(".txt")||name.endsWith(".ijm"))) {
                    if (macroFiles==null) macroFiles = new Vector();
                    macroFiles.addElement(name);
                } else {
                    if (!isClassFile)
                        checkSubdirectory(pluginsPath, name, v);
                }
            }
            list = new String[v.size()];
            v.copyInto((String[])list);
            StringSorter.sort(list);
            return list;
        }

        /** Looks for plugins and jar files in a subdirectory of the plugins directory. */
        static void checkSubdirectory(String path, String dir, Vector v) {
            if (dir.endsWith(".java"))
                return;
            File f = new File(path, dir);
            if (!f.isDirectory())
                return;
            String[] list = f.list();
            if (list==null)
                return;
            dir += "/";
            for (int i=0; i<list.length; i++) {
                String name = list[i];
                boolean hasUnderscore = name.indexOf('_')>=0;
                if (hasUnderscore && name.endsWith(".class") && name.indexOf('$')<0) {
                    name = name.substring(0, name.length()-6); // remove ".class"
                    v.addElement(dir+name);
                    //IJ.write("File: "+f+"/"+name);
                } else if (hasUnderscore && (name.endsWith(".jar") || name.endsWith(".zip"))) {
                    if (jarFiles==null) jarFiles = new Vector();
                    jarFiles.addElement(f.getPath() + File.separator + name);
                } else if (hasUnderscore && (name.endsWith(".txt")||name.endsWith(".ijm"))) {
                    if (macroFiles==null) macroFiles = new Vector();
                    macroFiles.addElement(dir + name);
                }
            }
        }

        static String submenuName;
        static Menu submenu;

        /** Installs a plugin in the Plugins menu using the class name,
            with underscores replaced by spaces, as the command. */
        void installUserPlugin(String className) {
            Menu menu = pluginsMenu;
            int slashIndex = className.indexOf('/');
            String command = className;
            if (slashIndex>0) {
                String dir = className.substring(0, slashIndex);
                command = className.substring(slashIndex+1, className.length());
                //className = className.replace('/', '.');
                if (submenu==null || !submenuName.equals(dir)) {
                    submenuName = dir;
                    submenu = new Menu(submenuName);
                    pluginsMenu.add(submenu);
                    if (menusTable==null) menusTable = new Hashtable();
                    menusTable.put(submenuName, submenu);
                }
                menu = submenu;
            //IJ.write(dir + "  " + className);
            }
            command = command.replace('_',' ');
            command.trim();
            if (pluginsTable.get(command)!=null)  // duplicate command?
                command = command + " Plugin";
            MenuItem item = new MenuItem(command);
            menu.add(item);
            item.addActionListener(ij);
            pluginsTable.put(command, className.replace('/', '.'));
            nPlugins++;
        }

        void installPopupMenu(ImageJ ij) {
            String s;
            int count = 0;
            MenuItem mi;
            popup = new PopupMenu("");
            if (fontSize!=0)
                popup.setFont(getFont());

            while (true) {
                count++;
                s = Prefs.getString("popup" + (count/10)%10 + count%10);
                if (s==null)
                    break;
                if (s.equals("-"))
                    popup.addSeparator();
                else if (!s.equals("")) {
                    mi = new MenuItem(s);
                    mi.addActionListener(ij);
                    popup.add(mi);
                }
            }
        }

        public static MenuBar getMenuBar() {
            return mbar;
        }

        public static Menu getMacrosMenu() {
            return macrosMenu;
        }

        static final int RGB_STACK=10, HSB_STACK=11;

        /** Updates the Image/Type and Window menus. */
        public static void updateMenus() {

            if (ij==null) return;
            gray8Item.setState(false);
            gray16Item.setState(false);
            gray32Item.setState(false);
            color256Item.setState(false);
            colorRGBItem.setState(false);
            RGBStackItem.setState(false);
            HSBStackItem.setState(false);
            ImagePlus imp = WindowManager.getCurrentImage();
            if (imp==null)
                return;
            int type = imp.getType();
            if (imp.getStackSize()>1) {
                ImageStack stack = imp.getStack();
                if (stack.isRGB()) type = RGB_STACK;
                else if (stack.isHSB()) type = HSB_STACK;
            }
            if (type==ImagePlus.GRAY8) {
                ImageProcessor ip = imp.getProcessor();
                if (ip!=null && ip.getMinThreshold()==ImageProcessor.NO_THRESHOLD && ip.isColorLut()) {
                    type = ImagePlus.COLOR_256;
                    if (!ip.isPseudoColorLut())
                        imp.setType(ImagePlus.COLOR_256);
                }
            }
            switch (type) {
                case ImagePlus.GRAY8:
                    gray8Item.setState(true);
                    break;
                case ImagePlus.GRAY16:
                    gray16Item.setState(true);
                    break;
                case ImagePlus.GRAY32:
                    gray32Item.setState(true);
                    break;
                case ImagePlus.COLOR_256:
                    color256Item.setState(true);
                    break;
                case ImagePlus.COLOR_RGB:
                    colorRGBItem.setState(true);
                    break;
                case RGB_STACK:
                    RGBStackItem.setState(true);
                    break;
                case HSB_STACK:
                    HSBStackItem.setState(true);
                    break;
            }

            //update Window menu
            int nItems = window.getItemCount();
            int start = WINDOW_MENU_ITEMS + windowMenuItems2;
            int index = start + WindowManager.getCurrentIndex();
            try {  // workaround for Linux/Java 5.0/bug
                for (int i=start; i<nItems; i++) {
                    CheckboxMenuItem item = (CheckboxMenuItem)window.getItem(i);
                    item.setState(i==index);
                }
            } catch (NullPointerException e) {}
        }

        static boolean isColorLut(ImagePlus imp) {
            ImageProcessor ip = imp.getProcessor();
            IndexColorModel cm = (IndexColorModel)ip.getColorModel();
            if (cm==null) return false;
            int mapSize = cm.getMapSize();
            byte[] reds = new byte[mapSize];
            byte[] greens = new byte[mapSize];
            byte[] blues = new byte[mapSize];
            cm.getReds(reds);
            cm.getGreens(greens);
            cm.getBlues(blues);
            boolean isColor = false;
            for (int i=0; i<mapSize; i++) {
                if ((reds[i] != greens[i]) || (greens[i] != blues[i])) {
                    isColor = true;
                    break;
                }
            }
            return isColor;
        }

        /** Returns the path to the user plugins directory or
            null if the plugins directory was not found. */
        public static String getPlugInsPath() {
            return pluginsPath;
        }

        /** Returns the path to the macros directory or
            null if the macros directory was not found. */
        public static String getMacrosPath() {
            return macrosPath;
        }

        /** Returns the hashtable that associates commands with plugins. */
        public static Hashtable getCommands() {
            return pluginsTable;
        }

        /** Returns the hashtable that associates shortcuts with commands. The keys
            in the hashtable are Integer keycodes, or keycode+200 for uppercase. */
        public static Hashtable getShortcuts() {
            return shortcuts;
        }

        /** Returns the hashtable that associates keyboard shortcuts with macros. The keys
            in the hashtable are Integer keycodes, or keycode+200 for uppercase. */
        public static Hashtable getMacroShortcuts() {
            if (macroShortcuts==null) macroShortcuts = new Hashtable();
            return macroShortcuts;
        }

        /** Returns the hashtable that associates menu names with menus. */
        //public static Hashtable getMenus() {
        //  return menusTable;
        //}

        /** Inserts one item (a non-image window) into the Window menu. */
        static synchronized void insertWindowMenuItem(Frame win) {
            if (ij==null || win==null)
                return;
            CheckboxMenuItem item = new CheckboxMenuItem(win.getTitle());
            item.addItemListener(ij);
            int index = WINDOW_MENU_ITEMS+windowMenuItems2;
            if (windowMenuItems2>=2)
                index--;
            window.insert(item, index);
            windowMenuItems2++;
            if (windowMenuItems2==1) {
                window.insertSeparator(WINDOW_MENU_ITEMS+windowMenuItems2);
                windowMenuItems2++;
            }
            //IJ.write("insertWindowMenuItem: "+windowMenuItems2);
        }

        /** Adds one image to the end of the Window menu. */
        static synchronized void addWindowMenuItem(ImagePlus imp) {
            //IJ.log("addWindowMenuItem: "+imp);
            if (ij==null) return;
            String name = imp.getTitle();
            int size = (imp.getWidth()*imp.getHeight()*imp.getStackSize())/1024;
            switch (imp.getType()) {
                case ImagePlus.GRAY32: case ImagePlus.COLOR_RGB: // 32-bit
                    size *=4;
                    break;
                case ImagePlus.GRAY16:  // 16-bit
                    size *= 2;
                    break;
                default: // 8-bit
                    ;
            }
            CheckboxMenuItem item = new CheckboxMenuItem(name + " " + size + "K");
            window.add(item);
            item.addItemListener(ij);
        }

        /** Removes the specified item from the Window menu. */
        static synchronized void removeWindowMenuItem(int index) {
            //IJ.log("removeWindowMenuItem: "+index+" "+windowMenuItems2+" "+window.getItemCount());
            if (ij==null)
                return;
            if (index>=0 && index<window.getItemCount()) {
                window.remove(WINDOW_MENU_ITEMS+index);
                if (index<windowMenuItems2) {
                    windowMenuItems2--;
                    if (windowMenuItems2==1) {
                        window.remove(WINDOW_MENU_ITEMS);
                        windowMenuItems2 = 0;
                    }
                }
            }
        }

        /** Changes the name of an item in the Window menu. */
        public static synchronized void updateWindowMenuItem(String oldLabel, String newLabel) {
            if (oldLabel.equals(newLabel))
                return;
            int first = WINDOW_MENU_ITEMS;
            int last = window.getItemCount()-1;
            //IJ.write("updateWindowMenuItem: "+" "+first+" "+last+" "+oldLabel+" "+newLabel);
            try {  // workaround for Linux/Java 5.0/bug
                for (int i=first; i<=last; i++) {
                    MenuItem item = window.getItem(i);
                    //IJ.write(i+" "+item.getLabel()+" "+newLabel);
                    String label = item.getLabel();
                    if (item!=null && label.startsWith(oldLabel)) {
                        if (label.endsWith("K")) {
                            int index = label.lastIndexOf(' ');
                            if (index>-1)
                                newLabel += label.substring(index, label.length());
                        }
                        item.setLabel(newLabel);
                        return;
                    }
                }
            } catch (NullPointerException e) {}
        }

        /** Adds a file path to the beginning of the File/Open Recent submenu. */
        public static synchronized void addOpenRecentItem(String path) {
            if (ij==null) return;
            int count = openRecentMenu.getItemCount();
            if (count>0 && openRecentMenu.getItem(0).getLabel().equals(path))
                return;
            if (count==MAX_OPEN_RECENT_ITEMS)
                openRecentMenu.remove(MAX_OPEN_RECENT_ITEMS-1);
            MenuItem item = new MenuItem(path);
            openRecentMenu.insert(item, 0);
            item.addActionListener(ij);
        }

        public static PopupMenu getPopupMenu() {
            return popup;
        }

        /** Adds a plugin based command to the end of a specified menu.
        * @param plugin         the plugin (e.g. "Inverter_", "Inverter_("arg")")
        * @param menuCode       PLUGINS_MENU, IMPORT_MENU, SAVE_AS_MENU or HOT_KEYS
        * @param command        the menu item label (set to "" to uninstall)
        * @param shortcut       the keyboard shortcut (e.g. "y", "Y", "F1")
        * @param ij             ImageJ (the action listener)
        *
        * @return               returns an error code(NORMAL_RETURN,COMMAND_IN_USE_ERROR, etc.)
        */
        public static int installPlugin(String plugin, char menuCode, String command, String shortcut, ImageJ ij) {
            if (command.equals("")) { //uninstall
                //Object o = pluginsPrefs.remove(plugin);
                //if (o==null)
                //  return NOT_INSTALLED;
                //else
                    return NORMAL_RETURN;
            }

            if (commandInUse(command))
                return COMMAND_IN_USE;
            if (!validShortcut(shortcut))
                return INVALID_SHORTCUT;
            if (shortcutInUse(shortcut))
                return SHORTCUT_IN_USE;

            Menu menu;
            switch (menuCode) {
                case PLUGINS_MENU: menu = pluginsMenu; break;
                case IMPORT_MENU: menu = importMenu; break;
                case SAVE_AS_MENU: menu = saveAsMenu; break;
                case SHORTCUTS_MENU: menu = shortcutsMenu; break;
                case ABOUT_MENU: menu = aboutMenu; break;
                case FILTERS_MENU: menu = filtersMenu; break;
                case TOOLS_MENU: menu = toolsMenu; break;
                case UTILITIES_MENU: menu = utilitiesMenu; break;
                default: return 0;
            }
            int code = convertShortcutToCode(shortcut);
            MenuItem item;
            boolean functionKey = code>=KeyEvent.VK_F1 && code<=KeyEvent.VK_F12;
            if (code==0)
                item = new MenuItem(command);
            else if (functionKey) {
                command += " [F"+(code-KeyEvent.VK_F1+1)+"]";
                shortcuts.put(new Integer(code),command);
                item = new MenuItem(command);
            }else {
                shortcuts.put(new Integer(code),command);
                int keyCode = code;
                boolean shift = false;
                if (keyCode>=265 && keyCode<=290) {
                    keyCode -= 200;
                    shift = true;
                }
                item = new MenuItem(command, new MenuShortcut(keyCode, shift));
            }
            menu.add(item);
            item.addActionListener(ij);
            pluginsTable.put(command, plugin);
            shortcut = code>0 && !functionKey?"["+shortcut+"]":"";
            //IJ.write("installPlugin: "+menuCode+",\""+command+shortcut+"\","+plugin);
            pluginsPrefs.addElement(menuCode+",\""+command+shortcut+"\","+plugin);
            return NORMAL_RETURN;
        }

        /** Deletes a command installed by installPlugin. */
        public static int uninstallPlugin(String command) {
            boolean found = false;
            for (Enumeration en=pluginsPrefs.elements(); en.hasMoreElements();) {
                String cmd = (String)en.nextElement();
                if (cmd.indexOf(command)>0) {
                    pluginsPrefs.removeElement((Object)cmd);
                    found = true;
                    break;
                }
            }
            if (found)
                return NORMAL_RETURN;
            else
                return COMMAND_NOT_FOUND;

        }

        public static boolean commandInUse(String command) {
            if (pluginsTable.get(command)!=null)
                return true;
            else
                return false;
        }

        public static int convertShortcutToCode(String shortcut) {
            int code = 0;
            int len = shortcut.length();
            if (len==2 && shortcut.charAt(0)=='F') {
                code = KeyEvent.VK_F1+(int)shortcut.charAt(1)-49;
                if (code>=KeyEvent.VK_F1 && code<=KeyEvent.VK_F9)
                    return code;
                else
                    return 0;
            }
            if (len==3 && shortcut.charAt(0)=='F') {
                code = KeyEvent.VK_F10+(int)shortcut.charAt(2)-48;
                if (code>=KeyEvent.VK_F10 && code<=KeyEvent.VK_F12)
                    return code;
                else
                    return 0;
            }
            if (len==2 && shortcut.charAt(0)=='N') { // numeric keypad
                code = KeyEvent.VK_NUMPAD0+(int)shortcut.charAt(1)-48;
                if (code>=KeyEvent.VK_NUMPAD0 && code<=KeyEvent.VK_NUMPAD9)
                    return code;
                switch (shortcut.charAt(1)) {
                    case '/': return KeyEvent.VK_DIVIDE;
                    case '*': return KeyEvent.VK_MULTIPLY;
                    case '-': return KeyEvent.VK_SUBTRACT;
                    case '+': return KeyEvent.VK_ADD;
                    case '.': return KeyEvent.VK_DECIMAL;
                    default: return 0;
                }
            }
            if (len!=1)
                return 0;
            int c = (int)shortcut.charAt(0);
            if (c>=65&&c<=90) //A-Z
                code = KeyEvent.VK_A+c-65 + 200;
            else if (c>=97&&c<=122) //a-z
                code = KeyEvent.VK_A+c-97;
            else if (c>=48&&c<=57) //0-9
                code = KeyEvent.VK_0+c-48;
            else {
                switch (c) {
                    case 43: code = KeyEvent.VK_PLUS; break;
                    case 45: code = KeyEvent.VK_MINUS; break;
                    //case 92: code = KeyEvent.VK_BACK_SLASH; break;
                    default: return 0;
                }
            }
            return code;
        }

        void installStartupMacroSet() {
            if (applet!=null) {
                String docBase = ""+applet.getDocumentBase();
                if (!docBase.endsWith("/")) {
                    int index = docBase.lastIndexOf("/");
                    if (index!=-1)
                        docBase = docBase.substring(0, index+1);
                }
                IJ.runPlugIn("ij.plugin.URLOpener", docBase+"StartupMacros.txt");
                return;
            }
            if (macrosPath==null) {
                (new MacroInstaller()).installFromIJJar("/macros/StartupMacros.txt");
                return;
            }
            String path = macrosPath + "StartupMacros.txt";
            File f = new File(path);
            if (!f.exists()) {
                path = macrosPath + "StartupMacros.ijm";
                f = new File(path);
                if (!f.exists()) {
                    (new MacroInstaller()).installFromIJJar("/macros/StartupMacros.txt");
                    return;
                }
            }
            String libraryPath = macrosPath + "Library.txt";
            f = new File(libraryPath);
            boolean isLibrary = f.exists();
            try {
                MacroInstaller mi = new MacroInstaller();
                if (isLibrary) mi.installLibrary(libraryPath);
                mi.installFile(path);
                nMacros += mi.getMacroCount();
            } catch (Exception e) {}
        }

        static boolean validShortcut(String shortcut) {
            int len = shortcut.length();
            if (shortcut.equals(""))
                return true;
            else if (len==1)
                return true;
            else if (shortcut.startsWith("F") && (len==2 || len==3))
                return true;
            else
                return false;
        }

        public static boolean shortcutInUse(String shortcut) {
            int code = convertShortcutToCode(shortcut);
            if (shortcuts.get(new Integer(code))!=null)
                return true;
            else
                return false;
        }

        /** Set the size (in points) used for the fonts in ImageJ menus.
            Set the size to 0 to use the Java default size. */
        public static void setFontSize(int size) {
            if (size<9 && size!=0) size = 9;
            if (size>24) size = 24;
            fontSize = size;
        }

        /** Returns the size (in points) used for the fonts in ImageJ menus. Returns
            0 if the default font size is being used or if this is a Macintosh. */
        public static int getFontSize() {
            return IJ.isMacintosh()?0:fontSize;
        }

        public static Font getFont() {
            if (menuFont==null)
                menuFont =  new Font("SanSerif", Font.PLAIN, fontSize==0?12:fontSize);
            return menuFont;
        }

        /** Called once when ImageJ quits. */
        public static void savePreferences(Properties prefs) {
            int index = 0;
            for (Enumeration en=pluginsPrefs.elements(); en.hasMoreElements();) {
                String key = "plugin" + (index/10)%10 + index%10;
                String value = (String)en.nextElement();
                prefs.put(key, value);
                index++;
            }
            int n = openRecentMenu.getItemCount();
            for (int i=0; i<n; i++) {
                String key = ""+i;
                if (key.length()==1) key = "0"+key;
                key = "recent"+key;
                prefs.put(key, openRecentMenu.getItem(i).getLabel());
            }
            prefs.put(Prefs.MENU_SIZE, Integer.toString(fontSize));
        }

    }

| Menus |