/*
* Copyright 2010, Maarten Billemont
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.lyndir.lanterna.view;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Optional;
import com.googlecode.lanterna.input.Key;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.terminal.Terminal;
import java.util.Deque;
import java.util.LinkedList;
import javax.annotation.Nonnull;
/**
* @author lhunath, 2013-07-21
*/
public abstract class InputView extends View {
private final Deque<String> inputHistory = new LinkedList<>();
private final Deque<String> inputFuture = new LinkedList<>();
private final StringBuilder inputText = new StringBuilder();
private Inset textPadding = new Inset( 0, 2, 0, 2 );
private String promptText = "> ";
private int cursorOffset;
private Terminal.Color textColor;
private Terminal.Color promptTextColor;
private Terminal.Color backgroundColor;
private TextView controlTextView;
@Override
protected void drawForeground(final Screen screen) {
super.drawForeground( screen );
Box contentBox = getContentBoxOnScreen().shrink( getTextPadding() );
int inputOnScreenLength = Math.min( getInputText().length(), contentBox.getSize().getWidth() - getPromptText().length() );
String inputOnScreen = getInputText().substring( getInputText().length() - inputOnScreenLength, getInputText().length() );
screen.putString( contentBox.getLeft(), contentBox.getTop(), getPromptText(), //
getPromptTextColor(), getBackgroundColor() );
screen.putString( contentBox.getLeft() + getPromptText().length(), contentBox.getTop(), inputOnScreen, //
getTextColor(), getBackgroundColor() );
screen.setCursorPosition( contentBox.getLeft() + getPromptText().length() + getCursorOffset(), contentBox.getTop() );
}
@Nonnull
@Override
public Optional<?> layoutValue(final LayoutParameter layoutParameter) {
if (layoutParameter == LinearView.Parameters.DESIRED_SPACE)
return Optional.of( 1 );
return super.layoutValue( layoutParameter );
}
@Override
protected boolean onKey(final Key key) {
// BACKSPACE: Delete last character
if (key.getKind() == Key.Kind.Backspace) {
if (key.isAltPressed())
deleteInputTextWord();
else
deleteInputTextCharacter();
}
// ESC: Erase input.
else if (key.getKind() == Key.Kind.Escape)
clearInputText();
// ENTER: Execute input.
else if (key.getKind() == Key.Kind.Enter) {
if (inputText.length() > 0) {
onEnterText( getInputText() );
getInputHistory().push( getInputText() );
clearInputText();
}
}
// UP/DOWN: History navigation, ALT UP/DOWN: Scroll Control View
else if (key.getKind() == Key.Kind.ArrowUp) {
if (key.isAltPressed() && getControlTextView() != null)
getControlTextView().updateTextOffset( 1 );
else {
if (!getInputHistory().isEmpty()) {
if (!getInputText().isEmpty())
getInputFuture().push( getInputText() );
clearInputText();
addInputText( getInputHistory().pop() );
}
}
} else if (key.getKind() == Key.Kind.ArrowDown) {
if (key.isAltPressed() && getControlTextView() != null)
getControlTextView().updateTextOffset( -1 );
else {
if (!getInputText().isEmpty())
getInputHistory().push( getInputText() );
clearInputText();
if (!getInputFuture().isEmpty()) {
addInputText( getInputFuture().pop() );
}
}
}
// LEFT/RIGHT: Cursor character navigation, ALT LEFT/RIGHT: Cursor word navigation.
else if (key.getKind() == Key.Kind.ArrowLeft) {
if (key.isAltPressed())
moveCursorOffset( getPreviousWordOffset() - getCursorOffset() );
else
moveCursorOffset( -1 );
} else if (key.getKind() == Key.Kind.ArrowRight) {
if (key.isAltPressed())
moveCursorOffset( getNextWordOffset() - getCursorOffset() );
else
moveCursorOffset( 1 );
}
// OTHERS: Add character to input.
else
addInputText( String.valueOf( key.getCharacter() ) );
return true;
}
private int getPreviousWordOffset() {
String textToCursor = getInputText().substring( 0, getCursorOffset() );
int previousWordOffset = textToCursor.lastIndexOf( ' ' );
return previousWordOffset == -1? 0: previousWordOffset;
}
private int getNextWordOffset() {
String textFromCursor = getInputText().substring( getCursorOffset(), getInputText().length() );
int nextWordOffset = textFromCursor.indexOf( ' ', 1 );
return nextWordOffset == -1? getInputText().length(): getCursorOffset() + nextWordOffset;
}
protected abstract void onEnterText(String text);
private void clearInputText() {
inputText.delete( 0, inputText.length() );
moveCursorOffset( -getCursorOffset() );
}
public String getPromptText() {
return promptText;
}
public void setPromptText(final String promptText) {
this.promptText = promptText;
}
public Terminal.Color getPromptTextColor() {
return ifNotNullElse( promptTextColor, getTheme().promptFg() );
}
public void setPromptTextColor(final Terminal.Color promptTextColor) {
this.promptTextColor = promptTextColor;
}
public Inset getTextPadding() {
return textPadding;
}
public void setTextPadding(final Inset textPadding) {
this.textPadding = textPadding;
}
@Override
public Terminal.Color getBackgroundColor() {
return ifNotNullElse( backgroundColor, getTheme().barBg() );
}
@Override
public void setBackgroundColor(final Terminal.Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
public Terminal.Color getTextColor() {
return ifNotNullElse( textColor, getTheme().textFg() );
}
public void setTextColor(final Terminal.Color textColor) {
this.textColor = textColor;
}
public TextView getControlTextView() {
return controlTextView;
}
public void setControlTextView(final TextView controlTextView) {
this.controlTextView = controlTextView;
}
@Nonnull
public String getInputText() {
return inputText.toString();
}
public void addInputText(@Nonnull final String text) {
inputText.insert( getCursorOffset(), text );
moveCursorOffset( text.length() );
}
public boolean deleteInputTextCharacter() {
if (inputText.length() == 0)
return false;
inputText.delete( cursorOffset - 1, cursorOffset );
moveCursorOffset( -1 );
return true;
}
public boolean deleteInputTextWord() {
if (inputText.length() == 0)
return false;
int previousWordOffset = getPreviousWordOffset();
inputText.delete( previousWordOffset, cursorOffset );
moveCursorOffset( previousWordOffset - cursorOffset );
return true;
}
public Deque<String> getInputHistory() {
return inputHistory;
}
public Deque<String> getInputFuture() {
return inputFuture;
}
public int getCursorOffset() {
return cursorOffset;
}
public void moveCursorOffset(final int dOffset) {
cursorOffset = Math.min( getInputText().length(), Math.max( 0, cursorOffset + dOffset ) );
}
}