home *** CD-ROM | disk | FTP | other *** search
/ Internet News 1999 October / INEWS_10_CD.ISO / pc / jdk / jdk1.2.2 / docs / guide / intl / sample / ActiveClient.java < prev    next >
Encoding:
Java Source  |  1999-09-19  |  13.3 KB  |  408 lines

  1. /*
  2.  * @(#)ActiveClient.java    1.3 98/06/09
  3.  * 
  4.  * Copyright 1997-1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  */
  8.  
  9. import java.awt.Component;
  10. import java.awt.Point;
  11. import java.awt.Rectangle;
  12. import java.awt.event.InputMethodEvent;
  13. import java.awt.event.InputMethodListener;
  14. import java.awt.im.InputMethodRequests;
  15. import java.awt.font.TextAttribute;
  16. import java.awt.font.TextHitInfo;
  17. import java.awt.font.TextLayout;
  18. import java.text.AttributedCharacterIterator;
  19. import java.text.AttributedCharacterIterator.Attribute;
  20. import java.text.AttributedString;
  21. import java.util.HashSet;
  22. import java.util.Map;
  23. import java.util.Set;
  24.  
  25.  
  26. /**
  27.  * Implements on-the-spot text input, the most closely integrated form
  28.  * of interaction with input methods. This class is an active client of
  29.  * the input method framework, that is, actively uses its APIs to accomplish
  30.  * integration.
  31.  * <p>
  32.  * This class directly implements the two client interfaces of the input method
  33.  * framework, InputMethodListener and InputMethodRequests.
  34.  * This is not required. Especially in cases where the public
  35.  * interface matters (such as in a class library), it may be more
  36.  * appropriate to hide the implementations of these two interfaces
  37.  * in separate classes.
  38.  * 
  39.  */
  40.  
  41. public class ActiveClient extends LWTextComponent
  42.         implements InputMethodListener, InputMethodRequests {
  43.  
  44.     // the composed text received from the input method,
  45.     // with font information added so we can easily create a TextLayout
  46.     private AttributedCharacterIterator composedText = null;
  47.  
  48.     // the caret received from the input method; relative  to composed text
  49.     private TextHitInfo caret = null;
  50.  
  51.     /**
  52.      * Constructs an ActiveClient. Input methods are always enabled for
  53.      * ActiveClient instances.
  54.      * @param name the component name to be displayed above the text
  55.      */
  56.     public ActiveClient(String name) {
  57.         super(name, true);
  58.         enableInputMethods(true);
  59.     addInputMethodListener(this);
  60.     }
  61.     
  62.     /**
  63.      * Implements getInputMethodRequests for ActiveClient by returning "this".
  64.      * @return "this"
  65.      */
  66.     public InputMethodRequests getInputMethodRequests() {
  67.         return this;
  68.     }
  69.  
  70.     /**
  71.      * Returns the text that the user has entered.
  72.      * This override returns the concatenation of committed text
  73.      * and composed text.
  74.      */
  75.     public AttributedCharacterIterator getDisplayText() {
  76.         if (composedText == null) {
  77.             return super.getDisplayText();
  78.         } else {
  79.             // We don't want to copy all the text and attribute data here.
  80.             // Instead, we return a CompositeIterator which iterates over
  81.             // the concatenation of two iterators.
  82.             return new CompositeIterator(super.getDisplayText(), composedText);
  83.         }
  84.     }
  85.     
  86.     /**
  87.      * Returns a text hit info indicating the current caret (insertion point).
  88.      * This override returns the caret provided by the input method while
  89.      * there is composed text; otherwise it returns a caret at the end
  90.      * of the committed text. The caret provided by the input method may be null.
  91.      */
  92.     public TextHitInfo getCaret() {
  93.         if (composedText == null) {
  94.             return super.getCaret();
  95.         } else if (caret == null) {
  96.             return null;
  97.         } else {
  98.             // the caret provided by the input method is relative
  99.             // to the composed text, so translate it to the entire text
  100.             return caret.getOffsetHit(getCommittedTextLength());
  101.         }
  102.     }
  103.  
  104.     // InputMethodListener implementation
  105.  
  106.     // constant attribute set for use when copying composed text
  107.     private static final Attribute[] IM_ATTRIBUTES =
  108.             { TextAttribute.INPUT_METHOD_HIGHLIGHT };
  109.  
  110.     /**
  111.      * Handles changes to the text entered through an input method.
  112.      * Committed text contained in the event is appended to the
  113.      * committed text of the text component. Composed text contained
  114.      * in the event replaces any existing composed text in the text
  115.      * component.
  116.      * The caret defined in the event is saved and will
  117.      * be returned by getCaret if there is composed text. The
  118.      * component is redrawn.
  119.      * <p>
  120.      * In this simple component, we only
  121.      * keep input method highlight attributes. Smarter components may want to
  122.      * keep language, reading, input method segment, and other
  123.      * attributes as well.
  124.      */
  125.     public void inputMethodTextChanged(InputMethodEvent event) {
  126.         int committedCharacterCount = event.getCommittedCharacterCount();
  127.         AttributedCharacterIterator text = event.getText();
  128.         composedText = null;
  129.         char c;
  130.         if (text != null) {
  131.             // copy the committed text
  132.             int toCopy = committedCharacterCount;
  133.             c = text.first();
  134.             while (toCopy-- > 0) {
  135.                 insertCharacter(c);
  136.                 c = text.next();
  137.             }
  138.  
  139.             // copy the composed text
  140.             if (text.getEndIndex() - (text.getBeginIndex() + committedCharacterCount) > 0) {
  141.                 AttributedString composedTextString;
  142.                 composedTextString = new AttributedString(text,
  143.                         text.getBeginIndex() + committedCharacterCount, // skip over committed text
  144.                         text.getEndIndex(), IM_ATTRIBUTES);
  145.                 // add font information because TextLayout requires it
  146.                 composedTextString.addAttribute(TextAttribute.FONT, getFont());
  147.                 composedText = composedTextString.getIterator();
  148.             }
  149.         }
  150.     event.consume();
  151.     invalidateTextLayout();
  152.     caret = event.getCaret();
  153.     repaint();
  154.     }
  155.  
  156.     /**
  157.      * Handles changes to the caret within composed text.
  158.      * The caret defined in the event is saved and will
  159.      * be returned by getCaret if there is composed text. The
  160.      * component is redrawn.
  161.      */
  162.     public void caretPositionChanged(InputMethodEvent event) {
  163.     caret = event.getCaret();
  164.     event.consume();
  165.     repaint();
  166.     }
  167.  
  168.     // InputMethodRequests implementation
  169.     // Note that getCommittedTextLength is already implemented in LWTextComponent
  170.  
  171.     /**
  172.      * Gets the location of a specified offset in the current composed text,
  173.      * or of the selection in committed text.
  174.      */
  175.     public Rectangle getTextLocation(TextHitInfo offset) {
  176.  
  177.         // determine the text location in component coordinates
  178.         Rectangle rectangle;
  179.         if (offset == null) {
  180.             // no composed text: return caret for committed text
  181.             rectangle = getCaretRectangle();
  182.         } else {
  183.             // composed text: return caret within composed text
  184.             TextHitInfo globalOffset = offset.getOffsetHit(getCommittedTextLength());
  185.             rectangle = getCaretRectangle(globalOffset);
  186.         }
  187.  
  188.         // translate to screen coordinates
  189.         Point location = getLocationOnScreen();
  190.         rectangle.translate(location.x, location.y);
  191.  
  192.     return rectangle;
  193.     }
  194.  
  195.     /**
  196.      * Gets the offset within the composed text for the specified absolute x
  197.      * and y coordinates on the screen.
  198.      */
  199.     public TextHitInfo getLocationOffset(int x, int y) {
  200.  
  201.         // translate from screen coordinates to coordinates in the text layout
  202.         Point location = getLocationOnScreen();
  203.         Point textOrigin = getTextOrigin();
  204.         x -= location.x + textOrigin.x;
  205.         y -= location.y + textOrigin.y;
  206.         
  207.         // TextLayout maps locations far outside its bounds to locations within.
  208.         // To avoid false hits, we use it only if it actually contains the location.
  209.         // We also have to translate the TextHitInfo to be relative to composed text.
  210.         TextLayout textLayout = getTextLayout();
  211.         if (textLayout != null &&
  212.                 textLayout.getBounds().contains(x, y)) {
  213.             return textLayout.hitTestChar(x, y).getOffsetHit(-getCommittedTextLength());
  214.         } else {
  215.             return null;
  216.         }
  217.     }
  218.  
  219.     /**
  220.      * Gets the offset of the insert position in the committed text contained
  221.      * in the text editing component. In this simple component, that's always
  222.      * at the end of the committed text.
  223.      */
  224.     public int getInsertPositionOffset() {
  225.         return getCommittedTextLength();
  226.     }
  227.  
  228.     /**
  229.      * Gets an iterator providing access to the entire text and attributes
  230.      * contained in the text editing component except for uncommitted
  231.      * text.
  232.      */
  233.     public AttributedCharacterIterator getCommittedText(int beginIndex,
  234.             int endIndex, Attribute[] attributes) {
  235.         return getCommittedText(beginIndex, endIndex);
  236.     }
  237.  
  238.     /**
  239.      * Returns null to indicate that the "Undo Commit" feature is not supported
  240.      * by this simple text component.
  241.      */
  242.     public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
  243.         return null;
  244.     }
  245.  
  246.     private static final AttributedCharacterIterator EMPTY_TEXT =
  247.             (new AttributedString("")).getIterator();
  248.  
  249.     /**
  250.      * Gets the currently selected text from the text editing component.
  251.      * Since this simple text component doesn't support selections, this is
  252.      * always an iterator over empty text.
  253.      */
  254.     public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
  255.         return EMPTY_TEXT;
  256.     }
  257. }
  258.  
  259. /**
  260.  * Iterates over the combined text of two AttributedCharacterIterators.
  261.  * Assumes that no annotation spans the two iterators.
  262.  */
  263.  
  264. class CompositeIterator implements AttributedCharacterIterator {
  265.  
  266.     AttributedCharacterIterator iterator1;
  267.     AttributedCharacterIterator iterator2;
  268.     int begin1, end1;
  269.     int begin2, end2;
  270.     int endIndex;
  271.     int currentIndex;
  272.     AttributedCharacterIterator currentIterator;
  273.     int currentIteratorDelta;
  274.     
  275.     /**
  276.      * Constructs a CompositeIterator that iterates over the concatenation
  277.      * of iterator1 and iterator2.
  278.      * @param iterator1, iterator2 the base iterators that this composite iterator concatenates
  279.      */
  280.     CompositeIterator(AttributedCharacterIterator iterator1, AttributedCharacterIterator iterator2) {
  281.         this.iterator1 = iterator1;
  282.         this.iterator2 = iterator2;
  283.         begin1 = iterator1.getBeginIndex();
  284.         end1 = iterator1.getEndIndex();
  285.         begin2 = iterator2.getBeginIndex();
  286.         end2 = iterator2.getEndIndex();
  287.         endIndex = (end1 - begin1) + (end2 - begin2);
  288.         internalSetIndex(0);
  289.     }
  290.     
  291.     // CharacterIterator implementation
  292.     
  293.     public char first() {
  294.         return internalSetIndex(0);
  295.     }
  296.     
  297.     public char last() {
  298.         if (endIndex == 0) {
  299.             return internalSetIndex(endIndex);
  300.         } else {
  301.             return internalSetIndex(endIndex - 1);
  302.         }
  303.     }
  304.     
  305.     public char next() {
  306.         if (currentIndex < endIndex) {
  307.             return internalSetIndex(currentIndex + 1);
  308.         } else {
  309.             return DONE;
  310.         }
  311.     }
  312.     
  313.     public char previous() {
  314.         if (currentIndex > 0) {
  315.             return internalSetIndex(currentIndex - 1);
  316.         } else {
  317.             return DONE;
  318.         }
  319.     }
  320.     
  321.     public char current() {
  322.         return currentIterator.setIndex(currentIndex + currentIteratorDelta);
  323.     }
  324.     
  325.     public char setIndex(int position) {
  326.         if (position < 0 || position > endIndex) {
  327.             throw new IllegalArgumentException("invalid index");
  328.         }
  329.         return internalSetIndex(position);
  330.     }
  331.     
  332.     private char internalSetIndex(int position) {
  333.         currentIndex = position;
  334.         if (currentIndex < end1 - begin1) {
  335.             currentIterator = iterator1;
  336.             currentIteratorDelta = begin1;
  337.         } else {
  338.             currentIterator = iterator2;
  339.             currentIteratorDelta = begin2 - (end1 - begin1);
  340.         }
  341.         return currentIterator.setIndex(currentIndex + currentIteratorDelta);
  342.     }
  343.     
  344.     public int getBeginIndex() {
  345.         return 0;
  346.     }
  347.     
  348.     public int getEndIndex() {
  349.         return endIndex;
  350.     }
  351.     
  352.     public int getIndex() {
  353.         return currentIndex;
  354.     }
  355.     
  356.     // AttributedCharacterIterator implementation
  357.     
  358.     public int getRunStart() {
  359.         return currentIterator.getRunStart() - currentIteratorDelta;
  360.     }
  361.     
  362.     public int getRunLimit() {
  363.         return currentIterator.getRunLimit() - currentIteratorDelta;
  364.     }
  365.     
  366.     public int getRunStart(Attribute attribute) {
  367.         return currentIterator.getRunStart(attribute) - currentIteratorDelta;
  368.     }
  369.     
  370.     public int getRunLimit(Attribute attribute) {
  371.         return currentIterator.getRunLimit(attribute) - currentIteratorDelta;
  372.     }
  373.  
  374.     public int getRunStart(Set attributes) {
  375.         return currentIterator.getRunStart(attributes) - currentIteratorDelta;
  376.     }
  377.     
  378.     public int getRunLimit(Set attributes) {
  379.         return currentIterator.getRunLimit(attributes) - currentIteratorDelta;
  380.     }
  381.     
  382.     public Map getAttributes() {
  383.         return currentIterator.getAttributes();
  384.     }
  385.     
  386.     public Set getAllAttributeKeys() {
  387.         Set keys = new HashSet(iterator1.getAllAttributeKeys());
  388.         keys.addAll(iterator2.getAllAttributeKeys());
  389.         return keys;
  390.     }
  391.     
  392.     public Object getAttribute(Attribute attribute) {
  393.         return currentIterator.getAttribute(attribute);
  394.     }
  395.     
  396.     // Object overrides
  397.  
  398.     public Object clone() {
  399.         try {
  400.             CompositeIterator other = (CompositeIterator) super.clone();
  401.             return other;
  402.         } catch (CloneNotSupportedException e) {
  403.             throw new InternalError();
  404.         }
  405.     }
  406. }
  407.  
  408.