com.jmex.bui.BTextField.java Source code

Java tutorial

Introduction

Here is the source code for com.jmex.bui.BTextField.java

Source

//
// $Id: BTextField.java,v 1.2 2007/04/27 19:46:29 vivaldi Exp $
//
// BUI - a user interface library for the JME 3D engine
// Copyright (C) 2005, Michael Bayne, All Rights Reserved
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.jmex.bui;

import java.util.LinkedList;
import java.util.List;

import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jmex.bui.enumeratedConstants.TextEffect;
import com.jmex.bui.event.*;
import com.jmex.bui.text.*;
import com.jmex.bui.util.Dimension;
import com.jmex.bui.util.Insets;
import com.jmex.bui.util.Rectangle;
import org.lwjgl.opengl.GL11;

/**
 * Displays and allows for the editing of a single line of text.
 */
public class BTextField extends BTextComponent implements EditCommands, Document.Listener {
    /**
     * Creates a blank text field.
     */
    public BTextField() {
        this("");
    }

    /**
     * Creates a blank text field with maximum input length. The maximum input
     * length is controlled by a {@link LengthLimitedDocument}, changing the
     * document will remove the length control.
     * 
     * @param maxLength
     *            int
     */
    public BTextField(int maxLength) {
        this("", maxLength);
    }

    /**
     * Creates a text field with the specified starting text.
     * 
     * @param text
     *            String
     */
    public BTextField(String text) {
        this(text, 0);
    }

    /**
     * Creates a text field with the specified starting text and max length. The
     * maximum input length is controlled by a {@link LengthLimitedDocument},
     * changing the document will remove the length control.
     * 
     * @param text
     *            String
     * @param maxLength
     *            int
     */
    public BTextField(String text, int maxLength) {
        setMaxLength(maxLength);
        setText(text);
        listeners = new LinkedList<FocusListener>();
    }

    /**
     * Configures this text field with the specified text for display and
     * editing. The cursor will be adjusted if this text is shorter than its
     * previous position.
     */
    public void setText(String text) {
        if (text == null) {
            text = "";
        }
        if (!_text.getText().equals(text)) {
            _text.setText(text);
        }
    }

    // documentation inherited
    public String getText() {
        return _text.getText();
    }

    /**
     * Configures the maximum length of this text field. This will replace any
     * currently set document with a LengthLimitedDocument (or no document at
     * all if maxLength is <= 0).
     * 
     * @param maxLength
     *            int
     */
    public void setMaxLength(int maxLength) {
        if (maxLength > 0) {
            setDocument(new LengthLimitedDocument(maxLength));
        } else {
            setDocument(new Document());
        }
    }

    /**
     * Configures this text field with a custom document.
     * 
     * @param document
     *            Document
     */
    public void setDocument(Document document) {
        _text = document;
        _text.addListener(this);
    }

    /**
     * Returns the underlying document used by this text field to maintain its
     * state. Changes to the document will be reflected in the text field
     * display.
     * 
     * @return Document
     */
    public Document getDocument() {
        return _text;
    }

    /**
     * Configures the preferred width of this text field (the preferred height
     * will be calculated from the font).
     * 
     * @param width
     *            int
     */
    public void setPreferredWidth(int width) {
        _prefWidth = width;
    }

    // documentation inherited from interface Document.Listener
    public void textInserted(Document document, int offset, int length) {
        // if we're already part of the hierarchy, recreate our glyps
        if (isAdded()) {
            recreateGlyphs();
        }

        // let anyone who is around to hear know that a tree fell in the woods
        emitEvent(new TextEvent(this, -1L));
    }

    // documentation inherited from interface Document.Listener
    public void textRemoved(Document document, int offset, int length) {
        final int len = _text.getLength();
        // confine the cursor to the new text
        if (_cursp > len) {
            setCursorPos(len);
        }

        // if we're already part of the hierarchy, recreate our glyps
        if (isAdded()) {
            recreateGlyphs();
        }

        // let anyone who is around to hear know that a tree fell in the woods
        emitEvent(new TextEvent(this, -1L));
    }

    @Override
    // documentation inherited
    public boolean acceptsFocus() {
        return isVisible() && isEnabled();
    }

