home *** CD-ROM | disk | FTP | other *** search
Java Source | 1999-09-19 | 12.0 KB | 341 lines |
- /*
- * @(#)LWTextComponent.java 1.4 98/09/03
- *
- * Copyright 1997-1998 by Sun Microsystems, Inc.,
- * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
- * All rights reserved.
- */
-
- import java.awt.Color;
- import java.awt.Component;
- import java.awt.Dimension;
- import java.awt.FontMetrics;
- import java.awt.Graphics;
- import java.awt.Graphics2D;
- import java.awt.Point;
- import java.awt.Rectangle;
- import java.awt.event.FocusEvent;
- import java.awt.event.FocusListener;
- import java.awt.event.KeyEvent;
- import java.awt.event.KeyListener;
- import java.awt.event.MouseAdapter;
- import java.awt.event.MouseEvent;
- import java.awt.font.FontRenderContext;
- import java.awt.font.TextAttribute;
- import java.awt.font.TextHitInfo;
- import java.awt.font.TextLayout;
- import java.text.AttributedCharacterIterator;
- import java.text.AttributedString;
-
- /**
- * Implements a very simple lightweight text editing component.
- * It lets the user edit a single line of text using the keyboard.
- * The only special character that it knows about is backspace; all other
- * characters are added to the text. Selections are not supported, so
- * there's only a simple caret indicating the insertion point.
- * The component also displays a component name above the editable
- * text line, and draws a black frame whose thickness indicates whether
- * the component has the focus.
- * <p>
- * The component can be initialized to enable or disable input
- * through input methods. Other than that, it doesn't do anything
- * to support input methods, so input method interaction (if any)
- * will occur in a separate composition window. However, the
- * component is designed to be easily extended with full input
- * method support. It distinguishes between "displayed text" and
- * "committed text" - here, they're the same, but in a subclass
- * that supports on-the-spot input, the displayed text would be the
- * combination of committed text and composed text. The component
- * also uses TextLayout to draw the text, so it can be easily
- * extended to handle input method highlights.
- */
-
- public class LWTextComponent extends Component implements KeyListener, FocusListener {
-
- // whether the component currently has the focus
- private transient boolean haveFocus;
-
- // the component name that's displayed at the top of the component's area
- private String name;
-
- // The text the user has entered. The term "committed text"
- // follows the usage in the input method framework.
- private StringBuffer committedText = new StringBuffer();
-
- // We use a text layout for drawing and measuring. Since they
- // are expensive to create, we cache it and invalidate it when
- // the text is modified.
- private transient TextLayout textLayout = null;
- private transient boolean validTextLayout = false;
-
- // constants that determine where the text is drawn
- private final static int TEXT_ORIGIN_X = 10;
- private final static int NAME_ORIGIN_Y = 20;
- private final static int TEXT_ORIGIN_Y = 40;
-
- /**
- * Constructs a LWTextComponent.
- * @param name the component name to be displayed above the text
- * @param enableInputMethods whether to enable input methods for this component
- */
- public LWTextComponent(String name, boolean enableInputMethods) {
- super();
- this.name = name;
- setSize(300, 80);
- // we have to set the foreground color because otherwise
- // text may not display correctly when we use an input
- // method highlight that swaps background and foreground
- // colors
- setForeground(Color.black);
- setBackground(Color.white);
- setVisible(true);
- setEnabled(true);
- addKeyListener(this);
- addFocusListener(this);
- addMouseListener(new MouseFocusListener(this));
- enableInputMethods(enableInputMethods);
- }
-
- /**
- * Draws the component. The following items are drawn:
- * <ul>
- * <li>the component's background
- * <li>a frame, thicker if the component has the focus
- * <li>the component name
- * <li>the text that the user has entered
- * <li>the caret, if the component has the focus
- * </ul>
- */
- public synchronized void paint(Graphics g) {
-
- // draw the background
- g.setColor(getBackground());
- Dimension size = getSize();
- g.fillRect(0, 0, size.width, size.height);
-
- // draw the frame, thicker if the component has the focus
- g.setColor(Color.black);
- g.drawRect(0, 0, size.width - 1, size.height - 1);
- if (haveFocus) {
- g.drawRect(1, 1, size.width - 3, size.height - 3);
- }
-
- // draw the component name
- g.setColor(getForeground());
- g.drawString(name, TEXT_ORIGIN_X, NAME_ORIGIN_Y);
-
- // draw the text that the user has entered
- TextLayout textLayout = getTextLayout();
- if (textLayout != null) {
- textLayout.draw((Graphics2D) g, TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
- }
-
- // draw the caret, if the component has the focus
- Rectangle rectangle = getCaretRectangle();
- if (haveFocus && rectangle != null) {
- g.setXORMode(getBackground());
- g.fillRect(rectangle.x, rectangle.y, 1, rectangle.height);
- g.setPaintMode();
- }
- }
-
- /**
- * Returns the text that the user has entered and committed.
- * Since this component does not support on-the-spot input, there's no
- * composed text, so all text that has been entered is committed.
- * @return an AttributedCharacterIterator for the text that the user has entered and committed
- */
- public AttributedCharacterIterator getCommittedText() {
- AttributedString string = new AttributedString(committedText.toString());
- return string.getIterator();
- }
-
- /**
- * Returns a subrange of the text that the user has entered and committed.
- * Since this component does not support on-the-spot input, there's no
- * composed text, so all text that has been entered is committed.
- * @param beginIndex the index of the first character of the subrange
- * @param endIndex the index of the character following the subrange
- * @return an AttributedCharacterIterator for a subrange of the text that the user has entered and committed
- */
- public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex) {
- AttributedString string = new AttributedString(committedText.toString());
- return string.getIterator(null, beginIndex, endIndex);
- }
-
- /**
- * Returns the length of the text that the user has entered and committed.
- * Since this component does not support on-the-spot input, there's no
- * composed text, so all text that has been entered is committed.
- * @return the length of the text that the user has entered and committed
- */
- public int getCommittedTextLength() {
- return committedText.length();
- }
-
- /**
- * Returns the text that the user has entered.
- * As TextLayout requires a font to be defined for each character,
- * the default font is applied to the entire text.
- * A subclass that supports on-the-spot input must override this
- * method to include composed text.
- * @return the text that the user has entered
- */
- public AttributedCharacterIterator getDisplayText() {
- AttributedString string = new AttributedString(committedText.toString());
- if (committedText.length() > 0) {
- string.addAttribute(TextAttribute.FONT, getFont());
- }
- return string.getIterator();
- }
-
- /**
- * Returns a text layout for the text that the user has entered.
- * This text layout is created from the text returned by getDisplayText.
- * The text layout is cached until invalidateTextLayout is called.
- * @see #invalidateTextLayout
- * @see #getDisplayText
- * @return a text layout for the text that the user has entered, or null
- */
- public synchronized TextLayout getTextLayout() {
- if (!validTextLayout) {
- textLayout = null;
- AttributedCharacterIterator text = getDisplayText();
- if (text.getEndIndex() > text.getBeginIndex()) {
- FontRenderContext context = ((Graphics2D) getGraphics()).getFontRenderContext();
- textLayout = new TextLayout(text, context);
- }
- }
- validTextLayout = true;
- return textLayout;
- }
-
- /**
- * Invalidates the cached text layout. This must be called whenever
- * the component's text is modified.
- * @see #getTextLayout
- */
- public synchronized void invalidateTextLayout() {
- validTextLayout = false;
- }
-
- /**
- * Returns the origin of the text. This is the leftmost point
- * on the baseline of the text.
- * @return the origin of the text
- */
- public Point getTextOrigin() {
- return new Point(TEXT_ORIGIN_X, TEXT_ORIGIN_Y);
- }
-
- /**
- * Returns a 0-width caret rectangle. This rectangle is derived from
- * the caret returned by getCaret. getCaretRectangle returns
- * null iff getCaret does.
- * @see #getCaret
- * @return the caret rectangle, or null
- */
- public Rectangle getCaretRectangle() {
- TextHitInfo caret = getCaret();
- if (caret == null) {
- return null;
- }
- return getCaretRectangle(caret);
- }
-
- /**
- * Returns a 0-width caret rectangle for the given text index.
- * It is calculated based on the text layout returned by getTextLayout,
- * so this method can be used for the entire displayed text.
- * @param caret the text index for which to calculate a caret rectangle
- * @return the caret rectangle
- */
- public Rectangle getCaretRectangle(TextHitInfo caret) {
- TextLayout textLayout = getTextLayout();
- int caretLocation;
- if (textLayout != null) {
- caretLocation = Math.round(textLayout.getCaretInfo(caret)[0]);
- } else {
- caretLocation = 0;
- }
- FontMetrics metrics = getGraphics().getFontMetrics();
- return new Rectangle(LWTextComponent.TEXT_ORIGIN_X + caretLocation,
- LWTextComponent.TEXT_ORIGIN_Y - metrics.getAscent(),
- 0, metrics.getAscent() + metrics.getDescent());
- }
-
- /**
- * Returns a text hit info indicating the current caret (insertion point).
- * This class always returns a caret at the end of the text that the user
- * has entered. Subclasses may return a different caret or null.
- * @return the caret, or null
- */
- public TextHitInfo getCaret() {
- return TextHitInfo.trailing(committedText.length() - 1);
- }
-
- /**
- * Inserts the given character at the end of the text.
- * @param c the character to be inserted
- */
- public void insertCharacter(char c) {
- committedText.append(c);
- invalidateTextLayout();
- }
-
- /**
- * Handles the key typed event. If the character is backspace,
- * the last character is removed from the text that the user
- * has entered. Otherwise, the character is appended to the text.
- * Then, the text is redrawn.
- * @event the key event to be handled
- */
- public void keyTyped(KeyEvent event) {
- char keyChar = event.getKeyChar();
- if (keyChar == '\b') {
- int len = committedText.length();
- if (len > 0) {
- committedText.setLength(len - 1);
- invalidateTextLayout();
- }
- } else {
- insertCharacter(keyChar);
- }
- event.consume();
- repaint();
- }
-
- /** Ignores key pressed events. */
- public void keyPressed(KeyEvent event) {}
-
- /** Ignores key released events. */
- public void keyReleased(KeyEvent event) {}
-
- /** Turns on drawing of the component's thicker frame and the caret. */
- public void focusGained(FocusEvent event) {
- haveFocus = true;
- repaint();
- }
-
- /** Turns off drawing of the component's thicker frame and the caret. */
- public void focusLost(FocusEvent event) {
- haveFocus = false;
- repaint();
- }
-
- }
-
- class MouseFocusListener extends MouseAdapter {
-
- private Component target;
-
- MouseFocusListener(Component target) {
- this.target = target;
- }
-
- public void mouseClicked(MouseEvent e) {
- target.requestFocus();
- }
-
- }
-