Fixes bug 624133, "Input freezes after impossible character". Try 'shsM' in

ACIP or 'ShSm' in Extended Wylie to see the new behavior.

We use a trie to store valid input sequences.  In the future, we could use
the same trie as a replacement for the more inefficient HashSets we use to
store characters, vowels, and punctuation.  For example, we'd use
'validInputSequences.put("K", new Pair("consonant", "k"))' when reading
in the ACIP keyboard's description of the first consonant of the Tibetan
alphabet in 'TibetanKeyboard.java'.

Note that the current trie implementation is only useful for 7- or 8-bit
transcription systems, and works best for tries with low average depth, which
describes a transcription system's trie very well.  If you used arbitrary
Unicode in your keyboard, you'd need a different trie implementation.

Improved the optional keyboard input mode status messages.
This commit is contained in:
dchandler 2002-11-02 18:44:24 +00:00
parent 92fd1bf0b6
commit de6ae79959
4 changed files with 183 additions and 38 deletions

View File

@ -1039,6 +1039,8 @@ public void paste(int offset) {
* Other keystrokes are handled by keyTyped.
*/
public void keyPressed(KeyEvent e) {
// FIXME: exceptions thrown here do not cause the program to fail, even in development mode.
if (e.isActionKey())
initKeyboard();
@ -1108,6 +1110,8 @@ public void paste(int offset) {
* @param e the KeyEvent
*/
public void keyReleased(KeyEvent e) {
// FIXME: exceptions thrown here do not cause the program to fail, even in development mode.
/*
* Apparently it works best to check for backspace
* and init the keyboard here in key released
@ -1128,6 +1132,8 @@ public void paste(int offset) {
* @param e a KeyEvent
*/
public void keyTyped(KeyEvent e) {
// FIXME: exceptions thrown here do not cause the program to fail, even in development mode.
e.consume();
// Respect setEditable(boolean):
@ -1174,29 +1180,34 @@ public void paste(int offset) {
* TibTextUtils.getGlyphs, which in turn relies on 'tibwn.ini'.
*
* @param e a KeyEvent */
public void processTibetan(KeyEvent e) {
char c = e.getKeyChar();
int start = getSelectionStart();
int end = getSelectionEnd();
public void processTibetan(KeyEvent kev) {
boolean shouldIBackSpace = true;
// We don't handle just any old keypress. We handle only the
// ones that enter text.
if (e.isControlDown() || e.isAltDown())
if (kev.isControlDown() || kev.isAltDown())
return;
if (start != end) {
if (e.getKeyCode() != KeyEvent.VK_ESCAPE) {
try {
initKeyboard();
doc.remove(start, end-start);
shouldIBackSpace = false;
}
catch (BadLocationException ble) {
}
}
}
{
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
if (kev.getKeyCode() != KeyEvent.VK_ESCAPE) {
try {
initKeyboard();
doc.remove(start, end-start);
shouldIBackSpace = false;
}
catch (BadLocationException ble) {
}
}
}
}
processTibetanChar(kev.getKeyChar(), shouldIBackSpace);
}
/** @see #processTibetan(KeyEvent) */
private void processTibetanChar(char c, boolean shouldIBackSpace) {
// Have we modified the status bar?
boolean changedStatus = false;
@ -1371,9 +1382,29 @@ public void paste(int offset) {
changedStatus = true;
updateStatus("You typed a non-vowel, Tibetan character.");
} else {
isTopHypothesis = false;
changedStatus = true;
updateStatus("invalid input, I think");
if (TibetanMachineWeb.hasInputPrefix(s)) {
isTopHypothesis = false;
changedStatus = true;
updateStatus("incomplete input (like the \"S\" in Extended Wylie's \"Sh\")");
} else {
// FIXME: ring a bell here so the user knows what's up.
initKeyboard();
// The recursive call to
// processTibetanChar will update the
// status bar, so set this to true:
changedStatus = true;
// This status message is bound to get
// overridden, but this is what we
// would say if we had a queue of
// messages or something like Emacs's
// M-x view-lossage to see a history
// of status messages:
appendStatus(" (because you typed something invalid [2nd way])");
processTibetanChar(c, false);
}
}
}
} else { //there is already a character in charList
@ -1454,7 +1485,7 @@ public void paste(int offset) {
putVowel(TibetanMachineWeb.A_VOWEL);
initKeyboard();
changedStatus = true;
appendStatus("we put a vowel");
appendStatus(" (because we put a vowel)");
break key_block;
}
@ -1470,9 +1501,34 @@ public void paste(int offset) {
updateStatus("you didn't type a 'char'; voodoo no. 5 ensues");
}
}
} else { //top char is just a guess! just keep it in holdCurrent
changedStatus = true;
updateStatus("top char is just a guess! just keep it in holdCurrent");
} else {
// top char is just a guess! Just keep c
// in holdCurrent if it may become valid
// input, or reset if we've no hope.
if (TibetanMachineWeb.hasInputPrefix(s)) {
isTopHypothesis = false;
changedStatus = true;
updateStatus("incomplete input (like the \"S\" in Extended Wylie's \"Sh\")");
} else {
// FIXME: ring a bell here so the user knows what's up.
initKeyboard();
// The recursive call to
// processTibetanChar will update the
// status bar, so set this to true:
changedStatus = true;
// This status message is bound to get
// overridden, but this is what we
// would say if we had a queue of
// messages or something like Emacs's
// M-x view-lossage to see a history
// of status messages:
appendStatus(" (because you typed something invalid [2nd way])");
processTibetanChar(c, false);
}
}
}
}

View File

@ -23,6 +23,8 @@ import java.io.*;
import java.lang.*;
import java.net.URL;
import org.thdl.util.Trie;
/**
* An alternate (non-Extended Wylie) keyboard input
* method. A keyboard URL is passed to its constructor. This URL
@ -43,6 +45,11 @@ import java.net.URL;
* @version 1.0
*/
public class TibetanKeyboard {
/** This addresses bug 624133, "Input freezes after impossible
character". We store all valid input sequences here. */
private Trie validInputSequences;
private boolean hasDisambiguatingKey;
private char disambiguatingKey;
private boolean hasSanskritStackingKey;
@ -68,6 +75,9 @@ public class TibetanKeyboard {
public class InvalidKeyboardException extends Exception {
}
/** Do not call this. */
private TibetanKeyboard() { }
/**
* Opens the URL specified by the parameter,
* and tries to construct a keyboard from it. If the file is
@ -89,6 +99,7 @@ public class TibetanKeyboard {
charMap = new HashMap();
vowelMap = new HashMap();
puncMap = new HashMap();
validInputSequences = new Trie();
command = NO_COMMAND;
@ -176,6 +187,7 @@ public class TibetanKeyboard {
break;
charMap.put(right, left);
validInputSequences.put(right, left);
break;
case VOWELS:
@ -186,6 +198,7 @@ public class TibetanKeyboard {
break;
vowelMap.put(right, left);
validInputSequences.put(right, left);
break;
case PUNCTUATION:
@ -196,6 +209,7 @@ public class TibetanKeyboard {
break;
puncMap.put(right, left);
validInputSequences.put(right, left);
break;
}
}
@ -298,14 +312,14 @@ public class TibetanKeyboard {
}
/**
* Decides whether or not a string is a character in this keyboard.
* Decides whether or not a string is a character (as opposed to a
* vowel or punctuation) in this keyboard.
* @return true if the parameter is a character
* in this keyboard. This method checks to see
* if the passed string has been mapped to a
* Wylie character - if not, then it returns false.
*
* @param s the possible character
*/
* @param s the possible character */
public boolean isChar(String s) {
if (charMap.containsKey(s))
return true;
@ -388,4 +402,15 @@ public class TibetanKeyboard {
return (String)vowelMap.get(s);
}
/** This addresses bug 624133, "Input freezes after impossible
* character". Returns true iff s is a proper prefix of some
* legal input for this keyboard. In the extended Wylie
* keyboard, hasInputPrefix("S") is true because "Sh" is legal
* input. hasInputPrefix("Sh") is false because though "Sh" is
* legal input, ("Sh" + y) is not valid input for any non-empty
* String y. */
public boolean hasInputPrefix(String s) {
return validInputSequences.hasPrefix(s);
}
}

View File

@ -28,6 +28,7 @@ import javax.swing.text.*;
import java.awt.font.*;
import org.thdl.util.ThdlDebug;
import org.thdl.util.Trie;
/**
* Interfaces between Extended Wylie and the TibetanMachineWeb fonts.
@ -37,6 +38,17 @@ import org.thdl.util.ThdlDebug;
* @version 1.0
*/
public class TibetanMachineWeb {
/** This addresses bug 624133, "Input freezes after impossible
* character". The input sequences that are valid in Extended
* Wylie. For example, "Sh" will be in this container, but "S"
* will not be. */
private static Trie validInputSequences = new Trie();
/** needed because a Trie cannot have a null value associated with
* a key */
private final static String anyOldObjectWillDo
= "this placeholder is useful for debugging; we need a nonnull Object anyway";
private static boolean hasReadData = false;
private static TibetanKeyboard keyboard = null;
private static final String DEFAULT_KEYBOARD = "default_keyboard.ini";
@ -306,8 +318,11 @@ public class TibetanMachineWeb {
line = in.readLine();
charSet = new HashSet();
StringTokenizer st = new StringTokenizer(line,",");
while (st.hasMoreTokens())
charSet.add(st.nextToken());
while (st.hasMoreTokens()) {
String ntk;
charSet.add(ntk = st.nextToken());
validInputSequences.put(ntk, anyOldObjectWillDo);
}
}
else if (line.equalsIgnoreCase("<?Vowels?>")) {
isSanskrit = false;
@ -315,8 +330,11 @@ public class TibetanMachineWeb {
line = in.readLine();
vowelSet = new HashSet();
StringTokenizer st = new StringTokenizer(line,",");
while (st.hasMoreTokens())
vowelSet.add(st.nextToken());
while (st.hasMoreTokens()) {
String ntk;
vowelSet.add(ntk = st.nextToken());
validInputSequences.put(ntk, anyOldObjectWillDo);
}
}
else if (line.equalsIgnoreCase("<?Other?>")) {
isSanskrit = false;
@ -324,8 +342,11 @@ public class TibetanMachineWeb {
line = in.readLine();
puncSet = new HashSet();
StringTokenizer st = new StringTokenizer(line,",");
while (st.hasMoreTokens())
puncSet.add(st.nextToken());
while (st.hasMoreTokens()) {
String ntk;
puncSet.add(ntk = st.nextToken());
validInputSequences.put(ntk, anyOldObjectWillDo);
}
}
else if (line.equalsIgnoreCase("<?Input:Punctuation?>")
@ -445,8 +466,10 @@ public static boolean setKeyboard(TibetanKeyboard kb) {
isAChungConsonant = false;
hasAVowel = true;
aVowel = WYLIE_aVOWEL;
if (!vowelSet.contains(WYLIE_aVOWEL))
if (!vowelSet.contains(WYLIE_aVOWEL)) {
vowelSet.add(WYLIE_aVOWEL);
validInputSequences.put(WYLIE_aVOWEL, anyOldObjectWillDo);
}
}
else {
hasDisambiguatingKey = keyboard.hasDisambiguatingKey();
@ -702,6 +725,13 @@ public static String getWylieForChar(String s) {
return keyboard.getWylieForChar(s);
}
/**
* Returns the current keyboard, or, if the current keyboard is the
* Extended Wylie keyboard, null. */
public static TibetanKeyboard getKeyboard() {
return keyboard;
}
/**
* Converts punctuation to its Extended Wylie correspondence.
* This assumes that the passed string is punctuation
@ -927,6 +957,22 @@ public static String getWylieForGlyph(DuffCode dc) {
return wylieForGlyph(hashKey);
}
/** This addresses bug 624133, "Input freezes after impossible
* character". Returns true iff s is a proper prefix of some
* legal input for this keyboard. In the extended Wylie
* keyboard, hasInputPrefix("S") is true because "Sh" is legal
* input. hasInputPrefix("Sh") is false because though "Sh" is
* legal input, ("Sh" + y) is not valid input for any non-empty
* String y. */
public static boolean hasInputPrefix(String s) {
if (null != keyboard) {
return keyboard.hasInputPrefix(s);
} else {
return validInputSequences.hasPrefix(s);
}
}
/**
* Says whether or not this glyph involves a Sanskrit stack.
* @param font the font of a TibetanMachineWeb glyph

View File

@ -81,6 +81,8 @@ Contributor(s): ______________________________________.
package org.thdl.util;
import org.thdl.util.ThdlDebug;
/**
* A digital search trie for 7-bit ASCII text. The API is a subset of
* java.util.Hashtable. The key must be a 7-bit ASCII string. The
@ -102,6 +104,9 @@ public class Trie
m_Root = new Node();
}
private static final boolean caseInsensitive = false;
/**
* Puts an object into the trie for lookup.
*
@ -120,7 +125,10 @@ public class Trie
for (int i = 0; i < len; i++)
{
Node nextNode = node.m_nextChar[Character.toUpperCase(key.charAt(i))];
Node nextNode
= node.m_nextChar[(caseInsensitive
? Character.toUpperCase(key.charAt(i))
: key.charAt(i))];
if (nextNode != null)
{
@ -132,7 +140,9 @@ public class Trie
{
Node newNode = new Node();
node.m_nextChar[Character.toUpperCase(key.charAt(i))] = newNode;
node.m_nextChar[(caseInsensitive
? Character.toUpperCase(key.charAt(i))
: key.charAt(i))] = newNode;
node.m_isLeaf = false;
node = newNode;
}
@ -165,13 +175,17 @@ public class Trie
{
try
{
node = node.m_nextChar[Character.toUpperCase(key.charAt(i))];
node = node.m_nextChar[(caseInsensitive
? Character.toUpperCase(key.charAt(i))
: key.charAt(i))];
}
catch (ArrayIndexOutOfBoundsException e)
{
// the key is not 7-bit ASCII so we won't find it here
node = null;
ThdlDebug.noteIffyCode();
}
if (node == null)
@ -204,13 +218,17 @@ public class Trie
{
try
{
node = node.m_nextChar[Character.toUpperCase(key.charAt(i))];
node = node.m_nextChar[(caseInsensitive
? Character.toUpperCase(key.charAt(i))
: key.charAt(i))];
}
catch (ArrayIndexOutOfBoundsException e)
{
// the key is not 7-bit ASCII so we won't find it here
node = null;
ThdlDebug.noteIffyCode();
}
if (node == null)