Home | History | Annotate | Download | only in keyboard
      1 package org.unicode.cldr.draft.keyboard;
      2 
      3 import static com.google.common.base.Preconditions.checkArgument;
      4 import static com.google.common.base.Preconditions.checkNotNull;
      5 
      6 import java.util.Iterator;
      7 import java.util.Set;
      8 
      9 import com.google.common.base.Objects;
     10 import com.google.common.collect.ImmutableSet;
     11 import com.google.common.collect.ImmutableSortedSet;
     12 import com.google.common.collect.Sets;
     13 
     14 /**
     15  * Object containing the combination of modifier keys that must be on and off for a particular
     16  * combination to be activated. All other keys are considered "don't care keys". This simulates
     17  * boolean logic for a 3 state system.
     18  *
     19  * <p>
     20  * For example, suppose we have three keys, "A", "B" and "C". Then the boolean expression:
     21  * {@code A AND !B} means that A must be on, B must be off and C is a don't care.
     22  *
     23  * <p>
     24  * Some keys may have L and R variants (like Control which has a Left-Control and a Right-Control).
     25  * When the situation occurs that the parent key (the variant without a left or right suffix) is
     26  * included into the on keys or off keys sets, then the children are included into the don't care
     27  * pool and are omitted when the combination is printed out.
     28  *
     29  * <p>
     30  * For example, suppose we have three keys "A", "B" and "C" and "A" has a left and right variant
     31  * which are "A-Right" and "A-Left" respectively. Continuing the example above, the keys fall into
     32  * the following categories:
     33  * <ul>
     34  * <li>ON: { A }
     35  * <li>OFF: { B }
     36  * <li>DON'T CARE: { C, A-Left, A-Right }
     37  * </ul>
     38  * <p>
     39  * However when printing out the combination, we would exclude the A-Left and A-Right keys because
     40  * their parent is already included in the ON set. Therefore the output result would be:
     41  * {@code A+C?}.
     42  *
     43  * <p>
     44  * A slightly different behavior exists for parent modifier keys. When a parent modifier key is
     45  * given then its children are also added to the don't care keys pool. Once again, the
     46  * simplification is shown when printing out the combination.
     47  *
     48  * <p>
     49  * For example, continuing the above example but this time assuming that C has a left and right
     50  * variant. Therefore the breakdown looks like:
     51  * <ul>
     52  * <li>ON: { A }
     53  * <li>OFF: { B }
     54  * <li>DON'T CARE: { C, C-Left, C-Right, A-Left, A-Right }
     55  * </ul>
     56  */
     57 public final class ModifierKeyCombination implements Comparable<ModifierKeyCombination> {
     58     public static final ModifierKeyCombination BASE = ModifierKeyCombination.ofOnKeys(
     59         ImmutableSet.<ModifierKey> of());
     60 
     61     private final ImmutableSet<ModifierKey> onKeys;
     62     private final ImmutableSet<ModifierKey> offKeys;
     63 
     64     private ModifierKeyCombination(ImmutableSet<ModifierKey> onKeys, ImmutableSet<ModifierKey> offKeys) {
     65         this.onKeys = checkNotNull(onKeys);
     66         this.offKeys = checkNotNull(offKeys);
     67     }
     68 
     69     /**
     70      * Create a modifier key combination from a set of ON keys. This is the most common factory
     71      * method since most sources will provide the keys that MUST be on. Simplifies the set as
     72      * needed.
     73      */
     74     public static ModifierKeyCombination ofOnKeys(Set<ModifierKey> onKeys) {
     75         return ofOnAndDontCareKeys(ImmutableSet.copyOf(onKeys), ImmutableSet.<ModifierKey> of());
     76     }
     77 
     78     /**
     79      * Create a modifier key combination from a set of ON keys and a set of DON'T CARE keys.
     80      * That is a set of keys that MUST be ON and a set of keys that CAN be ON or OFF. Simplifies
     81      * the sets as needed.
     82      */
     83     public static ModifierKeyCombination ofOnAndDontCareKeys(Set<ModifierKey> onKeys,
     84         Set<ModifierKey> dontCareKeys) {
     85         checkArgument(Sets.intersection(onKeys, dontCareKeys).size() == 0,
     86             "On keys and don't care keys must be disjoint");
     87         return ModifierKeySimplifier.simplifyInput(ImmutableSet.copyOf(onKeys),
     88             ImmutableSet.copyOf(dontCareKeys));
     89     }
     90 
     91     /** Internal. */
     92     static ModifierKeyCombination of(ImmutableSet<ModifierKey> onKeys,
     93         ImmutableSet<ModifierKey> offKeys) {
     94         return new ModifierKeyCombination(onKeys, offKeys);
     95     }
     96 
     97     /** Returns the set of keys that have to be ON for this combination to be active. */
     98     public ImmutableSet<ModifierKey> onKeys() {
     99         return onKeys;
    100     }
    101 
    102     /** Returns the set of keys that have to be OFF for this combination to be active. */
    103     public ImmutableSet<ModifierKey> offKeys() {
    104         return offKeys;
    105     }
    106 
    107     /**
    108      * Determines if this combination is a base combination. That is, is this combination valid when
    109      * no modifier keys are pressed?
    110      */
    111     public boolean isBase() {
    112         return onKeys.isEmpty();
    113     }
    114 
    115     @Override
    116     public boolean equals(Object o) {
    117         if (this == o) {
    118             return true;
    119         }
    120         if (o instanceof ModifierKeyCombination) {
    121             ModifierKeyCombination other = (ModifierKeyCombination) o;
    122             return onKeys.equals(other.onKeys) && offKeys.equals(other.offKeys);
    123         }
    124         return false;
    125     }
    126 
    127     @Override
    128     public int hashCode() {
    129         return Objects.hashCode(onKeys, offKeys);
    130     }
    131 
    132     @Override
    133     public String toString() {
    134         // TODO: cache this result.
    135         return ModifierKeySimplifier.simplifyToString(this);
    136     }
    137 
    138     @Override
    139     public int compareTo(ModifierKeyCombination o) {
    140         // Compare on keys first.
    141         ImmutableSortedSet<ModifierKey> sortedOnKeys1 = ImmutableSortedSet.copyOf(onKeys);
    142         ImmutableSortedSet<ModifierKey> sortedOnKeys2 = ImmutableSortedSet.copyOf(o.onKeys);
    143         int result = compareSetsDescending(sortedOnKeys1, sortedOnKeys2);
    144         // If they are identical, compare off keys (this will be the opposite from the result from the
    145         // on keys because what we really want is the order for the don't care keys which are simply the
    146         // converse of the off keys)
    147         // Here is a simple illustrative example:
    148         // -Suppose Alphabetic order within a combination
    149         // -Suppose reverse Alphabetic order between combinations
    150         // -Suppose four keys {A, B, C, D}
    151         // -Suppose two combinations, A.B.~D (A+B+C?) and A.B.~C (A+B+D?).
    152         // We want them ordered: A+B+D? A+B+C?
    153         // Clearly, AB are identical in both so we move onto the off keys: ~D and ~C respectively.
    154         // According to our initial comparison, ~D comes before ~C, but this is incorrect when looking
    155         // at the don't care keys which are C? and D? respectively. This is why we multiply the result
    156         // received from the off keys comparison by -1.
    157         //
    158         // More on the reverse ordering scheme between combinations:
    159         // Within a combination: A+B+C (A comes before B and B comes before C)
    160         // Between combinations: Suppose two combinations, A+C and B+C. Then the order would be B+C A+C.
    161         // (Reverse ordering so B comes before A).
    162         if (result == 0) {
    163             ImmutableSortedSet<ModifierKey> sortedOffKeys1 = ImmutableSortedSet.copyOf(offKeys);
    164             ImmutableSortedSet<ModifierKey> sortedOffKeys2 = ImmutableSortedSet.copyOf(o.offKeys);
    165             return -1 * compareSetsDescending(sortedOffKeys1, sortedOffKeys2);
    166         } else {
    167             return result;
    168         }
    169     }
    170 
    171     /**
    172      * Compare two sets of modifier key elements. Returns a negative integer if {@code set1} is
    173      * less than {@code set2}, a positive integer if {@code set1} is greater than {@code set2} and 0
    174      * if both sets are equal.
    175      *
    176      * <p>
    177      * Compares the sets based on the reverse ordering of the natural order imposed by the
    178      * modifier key enum. This is the convention used in the LDML Keyboard Standard.
    179      */
    180     private static int compareSetsDescending(
    181         ImmutableSortedSet<ModifierKey> set1, ImmutableSortedSet<ModifierKey> set2) {
    182         Iterator<ModifierKey> iterator1 = set1.iterator();
    183         Iterator<ModifierKey> iterator2 = set2.iterator();
    184         // Compare on keys until a difference is found.
    185         while (iterator1.hasNext() && iterator2.hasNext()) {
    186             ModifierKey modifierKey1 = iterator1.next();
    187             ModifierKey modifierKey2 = iterator2.next();
    188             if (modifierKey1.compareTo(modifierKey2) < 0) {
    189                 return 1;
    190             } else if (modifierKey1.compareTo(modifierKey2) > 0) {
    191                 return -1;
    192             }
    193         }
    194         // If the first x elements are identical, then the set with more modifier keys should come
    195         // after the set with less.
    196         return set1.size() - set2.size();
    197     }
    198 }
    199