Input methods

Input methods

Background

Event queue

AWT objects and events

Swing objects and events

AWT versus Swing

Listeners

Changing key values

This program uses a listener to change all keys to upper case

import java.awt.*;
import java.awt.event.*;

public class MapKey extends Frame {
 
   public static void main(String argv[]) {
	new MapKey().setVisible(true);
    }

    public MapKey() {
	TextField text = new TextField(20);
	add(text);	
	pack();

	text.addKeyListener(new ToUpper());
    }
}

class ToUpper implements KeyListener {
    
    public void keyTyped(KeyEvent e) {
	char ch = e.getKeyChar();
	System.out.println("Key typed was: " + ch);
	ch = Character.toUpperCase(ch);
	e.setKeyChar(ch);
	// empty
    }

    public void keyPressed(KeyEvent e) {
	//e.setModifiers(Event.SHIFT_MASK);
	char ch = e.getKeyChar();
	System.out.println("Key typed was: " + ch);
	ch = Character.toUpperCase(ch);
	e.setKeyChar(ch);
    }

    public void keyReleased(KeyEvent e) {
	// empty
    }
}

Discarding characters

The following program only accepts alphabetic characters

import java.awt.*;
import java.awt.event.*;

public class AlphaOnly extends Frame implements KeyListener {
 
   public static void main(String argv[]) {
	new AlphaOnly().setVisible(true);
    }

    public AlphaOnly() {
	TextField text = new TextField(20);
	add(text);	
	pack();

	text.addKeyListener(this);
    }
    
    public void keyTyped(KeyEvent e) {
	char ch = e.getKeyChar();
	System.out.println("Key typed: " + ch);
	if (! Character.isLetter(ch)) {
	    Toolkit.getDefaultToolkit().beep();

	    // we don't want it - stop it from getting 
	    // to the text field
	    e.consume();
	}
    }

    public void keyPressed(KeyEvent e) {
	char ch = e.getKeyChar();
	System.out.println("Key typed: " + ch);
	if (! Character.isLetter(ch)) {
	    Toolkit.getDefaultToolkit().beep();

	    // we don't want it - stop it from getting 
	    // to the text field
	    e.consume();
	}
	// empty
    }

    public void keyReleased(KeyEvent e) {
	// empty
    }
}

Attributed text

Terms

From Concepts in C/UNIX Internationalisation

Naive Unicode editor

The following text editor uses a very naive way of entering unicode text. It uses on-the-spot pre-editing



import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

/** A simple editor to enter Unicode text
 * Does NOT use the Java input methods.
 * Whenever the sequence \\uXXXX of 4 hex digits 
 * is seen, it is replaced with its Unicode value
 */

public class SimpleUnicodeEditor extends JFrame implements KeyListener {

    private static int NONE = 0;
    private static int ESCAPE = 1;
    private static int ESCAPE_U = 2;
    private static int FIRST = 3;
    private static int SECOND = 4;
    private static int THIRD = 5;

    private JTextArea text;
    private int state;
    private StringBuffer unicodeText = new StringBuffer();

    public static void main(String[] args) {
	SimpleUnicodeEditor editor = new SimpleUnicodeEditor();
	editor.pack();
	editor.setVisible(true);
    }
    

    public SimpleUnicodeEditor() {
	Container contentPane = getContentPane();
	text = new JTextArea(20,100);
	contentPane.setLayout(new BorderLayout());
	contentPane.add(text, BorderLayout.CENTER);

	text.addKeyListener(this);
    }

    public void keyPressed(KeyEvent e) {

    }
    public void keyReleased(KeyEvent e) {

    }
    public void keyTyped(KeyEvent e) {
	char ch = e.getKeyChar() ;
	switch (ch) {
	case '\\':
	    state = ESCAPE;
	    break;
	    
	case 'u':
	    if (state == ESCAPE) {
		state = ESCAPE_U;
	    } else {
		state = NONE;
	    }
	    break;

	default:
	    if (isHexDigit(ch)) {
		if (state == NONE) {
		    // nothing 
		} else
		    if ((state == ESCAPE_U)) {
			unicodeText.setLength(0);
			unicodeText.append(ch);
			state = FIRST;
		} else  
		    if (state == FIRST) {	
			unicodeText.append(ch);
			state = SECOND;
		} else 
		    if (state == SECOND) {
			unicodeText.append(ch);
			state = THIRD;
		} else 
		    if (state == THIRD) {
			unicodeText.append(ch);

			// we've now seen the 4th digit
			convertUnicode(); 
			state = NONE;
			// lose this 4th character
			e.consume();
		    }
	    } else {
		state = NONE;
	    }
	}
    }

