// (jEdit options) :folding=explicit:collapseFolds=1:
//{{{ Package, imports
package driftwood.gui;

//import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.*;
//import java.util.regex.*;
import javax.swing.*;
import javax.swing.event.*;
//import driftwood.*;
//}}}
/**
* <code>ReflectiveAction</code> compensates for the lack of first-class functions
* in the Java language by allowing the actions generated by menus, buttons, etc.
* to be directed to specific functions rather than anonymous inner classes.
* This is both lighter weight and easier to read, though you do lose some type
* safety. If the user specifies a function that does not exist, it will not
* be detected until runtime, although it will be detected as soon as this action
* is instantiated.
*
* By convention, a control named "Foo" should signal a method named "onFoo".
* The following warning should appear in the code:
* <br><code>// This method is the target of reflection -- DO NOT CHANGE ITS NAME</code>
* <p>Also, all methods must have exactly the following signature:
* <br><code>public void onFoo(ActionEvent e)</code>
*
* <p>This sort of thing is also known (apparently) as a trampoline,
* because it bounces you from one method to another.
* I got the idea from http://java.sun.com/docs/books/performance/ 
*
* <p>Copyright (C) 2003 by Ian W. Davis. All rights reserved.
* <br>Begun on Mon Apr  7 10:19:31 EDT 2003
*/
final public class ReflectiveAction extends AbstractAction
{
//{{{ Constants
//}}}

//{{{ Variable definitions
//##################################################################################################
    private Object target;
    private Method method;
//}}}

//{{{ Constructor(s)
//##################################################################################################
    /**
    * Creates a new Action with the given name and icon,
    * which will signal the named method in the given object
    * when it receives a call to actionPerformed().
    * @param name       the descriptive text that appears in a menu or on a button (may be null)
    * @param icon       the small icon that appears alongside name (may be null)
    * @param targetObj  the object to be signaled
    * @param methodName the name of the method in target that will be signaled
    * @throws IllegalArgumentException if targetObj or methodName is null,
    *   or if the named method with the correct signature cannot be found.
    */
    public ReflectiveAction(String name, Icon icon, Object targetObj, String methodName)
    {
        super(name, icon);
        
        if(targetObj == null | methodName == null)
            throw new IllegalArgumentException("Must supply both targetObj and methodName");

        target = targetObj;
        Class[] formalParams = { ActionEvent.class };
        try
        {
            method = targetObj.getClass().getMethod(methodName, formalParams);
        }
        catch(NoSuchMethodException ex)
        {
            throw new IllegalArgumentException("Object '"+targetObj.toString()
                +"' doesn't have a method 'public void "+methodName+"(ActionEvent e)'");
        }
        
        if(name != null)
            setCommandKey(name); // this allows bind() to work without more work
    }
//}}}

//{{{ actionPerformed
//##################################################################################################
    /**
    * This event is simply bounced to the previously specified
    * method of the target object.
    * @throws RuntimeException if access is not allowed
    *   or an exception occurs in the calling method.
    */
    public void actionPerformed(ActionEvent ev)
    {
        try
        {
            Object[] params = { ev };
            method.invoke(target, params);
        }
        catch(InvocationTargetException ex)
        {
            Throwable t = ex.getTargetException();
            if(t instanceof Error)                  throw (Error)t;
            else if(t instanceof RuntimeException)  throw (RuntimeException)t;
            else throw new RuntimeException("Exception in "+target.getClass()+"."+method+": "+t.getMessage());
        }
        catch(IllegalAccessException ex)
        {
            throw new RuntimeException("Exception in "+target.getClass()+"."+method+": "+ex.getMessage());
        }
    }
//}}}

//{{{ get/set{Name, Icon, CommandKey}
//##################################################################################################
    /**
    * Gets the name of this Action: the string that
    * appears in menus, on buttons, and so on.
    */
    public String getName()
    { return (String)getValue(Action.NAME); }
    
    /**
    * Sets the name of this Action: the string that
    * appears in menus, on buttons, and so on.
    */
    public void setName(String name)
    { putValue(Action.NAME, name); }
    
