+ // ------------------------------------------------------------------------
+ // TWidget ----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Draw the text field.
+ */
+ @Override
+ public void draw() {
+ CellAttributes fieldColor;
+
+ if (isAbsoluteActive()) {
+ fieldColor = getTheme().getColor(activeColorKey);
+ } else {
+ fieldColor = getTheme().getColor(inactiveColorKey);
+ }
+
+ int end = windowStart + getWidth();
+ if (end > StringUtils.width(text)) {
+ end = StringUtils.width(text);
+ }
+ hLineXY(0, 0, getWidth(), backgroundChar, fieldColor);
+ putStringXY(0, 0, text.substring(screenToTextPosition(windowStart),
+ screenToTextPosition(end)), fieldColor);
+
+ // Fix the cursor, it will be rendered by TApplication.drawAll().
+ updateCursor();
+ }
+
+ // ------------------------------------------------------------------------
+ // TField -----------------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Convert a char (codepoint) to a string.
+ *
+ * @param ch the char
+ * @return the string
+ */
+ private String codePointString(final int ch) {
+ StringBuilder sb = new StringBuilder(1);
+ sb.append(Character.toChars(ch));
+ assert (Character.charCount(ch) == sb.length());
+ return sb.toString();
+ }
+
+ /**
+ * Get field background character.
+ *
+ * @return background character
+ */
+ public final int getBackgroundChar() {
+ return backgroundChar;
+ }
+
+ /**
+ * Set field background character.
+ *
+ * @param backgroundChar the background character
+ */
+ public void setBackgroundChar(final int backgroundChar) {
+ this.backgroundChar = backgroundChar;
+ }
+
+ /**
+ * Get field text.
+ *
+ * @return field text
+ */
+ public final String getText() {
+ return text;
+ }
+
+ /**
+ * Set field text.
+ *
+ * @param text the new field text
+ */
+ public void setText(final String text) {
+ assert (text != null);
+ this.text = text;
+ position = 0;
+ windowStart = 0;
+ }
+
+ /**
+ * Dispatch to the action function.
+ *
+ * @param enter if true, the user pressed Enter, else this was an update
+ * to the text.
+ */
+ protected void dispatch(final boolean enter) {
+ if (enter) {
+ if (enterAction != null) {
+ enterAction.DO();
+ }
+ } else {
+ if (updateAction != null) {
+ updateAction.DO();
+ }
+ }
+ }
+
+ /**
+ * Determine string position from screen position.
+ *
+ * @param screenPosition the position on screen
+ * @return the equivalent position in text
+ */
+ protected int screenToTextPosition(final int screenPosition) {
+ if (screenPosition == 0) {
+ return 0;
+ }
+
+ int n = 0;
+ for (int i = 0; i < text.length(); i++) {
+ n += StringUtils.width(text.codePointAt(i));
+ if (n >= screenPosition) {
+ return i + 1;
+ }
+ }
+ // screenPosition exceeds the available text length.
+ throw new IndexOutOfBoundsException("screenPosition " + screenPosition +
+ " exceeds available text length " + text.length());
+ }
+
+ /**
+ * Update the visible cursor position to match the location of position
+ * and windowStart.
+ */
+ protected void updateCursor() {
+ if ((screenPosition > getWidth()) && fixed) {
+ setCursorX(getWidth());
+ } else if ((screenPosition - windowStart >= getWidth()) && !fixed) {
+ setCursorX(getWidth() - 1);
+ } else {
+ setCursorX(screenPosition - windowStart);
+ }
+ }
+
+ /**
+ * Normalize windowStart such that most of the field data if visible.
+ */
+ protected void normalizeWindowStart() {
+ if (fixed) {
+ // windowStart had better be zero, there is nothing to do here.
+ assert (windowStart == 0);
+ return;
+ }
+ windowStart = screenPosition - (getWidth() - 1);
+ if (windowStart < 0) {
+ windowStart = 0;
+ }
+
+ updateCursor();
+ }
+