    private boolean isHexDigit(char ch) {
	return (Character.digit(ch, 16) != -1);
    }

    private void convertUnicode() {
	char unicodeValue = 0;
	try {
	    unicodeValue = (char) Integer.valueOf(unicodeText.toString(), 16).intValue();
	} catch (NumberFormatException e) {
	    // nothing
	}
	String s = text.getText();
	int length = s.length();
	text.replaceRange("" + unicodeValue, length-5, length);
    }
} 

Problems with naive editor

Java Input Method Framework

Using the IMF

Method 1: don't change the application, let the user choose by hotkeys or system menu


import java.util.Locale;
import java.awt.im.spi.InputMethodDescriptor;
import javax.swing.*;
import java.awt.*;
import java.awt.im.InputContext;

public class TestInputMethod extends JFrame {

    public static void main(String[] args) {

	TestInputMethod test = new TestInputMethod();

	JTextArea text = new JTextArea(20, 200);
	Font font = new Font("Courier", Font.PLAIN, 24);
	text.setFont(font);

	test.getContentPane().add(text, BorderLayout.NORTH);

	test.pack();
	test.setVisible(true);
    }
}

While this is running, use the hot key or the system menu to get a selection of input methods

Using the IMF

Method 2: code the input method into the application


import java.util.Locale;
import java.awt.im.spi.InputMethodDescriptor;
import javax.swing.*;
import java.awt.*;
import java.awt.im.InputContext;

public class TestPigLatinInputMethod extends JFrame {

    public static void main(String[] args) {

	InputContext context = InputContext.getInstance();
	if (context.selectInputMethod(PigLatinInputMethodDescriptor.PIG_LATIN_LOCALE)) {
	    System.out.println("Pig latin available");
	} else {
	    System.out.println("Pig latin not available");
	}

	InputMethodDescriptor pigLatinDesc = new PigLatinInputMethodDescriptor();
	try {
	    Locale[] inputLocales = pigLatinDesc.getAvailableLocales();
	    for (int n = 0; n < inputLocales.length; n++) {
		System.out.println(inputLocales[n].toString());
	    }
	} catch (java.awt.AWTException e) {
	    e.printStackTrace();
	}

	TestPigLatinInputMethod test = new TestPigLatinInputMethod();

	JTextArea text = new JTextArea(20, 200);
	Font font = new Font("Courier", Font.PLAIN, 24);
	text.setFont(font);

	test.getContentPane().add(text, BorderLayout.NORTH);

	context = text.getInputContext();
	if (context.selectInputMethod(PigLatinInputMethodDescriptor.PIG_LATIN_LOCALE)) {
	    System.out.println("Pig latin set for JText");
	} else {
	    System.out.println("Pig latin not set for JText");
	}



	test.pack();
	test.setVisible(true);
    }
}

Event handling with input methods

Input styles

The IMF supports three styles:

Pig Latin

Locale for Pig Latin

Pig Latin input method

Input method descriptor

Pig Latin input method descriptor

import java.awt.Image;
import java.awt.im.spi.InputMethod;
import java.awt.im.spi.InputMethodDescriptor;
import java.util.Locale;

/**
 * Provides sufficient information about an input method
 * to enable selection and loading of that input method.
 * The input method itself is only loaded when it is actually used.
 */

public class PigLatinInputMethodDescriptor implements InputMethodDescriptor {

    public static Locale PIG_LATIN_LOCALE = new Locale("en", "", "x-pig-latin");

    public PigLatinInputMethodDescriptor() {
    }

    /**
     * @see java.awt.im.spi.InputMethodDescriptor#getAvailableLocales
     */
    public Locale[] getAvailableLocales() {
        Locale[] locales = {
	    PIG_LATIN_LOCALE
	};
        return locales;
    }
    
    /**
     * @see java.awt.im.spi.InputMethodDescriptor#hasDynamicLocaleList
     */
    public boolean hasDynamicLocaleList() {
        return false;
    }
    
