From: Kevin Lamonte Date: Sun, 15 Mar 2015 11:20:21 +0000 (-0400) Subject: text field working X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=128e5be1ffb65d047d8461ea1cfb65c22686ec91;p=fanfix-jexer.git text field working --- diff --git a/README.md b/README.md index f36e850..bbe38d6 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ Many tasks remain before calling this version 1.0: 0.0.1: - TDirectoryList -- TField - TMessageBox - THScroller / TVScroller - TText diff --git a/demos/Demo1.java b/demos/Demo1.java index f8e8b63..7735b05 100644 --- a/demos/Demo1.java +++ b/demos/Demo1.java @@ -275,14 +275,12 @@ class DemoMainWindow extends TWindow { row += 2; - /* addLabel("Variable-width text field:", 1, row); addField(35, row++, 15, false, "Field text"); addLabel("Fixed-width text field:", 1, row); addField(35, row, 15, true); row += 2; - */ if (!isModal()) { addLabel("Radio buttons and checkboxes", 1, row); diff --git a/src/jexer/TField.java b/src/jexer/TField.java new file mode 100644 index 0000000..14e93e7 --- /dev/null +++ b/src/jexer/TField.java @@ -0,0 +1,444 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program 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 3 of + * the License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import static jexer.TKeypress.*; + +/** + * + */ +public final class TField extends TWidget { + + /** + * Field text. + */ + private String text = ""; + + /** + * Get field text. + * + * @return field text + */ + public String getText() { + return text; + } + + /** + * If true, only allow enough characters that will fit in the width. If + * false, allow the field to scroll to the right. + */ + private boolean fixed = false; + + /** + * Current editing position within text. + */ + private int position = 0; + + /** + * Beginning of visible portion. + */ + private int windowStart = 0; + + /** + * If true, new characters are inserted at position. + */ + private boolean insertMode = true; + + /** + * Remember mouse state. + */ + private TMouseEvent mouse; + + /** + * The action to perform when the user presses enter. + */ + private TAction enterAction; + + /** + * The action to perform when the text is updated. + */ + private TAction updateAction; + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width visible text width + * @param fixed if true, the text cannot exceed the display width + */ + public TField(final TWidget parent, final int x, final int y, + final int width, final boolean fixed) { + + this(parent, x, y, width, fixed, "", null, null); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width visible text width + * @param fixed if true, the text cannot exceed the display width + * @param text initial text, default is empty string + */ + public TField(final TWidget parent, final int x, final int y, + final int width, final boolean fixed, final String text) { + + this(parent, x, y, width, fixed, text, null, null); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width visible text width + * @param fixed if true, the text cannot exceed the display width + * @param text initial text, default is empty string + * @param enterAction function to call when enter key is pressed + * @param updateAction function to call when the text is updated + */ + public TField(final TWidget parent, final int x, final int y, + final int width, final boolean fixed, final String text, + final TAction enterAction, final TAction updateAction) { + + // Set parent and window + super(parent); + setX(x); + setY(y); + setHeight(1); + setWidth(width); + setHasCursor(true); + + this.fixed = fixed; + this.text = text; + this.enterAction = enterAction; + this.updateAction = updateAction; + } + + /** + * Returns true if the mouse is currently on the field. + * + * @return if true the mouse is currently on the field + */ + private boolean mouseOnField() { + int rightEdge = getWidth() - 1; + if ((mouse != null) + && (mouse.getY() == 0) + && (mouse.getX() >= 0) + && (mouse.getX() <= rightEdge) + ) { + return true; + } + return false; + } + + /** + * Dispatch to the action function. + * + * @param enter if true, the user pressed Enter, else this was an update + * to the text. + */ + private void dispatch(final boolean enter) { + if (enter) { + if (enterAction != null) { + enterAction.DO(); + } + } else { + if (updateAction != null) { + updateAction.DO(); + } + } + } + + /** + * Draw the text field. + */ + @Override + public void draw() { + CellAttributes fieldColor; + + if (getAbsoluteActive()) { + fieldColor = getTheme().getColor("tfield.active"); + } else { + fieldColor = getTheme().getColor("tfield.inactive"); + } + + int end = windowStart + getWidth(); + if (end > text.length()) { + end = text.length(); + } + getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor); + getScreen().putStrXY(0, 0, text.substring(windowStart, end), + fieldColor); + + // Fix the cursor, it will be rendered by TApplication.drawAll(). + updateCursor(); + } + + /** + * Update the cursor position. + */ + private void updateCursor() { + if ((position > getWidth()) && fixed) { + setCursorX(getWidth()); + } else if ((position - windowStart == getWidth()) && !fixed) { + setCursorX(getWidth() - 1); + } else { + setCursorX(position - windowStart); + } + } + + /** + * Handle mouse button presses. + * + * @param mouse mouse button event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + this.mouse = mouse; + + if ((mouseOnField()) && (mouse.getMouse1())) { + // Move cursor + int deltaX = mouse.getX() - getCursorX(); + position += deltaX; + if (position > text.length()) { + position = text.length(); + } + updateCursor(); + return; + } + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + + if (keypress.equals(kbLeft)) { + if (position > 0) { + position--; + } + if (fixed == false) { + if ((position == windowStart) && (windowStart > 0)) { + windowStart--; + } + } + return; + } + + if (keypress.equals(kbRight)) { + if (position < text.length()) { + position++; + if (fixed == true) { + if (position == getWidth()) { + position--; + } + } else { + if ((position - windowStart) == getWidth()) { + windowStart++; + } + } + } + return; + } + + if (keypress.equals(kbEnter)) { + dispatch(true); + return; + } + + if (keypress.equals(kbIns)) { + insertMode = !insertMode; + return; + } + if (keypress.equals(kbHome)) { + position = 0; + windowStart = 0; + return; + } + + if (keypress.equals(kbEnd)) { + position = text.length(); + if (fixed == true) { + if (position >= getWidth()) { + position = text.length() - 1; + } + } else { + windowStart = text.length() - getWidth() + 1; + if (windowStart < 0) { + windowStart = 0; + } + } + return; + } + + if (keypress.equals(kbDel)) { + if ((text.length() > 0) && (position < text.length())) { + text = text.substring(0, position) + + text.substring(position + 1); + } + return; + } + + if (keypress.equals(kbBackspace) || keypress.equals(kbBackspaceDel)) { + if (position > 0) { + position--; + text = text.substring(0, position) + + text.substring(position + 1); + } + if (fixed == false) { + if ((position == windowStart) + && (windowStart > 0) + ) { + windowStart--; + } + } + dispatch(false); + return; + } + + if (!keypress.getKey().getIsKey() + && !keypress.getKey().getAlt() + && !keypress.getKey().getCtrl() + ) { + // Plain old keystroke, process it + if ((position == text.length()) + && (text.length() < getWidth())) { + + // Append case + appendChar(keypress.getKey().getCh()); + } else if ((position < text.length()) + && (text.length() < getWidth())) { + + // Overwrite or insert a character + if (insertMode == false) { + // Replace character + text = text.substring(0, position) + + keypress.getKey().getCh() + + text.substring(position + 1); + position++; + } else { + // Insert character + insertChar(keypress.getKey().getCh()); + } + } else if ((position < text.length()) + && (text.length() >= getWidth())) { + + // Multiple cases here + if ((fixed == true) && (insertMode == true)) { + // Buffer is full, do nothing + } else if ((fixed == true) && (insertMode == false)) { + // Overwrite the last character, maybe move position + text = text.substring(0, position) + + keypress.getKey().getCh() + + text.substring(position + 1); + if (position < getWidth() - 1) { + position++; + } + } else if ((fixed == false) && (insertMode == false)) { + // Overwrite the last character, definitely move position + text = text.substring(0, position) + + keypress.getKey().getCh() + + text.substring(position + 1); + position++; + } else { + if (position == text.length()) { + // Append this character + appendChar(keypress.getKey().getCh()); + } else { + // Insert this character + insertChar(keypress.getKey().getCh()); + } + } + } else { + assert (!fixed); + + // Append this character + appendChar(keypress.getKey().getCh()); + } + dispatch(false); + return; + } + + // Pass to parent for the things we don't care about. + super.onKeypress(keypress); + } + + /** + * Append char to the end of the field. + * + * @param ch = char to append + */ + private void appendChar(final char ch) { + // Append the LAST character + text += ch; + position++; + + assert (position == text.length()); + + if (fixed) { + if (position == getWidth()) { + position--; + } + } else { + if ((position - windowStart) == getWidth()) { + windowStart++; + } + } + } + + /** + * Insert char somewhere in the middle of the field. + * + * @param ch char to append + */ + private void insertChar(final char ch) { + text = text.substring(0, position) + ch + text.substring(position); + position++; + if ((position - windowStart) == getWidth()) { + assert (!fixed); + windowStart++; + } + } + +} diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 0f920ba..25e43f7 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1046,5 +1046,55 @@ public abstract class TWidget implements Comparable { return new TRadioGroup(this, x, y, label); } + /** + * Convenience function to add a text field to this container/window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param width visible text width + * @param fixed if true, the text cannot exceed the display width + * @return the new text field + */ + public final TField addField(final int x, final int y, + final int width, final boolean fixed) { + + return new TField(this, x, y, width, fixed); + } + + /** + * Convenience function to add a text field to this container/window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param width visible text width + * @param fixed if true, the text cannot exceed the display width + * @param text initial text, default is empty string + * @return the new text field + */ + public final TField addField(final int x, final int y, + final int width, final boolean fixed, final String text) { + + return new TField(this, x, y, width, fixed, text); + } + + /** + * Convenience function to add a text field to this container/window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param width visible text width + * @param fixed if true, the text cannot exceed the display width + * @param text initial text, default is empty string + * @param enterAction function to call when enter key is pressed + * @param updateAction function to call when the text is updated + * @return the new text field + */ + public final TField addField(final int x, final int y, + final int width, final boolean fixed, final String text, + final TAction enterAction, final TAction updateAction) { + + return new TField(this, x, y, width, fixed, text, enterAction, + updateAction); + } }