/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.tool;

import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileNotFoundException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.accessibility.AccessibleContext;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import com.sun.javatest.util.ExitCount;
import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.PrefixMap;
import com.sun.javatest.util.StringArray;

/**
 * A deskview defines the abstract behavior of a style of desktop,
 * such as MDI, SDI, or tabbed.
 * @see Desktop
 */
abstract class DeskView {
    /**
     * This exception is used to report problems while using a tool manager.
     */
    static class Fault extends Exception
    {
        /**
         * Create a Fault.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         */
        Fault(I18NResourceBundle i18n, String s) {
            super(i18n.getString(s));
        }

        /**
         * Create a Fault.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         * @param o An argument to be formatted with the detail message by
         * {@link java.text.MessageFormat#format}
         */
        Fault(I18NResourceBundle i18n, String s, Object o) {
            super(i18n.getString(s, o));
        }

        /**
         * Create a Fault.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         * @param o An array of arguments to be formatted with the detail message by
         * {@link java.text.MessageFormat#format}
         */
        Fault(I18NResourceBundle i18n, String s, Object[] o) {
            super(i18n.getString(s, o));
        }
    }

    protected DeskView(Desktop desktop) {
        this.desktop = desktop;
        uif = new UIFactory(getClass());
    }

    public Desktop getDesktop() {
        return desktop;
    }

    /**
     * Dispose of this desktop object, and resources it may use.
     * Subsequent call of access() will cause a new desktop object
     * to be created.
     * @see #access
     */
    public void dispose() {
        Tool[] tools = getTools();
        for (int i = 0; i < tools.length; i++)
            tools[i].dispose();

        /*
        if (this == theOne) {
            theOne = null;
            if (helpBroker != null && helpBroker.isDisplayed()) {
                helpBroker.setDisplayed(false);
            }
        }
        */
    }

    /**
     * Check if the top level windows of the desktop are visible or not.
     * @return true if the top level windows are visible; otherwise, return false
     * @see #setVisible
     */
    public abstract boolean isVisible();

    /**
     * Set whether or not the top level windows of the desktop should be visible.
     * @param v If true, the top level windows will be made visible; if false, they
     * will be hidden.
     */
    public abstract void setVisible(boolean v);

    /**
     * Check whether the desktop is empty of any tools.
     * @return true if there are no tools on the desktop, and false otherwise
     */
    public abstract boolean isEmpty();

    /**
     * Get the set of tools currently on the desktop.
     * @return the set of tools currently on the desktop
     */
    public abstract Tool[] getTools();

    /**
     * Add a new tool to the desktop.
     * @param t the tool to be added
     * @see #removeTool
     */
    public abstract void addTool(Tool t);

    /**
     * Remove a tool from the desktop.
     * @param t the tool to be removed
     * @see #addTool
     */
    public abstract void removeTool(Tool t);

    /**
     * Get the currently selected tool on the desktop.
     * @return the currently selected tool on the desktop
     * @see #setSelectedTool
     */
    public abstract Tool getSelectedTool();

    /**
     * Set the currently selected tool on the desktop.
     * @param t the the tool to be selected on the desktop
     * @see #getSelectedTool
     */
    public abstract void setSelectedTool(Tool t);

    //--------------------------------------------------------------------------

    /**
     * Get an integer signifying the style of the current desktop.
     * @return an integer signifying the style of the current desktop
     * @see #TAB_STYLE
     * @see #MDI_STYLE
     * @see #SDI_STYLE
     * @see #setStyle
     */
    public abstract int getStyle();

    /**
     * Get the top level frames that make up this desktop. TAB and MDI style
     * desktops just have a single frame; An SDI style desktop may have more
     * than one frame.
     * @return the top level frames of this desktop
     */
    public abstract JFrame[] getFrames();

    /**
     * Get a parent component for a dialog to use.
     * @return a component which can be used as a parent, or null if none
     *         is available.
     */
    public Component getDialogParent() {
        JFrame[] frames = getFrames();
        if (frames == null || frames.length == 0)
            return null;
        return frames[0].getContentPane();
    }

    /**
     * Get the bounds for this desktop.
     * @return a rectangle describing the size and position of this desktop
     * on the screen.
     */
    public abstract Rectangle getBounds();