    /**
     * @see java.awt.im.spi.InputMethodDescriptor#getInputMethodDisplayName
     */
    public synchronized String getInputMethodDisplayName(Locale inputLocale, Locale displayLanguage) {
        String localeName = null;
        if (inputLocale == PIG_LATIN_LOCALE) {
            localeName = "PigLatin English";
	} else if (localeName != null) {

            return "PigLatin English - " + localeName;
        } else {
            return "PigLatin English Method";
        }
	return null;
    }
    
    /**
     * @see java.awt.im.spi.InputMethodDescriptor#getInputMethodIcon
     */
    public Image getInputMethodIcon(Locale inputLocale) {
        return null;
    }
    
    /**
     * @see java.awt.im.spi.InputMethodDescriptor#getInputMethodClassName
     */
    public InputMethod createInputMethod() throws Exception {
	System.out.println("Creating pig latin input method");
	return new PigLatinInputMethod();
    }
}

Finding the input method

Input descriptor configuration file

Pig Latin Input Method


// required by InputMethod
import java.awt.im.spi.InputMethod;
import java.awt.Rectangle;
import java.awt.im.spi.InputMethodContext;
import java.util.Locale;
import java.awt.AWTEvent;

// required by implementation
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.awt.event.InputMethodEvent;
import java.awt.font.TextHitInfo;
import java.text.AttributedString;
import java.awt.im.InputMethodHighlight;

import java.awt.*;

public class PigLatinInputMethod implements InputMethod {

    private static Locale[] SUPPORTED_LOCALES = {
	PigLatinInputMethodDescriptor.PIG_LATIN_LOCALE
    };

    private Locale locale;
    private InputMethodContext inputMethodContext;
    private StringBuffer rawText = new StringBuffer();
    // private String convertedText;

    private static Window statusWindow;

    public void activate() {
	// Activates the input method for immediate input processing.
        // do nothing
    }

    public void deactivate(boolean isTemporary) {
	// Deactivates the input method.
    }

    public void dispatchEvent(AWTEvent event) {
	// Dispatches the event to the input method.

        if (event.getID() == KeyEvent.KEY_TYPED) {
            KeyEvent e = (KeyEvent) event;
            if (handleCharacter(e.getKeyChar())) {
                e.consume();
            }
        }
        // Also handle selection by mouse here
    }

    public void dispose() {
	// Disposes of the input method and releases the resources used by it.
	System.out.println("Pig latin di-XXX");
    }

    public void endComposition() {
	// Ends any input composition that may currently be going on in this context.
	String convertedText = convert(rawText);
	if (convertedText != null) {
	    commit(convertedText);
	}
    }

    public Object getControlObject() {
	// Returns a control object from this input method, or null.
        // not supported yet
	System.out.println("Pig latin gc-XXX");
	return null;
    }

    public Locale getLocale() {
	// Returns the current input locale.
	return locale;
    }

    public void hideWindows() {
	// Closes or hides all windows opened by this input method instance or its class.
    }

    public boolean isCompositionEnabled() {
        //  Determines whether this input method is enabled.
        // always enabled
        return true;
    }

    public void notifyClientWindowChange(Rectangle bounds) {
	// Notifies this input method of changes in the client window location or state.
        // not supported yet
	System.out.println("Pig latin nw-XXX");
        throw new UnsupportedOperationException();
    }

    public void reconvert() {
	// Starts the reconversion operation.
        // not supported yet
	System.out.println("Pig latin re-XXX");
        throw new UnsupportedOperationException();
    }

    public void removeNotify() {
	// Notifies the input method that a client component has been removed from its containment hierarchy, or that input method support has been disabled for the component.
        // not supported yet
	System.out.println("Pig latin rn-XXX");
        throw new UnsupportedOperationException();
    }

    public void setCharacterSubsets(Character.Subset[] subsets) {
	// Sets the subsets of the Unicode character set that this input method is allowed to input.
        // not supported yet
	System.out.println("Pig latin sc-XXX");
        return;
	// throw new UnsupportedOperationException();
    }

    public void setCompositionEnabled(boolean enable) {
	// Enables or disables this input method for composition, depending on the value of the parameter enable.
        // not supported yet
	System.out.println("Pig latin sce-XXX");
        throw new UnsupportedOperationException();
    }

