/*
* 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-2012 Martin
*/
package com.googlecode.lanterna.gui.component;
import com.googlecode.lanterna.gui.TextGraphics;
import com.googlecode.lanterna.gui.Theme;
import com.googlecode.lanterna.input.Key;
import com.googlecode.lanterna.terminal.ACS;
import com.googlecode.lanterna.terminal.TerminalPosition;
import com.googlecode.lanterna.terminal.TerminalSize;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This component is designed for displaying large chunks of text. If the text
* is larger than the component, it will display scrollbars and letting the
* user scroll through the text using the arrow keys.
* @author mberglun
*/
public class TextArea extends AbstractInteractableComponent
{
private final List<String> lines;
private final TerminalSize preferredSize;
private TerminalSize lastSize;
private TerminalSize maximumSize;
private TerminalSize minimumSize;
private int longestLine;
private int scrollTopIndex;
private int scrollLeftIndex;
public TextArea() {
this("");
}
public TextArea(String text) {
this(new TerminalSize(0, 0), text);
}
public TextArea(TerminalSize preferredSize, String text) {
if(text == null)
text = "";
this.lines = new ArrayList<String>();
this.preferredSize = preferredSize;
this.scrollTopIndex = 0;
this.scrollLeftIndex = 0;
this.maximumSize = null;
this.minimumSize = new TerminalSize(10, 3);
this.lastSize = null;
lines.addAll(Arrays.asList(text.split("\n")));
scanForLongestLine();
}
@Override
protected TerminalSize calculatePreferredSize() {
int width = preferredSize.getColumns() > 0 ? preferredSize.getColumns() : longestLine + 1;
int height = preferredSize.getRows() > 0 ? preferredSize.getRows() : lines.size() + 1;
if(maximumSize != null) {
if(width > maximumSize.getColumns())
width = maximumSize.getColumns();
if(height > maximumSize.getRows())
height = maximumSize.getRows();
}
if(minimumSize != null) {
if(width < minimumSize.getColumns())
width = minimumSize.getColumns();
if(height < minimumSize.getRows())
height = minimumSize.getRows();
}
return new TerminalSize(width, height);
}
@Override
public void repaint(TextGraphics graphics)
{
lastSize = new TerminalSize(graphics.getWidth(), graphics.getHeight());
//Do we need to recalculate the scroll position?
//This code would be triggered by resizing the window when the scroll
//position is at the bottom
if(lines.size() > graphics.getHeight() &&
lines.size() - scrollTopIndex < graphics.getHeight()) {
scrollTopIndex = lines.size() - graphics.getHeight();
}
if(longestLine > graphics.getWidth() &&
longestLine - scrollLeftIndex < graphics.getWidth()) {
scrollLeftIndex = longestLine - graphics.getWidth();
}
if(hasFocus())
graphics.applyTheme(Theme.Category.TEXTBOX_FOCUSED);
else
graphics.applyTheme(Theme.Category.TEXTBOX);
graphics.fillArea(' ');
for(int i = scrollTopIndex; i < lines.size(); i++) {
if(i - scrollTopIndex >= graphics.getHeight())
break;
printItem(graphics, 0, 0 + i - scrollTopIndex, lines.get(i));
}
boolean hasSetHotSpot = false;
if(lines.size() > graphics.getHeight()) {
graphics.applyTheme(Theme.Category.DIALOG_AREA);
graphics.drawString(graphics.getWidth() - 1, 0, ACS.ARROW_UP + "");
graphics.applyTheme(Theme.Category.DIALOG_AREA);
for(int i = 1; i < graphics.getHeight() - 1; i++)
graphics.drawString(graphics.getWidth() - 1, i, ACS.BLOCK_MIDDLE + "");
graphics.applyTheme(Theme.Category.DIALOG_AREA);
graphics.drawString(graphics.getWidth() - 1, graphics.getHeight() - 1, ACS.ARROW_DOWN + "");
//Finally print the 'tick'
int scrollableSize = lines.size() - graphics.getHeight();
double position = (double)scrollTopIndex / ((double)scrollableSize);
int tickPosition = (int)(((double)graphics.getHeight() - 3.0) * position);
graphics.applyTheme(Theme.Category.SHADOW);
graphics.drawString(graphics.getWidth() - 1, 1 + tickPosition, " ");
if(hasFocus()) {
setHotspot(graphics.translateToGlobalCoordinates(new TerminalPosition(graphics.getWidth() - 1, 1 + tickPosition)));
hasSetHotSpot = true;
}
}
if(longestLine > graphics.getWidth()) {
graphics.applyTheme(Theme.Category.DIALOG_AREA);
graphics.drawString(0, graphics.getHeight() - 1, ACS.ARROW_LEFT + "");
graphics.applyTheme(Theme.Category.DIALOG_AREA);
for(int i = 1; i < graphics.getWidth() - 2; i++)
graphics.drawString(i, graphics.getHeight() - 1, ACS.BLOCK_MIDDLE + "");
graphics.applyTheme(Theme.Category.DIALOG_AREA);
graphics.drawString(graphics.getWidth() - 2, graphics.getHeight() - 1, ACS.ARROW_RIGHT + "");
//Finally print the 'tick'
int scrollableSize = longestLine - graphics.getWidth();
double position = (double)scrollLeftIndex / ((double)scrollableSize);
int tickPosition = (int)(((double)graphics.getWidth() - 4.0) * position);
graphics.applyTheme(Theme.Category.SHADOW);
graphics.drawString(1 + tickPosition, graphics.getHeight() - 1, " ");
}
if(!hasSetHotSpot)
setHotspot(graphics.translateToGlobalCoordinates(new TerminalPosition(0, 0)));
}
@Override
public Result keyboardInteraction(Key key)
{
try {
switch(key.getKind()) {
case Tab:
case Enter:
return Result.NEXT_INTERACTABLE_RIGHT;
case ReverseTab:
return Result.PREVIOUS_INTERACTABLE_LEFT;
case ArrowRight:
if(lastSize != null && scrollLeftIndex < longestLine - lastSize.getColumns())
scrollLeftIndex++;
break;
case ArrowLeft:
if(scrollLeftIndex > 0)
scrollLeftIndex--;
break;
case ArrowDown:
if(lastSize != null && scrollTopIndex < lines.size() - lastSize.getRows())
scrollTopIndex++;
break;
case ArrowUp:
if(scrollTopIndex > 0)
scrollTopIndex--;
break;
case PageUp:
scrollTopIndex -= lastSize.getRows();
if(scrollTopIndex < 0)
scrollTopIndex = 0;
break;
case PageDown:
scrollTopIndex += lastSize.getRows();
if(scrollTopIndex >= lines.size() - lastSize.getRows())
scrollTopIndex = lines.size() - lastSize.getRows();
break;
case Home:
scrollTopIndex = 0;
break;
case End:
scrollTopIndex = lines.size() - lastSize.getRows();
break;
default:
return Result.EVENT_NOT_HANDLED;
}
return Result.EVENT_HANDLED;
}
finally {
invalidate();
}
}
@Override
public boolean isScrollable() {
return true;
}
public void setMaximumSize(TerminalSize maximumSize) {
this.maximumSize = maximumSize;
}
public TerminalSize getMaximumSize() {
return maximumSize;
}
public void setMinimumSize(TerminalSize minimumSize) {
this.minimumSize = minimumSize;
}
public TerminalSize getMinimumSize() {
return minimumSize;
}
public void clear() {
lines.clear();
lines.add("");
invalidate();
}
public String getLine(int index) {
return lines.get(index);
}
public void appendLine(String line) {
lines.add(line);
if(line.length() > longestLine)
longestLine = line.length();
invalidate();
}
public void insertLine(int index, String line) {
lines.add(index, line);
if(line.length() > longestLine)
longestLine = line.length();
invalidate();
}
public void setLine(int index, String line) {
String oldLine = lines.set(index, line);
if(oldLine.length() == longestLine) {
scanForLongestLine();
}
else {
if(line.length() > longestLine)
longestLine = line.length();
}
invalidate();
}
public void removeLine(int lineNumber) {
String line = lines.get(lineNumber);
lines.remove(lineNumber);
if(line.length() >= longestLine)
scanForLongestLine();
invalidate();
}
private void printItem(TextGraphics graphics, int x, int y, String text)
{
//TODO: fix this
text = text.replace("\t", " ");
if(scrollLeftIndex >= text.length())
text = "";
else
text = text.substring(scrollLeftIndex);
if(text.length() > graphics.getWidth())
text = text.substring(0, graphics.getWidth());
graphics.drawString(x, y, text);
}
private void scanForLongestLine() {
longestLine = 0;
for(String line: lines)
if(line.replace("\t", " ").length() > longestLine)
longestLine = line.replace("\t", " ").length();
}
}