| CWAction.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * Copyright: 2005
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: CWAction.java 2110 2011-03-08 13:55:32Z dmsmith $
21 */
22 package org.crosswire.common.swing;
23
24 import java.awt.Toolkit;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.awt.event.InputEvent;
28
29 import javax.swing.AbstractAction;
30 import javax.swing.Action;
31 import javax.swing.Icon;
32 import javax.swing.KeyStroke;
33 import javax.swing.event.EventListenerList;
34
35 import org.crosswire.common.util.Logger;
36 import org.crosswire.common.util.StringUtil;
37
38 /**
39 * A CrossWire Action is a generic extension of AbstractAction, that adds
40 * LARGE_ICON to Action and also forwards the Action to its listeners after
41 * modifying the ActionEvent to include the ACTION_COMMAND_KEY.
42 *
43 * @see gnu.lgpl.License for license details.<br>
44 * The copyright to this program is held by it's authors.
45 * @author DM Smith [dmsmith555 at yahoo dot com]
46 */
47 public class CWAction extends AbstractAction {
48 /**
49 * The icon to display when a large one is needed. This is still not part of
50 * Java as of 1.5. Now it is with Java 1.6!
51 */
52 public static final String LARGE_ICON = "LargeIcon";
53
54 /**
55 * The tooltip to display. This is an alias for SHORT_DESCRIPTION. The
56 * creator and user of a CWAction is to store and retrieve
57 * SHORT_DESCRIPTION.
58 */
59 public static final String TOOL_TIP = "ToolTip";
60
61 /**
62 * Set or clear, using null, the icon on this action.
63 * @param icon the small icon to set
64 * @return this action
65 */
66 public CWAction setLargeIcon(Icon icon) {
67 putValue(LARGE_ICON, icon);
68 return this;
69 }
70
71 public CWAction setLargeIcon(String iconPath) {
72 return setLargeIcon(GuiUtil.getIcon(iconPath));
73 }
74
75 public CWAction setTooltip(String tooltip) {
76 putValue(Action.SHORT_DESCRIPTION, tooltip);
77 return this;
78 }
79
80 /**
81 * Set or clear, using null, the icon on this action.
82 * @param icon the small icon to set
83 * @return this action
84 */
85 public CWAction setSmallIcon(Icon icon) {
86 putValue(SMALL_ICON, icon);
87 return this;
88 }
89
90 public CWAction setSmallIcon(String iconPath) {
91 return setSmallIcon(GuiUtil.getIcon(iconPath));
92 }
93
94 /**
95 * Set the accelerator key from spec. If the spec is invalid it is logged and ignored.
96 * @param acceleratorSpec
97 * @return this action
98 */
99 public CWAction setAccelerator(String acceleratorSpec) {
100 putValue(Action.ACCELERATOR_KEY, getAccelerator(acceleratorSpec));
101 return this;
102 }
103
104 /**
105 * Set enabled either true or false on this action.
106 *
107 * @param newEnabled the desired state
108 * @return this action
109 */
110 public CWAction enable(boolean newEnabled) {
111 setEnabled(newEnabled);
112 return this;
113 }
114
115 /**
116 * Create a clone of this action and attache the listener. If
117 * no listener is supplied, the action is not cloned.
118 *
119 * @param listener the listener for the action
120 * @return a cloned action with the listener attached or the current action
121 */
122 public CWAction setListener(ActionListener listener) {
123 CWAction action = this;
124 if (listener != null) {
125 action = action.clone();
126 action.addActionListener(listener);
127 }
128 return action;
129 }
130
131 /**
132 * Forwards the ActionEvent to the registered listener.
133 *
134 * @param evt
135 * ActionEvent
136 */
137 public void actionPerformed(ActionEvent evt) {
138 if (listeners != null) {
139 Object[] listenerList = listeners.getListenerList();
140
141 // Recreate the ActionEvent and stuff the value of the
142 // ACTION_COMMAND_KEY
143 ActionEvent e = new ActionEvent(evt.getSource(), evt.getID(), (String) getValue(Action.ACTION_COMMAND_KEY));
144 for (int i = 0; i <= listenerList.length - 2; i += 2) {
145 ((ActionListener) listenerList[i + 1]).actionPerformed(e);
146 }
147 }
148 }
149
150 /**
151 * Adds a listener for Action events.
152 *
153 * @param listener
154 * <code>ActionListener</code> to add
155 */
156 public void addActionListener(ActionListener listener) {
157 if (listeners == null) {
158 listeners = new EventListenerList();
159 }
160 listeners.add(ActionListener.class, listener);
161 }
162
163 /**
164 * Remove an ActionListener
165 *
166 * @param listener
167 * <code>ActionListener</code> to remove
168 */
169 public void removeActionListener(ActionListener listener) {
170 if (listeners == null) {
171 return;
172 }
173 listeners.remove(ActionListener.class, listener);
174 }
175
176 /**
177 * String representation of this object suitable for debugging
178 *
179 */
180 @Override
181 public String toString() {
182 StringBuilder sb = new StringBuilder();
183
184 sb.append("Name:");
185 sb.append((String) getValue(Action.NAME));
186 sb.append("\n Desc:");
187 sb.append((String) getValue(Action.SHORT_DESCRIPTION));
188 sb.append("\n ActionCommandKey:");
189 sb.append((String) getValue(Action.ACTION_COMMAND_KEY));
190 sb.append("\n Enabled:");
191 sb.append(isEnabled());
192 sb.append("\n ObjectID:");
193 sb.append(System.identityHashCode(this));
194 sb.append('\n');
195
196 return sb.toString();
197 }
198
199 /**
200 * Create a clone that does not copy the listeners. These CWActions need to
201 * have listeners added to be meaningful.
202 *
203 * @see javax.swing.AbstractAction#clone()
204 */
205 @Override
206 public CWAction clone() {
207 CWAction action = null;
208 try {
209 action = (CWAction) super.clone();
210 action.listeners = null;
211 } catch (CloneNotSupportedException e) {
212 assert false : e;
213 }
214 return action;
215 }
216
217 /**
218 * Convert the string to a valid Accelerator (that is a KeyStroke)
219 */
220 private KeyStroke getAccelerator(String acceleratorSpec) {
221 KeyStroke accelerator = null;
222 if (acceleratorSpec != null && acceleratorSpec.length() > 0) {
223 try {
224 accelerator = getKeyStroke(acceleratorSpec);
225 } catch (NumberFormatException nfe) {
226 log.warn("Could not parse integer for accelerator of action", nfe);
227 }
228 }
229 return accelerator;
230 }
231
232 /**
233 *
234 */
235 private KeyStroke getKeyStroke(String acceleratorSpec) throws NumberFormatException {
236 int keyModifier = 0;
237 int key = 0;
238 String[] parts = StringUtil.split(acceleratorSpec, ',');
239 for (int j = 0; j < parts.length; j++) {
240 String part = parts[j].trim();
241 if ("ctrl".equalsIgnoreCase(part)) {
242 // use this so MacOS users are happy
243 // It will map to the CMD key on Mac; CTRL otherwise.
244 keyModifier |= Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
245 } else if ("shift".equalsIgnoreCase(part)) {
246 keyModifier |= InputEvent.SHIFT_MASK;
247 } else if ("alt".equalsIgnoreCase(part)) {
248 keyModifier |= InputEvent.ALT_MASK;
249 } else if (part.startsWith("0x")) {
250 key = Integer.parseInt(part.substring(2), 16);
251 } else if (part.length() == 1) {
252 key = part.charAt(0);
253 }
254 }
255 return KeyStroke.getKeyStroke(key, keyModifier);
256 }
257
258 private EventListenerList listeners;
259
260 /**
261 * The log stream
262 */
263 private static final Logger log = Logger.getLogger(CWAction.class);
264
265 /**
266 * Serialization ID
267 */
268 private static final long serialVersionUID = 3258416148742484276L;
269 }
270