    public void setInputMethodContext(InputMethodContext context) {
	// Sets the input method context, which is used to dispatch input method events to the client component and to request information from the client component.

	inputMethodContext = context;
	System.out.println("Input context set " + context);
	
	/*
        // if (statusWindow == null) {
            statusWindow = context.createInputMethodWindow("Simp. Chinese Pinyin", false);
            Label label = new Label("Pig Latin locale");
            label.setBackground(Color.white);
            statusWindow.add(label);
            // updateStatusWindow(locale);
            label.setSize(200, 50);
            statusWindow.pack();
            Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
            statusWindow.setLocation(d.width - statusWindow.getWidth(),
                                     d.height - statusWindow.getHeight());
	    // }
	    */
    }

    public boolean setLocale(Locale locale) {
	// Attempts to set the input locale.

	for (int i = 0; i < SUPPORTED_LOCALES.length; i++) {
            if (locale.equals(SUPPORTED_LOCALES[i])) {
                this.locale = locale;
		/*
		if (statusWindow != null) {
                    updateStatusWindow(locale);
                }
		*/
		System.out.println("Locale set");
                return true;
            }
        }
	System.out.println("Locale not set");
        return false;
    }

   /**
     * Attempts to handle a typed character.
     * @return whether the character was handled
     */
    private boolean handleCharacter(char ch) {
	System.out.println("handling char \"" + ch + "\"");
	if (isEndChar(ch)) {
	    String convertedText = convert(rawText);
	    if (convertedText != null) {
		commit(convertedText + ch);
		return true;
	    }
	    // let the end char be passed on
	    return false;
	}

	if (ch == '\b') {
	    // backspace - delete char
	    if (rawText.length() != 0) {
		rawText.setLength(rawText.length() - 1);
		sendRawText();
		return true;
	    }
	} else if (Character.isLetter(ch)) {
	    rawText.append(ch);
	    sendRawText();
	    return true;
	}

        return false;
    }

    private boolean isEndChar(char ch) {
	if (Character.isWhitespace(ch)) {
	    return true;
	} else {
	    // Unicode category Po, Terminal punctuation: !,. etc
	    if (Character.getType(ch) == Character.OTHER_PUNCTUATION) {
		return true;
	    }
	}
	return false;
    }


    private boolean isVowel(char ch) {
	// we are in English - vowels are easy!
	switch (Character.toLowerCase(ch)) {
	case 'a':
	case 'e':
	case 'i':
	case 'o':
	case 'u':
	    return true;
	default:
	    return false;
	}
    }

    private String convert(StringBuffer rawText) {
	if (rawText.length() == 0) {
	    return null;
	}

        InputMethodHighlight highlight;
	String convertedText = null;

	// if the rawText begins with a vowel, add "yay" to the end
	if (isVowel(rawText.charAt(0))) {
	    convertedText = rawText + "yay";
	} else {
	    // move first consonant group to end and add "ay"
	    int n = 0;
	    while ( ! isVowel(rawText.charAt(n))) {
		n++;
	    }
	    convertedText = rawText.substring(n, rawText.length()) +
		rawText.substring(0, n) +
		"ay";
	}
	return convertedText;
    }

    private void commit(String text) {
	sendConvertedText(text);
	rawText.setLength(0);
    }

    private void sendConvertedText(String convertedText) {
        InputMethodHighlight highlight;

	highlight = InputMethodHighlight.SELECTED_CONVERTED_TEXT_HIGHLIGHT;
        AttributedString as = new AttributedString(convertedText);
	as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, highlight);
	
        inputMethodContext.dispatchInputMethodEvent(
				    InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
				    as.getIterator(),
				    convertedText.length(),
				    TextHitInfo.leading(convertedText.length()),
				    null);

    }

    private void sendRawText() {
	String text = rawText.toString();
        InputMethodHighlight highlight;
	AttributedString as = new AttributedString(text);

	highlight = InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT;
	as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT, highlight);
	
        inputMethodContext.dispatchInputMethodEvent(
				   InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
				   as.getIterator(),
				   0,
				   null,
				   null);
   
    }


}

Presenting alternative choices

Presenting choices


Jan Newmarch (http://jan.newmarch.name)
jan@newmarch.name
Last modified: Mon Aug 14 11:10:05 EST 2006
Copyright ©Jan Newmarch
Copyright © Jan Newmarch, Monash University, 2007
Creative Commons License This work is licensed under a Creative Commons License
The moral right of Jan Newmarch to be identified as the author of this page has been asserted.