X-Git-Url: http://git.nikiroo.be/?p=jvcard.git;a=blobdiff_plain;f=src%2Fcom%2Fgooglecode%2Flanterna%2Fgui2%2FTextBox.java;fp=src%2Fcom%2Fgooglecode%2Flanterna%2Fgui2%2FTextBox.java;h=0000000000000000000000000000000000000000;hp=b0edcc8f384886d3958d5ba9f17915fa33ca3c09;hb=f06c81000632cfb5f525ca458f719338f55f9f66;hpb=a73a906356c971b080c36368e71a15d87e8b8d31 diff --git a/src/com/googlecode/lanterna/gui2/TextBox.java b/src/com/googlecode/lanterna/gui2/TextBox.java deleted file mode 100644 index b0edcc8..0000000 --- a/src/com/googlecode/lanterna/gui2/TextBox.java +++ /dev/null @@ -1,783 +0,0 @@ -/* - * This file is part of lanterna (http://code.google.com/p/lanterna/). - * - * lanterna 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 Lesser 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 . - * - * Copyright (C) 2010-2015 Martin - */ -package com.googlecode.lanterna.gui2; - -import com.googlecode.lanterna.TerminalTextUtils; -import com.googlecode.lanterna.TerminalPosition; -import com.googlecode.lanterna.TerminalSize; -import com.googlecode.lanterna.input.KeyStroke; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -/** - * This component keeps a text content that is editable by the user. A TextBox can be single line or multiline and lets - * the user navigate the cursor in the text area by using the arrow keys, page up, page down, home and end. For - * multi-line {@code TextBox}:es, scrollbars will be automatically displayed if needed. - *

