/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package net.puppygames.applet.widgets; import net.puppygames.applet.Game; import org.lwjgl.Sys; import org.lwjgl.input.Keyboard; import org.lwjgl.util.ReadableColor; import org.lwjgl.util.Rectangle; import com.shavenpuppy.jglib.opengl.ColorUtil; import com.shavenpuppy.jglib.opengl.GLFont; import com.shavenpuppy.jglib.opengl.GLRenderable; import com.shavenpuppy.jglib.opengl.GLString; import com.shavenpuppy.jglib.sprites.SimpleRenderer; import static org.lwjgl.opengl.GL11.*; /** * $Id: TextField.java,v 1.5 2010/04/14 23:38:32 foo Exp $ * <p> * A single line editing text field. * * @author $Author: foo $ * @version $Revision: 1.5 $ */ public class TextField { /** Are we editing? */ private boolean editing; /** Old text value */ private String oldText; /** Allow uppercase? */ private boolean allowUppercase; /** All caps */ private boolean allCaps; /** Cursor pos */ private int cursorPos = 0; /** Flash ticker */ private int flashTick = 0; /** Cursor visibility */ private boolean cursorVisible; /** Edit display */ private GLString display; /** Editing buffer */ private final StringBuilder buffer; /** Width, in pixels */ private int width; /** * C'tor */ public TextField(int maxLength, int width) { display = new GLString(maxLength); buffer = new StringBuilder(maxLength); this.width = width; } /** * @param width the width to set */ public void setWidth(int width) { this.width = width; } /** * Sets this text field to "editing" mode or not * @param editing */ public void setEditing(boolean editing) { this.editing = editing; if (editing) { oldText = display.getText(); } } /** * Undo all edits. Does nothing if not currently editing. */ public void undo() { if (editing) { setText(oldText); } } /** * Cancel editing programmatically. Also called when user taps ESC. */ public void cancel() { undo(); setEditing(false); onCancelled(); } /** * Called when editing is cancelled */ protected void onCancelled() { } public void tick() { if (editing) { // We're editing flashCursor(); processKeyboard(); } } private void flashCursor() { flashTick ++; if (flashTick > 6) { flashTick = 0; cursorVisible = !cursorVisible; } } private void processKeyboard() { int oldCursorPos = cursorPos; while (Keyboard.next()) { if (!Keyboard.getEventKeyState()) { continue; } int key = Keyboard.getEventKey(); switch (key) { case Keyboard.KEY_DOWN: case Keyboard.KEY_END: cursorPos = display.length(); break; case Keyboard.KEY_UP: case Keyboard.KEY_HOME: cursorPos = 0; break; case Keyboard.KEY_LEFT: if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) { cursorPos = 0; } else if (cursorPos > 0) { cursorPos --; } break; case Keyboard.KEY_RIGHT: if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) { cursorPos = display.length(); } else if (cursorPos < display.length()) { cursorPos ++; } break; case Keyboard.KEY_DELETE: if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) { buffer.setLength(0); cursorPos = 0; display.setText(""); } else if (cursorPos < display.length()) { buffer.deleteCharAt(cursorPos); display.setText(buffer.toString()); } onEdited(); break; case Keyboard.KEY_BACK: if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) { buffer.setLength(0); cursorPos = 0; display.setText(""); } else { if (cursorPos > 0) { buffer.deleteCharAt(-- cursorPos); display.setText(buffer.toString()); } } onEdited(); break; case Keyboard.KEY_TAB: case Keyboard.KEY_RETURN: // Change focus changeFocus(); return; case Keyboard.KEY_ESCAPE: // Cancel edits cancel(); return; default: // Type this character char c = Keyboard.getEventCharacter(); if (c == 22 || key == Keyboard.KEY_INSERT && Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) { String paste = Sys.getClipboard(); if (paste == null) { break; } for (int i = 0; i < paste.length(); i ++) { c = Character.toLowerCase(paste.charAt(i)); if (!addChar(c)) { break; } } } else if (c == 26 && Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) { undo(); } else if (c >= 32 && c < 127) { addChar(c); } break; } } if (oldCursorPos != cursorPos) { cursorVisible = true; flashTick = 0; } } private boolean addChar(char c) { if (!allowUppercase) { c = Character.toLowerCase(c); } if (allCaps) { c = Character.toUpperCase(c); } if (buffer.length() < buffer.capacity() && acceptChar(c)) { buffer.insert(cursorPos ++, c); display.setText(buffer.toString()); onEdited(); return true; } else { return false; } } public boolean acceptChar(char c) { return true; } /** * Change focus to something else. Called on TAB or RETURN */ public final void changeFocus() { editing = false; cursorVisible = false; flashTick = 0; onChangeFocus(); } protected void onChangeFocus() { } protected void onEdited() { } public boolean isEditing() { return editing; } public String getText() { return display.getText(); } public int length() { return display.length(); } public void setFont(GLFont font) { display.setFont(font); } public void setText(String s) { display.setText(s); buffer.delete(0, buffer.length()); buffer.append(s); cursorPos = s.length(); } public void setLocation(int xp, int yp) { display.setLocation(xp, yp); } /** * @return the X position of the cursor */ private int getCursorXPos() { if (!editing) { return 0; } else { Rectangle tempBounds = display.getGlyphBounds(cursorPos - 1, null); return tempBounds.getX() + tempBounds.getWidth(); } } public void render(final SimpleRenderer renderer) { final int cursorX = getCursorXPos(); renderer.glRender(new GLRenderable() { @Override public void render() { glEnable(GL_SCISSOR_TEST); } }); if (cursorX - display.getX() > width && editing) { renderer.glRender(new GLRenderable() { @Override public void render() { glPushMatrix(); glTranslatef(width - cursorX + display.getX(), 0.0f, 0.0f); } }); } // Annoyingly have to scissor according to window coords... float wRatio = (float) Game.getViewPort().getWidth() / (float) Game.getWidth(); float hRatio = (float) Game.getViewPort().getHeight() / (float) Game.getHeight(); final int sx = (int)(display.getX() * wRatio) + Game.getViewPort().getX(); final int sw = (int)(width * wRatio); final int cy = display.getY() - display.getFont().getDescent(); final int sy = (int)((cy) * hRatio) + Game.getViewPort().getY(); final int sh = (int)(display.getFont().getHeight() * hRatio); renderer.glRender(new GLRenderable() { @Override public void render() { glScissor(sx, sy, sw, sh); } }); display.render(renderer); renderer.glRender(new GLRenderable() { @Override public void render() { glDisable(GL_SCISSOR_TEST); } }); // Draw cursor if (cursorVisible && editing) { renderer.glRender(new GLRenderable() { @Override public void render() { glDisable(GL_TEXTURE_2D); } }); if (display.isColoured()) { ColorUtil.setGLColorPre(display.getBottomColour(), display.getAlpha(), renderer); } short idx = renderer.glVertex2f(cursorX, cy); renderer.glVertex2f(cursorX + 4, cy); if (display.isColoured()) { ColorUtil.setGLColorPre(display.getTopColour(), display.getAlpha(), renderer); } renderer.glVertex2f(cursorX + 4, cy + display.getFont().getHeight()); renderer.glVertex2f(cursorX, cy + display.getFont().getHeight()); renderer.glRender(GL_TRIANGLE_FAN, new short[] {(short) (idx + 0), (short) (idx + 1), (short) (idx + 2), (short) (idx + 3)}); renderer.glRender(new GLRenderable() { @Override public void render() { glEnable(GL_TEXTURE_2D); } }); } if (cursorX - display.getX() > width && editing) { renderer.glRender(new GLRenderable() { @Override public void render() { glPopMatrix(); } }); } } /** * @param allowUppercase the allowUppercase to set */ public void setAllowUppercase(boolean allowUppercase) { this.allowUppercase = allowUppercase; } public void setAllCaps(boolean allCaps) { this.allCaps = allCaps; } public void setAlpha(int alpha) { display.setAlpha(alpha); } public void setBottomColour(ReadableColor c) { display.setBottomColour(c); } public void setColour(ReadableColor c) { display.setColour(c); } public void setTopColour(ReadableColor c) { display.setTopColour(c); } }