1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.javafx.scene.control.inputmap; 26 27 import javafx.beans.property.BooleanProperty; 28 import javafx.beans.property.ObjectProperty; 29 import javafx.beans.property.ReadOnlyObjectProperty; 30 import javafx.beans.property.ReadOnlyObjectWrapper; 31 import javafx.beans.property.SimpleBooleanProperty; 32 import javafx.beans.property.SimpleObjectProperty; 33 import javafx.collections.FXCollections; 34 import javafx.collections.ListChangeListener; 35 import javafx.collections.ObservableList; 36 import javafx.event.Event; 37 import javafx.event.EventHandler; 38 import javafx.event.EventType; 39 import javafx.scene.Node; 40 import javafx.scene.input.KeyCode; 41 import javafx.scene.input.KeyEvent; 42 import javafx.scene.input.MouseEvent; 43 import javafx.util.Pair; 44 45 import java.util.ArrayList; 46 import java.util.Collections; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Optional; 52 import java.util.function.Predicate; 53 import java.util.stream.Collectors; 54 55 /** 56 * InputMap is a class that is set on a given {@link Node}. When the Node receives 57 * an input event from the system, it passes this event in to the InputMap where 58 * the InputMap can check all installed 59 * {@link InputMap.Mapping mappings} to see if there is any 60 * suitable mapping, and if so, fire the provided {@link EventHandler}. 61 * 62 * @param <N> The type of the Node that the InputMap is installed in. 63 * @since 9 64 */ 65 public class InputMap<N extends Node> implements EventHandler<Event> { 66 67 /*************************************************************************** 68 * * 69 * Private fields * 70 * * 71 **************************************************************************/ 72 73 private final N node; 74 75 private final ObservableList<InputMap<N>> childInputMaps; 76 77 private final ObservableList<Mapping<?>> mappings; 78 79 // private final ObservableList<Predicate<? extends Event>> interceptors; 80 81 private final Map<EventType<?>, List<EventHandler<? super Event>>> installedEventHandlers; 82 83 private final Map<EventType, List<Mapping>> eventTypeMappings; 84 85 86 87 /*************************************************************************** 88 * * 89 * Constructors * 90 * * 91 **************************************************************************/ 92 93 /** 94 * Creates the new InputMap instance which is related specifically to the 95 * given Node. 96 * @param node The Node for which this InputMap is attached. 97 */ 98 public InputMap(N node) { 99 if (node == null) { 100 throw new IllegalArgumentException("Node can not be null"); 101 } 102 103 this.node = node; 104 this.eventTypeMappings = new HashMap<>(); 105 this.installedEventHandlers = new HashMap<>(); 106 // this.interceptors = FXCollections.observableArrayList(); 107 108 // listeners 109 this.mappings = FXCollections.observableArrayList(); 110 mappings.addListener((ListChangeListener<Mapping<?>>) c -> { 111 while (c.next()) { 112 // TODO handle mapping removal 113 if (c.wasRemoved()) { 114 for (Mapping<?> mapping : c.getRemoved()) { 115 removeMapping(mapping); 116 } 117 } 118 119 if (c.wasAdded()) { 120 List<Mapping<?>> toRemove = new ArrayList<>(); 121 for (Mapping<?> mapping : c.getAddedSubList()) { 122 if (mapping == null) { 123 toRemove.add(null); 124 } else { 125 addMapping(mapping); 126 } 127 } 128 129 if (!toRemove.isEmpty()) { 130 getMappings().removeAll(toRemove); 131 throw new IllegalArgumentException("Null mappings not permitted"); 132 } 133 } 134 } 135 }); 136 137 childInputMaps = FXCollections.observableArrayList(); 138 childInputMaps.addListener((ListChangeListener<InputMap<N>>) c -> { 139 while (c.next()) { 140 if (c.wasRemoved()) { 141 for (InputMap<N> map : c.getRemoved()) { 142 map.setParentInputMap(null); 143 } 144 } 145 146 if (c.wasAdded()) { 147 List<InputMap<N>> toRemove = new ArrayList<>(); 148 for (InputMap<N> map : c.getAddedSubList()) { 149 // we check that the child input map maps to the same node 150 // as this input map 151 if (map.getNode() != getNode()) { 152 toRemove.add(map); 153 } else { 154 map.setParentInputMap(this); 155 } 156 } 157 158 if (!toRemove.isEmpty()) { 159 getChildInputMaps().removeAll(toRemove); 160 throw new IllegalArgumentException("Child InputMap intances need to share a common Node object"); 161 } 162 } 163 } 164 }); 165 } 166 167 168 169 /*************************************************************************** 170 * * 171 * Properties * 172 * * 173 **************************************************************************/ 174 175 // --- parent behavior - for now this is an private property 176 private ReadOnlyObjectWrapper<InputMap<N>> parentInputMap = new ReadOnlyObjectWrapper<InputMap<N>>(this, "parentInputMap") { 177 @Override protected void invalidated() { 178 // whenever the parent InputMap changes, we uninstall all mappings and 179 // then reprocess them so that they are installed in the correct root. 180 reprocessAllMappings(); 181 } 182 }; 183 private final void setParentInputMap(InputMap<N> value) { parentInputMap.set(value); } 184 private final InputMap<N> getParentInputMap() {return parentInputMap.get(); } 185 private final ReadOnlyObjectProperty<InputMap<N>> parentInputMapProperty() { return parentInputMap.getReadOnlyProperty(); } 186 187 188 // --- interceptor 189 /** 190 * The role of the interceptor is to block the InputMap on which it is 191 * set from executing any mappings (contained within itself, or within a 192 * {@link #getChildInputMaps() child InputMap}, whenever the interceptor 193 * returns true. The interceptor is called every time an input event is received, 194 * and is allowed to reason on the given input event 195 * before returning a boolean value, where boolean true means block 196 * execution, and boolean false means to allow execution. 197 */ 198 private ObjectProperty<Predicate<? extends Event>> interceptor = new SimpleObjectProperty<>(this, "interceptor"); 199 public final Predicate<? extends Event> getInterceptor() { 200 return interceptor.get(); 201 } 202 public final void setInterceptor(Predicate<? extends Event> value) { 203 interceptor.set(value); 204 } 205 public final ObjectProperty<Predicate<? extends Event>> interceptorProperty() { 206 return interceptor; 207 } 208 209 210 211 /*************************************************************************** 212 * * 213 * Public API * 214 * * 215 **************************************************************************/ 216 217 /** 218 * The Node for which this InputMap is attached. 219 */ 220 public final N getNode() { 221 return node; 222 } 223 224 /** 225 * A mutable list of input mappings. Each will be considered whenever an 226 * input event is being looked up, and one of which may be used to handle 227 * the input event, based on the specifificity returned by each mapping 228 * (that is, the mapping with the highest specificity wins). 229 */ 230 public ObservableList<Mapping<?>> getMappings() { 231 return mappings; 232 } 233 234 /** 235 * A mutable list of child InputMaps. An InputMap may have child input maps, 236 * as this allows for easy addition 237 * of mappings that are state-specific. For example, if a Node can be in two 238 * different states, and the input mappings are different for each, then it 239 * makes sense to have one root (and empty) InputMap, with two children 240 * input maps, where each is populated with the specific input mappings for 241 * one of the two states. To prevent the wrong input map from being considered, 242 * it is simply a matter of setting an appropriate 243 * {@link #interceptorProperty() interceptor} on each map, so that they are only 244 * considered in one of the two states. 245 */ 246 public ObservableList<InputMap<N>> getChildInputMaps() { 247 return childInputMaps; 248 } 249 250 /** 251 * Disposes all child InputMaps, removes all event handlers from the Node, 252 * and clears the mappings list. 253 */ 254 public void dispose() { 255 for (InputMap<N> childInputMap : getChildInputMaps()) { 256 childInputMap.dispose(); 257 } 258 259 // uninstall event handlers 260 removeAllEventHandlers(); 261 262 // clear out all mappings 263 getMappings().clear(); 264 } 265 266 /** {@inheritDoc} */ 267 @Override public void handle(Event e) { 268 if (e == null || e.isConsumed()) return; 269 270 List<Mapping<?>> mappings = lookup(e, true); 271 for (Mapping<?> mapping : mappings) { 272 EventHandler eventHandler = mapping.getEventHandler(); 273 if (eventHandler != null) { 274 eventHandler.handle(e); 275 } 276 277 if (mapping.isAutoConsume()) { 278 e.consume(); 279 } 280 281 if (e.isConsumed()) { 282 break; 283 } 284 285 // If we are here, the event has not been consumed, so we continue 286 // looping through our list of matches. Refer to the documentation in 287 // lookup(Event) for more details on the list ordering. 288 } 289 } 290 291 /** 292 * Looks up the most specific mapping given the input, ignoring all 293 * interceptors. The valid values that can be passed into this method is 294 * based on the values returned by the {@link Mapping#getMappingKey()} 295 * method. Based on the subclasses of Mapping that ship with JavaFX, the 296 * valid values are therefore: 297 * 298 * <ul> 299 * <li><strong>KeyMapping:</strong> A valid {@link KeyBinding}.</li> 300 * <li><strong>MouseMapping:</strong> A valid {@link MouseEvent} event 301 * type (e.g. {@code MouseEvent.MOUSE_PRESSED}).</li> 302 * </ul> 303 * 304 * For other Mapping subclasses, refer to their javadoc, and specifically 305 * what is returned by {@link Mapping#getMappingKey()}, 306 * 307 * @param mappingKey 308 * @return 309 */ 310 // TODO return all mappings, or just the first one? 311 public Optional<Mapping<?>> lookupMapping(Object mappingKey) { 312 if (mappingKey == null) { 313 return Optional.empty(); 314 } 315 316 List<Mapping<?>> mappings = lookupMappingKey(mappingKey); 317 318 // descend into our child input maps as well 319 for (int i = 0; i < getChildInputMaps().size(); i++) { 320 InputMap<N> childInputMap = getChildInputMaps().get(i); 321 322 List<Mapping<?>> childMappings = childInputMap.lookupMappingKey(mappingKey); 323 mappings.addAll(0, childMappings); 324 } 325 326 return mappings.size() > 0 ? Optional.of(mappings.get(0)) : Optional.empty(); 327 } 328 329 330 331 332 /*************************************************************************** 333 * * 334 * Private implementation * 335 * * 336 **************************************************************************/ 337 338 private List<Mapping<?>> lookupMappingKey(Object mappingKey) { 339 return getMappings().stream() 340 .filter(mapping -> !mapping.isDisabled()) 341 .filter(mapping -> mappingKey.equals(mapping.getMappingKey())) 342 .collect(Collectors.toList()); 343 } 344 345 /* 346 * Returns a List of Mapping instances, in priority order (from highest priority 347 * to lowest priority). All mappings in the list have the same value specificity, 348 * so are ranked based on the input map (with the leaf input maps taking 349 * precedence over parent / root input maps). 350 */ 351 private List<Mapping<?>> lookup(Event event, boolean testInterceptors) { 352 // firstly we look at ourselves to see if we have a mapping, assuming our 353 // interceptors are valid 354 if (testInterceptors) { 355 boolean interceptorsApplies = testInterceptor(event, getInterceptor()); 356 357 if (interceptorsApplies) { 358 return Collections.emptyList(); 359 } 360 } 361 362 List<Mapping<?>> mappings = new ArrayList<>(); 363 364 int minSpecificity = 0; 365 List<Pair<Integer, Mapping<?>>> results = lookupMappingAndSpecificity(event, minSpecificity); 366 if (! results.isEmpty()) { 367 minSpecificity = results.get(0).getKey(); 368 mappings.addAll(results.stream().map(pair -> pair.getValue()).collect(Collectors.toList())); 369 } 370 371 // but we always descend into our child input maps as well, to see if there 372 // is a more specific mapping there. If there is a mapping of equal 373 // specificity, we take the child mapping over the parent mapping. 374 for (int i = 0; i < getChildInputMaps().size(); i++) { 375 InputMap childInputMap = getChildInputMaps().get(i); 376 377 // test if the childInputMap should be considered 378 if (testInterceptors) { 379 boolean interceptorsApplies = testInterceptor(event, childInputMap.getInterceptor()); 380 if (interceptorsApplies) { 381 continue; 382 } 383 } 384 385 List<Pair<Integer, Mapping<?>>> childResults = childInputMap.lookupMappingAndSpecificity(event, minSpecificity); 386 if (!childResults.isEmpty()) { 387 int specificity = childResults.get(0).getKey(); 388 List<Mapping<?>> childMappings = childResults.stream() 389 .map(pair -> pair.getValue()) 390 .collect(Collectors.toList()); 391 if (specificity == minSpecificity) { 392 mappings.addAll(0, childMappings); 393 } else if (specificity > minSpecificity) { 394 mappings.clear(); 395 minSpecificity = specificity; 396 mappings.addAll(childMappings); 397 } 398 } 399 } 400 401 return mappings; 402 } 403 404 private InputMap<N> getRootInputMap() { 405 InputMap<N> rootInputMap = this; 406 while (true) { 407 if (rootInputMap == null) break; 408 InputMap<N> parentInputMap = rootInputMap.getParentInputMap(); 409 if (parentInputMap == null) break; 410 rootInputMap = parentInputMap; 411 } 412 return rootInputMap; 413 } 414 415 private void addMapping(Mapping<?> mapping) { 416 InputMap<N> rootInputMap = getRootInputMap(); 417 418 // we want to track the event handlers we install, so that we can clean 419 // up in the dispose() method (and also so that we don't duplicate 420 // event handlers for a single event type). Because this is all handled 421 // in the root InputMap, we firstly find it, and then we defer to it. 422 rootInputMap.addEventHandler(mapping.eventType); 423 424 // we maintain a separate map of all mappings, which maps from the 425 // mapping event type into a list of mappings. This allows for easier 426 // iteration in the lookup methods. 427 EventType<?> et = mapping.getEventType(); 428 List<Mapping> _eventTypeMappings = this.eventTypeMappings.computeIfAbsent(et, f -> new ArrayList<>()); 429 _eventTypeMappings.add(mapping); 430 } 431 432 private void removeMapping(Mapping<?> mapping) { 433 // TODO 434 } 435 436 private void addEventHandler(EventType et) { 437 List<EventHandler<? super Event>> eventHandlers = 438 installedEventHandlers.computeIfAbsent(et, f -> new ArrayList<>()); 439 440 final EventHandler<? super Event> eventHandler = this::handle; 441 442 if (eventHandlers.isEmpty()) { 443 // System.out.println("Added event handler for type " + et); 444 node.addEventHandler(et, eventHandler); 445 } 446 447 // We need to store these event handlers so we can dispose cleanly. 448 eventHandlers.add(eventHandler); 449 } 450 451 private void removeAllEventHandlers() { 452 for (EventType<?> et : installedEventHandlers.keySet()) { 453 List<EventHandler<? super Event>> handlers = installedEventHandlers.get(et); 454 for (EventHandler<? super Event> handler : handlers) { 455 // System.out.println("Removed event handler for type " + et); 456 node.removeEventHandler(et, handler); 457 } 458 } 459 } 460 461 private void reprocessAllMappings() { 462 removeAllEventHandlers(); 463 this.mappings.stream().forEach(this::addMapping); 464 465 // now do the same for all children 466 for (InputMap<N> child : getChildInputMaps()) { 467 child.reprocessAllMappings(); 468 } 469 } 470 471 private List<Pair<Integer, Mapping<?>>> lookupMappingAndSpecificity(final Event event, final int minSpecificity) { 472 int _minSpecificity = minSpecificity; 473 474 List<Mapping> mappings = this.eventTypeMappings.getOrDefault(event.getEventType(), Collections.emptyList()); 475 List<Pair<Integer, Mapping<?>>> result = new ArrayList<>(); 476 for (Mapping mapping : mappings) { 477 if (mapping.isDisabled()) continue; 478 479 // test if mapping has an interceptor that will block this event. 480 // Interceptors return true if the interception should occur. 481 boolean interceptorsApplies = testInterceptor(event, mapping.getInterceptor()); 482 if (interceptorsApplies) { 483 continue; 484 } 485 486 int specificity = mapping.getSpecificity(event); 487 if (specificity > 0 && specificity == _minSpecificity) { 488 result.add(new Pair<>(specificity, mapping)); 489 } else if (specificity > _minSpecificity) { 490 result.clear(); 491 result.add(new Pair<>(specificity, mapping)); 492 _minSpecificity = specificity; 493 } 494 } 495 496 return result; 497 } 498 499 // Interceptors return true if the interception should occur. 500 private boolean testInterceptor(Event e, Predicate interceptor) { 501 return interceptor != null && interceptor.test(e); 502 } 503 504 505 506 /*************************************************************************** 507 * * 508 * Support classes * 509 * * 510 **************************************************************************/ 511 512 /** 513 * Abstract base class for all input mappings as used by the 514 * {@link InputMap} class. 515 * 516 * @param <T> The type of {@link Event} the mapping represents. 517 */ 518 public static abstract class Mapping<T extends Event> { 519 520 /*********************************************************************** 521 * * 522 * Private fields * 523 * * 524 **********************************************************************/ 525 private final EventType<T> eventType; 526 private final EventHandler<T> eventHandler; 527 528 529 530 /*********************************************************************** 531 * * 532 * Constructors * 533 * * 534 **********************************************************************/ 535 536 /** 537 * Creates a new Mapping instance. 538 * 539 * @param eventType The {@link EventType} that is being listened for. 540 * @param eventHandler The {@link EventHandler} to fire when the mapping 541 * is selected as the most-specific mapping. 542 */ 543 public Mapping(final EventType<T> eventType, final EventHandler<T> eventHandler) { 544 this.eventType = eventType; 545 this.eventHandler = eventHandler; 546 } 547 548 549 550 /*********************************************************************** 551 * * 552 * Abstract methods * 553 * * 554 **********************************************************************/ 555 556 /** 557 * This method must be implemented by all mapping implementations such 558 * that it returns an integer value representing how closely the mapping 559 * matches the given {@link Event}. The higher the number, the greater 560 * the match. This allows the InputMap to determine 561 * which mapping is most specific, and to therefore fire the appropriate 562 * mapping {@link Mapping#getEventHandler() EventHandler}. 563 * 564 * @param event The {@link Event} that needs to be assessed for its 565 * specificity. 566 * @return An integer indicating how close of a match the mapping is to 567 * the given Event. The higher the number, the greater the match. 568 */ 569 public abstract int getSpecificity(Event event); 570 571 572 573 /*********************************************************************** 574 * * 575 * Properties * 576 * * 577 **********************************************************************/ 578 579 // --- disabled 580 /** 581 * By default all mappings are enabled (so this disabled property is set 582 * to false by default). In some cases it is useful to be able to disable 583 * a mapping until it is applicable. In these cases, users may simply 584 * toggle the disabled property until desired. 585 * 586 * <p>When the disabled property is true, the mapping will not be 587 * considered when input events are received, even if it is the most 588 * specific mapping available.</p> 589 */ 590 private BooleanProperty disabled = new SimpleBooleanProperty(this, "disabled", false); 591 public final void setDisabled(boolean value) { disabled.set(value); } 592 public final boolean isDisabled() {return disabled.get(); } 593 public final BooleanProperty disabledProperty() { return disabled; } 594 595 596 // --- auto consume 597 /** 598 * By default mappings are set to 'auto consume' their specified event 599 * handler. This means that the event handler will not propagate further, 600 * but in some cases this is not desirable - sometimes it is preferred 601 * that the event continue to 'bubble up' to parent nodes so that they 602 * may also benefit from receiving this event. In these cases, it is 603 * important that this autoConsume property be changed from the default 604 * boolean true to instead be boolean false. 605 */ 606 private BooleanProperty autoConsume = new SimpleBooleanProperty(this, "autoConsume", true); 607 public final void setAutoConsume(boolean value) { autoConsume.set(value); } 608 public final boolean isAutoConsume() {return autoConsume.get(); } 609 public final BooleanProperty autoConsumeProperty() { return autoConsume; } 610 611 612 613 /*********************************************************************** 614 * * 615 * Public API * 616 * * 617 **********************************************************************/ 618 619 /** 620 * The {@link EventType} that is being listened for. 621 */ 622 public final EventType<T> getEventType() { 623 return eventType; 624 } 625 626 /** 627 * The {@link EventHandler} that will be fired should this mapping be 628 * the most-specific mapping for a given input, and should it not be 629 * blocked by an interceptor (either at a 630 * {@link InputMap#interceptorProperty() input map} level or a 631 * {@link Mapping#interceptorProperty() mapping} level). 632 */ 633 public final EventHandler<T> getEventHandler() { 634 return eventHandler; 635 } 636 637 638 // --- interceptor 639 /** 640 * The role of the interceptor is to block the mapping on which it is 641 * set from executing, whenever the interceptor returns true. The 642 * interceptor is called every time the mapping is the best match for 643 * a given input event, and is allowed to reason on the given input event 644 * before returning a boolean value, where boolean true means block 645 * execution, and boolean false means to allow execution. 646 */ 647 private ObjectProperty<Predicate<? extends Event>> interceptor = new SimpleObjectProperty<>(this, "interceptor"); 648 public final Predicate<? extends Event> getInterceptor() { 649 return interceptor.get(); 650 } 651 public final void setInterceptor(Predicate<? extends Event> value) { 652 interceptor.set(value); 653 } 654 public final ObjectProperty<Predicate<? extends Event>> interceptorProperty() { 655 return interceptor; 656 } 657 658 /** 659 * 660 * @return 661 */ 662 public Object getMappingKey() { 663 return eventType; 664 } 665 666 /** {@inheritDoc} */ 667 @Override public boolean equals(Object o) { 668 if (this == o) return true; 669 if (!(o instanceof Mapping)) return false; 670 671 Mapping that = (Mapping) o; 672 673 if (eventType != null ? !eventType.equals(that.getEventType()) : that.getEventType() != null) return false; 674 675 return true; 676 } 677 678 /** {@inheritDoc} */ 679 @Override public int hashCode() { 680 return eventType != null ? eventType.hashCode() : 0; 681 } 682 } 683 684 /** 685 * The KeyMapping class provides API to specify 686 * {@link InputMap.Mapping mappings} related to key input. 687 */ 688 public static class KeyMapping extends Mapping<KeyEvent> { 689 690 /*********************************************************************** 691 * * 692 * Private fields * 693 * * 694 **********************************************************************/ 695 private final KeyBinding keyBinding; 696 697 698 699 /*********************************************************************** 700 * * 701 * Constructors * 702 * * 703 **********************************************************************/ 704 705 /** 706 * Creates a new KeyMapping instance that will fire when the given 707 * {@link KeyCode} is entered into the application by the user, and this 708 * will result in the given {@link EventHandler} being fired. 709 * 710 * @param keyCode The {@link KeyCode} to listen for. 711 * @param eventHandler The {@link EventHandler} to fire when the 712 * {@link KeyCode} is observed. 713 */ 714 public KeyMapping(final KeyCode keyCode, final EventHandler<KeyEvent> eventHandler) { 715 this(new KeyBinding(keyCode), eventHandler); 716 } 717 718 /** 719 * Creates a new KeyMapping instance that will fire when the given 720 * {@link KeyCode} is entered into the application by the user, and this 721 * will result in the given {@link EventHandler} being fired. The 722 * eventType argument can be one of the following: 723 * 724 * <ul> 725 * <li>{@link KeyEvent#ANY}</li> 726 * <li>{@link KeyEvent#KEY_PRESSED}</li> 727 * <li>{@link KeyEvent#KEY_TYPED}</li> 728 * <li>{@link KeyEvent#KEY_RELEASED}</li> 729 * </ul> 730 * 731 * @param keyCode The {@link KeyCode} to listen for. 732 * @param eventType The type of {@link KeyEvent} to listen for. 733 * @param eventHandler The {@link EventHandler} to fire when the 734 * {@link KeyCode} is observed. 735 */ 736 public KeyMapping(final KeyCode keyCode, final EventType<KeyEvent> eventType, final EventHandler<KeyEvent> eventHandler) { 737 this(new KeyBinding(keyCode, eventType), eventHandler); 738 } 739 740 /** 741 * Creates a new KeyMapping instance that will fire when the given 742 * {@link KeyBinding} is entered into the application by the user, and this 743 * will result in the given {@link EventHandler} being fired. 744 * 745 * @param keyBinding The {@link KeyBinding} to listen for. 746 * @param eventHandler The {@link EventHandler} to fire when the 747 * {@link KeyBinding} is observed. 748 */ 749 public KeyMapping(KeyBinding keyBinding, final EventHandler<KeyEvent> eventHandler) { 750 this(keyBinding, eventHandler, null); 751 } 752 753 /** 754 * Creates a new KeyMapping instance that will fire when the given 755 * {@link KeyBinding} is entered into the application by the user, and this 756 * will result in the given {@link EventHandler} being fired, as long as the 757 * given interceptor is not true. 758 * 759 * @param keyBinding The {@link KeyBinding} to listen for. 760 * @param eventHandler The {@link EventHandler} to fire when the 761 * {@link KeyBinding} is observed. 762 * @param interceptor A {@link Predicate} that, if true, will prevent the 763 * {@link EventHandler} from being fired. 764 */ 765 public KeyMapping(KeyBinding keyBinding, final EventHandler<KeyEvent> eventHandler, Predicate<KeyEvent> interceptor) { 766 super(keyBinding == null ? null : keyBinding.getType(), eventHandler); 767 if (keyBinding == null) { 768 throw new IllegalArgumentException("KeyMapping keyBinding constructor argument can not be null"); 769 } 770 this.keyBinding = keyBinding; 771 setInterceptor(interceptor); 772 } 773 774 775 776 /*********************************************************************** 777 * * 778 * Public API * 779 * * 780 **********************************************************************/ 781 782 /** {@inheritDoc} */ 783 @Override public Object getMappingKey() { 784 return keyBinding; 785 } 786 787 /** {@inheritDoc} */ 788 @Override public int getSpecificity(Event e) { 789 if (isDisabled()) return 0; 790 if (!(e instanceof KeyEvent)) return 0; 791 return keyBinding.getSpecificity((KeyEvent)e); 792 } 793 794 /** {@inheritDoc} */ 795 @Override public boolean equals(Object o) { 796 if (this == o) return true; 797 if (!(o instanceof KeyMapping)) return false; 798 if (!super.equals(o)) return false; 799 800 KeyMapping that = (KeyMapping) o; 801 802 // we know keyBinding is non-null here 803 return keyBinding.equals(that.keyBinding); 804 } 805 806 /** {@inheritDoc} */ 807 @Override public int hashCode() { 808 return Objects.hash(keyBinding); 809 } 810 } 811 812 813 814 /** 815 * The MouseMapping class provides API to specify 816 * {@link InputMap.Mapping mappings} related to mouse input. 817 */ 818 public static class MouseMapping extends Mapping<MouseEvent> { 819 820 /*********************************************************************** 821 * * 822 * Constructors * 823 * * 824 **********************************************************************/ 825 826 /** 827 * Creates a new KeyMapping instance that will fire when the given 828 * {@link KeyCode} is entered into the application by the user, and this 829 * will result in the given {@link EventHandler} being fired. The 830 * eventType argument can be any of the {@link MouseEvent} event types, 831 * but typically it is one of the following: 832 * 833 * <ul> 834 * <li>{@link MouseEvent#ANY}</li> 835 * <li>{@link MouseEvent#MOUSE_PRESSED}</li> 836 * <li>{@link MouseEvent#MOUSE_CLICKED}</li> 837 * <li>{@link MouseEvent#MOUSE_RELEASED}</li> 838 * </ul> 839 * 840 * @param eventType The type of {@link MouseEvent} to listen for. 841 * @param eventHandler The {@link EventHandler} to fire when the 842 * {@link MouseEvent} is observed. 843 */ 844 public MouseMapping(final EventType<MouseEvent> eventType, final EventHandler<MouseEvent> eventHandler) { 845 super(eventType, eventHandler); 846 if (eventType == null) { 847 throw new IllegalArgumentException("MouseMapping eventType constructor argument can not be null"); 848 } 849 } 850 851 852 853 /*********************************************************************** 854 * * 855 * Public API * 856 * * 857 **********************************************************************/ 858 859 /** {@inheritDoc} */ 860 @Override public int getSpecificity(Event e) { 861 if (isDisabled()) return 0; 862 if (!(e instanceof MouseEvent)) return 0; 863 EventType<MouseEvent> et = getEventType(); 864 865 // FIXME naive 866 int s = 0; 867 if (e.getEventType() == MouseEvent.MOUSE_CLICKED && et != MouseEvent.MOUSE_CLICKED) return 0; else s++; 868 if (e.getEventType() == MouseEvent.MOUSE_DRAGGED && et != MouseEvent.MOUSE_DRAGGED) return 0; else s++; 869 if (e.getEventType() == MouseEvent.MOUSE_ENTERED && et != MouseEvent.MOUSE_ENTERED) return 0; else s++; 870 if (e.getEventType() == MouseEvent.MOUSE_ENTERED_TARGET && et != MouseEvent.MOUSE_ENTERED_TARGET) return 0; else s++; 871 if (e.getEventType() == MouseEvent.MOUSE_EXITED && et != MouseEvent.MOUSE_EXITED) return 0; else s++; 872 if (e.getEventType() == MouseEvent.MOUSE_EXITED_TARGET && et != MouseEvent.MOUSE_EXITED_TARGET) return 0; else s++; 873 if (e.getEventType() == MouseEvent.MOUSE_MOVED && et != MouseEvent.MOUSE_MOVED) return 0; else s++; 874 if (e.getEventType() == MouseEvent.MOUSE_PRESSED && et != MouseEvent.MOUSE_PRESSED) return 0; else s++; 875 if (e.getEventType() == MouseEvent.MOUSE_RELEASED && et != MouseEvent.MOUSE_RELEASED) return 0; else s++; 876 877 // TODO handle further checks 878 879 return s; 880 } 881 } 882 883 /** 884 * Convenience class that can act as an keyboard input interceptor, either at a 885 * {@link InputMap#interceptorProperty() input map} level or a 886 * {@link Mapping#interceptorProperty() mapping} level. 887 * 888 * @see InputMap#interceptorProperty() 889 * @see Mapping#interceptorProperty() 890 */ 891 public static class KeyMappingInterceptor implements Predicate<Event> { 892 893 private final KeyBinding keyBinding; 894 895 /** 896 * Creates a new KeyMappingInterceptor, which will block execution of 897 * event handlers (either at a 898 * {@link InputMap#interceptorProperty() input map} level or a 899 * {@link Mapping#interceptorProperty() mapping} level), where the input 900 * received is equal to the given {@link KeyBinding}. 901 * 902 * @param keyBinding The {@link KeyBinding} for which mapping execution 903 * should be blocked. 904 */ 905 public KeyMappingInterceptor(KeyBinding keyBinding) { 906 this.keyBinding = keyBinding; 907 } 908 909 /** {@inheritDoc} */ 910 public boolean test(Event event) { 911 if (!(event instanceof KeyEvent)) return false; 912 return KeyBinding.toKeyBinding((KeyEvent)event).equals(keyBinding); 913 } 914 } 915 916 /** 917 * Convenience class that can act as a mouse input interceptor, either at a 918 * {@link InputMap#interceptorProperty() input map} level or a 919 * {@link Mapping#interceptorProperty() mapping} level. 920 * 921 * @see InputMap#interceptorProperty() 922 * @see Mapping#interceptorProperty() 923 */ 924 public static class MouseMappingInterceptor implements Predicate<Event> { 925 926 private final EventType<MouseEvent> eventType; 927 928 /** 929 * Creates a new MouseMappingInterceptor, which will block execution of 930 * event handlers (either at a 931 * {@link InputMap#interceptorProperty() input map} level or a 932 * {@link Mapping#interceptorProperty() mapping} level), where the input 933 * received is equal to the given {@link EventType}. 934 * 935 * @param eventType The {@link EventType} for which mapping execution 936 * should be blocked (typically one of 937 * {@link MouseEvent#MOUSE_PRESSED}, 938 * {@link MouseEvent#MOUSE_CLICKED}, or 939 * {@link MouseEvent#MOUSE_RELEASED}). 940 */ 941 public MouseMappingInterceptor(EventType<MouseEvent> eventType) { 942 this.eventType = eventType; 943 } 944 945 /** {@inheritDoc} */ 946 public boolean test(Event event) { 947 if (!(event instanceof MouseEvent)) return false; 948 return event.getEventType() == this.eventType; 949 } 950 } 951 }