[Japi-cvs] SF.net SVN: japi: [187] libs/swing-action/trunk
Status: Beta
Brought to you by:
christianhujer
From: <chr...@us...> - 2006-10-14 12:49:47
|
Revision: 187 http://svn.sourceforge.net/japi/?rev=187&view=rev Author: christianhujer Date: 2006-10-14 05:49:23 -0700 (Sat, 14 Oct 2006) Log Message: ----------- Copied action classes from historic to swing-action subproject. Added Paths: ----------- libs/swing-action/trunk/src/ libs/swing-action/trunk/src/net/ libs/swing-action/trunk/src/net/sf/ libs/swing-action/trunk/src/net/sf/japi/ libs/swing-action/trunk/src/net/sf/japi/swing/ libs/swing-action/trunk/src/net/sf/japi/swing/ActionFactory.java libs/swing-action/trunk/src/net/sf/japi/swing/ActionMethod.java libs/swing-action/trunk/src/net/sf/japi/swing/ActionProvider.java libs/swing-action/trunk/src/net/sf/japi/swing/DisposeAction.java libs/swing-action/trunk/src/net/sf/japi/swing/DummyAction.java libs/swing-action/trunk/src/net/sf/japi/swing/NamedActionMap.java libs/swing-action/trunk/src/net/sf/japi/swing/ReflectionAction.java libs/swing-action/trunk/src/net/sf/japi/swing/ToggleAction.java Copied: libs/swing-action/trunk/src/net/sf/japi/swing/ActionFactory.java (from rev 181, historic/trunk/src/app/net/sf/japi/swing/ActionFactory.java) =================================================================== --- libs/swing-action/trunk/src/net/sf/japi/swing/ActionFactory.java (rev 0) +++ libs/swing-action/trunk/src/net/sf/japi/swing/ActionFactory.java 2006-10-14 12:49:23 UTC (rev 187) @@ -0,0 +1,1099 @@ +/* JAPI - (Yet another (hopefully) useful) Java API + * + * Copyright (C) 2004-2006 Christian Hujer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +package net.sf.japi.swing; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.lang.reflect.Field; +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import static java.util.ResourceBundle.getBundle; +import java.util.WeakHashMap; +import java.util.ArrayList; +import java.util.prefs.Preferences; +import static java.util.prefs.Preferences.userNodeForPackage; +import javax.swing.AbstractAction; +import javax.swing.Action; +import static javax.swing.Action.ACCELERATOR_KEY; +import static javax.swing.Action.LONG_DESCRIPTION; +import static javax.swing.Action.MNEMONIC_KEY; +import static javax.swing.Action.NAME; +import static javax.swing.Action.SHORT_DESCRIPTION; +import static javax.swing.Action.SMALL_ICON; +import javax.swing.ActionMap; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JToolBar; +import javax.swing.JPopupMenu; +import static javax.swing.KeyStroke.getKeyStroke; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import static net.sf.japi.swing.IconManager.getDefaultIconManager; +import static net.sf.japi.swing.ReflectionAction.REFLECTION_MESSAGE_PROVIDER; +import static net.sf.japi.swing.ReflectionAction.REFLECTION_TARGET; +import static net.sf.japi.swing.ToggleAction.REFLECTION_PROPERTY_NAME; + +/** Class for creating and initializing {@link Action}s that are localized, user configurable and may invoke their final methods using Reflection; + * also handles extremely much useful stuff for i18n/l10n. + * It is meant as a general service for creating Action objects that have some comfort for the programmer in several aspects: + * <ul> + * <li>Allow zero or more ResourceBundles to be used when creating Actions</li> + * <li>Allow zero or more UserPreferences to be used when creating Actions</li> + * <li>Manage an ActionMap to which created Actions are automatically added</li> + * </ul> + * You may choose to use one or more ActionFactories, depending on the size of your application. + * You may use to spread Action configuration information accross one or more ResourceBundles and one or more Preferences, as you wish. + * When looking for values, the Preferences are queried first, in addition order, after that the ResourceBundles, again in addition order, until + * a value was found. The behaviour when no value was found is undefined. + * <h3>Usage</h3> + * The recommended usage is to + * <ol> + * <li> + * create and initialize an ActionFactory similar as the following example code, put it somewhere at the start of your program: + * <pre> + * ActionFactory myActionFactory = ActionFactory.getFactory("MyApplication"); + * myActionFactory.addBundle("com.mycompany.mypackage.myresource"); // not always required + * myActionFactory.addPref(MyClass.class); + * </pre> + * </li> + * <li> + * then use the ActionFactory from anywhere within the application like this: + * <pre> + * ActionFactory myActionFactory = ActionFactory.getFactory("MyApplication"); + * Action myAction = myActionFactory.createAction("load", this); + * </pre> + * </li> + * </ol> + * <p> + * All actions created or initialized by an instance of this class are optionally put into that instance's {@link ActionMap}. + * If they are stored, you can use that map for later retrieval. + * </p> + * <h4>Usage Notes: Factory Name</h4> + * <ul> + * <li> + * The factory name is used to try to load a resource bundle when a bundle is created. + * The factory name is used as package name for the bundle, the bundle name itself is "action". + * Example: When calling <code>ActionFactory.getFactory("net.sf.japi.swing");</code> for the first time, it is tried to load a + * {@link ResourceBundle} named <code>net.sf.japi.swing.actions</code> for that <code>ActionFactory</code>. + * This automatism has been implemented to save you from the need of initializing an ActionFactory before use. + * </li> + * </ul> + * <h4>Usage Notes: Action Key / Action Name</h4> + * The key you supply as first argument of {@link #createAction(boolean, String, Object)} determines several things: + * <ul> + * <li>The base name for the different keys in the preferences / resource bundle and other known Action Keys: + * <table border="1"> + * <tr><th>What</th><th>Preferences / Bundle key</th><th>Action key if stored in an action</th></tr> + * <tr><td>An (somewhat unique) ID</td><td>(<var>basename</var> itself)</td><td>{@link #ACTION_ID}</td></tr> + * <tr><td>The icon</td><td><code><var>basename</var> + ".icon"</code></td><td>{@link Action#SMALL_ICON}</td></tr> + * <tr><td>The tooltip help</td><td><code><var>basename</var> + ".shortdescription"</code></td><td>{@link Action#SHORT_DESCRIPTION}</td></tr> + * <tr><td>The long help</td><td><code><var>basename</var> + ".longdescription"</code></td><td>{@link Action#LONG_DESCRIPTION}</td></tr> + * <tr><td>The text label</td><td><code><var>basename</var> + ".text"</code></td><td>{@link Action#NAME}</td></tr> + * <tr><td>The keyboard accelerator</td><td><code><var>basename</var> + ".accel"</code></td><td>{@link Action#ACCELERATOR_KEY}</td></tr> + * <tr><td>The alternate keyboard accelerator</td><td><code><var>basename</var> + ".accel2"</code></td><td>{@link #ACCELERATOR_KEY_2}</td></tr> + * <tr><td>The mnemonic</td><td><code><var>basename</var> + ".mnemonic"</code></td><td>{@link Action#MNEMONIC_KEY}</td></tr> + * <tr><td>The method name</td><td></td><td>{@link ReflectionAction#REFLECTION_METHOD_NAME}</td></tr> + * <tr><td>The method</td><td></td><td>{@link ReflectionAction#REFLECTION_METHOD}</td></tr> + * <tr><td>The boolean property name</td><td></td><td>{@link ToggleAction#REFLECTION_PROPERTY_NAME}</td></tr> + * <tr><td>The target instance</td><td></td><td>{@link ReflectionAction#REFLECTION_TARGET}</td></tr> + * <tr><td>Exception handler dialogs</td><td><code><var>basename</var> + ".exception." + <var>exception class name</var> + ...</code><br/>The message can be formatted with 1 parameter that will be the localized message of the thrown exception.</td><td>n/a</td></tr> + * </table> + * </li> + * </ul> + * <p>Some methods are not related to actions, yet take base keys:</p> + * <ul> + * <li>The methods for dialogs, e.g. {@link #showMessageDialog(Component, String, Object...)}: + * <table border="1"> + * <tr><th>What</th><th>Preferences / Bundle key</th></tr> + * <tr><td>Dialog title</td><td><code><var>basename</var> + ".title"</code></td></tr> + * <tr><td>Dialog message</td><td><code><var>basename</var> + ".message"</code></td></tr> + * <tr><td>Dialog messagetype </td><td><code><var>basename</var> + ".messagetype"</code><br/>The message type should be one of the message types defined in {@link JOptionPane}, e.g. {@link JOptionPane#PLAIN_MESSAGE}.</td></tr> + * </table> + * </li> + * </ul> + * <h4>Final Notes</h4> + * <ul> + * <li> + * If by having read all this you think it might often be a good idea to use a package name as a factory name: this is completely right and the + * most common way of using an ActionFactory. + * </li> + * <li> + * If you think you're too lazy to hold your own ActionFactory reference and instead more often call {@link #getFactory(String)}, just go ahead + * and do so. + * Looking up created ActionFactories is extremely fast, and of course they are initialized exactly once, not more. + * </li> + * </ul> + * @see AbstractAction + * @see Action + * @see Preferences + * @see ResourceBundle + * @todo think about toolbar interaction + * @todo think about toolbar configuration + * @todo eventually rename this ActionBuilder and provide an ActionBuilderFactory. + * @todo whether a dialog is a onetime dialog should be a property and user configurable + * @author <a href="mailto:ch...@ri...">Christian Hujer</a> + */ +public final class ActionFactory { + + /** The key used for storing a somewhat unique id for this Action. */ + @NotNull public static final String ACTION_ID = "ActionID"; + + /** The key used for storing an alternative accelerator for this Action. + * Currently unused. + */ + @NotNull public static final String ACCELERATOR_KEY_2 = "AcceleratorKey2"; + + /** The ActionFactories. */ + @NotNull private static final Map<String, ActionFactory> FACTORIES = new WeakHashMap<String, ActionFactory>(); + + /** The parent ActionFactories. */ + @NotNull private final List<ActionFactory> parents = new LinkedList<ActionFactory>(); + + /** The ResourceBundles to look for. + * Type: ResourceBundle + */ + @NotNull private final List<ResourceBundle> bundles = new LinkedList<ResourceBundle>(); + + /** The Preferences to look for. + * Type: Preferences + */ + @NotNull private final List<Preferences> prefs = new LinkedList<Preferences>(); + + /** The ActionMap to which created Actions are automatically added. */ + @NotNull private final ActionMap actionMap = new NamedActionMap(); + + private List<ActionProvider> actionProviders = new ArrayList<ActionProvider>(); + + /** Get an ActionFactory. + * If there is no ActionFactory with name <var>key</var>, a new ActionFactory is created and stored. + * Future invocations of this method will constantly return that ActionFactory unless the key is garbage collected. + * If you must prevent the key from being garbage collected (and with it the ActionFactory), you may internalize the key ({@link String#intern()}). + * A good name for a key is the application or package name. + * The <code><var>key</var></code> may be a package name, in which case it is tried to load a {@link ResourceBundle} named "action" from that + * package and add it ({@link #addBundle(ResourceBundle)}); nothing special happens if that fails. + * @param key name of ActionFactory (which even may be <code>null</code> if you are too lazy to invent a key) + * @return ActionFactory for given key. The factory is created in case it didn't already exist. + */ + @NotNull public static ActionFactory getFactory(@Nullable final String key) { + ActionFactory factory = FACTORIES.get(key); + if (factory == null) { + factory = new ActionFactory(); + FACTORIES.put(key, factory); + try { + factory.addBundle(key + ".action"); + } catch (final MissingResourceException e) { + /* ignore */ + } + // eventually initialize factory here + } + return factory; + } + + /** Add a ResourceBundle to the list of used bundles. + * @param baseName the base name of the resource bundle, a fully qualified class name + * @see ResourceBundle#getBundle(String) + */ + public void addBundle(@NotNull final String baseName) { + //noinspection ConstantConditions + if (baseName == null) { + throw new NullPointerException("null bundle name not allowed"); + } + @NotNull final ResourceBundle newBundle = getBundle(baseName); + addBundle(newBundle); + @Nullable final String additionalBundles = newBundle.getString("ActionFactory.additionalBundles"); + if (additionalBundles != null) { + for (final String additionalBundle : additionalBundles.split("\\s+")) { + addBundle(additionalBundle); + } + } + } + + /** Method to find the JMenuItem for a specific Action. + * @param menuBar JMenuBar to search + * @param action Action to find JMenuItem for + * @return JMenuItem for action or <code>null</code> if none found + * @throws NullPointerException if <var>action</var> or <var>menuBar</var> is <code>null</code> + */ + @Nullable public static JMenuItem find(@NotNull final JMenuBar menuBar, @NotNull final Action action) { + //noinspection ConstantConditions + if (menuBar == null) { + throw new NullPointerException("null JMenuBar not allowed"); + } + //noinspection ConstantConditions + if (action == null) { + throw new NullPointerException("null Action not allowed"); + } + for (int i = 0; i < menuBar.getMenuCount(); i++) { + final JMenu menu = menuBar.getMenu(i); + if (menu.getAction() == action) { + return menu; + } else { + final JMenuItem ret = find(menu, action); + if (ret != null) { + return ret; + } + } + } + return null; + } + + /** Method to find the JMenuItem for a specific Action. + * @param menu JMenu to search + * @param action Action to find JMenuItem for + * @return JMenuItem for action or <code>null</code> if none found + * @throws NullPointerException if <var>menu</var> or <var>action</var> is <code>null</code> + */ + @Nullable public static JMenuItem find(@NotNull final JMenu menu, @NotNull final Action action) { + for (int i = 0; i < menu.getItemCount(); i++) { + final JMenuItem item = menu.getItem(i); + if (item == null) { + // Ignore Separators + } else if (item.getAction() == action) { + return item; + } else if (item instanceof JMenu) { + final JMenuItem ret = find((JMenu) item, action); + if (ret != null) { + return ret; + } + } + } + return null; + } + + /** Create an ActionFactory. + * This constructor is private to force users to use the method {@link #getFactory(String)} for recycling ActionFactories and profit of easy + * access to the same ActionFactory from within the whole application without passing around ActionFactory references. + */ + private ActionFactory() { + } + + /** Get the ActionMap. + * @return ActionMap + */ + @NotNull public ActionMap getActionMap() { + return actionMap; + } + + /** Add a ResourceBundle to the list of used bundles. + * @param bundle ResourceBundle to add + * @throws NullPointerException if <code>bundle == null</code> + */ + public void addBundle(@NotNull final ResourceBundle bundle) throws NullPointerException { + //noinspection ConstantConditions + if (bundle == null) { + throw new NullPointerException("null ResourceBundle not allowed"); + } + if (!bundles.contains(bundle)) { + // insert first because new bundles override old bundles + bundles.add(0, bundle); + } + } + + /** Add a parent to the list of used parents. + * @param parent Parent to use if lookups failed in this ActionFactory + * WARNING: Adding a descendents as parents of ancestors or vice versa will result in endless recursion and thus stack overflow! + * @throws NullPointerException if <code>parent == null</code> + */ + public void addParent(@NotNull final ActionFactory parent) throws NullPointerException { + //noinspection ConstantConditions + if (parent == null) { + throw new NullPointerException("null ActionFactory not allowed"); + } + parents.add(parent); + } + + /** Add a Preferences to the list of used preferences. + * @param pref Preferences to add + * @throws NullPointerException if <code>pref == null</code> + */ + public void addPref(@NotNull final Preferences pref) throws NullPointerException { + //noinspection ConstantConditions + if (pref == null) { + throw new NullPointerException("null ResourceBundle not allowed"); + } + if (!prefs.contains(pref)) { + prefs.add(pref); + } + } + + /** Add a Preferences to the list of used preferences. + * @param clazz the class whose package a user preference node is desired + * @see Preferences#userNodeForPackage(Class) + * @throws NullPointerException if <code>clazz == null</code> + */ + public void addPref(@NotNull final Class<?> clazz) { + //noinspection ConstantConditions + if (clazz == null) { + throw new NullPointerException("null Class not allowed"); + } + addPref(userNodeForPackage(clazz)); + } + + /** Creates actions. + * This is a loop variant of {@link #createAction(boolean,String)}. + * The actions created can be retrieved using {@link #getAction(String)} or via the ActionMap returned by {@link #getActionMap()}. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param keys Keys of actions to create + * @return Array with created actions + * @throws NullPointerException in case keys is or contains <code>null</code> + */ + public Action[] createActions(final boolean store, @NotNull final String... keys) throws NullPointerException { + final Action[] actions = new Action[keys.length]; + for (int i = 0; i < keys.length; i++) { + actions[i] = createAction(store, keys[i]); + } + return actions; + } + + /** Create an Action. + * The created Action is automatically stored together with all other Actions created by this Factory instance in an ActionMap, which you can + * retreive using {@link #getActionMap()}. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param key Key for Action, which is used as basename for access to Preferences and ResourceBundles and as ActionMap key (may not be + * <code>null</code>) + * @return created Action, which is a dummy in the sense that its {@link Action#actionPerformed(ActionEvent)} method does not do anything + * @throws NullPointerException in case <var>key</var> was <code>null</code> + * @see #createAction(boolean,String,Object) + * @see #createToggle(boolean,String,Object) + * @see #initAction(boolean,Action,String) + */ + public Action createAction(final boolean store, @NotNull final String key) throws NullPointerException { + // initAction() checks for null key + return initAction(store, new DummyAction(), key); + } + + /** Initialize an Action. + * This is a convenience method for Action users which want to use the services provided by this {@link ActionFactory} class but need more + * sophisticated Action objects they created on their own. + * So you can simply create an Action and pass it to this Initialization method to fill its data. + * The initialized Action is automatically stored together with all other Actions created by this Factory instance in an ActionMap, which you can + * retreive using {@link #getActionMap()}. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param action Action to fill + * @param key Key for Action, which is used as basename for access to Preferences and ResourceBundles and as ActionMap key (may not be <code>null</code>) + * @return the supplied Action object (<var>action</var>) is returned for convenience + * @throws NullPointerException in case <var>key</var> was <code>null</code> + */ + @SuppressWarnings({"NestedAssignment"}) + public Action initAction(final boolean store, @NotNull final Action action, @NotNull final String key) throws NullPointerException { + if (key == null) { throw new NullPointerException("null key for Action initialization not allowed."); } + action.putValue(ACTION_ID, key); + String value; + if ((value = getString(key + ".text" )) != null) { action.putValue(NAME, value); } + if ((value = getString(key + ".shortdescription")) != null) { action.putValue(SHORT_DESCRIPTION, value); } + if ((value = getString(key + ".longdescription" )) != null) { action.putValue(LONG_DESCRIPTION, value); } + if ((value = getString(key + ".accel" )) != null) { action.putValue(ACCELERATOR_KEY, getKeyStroke(value)); } + if ((value = getString(key + ".accel2" )) != null) { action.putValue(ACCELERATOR_KEY_2, getKeyStroke(value)); } + if ((value = getString(key + ".mnemonic" )) != null) { action.putValue(MNEMONIC_KEY, getKeyStroke(value).getKeyCode()); } + if ((value = getString(key + ".icon" )) != null) { + final Icon image = getDefaultIconManager().getIcon(value); + if (image != null) { action.putValue(SMALL_ICON, image); } + } + action.putValue(REFLECTION_MESSAGE_PROVIDER, this); + if (store) { + actionMap.put(key, action); + } + return action; + } + + /** Get a String. + * First looks one pref after another, in their addition order. + * Then looks one bundle after another, in their addition order. + * @param key Key to get String for + * @return the first value found or <code>null</code> if no value could be found + * @throws NullPointerException if <var>key</var> is <code>null</code> + */ + @Nullable public String getString(@NotNull final String key) throws NullPointerException { + if (key == null) { + throw new NullPointerException("null key not allowed"); + } + String value = null; + for (final Preferences pref : prefs) { + value = pref.get(key, value); + if (value != null) { + return value; + } + } + for (final ResourceBundle bundle : bundles) { + try { + value = bundle.getString(key); + return value; + } catch (final MissingResourceException e) { /* ignore */ + } catch (final ClassCastException e) { /* ignore */ + } // ignore exceptions because they don't mean errors just there's no resource, so parents are checked or null is returned. + } + for (final ActionFactory parent : parents) { + value = parent.getString(key); + if (value != null) { + return value; + } + } + return null; + } + + /** Get a String from the preferences, ignoring the resource bundles. + * @param key Key to get String for + * @return the first value found or <code>null</code> if no value could be found + * @throws NullPointerException if <var>key</var> is <code>null</code> + */ + @Nullable public String getStringFromPrefs(@NotNull final String key) throws NullPointerException { + if (key == null) { + throw new NullPointerException("null key not allowed"); + } + String value = null; + for (final Preferences pref : prefs) { + value = pref.get(key, value); + if (value != null) { + return value; + } + } + for (final ActionFactory parent : parents) { + value = parent.getStringFromPrefs(key); + if (value != null) { + return value; + } + } + return null; + } + + /** Get a String from the resource bundles, ignoring the preferences. + * @param key Key to get String for + * @return the first value found or <code>null</code> if no value could be found + * @throws NullPointerException if <var>key</var> is <code>null</code> + */ + @Nullable public String getStringFromBundles(@NotNull final String key) throws NullPointerException { + if (key == null) { + throw new NullPointerException("null key not allowed"); + } + String value; + for (final ResourceBundle bundle : bundles) { + try { + value = bundle.getString(key); + return value; + } catch (final MissingResourceException e) { /* ignore */ + } catch (final ClassCastException e) { /* ignore */ + } // ignore exceptions because they don't mean errors just there's no resource, so parents are checked or null is returned. + } + for (final ActionFactory parent : parents) { + value = parent.getStringFromBundles(key); + if (value != null) { + return value; + } + } + return null; + } + /** Creates actions. + * This is a loop variant of {@link #createAction(boolean,String,Object)}. + * The actions created can be retrieved using {@link #getAction(String)} or via the ActionMap returned by {@link #getActionMap()}. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param target Target object + * @param keys Keys of actions to create + * @return Array with created actions + * @throws NullPointerException in case keys is or contains <code>null</code> + */ + public Action[] createActions(final boolean store, final Object target, final String... keys) throws NullPointerException { + final Action[] actions = new Action[keys.length]; + for (int i = 0; i < keys.length; i++) { + actions[i] = createAction(store, keys[i], target); + } + return actions; + } + + /** Create an Action. + * The created Action is automatically stored together with all other Actions created by this Factory instance in an ActionMap, which you can + * retreive using {@link #getActionMap()}. + * The supplied object needs to have a zero argument method named <var>key</var>. + * You may pass <code>null</code> as object, which means that the object the method is invoked on is not defined yet. + * You may safely use the Action, it will not throw any Exceptions upon {@link Action#actionPerformed(ActionEvent)} but simply silently do nothing. + * The desired object can be set later using <code>action.putValue({@link ReflectionAction#REFLECTION_TARGET}, <var>object</var>)</code>. + * <p /> + * Users of this method can assume that the returned object behaves quite like {@link ReflectionAction}. + * Wether or not this method returns an instance of {@link ReflectionAction} should not be relied on. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param key Key for Action, which is used as basename for access to Preferences and ResourceBundles, as ActionMap key and as Reflection Method + * name within the supplied object (may not be <code>null</code>) + * @param object Instance to invoke method on if the Action was activated ({@link Action#actionPerformed(ActionEvent)}) + * @return created Action + * @throws NullPointerException in case <var>key</var> was <code>null</code> + * @see #createAction(boolean,String) + * @see #createToggle(boolean,String,Object) + * @see #initAction(boolean,Action,String) + */ + public Action createAction(final boolean store, final String key, final Object object) throws NullPointerException { + if (key == null) { throw new NullPointerException("null key for Action creation not allowed."); } + final Action action = new ReflectionAction(key, object); + initAction(store, action, key); + return action; + } + + /** Method for creating a Menu. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param menuKey action key for Menu + * @param keys Action keys for menu items + * @return menu created from the menu definition found + * @throws Error in case action definitions for <var>keys</var> weren't found + */ + public JMenu createMenu(final boolean store, final String menuKey, final String... keys) throws Error { + final JMenu menu = new JMenu(createAction(store, menuKey)); + for (final String key : keys) { + if (key != null && key.length() == 0) { + /* ignore this for empty menus */ + } else if (key == null || "-".equals(key) || "|".equals(key)) { + menu.addSeparator(); + } else { + final Action action = getAction(key); + if (action == null) { + throw new Error("No Action for key " + key); + } + if (action instanceof ToggleAction) { + menu.add(((ToggleAction) action).createCheckBoxMenuItem()); + } else { + menu.add(action); + } + } + } + return menu; + } + + /** Get an Action. + * For an action to be retrieved with this method, it must have been initialized with {@link #initAction(boolean,Action,String)}, either directly by + * invoking {@link #initAction(boolean,Action,String)} or indirectly by invoking one of this class' creation methods like {@link #createAction(boolean,String)}, + * {@link #createAction(boolean,String,Object)} or {@link #createToggle(boolean,String,Object)}. + * @param key Key of action to get + * @return Action for <var>key</var> + * This method does the same as <code>getActionMap().get(key)</code>. + */ + public Action getAction(final String key) { + Action action = actionMap.get(key); + if (action == null) { + for (final ActionProvider actionProvider : actionProviders) { + action = actionProvider.getAction(key); + if (action != null) { + actionMap.put(key, action); + break; + } + } + } + return action; + } + + /** Method for creating a menubar. + * @param store whether to store the initialized Actions in the ActionMap of this ActionFactory + * @param barKey Action key of menu to create + * @return JMenuBar created for <var>barKey</var> + * @throws NullPointerException if no menubar definition was found + * @todo make error handling consistent (see createMenu and others) + */ + @NotNull public JMenuBar createMenuBar(final boolean store, final String barKey) throws NullPointerException { + final JMenuBar menuBar = new JMenuBar(); + //noinspection ConstantConditions + for (final String key : getString(barKey + ".menubar").split("\\s+")) { + menuBar.add(createMenu(store, key)); + } + return menuBar; + } + + /** Method for creating a popup menu. + * @param store whether to store the initialized Actions in the ActionMap of this ActionFactory + * @param popupKey Action key of popup menu to create + * @return JPopupMenu created for <var>popupKey</var> + * @throws NullPointerException if no menubar definition was found + * @todo make error handling consistent (see createMenu and others) + */ + @NotNull public JPopupMenu createPopupMenu(final boolean store, final String popupKey) throws NullPointerException { + final JPopupMenu menu = new JPopupMenu(); + for (final String key : getString(popupKey + ".menu").split("\\s+")) { + if (key != null && key.length() == 0) { + /* ignore this for empty menus */ + } else if (key == null || "-".equals(key) || "|".equals(key)) { + menu.addSeparator(); + } else if (getString(key + ".menu") != null) { + menu.add(createMenu(store, key)); + } else { + final Action action = getAction(key); + if (action == null) { + throw new Error("No Action for key " + key); + } + if (action instanceof ToggleAction) { + menu.add(((ToggleAction) action).createCheckBoxMenuItem()); + } else { + menu.add(action); + } + } + } + return menu; + } + + /** Method for creating a Menu. + * This method assumes that the underlying properties contain an entry like <code>key + ".menu"</code> which lists the menu element's keys. + * Submenus are build recursively. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param menuKey action key for menu + * @return menu created from the menu definition found + * @throws Error in case a menu definition for <var>menuKey</var> wasn't found + */ + public JMenu createMenu(final boolean store, final String menuKey) throws Error { + final JMenu menu = new JMenu(createAction(store, menuKey)); + for (final String key : getString(menuKey + ".menu").split("\\s+")) { + if (key != null && key.length() == 0) { + /* ignore this for empty menus */ + } else if (key == null || "-".equals(key) || "|".equals(key)) { + menu.addSeparator(); + } else if (getString(key + ".menu") != null) { + menu.add(createMenu(store, key)); + } else { + final Action action = getAction(key); + if (action == null) { + throw new Error("No Action for key " + key); + } + if (action instanceof ToggleAction) { + menu.add(((ToggleAction) action).createCheckBoxMenuItem()); + } else { + menu.add(action); + } + } + } + return menu; + } + + /** Method for creating a menubar. + * @param store whether to store the initialized Actions in the ActionMap of this ActionFactory + * @param barKey Action key of menu to create + * @param target Target object + * @return JMenuBar created for <var>barKey</var> + */ + public JMenuBar createMenuBar(final boolean store, final String barKey, final Object target) { + final JMenuBar menuBar = new JMenuBar(); + final String menuBarSpec = getString(barKey + ".menubar"); + if (menuBarSpec == null) { + throw new RuntimeException("Missing Resource for " + barKey + ".menubar"); + } + for (final String key : menuBarSpec.split("\\s+")) { + menuBar.add(createMenu(store, key, target)); + } + return menuBar; + } + + /** Method for creating a popup menu. + * @param store whether to store the initialized Actions in the ActionMap of this ActionFactory + * @param popupKey Action key of popup menu to create + * @param target Target object + * @return JPopupMenu created for <var>barKey</var> + */ + public JPopupMenu createPopupMenu(final boolean store, final String popupKey, final Object target) { + final JPopupMenu menu = new JPopupMenu(); + for (final String key : getString(popupKey + ".menu").split("\\s+")) { + if (key != null && key.length() == 0) { + /* ignore this for empty menus */ + } else if (key == null || "-".equals(key) || "|".equals(key)) { + menu.addSeparator(); + } else if (getString(key + ".menu") != null) { + menu.add(createMenu(store, key, target)); + } else { + Action action = null; + if (store) { + action = getAction(key); + } + if (action == null) { + action = createAction(store, key, target); + } + if (action == null) { + throw new Error("No Action for key " + key); + } + if (action instanceof ToggleAction) { + menu.add(((ToggleAction) action).createCheckBoxMenuItem()); + } else { + menu.add(action); + } + } + } + return menu; + } + + /** Method for creating a Menu. + * This method assumes that the underlying properties contain an entry like <code>key + ".menu"</code> which lists the menu element's keys. + * Submenus are build recursively. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param menuKey action key for menu + * @param target Target object + * @return menu created from the menu definition found + * @throws Error in case a menu definition for <var>menuKey</var> wasn't found + */ + public JMenu createMenu(final boolean store, final String menuKey, final Object target) throws Error { + final JMenu menu = new JMenu(createAction(store, menuKey)); + for (final String key : getString(menuKey + ".menu").split("\\s+")) { + if (key != null && key.length() == 0) { + /* ignore this for empty menus */ + } else if (key == null || "-".equals(key) || "|".equals(key)) { + menu.addSeparator(); + } else if (getString(key + ".menu") != null) { + menu.add(createMenu(store, key, target)); + } else { + Action action = null; + if (store) { + action = getAction(key); + } + if (action == null) { + action = createAction(store, key, target); + } + if (action == null) { + throw new Error("No Action for key " + key); + } + if (action instanceof ToggleAction) { + menu.add(((ToggleAction) action).createCheckBoxMenuItem()); + } else { + menu.add(action); + } + } + } + return menu; + } + + /** Creates actions. + * This is a loop variant of {@link #createToggle(boolean,String,Object)}. + * The actions created can be retrieved using {@link #getAction(String)} or via the ActionMap returned by {@link #getActionMap()}. + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param target Target object + * @param keys Keys of actions to create + * @throws NullPointerException in case <var>keys</var> was or contained <code>null</code> + */ + public void createToggles(final boolean store, final Object target, final String... keys) throws NullPointerException { + for (final String key : keys) { + createToggle(store, key, target); + } + } + + /** Create an Action. + * The created Action is automatically stored together with all other Actions created by this Factory instance in an ActionMap, which you can + * retreive using {@link #getActionMap()}. + * The supplied object needs to have a boolean return void argument getter method and a void return boolean argument setter method matching the + * <var>key</var>. + * You may pass <code>null</code> as object, which means that the object the getters and setters are invoked on is not defined yet. + * You may safely use the Action, it will not throw any Exceptions upon {@link Action#actionPerformed(ActionEvent)} but simply silently do nothing. + * The desired object can be set later using <code>action.putValue({@link ToggleAction#REFLECTION_TARGET}, <var>object</var>)</code>. + * <p /> + * Users of this method can assume that the returned object behaves quite like {@link ToggleAction}. + * Wether or not this method returns an instance of {@link ToggleAction} shuold not be relied on. + * @see #createAction(boolean,String) + * @see #createAction(boolean,String,Object) + * @see #initAction(boolean,Action,String) + * @param store whether to store the initialized Action in the ActionMap of this ActionFactory + * @param key Key for Action, which is used as basename for access to Preferences and ResourceBundles, as ActionMap key and as Property name within + * the supplied object (may not be <code>null</code>) + * @param object Instance to invoke method on if the Action was activated ({@link Action#actionPerformed(ActionEvent)}) + * @throws NullPointerException in case <var>key</var> was <code>null</code> + * @return ToggleAction + */ + public Action createToggle(final boolean store, final String key, final Object object) throws NullPointerException { + final Action action = new ToggleAction(); + initAction(store, action, key); + action.putValue(REFLECTION_PROPERTY_NAME, key); + action.putValue(REFLECTION_TARGET, object); + return action; + } + + /** Method for creating a toolbar. + * @param keys Action keys for toolbar entries + * @return JToolBar created for <var>keys</var> + */ + public JToolBar createToolBar(final String... keys) { + final JToolBar toolBar = new JToolBar(); + for (final String key : keys) { + if (key == null || "-".equals(key) || "|".equals(key)) { + toolBar.addSeparator(); + } else { + final Action action = getAction(key); + if (action == null) { + throw new Error("No Action for key " + key); + } + toolBar.add(action); + } + } + return toolBar; + } + + /** Method for creating a toolbar. + * @param barKey Action keys of toolbar to create + * @return JToolBar created for <var>barKey</var> + */ + public JToolBar createToolBar(final String barKey) { + return createToolBar(getString(barKey + ".toolbar").split("\\s+")); + } + + /** Method for creating a toolbar. + * @param object Instance to invoke method on if the Action was activated ({@link Action#actionPerformed(ActionEvent)}) + * @param keys Action keys for toolbar entries + * @return JToolBar created for <var>keys</var> + */ + public JToolBar createToolBar(final Object object, final String... keys) { + final JToolBar toolBar = new JToolBar(); + for (final String key : keys) { + if (key == null || "-".equals(key) || "|".equals(key)) { + toolBar.addSeparator(); + } else { + Action action = getAction(key); + if (action == null) { + action = createAction(false, key, object); + } + toolBar.add(action); + } + } + return toolBar; + } + + /** Method for creating a toolbar. + * @param object Instance to invoke method on if the Action was activated ({@link Action#actionPerformed(ActionEvent)}) + * @param barKey Action keys of toolbar to create + * @return JToolBar created for <var>barKey</var> + */ + public JToolBar createToolBar(final Object object, final String barKey) { + return createToolBar(object, getString(barKey + ".toolbar").split("\\s+")); + } + + /** Method to find the JMenuItem for a specific Action key. + * @param menuBar JMenuBar to search + * @param key Key to find JMenuItem for + * @return JMenuItem for key or <code>null</code> if none found + */ + public JMenuItem find(final JMenuBar menuBar, final String key) { + return find(menuBar, getAction(key)); + } + + /** Get an icon. + * @param key i18n key for icon + * @return icon + */ + public Icon getIcon(final String key) { + return getDefaultIconManager().getIcon(getString(key)); + } + + /** Show a localized message dialog. + * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has + * no <code>Frame</code>, a default <code>Frame</code> is used + * @param messageType the type of message to be displayed + * @param key localization key to use for the title, the message and eventually the icon + * @param args formatting arguments for the message text + * @deprecated use {@link #showMessageDialog(Component, String, Object...)} instead and put the messagetype in the action.properties file. + */ + @Deprecated public void showMessageDialog(final Component parentComponent, final int messageType, final String key, final Object... args) { + JOptionPane.showMessageDialog(parentComponent, format(key + ".message", args), format(key + ".title", args), messageType); + } + + /** Show a localized message dialog. + * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has + * no <code>Frame</code>, a default <code>Frame</code> is used + * @param key localization key to use for the title, the message and eventually the icon + * @param args formatting arguments for the message text + */ + public void showMessageDialog(@Nullable final Component parentComponent, @NotNull final String key, final Object... args) { + JOptionPane.showMessageDialog(parentComponent, format(key + ".message", args), format(key + ".title", args), getMessageType(key)); + } + + /** Get the message type for a dialog. + * @param dialogKey dialog key + * @return message type + */ + public int getMessageType(@NotNull final String dialogKey) { + final String typeText = getString(dialogKey + ".messageType"); + if (typeText == null) { + return JOptionPane.PLAIN_MESSAGE; + } + if (!typeText.endsWith("_MESSAGE")) { + System.err.println("Warning: Illegal type value " + typeText + " for dialog " + dialogKey); // TODO: I18N/L10N + return JOptionPane.PLAIN_MESSAGE; + } + if ("PLAIN_MESSAGE".equals(typeText)) { + return JOptionPane.PLAIN_MESSAGE; + } else if ("QUESTION_MESSAGE".equals(typeText)) { + return JOptionPane.QUESTION_MESSAGE; + } else if ("WARNING_MESSAGE".equals(typeText)) { + return JOptionPane.WARNING_MESSAGE; + } else if ("INFORMATION_MESSAGE".equals(typeText)) { + return JOptionPane.INFORMATION_MESSAGE; + } else if ("ERROR_MESSAGE".equals(typeText)) { + return JOptionPane.ERROR_MESSAGE; + } else { + // key not known, try reflection. + try { + final Field f = JOptionPane.class.getField(typeText); + if (f.getType() == Integer.TYPE) { + return f.getInt(null); + } + } catch (final NoSuchFieldException e) { + System.err.println("Warning: Field " + typeText + " not found in JOptionPane (dialog: " + dialogKey + ")."); // TODO: I18N/L10N + e.printStackTrace(); //TODO + } catch (final IllegalAccessException e) { + System.err.println("Warning: Field " + typeText + " not accessible in JOptionPane (dialog: " + dialogKey + ")."); // TODO: I18N/L10N + e.printStackTrace(); //TODO + } + return JOptionPane.PLAIN_MESSAGE; + } + } + + /** Formats a message with parameters. + * It's a proxy method for using {@link MessageFormat}. + * @param key message key + * @param args parameters + * @return formatted String + * @see MessageFormat#format(String,Object...) + */ + public String format(@NotNull final String key, final Object... args) { + return MessageFormat.format(getString(key), args); + } + + /** Show a localized confirmation dialog which the user can suppress in future (remembering his choice). + * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has + * no <code>Frame</code>, a default <code>Frame</code> is used + * @param optionType the option type + * @param messageType the type of message to be displayed + * @param key localization key to use for the title, the message and eventually the icon + * @param args formatting arguments for the message text + * @return an integer indicating the option selected by the user now or eventually in a previous choice + * @throws IllegalStateException if no preferences are associated + */ + public int showOnetimeConfirmDialog(@Nullable final Component parentComponent, final int optionType, final int messageType, @NotNull final String key, final Object... args) throws IllegalStateException { + @NonNls String showString = getString(key + ".show"); + if (showString == null) { + showString = getString(key + ".showDefault"); + } + if (showString == null) { + showString = "true"; + } + final boolean show = !"false".equalsIgnoreCase(showString); // undefined should be treated true + if (!show) { + try { + return Integer.parseInt(getString(key + ".choice")); + } catch (final Exception ignore) { + /* ignore, continue with dialog then. */ + } + } + final JCheckBox dontShowAgain = new JCheckBox(getString("dialogDontShowAgain"), true); + final int result = JOptionPane.showConfirmDialog(parentComponent, new Object[] { format(key + ".message", args), dontShowAgain }, format(key + ".title", args), optionType, messageType); + if (!dontShowAgain.isSelected()) { + if (prefs.size() > 0) { + prefs.get(0).put(key + ".show", "false"); + prefs.get(0).put(key + ".choice", Integer.toString(result)); + } else { + throw new IllegalStateException("Cannot store prefs for this dialog - no Preferences associated with this ActionFactory!"); + } + } + return result; + } + + /** Show a localized message dialog which the user can suppress in future. + * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has + * no <code>Frame</code>, a default <code>Frame</code> is used + * @param messageType the type of message to be displayed + * @param key localization key to use for the title, the message and eventually the icon + * @param args formatting arguments for the message text + * @throws IllegalStateException if no preferences are associated + */ + public void showOnetimeMessageDialog(@Nullable final Component parentComponent, final int messageType, @NotNull final String key, final Object... args) throws IllegalStateException { + @NonNls String showString = getString(key + ".show"); + if (showString == null) { + showString = getString(key + ".showDefault"); + } + if (showString == null) { + showString = "true"; + } + final boolean show = !"false".equalsIgnoreCase(showString); // undefined should be treated true + if (show) { + final JCheckBox dontShowAgain = new JCheckBox(getString("dialogDontShowAgain"), true); + JOptionPane.showMessageDialog(parentComponent, new Object[] { format(key + ".message", args), dontShowAgain }, format(key + ".title", args), messageType); + if (!dontShowAgain.isSelected()) { + if (prefs.size() > 0) { + prefs.get(0).put(key + ".show", "false"); + } else { + throw new IllegalStateException("Cannot store prefs for this dialog - no Preferences associated with this ActionFactory!"); + } + } + } + } + + /** Show a localized question dialog. + * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has + * no <code>Frame</code>, a default <code>Frame</code> is used + * @param key localization key to use for the title, the message and eventually the icon + * @param args formatting arguments for the message text + * @return <code>true</code> if user confirmed, otherwise <code>false</code> + */ + public boolean showQuestionDialog(final Component parentComponent, final String key, final Object... args) { + return showConfirmDialog(parentComponent, JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, key, args) == JOptionPane.YES_OPTION; + } + + /** Show a localized confirmation dialog. + * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has + * no <code>Frame</code>, a default <code>Frame</code> is used + * @param optionType the option type + * @param messageType the type of message to be displayed + * @param key localization key to use for the title, the message and eventually the icon + * @param args formatting arguments for the message text + * @return an integer indicating the option selected by the user + */ + public int showConfirmDialog(final Component parentComponent, final int optionType, final int messageType, final String key, final Object... args) { + return JOptionPane.showConfirmDialog(parentComponent, format(key + ".message", args), format(key + ".title", args), optionType, messageType); + } + + /** Creates a label for a specified key. + * @param key Key to create label for + * @param args formatting arguments for the label text + * @return JLabel for key Key + * @note the label text will be the key if no String for the key was found + */ + public JLabel createLabel(final String key, final Object... args) { + return createLabel((Component) null, key, args); + } + + /** Creates a label for a specified key and component. + * @param component Component to associate label to (maybe <code>null</code>) + * @param key Key to create label for + * @param args formatting arguments for the label text + * @return JLabel for key Key + * @note the label text will be the key if no String for the key was found + * @todo support icons + * @todo support mnemonics + * @todo alignments and textpositions + */ + public JLabel createLabel(final Component component, final String key, final Object... args) { + final String labelText = format(key, args); + final JLabel label = new JLabel(labelText != null ? labelText : key); + label.setLabelFor(component); + return label; + } + + /** Registers an ActionProvider with this ActionFactory. + * @param actionProvider ActionProvider to register + */ + public void addActionProvider(final ActionProvider actionProvider) { + actionProviders.add(actionProvider); + } + +} // class ActionFactory Copied: libs/swing-action/trunk/src/net/sf/japi/swing/ActionMethod.java (from rev 181, historic/trunk/src/app/net/sf/japi/swing/ActionMethod.java) =================================================================== --- libs/swing-action/trunk/src/net/sf/japi/swing/ActionMethod.java (rev 0) +++ libs/swing-action/trunk/src/net/sf/japi/swing/ActionMethod.java 2006-10-14 12:49:23 UTC (rev 187) @@ -0,0 +1,41 @@ +/* JAPI - (Yet another (hopefully) useful) Java API + * + * Copyright (C) 2004-2006 Christian Hujer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +package net.sf.japi.swing; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** Annotation for methods that are Actions. + * {@link ActionFactory} in future will automatically configure and store Actions with properties for methods that are annotated with this Annotation. + * In future, this Annotation might get some attributes, but currently it hasn't got any. + * There is no guarantee for future attributes to be optional. + * @author <a href="mailto:ch...@ri...">Christian Hujer</a> + */ +@Documented +@Inherited +@Retention(RUNTIME) +@Target(METHOD) +public @interface ActionMethod { +} // @interface ActionMethod Copied: libs/swing-action/trunk/src/net/sf/japi/swing/ActionProvider.java (from rev 181, historic/trunk/src/app/net/sf/japi/swing/ActionProvider.java) =================================================================== --- libs/swing-action/trunk/src/net/sf/japi/swing/ActionProvider.java (rev 0) +++ libs/swing-action/trunk/src/net/sf/japi/swing/ActionProvider.java 2006-10-14 12:49:23 UTC (rev 187) @@ -0,0 +1,37 @@ +/* JAPI - (Yet another (hopefully) useful) Java API + * + * Copyright (C) 2004-2006 Christian Hujer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You ... [truncated message content] |