2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.terminal
;
21 import com
.googlecode
.lanterna
.SGR
;
22 import com
.googlecode
.lanterna
.TerminalPosition
;
23 import com
.googlecode
.lanterna
.TerminalSize
;
24 import com
.googlecode
.lanterna
.graphics
.AbstractTextGraphics
;
25 import com
.googlecode
.lanterna
.TextCharacter
;
26 import com
.googlecode
.lanterna
.graphics
.TextGraphics
;
28 import java
.io
.IOException
;
29 import java
.util
.HashMap
;
31 import java
.util
.concurrent
.atomic
.AtomicInteger
;
34 * This is the terminal's implementation of TextGraphics. Upon creation it takes a snapshot for the terminal's size, so
35 * that it won't require to do an expensive lookup on every call to {@code getSize()}, but this also means that it can
36 * go stale quickly if the terminal is resized. You should try to use the object quickly and then let it be GC:ed. It
37 * will not pick up on terminal resize! Also, the state of the Terminal after an operation performed by this
38 * TextGraphics implementation is undefined and you should probably re-initialize colors and modifiers.
40 * Any write operation that results in an IOException will be wrapped by a RuntimeException since the TextGraphics
41 * interface doesn't allow throwing IOException
43 class TerminalTextGraphics
extends AbstractTextGraphics
{
45 private final Terminal terminal
;
46 private final TerminalSize terminalSize
;
48 private final Map
<TerminalPosition
, TextCharacter
> writeHistory
;
50 private AtomicInteger manageCallStackSize
;
51 private TextCharacter lastCharacter
;
52 private TerminalPosition lastPosition
;
54 TerminalTextGraphics(Terminal terminal
) throws IOException
{
55 this.terminal
= terminal
;
56 this.terminalSize
= terminal
.getTerminalSize();
57 this.manageCallStackSize
= new AtomicInteger(0);
58 this.writeHistory
= new HashMap
<TerminalPosition
, TextCharacter
>();
59 this.lastCharacter
= null;
60 this.lastPosition
= null;
64 public TextGraphics
setCharacter(int columnIndex
, int rowIndex
, TextCharacter textCharacter
) {
65 return setCharacter(new TerminalPosition(columnIndex
, rowIndex
), textCharacter
);
69 public synchronized TextGraphics
setCharacter(TerminalPosition position
, TextCharacter textCharacter
) {
71 if(manageCallStackSize
.get() > 0) {
72 if(lastCharacter
== null || !lastCharacter
.equals(textCharacter
)) {
73 applyGraphicState(textCharacter
);
74 lastCharacter
= textCharacter
;
76 if(lastPosition
== null || !lastPosition
.equals(position
)) {
77 terminal
.setCursorPosition(position
.getColumn(), position
.getRow());
78 lastPosition
= position
;
82 terminal
.setCursorPosition(position
.getColumn(), position
.getRow());
83 applyGraphicState(textCharacter
);
85 terminal
.putCharacter(textCharacter
.getCharacter());
86 if(manageCallStackSize
.get() > 0) {
87 lastPosition
= position
.withRelativeColumn(1);
89 writeHistory
.put(position
, textCharacter
);
91 catch(IOException e
) {
92 throw new RuntimeException(e
);
98 public TextCharacter
getCharacter(int column
, int row
) {
99 return getCharacter(new TerminalPosition(column
, row
));
103 public synchronized TextCharacter
getCharacter(TerminalPosition position
) {
104 return writeHistory
.get(position
);
107 private void applyGraphicState(TextCharacter textCharacter
) throws IOException
{
108 terminal
.resetColorAndSGR();
109 terminal
.setForegroundColor(textCharacter
.getForegroundColor());
110 terminal
.setBackgroundColor(textCharacter
.getBackgroundColor());
111 for(SGR sgr
: textCharacter
.getModifiers()) {
112 terminal
.enableSGR(sgr
);
117 public TerminalSize
getSize() {
122 public synchronized TextGraphics
drawLine(TerminalPosition fromPoint
, TerminalPosition toPoint
, char character
) {
125 super.drawLine(fromPoint
, toPoint
, character
);
134 public synchronized TextGraphics
drawTriangle(TerminalPosition p1
, TerminalPosition p2
, TerminalPosition p3
, char character
) {
137 super.drawTriangle(p1
, p2
, p3
, character
);
146 public synchronized TextGraphics
fillTriangle(TerminalPosition p1
, TerminalPosition p2
, TerminalPosition p3
, char character
) {
149 super.fillTriangle(p1
, p2
, p3
, character
);
158 public synchronized TextGraphics
fillRectangle(TerminalPosition topLeft
, TerminalSize size
, char character
) {
161 super.fillRectangle(topLeft
, size
, character
);
170 public synchronized TextGraphics
drawRectangle(TerminalPosition topLeft
, TerminalSize size
, char character
) {
173 super.drawRectangle(topLeft
, size
, character
);
182 public synchronized TextGraphics
putString(int column
, int row
, String string
) {
185 return super.putString(column
, row
, string
);
193 * It's tricky with this implementation because we can't rely on any state in between two calls to setCharacter
194 * since the caller might modify the terminal's state outside of this writer. However, many calls inside
195 * TextGraphics will indeed make multiple calls in setCharacter where we know that the state won't change (actually,
196 * we can't be 100% sure since the caller might create a separate thread and maliciously write directly to the
197 * terminal while call one of the draw/fill/put methods in here). We could just set the state before writing every
198 * single character but that would be inefficient. Rather, we keep a counter of if we are inside an 'atomic'
199 * (meaning we know multiple calls to setCharacter will have the same state). Some drawing methods call other
200 * drawing methods internally for their implementation so that's why this is implemented with an integer value
201 * instead of a boolean; when the counter reaches zero we remove the memory of what state the terminal is in.
203 private void enterAtomic() {
204 manageCallStackSize
.incrementAndGet();
207 private void leaveAtomic() {
208 if(manageCallStackSize
.decrementAndGet() == 0) {
210 lastCharacter
= null;