blob: f324f5d8918eccfc4198906b8472e6ab08d62b18 [file] [log] [blame]
package org.unicode.cldr.draft.keyboard;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Iterator;
import java.util.Set;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
/**
* Object containing the combination of modifier keys that must be on and off for a particular
* combination to be activated. All other keys are considered "don't care keys". This simulates
* boolean logic for a 3 state system.
*
* <p>
* For example, suppose we have three keys, "A", "B" and "C". Then the boolean expression:
* {@code A AND !B} means that A must be on, B must be off and C is a don't care.
*
* <p>
* Some keys may have L and R variants (like Control which has a Left-Control and a Right-Control).
* When the situation occurs that the parent key (the variant without a left or right suffix) is
* included into the on keys or off keys sets, then the children are included into the don't care
* pool and are omitted when the combination is printed out.
*
* <p>
* For example, suppose we have three keys "A", "B" and "C" and "A" has a left and right variant
* which are "A-Right" and "A-Left" respectively. Continuing the example above, the keys fall into
* the following categories:
* <ul>
* <li>ON: { A }
* <li>OFF: { B }
* <li>DON'T CARE: { C, A-Left, A-Right }
* </ul>
* <p>
* However when printing out the combination, we would exclude the A-Left and A-Right keys because
* their parent is already included in the ON set. Therefore the output result would be:
* {@code A+C?}.
*
* <p>
* A slightly different behavior exists for parent modifier keys. When a parent modifier key is
* given then its children are also added to the don't care keys pool. Once again, the
* simplification is shown when printing out the combination.
*
* <p>
* For example, continuing the above example but this time assuming that C has a left and right
* variant. Therefore the breakdown looks like:
* <ul>
* <li>ON: { A }
* <li>OFF: { B }
* <li>DON'T CARE: { C, C-Left, C-Right, A-Left, A-Right }
* </ul>
*/
public final class ModifierKeyCombination implements Comparable<ModifierKeyCombination> {
public static final ModifierKeyCombination BASE = ModifierKeyCombination.ofOnKeys(
ImmutableSet.<ModifierKey>of());
private final ImmutableSet<ModifierKey> onKeys;
private final ImmutableSet<ModifierKey> offKeys;
private ModifierKeyCombination(ImmutableSet<ModifierKey> onKeys, ImmutableSet<ModifierKey> offKeys) {
this.onKeys = checkNotNull(onKeys);
this.offKeys = checkNotNull(offKeys);
}
/**
* Create a modifier key combination from a set of ON keys. This is the most common factory
* method since most sources will provide the keys that MUST be on. Simplifies the set as
* needed.
*/
public static ModifierKeyCombination ofOnKeys(Set<ModifierKey> onKeys) {
return ofOnAndDontCareKeys(ImmutableSet.copyOf(onKeys), ImmutableSet.<ModifierKey>of());
}
/**
* Create a modifier key combination from a set of ON keys and a set of DON'T CARE keys.
* That is a set of keys that MUST be ON and a set of keys that CAN be ON or OFF. Simplifies
* the sets as needed.
*/
public static ModifierKeyCombination ofOnAndDontCareKeys(Set<ModifierKey> onKeys,
Set<ModifierKey> dontCareKeys) {
checkArgument(Sets.intersection(onKeys, dontCareKeys).size() == 0,
"On keys and don't care keys must be disjoint");
return ModifierKeySimplifier.simplifyInput(ImmutableSet.copyOf(onKeys),
ImmutableSet.copyOf(dontCareKeys));
}
/** Internal. */
static ModifierKeyCombination of(ImmutableSet<ModifierKey> onKeys,
ImmutableSet<ModifierKey> offKeys) {
return new ModifierKeyCombination(onKeys, offKeys);
}
/** Returns the set of keys that have to be ON for this combination to be active. */
public ImmutableSet<ModifierKey> onKeys() {
return onKeys;
}
/** Returns the set of keys that have to be OFF for this combination to be active. */
public ImmutableSet<ModifierKey> offKeys() {
return offKeys;
}
/**
* Determines if this combination is a base combination. That is, is this combination valid when
* no modifier keys are pressed?
*/
public boolean isBase() {
return onKeys.isEmpty();
}
@Override public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof ModifierKeyCombination) {
ModifierKeyCombination other = (ModifierKeyCombination) o;
return onKeys.equals(other.onKeys) && offKeys.equals(other.offKeys);
}
return false;
}
@Override public int hashCode() {
return Objects.hashCode(onKeys, offKeys);
}
@Override public String toString() {
// TODO: cache this result.
return ModifierKeySimplifier.simplifyToString(this);
}
@Override public int compareTo(ModifierKeyCombination o) {
// Compare on keys first.
ImmutableSortedSet<ModifierKey> sortedOnKeys1 = ImmutableSortedSet.copyOf(onKeys);
ImmutableSortedSet<ModifierKey> sortedOnKeys2 = ImmutableSortedSet.copyOf(o.onKeys);
int result = compareSetsDescending(sortedOnKeys1, sortedOnKeys2);
// If they are identical, compare off keys (this will be the opposite from the result from the
// on keys because what we really want is the order for the don't care keys which are simply the
// converse of the off keys)
// Here is a simple illustrative example:
// -Suppose Alphabetic order within a combination
// -Suppose reverse Alphabetic order between combinations
// -Suppose four keys {A, B, C, D}
// -Suppose two combinations, A.B.~D (A+B+C?) and A.B.~C (A+B+D?).
// We want them ordered: A+B+D? A+B+C?
// Clearly, AB are identical in both so we move onto the off keys: ~D and ~C respectively.
// According to our initial comparison, ~D comes before ~C, but this is incorrect when looking
// at the don't care keys which are C? and D? respectively. This is why we multiply the result
// received from the off keys comparison by -1.
//
// More on the reverse ordering scheme between combinations:
// Within a combination: A+B+C (A comes before B and B comes before C)
// Between combinations: Suppose two combinations, A+C and B+C. Then the order would be B+C A+C.
// (Reverse ordering so B comes before A).
if (result == 0) {
ImmutableSortedSet<ModifierKey> sortedOffKeys1 = ImmutableSortedSet.copyOf(offKeys);
ImmutableSortedSet<ModifierKey> sortedOffKeys2 = ImmutableSortedSet.copyOf(o.offKeys);
return -1 * compareSetsDescending(sortedOffKeys1, sortedOffKeys2);
} else {
return result;
}
}
/**
* Compare two sets of modifier key elements. Returns a negative integer if {@code set1} is
* less than {@code set2}, a positive integer if {@code set1} is greater than {@code set2} and 0
* if both sets are equal.
*
* <p>
* Compares the sets based on the reverse ordering of the natural order imposed by the
* modifier key enum. This is the convention used in the LDML Keyboard Standard.
*/
private static int compareSetsDescending(
ImmutableSortedSet<ModifierKey> set1, ImmutableSortedSet<ModifierKey> set2) {
Iterator<ModifierKey> iterator1 = set1.iterator();
Iterator<ModifierKey> iterator2 = set2.iterator();
// Compare on keys until a difference is found.
while (iterator1.hasNext() && iterator2.hasNext()) {
ModifierKey modifierKey1 = iterator1.next();
ModifierKey modifierKey2 = iterator2.next();
if (modifierKey1.compareTo(modifierKey2) < 0) {
return 1;
} else if (modifierKey1.compareTo(modifierKey2) > 0) {
return -1;
}
}
// If the first x elements are identical, then the set with more modifier keys should come
// after the set with less.
return set1.size() - set2.size();
}
}