}
```
-See the file demos/Demo1.java for example usage.
+See the file demos/Demo1.java for detailed examples.
- AWT:
- Blinking cursor
- - More optimal refresh
- - Jittery refresh with mouse movement
- Clean up TWidget constuctors (everyone is doing setX() / setY() / set...)
- ECMA48Backend running on socket
- TTreeView
- TFileOpen
- Decide on naming convention: getText, getValue, getLabel: one or all
of them?
-- TPasswordField (displays stars when not active)
0.0.3:
doIdle();
// Update the screen
- drawAll();
+ synchronized (getScreen()) {
+ drawAll();
+ }
}
// Shutdown the consumer threads
* @param title menu title
* @return the new menu
*/
- public final TMenu addMenu(String title) {
+ public final TMenu addMenu(final String title) {
int x = 0;
int y = 0;
TMenu menu = new TMenu(this, x, y, title);
import static jexer.TKeypress.*;
/**
- *
+ * TField implements an editable text field.
*/
-public final class TField extends TWidget {
+public class TField extends TWidget {
/**
* Field text.
*/
- private String text = "";
+ protected String text = "";
/**
* Get field text.
*
* @return field text
*/
- public String getText() {
+ public final 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;
+ protected boolean fixed = false;
/**
* Current editing position within text.
*/
- private int position = 0;
+ protected int position = 0;
/**
* Beginning of visible portion.
*/
- private int windowStart = 0;
+ protected int windowStart = 0;
/**
* If true, new characters are inserted at position.
*/
- private boolean insertMode = true;
+ protected boolean insertMode = true;
/**
* Remember mouse state.
*/
- private TMouseEvent mouse;
+ protected TMouseEvent mouse;
/**
* The action to perform when the user presses enter.
*/
- private TAction enterAction;
+ protected TAction enterAction;
/**
* The action to perform when the text is updated.
*/
- private TAction updateAction;
+ protected TAction updateAction;
/**
* Public constructor.
*
* @return if true the mouse is currently on the field
*/
- private boolean mouseOnField() {
+ protected boolean mouseOnField() {
int rightEdge = getWidth() - 1;
if ((mouse != null)
&& (mouse.getY() == 0)
* @param enter if true, the user pressed Enter, else this was an update
* to the text.
*/
- private void dispatch(final boolean enter) {
+ protected void dispatch(final boolean enter) {
if (enter) {
if (enterAction != null) {
enterAction.DO();
/**
* Update the cursor position.
*/
- private void updateCursor() {
+ protected void updateCursor() {
if ((position > getWidth()) && fixed) {
setCursorX(getWidth());
} else if ((position - windowStart == getWidth()) && !fixed) {
*
* @param ch = char to append
*/
- private void appendChar(final char ch) {
+ protected void appendChar(final char ch) {
// Append the LAST character
text += ch;
position++;
*
* @param ch char to append
*/
- private void insertChar(final char ch) {
+ protected void insertChar(final char ch) {
text = text.substring(0, position) + ch + text.substring(position);
position++;
if ((position - windowStart) == getWidth()) {
--- /dev/null
+/**
+ * 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;
+
+/**
+ * TField implements an editable text field.
+ */
+public final class TPasswordField extends TField {
+
+ /**
+ * 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 TPasswordField(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 TPasswordField(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 TPasswordField(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, x, y, width, fixed, text, enterAction, updateAction);
+ }
+
+ /**
+ * Draw the text field.
+ */
+ @Override
+ public void draw() {
+ CellAttributes fieldColor;
+
+ boolean showStars = false;
+ if (getAbsoluteActive()) {
+ fieldColor = getTheme().getColor("tfield.active");
+ } else {
+ fieldColor = getTheme().getColor("tfield.inactive");
+ showStars = true;
+ }
+
+ int end = windowStart + getWidth();
+ if (end > text.length()) {
+ end = text.length();
+ }
+
+ getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
+ if (showStars) {
+ getScreen().hLineXY(0, 0, getWidth() - 2, '*',
+ fieldColor);
+ } else {
+ getScreen().putStrXY(0, 0, text.substring(windowStart, end),
+ fieldColor);
+ }
+
+ // Fix the cursor, it will be rendered by TApplication.drawAll().
+ updateCursor();
+ }
+
+}
return getApplication().inputBox(title, caption, text);
}
+ /**
+ * Convenience function to add a password 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 TPasswordField addPasswordField(final int x, final int y,
+ final int width, final boolean fixed) {
+
+ return new TPasswordField(this, x, y, width, fixed);
+ }
+
+ /**
+ * Convenience function to add a password 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 TPasswordField addPasswordField(final int x, final int y,
+ final int width, final boolean fixed, final String text) {
+
+ return new TPasswordField(this, x, y, width, fixed, text);
+ }
+
+ /**
+ * Convenience function to add a password 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 TPasswordField addPasswordField(final int x, final int y,
+ final int width, final boolean fixed, final String text,
+ final TAction enterAction, final TAction updateAction) {
+
+ return new TPasswordField(this, x, y, width, fixed, text, enterAction,
+ updateAction);
+ }
+
}
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);
+ addField(35, row++, 15, true);
+ addLabel("Variable-width password:", 1, row);
+ addPasswordField(35, row++, 15, false);
+ addLabel("Fixed-width password:", 1, row);
+ addPasswordField(35, row++, 15, true, "hunter2");
row += 2;
if (!isModal()) {
*/
@Override
public void paint(final Graphics gr) {
+ // Do nothing until the screen reference has been set.
+ if (screen == null) {
+ return;
+ }
+ if (screen.frame == null) {
+ return;
+ }
+
+ int xCellMin = 0;
+ int xCellMax = screen.width;
+ int yCellMin = 0;
+ int yCellMax = screen.height;
+
Rectangle bounds = gr.getClipBounds();
+ if (bounds != null) {
+ // Only update what is in the bounds
+ xCellMin = screen.textColumn(bounds.x);
+ xCellMax = screen.textColumn(bounds.x + bounds.width) + 1;
+ if (xCellMax > screen.width) {
+ xCellMax = screen.width;
+ }
+ if (xCellMin >= xCellMax) {
+ xCellMin = xCellMax - 2;
+ }
+ if (xCellMin < 0) {
+ xCellMin = 0;
+ }
+ yCellMin = screen.textRow(bounds.y);
+ yCellMax = screen.textRow(bounds.y + bounds.height) + 1;
+ if (yCellMax > screen.height) {
+ yCellMax = screen.height;
+ }
+ if (yCellMin >= yCellMax) {
+ yCellMin = yCellMax - 2;
+ }
+ if (yCellMin < 0) {
+ yCellMin = 0;
+ }
+ }
- for (int y = 0; y < screen.height; y++) {
- for (int x = 0; x < screen.width; x++) {
- int xPixel = x * textWidth + left;
- int yPixel = y * textHeight + top;
-
- Cell lCell = screen.logical[x][y];
- Cell pCell = screen.physical[x][y];
-
- boolean inBounds = true;
- if (bounds != null) {
- if (bounds.contains(xPixel, yPixel)
- || bounds.contains(xPixel + textWidth, yPixel)
- || bounds.contains(xPixel, yPixel + textHeight)
- || bounds.contains(xPixel + textWidth,
- yPixel + textHeight)
- ) {
- // This area is damaged and will definitely be
- // redrawn.
- inBounds = true;
+ // Prevent updates to the screen's data from the TApplication
+ // threads.
+ synchronized (screen) {
+ /*
+ System.err.printf("bounds %s X %d %d Y %d %d\n",
+ bounds, xCellMin, xCellMax, yCellMin, yCellMax);
+ */
+
+ for (int y = yCellMin; y < yCellMax; y++) {
+ for (int x = xCellMin; x < xCellMax; x++) {
+
+ int xPixel = x * textWidth + left;
+ int yPixel = y * textHeight + top;
+
+ Cell lCell = screen.logical[x][y];
+ Cell pCell = screen.physical[x][y];
+
+ if (!lCell.equals(pCell) || reallyCleared) {
+ // Draw the background rectangle, then the
+ // foreground character.
+ gr.setColor(attrToBackgroundColor(lCell));
+ gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+ gr.setColor(attrToForegroundColor(lCell));
+ char [] chars = new char[1];
+ chars[0] = lCell.getChar();
+ gr.drawChars(chars, 0, 1, xPixel,
+ yPixel + textHeight - maxDescent);
+
+ // Physical is always updated
+ physical[x][y].setTo(lCell);
}
}
+ }
- if (!lCell.equals(pCell) || inBounds) {
- // Draw the background rectangle, then the foreground
- // character.
- gr.setColor(attrToBackgroundColor(lCell));
- gr.fillRect(xPixel, yPixel, textWidth, textHeight);
- gr.setColor(attrToForegroundColor(lCell));
- char [] chars = new char[1];
- chars[0] = lCell.getChar();
- gr.drawChars(chars, 0, 1, xPixel,
- yPixel + textHeight - maxDescent);
-
- // Physical is always updated
- physical[x][y].setTo(lCell);
- }
+ // Draw the cursor if it is visible
+ if ((cursorVisible)
+ && (cursorY <= screen.height - 1)
+ && (cursorX <= screen.width - 1)
+ ) {
+ int xPixel = cursorX * textWidth + left;
+ int yPixel = cursorY * textHeight + top;
+ Cell lCell = screen.logical[cursorX][cursorY];
+ gr.setColor(attrToForegroundColor(lCell));
+ gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
}
- }
- // Draw the cursor if it is visible
- if ((cursorVisible)
- && (cursorY <= screen.height - 1)
- && (cursorX <= screen.width - 1)
- ) {
- int xPixel = cursorX * textWidth + left;
- int yPixel = cursorY * textHeight + top;
- Cell lCell = screen.logical[cursorX][cursorY];
- gr.setColor(attrToForegroundColor(lCell));
- gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
- }
+ dirty = false;
+ reallyCleared = false;
+ } // synchronized (screen)
}
}
*/
@Override
public void flushPhysical() {
+
+ if (reallyCleared) {
+ // Really refreshed, do it all
+ frame.repaint();
+ return;
+ }
+
+ // Do nothing if nothing happened.
+ if (!dirty) {
+ return;
+ }
+
// Request a repaint, let the frame's repaint/update methods do the
// right thing.
+
// Find the minimum-size damaged region.
int xMin = frame.getWidth();
int xMax = 0;
int yMin = frame.getHeight();
int yMax = 0;
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- Cell lCell = logical[x][y];
- Cell pCell = physical[x][y];
-
- int xPixel = x * frame.textWidth + frame.left;
- int yPixel = y * frame.textHeight + frame.top;
- if (!lCell.equals(pCell)
- || ((x == cursorX) && (y == cursorY))
- ) {
- if (xPixel < xMin) {
- xMin = xPixel;
- }
- if (xPixel + frame.textWidth > xMax) {
- xMax = xPixel + frame.textWidth;
- }
- if (yPixel < yMin) {
- yMin = yPixel;
- }
- if (yPixel + frame.textHeight > yMax) {
- yMax = yPixel + frame.textHeight;
+ synchronized (this) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ int xPixel = x * frame.textWidth + frame.left;
+ int yPixel = y * frame.textHeight + frame.top;
+
+ if (!lCell.equals(pCell)
+ || ((x == cursorX)
+ && (y == cursorY)
+ && cursorVisible)
+ ) {
+ if (xPixel < xMin) {
+ xMin = xPixel;
+ }
+ if (xPixel + frame.textWidth > xMax) {
+ xMax = xPixel + frame.textWidth;
+ }
+ if (yPixel < yMin) {
+ yMin = yPixel;
+ }
+ if (yPixel + frame.textHeight > yMax) {
+ yMax = yPixel + frame.textHeight;
+ }
}
}
}
}
+ if (xMin + frame.textWidth >= xMax) {
+ xMax += frame.textWidth;
+ }
+ if (yMin + frame.textHeight >= yMax) {
+ yMax += frame.textHeight;
+ }
- // Ask for a repaint sometime in the next 10 millis.
- frame.repaint(10, xMin, yMin, xMax - xMin, yMax - yMin);
+ // Repaint the desired area
+ frame.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
+ // System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax, yMin, yMax);
}
/**
&& (cursorX <= width - 1)
) {
// Make the current cursor position dirty
- if (physical[cursorX][cursorY].getChar() == ' ') {
+ if (physical[cursorX][cursorY].getChar() == 'Q') {
physical[cursorX][cursorY].setChar('X');
} else {
- physical[cursorX][cursorY].setChar(' ');
+ physical[cursorX][cursorY].setChar('Q');
}
}
super.putCursor(visible, x, y);
}
+ /**
+ * Convert pixel column position to text cell column position.
+ *
+ * @param x pixel column position
+ * @return text cell column position
+ */
+ public int textColumn(final int x) {
+ return ((x - frame.left) / frame.textWidth);
+ }
+
+ /**
+ * Convert pixel row position to text cell row position.
+ *
+ * @param y pixel row position
+ * @return text cell row position
+ */
+ public int textRow(final int y) {
+ return ((y - frame.top) / frame.textHeight);
+ }
+
}
import java.util.LinkedList;
import jexer.TKeypress;
-import jexer.bits.Color;
import jexer.event.TCommandEvent;
import jexer.event.TInputEvent;
import jexer.event.TKeypressEvent;
case 0x0A:
keypress = kbEnter;
break;
+ case 0x1B:
+ keypress = kbEsc;
+ break;
case 0x0D:
keypress = kbEnter;
break;
mouse1 = eventMouse1;
mouse2 = eventMouse2;
mouse3 = eventMouse3;
- int x = sessionInfo.textColumn(mouse.getX());
- int y = sessionInfo.textRow(mouse.getY());
+ int x = screen.textColumn(mouse.getX());
+ int y = screen.textRow(mouse.getY());
TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
x, y, x, y, mouse1, mouse2, mouse3, false, false);
*/
@Override
public void mouseMoved(final MouseEvent mouse) {
- int x = sessionInfo.textColumn(mouse.getX());
- int y = sessionInfo.textRow(mouse.getY());
+ int x = screen.textColumn(mouse.getX());
+ int y = screen.textRow(mouse.getY());
TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_MOTION,
x, y, x, y, mouse1, mouse2, mouse3, false, false);
mouse1 = eventMouse1;
mouse2 = eventMouse2;
mouse3 = eventMouse3;
- int x = sessionInfo.textColumn(mouse.getX());
- int y = sessionInfo.textRow(mouse.getY());
+ int x = screen.textColumn(mouse.getX());
+ int y = screen.textRow(mouse.getY());
TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN,
x, y, x, y, mouse1, mouse2, mouse3, false, false);
mouse3 = false;
eventMouse3 = true;
}
- int x = sessionInfo.textColumn(mouse.getX());
- int y = sessionInfo.textRow(mouse.getY());
+ int x = screen.textColumn(mouse.getX());
+ int y = screen.textRow(mouse.getY());
TMouseEvent mouseEvent = new TMouseEvent(TMouseEvent.Type.MOUSE_UP,
x, y, x, y, eventMouse1, eventMouse2, eventMouse3, false, false);
mouse1 = eventMouse1;
mouse2 = eventMouse2;
mouse3 = eventMouse3;
- int x = sessionInfo.textColumn(mouse.getX());
- int y = sessionInfo.textRow(mouse.getY());
+ int x = screen.textColumn(mouse.getX());
+ int y = screen.textRow(mouse.getY());
if (mouse.getWheelRotation() > 0) {
mouseWheelDown = true;
}
* @return attributes at (x, y)
*/
public final CellAttributes getAttrXY(final int x, final int y) {
+
CellAttributes attr = new CellAttributes();
if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
attr.setTo(logical[x][y]);
* @param attr attributes to use (bold, foreColor, backColor)
* @param clip if true, honor clipping/offset
*/
- public final void putAttrXY(final int x, final int y,
- final CellAttributes attr, final boolean clip) {
+ public final void putAttrXY(final int x, final int y
+ , final CellAttributes attr, final boolean clip) {
int X = x;
int Y = y;
* @param attr attributes to use (bold, foreColor, backColor)
*/
public final void putAll(final char ch, final CellAttributes attr) {
+
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
putCharXY(x, y, ch, attr);
* @param ch character to draw
*/
public final void putCharXY(final int x, final int y, final char ch) {
+
if ((x < clipLeft)
|| (x >= clipRight)
|| (y < clipTop)
* @param str string to draw
*/
public final void putStrXY(final int x, final int y, final String str) {
+
int i = x;
for (int j = 0; j < str.length(); j++) {
char ch = str.charAt(j);
* @param width new width
* @param height new height
*/
- private void reallocate(final int width, final int height) {
+ private synchronized void reallocate(final int width, final int height) {
if (logical != null) {
for (int row = 0; row < this.height; row++) {
for (int col = 0; col < this.width; col++) {
*
* @param width new screen width
*/
- public final void setWidth(final int width) {
+ public final synchronized void setWidth(final int width) {
reallocate(width, this.height);
}
*
* @param height new screen height
*/
- public final void setHeight(final int height) {
+ public final synchronized void setHeight(final int height) {
reallocate(this.width, height);
}
*
* @return current screen height
*/
- public final int getHeight() {
+ public final synchronized int getHeight() {
return this.height;
}
*
* @return current screen width
*/
- public final int getWidth() {
+ public final synchronized int getWidth() {
return this.width;
}
* Reset screen to not-bold, white-on-black. Also flushes the offset and
* clip variables.
*/
- public final void reset() {
+ public final synchronized void reset() {
dirty = true;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
}
- /**
- * Convert pixel column position to text cell column position.
- *
- * @param x pixel column position
- * @return text cell column position
- */
- public int textColumn(final int x) {
- Insets insets = frame.getInsets();
- return ((x - insets.left) / textWidth);
- }
-
- /**
- * Convert pixel row position to text cell row position.
- *
- * @param y pixel row position
- * @return text cell row position
- */
- public int textRow(final int y) {
- Insets insets = frame.getInsets();
- return ((y - insets.top) / textHeight);
- }
-
}