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