001: package com.opensymphony.workflow.designer;
002:
003: import java.net.URL;
004: import java.util.*;
005: import javax.swing.*;
006:
007: /**
008: * Central repository for all Actions.
009: * On startup, the application is responsible for registering all actions via the {@link #register(java.lang.String, javax.swing.Action)}
010: * method. Once this is done, any action can be retrieved via the {@link #get(java.lang.String)} method.
011: * The action manager will look for an actions.properties file in the same package, and will read all properties
012: * specified in it for a given action.
013: * <p>
014: * The benefit of specifying actions in the external file is that actions themselves need not be aware of their textual or
015: * graphic representation or key bindings.
016: */
017: public final class ActionManager {
018: private static final String OS_NAME_STRING = System.getProperty(
019: "os.name").replace(' ', '_').toLowerCase();
020:
021: private static final String SMALL_GRAY_ICON = "smallGrayIcon";
022: private static final String DISPLAYED_MNEMONIC_INDEX = "mnemonicIndex";
023:
024: private static final ActionManager INSTANCE = new ActionManager();
025:
026: private final Map actions;
027: private ResourceBundle bundle;
028:
029: private ActionManager() {
030: this .actions = new HashMap(50);
031: bundle = ResourceBundle
032: .getBundle("com.opensymphony.workflow.designer.actions");
033: }
034:
035: /**
036: * Register an action.
037: * @param id The action id denotes the set of properties that will be read for the action
038: * @param action The action instance to bind to the specified id.
039: * @return The action, once it has been initialised. If the id is not specified in the
040: * properties file, then null is returned.
041: * @throws NullPointerException if the specified action is null
042: */
043: public static Action register(String id, Action action) {
044: if (action == null)
045: throw new NullPointerException(
046: "Registered actions must not be null.");
047:
048: boolean exists = ActionReader.readAndPutValues(action,
049: INSTANCE.bundle, id);
050: if (!exists)
051: return null;
052:
053: Object oldValue = INSTANCE.actions.put(id, action);
054: if (oldValue != null)
055: System.out.println("WARNING: Duplicate action id: " + id);
056: return action;
057: }
058:
059: /**
060: * Remove a registered action
061: * @param id the action id
062: * @return The removed action, if it existed.
063: */
064: public static Action deregister(String id) {
065: return (Action) INSTANCE.actions.remove(id);
066: }
067:
068: /**
069: * Get a previously registered action
070: * @param id The action id
071: * @return The action bound to the specified id, or null if no action is bound.
072: */
073: public static Action get(String id) {
074: Action action = (Action) (INSTANCE.actions.get(id));
075: if (null == action) {
076: System.out.println("ERROR: No action found for id: " + id);
077: return null;
078: }
079: return action;
080: }
081:
082: /**
083: * Retrieves and answers the small icon for the given <code>id</code>.
084: */
085: public static Icon getIcon(String id) {
086: Action action = get(id);
087: if (action == null)
088: return null;
089: return (Icon) action.getValue(Action.SMALL_ICON);
090: }
091:
092: /**
093: * Alias a particular id to another one.
094: * This allows one action to be bound to multiple keys.
095: * @param newKey The new alias to bind to.
096: * @param oldKey The old id to bind to.
097: */
098: public static void alias(String newKey, String oldKey) {
099: Object oldValue = INSTANCE.actions.put(newKey, INSTANCE.actions
100: .get(oldKey));
101: if (oldValue != null)
102: System.out.println("WARNING: Duplicate action id: "
103: + newKey);
104: }
105:
106: private static class ActionReader {
107: private static final String LABEL = "label";
108: private static final char MNEMONIC_MARKER = '&';
109: private static final String DOT_STRING = "...";
110: private static final String SHORT_DESCRIPTION = "tooltip";
111: private static final String LONG_DESCRIPTION = "helptext";
112: private static final String ICON = "icon";
113: private static final String GRAY_ICON = ICON + ".gray";
114: private static final String ACCELERATOR = "accelerator";
115: private static final String COMMAND = "command";
116:
117: private String id;
118: private String name;
119: private Integer mnemonic;
120: private Integer aMnemonicIndex;
121: private String shortDescription;
122: private String longDescription;
123: private ImageIcon icon;
124: private ImageIcon grayIcon;
125: private KeyStroke accelerator;
126: private String command;
127: private boolean exists = true;
128:
129: /**
130: * Reads properties for <code>id</code> in <code>bundle</code>.
131: */
132: static void readValues(ResourceBundle bundle, String id) {
133: new ActionReader(bundle, id);
134: }
135:
136: /**
137: * Reads properties for <code>id</code> in <code>bundle</code> and
138: * sets the approriate values in the given <code>action</code>.
139: */
140: static boolean readAndPutValues(Action action,
141: ResourceBundle bundle, String id) {
142: ActionReader reader = new ActionReader(bundle, id);
143: if (!reader.actionExists())
144: return false;
145: reader.putValues(action);
146: return true;
147: }
148:
149: private ActionReader(ResourceBundle bundle, String id) {
150: String iconPath = getString(bundle, id + '.' + ICON, null);
151: if (getString(bundle, id + "." + LABEL, null) == null
152: && iconPath == null) {
153: exists = false;
154: return;
155: }
156:
157: this .id = id;
158: String nameWithMnemonic = getString(bundle, id + "."
159: + LABEL, id);
160: int index = mnemonicIndex(nameWithMnemonic);
161: name = stripName(nameWithMnemonic, index);
162: mnemonic = stripMnemonic(nameWithMnemonic, index);
163: aMnemonicIndex = new Integer(index);
164:
165: shortDescription = getString(bundle, id + '.'
166: + SHORT_DESCRIPTION, defaultShortDescription(name));
167: longDescription = getString(bundle, id + '.'
168: + LONG_DESCRIPTION, name);
169:
170: URL iconURL = iconPath != null ? getClass()
171: .getClassLoader().getResource(iconPath) : null;
172: if (iconURL == null && iconPath != null) {
173: System.out
174: .println("WARNING Invalid icon "
175: + iconPath
176: + " specified in actions.properties for action '"
177: + name + "'");
178: icon = null;
179: } else {
180: icon = (iconPath == null) ? null : new ImageIcon(
181: iconURL);
182: }
183:
184: String grayIconPath = getString(bundle, id + '.'
185: + GRAY_ICON, null);
186: grayIcon = (grayIconPath == null) ? null : new ImageIcon(
187: getClass().getClassLoader().getResource(
188: grayIconPath));
189:
190: String shortcut = getString(bundle, id + '.' + ACCELERATOR
191: + '.' + OS_NAME_STRING, null);
192: if (shortcut == null) {
193: shortcut = getString(bundle, id + '.' + ACCELERATOR,
194: null);
195: }
196: accelerator = getKeyStroke(shortcut);
197:
198: command = getString(bundle, id + '.' + COMMAND, null);
199: }
200:
201: public boolean actionExists() {
202: return exists;
203: }
204:
205: /**
206: * Put the ActionReader's properties as values in the Action.
207: */
208: private void putValues(Action action) {
209: action.putValue(Action.NAME, name);
210: action.putValue(Action.SHORT_DESCRIPTION, shortDescription);
211: action.putValue(Action.LONG_DESCRIPTION, longDescription);
212: if (icon != null)
213: action.putValue(Action.SMALL_ICON, icon);
214: if (grayIcon != null)
215: action
216: .putValue(ActionManager.SMALL_GRAY_ICON,
217: grayIcon);
218: if (accelerator != null)
219: action.putValue(Action.ACCELERATOR_KEY, accelerator);
220: if (mnemonic != null)
221: action.putValue(Action.MNEMONIC_KEY, mnemonic);
222: if (command != null)
223: action.putValue(Action.ACTION_COMMAND_KEY, command);
224: action.putValue(ActionManager.DISPLAYED_MNEMONIC_INDEX,
225: aMnemonicIndex);
226: }
227:
228: private int mnemonicIndex(String nameWithMnemonic) {
229: return nameWithMnemonic.indexOf(MNEMONIC_MARKER);
230: }
231:
232: private String stripName(String nameWithMnemonic,
233: int mnemonicIndex) {
234: return mnemonicIndex == -1 ? nameWithMnemonic
235: : nameWithMnemonic.substring(0, mnemonicIndex)
236: + nameWithMnemonic
237: .substring(mnemonicIndex + 1);
238: }
239:
240: private Integer stripMnemonic(String nameWithMnemonic,
241: int mnemonicIndex) {
242: return mnemonicIndex == -1 ? null : new Integer(
243: nameWithMnemonic.charAt(mnemonicIndex + 1));
244: }
245:
246: private String defaultShortDescription(String nameWithDots) {
247: return nameWithDots.endsWith(DOT_STRING) ? (nameWithDots
248: .substring(0, nameWithDots.length()
249: - DOT_STRING.length())) : nameWithDots;
250: }
251:
252: private KeyStroke getKeyStroke(String accelerator) {
253: if (accelerator == null) {
254: return null;
255: } else {
256: KeyStroke keyStroke = KeyStroke
257: .getKeyStroke(accelerator);
258: if (keyStroke == null)
259: System.out.println("WARNING: Action " + id
260: + " has an invalid accelerator "
261: + accelerator);
262: return keyStroke;
263: }
264: }
265:
266: private String getString(ResourceBundle bundle, String key,
267: String defaultString) {
268: try {
269: return bundle.getString(key);
270: } catch (MissingResourceException e) {
271: return defaultString;
272: }
273: }
274: }
275: }
|