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 }