Commit | Line | Data |
---|---|---|
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 | */ | |
19 | package com.googlecode.lanterna.screen; | |
20 | ||
21 | import com.googlecode.lanterna.TerminalTextUtils; | |
22 | import com.googlecode.lanterna.TerminalSize; | |
23 | import com.googlecode.lanterna.TextCharacter; | |
24 | import com.googlecode.lanterna.graphics.TextGraphics; | |
25 | import com.googlecode.lanterna.TerminalPosition; | |
26 | import com.googlecode.lanterna.graphics.TextImage; | |
27 | ||
28 | import 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 | */ | |
36 | public 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 | } |