- * Size-wise, a {@code TextBox} should be hard-coded to a particular size, it's not good at guessing how large it should - * be. You can do this through the constructor. - */ -public class TextBox extends AbstractInteractableComponent { - - /** - * Enum value to force a {@code TextBox} to be either single line or multi line. This is usually auto-detected if - * the text box has some initial content by scanning that content for \n characters. - */ - public enum Style { - /** - * The {@code TextBox} contains a single line of text and is typically drawn on one row - */ - SINGLE_LINE, - /** - * The {@code TextBox} contains a none, one or many lines of text and is normally drawn over multiple lines - */ - MULTI_LINE, - ; - } - - private final List lines; - private final Style style; - - private TerminalPosition caretPosition; - private boolean caretWarp; - private boolean readOnly; - private boolean horizontalFocusSwitching; - private boolean verticalFocusSwitching; - private int maxLineLength; - private int longestRow; - private char unusedSpaceCharacter; - private Character mask; - private Pattern validationPattern; - - /** - * Default constructor, this creates a single-line {@code TextBox} of size 10 which is initially empty - */ - public TextBox() { - this(new TerminalSize(10, 1), "", Style.SINGLE_LINE); - } - - /** - * Constructor that creates a {@code TextBox} with an initial content and attempting to be big enough to display - * the whole text at once without scrollbars - * @param initialContent Initial content of the {@code TextBox} - */ - public TextBox(String initialContent) { - this(null, initialContent, initialContent.contains("\n") ? Style.MULTI_LINE : Style.SINGLE_LINE); - } - - /** - * Creates a {@code TextBox} that has an initial content and attempting to be big enough to display the whole text - * at once without scrollbars. - * - * @param initialContent Initial content of the {@code TextBox} - * @param style Forced style instead of auto-detecting - */ - public TextBox(String initialContent, Style style) { - this(null, initialContent, style); - } - - /** - * Creates a new empty {@code TextBox} with a specific size - * @param preferredSize Size of the {@code TextBox} - */ - public TextBox(TerminalSize preferredSize) { - this(preferredSize, (preferredSize != null && preferredSize.getRows() > 1) ? Style.MULTI_LINE : Style.SINGLE_LINE); - } - - /** - * Creates a new empty {@code TextBox} with a specific size and style - * @param preferredSize Size of the {@code TextBox} - * @param style Style to use - */ - public TextBox(TerminalSize preferredSize, Style style) { - this(preferredSize, "", style); - } - - /** - * Creates a new empty {@code TextBox} with a specific size and initial content - * @param preferredSize Size of the {@code TextBox} - * @param initialContent Initial content of the {@code TextBox} - */ - public TextBox(TerminalSize preferredSize, String initialContent) { - this(preferredSize, initialContent, (preferredSize != null && preferredSize.getRows() > 1) || initialContent.contains("\n") ? Style.MULTI_LINE : Style.SINGLE_LINE); - } - - /** - * Main constructor of the {@code TextBox} which decides size, initial content and style - * @param preferredSize Size of the {@code TextBox} - * @param initialContent Initial content of the {@code TextBox} - * @param style Style to use for this {@code TextBox}, instead of auto-detecting - */ - public TextBox(TerminalSize preferredSize, String initialContent, Style style) { - this.lines = new ArrayList(); - this.style = style; - this.readOnly = false; - this.caretWarp = false; - this.verticalFocusSwitching = true; - this.horizontalFocusSwitching = (style == Style.SINGLE_LINE); - this.caretPosition = TerminalPosition.TOP_LEFT_CORNER; - this.maxLineLength = -1; - this.longestRow = 1; //To fit the cursor - this.unusedSpaceCharacter = ' '; - this.mask = null; - this.validationPattern = null; - setText(initialContent); - if (preferredSize == null) { - preferredSize = new TerminalSize(Math.max(10, longestRow), lines.size()); - } - setPreferredSize(preferredSize); - } - - /** - * Sets a pattern on which the content of the text box is to be validated. For multi-line TextBox:s, the pattern is - * checked against each line individually, not the content as a whole. Partial matchings will not be allowed, the - * whole pattern must match, however, empty lines will always be allowed. When the user tried to modify the content - * of the TextBox in a way that does not match the pattern, the operation will be silently ignored. If you set this - * pattern to {@code null}, all validation is turned off. - * @param validationPattern Pattern to validate the lines in this TextBox against, or {@code null} to disable - * @return itself - */ - public synchronized TextBox setValidationPattern(Pattern validationPattern) { - if(validationPattern != null) { - for(String line: lines) { - if(!validated(line)) { - throw new IllegalStateException("TextBox validation pattern " + validationPattern + " does not match existing content"); - } - } - } - this.validationPattern = validationPattern; - return this; - } - - /** - * Updates the text content of the {@code TextBox} to the supplied string. - * @param text New text to assign to the {@code TextBox} - * @return Itself - */ - public synchronized TextBox setText(String text) { - String[] split = text.split("\n"); - lines.clear(); - longestRow = 1; - for(String line : split) { - addLine(line); - } - if(caretPosition.getRow() > lines.size() - 1) { - caretPosition = caretPosition.withRow(lines.size() - 1); - } - if(caretPosition.getColumn() > lines.get(caretPosition.getRow()).length()) { - caretPosition = caretPosition.withColumn(lines.get(caretPosition.getRow()).length()); - } - invalidate(); - return this; - } - - @Override - public TextBoxRenderer getRenderer() { - return (TextBoxRenderer)super.getRenderer(); - } - - /** - * Adds a single line to the {@code TextBox} at the end, this only works when in multi-line mode - * @param line Line to add at the end of the content in this {@code TextBox} - * @return Itself - */ - public synchronized TextBox addLine(String line) { - StringBuilder bob = new StringBuilder(); - for(int i = 0; i < line.length(); i++) { - char c = line.charAt(i); - if(c == '\n' && style == Style.MULTI_LINE) { - String string = bob.toString(); - int lineWidth = TerminalTextUtils.getColumnWidth(string); - lines.add(string); - if(longestRow < lineWidth + 1) { - longestRow = lineWidth + 1; - } - addLine(line.substring(i + 1)); - return this; - } - else if(Character.isISOControl(c)) { - continue; - } - - bob.append(c); - } - String string = bob.toString(); - if(!validated(string)) { - throw new IllegalStateException("TextBox validation pattern " + validationPattern + " does not match the supplied text"); - } - int lineWidth = TerminalTextUtils.getColumnWidth(string); - lines.add(string); - if(longestRow < lineWidth + 1) { - longestRow = lineWidth + 1; - } - invalidate(); - return this; - } - - /** - * Sets if the caret should jump to the beginning of the next line if right arrow is pressed while at the end of a - * line. Similarly, pressing left arrow at the beginning of a line will make the caret jump to the end of the - * previous line. This only makes sense for multi-line TextBox:es; for single-line ones it has no effect. By default - * this is {@code false}. - * @param caretWarp Whether the caret will warp at the beginning/end of lines - * @return Itself - */ - public TextBox setCaretWarp(boolean caretWarp) { - this.caretWarp = caretWarp; - return this; - } - - /** - * Checks whether caret warp mode is enabled or not. See {@code setCaretWarp} for more details. - * @return {@code true} if caret warp mode is enabled - */ - public boolean isCaretWarp() { - return caretWarp; - } - - /** - * Returns the position of the caret, as a {@code TerminalPosition} where the row and columns equals the coordinates - * in a multi-line {@code TextBox} and for single-line {@code TextBox} you can ignore the {@code row} component. - * @return Position of the text input caret - */ - public TerminalPosition getCaretPosition() { - return caretPosition; - } - - /** - * Returns the text in this {@code TextBox}, for multi-line mode all lines will be concatenated together with \n as - * separator. - * @return The text inside this {@code TextBox} - */ - public synchronized String getText() { - StringBuilder bob = new StringBuilder(lines.get(0)); - for(int i = 1; i < lines.size(); i++) { - bob.append("\n").append(lines.get(i)); - } - return bob.toString(); - } - - /** - * Helper method, it will return the content of the {@code TextBox} unless it's empty in which case it will return - * the supplied default value - * @param defaultValueIfEmpty Value to return if the {@code TextBox} is empty - * @return Text in the {@code TextBox} or {@code defaultValueIfEmpty} is the {@code TextBox} is empty - */ - public String getTextOrDefault(String defaultValueIfEmpty) { - String text = getText(); - if(text.isEmpty()) { - return defaultValueIfEmpty; - } - return text; - } - - /** - * Returns the current text mask, meaning the substitute to draw instead of the text inside the {@code TextBox}. - * This is normally used for password input fields so the password isn't shown - * @return Current text mask or {@code null} if there is no mask - */ - public Character getMask() { - return mask; - } - - /** - * Sets the current text mask, meaning the substitute to draw instead of the text inside the {@code TextBox}. - * This is normally used for password input fields so the password isn't shown - * @param mask New text mask or {@code null} if there is no mask - * @return Itself - */ - public TextBox setMask(Character mask) { - if(mask != null && TerminalTextUtils.isCharCJK(mask)) { - throw new IllegalArgumentException("Cannot use a CJK character as a mask"); - } - this.mask = mask; - invalidate(); - return this; - } - - /** - * Returns {@code true} if this {@code TextBox} is in read-only mode, meaning text input from the user through the - * keyboard is prevented - * @return {@code true} if this {@code TextBox} is in read-only mode - */ - public boolean isReadOnly() { - return readOnly; - } - - /** - * Sets the read-only mode of the {@code TextBox}, meaning text input from the user through the keyboard is - * prevented. The user can still focus and scroll through the text in this mode. - * @param readOnly If {@code true} then the {@code TextBox} will switch to read-only mode - * @return Itself - */ - public TextBox setReadOnly(boolean readOnly) { - this.readOnly = readOnly; - invalidate(); - return this; - } - - /** - * If {@code true}, the component will switch to the next available component above if the cursor is at the top of - * the TextBox and the user presses the 'up' array key, or switch to the next available component below if the - * cursor is at the bottom of the TextBox and the user presses the 'down' array key. The means that for single-line - * TextBox:es, pressing up and down will always switch focus. - * @return {@code true} if vertical focus switching is enabled - */ - public boolean isVerticalFocusSwitching() { - return verticalFocusSwitching; - } - - /** - * If set to {@code true}, the component will switch to the next available component above if the cursor is at the - * top of the TextBox and the user presses the 'up' array key, or switch to the next available component below if - * the cursor is at the bottom of the TextBox and the user presses the 'down' array key. The means that for - * single-line TextBox:es, pressing up and down will always switch focus with this mode enabled. - * @param verticalFocusSwitching If called with true, vertical focus switching will be enabled - * @return Itself - */ - public TextBox setVerticalFocusSwitching(boolean verticalFocusSwitching) { - this.verticalFocusSwitching = verticalFocusSwitching; - return this; - } - - /** - * If {@code true}, the TextBox will switch focus to the next available component to the left if the cursor in the - * TextBox is at the left-most position (index 0) on the row and the user pressed the 'left' arrow key, or vice - * versa for pressing the 'right' arrow key when the cursor in at the right-most position of the current row. - * @return {@code true} if horizontal focus switching is enabled - */ - public boolean isHorizontalFocusSwitching() { - return horizontalFocusSwitching; - } - - /** - * If set to {@code true}, the TextBox will switch focus to the next available component to the left if the cursor - * in the TextBox is at the left-most position (index 0) on the row and the user pressed the 'left' arrow key, or - * vice versa for pressing the 'right' arrow key when the cursor in at the right-most position of the current row. - * @param horizontalFocusSwitching If called with true, horizontal focus switching will be enabled - * @return Itself - */ - public TextBox setHorizontalFocusSwitching(boolean horizontalFocusSwitching) { - this.horizontalFocusSwitching = horizontalFocusSwitching; - return this; - } - - /** - * Returns the line on the specific row. For non-multiline TextBox:es, calling this with index set to 0 will return - * the same as calling {@code getText()}. If the row index is invalid (less than zero or equals or larger than the - * number of rows), this method will throw IndexOutOfBoundsException. - * @param index - * @return The line at the specified index, as a String - * @throws IndexOutOfBoundsException if the row index is less than zero or too large - */ - public synchronized String getLine(int index) { - return lines.get(index); - } - - /** - * Returns the number of lines currently in this TextBox. For single-line TextBox:es, this will always return 1. - * @return Number of lines of text currently in this TextBox - */ - public synchronized int getLineCount() { - return lines.size(); - } - - @Override - protected TextBoxRenderer createDefaultRenderer() { - return new DefaultTextBoxRenderer(); - } - - @Override - public synchronized Result handleKeyStroke(KeyStroke keyStroke) { - if(readOnly) { - return handleKeyStrokeReadOnly(keyStroke); - } - String line = lines.get(caretPosition.getRow()); - switch(keyStroke.getKeyType()) { - case Character: - if(maxLineLength == -1 || maxLineLength > line.length() + 1) { - line = line.substring(0, caretPosition.getColumn()) + keyStroke.getCharacter() + line.substring(caretPosition.getColumn()); - if(validated(line)) { - lines.set(caretPosition.getRow(), line); - caretPosition = caretPosition.withRelativeColumn(1); - } - } - return Result.HANDLED; - case Backspace: - if(caretPosition.getColumn() > 0) { - line = line.substring(0, caretPosition.getColumn() - 1) + line.substring(caretPosition.getColumn()); - if(validated(line)) { - lines.set(caretPosition.getRow(), line); - caretPosition = caretPosition.withRelativeColumn(-1); - } - } - else if(style == Style.MULTI_LINE && caretPosition.getRow() > 0) { - String concatenatedLines = lines.get(caretPosition.getRow() - 1) + line; - if(validated(concatenatedLines)) { - lines.remove(caretPosition.getRow()); - caretPosition = caretPosition.withRelativeRow(-1); - caretPosition = caretPosition.withColumn(lines.get(caretPosition.getRow()).length()); - lines.set(caretPosition.getRow(), concatenatedLines); - } - } - return Result.HANDLED; - case Delete: - if(caretPosition.getColumn() < line.length()) { - line = line.substring(0, caretPosition.getColumn()) + line.substring(caretPosition.getColumn() + 1); - if(validated(line)) { - lines.set(caretPosition.getRow(), line); - } - } - else if(style == Style.MULTI_LINE && caretPosition.getRow() < lines.size() - 1) { - String concatenatedLines = line + lines.get(caretPosition.getRow() + 1); - if(validated(concatenatedLines)) { - lines.set(caretPosition.getRow(), concatenatedLines); - lines.remove(caretPosition.getRow() + 1); - } - } - return Result.HANDLED; - case ArrowLeft: - if(caretPosition.getColumn() > 0) { - caretPosition = caretPosition.withRelativeColumn(-1); - } - else if(style == Style.MULTI_LINE && caretWarp && caretPosition.getRow() > 0) { - caretPosition = caretPosition.withRelativeRow(-1); - caretPosition = caretPosition.withColumn(lines.get(caretPosition.getRow()).length()); - } - else if(horizontalFocusSwitching) { - return Result.MOVE_FOCUS_LEFT; - } - return Result.HANDLED; - case ArrowRight: - if(caretPosition.getColumn() < lines.get(caretPosition.getRow()).length()) { - caretPosition = caretPosition.withRelativeColumn(1); - } - else if(style == Style.MULTI_LINE && caretWarp && caretPosition.getRow() < lines.size() - 1) { - caretPosition = caretPosition.withRelativeRow(1); - caretPosition = caretPosition.withColumn(0); - } - else if(horizontalFocusSwitching) { - return Result.MOVE_FOCUS_RIGHT; - } - return Result.HANDLED; - case ArrowUp: - if(caretPosition.getRow() > 0) { - int trueColumnPosition = TerminalTextUtils.getColumnIndex(lines.get(caretPosition.getRow()), caretPosition.getColumn()); - caretPosition = caretPosition.withRelativeRow(-1); - line = lines.get(caretPosition.getRow()); - if(trueColumnPosition > TerminalTextUtils.getColumnWidth(line)) { - caretPosition = caretPosition.withColumn(line.length()); - } - else { - caretPosition = caretPosition.withColumn(TerminalTextUtils.getStringCharacterIndex(line, trueColumnPosition)); - } - } - else if(verticalFocusSwitching) { - return Result.MOVE_FOCUS_UP; - } - return Result.HANDLED; - case ArrowDown: - if(caretPosition.getRow() < lines.size() - 1) { - int trueColumnPosition = TerminalTextUtils.getColumnIndex(lines.get(caretPosition.getRow()), caretPosition.getColumn()); - caretPosition = caretPosition.withRelativeRow(1); - line = lines.get(caretPosition.getRow()); - if(trueColumnPosition > TerminalTextUtils.getColumnWidth(line)) { - caretPosition = caretPosition.withColumn(line.length()); - } - else { - caretPosition = caretPosition.withColumn(TerminalTextUtils.getStringCharacterIndex(line, trueColumnPosition)); - } - } - else if(verticalFocusSwitching) { - return Result.MOVE_FOCUS_DOWN; - } - return Result.HANDLED; - case End: - caretPosition = caretPosition.withColumn(line.length()); - return Result.HANDLED; - case Enter: - if(style == Style.SINGLE_LINE) { - return Result.MOVE_FOCUS_NEXT; - } - String newLine = line.substring(caretPosition.getColumn()); - String oldLine = line.substring(0, caretPosition.getColumn()); - if(validated(newLine) && validated(oldLine)) { - lines.set(caretPosition.getRow(), oldLine); - lines.add(caretPosition.getRow() + 1, newLine); - caretPosition = caretPosition.withColumn(0).withRelativeRow(1); - } - return Result.HANDLED; - case Home: - caretPosition = caretPosition.withColumn(0); - return Result.HANDLED; - case PageDown: - caretPosition = caretPosition.withRelativeRow(getSize().getRows()); - if(caretPosition.getRow() > lines.size() - 1) { - caretPosition = caretPosition.withRow(lines.size() - 1); - } - if(lines.get(caretPosition.getRow()).length() < caretPosition.getColumn()) { - caretPosition = caretPosition.withColumn(lines.get(caretPosition.getRow()).length()); - } - return Result.HANDLED; - case PageUp: - caretPosition = caretPosition.withRelativeRow(-getSize().getRows()); - if(caretPosition.getRow() < 0) { - caretPosition = caretPosition.withRow(0); - } - if(lines.get(caretPosition.getRow()).length() < caretPosition.getColumn()) { - caretPosition = caretPosition.withColumn(lines.get(caretPosition.getRow()).length()); - } - return Result.HANDLED; - default: - } - return super.handleKeyStroke(keyStroke); - } - - private boolean validated(String line) { - return validationPattern == null || line.isEmpty() || validationPattern.matcher(line).matches(); - } - - private Result handleKeyStrokeReadOnly(KeyStroke keyStroke) { - switch (keyStroke.getKeyType()) { - case ArrowLeft: - if(getRenderer().getViewTopLeft().getColumn() == 0 && horizontalFocusSwitching) { - return Result.MOVE_FOCUS_LEFT; - } - getRenderer().setViewTopLeft(getRenderer().getViewTopLeft().withRelativeColumn(-1)); - return Result.HANDLED; - case ArrowRight: - if(getRenderer().getViewTopLeft().getColumn() + getSize().getColumns() == longestRow && horizontalFocusSwitching) { - return Result.MOVE_FOCUS_RIGHT; - } - getRenderer().setViewTopLeft(getRenderer().getViewTopLeft().withRelativeColumn(1)); - return Result.HANDLED; - case ArrowUp: - if(getRenderer().getViewTopLeft().getRow() == 0 && verticalFocusSwitching) { - return Result.MOVE_FOCUS_UP; - } - getRenderer().setViewTopLeft(getRenderer().getViewTopLeft().withRelativeRow(-1)); - return Result.HANDLED; - case ArrowDown: - if(getRenderer().getViewTopLeft().getRow() + getSize().getRows() == lines.size() && verticalFocusSwitching) { - return Result.MOVE_FOCUS_DOWN; - } - getRenderer().setViewTopLeft(getRenderer().getViewTopLeft().withRelativeRow(1)); - return Result.HANDLED; - case Home: - getRenderer().setViewTopLeft(TerminalPosition.TOP_LEFT_CORNER); - return Result.HANDLED; - case End: - getRenderer().setViewTopLeft(TerminalPosition.TOP_LEFT_CORNER.withRow(getLineCount() - getSize().getRows())); - return Result.HANDLED; - case PageDown: - getRenderer().setViewTopLeft(getRenderer().getViewTopLeft().withRelativeRow(getSize().getRows())); - return Result.HANDLED; - case PageUp: - getRenderer().setViewTopLeft(getRenderer().getViewTopLeft().withRelativeRow(-getSize().getRows())); - return Result.HANDLED; - default: - } - return super.handleKeyStroke(keyStroke); - } - - /** - * Helper interface that doesn't add any new methods but makes coding new text box renderers a little bit more clear - */ - public interface TextBoxRenderer extends InteractableRenderer { - TerminalPosition getViewTopLeft(); - void setViewTopLeft(TerminalPosition position); - } - - /** - * This is the default text box renderer that is used if you don't override anything. With this renderer, the text - * box is filled with a solid background color and the text is drawn on top of it. Scrollbars are added for - * multi-line text whenever the text inside the {@code TextBox} does not fit in the available area. - */ - public static class DefaultTextBoxRenderer implements TextBoxRenderer { - private TerminalPosition viewTopLeft; - private ScrollBar verticalScrollBar; - private ScrollBar horizontalScrollBar; - private boolean hideScrollBars; - - /** - * Default constructor - */ - public DefaultTextBoxRenderer() { - viewTopLeft = TerminalPosition.TOP_LEFT_CORNER; - verticalScrollBar = new ScrollBar(Direction.VERTICAL); - horizontalScrollBar = new ScrollBar(Direction.HORIZONTAL); - hideScrollBars = false; - } - - @Override - public TerminalPosition getViewTopLeft() { - return viewTopLeft; - } - - @Override - public void setViewTopLeft(TerminalPosition position) { - if(position.getColumn() < 0) { - position = position.withColumn(0); - } - if(position.getRow() < 0) { - position = position.withRow(0); - } - viewTopLeft = position; - } - - @Override - public TerminalPosition getCursorLocation(TextBox component) { - if(component.isReadOnly()) { - return null; - } - - //Adjust caret position if necessary - TerminalPosition caretPosition = component.getCaretPosition(); - String line = component.getLine(caretPosition.getRow()); - caretPosition = caretPosition.withColumn(Math.min(caretPosition.getColumn(), line.length())); - - return caretPosition - .withColumn(TerminalTextUtils.getColumnIndex(line, caretPosition.getColumn())) - .withRelativeColumn(-viewTopLeft.getColumn()) - .withRelativeRow(-viewTopLeft.getRow()); - } - - @Override - public TerminalSize getPreferredSize(TextBox component) { - return new TerminalSize(component.longestRow, component.lines.size()); - } - - /** - * Controls whether scrollbars should be visible or not when a multi-line {@code TextBox} has more content than - * it can draw in the area it was assigned (default: false) - * @param hideScrollBars If {@code true}, don't show scrollbars if the multi-line content is bigger than the - * area - */ - public void setHideScrollBars(boolean hideScrollBars) { - this.hideScrollBars = hideScrollBars; - } - - @Override - public void drawComponent(TextGUIGraphics graphics, TextBox component) { - TerminalSize realTextArea = graphics.getSize(); - if(realTextArea.getRows() == 0 || realTextArea.getColumns() == 0) { - return; - } - boolean drawVerticalScrollBar = false; - boolean drawHorizontalScrollBar = false; - int textBoxLineCount = component.getLineCount(); - if(!hideScrollBars && textBoxLineCount > realTextArea.getRows() && realTextArea.getColumns() > 1) { - realTextArea = realTextArea.withRelativeColumns(-1); - drawVerticalScrollBar = true; - } - if(!hideScrollBars && component.longestRow > realTextArea.getColumns() && realTextArea.getRows() > 1) { - realTextArea = realTextArea.withRelativeRows(-1); - drawHorizontalScrollBar = true; - if(textBoxLineCount > realTextArea.getRows() && realTextArea.getRows() == graphics.getSize().getRows()) { - realTextArea = realTextArea.withRelativeColumns(-1); - drawVerticalScrollBar = true; - } - } - - drawTextArea(graphics.newTextGraphics(TerminalPosition.TOP_LEFT_CORNER, realTextArea), component); - - //Draw scrollbars, if any - if(drawVerticalScrollBar) { - verticalScrollBar.setViewSize(realTextArea.getRows()); - verticalScrollBar.setScrollMaximum(textBoxLineCount); - verticalScrollBar.setScrollPosition(viewTopLeft.getRow()); - verticalScrollBar.draw(graphics.newTextGraphics( - new TerminalPosition(graphics.getSize().getColumns() - 1, 0), - new TerminalSize(1, graphics.getSize().getRows() - 1))); - } - if(drawHorizontalScrollBar) { - horizontalScrollBar.setViewSize(realTextArea.getColumns()); - horizontalScrollBar.setScrollMaximum(component.longestRow - 1); - horizontalScrollBar.setScrollPosition(viewTopLeft.getColumn()); - horizontalScrollBar.draw(graphics.newTextGraphics( - new TerminalPosition(0, graphics.getSize().getRows() - 1), - new TerminalSize(graphics.getSize().getColumns() - 1, 1))); - } - } - - private void drawTextArea(TextGUIGraphics graphics, TextBox component) { - TerminalSize textAreaSize = graphics.getSize(); - if(viewTopLeft.getColumn() + textAreaSize.getColumns() > component.longestRow) { - viewTopLeft = viewTopLeft.withColumn(component.longestRow - textAreaSize.getColumns()); - if(viewTopLeft.getColumn() < 0) { - viewTopLeft = viewTopLeft.withColumn(0); - } - } - if(viewTopLeft.getRow() + textAreaSize.getRows() > component.getLineCount()) { - viewTopLeft = viewTopLeft.withRow(component.getLineCount() - textAreaSize.getRows()); - if(viewTopLeft.getRow() < 0) { - viewTopLeft = viewTopLeft.withRow(0); - } - } - if (component.isFocused()) { - graphics.applyThemeStyle(graphics.getThemeDefinition(TextBox.class).getActive()); - } - else { - graphics.applyThemeStyle(graphics.getThemeDefinition(TextBox.class).getNormal()); - } - graphics.fill(component.unusedSpaceCharacter); - - if(!component.isReadOnly()) { - //Adjust caret position if necessary - TerminalPosition caretPosition = component.getCaretPosition(); - String caretLine = component.getLine(caretPosition.getRow()); - caretPosition = caretPosition.withColumn(Math.min(caretPosition.getColumn(), caretLine.length())); - - //Adjust the view if necessary - int trueColumnPosition = TerminalTextUtils.getColumnIndex(caretLine, caretPosition.getColumn()); - if (trueColumnPosition < viewTopLeft.getColumn()) { - viewTopLeft = viewTopLeft.withColumn(trueColumnPosition); - } - else if (trueColumnPosition >= textAreaSize.getColumns() + viewTopLeft.getColumn()) { - viewTopLeft = viewTopLeft.withColumn(trueColumnPosition - textAreaSize.getColumns() + 1); - } - if (caretPosition.getRow() < viewTopLeft.getRow()) { - viewTopLeft = viewTopLeft.withRow(caretPosition.getRow()); - } - else if (caretPosition.getRow() >= textAreaSize.getRows() + viewTopLeft.getRow()) { - viewTopLeft = viewTopLeft.withRow(caretPosition.getRow() - textAreaSize.getRows() + 1); - } - - //Additional corner-case for CJK characters - if(trueColumnPosition - viewTopLeft.getColumn() == graphics.getSize().getColumns() - 1) { - if(caretLine.length() > caretPosition.getColumn() && - TerminalTextUtils.isCharCJK(caretLine.charAt(caretPosition.getColumn()))) { - viewTopLeft = viewTopLeft.withRelativeColumn(1); - } - } - } - - for (int row = 0; row < textAreaSize.getRows(); row++) { - int rowIndex = row + viewTopLeft.getRow(); - if(rowIndex >= component.lines.size()) { - continue; - } - String line = component.lines.get(rowIndex); - graphics.putString(0, row, TerminalTextUtils.fitString(line, viewTopLeft.getColumn(), textAreaSize.getColumns())); - } - } - } -}