    @Override
    // documentation inherited
    public boolean dispatchEvent(BEvent event) {
        final int len = _text.getLength();

        if (event instanceof KeyEvent) {
            KeyEvent kev = (KeyEvent) event;

            final int modifiers = kev.getModifiers();

            if (kev.getType() == KeyEvent.KEY_PRESSED) {

                int keyCode = kev.getKeyCode();
                switch (_keymap.lookupMapping(modifiers, keyCode)) {
                case BACKSPACE:
                    if (_cursp > 0 && len > 0) {
                        int pos = _cursp - 1;
                        if (_text.remove(pos, 1)) { // might change _cursp
                            setCursorPos(pos);
                        }
                    }
                    break;

                case DELETE:
                    if (_cursp < len) {
                        _text.remove(_cursp, 1);
                    }
                    break;

                case CURSOR_LEFT:
                    setCursorPos(Math.max(0, _cursp - 1));
                    break;

                case CURSOR_RIGHT:
                    setCursorPos(Math.min(len, _cursp + 1));
                    break;

                case START_OF_LINE:
                    setCursorPos(0);
                    break;

                case END_OF_LINE:
                    setCursorPos(len);
                    break;

                case ACTION:
                    emitEvent(new ActionEvent(this, kev.getWhen(), modifiers, ""));
                    break;

                case RELEASE_FOCUS:
                    getWindow().requestFocus(null);
                    break;

                case CLEAR:
                    _text.setText("");
                    break;

                default:
                    // insert printable and shifted printable characters
                    char c = kev.getKeyChar();
                    if ((modifiers & ~KeyEvent.SHIFT_DOWN_MASK) == 0 && !Character.isISOControl(c)) {
                        String text = String.valueOf(c);
                        if (_text.insert(_cursp, text)) {
                            setCursorPos(_cursp + 1);
                        }
                    } else {
                        return super.dispatchEvent(event);
                    }
                    break;
                }

                return true; // we've consumed these events
            }
        } else if (event instanceof MouseEvent) {
            MouseEvent mev = (MouseEvent) event;
            if (mev.getType() == MouseEvent.MOUSE_PRESSED &&
            // don't adjust the cursor if we have no text
                    len > 0) {
                Insets insets = getInsets();
                int mx = mev.getX() - getAbsoluteX() - insets.left + _txoff,
                        my = mev.getY() - getAbsoluteY() - insets.bottom;
                setCursorPos(_glyphs.getHitPos(mx, my));
                return true;
            }
        } else if (event instanceof FocusEvent) {
            FocusEvent fev = (FocusEvent) event;
            switch (fev.getType()) {
            case FocusEvent.FOCUS_GAINED:
                gainedFocus();
                break;
            case FocusEvent.FOCUS_LOST:
                lostFocus();
                break;
            }
            return true;
        }

        return super.dispatchEvent(event);
    }

    @Override
    // documentation inherited
    protected String getDefaultStyleClass() {
        return "textfield";
    }

    @Override
    // documentation inherited
    protected void configureStyle(BStyleSheet style) {
        super.configureStyle(style);

        // look up our keymap
        _keymap = style.getKeyMap(this, null);
    }

    @Override
    // documentation inherited
    protected void wasAdded() {
        super.wasAdded();

        // create our underlying text texture
        recreateGlyphs();
    }

    @Override
    // documentation inherited
    protected void wasRemoved() {
        super.wasRemoved();

        if (_glyphs != null) {
            _glyphs.wasRemoved();
            _glyphs = null;
        }
    }

    @Override
    // documentation inherited
    protected void layout() {
        super.layout();

        // cope with becoming smaller or larger
        recreateGlyphs();
    }

    @Override
    // documentation inherited
    protected void stateDidChange() {
        super.stateDidChange();
        recreateGlyphs();
    }

