Fix UTF8 bug, create first executable JAR file
[jvcard.git] / src / com / googlecode / lanterna / terminal / TerminalTextGraphics.java
CommitLineData
a3b510ab
NR
1/*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
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.
8 *
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.
13 *
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/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19package com.googlecode.lanterna.terminal;
20
21import com.googlecode.lanterna.SGR;
22import com.googlecode.lanterna.TerminalPosition;
23import com.googlecode.lanterna.TerminalSize;
24import com.googlecode.lanterna.graphics.AbstractTextGraphics;
25import com.googlecode.lanterna.TextCharacter;
26import com.googlecode.lanterna.graphics.TextGraphics;
27
28import java.io.IOException;
29import java.util.HashMap;
30import java.util.Map;
31import java.util.concurrent.atomic.AtomicInteger;
32
33/**
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.
39 * <p/>
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
42 */
43class TerminalTextGraphics extends AbstractTextGraphics {
44
45 private final Terminal terminal;
46 private final TerminalSize terminalSize;
47
48 private final Map<TerminalPosition, TextCharacter> writeHistory;
49
50 private AtomicInteger manageCallStackSize;
51 private TextCharacter lastCharacter;
52 private TerminalPosition lastPosition;
53
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;
61 }
62
63 @Override
64 public TextGraphics setCharacter(int columnIndex, int rowIndex, TextCharacter textCharacter) {
65 return setCharacter(new TerminalPosition(columnIndex, rowIndex), textCharacter);
66 }
67
68 @Override
69 public synchronized TextGraphics setCharacter(TerminalPosition position, TextCharacter textCharacter) {
70 try {
71 if(manageCallStackSize.get() > 0) {
72 if(lastCharacter == null || !lastCharacter.equals(textCharacter)) {
73 applyGraphicState(textCharacter);
74 lastCharacter = textCharacter;
75 }
76 if(lastPosition == null || !lastPosition.equals(position)) {
77 terminal.setCursorPosition(position.getColumn(), position.getRow());
78 lastPosition = position;
79 }
80 }
81 else {
82 terminal.setCursorPosition(position.getColumn(), position.getRow());
83 applyGraphicState(textCharacter);
84 }
85 terminal.putCharacter(textCharacter.getCharacter());
86 if(manageCallStackSize.get() > 0) {
87 lastPosition = position.withRelativeColumn(1);
88 }
89 writeHistory.put(position, textCharacter);
90 }
91 catch(IOException e) {
92 throw new RuntimeException(e);
93 }
94 return this;
95 }
96
97 @Override
98 public TextCharacter getCharacter(int column, int row) {
99 return getCharacter(new TerminalPosition(column, row));
100 }
101
102 @Override
103 public synchronized TextCharacter getCharacter(TerminalPosition position) {
104 return writeHistory.get(position);
105 }
106
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);
113 }
114 }
115
116 @Override
117 public TerminalSize getSize() {
118 return terminalSize;
119 }
120
121 @Override
122 public synchronized TextGraphics drawLine(TerminalPosition fromPoint, TerminalPosition toPoint, char character) {
123 try {
124 enterAtomic();
125 super.drawLine(fromPoint, toPoint, character);
126 return this;
127 }
128 finally {
129 leaveAtomic();
130 }
131 }
132
133 @Override
134 public synchronized TextGraphics drawTriangle(TerminalPosition p1, TerminalPosition p2, TerminalPosition p3, char character) {
135 try {
136 enterAtomic();
137 super.drawTriangle(p1, p2, p3, character);
138 return this;
139 }
140 finally {
141 leaveAtomic();
142 }
143 }
144
145 @Override
146 public synchronized TextGraphics fillTriangle(TerminalPosition p1, TerminalPosition p2, TerminalPosition p3, char character) {
147 try {
148 enterAtomic();
149 super.fillTriangle(p1, p2, p3, character);
150 return this;
151 }
152 finally {
153 leaveAtomic();
154 }
155 }
156
157 @Override
158 public synchronized TextGraphics fillRectangle(TerminalPosition topLeft, TerminalSize size, char character) {
159 try {
160 enterAtomic();
161 super.fillRectangle(topLeft, size, character);
162 return this;
163 }
164 finally {
165 leaveAtomic();
166 }
167 }
168
169 @Override
170 public synchronized TextGraphics drawRectangle(TerminalPosition topLeft, TerminalSize size, char character) {
171 try {
172 enterAtomic();
173 super.drawRectangle(topLeft, size, character);
174 return this;
175 }
176 finally {
177 leaveAtomic();
178 }
179 }
180
181 @Override
182 public synchronized TextGraphics putString(int column, int row, String string) {
183 try {
184 enterAtomic();
185 return super.putString(column, row, string);
186 }
187 finally {
188 leaveAtomic();
189 }
190 }
191
192 /**
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.
202 */
203 private void enterAtomic() {
204 manageCallStackSize.incrementAndGet();
205 }
206
207 private void leaveAtomic() {
208 if(manageCallStackSize.decrementAndGet() == 0) {
209 lastPosition = null;
210 lastCharacter = null;
211 }
212 }
213}