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

  1. /*
  2.  * @(#)LWTextComponent.java    1.4 98/09/03
  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.Color;
  10. import java.awt.Component;
  11. import java.awt.Dimension;
  12. import java.awt.FontMetrics;
  13. import java.awt.Graphics;
  14. import java.awt.Graphics2D;
  15. import java.awt.Point;
  16. import java.awt.Rectangle;
  17. import java.awt.event.FocusEvent;
  18. import java.awt.event.FocusListener;
  19. import java.awt.event.KeyEvent;
  20. import java.awt.event.KeyListener;
  21. import java.awt.event.MouseAdapter;
  22. import java.awt.event.MouseEvent;
  23. import java.awt.font.FontRenderContext;
  24. import java.awt.font.TextAttribute;
  25. import java.awt.font.TextHitInfo;
  26. import java.awt.font.TextLayout;
  27. import java.text.AttributedCharacterIterator;
  28. import java.text.AttributedString;
  29.  
  30. /**
  31.  * Implements a very simple lightweight text editing component.
  32.  * It lets the user edit a single line of text using the keyboard.
  33.  * The only special character that it knows about is backspace; all other
  34.  * characters are added to the text. Selections are not supported, so
  35.  * there's only a simple caret indicating the insertion point.
  36.  * The component also displays a component name above the editable
  37.  * text line, and draws a black frame whose thickness indicates whether
  38.  * the component has the focus.
  39.  * <p>
  40.  * The component can be initialized to enable or disable input
  41.  * through input methods. Other than that, it doesn't do anything
  42.  * to support input methods, so input method interaction (if any)
  43.  * will occur in a separate composition window. However, the
  44.  * component is designed to be easily extended with full input
  45.  * method support. It distinguishes between "displayed text" and
  46.  * "committed text" - here, they're the same, but in a subclass
  47.  * that supports on-the-spot input, the displayed text would be the
  48.  * combination of committed text and composed text. The component
  49.  * also uses TextLayout to draw the text, so it can be easily
  50.  * extended to handle input method highlights.
  51.  */
  52.  
  53. public class LWTextComponent extends Component implements KeyListener, FocusListener {
  54.  
  55.     // whether the component currently has the focus
  56.     private transient boolean haveFocus;
  57.     
  58.     // the component name that's displayed at the top of the component's area
  59.     private String name;
  60.     
  61.     // The text the user has entered. The term "committed text"
  62.     // follows the usage in the input method framework.
  63.     private StringBuffer committedText = new StringBuffer();
  64.  
  65.     // We use a text layout for drawing and measuring. Since they
  66.     // are expensive to create, we cache it and invalidate it when
  67.     // the text is modified.
  68.     private transient TextLayout textLayout = null;
  69.     private transient boolean validTextLayout = false;
  70.  
  71.     // constants that determine where the text is drawn
  72.     private final static int TEXT_ORIGIN_X = 10;
  73.     private final static int NAME_ORIGIN_Y = 20;
  74.     private final static int TEXT_ORIGIN_Y = 40;
  75.  
  76.     /**
  77.      * Constructs a LWTextComponent.
  78.      * @param name the component name to be displayed above the text
  79.      * @param enableInputMethods whether to enable input methods for this component
  80.      */
  81.     public LWTextComponent(String name, boolean enableInputMethods) {
  82.         super();
  83.         this.name = name;
  84.     setSize(300, 80);
  85.     // we have to set the foreground color because otherwise
  86.     // text may not display correctly when we use an input
  87.     // method highlight that swaps background and foreground
  88.     // colors
  89.     setForeground(Color.black);
  90.     setBackground(Color.white);
  91.     setVisible(true);
  92.     setEnabled(true);
  93.     addKeyListener(this);
  94.     addFocusListener(this);
  95.     addMouseListener(new MouseFocusListener(this));
  96.     enableInputMethods(enableInputMethods);
  97.     }
  98.     
  99.     /**
  100.      * Draws the component. The following items are drawn:
  101.      * <ul>
  102.      * <li>the component's background
  103.      * <li>a frame, thicker if the component has the focus
  104.      * <li>the component name
  105.      * <li>the text that the user has entered
  106.      * <li>the caret, if the component has the focus
  107.      * </ul>
  108.      */
  109.     public synchronized void paint(Graphics g) {
  110.  
  111.         // draw the background
  112.     g.setColor(getBackground());
  113.     Dimension size = getSize();
  114.     g.fillRect(0, 0, size.width, size.height);
  115.     
  116.     // draw the frame, thicker if the component has the focus
  117.     g.setColor(Color.black);
  118.     g.drawRect(0, 0, size.width - 1, size.height - 1);
  119.     if (haveFocus) {
  120.         g.drawRect(1, 1, size.width - 3, size.height - 3);
  121.     }
  122.  
  123.         // draw the component name
  124.     g.setColor(getForeground());
  125.     g.drawString(name, TEXT_ORIGIN_X, NAME_ORIGIN_Y);
  126.  
  127.         // draw the text that the user has entered
  128.         TextLayout textLayout = getTextLayout();
  129.         if (textLayout != null) {
  130.             textLayout.draw((Graphics2D) g, TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
  131.         }
  132.         
  133.         // draw the caret, if the component has the focus
  134.     Rectangle rectangle = getCaretRectangle();
  135.     if (haveFocus && rectangle != null) {
  136.         g.setXORMode(getBackground());
  137.             g.fillRect(rectangle.x, rectangle.y, 1, rectangle.height);
  138.         g.setPaintMode();
  139.         }
  140.     }
  141.     
  142.     /**
  143.      * Returns the text that the user has entered and committed.
  144.      * Since this component does not support on-the-spot input, there's no
  145.      * composed text, so all text that has been entered is committed.
  146.      * @return an AttributedCharacterIterator for the text that the user has entered and committed
  147.      */
  148.     public AttributedCharacterIterator getCommittedText() {
  149.         AttributedString string = new AttributedString(committedText.toString());
  150.         return string.getIterator();
  151.     }
  152.     
  153.     /**
  154.      * Returns a subrange of the text that the user has entered and committed.
  155.      * Since this component does not support on-the-spot input, there's no
  156.      * composed text, so all text that has been entered is committed.
  157.      * @param beginIndex the index of the first character of the subrange
  158.      * @param endIndex the index of the character following the subrange
  159.      * @return an AttributedCharacterIterator for a subrange of the text that the user has entered and committed
  160.      */
  161.     public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex) {
  162.         AttributedString string = new AttributedString(committedText.toString());
  163.         return string.getIterator(null, beginIndex, endIndex);
  164.     }
  165.     
  166.     /**
  167.      * Returns the length of the text that the user has entered and committed.
  168.      * Since this component does not support on-the-spot input, there's no
  169.      * composed text, so all text that has been entered is committed.
  170.      * @return the length of the text that the user has entered and committed
  171.      */
  172.     public int getCommittedTextLength() {
  173.         return committedText.length();
  174.     }
  175.     
  176.     /**
  177.      * Returns the text that the user has entered.
  178.      * As TextLayout requires a font to be defined for each character,
  179.      * the default font is applied to the entire text.
  180.      * A subclass that supports on-the-spot input must override this
  181.      * method to include composed text.
  182.      * @return the text that the user has entered
  183.      */
  184.     public AttributedCharacterIterator getDisplayText() {
  185.         AttributedString string = new AttributedString(committedText.toString());
  186.         if (committedText.length() > 0) {
  187.             string.addAttribute(TextAttribute.FONT, getFont());
  188.         }
  189.         return string.getIterator();
  190.     }
  191.     
  192.     /**
  193.      * Returns a text layout for the text that the user has entered.
  194.      * This text layout is created from the text returned by getDisplayText.
  195.      * The text layout is cached until invalidateTextLayout is called.
  196.      * @see #invalidateTextLayout
  197.      * @see #getDisplayText
  198.      * @return a text layout for the text that the user has entered, or null
  199.      */
  200.     public synchronized TextLayout getTextLayout() {
  201.         if (!validTextLayout) {
  202.             textLayout = null;
  203.             AttributedCharacterIterator text = getDisplayText();
  204.             if (text.getEndIndex() > text.getBeginIndex()) {
  205.                 FontRenderContext context = ((Graphics2D) getGraphics()).getFontRenderContext();
  206.                 textLayout = new TextLayout(text, context);
  207.             }
  208.         }
  209.         validTextLayout = true;
  210.         return textLayout;
  211.     }
  212.     
  213.     /**
  214.      * Invalidates the cached text layout. This must be called whenever
  215.      * the component's text is modified.
  216.      * @see #getTextLayout
  217.      */
  218.     public synchronized void invalidateTextLayout() {
  219.         validTextLayout = false;
  220.     }
  221.     
  222.     /**
  223.      * Returns the origin of the text. This is the leftmost point
  224.      * on the baseline of the text.
  225.      * @return the origin of the text
  226.      */
  227.     public Point getTextOrigin() {
  228.         return new Point(TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
  229.     }
  230.     
  231.     /**
  232.      * Returns a 0-width caret rectangle. This rectangle is derived from
  233.      * the caret returned by getCaret. getCaretRectangle returns
  234.      * null iff getCaret does.
  235.      * @see #getCaret
  236.      * @return the caret rectangle, or null
  237.      */
  238.     public Rectangle getCaretRectangle() {
  239.         TextHitInfo caret = getCaret();
  240.         if (caret == null) {
  241.             return null;
  242.         }
  243.         return getCaretRectangle(caret);
  244.     }
  245.     
  246.     /**
  247.      * Returns a 0-width caret rectangle for the given text index.
  248.      * It is calculated based on the text layout returned by getTextLayout,
  249.      * so this method can be used for the entire displayed text.
  250.      * @param caret the text index for which to calculate a caret rectangle
  251.      * @return the caret rectangle
  252.      */
  253.     public Rectangle getCaretRectangle(TextHitInfo caret) {
  254.         TextLayout textLayout = getTextLayout();
  255.         int caretLocation;
  256.         if (textLayout != null) {
  257.             caretLocation = Math.round(textLayout.getCaretInfo(caret)[0]);
  258.         } else {
  259.             caretLocation = 0;
  260.         }
  261.         FontMetrics metrics = getGraphics().getFontMetrics();
  262.         return new Rectangle(LWTextComponent.TEXT_ORIGIN_X + caretLocation,
  263.                              LWTextComponent.TEXT_ORIGIN_Y - metrics.getAscent(),
  264.                              0, metrics.getAscent() + metrics.getDescent());
  265.     }
  266.     
  267.     /**
  268.      * Returns a text hit info indicating the current caret (insertion point).
  269.      * This class always returns a caret at the end of the text that the user
  270.      * has entered. Subclasses may return a different caret or null.
  271.      * @return the caret, or null
  272.      */
  273.     public TextHitInfo getCaret() {
  274.         return TextHitInfo.trailing(committedText.length() - 1);
  275.     }
  276.  
  277.     /**
  278.      * Inserts the given character at the end of the text.
  279.      * @param c the character to be inserted
  280.      */
  281.     public void insertCharacter(char c) {
  282.         committedText.append(c);
  283.     invalidateTextLayout();
  284.     }
  285.  
  286.     /**
  287.      * Handles the key typed event. If the character is backspace,
  288.      * the last character is removed from the text that the user
  289.      * has entered. Otherwise, the character is appended to the text.
  290.      * Then, the text is redrawn.
  291.      * @event the key event to be handled
  292.      */
  293.     public void keyTyped(KeyEvent event) {
  294.         char keyChar = event.getKeyChar();
  295.         if (keyChar == '\b') {
  296.             int len = committedText.length();
  297.             if (len > 0) {
  298.                 committedText.setLength(len - 1);
  299.         invalidateTextLayout();
  300.             }
  301.         } else {
  302.             insertCharacter(keyChar);
  303.         }
  304.     event.consume();
  305.     repaint();
  306.     }
  307.  
  308.     /** Ignores key pressed events. */
  309.     public void keyPressed(KeyEvent event) {}
  310.  
  311.     /** Ignores key released events. */
  312.     public void keyReleased(KeyEvent event) {}
  313.     
  314.     /** Turns on drawing of the component's thicker frame and the caret. */
  315.     public void focusGained(FocusEvent event) {
  316.         haveFocus = true;
  317.     repaint();
  318.     }
  319.     
  320.     /** Turns off drawing of the component's thicker frame and the caret. */
  321.     public void focusLost(FocusEvent event) {
  322.         haveFocus = false;
  323.     repaint();
  324.     }    
  325.  
  326. }
  327.  
  328. class MouseFocusListener extends MouseAdapter {
  329.  
  330.     private Component target;
  331.     
  332.     MouseFocusListener(Component target) {
  333.         this.target = target;
  334.     }
  335.  
  336.     public void mouseClicked(MouseEvent e) {
  337.         target.requestFocus();
  338.     } 
  339.  
  340. }
  341.