    @Override
    // documentation inherited
    protected void renderComponent(Renderer renderer) {
        super.renderComponent(renderer);

        Insets insets = getInsets();

        // render our text
        if (_glyphs != null) {
            // clip the text to our visible text region
            boolean scissored = intersectScissorBox(_srect, getAbsoluteX() + insets.left,
                    getAbsoluteY() + insets.bottom, _width - insets.getHorizontal(),
                    _height - insets.getVertical());
            try {
                _glyphs.render(renderer, insets.left - _txoff, insets.bottom, _alpha);
            } finally {
                restoreScissorState(scissored, _srect);
            }
        }

        // render the cursor if we have focus
        if (_showCursor) {
            int cx = insets.left - _txoff + _cursx;
            BComponent.applyDefaultStates();
            ColorRGBA c = getColor();
            GL11.glColor4f(c.r, c.g, c.b, c.a);
            GL11.glBegin(GL11.GL_LINE_STRIP);
            GL11.glVertex2f(cx, insets.bottom);
            int cheight = getTextFactory().getHeight();
            GL11.glVertex2f(cx, insets.bottom + cheight);
            GL11.glEnd();
        }
    }

    @Override
    // documentation inherited
    protected Dimension computePreferredSize(int whint, int hhint) {
        Dimension d = (_glyphs == null) ? new Dimension(0, getTextFactory().getHeight())
                : new Dimension(_glyphs.getSize());
        if (_prefWidth != -1) {
            d.width = _prefWidth;
        }
        return d;
    }

    public void addFocusListener(FocusListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    public void removeFocusListener(FocusListener listener) {
        listeners.remove(listener);
    }

    /**
     * Called when this text field has gained the focus.
     */
    protected void gainedFocus() {
        _showCursor = true;
        setCursorPos(_cursp);
        FocusEvent event = new FocusEvent(this, System.currentTimeMillis(), FocusEvent.FOCUS_GAINED);
        for (FocusListener each : listeners) {
            each.focusGained(event);
        }
    }

    /**
     * Called when this text field has lost the focus.
     */
    protected void lostFocus() {
        _showCursor = false;
        FocusEvent event = new FocusEvent(this, System.currentTimeMillis(), FocusEvent.FOCUS_LOST);
        for (FocusListener each : listeners) {
            each.focusLost(event);
        }
    }

    /**
     * Recreates the entity that we use to render our text.
     */
    protected void recreateGlyphs() {
        clearGlyphs();

        // if we have no text, clear out all our internal markers
        if (_text.getLength() == 0) {
            _txoff = _cursp = _cursx = 0;
            return;
        }

        // format our text and determine how much of it we can display
        _glyphs = getTextFactory().createText(getDisplayText(), getColor(), TextEffect.PLAIN,
                BConstants.DEFAULT_SIZE, null, true);
        if (isAdded()) {
            _glyphs.wasAdded();
        }
        setCursorPos(_cursp);
    }

    /**
     * Clears out our text textures and other related bits.
     */
    protected void clearGlyphs() {
        if (_glyphs != null && isAdded()) {
            _glyphs.wasRemoved();
        }
        _glyphs = null;
    }

    /**
     * This method allows a derived class (specifically {@link BPasswordField})
     * to display something other than the actual contents of the text field.
     * 
     * @return String
     */
    protected String getDisplayText() {
        return _text.getText();
    }

    /**
     * Updates the cursor position, moving the visible representation as well as
     * the insertion and deletion point.
     * 
     * @param cursorPos
     *            int
     */
    protected void setCursorPos(int cursorPos) {
        // note the new cursor character position
        _cursp = cursorPos;

        // compute the new cursor screen position
        if (_glyphs != null) {
            _cursx = _glyphs.getCursorPos(cursorPos);
        } else {
            _cursx = 0;
        }

        // scroll our text left or right as necessary
        if (_cursx < _txoff) {
            _txoff = _cursx;
        } else {
            int avail = getWidth() - getInsets().getHorizontal();
            if (_cursx > _txoff + avail) {
                _txoff = _cursx - avail;
            } else if (_glyphs != null && _glyphs.getSize().width - _txoff < avail) {
                _txoff = Math.max(0, _cursx - avail);
            }
        }
    }

    protected Document _text;
    protected BText _glyphs;
    protected BKeyMap _keymap;

    protected int _prefWidth = -1;
    protected boolean _showCursor;
    protected int _cursp, _cursx, _txoff;

    protected Rectangle _srect = new Rectangle();
    private List<FocusListener> listeners;
}