/*******************************************************************************
 * Copyright (c) 2005-2008 SAS Institute Inc., ILOG S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     SAS Institute Inc. - initial API and implementation
 *     ILOG S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.albireo.core;

import java.util.WeakHashMap;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;


/**
 * This class allows you to register SWT popup menus on AWT/Swing components.
 * <p>
 * It is customizable through the "replaceable singleton" design pattern.
 * @see #setMenu(java.awt.Component, boolean, Menu)
 * @see SwingControl#getMenu(java.awt.Component, int, int, int, int)
 */
public class SwtPopupRegistry {

    // -------------------- Static registry of popup menus --------------------

    /**
     * A popup menu specification.
     */
    private static class MenuInfo {
        // The menu that shall appear.
        Menu menu;
        // Whether it also applies to subcomponents (if not overridden).
        boolean recursive;
        // Constructor.
        MenuInfo(Menu menu, boolean recursive) {
            this.menu = menu;
            this.recursive = recursive;
        }
    }

    private WeakHashMap /* java.awt.Component -> MenuInfo */ menuTable =
        new WeakHashMap();

    /**
     * Returns the registered menu for a given component.
     * @param component An AWT/Swing component.
     * @param recursive Whether to look for a recursive or for a non-recursive
     *                  specification of a popup menu.
     * @return A popup menu, or <code>null</code>.
     */
    public Menu getMenu(java.awt.Component component, boolean recursive) {
        synchronized(menuTable) {
            MenuInfo mi = (MenuInfo)menuTable.get(component);
            if (mi != null && mi.recursive == recursive)
                return mi.menu;
            else
                return null;
        }
    }

    /**
     * Registers a popup menu for a given component and, optionally, its
     * subcomponents.
     * <p>
     * Note: You can only specify one popup menu on a given component. You
     * cannot specify a recursive and a non-recursive popup menu simultaneously
     * on the same component.
     * @param component An AWT/Swing component.
     * @param recursive Whether the menu also applies to subcomponents (unless
     *                  another popup menu is specified on the subcomponent or
     *                  a component in between in the hierarchy).
     * @param menu A popup menu, or <code>null</code> to clear the previously
     *             specified popup menu.
     */
    public void setMenu(java.awt.Component component, boolean recursive, final Menu menu) {
        synchronized(menuTable) {
            MenuInfo mi = (MenuInfo)menuTable.get(component);
            if (menu != null) {
                if (mi != null) {
                    mi.menu = menu;
                    mi.recursive = recursive;
                } else {
                    mi = new MenuInfo(menu, recursive);
                    menuTable.put(component, mi);
                }
            } else {
                if (mi != null)
                    menuTable.remove(component);
            }
        }
        
    }
    
    /**
     * Searches for a popup menu to be used on a given component.
     * <p>
     * The default implementation walks up the component hierarchy, looking
     * for popup menus registered with {@link #setMenu}.
     * <p>
     * This method can be overridden, to achieve dynamic popup menus.
     * @param component The component on which a popup event was received.
     * @param x The x coordinate, relative to the component's top left corner,
     *          of the mouse cursor when the event occurred.
     * @param y The y coordinate, relative to the component's top left corner,
     *          of the mouse cursor when the event occurred.
     * @param xAbsolute The x coordinate, relative to this control's top left
     *                  corner, of the mouse cursor when the event occurred.
     * @param yAbsolute The y coordinate, relative to this control's top left
     *                  corner, of the mouse cursor when the event occurred.
     */
    protected Menu findMenu(java.awt.Component component, int x, int y, int xAbsolute, int yAbsolute) {
        assert Display.getCurrent() != null;
        
        synchronized(menuTable) {
            MenuInfo mi = (MenuInfo)menuTable.get(component);
            if (mi != null)
                // On the component itself, ignore whether recursive or not.
                return mi.menu;
            for (java.awt.Component parent = component.getParent();
                   parent != null;
                   parent = parent.getParent()) {
                mi = (MenuInfo)menuTable.get(parent);
                if (mi != null) {
                    if (mi.recursive)
                        return mi.menu;
                    else
                        return null;
                }
            }
        }
        // not found
        return null;
    }

    // ========================================================================
    // Singleton design pattern

    private static SwtPopupRegistry theRegistry = new SwtPopupRegistry();

    /**
     * Returns the currently active singleton of this class.
     */
    public static SwtPopupRegistry getInstance() {
        return theRegistry;
    }

    /**
     * Replaces the singleton of this class.
     * @param instance An instance of this class or of a customized subclass.
     */
    public static void setInstance(SwtPopupRegistry instance) {
        theRegistry = instance;
    }

}
