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