Fix UTF8 bug, create first executable JAR file
[jvcard.git] / src / com / googlecode / lanterna / screen / AbstractScreen.java
... / ...
CommitLineData
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.screen;
20
21import com.googlecode.lanterna.TerminalTextUtils;
22import com.googlecode.lanterna.TerminalSize;
23import com.googlecode.lanterna.TextCharacter;
24import com.googlecode.lanterna.graphics.TextGraphics;
25import com.googlecode.lanterna.TerminalPosition;
26import com.googlecode.lanterna.graphics.TextImage;
27
28import java.io.IOException;
29
30/**
31 * This class implements some of the Screen logic that is not directly tied to the actual implementation of how the
32 * Screen translate to the terminal. It keeps data structures for the front- and back buffers, the cursor location and
33 * some other simpler states.
34 * @author martin
35 */
36public abstract class AbstractScreen implements Screen {
37 private TerminalPosition cursorPosition;
38 private ScreenBuffer backBuffer;
39 private ScreenBuffer frontBuffer;
40 private final TextCharacter defaultCharacter;
41
42 //How to deal with \t characters
43 private TabBehaviour tabBehaviour;
44
45 //Current size of the screen
46 private TerminalSize terminalSize;
47
48 //Pending resize of the screen
49 private TerminalSize latestResizeRequest;
50
51 public AbstractScreen(TerminalSize initialSize) {
52 this(initialSize, DEFAULT_CHARACTER);
53 }
54
55 /**
56 * Creates a new Screen on top of a supplied terminal, will query the terminal for its size. The screen is initially
57 * blank. You can specify which character you wish to be used to fill the screen initially; this will also be the
58 * character used if the terminal is enlarged and you don't set anything on the new areas.
59 *
60 * @param initialSize Size to initially create the Screen with (can be resized later)
61 * @param defaultCharacter What character to use for the initial state of the screen and expanded areas
62 */
63 @SuppressWarnings({"SameParameterValue", "WeakerAccess"})
64 public AbstractScreen(TerminalSize initialSize, TextCharacter defaultCharacter) {
65 this.frontBuffer = new ScreenBuffer(initialSize, defaultCharacter);
66 this.backBuffer = new ScreenBuffer(initialSize, defaultCharacter);
67 this.defaultCharacter = defaultCharacter;
68 this.cursorPosition = new TerminalPosition(0, 0);
69 this.tabBehaviour = TabBehaviour.ALIGN_TO_COLUMN_4;
70 this.terminalSize = initialSize;
71 this.latestResizeRequest = null;
72 }
73
74 /**
75 * @return Position where the cursor will be located after the screen has been refreshed or {@code null} if the
76 * cursor is not visible
77 */
78 @Override
79 public TerminalPosition getCursorPosition() {
80 return cursorPosition;
81 }
82
83 /**
84 * Moves the current cursor position or hides it. If the cursor is hidden and given a new position, it will be
85 * visible after this method call.
86 *
87 * @param position 0-indexed column and row numbers of the new position, or if {@code null}, hides the cursor
88 */
89 @Override
90 public void setCursorPosition(TerminalPosition position) {
91 if(position == null) {
92 //Skip any validation checks if we just want to hide the cursor
93 this.cursorPosition = null;
94 return;
95 }
96 if(position.getColumn() >= 0 && position.getColumn() < terminalSize.getColumns()
97 && position.getRow() >= 0 && position.getRow() < terminalSize.getRows()) {
98 this.cursorPosition = position;
99 }
100 else {
101 this.cursorPosition = null;
102 }
103 }
104
105 @Override
106 public void setTabBehaviour(TabBehaviour tabBehaviour) {
107 if(tabBehaviour != null) {
108 this.tabBehaviour = tabBehaviour;
109 }
110 }
111
112 @Override
113 public TabBehaviour getTabBehaviour() {
114 return tabBehaviour;
115 }
116
117 @Override
118 public void setCharacter(TerminalPosition position, TextCharacter screenCharacter) {
119 setCharacter(position.getColumn(), position.getRow(), screenCharacter);
120 }
121
122 @Override
123 public TextGraphics newTextGraphics() {
124 return new ScreenTextGraphics(this) {
125 @Override
126 public TextGraphics drawImage(TerminalPosition topLeft, TextImage image, TerminalPosition sourceImageTopLeft, TerminalSize sourceImageSize) {
127 backBuffer.copyFrom(image, sourceImageTopLeft.getRow(), sourceImageSize.getRows(), sourceImageTopLeft.getColumn(), sourceImageSize.getColumns(), topLeft.getRow(), topLeft.getColumn());
128 return this;
129 }
130 };
131 }
132
133 @Override
134 public synchronized void setCharacter(int column, int row, TextCharacter screenCharacter) {
135 //It would be nice if we didn't have to care about tabs at this level, but we have no such luxury
136 if(screenCharacter.getCharacter() == '\t') {
137 //Swap out the tab for a space
138 screenCharacter = screenCharacter.withCharacter(' ');
139
140 //Now see how many times we have to put spaces...
141 for(int i = 0; i < tabBehaviour.replaceTabs("\t", column).length(); i++) {
142 backBuffer.setCharacterAt(column + i, row, screenCharacter);
143 }
144 }
145 else {
146 //This is the normal case, no special character
147 backBuffer.setCharacterAt(column, row, screenCharacter);
148 }
149
150 //Pad CJK character with a trailing space
151 if(TerminalTextUtils.isCharCJK(screenCharacter.getCharacter())) {
152 backBuffer.setCharacterAt(column + 1, row, screenCharacter.withCharacter(' '));
153 }
154 //If there's a CJK character immediately to our left, reset it
155 if(column > 0) {
156 TextCharacter cjkTest = backBuffer.getCharacterAt(column - 1, row);
157 if(cjkTest != null && TerminalTextUtils.isCharCJK(cjkTest.getCharacter())) {
158 backBuffer.setCharacterAt(column - 1, row, backBuffer.getCharacterAt(column - 1, row).withCharacter(' '));
159 }
160 }
161 }
162
163 @Override
164 public synchronized TextCharacter getFrontCharacter(TerminalPosition position) {
165 return getFrontCharacter(position.getColumn(), position.getRow());
166 }
167
168 @Override
169 public TextCharacter getFrontCharacter(int column, int row) {
170 return getCharacterFromBuffer(frontBuffer, column, row);
171 }
172
173 @Override
174 public synchronized TextCharacter getBackCharacter(TerminalPosition position) {
175 return getBackCharacter(position.getColumn(), position.getRow());
176 }
177
178 @Override
179 public TextCharacter getBackCharacter(int column, int row) {
180 return getCharacterFromBuffer(backBuffer, column, row);
181 }
182
183 @Override
184 public void refresh() throws IOException {
185 refresh(RefreshType.AUTOMATIC);
186 }
187
188 @Override
189 public synchronized void clear() {
190 backBuffer.setAll(defaultCharacter);
191 }
192
193 @Override
194 public synchronized TerminalSize doResizeIfNecessary() {
195 TerminalSize pendingResize = getAndClearPendingResize();
196 if(pendingResize == null) {
197 return null;
198 }
199
200 backBuffer = backBuffer.resize(pendingResize, defaultCharacter);
201 frontBuffer = frontBuffer.resize(pendingResize, defaultCharacter);
202 return pendingResize;
203 }
204
205 @Override
206 public TerminalSize getTerminalSize() {
207 return terminalSize;
208 }
209
210 /**
211 * Returns the front buffer connected to this screen, don't use this unless you know what you are doing!
212 * @return This Screen's front buffer
213 */
214 protected ScreenBuffer getFrontBuffer() {
215 return frontBuffer;
216 }
217
218 /**
219 * Returns the back buffer connected to this screen, don't use this unless you know what you are doing!
220 * @return This Screen's back buffer
221 */
222 protected ScreenBuffer getBackBuffer() {
223 return backBuffer;
224 }
225
226 private synchronized TerminalSize getAndClearPendingResize() {
227 if(latestResizeRequest != null) {
228 terminalSize = latestResizeRequest;
229 latestResizeRequest = null;
230 return terminalSize;
231 }
232 return null;
233 }
234
235 /**
236 * Tells this screen that the size has changed and it should, at next opportunity, resize itself and its buffers
237 * @param newSize New size the 'real' terminal now has
238 */
239 protected void addResizeRequest(TerminalSize newSize) {
240 latestResizeRequest = newSize;
241 }
242
243 private TextCharacter getCharacterFromBuffer(ScreenBuffer buffer, int column, int row) {
244 if(column > 0) {
245 //If we are picking the padding of a CJK character, pick the actual CJK character instead of the padding
246 TextCharacter leftOfSpecifiedCharacter = buffer.getCharacterAt(column - 1, row);
247 if(leftOfSpecifiedCharacter == null) {
248 //If the character left of us doesn't exist, we don't exist either
249 return null;
250 }
251 else if(TerminalTextUtils.isCharCJK(leftOfSpecifiedCharacter.getCharacter())) {
252 return leftOfSpecifiedCharacter;
253 }
254 }
255 return buffer.getCharacterAt(column, row);
256 }
257
258 @Override
259 public String toString() {
260 return getBackBuffer().toString();
261 }
262
263 /**
264 * Performs the scrolling on its back-buffer.
265 */
266 @Override
267 public void scrollLines(int firstLine, int lastLine, int distance) {
268 getBackBuffer().scrollLines(firstLine, lastLine, distance);
269 }
270}