Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.input; 027 028import com.sun.javafx.tk.Toolkit; 029import java.util.ArrayList; 030import java.util.List; 031 032// PENDING_DOC_REVIEW 033/** 034 * Represents a combination of keys which are used in keyboard shortcuts. 035 * A key combination consists of a main key and a set of modifier keys. The main 036 * key can be specified by its key code - {@code KeyCodeCombination} or key 037 * character - {@code KeyCharacterCombination}. A modifier key is {@code shift}, 038 * {@code control}, {@code alt}, {@code meta} or {@code shortcut} and can be 039 * defined as {@code DOWN}, {@code UP} or {@code ANY}. 040 * <p> 041 * The {@code shortcut} modifier is used to represent the modifier key which is 042 * used commonly in keyboard shortcuts on the host platform. This is for 043 * example {@code control} on Windows and {@code meta} (command key) on Mac. 044 * By using {@code shortcut} key modifier developers can create platform 045 * independent shortcuts. So the "Shortcut+C" key combination is handled 046 * internally as "Ctrl+C" on Windows and "Meta+C" on Mac. 047 */ 048public abstract class KeyCombination { 049 050 /** Modifier which specifies that the {@code shift} key must be down. */ 051 public static final Modifier SHIFT_DOWN = 052 new Modifier(KeyCode.SHIFT, ModifierValue.DOWN); 053 /** 054 * Modifier which specifies that the {@code shift} key can be either up or 055 * down. 056 */ 057 public static final Modifier SHIFT_ANY = 058 new Modifier(KeyCode.SHIFT, ModifierValue.ANY); 059 /** Modifier which specifies that the {@code control} key must be down. */ 060 public static final Modifier CONTROL_DOWN = 061 new Modifier(KeyCode.CONTROL, ModifierValue.DOWN); 062 /** 063 * Modifier which specifies that the {@code control} key can be either up or 064 * down. 065 */ 066 public static final Modifier CONTROL_ANY = 067 new Modifier(KeyCode.CONTROL, ModifierValue.ANY); 068 /** Modifier which specifies that the {@code alt} key must be down. */ 069 public static final Modifier ALT_DOWN = 070 new Modifier(KeyCode.ALT, ModifierValue.DOWN); 071 /** 072 * Modifier which specifies that the {@code alt} key can be either up or 073 * down. 074 */ 075 public static final Modifier ALT_ANY = 076 new Modifier(KeyCode.ALT, ModifierValue.ANY); 077 /** Modifier which specifies that the {@code meta} key must be down. */ 078 public static final Modifier META_DOWN = 079 new Modifier(KeyCode.META, ModifierValue.DOWN); 080 /** 081 * Modifier which specifies that the {@code meta} key can be either up or 082 * down. 083 */ 084 public static final Modifier META_ANY = 085 new Modifier(KeyCode.META, ModifierValue.ANY); 086 /** Modifier which specifies that the {@code shortcut} key must be down. */ 087 public static final Modifier SHORTCUT_DOWN = 088 new Modifier(KeyCode.SHORTCUT, ModifierValue.DOWN); 089 /** 090 * Modifier which specifies that the {@code shortcut} key can be either up 091 * or down. 092 */ 093 public static final Modifier SHORTCUT_ANY = 094 new Modifier(KeyCode.SHORTCUT, ModifierValue.ANY); 095 096 private static final Modifier[] POSSIBLE_MODIFIERS = { 097 SHIFT_DOWN, SHIFT_ANY, 098 CONTROL_DOWN, CONTROL_ANY, 099 ALT_DOWN, ALT_ANY, 100 META_DOWN, META_ANY, 101 SHORTCUT_DOWN, SHORTCUT_ANY 102 }; 103 /** The state of the {@code shift} key in this key combination. */ 104 private final ModifierValue shift; 105 106 /** 107 * The state of the {@code shift} key in this key combination. 108 * @return The state of the {@code shift} key in this key combination 109 */ 110 public final ModifierValue getShift() { 111 return shift; 112 } 113 /** The state of the {@code control} key in this key combination. */ 114 private final ModifierValue control; 115 116 /** 117 * The state of the {@code control} key in this key combination. 118 * @return The state of the {@code control} key in this key combination 119 */ 120 public final ModifierValue getControl() { 121 return control; 122 } 123 /** The state of the {@code alt} key in this key combination. */ 124 private final ModifierValue alt; 125 126 /** 127 * The state of the {@code alt} key in this key combination. 128 * @return The state of the {@code alt} key in this key combination. 129 */ 130 public final ModifierValue getAlt() { 131 return alt; 132 } 133 /** The state of the {@code meta} key in this key combination. */ 134 private final ModifierValue meta; 135 136 /** 137 * The state of the {@code meta} key in this key combination. 138 * @return The state of the {@code meta} key in this key combination 139 */ 140 public final ModifierValue getMeta() { 141 return meta; 142 } 143 144 /** The state of the {@code shortcut} key in this key combination. */ 145 private final ModifierValue shortcut; 146 147 /** 148 * The state of the {@code shortcut} key in this key combination. 149 * @return The state of the {@code shortcut} key in this key combination 150 */ 151 public final ModifierValue getShortcut() { 152 return shortcut; 153 } 154 155 /** 156 * Constructs a {@code KeyCombination} with an explicit specification 157 * of all modifier keys. Each modifier key can be set to {@code DOWN}, 158 * {@code UP} or {@code ANY}. 159 * 160 * @param shift the value of the {@code shift} modifier key 161 * @param control the value of the {@code control} modifier key 162 * @param alt the value of the {@code alt} modifier key 163 * @param meta the value of the {@code meta} modifier key 164 * @param shortcut the value of the {@code shortcut} modifier key 165 */ 166 protected KeyCombination(final ModifierValue shift, 167 final ModifierValue control, 168 final ModifierValue alt, 169 final ModifierValue meta, 170 final ModifierValue shortcut) { 171 if ((shift == null) 172 || (control == null) 173 || (alt == null) 174 || (meta == null) 175 || (shortcut == null)) { 176 throw new NullPointerException("Modifier value must not be null!"); 177 } 178 179 this.shift = shift; 180 this.control = control; 181 this.alt = alt; 182 this.meta = meta; 183 this.shortcut = shortcut; 184 } 185 186 /** 187 * Constructs a {@code KeyCombination} with the specified list of modifiers. 188 * All modifier keys which are not explicitly listed are set to the 189 * default {@code UP} value. 190 * <p> 191 * All possible modifiers which change the default modifier value are 192 * defined as constants in the {@code KeyCombination} class. 193 * 194 * @param modifiers the list of modifier keys and their corresponding values 195 */ 196 protected KeyCombination(final Modifier... modifiers) { 197 this(getModifierValue(modifiers, KeyCode.SHIFT), 198 getModifierValue(modifiers, KeyCode.CONTROL), 199 getModifierValue(modifiers, KeyCode.ALT), 200 getModifierValue(modifiers, KeyCode.META), 201 getModifierValue(modifiers, KeyCode.SHORTCUT)); 202 } 203 204 /** 205 * Tests whether this key combination matches the combination in the given 206 * {@code KeyEvent}. 207 * <p> 208 * The implementation of this method in the {@code KeyCombination} class 209 * does only a partial test with the modifier keys. This method is 210 * overridden in subclasses to include the main key in the test. 211 * 212 * @param event the key event 213 * @return {@code true} if the key combinations match, {@code false} 214 * otherwise 215 */ 216 public boolean match(final KeyEvent event) { 217 final KeyCode shortcutKey = 218 Toolkit.getToolkit().getPlatformShortcutKey(); 219 return test(KeyCode.SHIFT, shift, shortcutKey, shortcut, 220 event.isShiftDown()) 221 && test(KeyCode.CONTROL, control, shortcutKey, shortcut, 222 event.isControlDown()) 223 && test(KeyCode.ALT, alt, shortcutKey, shortcut, 224 event.isAltDown()) 225 && test(KeyCode.META, meta, shortcutKey, shortcut, 226 event.isMetaDown()); 227 } 228 229 /** 230 * Returns a string representation of this {@code KeyCombination}. 231 * <p> 232 * The string representation consists of sections separated by plus 233 * characters. Each section specifies either a modifier key or the main key. 234 * <p> 235 * A modifier key section contains the {@code KeyCode} name of a modifier 236 * key. It can be prefixed with the {@code Ignored} keyword. A non-prefixed 237 * modifier key implies its {@code DOWN} value while the prefixed version 238 * implies the {@code ANY} (ignored) value. If some modifier key is not 239 * specified in the string at all, it means it has the default {@code UP} 240 * value. 241 * <p> 242 * The format of the main key section of the key combination string depends 243 * on the {@code KeyCombination} subclass. It is either the key code name 244 * for {@code KeyCodeCombination} or the single quoted key character for 245 * {@code KeyCharacterCombination}. 246 * <p> 247 * Examples of {@code KeyCombination} string representations: 248 249<PRE> 250"Ctrl+Alt+Q" 251"Ignore Shift+Ctrl+A" 252"Alt+'w'" 253</PRE> 254 255 * @return the string representation of this {@code KeyCombination} 256 */ 257 public String getName() { 258 StringBuilder sb = new StringBuilder(); 259 addModifiersIntoString(sb); 260 261 return sb.toString(); 262 } 263 264 /** 265 * Tests whether this {@code KeyCombination} equals to the specified object. 266 * 267 * @param obj the object to compare to 268 * @return {@code true} if the objects are equal, {@code false} otherwise 269 */ 270 @Override 271 public boolean equals(final Object obj) { 272 if (!(obj instanceof KeyCombination)) { 273 return false; 274 } 275 276 final KeyCombination other = (KeyCombination) obj; 277 return (shift == other.shift) 278 && (control == other.control) 279 && (alt == other.alt) 280 && (meta == other.meta) 281 && (shortcut == other.shortcut); 282 } 283 284 /** 285 * Returns a hash code value for this {@code KeyCombination}. 286 * 287 * @return the hash code value 288 */ 289 @Override 290 public int hashCode() { 291 int hash = 7; 292 293 hash = 23 * hash + shift.hashCode(); 294 hash = 23 * hash + control.hashCode(); 295 hash = 23 * hash + alt.hashCode(); 296 hash = 23 * hash + meta.hashCode(); 297 hash = 23 * hash + shortcut.hashCode(); 298 299 return hash; 300 } 301 302 /** 303 * Returns a string representation of this object. Implementation returns 304 * the result of the {@code getName()} call. 305 * 306 * @return the string representation of this {@code KeyCombination} 307 */ 308 @Override 309 public String toString() { 310 return getName(); 311 } 312 313 /** 314 * Constructs a new {@code KeyCombination} from the specified string. The 315 * string should be in the same format as produced by the {@code getName} 316 * method. 317 * <p> 318 * If the main key section string is quoted in single quotes the method 319 * creates a new {@code KeyCharacterCombination} for the unquoted substring. 320 * Otherwise it finds the key code which name corresponds to the main key 321 * section string and creates a {@code KeyCodeCombination} for it. If this 322 * can't be done, it falls back to the {@code KeyCharacterCombination}. 323 * 324 * @param value the string which represents the requested key combination 325 * @return the constructed {@code KeyCombination} 326 */ 327 public static KeyCombination valueOf(String value) { 328 final List<Modifier> modifiers = new ArrayList<Modifier>(4); 329 330 final String[] tokens = splitName(value); 331 332 KeyCode keyCode = null; 333 String keyCharacter = null; 334 for (String token : tokens) { 335 336 if ((token.length() > 2) 337 && (token.charAt(0) == '\'') 338 && (token.charAt(token.length() - 1) == '\'')) { 339 if ((keyCode != null) || (keyCharacter != null)) { 340 throw new IllegalArgumentException( 341 "Cannot parse key binding " + value); 342 } 343 344 keyCharacter = token.substring(1, token.length() - 1) 345 .replace("\\'", "'"); 346 continue; 347 } 348 349 final String normalizedToken = normalizeToken(token); 350 351 final Modifier modifier = getModifier(normalizedToken); 352 if (modifier != null) { 353 modifiers.add(modifier); 354 continue; 355 } 356 357 if ((keyCode != null) || (keyCharacter != null)) { 358 throw new IllegalArgumentException( 359 "Cannot parse key binding " + value); 360 } 361 362 keyCode = KeyCode.getKeyCode(normalizedToken); 363 if (keyCode == null) { 364 keyCharacter = token; 365 } 366 } 367 368 if ((keyCode == null) && (keyCharacter == null)) { 369 throw new IllegalArgumentException( 370 "Cannot parse key binding " + value); 371 } 372 373 final Modifier[] modifierArray = 374 modifiers.toArray(new Modifier[modifiers.size()]); 375 return (keyCode != null) 376 ? new KeyCodeCombination(keyCode, modifierArray) 377 : new KeyCharacterCombination(keyCharacter, modifierArray); 378 } 379 380 /** 381 * Constructs a new {@code KeyCombination} from the specified string. This 382 * method simply delegates to {@link #valueOf(String)}. 383 * 384 * @param name the string which represents the requested key combination 385 * @return the constructed {@code KeyCombination} 386 * 387 * @see #valueOf(String) 388 */ 389 public static KeyCombination keyCombination(String name) { 390 return valueOf(name); 391 } 392 393 /** 394 * This class represents a pair of modifier key and its value. 395 */ 396 public static final class Modifier { 397 private final KeyCode key; 398 private final ModifierValue value; 399 400 private Modifier(final KeyCode key, 401 final ModifierValue value) { 402 this.key = key; 403 this.value = value; 404 } 405 406 /** 407 * Gets the modifier key of this {@code Modifier}. 408 * 409 * @return the modifier key 410 */ 411 public KeyCode getKey() { 412 return key; 413 } 414 415 /** 416 * Gets the modifier value of this {@code Modifier}. 417 * 418 * @return the modifier value 419 */ 420 public ModifierValue getValue() { 421 return value; 422 } 423 424 /** 425 * Returns a string representation of the modifier. 426 * @return a string representation of the modifier 427 */ 428 @Override 429 public String toString() { 430 return ((value == ModifierValue.ANY) ? "Ignore " : "") 431 + key.getName(); 432 433 } 434 } 435 436 /** 437 * {@code ModifierValue} specifies state of modifier keys. 438 */ 439 public static enum ModifierValue { 440 /** Constant which indicates that the modifier key must be down. */ 441 DOWN, 442 /** Constant which indicates that the modifier key must be up. */ 443 UP, 444 /** 445 * Constant which indicates that the modifier key can be either up or 446 * down. 447 */ 448 ANY 449 } 450 451 private void addModifiersIntoString(final StringBuilder sb) { 452 addModifierIntoString(sb, KeyCode.SHIFT, shift); 453 addModifierIntoString(sb, KeyCode.CONTROL, control); 454 addModifierIntoString(sb, KeyCode.ALT, alt); 455 addModifierIntoString(sb, KeyCode.META, meta); 456 addModifierIntoString(sb, KeyCode.SHORTCUT, shortcut); 457 } 458 459 private static void addModifierIntoString( 460 final StringBuilder sb, 461 final KeyCode modifierKey, 462 final ModifierValue modifierValue) { 463 464 if (modifierValue == ModifierValue.UP) { 465 return; 466 } 467 468 if (sb.length() > 0) { 469 sb.append("+"); 470 } 471 472 if (modifierValue == ModifierValue.ANY) { 473 sb.append("Ignore "); 474 } 475 476 sb.append(modifierKey.getName()); 477 } 478 479 private static boolean test(final KeyCode testedModifierKey, 480 final ModifierValue testedModifierValue, 481 final KeyCode shortcutModifierKey, 482 final ModifierValue shortcutModifierValue, 483 final boolean isKeyDown) { 484 final ModifierValue finalModifierValue = 485 (testedModifierKey == shortcutModifierKey) 486 ? resolveModifierValue(testedModifierValue, 487 shortcutModifierValue) 488 : testedModifierValue; 489 490 return test(finalModifierValue, isKeyDown); 491 492 } 493 494 private static boolean test(final ModifierValue modifierValue, 495 final boolean isDown) { 496 switch (modifierValue) { 497 case DOWN: 498 return isDown; 499 500 case UP: 501 return !isDown; 502 503 case ANY: 504 default: 505 return true; 506 } 507 } 508 509 private static ModifierValue resolveModifierValue( 510 final ModifierValue firstValue, 511 final ModifierValue secondValue) { 512 if ((firstValue == ModifierValue.DOWN) 513 || (secondValue == ModifierValue.DOWN)) { 514 return ModifierValue.DOWN; 515 } 516 517 if ((firstValue == ModifierValue.ANY) 518 || (secondValue == ModifierValue.ANY)) { 519 return ModifierValue.ANY; 520 } 521 522 return ModifierValue.UP; 523 } 524 525 static Modifier getModifier(final String name) { 526 for (final Modifier modifier: POSSIBLE_MODIFIERS) { 527 if (modifier.toString().equals(name)) { 528 return modifier; 529 } 530 } 531 532 return null; 533 } 534 535 private static ModifierValue getModifierValue( 536 final Modifier[] modifiers, 537 final KeyCode modifierKey) { 538 ModifierValue modifierValue = ModifierValue.UP; 539 for (final Modifier modifier: modifiers) { 540 if (modifier == null) { 541 throw new NullPointerException("Modifier must not be null!"); 542 } 543 544 if (modifier.getKey() == modifierKey) { 545 if (modifierValue != ModifierValue.UP) { 546 throw new IllegalArgumentException( 547 (modifier.getValue() != modifierValue) 548 ? "Conflicting modifiers specified!" 549 : "Duplicate modifiers specified!"); 550 } 551 552 modifierValue = modifier.getValue(); 553 } 554 } 555 556 return modifierValue; 557 } 558 559 private static String normalizeToken(final String token) { 560 final String[] words = token.split("\\s+"); 561 final StringBuilder sb = new StringBuilder(); 562 563 for (final String word: words) { 564 if (sb.length() > 0) { 565 sb.append(' '); 566 } 567 568 sb.append(word.substring(0, 1).toUpperCase()); 569 sb.append(word.substring(1).toLowerCase()); 570 } 571 572 return sb.toString(); 573 } 574 575 private static String[] splitName(String name) { 576 List<String> tokens = new ArrayList<String>(); 577 char[] chars = name.trim().toCharArray(); 578 579 final int STATE_BASIC = 0; // general text 580 final int STATE_WHITESPACE = 1; // spaces found 581 final int STATE_SEPARATOR = 2; // plus found 582 final int STATE_QUOTED = 3; // quoted text 583 584 int state = STATE_BASIC; 585 int tokenStart = 0; 586 int tokenEnd = -1; 587 588 for (int i = 0; i < chars.length; i++) { 589 char c = chars[i]; 590 switch(state) { 591 case STATE_BASIC: 592 switch(c) { 593 case ' ': 594 case '\t': 595 case '\n': 596 case '\f': 597 case '\r': 598 case '\u000B': 599 tokenEnd = i; 600 state = STATE_WHITESPACE; 601 break; 602 case '+': 603 tokenEnd = i; 604 state = STATE_SEPARATOR; 605 break; 606 case '\'': 607 if (i == 0 || chars[i - 1] != '\\') { 608 state = STATE_QUOTED; 609 } 610 break; 611 default: 612 break; 613 } 614 break; 615 case STATE_WHITESPACE: 616 switch(c) { 617 case ' ': 618 case '\t': 619 case '\n': 620 case '\f': 621 case '\r': 622 case '\u000B': 623 break; 624 case '+': 625 state = STATE_SEPARATOR; 626 break; 627 case '\'': 628 state = STATE_QUOTED; 629 tokenEnd = -1; 630 break; 631 default: 632 state = STATE_BASIC; 633 tokenEnd = -1; 634 break; 635 } 636 break; 637 case STATE_SEPARATOR: 638 switch(c) { 639 case ' ': 640 case '\t': 641 case '\n': 642 case '\f': 643 case '\r': 644 case '\u000B': 645 break; 646 case '+': 647 throw new IllegalArgumentException( 648 "Cannot parse key binding " + name); 649 default: 650 if (tokenEnd <= tokenStart) { 651 throw new IllegalArgumentException( 652 "Cannot parse key binding " + name); 653 } 654 tokens.add(new String(chars, 655 tokenStart, tokenEnd - tokenStart)); 656 tokenStart = i; 657 tokenEnd = -1; 658 state = (c == '\'' ? STATE_QUOTED : STATE_BASIC); 659 break; 660 } 661 break; 662 case STATE_QUOTED: 663 if (c == '\'' && chars[i - 1] != '\\') { 664 state = STATE_BASIC; 665 } 666 break; 667 } 668 } 669 670 switch(state) { 671 case STATE_BASIC: 672 case STATE_WHITESPACE: 673 tokens.add(new String(chars, 674 tokenStart, chars.length - tokenStart)); 675 break; 676 case STATE_SEPARATOR: 677 case STATE_QUOTED: 678 throw new IllegalArgumentException( 679 "Cannot parse key binding " + name); 680 } 681 682 return tokens.toArray(new String[tokens.size()]); 683 } 684}