    /**
    * Gets the small icon that appears alongside
    * this Action's name in menus, buttons, etc.
    */
    public Icon getIcon()
    { return (Icon)getValue(Action.SMALL_ICON); }
    
    /**
    * Sets the small icon that appears alongside
    * this Action's name in menus, buttons, etc.
    */
    public void setIcon(Icon icon)
    { putValue(Action.SMALL_ICON, icon); }
    
    /**
    * Gets the string to be used as a key for this object
    * in ActionMaps and InputMaps.
    */
    public String getCommandKey()
    { return (String)getValue(Action.ACTION_COMMAND_KEY); }
    
    /**
    * Sets the string to be used as a key for this object
    * in ActionMaps and InputMaps.
    */
    public void setCommandKey(String key)
    { putValue(Action.ACTION_COMMAND_KEY, key); }
//}}}

//{{{ get/set{Tooltip, Mnemonic, Accelerator}
//##################################################################################################
    /** Gets the tooltip text for this Action */
    public String getTooltip()
    { return (String)getValue(Action.SHORT_DESCRIPTION); }
    
    /** Sets the tooltip text for this Action */
    public void setTooltip(String tip)
    { putValue(Action.SHORT_DESCRIPTION, tip); }
    
    /** Gets the mnemonic key for this Action (one of the KeyEvent.VK_* constants) */
    public Integer getMnemonic()
    { return (Integer)getValue(Action.MNEMONIC_KEY); }
    
    /** Sets the mnemonic key for this Action (one of the KeyEvent.VK_* constants) */
    public void setMnemonic(int key)
    { putValue(Action.MNEMONIC_KEY, new Integer(key)); }
    
    /** Gets the accelerator key for this Action */
    public KeyStroke getAccelerator()
    { return (KeyStroke)getValue(Action.ACCELERATOR_KEY); }
    
    /** Sets the accelerator key for this Action */
    public void setAccelerator(KeyStroke key)
    { putValue(Action.ACCELERATOR_KEY, key); }
    
    /**
    * Sets the accelerator key for this Action.
    * @param keyCode        one of the KeyEvent.VK_* constants
    * @param modifiers      zero, or some combination (use + or |) of
    *   the KeyEvent constants SHIFT_MASK, CTRL_MASK, ALT_MASK, and META_MASK.
    */
    public void setAccelerator(int keyCode, int modifiers)
    { setAccelerator(KeyStroke.getKeyStroke(keyCode, modifiers)); }
//}}}

//{{{ bind, bindWindow
//##################################################################################################
    /**
    * Binds this Action to the InputMap and ActionMap of the specified component.
    * The keystroke is given by this.getAccelerator()
    * and the command is given by this.getCommandKey().
    * This operation will fail if no command key is defined.
    * The action is bound to the WHEN_FOCUSED input map.
    */
    public void bind(JComponent comp)
    {
        InputMap    im  = comp.getInputMap();
        ActionMap   am  = comp.getActionMap();
        String      ck  = this.getCommandKey();
        KeyStroke   ks  = this.getAccelerator();
        
        if(im == null || am == null || ck == null || ks == null) return;
        
        im.put(ks, ck);
        am.put(ck, this);
    }

    /**
    * Binds this Action to the InputMap and ActionMap of the specified component.
    * The keystroke is given by this.getAccelerator()
    * and the command is given by this.getCommandKey().
    * This operation will fail if no command key is defined.
    * The action is bound to the WHEN_IN_FOCUSED_WINDOW input map.
    */
    public void bindWindow(JComponent comp)
    {
        InputMap    im  = comp.getInputMap(comp.WHEN_IN_FOCUSED_WINDOW);
        ActionMap   am  = comp.getActionMap();
        String      ck  = this.getCommandKey();
        KeyStroke   ks  = this.getAccelerator();
        
        if(im == null || am == null || ck == null || ks == null) return;
        
        im.put(ks, ck);
        am.put(ck, this);
    }
//}}}

//{{{ empty_code_segment
//##################################################################################################
//}}}
}//class

