//
// $Id: DebugChords.java,v 1.1 2002/07/17 01:51:53 mdb Exp $

package com.samskivert.util;

import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;

import java.util.ArrayList;

import com.samskivert.Log;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Tuple;

/**
 * Provides a mechanism for causing code to be invoked when a particular
 * key combination is pressed in a GUI application. The code invoked is
 * presumed to be global debugging code; this is not a mechanism for
 * implementing a client's normal user interface.
 */
public class DebugChords
{
    /**
     * Provides the mechanism by which code is provided to be run when a
     * user depresses a particular key combination.
     */
    public static interface Hook
    {
        /**
         * Called when a key combination to which this hook is bound is
         * invoked.
         */
        public void invoke ();
    }

    /**
     * Initializes the debug chords services and wires up the key event
     * listener that will be used to invoke the bound code.
     */
    public static void activate ()
    {
        // capture low-level keyboard events via the keyboard focus manager
        if (_dispatcher == null) {
            _dispatcher = new KeyEventDispatcher() {
                public boolean dispatchKeyEvent (KeyEvent e) {
                    return DebugChords.dispatchKeyEvent(e);
                }
            };

            KeyboardFocusManager keymgr =
                KeyboardFocusManager.getCurrentKeyboardFocusManager();
            keymgr.addKeyEventDispatcher(_dispatcher);
        }
    }

    /**
     * Registers the supplied debug hook to be invoked when the specified
     * key combination is depressed.
     *
     * @param modifierMask a mask with bits on for all modifiers that must
     * be present when the specified key code is received (e.g. {@link
     * KeyEvent#CTRL_DOWN_MASK}|{@link KeyEvent#ALT_DOWN_MASK}).
     * @param keyCode the code that identifies the normal key that must be
     * pressed to activate the hook (e.g. {@link KeyEvent#VK_E}).
     * @param hook the hook to be invoked when the specified key
     * combination is received.
     */
    public static void registerHook (int modifierMask, int keyCode, Hook hook)
    {
        // store the hooks mapped by key code
        ArrayList list = (ArrayList)_bindings.get(keyCode);
        if (list == null) {
            list = new ArrayList();
            _bindings.put(keyCode, list);
        }

        // append the hook and modifier mask to the list
        list.add(new Tuple(new Integer(modifierMask), hook));
    }

    /**
     * Called when the {@link KeyboardFocusManager} has a key event for us
     * to scrutinize.  Returns whether we've handled the key event.
     */
    protected static boolean dispatchKeyEvent (KeyEvent e)
    {
        // ignore non-keypress events
        if (e.getID() != KeyEvent.KEY_PRESSED) {
            return false;
        }

        // bail here if we have no hooks registered for this key code
        ArrayList list = (ArrayList)_bindings.get(e.getKeyCode());
        if (list == null) {
            return false;
        }

        // scan the list of registered hooks checking for any that match
        // the depressed set of modifier keys
        boolean handled = false;
        int hcount = list.size();
        for (int ii = 0; ii < hcount; ii++) {
            Tuple tup = (Tuple)list.get(ii);
            int mask = ((Integer)tup.left).intValue();
            if ((e.getModifiersEx() & mask) == mask) {
                Hook hook = (Hook)tup.right;
                try {
                    hook.invoke();
                    handled = true;
                } catch (Throwable t) {
                    Log.warning("Hook failed [event=" + e +
                                ", hook=" + hook + "].");
                    Log.logStackTrace(t);
                }
            }
        }

        return handled;
    }

    /** Intercepts key events and presents them to us for scrutiny. */
    protected static KeyEventDispatcher _dispatcher;

    /** A mapping from key binding to debug code. */
    protected static HashIntMap _bindings = new HashIntMap();
}
