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 ::= (<modifier>)* keySpec
356 * keySpec ::= <character> | <specialKey> | <keyCode>
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 }