    /**
     * Get thedefault bounds for a JT Harness desktop.
     * @return a rectangle describing the size and position on the screen
     * of the default desktop.
     */
    public static Rectangle getDefaultBounds() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Rectangle mwb = ge.getMaximumWindowBounds();
        int w = Math.min(mwb.width, Math.max(640, mwb.width * 3 / 4));
        int h = Math.min(mwb.height, Math.max(480, mwb.height * 3 / 4));

        int x = ge.getCenterPoint().x - w/2;
        int y = ge.getCenterPoint().y - h/2;

        return new Rectangle(x, y, w, h);
    }

    /**
     * Create a new top level frame for the desktop. The frame will have the
     * standard JT Harness menu bar.
     * @param winMenuListener a listener to be invoked when the user clicks on
     * the standard Windows menu
     * @param uiKey key to use for retrieving accessiblity info for the frame
     * @return a new top level frame for the desktop
     */
    protected JFrame createFrame(MenuListener winMenuListener, String uiKey) {
        return createFrame(winMenuListener, null, uiKey);
    }

    /**
     * Create a new top level frame for the desktop. The frame will have the
     * standard JT Harness menu bar.
     * @param winMenuListener a listener to be invoked when the user clicks on
     * the standard Windows menu
     * @param fileCloseAction an action to use to create the File>Close menu item
     * to close the frame
     * @param uiKey key to use for retrieving accessiblity info for the frame
     * @return a new top level frame for the desktop
     */
    protected JFrame createFrame(MenuListener winMenuListener, Action fileCloseAction,
                                 String uiKey) {
        JFrame frame = new JFrame();
        frame.setName(uiKey + ":" + (frameIndex++));
        frame.setTitle(uif.getI18NString("dt.title.txt"));
        frame.setIconImage(uif.createImage("images/jticon.gif"));
        uif.setAccessibleInfo(frame, uiKey);

        JRootPane root = frame.getRootPane();
        root.setName("root");
        uif.setAccessibleInfo(root, uiKey); // same as for frame, deliberate

        //System.err.println("DT: createFrame");

        JMenuBar mb = uif.createMenuBar("dt.menuBar");
        JMenu fileMenu = uif.createMenu("dt.file");
        // fileMenu is populated dynamically by FileMenuListener
        fileMenu.addMenuListener(new FileMenuListener(fileCloseAction));
        mb.add(fileMenu);

        JMenu toolMenu = uif.createMenu("dt.tasks");
        // could dynamically create this list when menu invoked
        ToolManager[] mgrs = desktop.getToolManagers();
        for (int i = 0; i < mgrs.length; i++) {
            //Action[] actions = mgrs[i].getTaskMenuActions();
            Action[] actions = mgrs[i].getWindowOpenMenuActions();  // method name is out of date
            if (actions != null) {
                for (int j = 0; j < actions.length; j++)
                    toolMenu.add(actions[j]);
            }
        }
        mb.add(toolMenu);

        JMenu winMenu = uif.createMenu("dt.windows");
        // winMenu is populated dynamically by WinMenuListener
        winMenu.addMenuListener(winMenuListener);
        mb.add(winMenu);

        mb.add(uif.createHorizontalGlue("dt.pad"));

        JMenu helpMenu = new HelpMenu(frame, desktop, uif);
        mb.add(helpMenu);

        frame.setJMenuBar(mb);

        Desktop.addPreferredSizeDebugListener(frame);

        if (focusMonitor != null)
            focusMonitor.monitor(frame);

        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosed(WindowEvent e) {
                // WARNING: this event may be called more than once
                // so only do post-processing the first time it is called
                // for this window
                final JFrame frame = (JFrame) (e.getSource());
                //System.err.println("DT: closed " + frame.getTitle());
                synchronized (allFrames) {
                    if (allFrames.remove(frame) && allFrames.isEmpty()) {
                        // defer until outstanding events are processed
                        EventQueue.invokeLater(new Runnable() {
                            public void run() {
                                ExitCount.dec();
                            }
                        });
                    }
                }
            }
        });

        synchronized (allFrames) {
            allFrames.add(frame);
            if (allFrames.size() == 1)
                ExitCount.inc();
        }

        //System.err.println("DT: createFrame done");

        return frame;
    }

    /**
     * Add tool menu items from the given tool into the given frame's
     * tool menu.
     * @param frame the frame to modify
     * @param tool the tool to get the menu items from
     */
    protected void addToolMenuItemsToFrameMenuBar(JFrame frame, Tool tool) {
        JMenuBar frameMenuBar = frame.getJMenuBar();
        Tool curr = (Tool)(frameMenuBar.getClientProperty(getClass()));
        if (tool == curr)
            return;
        else if (curr != null)
            removeToolMenuItemsFromFrameMenuBar(frame, curr);

        JMenuBar toolMenuBar = tool.getMenuBar();
        if (toolMenuBar == null)
            return;

        for (int i = 0; i < toolMenuBar.getMenuCount(); i++) {
            JMenu toolMenu = toolMenuBar.getMenu(i);
            if (toolMenu == null)
                // null if i'th item not a menu, e.g. glue
                continue;

            int toolMenuSize = toolMenu.getMenuComponentCount();
            JMenu frameMenu = findMenu(frameMenuBar, toolMenu.getText());
            if (frameMenu == null) {
                frameMenu = new JMenu(toolMenu.getText());
                frameMenu.setName(toolMenu.getName());
                frameMenu.setMnemonic(toolMenu.getMnemonic());
                AccessibleContext bc = frameMenu.getAccessibleContext();
                AccessibleContext tc = toolMenu.getAccessibleContext();
                bc.setAccessibleName(tc.getAccessibleName());
                bc.setAccessibleDescription(tc.getAccessibleDescription());
                copyMenuListeners(toolMenu, frameMenu);
                for (int j = 0; j < toolMenuSize; j++) {
                    frameMenu.add(toolMenu.getMenuComponent(0));
                }
                frameMenuBar.add(frameMenu, frameMenuBar.getMenuCount() - MENU_INSERT_POINT);
            }
            else {
                for (int j = 0; j < toolMenuSize; j++) {
                    frameMenu.add(toolMenu.getMenuComponent(0), j);
                }
                frameMenu.insertSeparator(toolMenuSize);
            }
            frameMenu.putClientProperty(getClass(), new Integer(toolMenuSize));
        }   // for
        frameMenuBar.putClientProperty(getClass(), tool);
    }

    private void removeToolMenuItemsFromFrameMenuBar(JFrame frame) {
        JMenuBar frameMenuBar = frame.getJMenuBar();
        Tool tool = (Tool)(frameMenuBar.getClientProperty(getClass()));
        if (tool == null)
            return;
        removeToolMenuItemsFromFrameMenuBar(frame, tool);
    }

    /**
     * Remove the tool menu items for the given tool from the given frame's
     * tool menu.
     * @param frame the frame with the menu bar to which the tool's menus have
     * been added
     * @param tool the tool to which to return the menu items
     */
    protected void removeToolMenuItemsFromFrameMenuBar(JFrame frame, Tool tool) {
        JMenuBar frameMenuBar = frame.getJMenuBar();
        JMenuBar toolMenuBar = tool.getMenuBar();
        for (int i = 0; i < toolMenuBar.getMenuCount(); i++) {
            JMenu toolMenu = toolMenuBar.getMenu(i);
            if (toolMenu == null)
                // null if i'th item not a menu, e.g. glue
                continue;

            JMenu frameMenu = findMenu(frameMenuBar, toolMenu.getText());
            int toolMenuSize = ((Integer)(frameMenu.getClientProperty(getClass()))).intValue();
            for (int j = 0; j < toolMenuSize; j++) {
                toolMenu.add(frameMenu.getMenuComponent(0));
            }
            if (frameMenu.getItemCount() == 0)
                frameMenuBar.remove(frameMenu);
            else {
                frameMenu.remove(0); // separator
                frameMenu.putClientProperty(getClass(), null);
            }
        }

        frameMenuBar.putClientProperty(getClass(), null);
    }

    protected JMenu getWindowOpenMenu() {
        JMenu menu = uif.createMenu("dt.windows.open");
        // could dynamically create this list when menu invoked
        ToolManager[] mgrs = desktop.getToolManagers();
        for (int i = 0; i < mgrs.length; i++) {
            Action[] actions = mgrs[i].getWindowOpenMenuActions();
            if (actions != null) {
                for (int j = 0; j < actions.length; j++)
                    menu.add(actions[j]);
            }
        }
        return menu;
    }

    private void copyMenuListeners(JMenu src, JMenu dst) {
        MenuListener[] ll = (MenuListener[]) (src.getListeners(MenuListener.class));
        for (int i = 0; i < ll.length; i++)
            dst.addMenuListener(ll[i]);
    }

    private JMenu findMenu(JMenuBar mb, String text) {
        for (int i = 0; i < mb.getMenuCount(); i++) {
            JMenu m = mb.getMenu(i);
            if (m != null && m.getText().equals(text))
                return m;
        }
        return null;
    }

    private static final int MENU_INSERT_POINT = 4; // before Windows, glue and Help

    /**
     * Create a dialog.
     * @param tool the parent tool for the dialog
     * @param uiKey a string which is to be used as the base name for any
     * resources that may be required
     * @param title the title for the dialog
     * @param menuBar the menu bar for the dialog
     * @param body the body component for the dialog
     * @param bounds the size and position for the dialog
     * @return a JDialog or JInternalDialog built from the supplied values.
     */
    public abstract Container createDialog(Tool tool, String uiKey, String title,
                                           JMenuBar menuBar, Container body,
                                           Rectangle bounds);

    /**
     * Check if the tool's parent Window is the owner of a dialog.
     * This may become false if the desktop style is changed after the dialog
     * was created.
     * @param tool the tool from which to determine the parent Window
     * @param dialog the dialog to be checked
     * @return true if the tool's parent Window is the owner of the dialog, and
     * false otherwise.
     */
    public abstract boolean isToolOwnerForDialog(Tool tool, Container dialog);

    /**
     * Handle the File>Preferences menu: show a Preferences window.
     */
    private void doPrefs(JFrame parent) {
        desktop.showPreferences(parent);
    }

    /**
     * Save the state of the desktop to a map object.
     * @param m The map object to which to write the state of the desktop
     * @see #restoreDesktop
     */
    protected abstract void saveDesktop(Map m);

    /**
     * Save the state of the current tools on the desktop to a map object.
     * @param m The map object to which to write the state of the desktop
     * @see #restoreDesktop
     */
    protected void saveTools(Map m) {
        Tool[] tools = getTools();
        m.put("tool.count", String.valueOf(tools.length));
        for (int i = 0; i < tools.length; i++)
            saveTool(new PrefixMap(m, "tool." + String.valueOf(i)), tools[i]);
    }

    /**
     * Save information about a tool on the desktop.
     * @param m A map object in which to save the information
     * @param t the tool to be saved
     * @see #restoreTool
     */
    protected void saveTool(Map m, Tool t) {
        m.put("mgr", t.getManager().getClass().getName());
        m.put("class", t.getClass().getName());
        m.put("selected", String.valueOf(t == getSelectedTool()));
        t.save(m);
    }

    /**
     * Restore the state of the desktop from a map object.
     * @param m The map object from which to restore the state of the desktop
     * @see #saveDesktop
     */
    protected abstract void restoreDesktop(Map m);

    /**
     * Restore the state of the saved tools from a map object.
     * @param m The map object from which to restore the state of the saved tools
     * @see #saveTools
     */
    protected void restoreTools(Map m) {
        try {
            String c = (String) (m.get("tool.count"));
            if (c != null) {
                int count = Integer.parseInt(c);
                for (int i = 0; i < count; i++) {
                    try {
                        String prefix = "tool." + i;
                        Map toolMap = new PrefixMap(m, prefix);
                        restoreTool(toolMap, prefix);
                    }
                    catch (ToolManager.Fault e) {
                        uif.showError("dv.restore.cantRestoreTool",
                                      new Object[] { new Integer(i), e.getMessage() });
                    }
                    catch (Fault e) {
                        uif.showError("dv.restore.cantRestoreTool",
                                      new Object[] { new Integer(i), e.getMessage() });
                    }
                    catch (Throwable e) {
                        uif.showError("dv.restore.cantRestoreTool",
                                      new Object[] { new Integer(i), e.toString() });
                        I18NResourceBundle i18n = uif.getI18NResourceBundle();
                        desktop.log(i18n, "dv.restore.cantRestoreTool",
                                    new Object[] { e, new Integer(i) });
                    }
                }
            }
        }
        catch (NumberFormatException ignore) {
            // ignore, for now
        }

    }

    /**
     * Restore a tool on the desktop from information in a map object.
     * @param m The map containing the information about a tool
     * @return the tool that was restored from the specified information in the map
     * @see #saveTool
     * @throws ClassNotFoundException if the class for the tool cannot be found
     * @throws NoSuchMethodException if the tool does not have the appropriate restore method
     * @throws IllegalAccessException if the tool's restore method does not have public access
     * @throws InvocationTargetException if the tool's restore method threw an exception
     */
    protected Tool restoreTool(Map m, String name) throws Fault, ToolManager.Fault
    {
        String mgrClassName = (String) (m.get("mgr"));
        if (mgrClassName == null) {
            // backwards compatibility with 3.1
            String toolClassName = (String) (m.get("class"));
            if (toolClassName != null || toolClassName.endsWith("Tool")) {
                String n = toolClassName.substring(0, toolClassName.length()) + "Manager";
                try {
                    if (Class.forName(n) != null)
                        mgrClassName = n;
                }
                catch (Throwable e) {
                    // ignore
                }
            }

            if (mgrClassName == null)
                throw new Fault(i18n, "dv.restore.noMgrClass", name);
        }

        ToolManager mgr = desktop.getToolManager(mgrClassName);
        if (mgr == null)
            throw new Fault(i18n, "dv.restore.noMgr",
                            new Object[] { name, mgrClassName } );

        Tool t = mgr.restoreTool(m);

        addTool(t);
        boolean selected = "true".equals(m.get("selected"));
        if (selected)
            setSelectedTool(t);

        return t;
    }

    /**
     * Save the screen size and position for a component in a map object.
     * @param c the component whose size and position are to be recorded in the map.
     * @param m the map in which the information is to be recorded
     * @see #restoreBounds
     */
    protected static void saveBounds(Component c, Map m) {
        Rectangle r = c.getBounds();
        m.put("x", String.valueOf(r.x));
        m.put("y", String.valueOf(r.y));
        m.put("w", String.valueOf(r.width));
        m.put("h", String.valueOf(r.height));
    }


    /**
     * Restore the screen size and position for a component from information
     * in a map object.
     * @param c the component whose size and position are to be restored.
     * @param m the map in which the information is to be recorded
     * @see #saveBounds
     */
    protected static void restoreBounds(Component c, Map m) {
        try {
            String xs = (String) (m.get("x"));
            String ys = (String) (m.get("y"));
            String ws = (String) (m.get("w"));
            String hs = (String) (m.get("h"));
            if (xs != null && ys != null && ws != null && hs != null)
                c.setBounds(Integer.parseInt(xs), Integer.parseInt(ys),
                            Integer.parseInt(ws), Integer.parseInt(hs));
        }
        catch (NumberFormatException e) {
            // ignore
        }
    }

    // keep a private list of all the outstanding frames, so that
    // when the collection becomes empty, we can lower the ExitCount.
    private static Collection allFrames = new Vector();

    // counter for unique name generation
    private static int frameIndex;

    private Desktop desktop;

    /**
     * The UI factory used to create GUI components.
     */
    protected final UIFactory uif;

    private static FocusMonitor focusMonitor;

    static {
        String opts = System.getProperty("javatest.focus.monitor");
        if (opts != null) {
            focusMonitor = FocusMonitor.access();
            if (!opts.equals("true")) // support old value for back compat
                focusMonitor.setOptions(StringArray.split(opts));
            focusMonitor.setActivateKey("alt 2");
            focusMonitor.setReportKey("shift alt 2");
            focusMonitor.setReportFile(System.getProperty("javatest.focus.monitor.log"));
        }
    };

            static final String CLOSE = "close"; // visible for file close listeners
    private static final String EXIT = "exit";
    private static final String PREFS = "prefs";
    private static final String HISTORY = "history";
    private static final String SEPARATOR = null;

    private static final I18NResourceBundle i18n =
        I18NResourceBundle.getBundleForClass(DeskView.class);


    //-------------------------------------------------------------------------

    private class FileMenuListener implements MenuListener, ActionListener {
        FileMenuListener(Action closeAction) {

            prefs = uif.createMenuItem("dt.file", PREFS, this);
            if (closeAction != null)
                close = uif.createMenuItem(closeAction);
            exit = uif.createMenuItem("dt.file", EXIT, this);
            // additional entries are created dynamically
        }

        public void menuSelected(MenuEvent e) {
            //System.err.println("DT:FileMenu: selected " + e);
            JMenu m = (JMenu) (e.getSource());
            m.removeAll();

            // this code is not ideal, and essentially works (for now) because
            // only one tool manager provides file menu actions: ie. ExecManager.
            // If that gets changed, we'll want to consider sorting/grouping
            // the actions somehow.
            ToolManager[] mgrs = desktop.getToolManagers();
            for (int i = 0; i < mgrs.length; i++) {
                Action[] fma = mgrs[i].getFileMenuActions();
                if (fma != null) {
                    for (int j = 0; j < fma.length; j++)
                        m.add(new JMenuItem(fma[j]));
                }

                JMenuItem[] jmi = mgrs[i].getFileMenuPrimaries();
                if (jmi != null) {
                    for (int j = 0; j < jmi.length; j++)
                        m.add(jmi[j]);
                }

            }   // for

            // add secondary items before prefs, close, exit
            for (int i = 0; i < mgrs.length; i++) {
                JMenuItem[] jmi = mgrs[i].getFileMenuSecondaries();
                if (jmi != null) {
                    for (int j = 0; j < jmi.length; j++)
                        m.add(jmi[j]);
                    m.addSeparator();
                }

            }   // for

            List fileHistory = desktop.getFileHistory();
//          if (!fileHistory.isEmpty()) {
        JMenu hm = uif.createMenu("dt.file.recentwd");
        if (!fileHistory.isEmpty()) {
                int n = 0;

                for (Iterator i = fileHistory.iterator(); i.hasNext(); ) {
                    Desktop.FileHistoryEntry h = (Desktop.FileHistoryEntry) (i.next());
                    if (!h.file.exists())
                        continue;
                    String s = uif.getI18NString("dt.file.historyX.mit",
                                             new Object[] {new Integer(n), h.file.getPath()});
                    JMenuItem mi = new JMenuItem(s);
                    mi.setActionCommand(HISTORY);
                    mi.addActionListener(this);
                    mi.putClientProperty(this, h);
                    if (n < 10)
                        mi.setMnemonic(Character.forDigit(n, 10));
                    n++;
                    hm.add(mi);
                }
        } else {
                JMenuItem noEntries = new JMenuItem(i18n.getString("fh.empty"));
                noEntries.setEnabled(false);
                hm.add(noEntries);

        }
        m.add(hm);
                m.addSeparator();

            m.add(prefs);
            m.addSeparator();
            if (close != null)
                m.add(close);
            m.add(exit);
        }

        public void menuDeselected(MenuEvent e) {
            JMenu m = (JMenu) (e.getSource());
            m.removeAll();
        }

        public void menuCanceled(MenuEvent e) {
            JMenu m = (JMenu) (e.getSource());
            m.removeAll();
        }

        public void actionPerformed(ActionEvent e) {
            //System.err.println("DT:FileMenu: action " + e);
            Component src = (Component) (e.getSource());
            JFrame parent = (JFrame) (SwingUtilities.getAncestorOfClass(JFrame.class, src));
            if (parent == null) // only happens during testing when invoking menuitem directly,
                parent = getFrames()[0];

            String cmd = e.getActionCommand();
            if (cmd.equals(PREFS)) {
                doPrefs(parent);
            }
            else if (cmd.equals(HISTORY)) {
                JMenuItem mi = (JMenuItem) (e.getSource());
                Desktop.FileHistoryEntry h = (Desktop.FileHistoryEntry) (mi.getClientProperty(this));
                try {
                    h.fileOpener.open(h.file);
                }
                catch (FileNotFoundException ex) {
                    uif.showError("dt.file.cannotFind", h.file);
                }
                catch (FileOpener.Fault ex) {
                    uif.showError("dt.file.cannotOpen",
                                  new Object[] { h.file, ex.getMessage() });
                }
            }
            else if (cmd.equals(EXIT)) {
                desktop.checkToolsAndExitIfOK(parent);
            }
        }

        private JMenu fileOpenMenu;
        private JMenuItem prefs;
        private JMenuItem close;
        private JMenuItem exit;
    }


}
