package com.googlecode.lanterna.graphics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import com.googlecode.lanterna.SGR;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalTextUtils;
import com.googlecode.lanterna.TextCharacter;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.screen.TabBehaviour;
import com.googlecode.lanterna.screen.WrapBehaviour;
public class TextGraphicsWriter implements StyleSet<TextGraphicsWriter> {
private final TextGraphics backend;
private TerminalPosition cursorPosition;
private TextColor foregroundColor, backgroundColor;
private final EnumSet<SGR> style = EnumSet.noneOf(SGR.class);
private WrapBehaviour wrapBehaviour = WrapBehaviour.WORD;
private boolean styleable = true;
public TextGraphicsWriter(TextGraphics backend) {
this.backend = backend;
setStyleFrom( backend );
cursorPosition = new TerminalPosition(0, 0);
}
public TextGraphicsWriter putString(String string) {
StringBuilder wordpart = new StringBuilder();
StyleSet.Set originalStyle = new StyleSet.Set(backend);
backend.setStyleFrom(this);
int wordlen = 0;
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
switch (ch) {
case '\n':
flush(wordpart,wordlen); wordlen = 0;
linefeed(-1);
break;
case '\t':
flush(wordpart,wordlen); wordlen = 0;
if (backend.getTabBehaviour() != TabBehaviour.IGNORE) {
String repl = backend.getTabBehaviour()
.getTabReplacement(cursorPosition.getColumn());
for(int j = 0; j < repl.length(); j++) {
backend.setCharacter(cursorPosition.withRelativeColumn(j), repl.charAt(j));
}
cursorPosition = cursorPosition.withRelativeColumn(repl.length());
} else {
linefeed(2); putControlChar(ch);
}
break;
case '\033':
if (isStyleable()) {
stash(wordpart,wordlen);
String seq = TerminalTextUtils.getANSIControlSequenceAt(string, i);
TerminalTextUtils.updateModifiersFromCSICode(seq, this, originalStyle);
backend.setStyleFrom(this);
i += seq.length() - 1;
} else {
flush(wordpart,wordlen); wordlen = 0;
linefeed(2); putControlChar(ch);
}
break;
default:
if (Character.isISOControl(ch)) {
flush(wordpart,wordlen); wordlen = 0;
linefeed(1); putControlChar(ch);
} else if (Character.isWhitespace(ch)) {
flush(wordpart,wordlen); wordlen = 0;
backend.setCharacter(cursorPosition, ch);
cursorPosition = cursorPosition.withRelativeColumn(1);
} else if (TerminalTextUtils.isCharCJK(ch)) {
flush(wordpart, wordlen); wordlen = 0;
linefeed(2);
backend.setCharacter(cursorPosition, ch);
cursorPosition = cursorPosition.withRelativeColumn(2);
} else {
if (wrapBehaviour.keepWords()) {
wordpart.append(ch); wordlen++;
} else {
linefeed(1);
backend.setCharacter(cursorPosition, ch);
cursorPosition = cursorPosition.withRelativeColumn(1);
}
}
}
linefeed(wordlen);
}
flush(wordpart,wordlen);
backend.setStyleFrom(originalStyle);
return this;
}
private void linefeed(int lenToFit) {
int curCol = cursorPosition.getColumn();
int spaceLeft = backend.getSize().getColumns() - curCol;
if (wrapBehaviour.allowLineFeed()) {
boolean wantWrap = curCol > 0 && lenToFit > spaceLeft;
if (lenToFit < 0 || ( wantWrap && wrapBehaviour.autoWrap() ) ) {
cursorPosition = cursorPosition.withColumn(0).withRelativeRow(1);
}
} else {
if (lenToFit < 0) {
putControlChar('\n');
}
}
}
public void putControlChar(char ch) {
char subst;
switch (ch) {
case '\033': subst = '['; break;
case '\034': subst = '\\'; break;
case '\035': subst = ']'; break;
case '\036': subst = '^'; break;
case '\037': subst = '_'; break;
case '\177': subst = '?'; break;
default:
if (ch <= 26) {
subst = (char)(ch + '@');
} else {
backend.setCharacter(cursorPosition, ch);
cursorPosition = cursorPosition.withRelativeColumn(1);
return;
}
}
EnumSet<SGR> style = getActiveModifiers();
if (style.contains(SGR.REVERSE)) {
style.remove(SGR.REVERSE);
} else {
style.add(SGR.REVERSE);
}
TextCharacter tc = new TextCharacter('^',
getForegroundColor(), getBackgroundColor(), style);
backend.setCharacter(cursorPosition, tc);
cursorPosition = cursorPosition.withRelativeColumn(1);
tc = tc.withCharacter(subst);
backend.setCharacter(cursorPosition, tc);
cursorPosition = cursorPosition.withRelativeColumn(1);
}
private static class WordPart extends StyleSet.Set {
private final String word;
private final int wordlen;
WordPart(String word, int wordlen, StyleSet<?> style) {
this.word = word; this.wordlen = wordlen;
setStyleFrom(style);
}
}
private final List<WordPart> chunk_queue = new ArrayList<>();
private void stash(StringBuilder word, int wordlen) {
if (word.length() > 0) {
WordPart chunk = new WordPart(word.toString(),wordlen, this);
chunk_queue.add(chunk);
word.setLength(0);
}
}
private void flush(StringBuilder word, int wordlen) {
stash(word, wordlen);
if (chunk_queue.isEmpty()) {
return;
}
int row = cursorPosition.getRow();
int col = cursorPosition.getColumn();
int offset = 0;
for (WordPart chunk : chunk_queue) {
backend.setStyleFrom(chunk);
backend.putString(col+offset,row, chunk.word);
offset = chunk.wordlen;
}
chunk_queue.clear();
cursorPosition = cursorPosition.withColumn(col+offset);
backend.setStyleFrom(this);
}
public TerminalPosition getCursorPosition() {
return cursorPosition;
}
public void setCursorPosition(TerminalPosition cursorPosition) {
this.cursorPosition = cursorPosition;
}
public TextColor getForegroundColor() {
return foregroundColor;
}
public TextGraphicsWriter setForegroundColor(TextColor foreground) {
this.foregroundColor = foreground;
return this;
}
public TextColor getBackgroundColor() {
return backgroundColor;
}
public TextGraphicsWriter setBackgroundColor(TextColor background) {
this.backgroundColor = background;
return this;
}
@Override
public TextGraphicsWriter enableModifiers(SGR... modifiers) {
style.addAll(Arrays.asList(modifiers));
return this;
}
@Override
public TextGraphicsWriter disableModifiers(SGR... modifiers) {
style.removeAll(Arrays.asList(modifiers));
return this;
}
@Override
public TextGraphicsWriter setModifiers(EnumSet<SGR> modifiers) {
style.clear(); style.addAll(modifiers);
return this;
}
@Override
public TextGraphicsWriter clearModifiers() {
style.clear();
return this;
}
@Override
public EnumSet<SGR> getActiveModifiers() {
return EnumSet.copyOf(style);
}
@Override
public TextGraphicsWriter setStyleFrom(StyleSet<?> source) {
setBackgroundColor(source.getBackgroundColor());
setForegroundColor(source.getForegroundColor());
setModifiers(source.getActiveModifiers());
return this;
}
public WrapBehaviour getWrapBehaviour() {
return wrapBehaviour;
}
public void setWrapBehaviour(WrapBehaviour wrapBehaviour) {
this.wrapBehaviour = wrapBehaviour;
}
public boolean isStyleable() {
return styleable;
}
public void setStyleable(boolean styleable) {
this.styleable = styleable;
}
}