Version 2.0.0: update sources
[jvcard.git] / src / com / googlecode / lanterna / gui2 / TextBox.java
diff --git a/src/com/googlecode/lanterna/gui2/TextBox.java b/src/com/googlecode/lanterna/gui2/TextBox.java
deleted file mode 100644 (file)
index b0edcc8..0000000
+++ /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 <http://www.gnu.org/licenses/>.
- * 
- * 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.
- * <p>
- * 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<TextBox> {
-
-    /**
-     * 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<String> 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<String>();
-        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<TextBox> {
-        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()));
-            }
-        }
-    }
-}