View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.builder.action;
17  
18  import java.util.Collections;
19  import java.util.EnumSet;
20  import java.util.Set;
21  
22  import net.sf.jguiraffe.gui.builder.event.Keys;
23  import net.sf.jguiraffe.gui.builder.event.Modifiers;
24  
25  import org.apache.commons.lang.ObjectUtils;
26  
27  /**
28   * <p>
29   * A class that represents an <em>accelerator</em> for invoking an action.
30   * </p>
31   * <p>
32   * An accelerator is a key (or a combination of keys, typically associated with
33   * some modifier keys like SHIFT or ALT) that triggers an action when pressed.
34   * Thus it is a keyboard short cut that has the same effect as clicking a tool
35   * bar button or selecting a menu element. When a {@link FormAction} is created
36   * (defined by an {@link ActionData} object) an accelerator can be specified.
37   * This can have effect on GUI elements associated with this action. For
38   * instance, menus will typically display the keyboard combinations that
39   * correspond to the menu elements.
40   * </p>
41   * <p>
42   * This class has the same purpose as <code>javax.swing.KeyStroke</code>:
43   * serving as an abstract description of a combination of key presses. However,
44   * it is more tailored towards the builder approach followed by this library.
45   * This means that the main use case of this class is being created indirectly
46   * in a builder script (mostly using a text representation) and then being
47   * passed to an implementation of <code>ActionManager</code>. A concrete
48   * <code>ActionManager</code> implementation is responsible for converting a
49   * generic <code>Accelerator</code> object into a platform-specific
50   * representation of a key stroke.
51   * </p>
52   * <p>
53   * Instances of this class store a set of modifiers (like SHIFT or CONTROL) that
54   * must be pressed together with the key. The actual key can be specified in the
55   * following different ways:
56   * <ul>
57   * <li>If it is a "normal" (i.e. printable) character, it can be queried using
58   * the <code>getKey()</code> method, which returns a <code>Character</code>.</li>
59   * <li>For special keys like the function keys, Escape, or Enter enumeration
60   * literals are defined. If such a key is used for the accelerator, the
61   * <code>getSpecialKey()</code> method will return a non-<b>null</b> value.</li>
62   * <li>It is also possible to use a key code specific to a concrete GUI library,
63   * e.g. a virtual key code used within Swing (the <code>VK_XXX</code> constants
64   * of the <code>KeyEvent</code> class). If such a code is set, it can be queried
65   * using the <code>getKeyCode()</code> method. Note that this variant is not
66   * portable.</li>
67   * </ul>
68   * Exactly one of the methods listed above will return a non-<b>null</b> value.
69   * </p>
70   * <p>
71   * Implementation note: Instances of this class are immutable and can be shared
72   * among multiple threads.
73   * </p>
74   *
75   * @author Oliver Heger
76   * @version $Id: Accelerator.java 205 2012-01-29 18:29:57Z oheger $
77   */
78  public final class Accelerator
79  {
80      /** Constant for the regex for splitting a string to be parsed. */
81      private static final String REGEX_SPLIT = "\\s";
82  
83      /** Constant for the separator used by the toString() method. */
84      private static final char SEPARATOR = ' ';
85  
86      /** Constant for the padding character for single digit key codes. */
87      private static final char PAD = '0';
88  
89      /** Constant for determining single digit key codes. */
90      private static final int MULTI_DIGITS = 10;
91  
92      /** Constant for the seed value of the hash code computation. */
93      private static final int HASH_SEED = 17;
94  
95      /** Constant for the factory for the hash code computation. */
96      private static final int HASH_FACTOR = 37;
97  
98      /** Stores the modifiers associated with this accelerator. */
99      private final Set<Modifiers> modifiers;
100 
101     /** Stores the special key for this accelerator. */
102     private final Keys specialKey;
103 
104     /** Stores the character for this accelerator. */
105     private final Character key;
106 
107     /** Stores a platform-specific key code. */
108     private final Integer keyCode;
109 
110     /**
111      * Creates a new instance of <code>Accelerator</code> and initializes it.
112      * Clients obtain instances through the static factory methods.
113      *
114      * @param mods the modifiers
115      * @param specKey the special key
116      * @param c the character
117      * @param code the key code
118      */
119     private Accelerator(Set<Modifiers> mods, Keys specKey, Character c,
120             Integer code)
121     {
122         specialKey = specKey;
123         key = c;
124         keyCode = code;
125         if (mods == null)
126         {
127             modifiers = Collections.emptySet();
128         }
129         else
130         {
131             modifiers = Collections.unmodifiableSet(EnumSet.copyOf(mods));
132         }
133     }
134 
135     /**
136      * Returns a set with the modifiers set for this accelerator. These are
137      * special mode keys (like SHIFT or CONTROL) that must be pressed together
138      * with the actual key for triggering this accelerator.
139      *
140      * @return a set with the modifiers (this set cannot be modified)
141      */
142     public Set<Modifiers> getModifiers()
143     {
144         return modifiers;
145     }
146 
147     /**
148      * Returns the special key. If this accelerator is represented by a special
149      * key (for which a constant is available), this key is returned by this
150      * method. Otherwise <b>null</b> is returned.
151      *
152      * @return the special key representing this accelerator or <b>null</b>
153      */
154     public Keys getSpecialKey()
155     {
156         return specialKey;
157     }
158 
159     /**
160      * Returns the character. If the key of this accelerator is a printable
161      * character, it is returned by this method. Otherwise result is
162      * <b>null</b>.
163      *
164      * @return the character of this accelerator or <b>null</b>
165      */
166     public Character getKey()
167     {
168         return key;
169     }
170 
171     /**
172      * Returns the key code. If this accelerator is represented by a
173      * (platform-specific) key code, this code is returned here. Otherwise
174      * result is <b>null</b>.
175      *
176      * @return the key code representing this accelerator or <b>null</b>
177      */
178     public Integer getKeyCode()
179     {
180         return keyCode;
181     }
182 
183     /**
184      * Returns a string representation of this object. Strings returned by this
185      * method are compatible with the <code>{@link #parse(String)}</code>
186      * method, i.e. they can be used for creating <code>Accelerator</code>
187      * instances. They are normalized in the following way:
188      * <ul>
189      * <li>Modifiers are listed in their natural order (this is the order in
190      * which the <code>enum</code> constants are declared and happens to be
191      * alphabetic order).</li>
192      * <li>For each component of the string a single space is used as separator.
193      * </li>
194      * <li>All constants are written in capital letters.</li>
195      * </ul>
196      *
197      * @return a string for this object
198      */
199     @Override
200     public String toString()
201     {
202         StringBuilder buf = new StringBuilder();
203 
204         for (Modifiers m : getModifiers())
205         {
206             append(buf, m);
207         }
208 
209         if (getKey() != null)
210         {
211             append(buf, getKey());
212         }
213         else if (getSpecialKey() != null)
214         {
215             append(buf, getSpecialKey().name());
216         }
217         else
218         {
219             if (getKeyCode() < MULTI_DIGITS)
220             {
221                 append(buf, PAD).append(getKeyCode());
222             }
223             else
224             {
225                 append(buf, getKeyCode());
226             }
227         }
228 
229         return buf.toString();
230     }
231 
232     /**
233      * Compares this object with another one. Two objects are equal if and only
234      * if they use the same way of describing the key and have the same
235      * modifiers.
236      *
237      * @param obj the object to compare
238      * @return a flag whether these objects are equal
239      */
240     @Override
241     public boolean equals(Object obj)
242     {
243         if (obj == this)
244         {
245             return true;
246         }
247         if (!(obj instanceof Accelerator))
248         {
249             return false;
250         }
251 
252         Accelerator c = (Accelerator) obj;
253         return ObjectUtils.equals(getKey(), c.getKey())
254                 && ObjectUtils.equals(getSpecialKey(), c.getSpecialKey())
255                 && ObjectUtils.equals(getKeyCode(), c.getKeyCode())
256                 && getModifiers().equals(c.getModifiers());
257     }
258 
259     /**
260      * Returns a hash code for this object.
261      *
262      * @return a hash code
263      */
264     @Override
265     public int hashCode()
266     {
267         int result = HASH_SEED;
268 
269         if (getKey() != null)
270         {
271             result = HASH_FACTOR * result + getKey().hashCode();
272         }
273         else if (getKeyCode() != null)
274         {
275             result = HASH_FACTOR * result + getKeyCode().hashCode();
276         }
277         else
278         {
279             result = HASH_FACTOR * result + getSpecialKey().hashCode();
280         }
281         result = HASH_FACTOR * result + getModifiers().hashCode();
282 
283         return result;
284     }
285 
286     /**
287      * Returns an <code>Accelerator</code> for the specified special key and the
288      * (optional) modifiers.
289      *
290      * @param key the special key for this accelerator (must not be <b>null</b>)
291      * @param modifiers a set with modifiers (can be <b>null</b>)
292      * @return the <code>Accelerator</code> instance
293      * @throws IllegalArgumentException if the key is undefined
294      */
295     public static Accelerator getInstance(Keys key, Set<Modifiers> modifiers)
296     {
297         if (key == null)
298         {
299             throw new IllegalArgumentException("Key must not be null");
300         }
301         return new Accelerator(modifiers, key, null, null);
302     }
303 
304     /**
305      * Returns an <code>Accelerator</code> for the specified printable key and
306      * the (optional) modifiers.
307      *
308      * @param key the character for this accelerator (must not be <b>null</b>)
309      * @param modifiers a set with modifiers (can be <b>null</b>)
310      * @return the <code>Accelerator</code> instance
311      * @throws IllegalArgumentException if the key is undefined
312      */
313     public static Accelerator getInstance(Character key,
314             Set<Modifiers> modifiers)
315     {
316         if (key == null)
317         {
318             throw new IllegalArgumentException("Key must not be null!");
319         }
320         return new Accelerator(modifiers, null, key, null);
321     }
322 
323     /**
324      * Returns an <code>Accelerator</code> for the specified platform-specific
325      * key code and the (optional) modifiers.
326      *
327      * @param keyCode the special key code for this accelerator (must not be
328      *        <b>null</b>)
329      * @param modifiers a set with modifiers (can be <b>null</b>)
330      * @return the <code>Accelerator</code> instance
331      * @throws IllegalArgumentException if the key is undefined
332      */
333     public static Accelerator getInstance(Integer keyCode,
334             Set<Modifiers> modifiers)
335     {
336         if (keyCode == null)
337         {
338             throw new IllegalArgumentException("Key code must not be null!");
339         }
340         return new Accelerator(modifiers, null, null, keyCode);
341     }
342 
343     /**
344      * <p>
345      * Returns an <code>Accelerator</code> instance from the specified string
346      * representation. The string must be a valid representation of an
347      * <code>Accelerator</code> instance as it is returned by the
348      * <code>toString()</code> method. Otherwise an
349      * <code>IllegalArgumentException</code> exception will be thrown.
350      * </p>
351      * <p>
352      * Valid strings have the following form:
353      *
354      * <pre>
355      * acceleratorString ::= (&lt;modifier&gt;)* keySpec
356      * keySpec           ::= &lt;character&gt; | &lt;specialKey&gt; | &lt;keyCode&gt;
357      * </pre>
358      *
359      * With other words: The string can contain multiple components all
360      * separated by whitespace. The last component defines the actual keys, all
361      * others are interpreted as modifiers. They must conform to literals of the
362      * <code>Modifiers</code> enumeration (case does not matter). For the last
363      * component, there are the following possibilities:
364      * <ul>
365      * <li>If it is a string of length 1, it is interpreted as a printable
366      * character.</li>
367      * <li>If it is a literal defined in the <code>Keys</code> enumeration, the
368      * corresponding special key is set (the comparison is not case sensitive).</li>
369      * <li>If it is a number, it is parsed to an integer and interpreted as key
370      * code. Note that there is an ambiguity for key codes consisting of a
371      * single digit (0-9). Because they are represented by a string with the
372      * length 1 they are interpreted as characters (see above). To avoid this,
373      * add a leading 0, e.g. "05".</li>
374      * </ul>
375      * </p>
376      * <p>
377      * Here are some examples:
378      * <dl>
379      * <dt><code>"A"</code></dt>
380      * <dd>This is simply the letter A without any modifiers.</dd>
381      * <dt><code>control A</code></dt>
382      * <dd>The letter A with the CONTROL modifier.</dd>
383      * <dt><code>Shift CONTROL A</code></dt>
384      * <dd>The letter A with both the SHIFT and the CONTROL modifier. Note that
385      * case of the modifiers does not matter. The same is true for the order in
386      * which they are given.</dd>
387      * <dt><code>Alt Backspace</code></dt>
388      * <dd>The backspace key plus the ALT modifier (as is often used as undo
389      * command). For special keys case does not matter either.</dd>
390      * <dt><code>F1</code></dt>
391      * <dd>The F1 function key, as is often used for the help command.</dd>
392      * <dt><code>42</code></dt>
393      * <dd>A special key code with the numeric value of 42.</dd>
394      * <dt><code>CONTROL 5</code></dt>
395      * <dd>The character '5' plus the CONTROL modifier.</dd>
396      * <dt><code>CONTROL 05</code></dt>
397      * <dd>The numeric key code 5 (whatever this means) plus the CONTROL
398      * modifier. Note that here a leading 0 must be used, otherwise the number
399      * will be interpreted as character.</dd>
400      * </dl>
401      * </p>
402      * <p>
403      * If the whole string is <b>null</b> or empty, <b>null</b> is returned.
404      * </p>
405      *
406      * @param s the string to be parsed (can be <b>null</b>)
407      * @return the corresponding <code>Accelerator</code> instance
408      * @throws IllegalArgumentException if the string cannot be parsed
409      */
410     public static Accelerator parse(String s)
411     {
412         if (s == null || s.length() == 0)
413         {
414             return null;
415         }
416 
417         String[] comps = s.split(REGEX_SPLIT);
418         if (comps.length == 0)
419         {
420             return null;
421         }
422 
423         Set<Modifiers> mods = EnumSet.noneOf(Modifiers.class);
424         for (int i = 0; i < comps.length - 1; i++)
425         {
426             if (comps[i].length() > 0)
427             {
428                 mods.add(Modifiers.fromString(comps[i]));
429             }
430         }
431 
432         return parseKeySpec(mods, comps[comps.length - 1]);
433     }
434 
435     /**
436      * Helper method for parsing the key specification after the modifiers have
437      * been parsed successfully.
438      *
439      * @param mods the modifiers
440      * @param keySpec the string with the key specification
441      * @return the corresponding accelerator instance
442      * @throws IllegalArgumentException if the key cannot be parsed
443      */
444     private static Accelerator parseKeySpec(Set<Modifiers> mods, String keySpec)
445     {
446         if (keySpec.length() == 1)
447         {
448             // a character
449             return getInstance(Character.valueOf(keySpec.charAt(0)), mods);
450         }
451 
452         // test for a special key
453         String keySpecCase = keySpec.toUpperCase();
454         for (Keys k : Keys.values())
455         {
456             if (k.name().equals(keySpecCase))
457             {
458                 return getInstance(k, mods);
459             }
460         }
461 
462         // Can it be converted to an integer? Then it is a key code.
463         try
464         {
465             return getInstance(Integer.parseInt(keySpec), mods);
466         }
467         catch (NumberFormatException nfex)
468         {
469             throw new IllegalArgumentException("Invalid key specificaton: "
470                     + keySpec);
471         }
472     }
473 
474     /**
475      * Helper method for appending data to a string builder. If required, this
476      * method adds a separator before the new data is appended.
477      *
478      * @param buf the buffer
479      * @param data the data to be added
480      * @return a reference to the same buffer
481      */
482     private static StringBuilder append(StringBuilder buf, Object data)
483     {
484         if (buf.length() > 0)
485         {
486             buf.append(SEPARATOR);
487         }
488         return buf.append(data);
489     }
490 }