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
.screen
;
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
;
28 import java
.io
.IOException
;
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.
36 public abstract class AbstractScreen
implements Screen
{
37 private TerminalPosition cursorPosition
;
38 private ScreenBuffer backBuffer
;
39 private ScreenBuffer frontBuffer
;
40 private final TextCharacter defaultCharacter
;
42 //How to deal with \t characters
43 private TabBehaviour tabBehaviour
;
45 //Current size of the screen
46 private TerminalSize terminalSize
;
48 //Pending resize of the screen
49 private TerminalSize latestResizeRequest
;
51 public AbstractScreen(TerminalSize initialSize
) {
52 this(initialSize
, DEFAULT_CHARACTER
);
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.
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
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;
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
79 public TerminalPosition
getCursorPosition() {
80 return cursorPosition
;
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.
87 * @param position 0-indexed column and row numbers of the new position, or if {@code null}, hides the cursor
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;
96 if(position
.getColumn() >= 0 && position
.getColumn() < terminalSize
.getColumns()
97 && position
.getRow() >= 0 && position
.getRow() < terminalSize
.getRows()) {
98 this.cursorPosition
= position
;
101 this.cursorPosition
= null;
106 public void setTabBehaviour(TabBehaviour tabBehaviour
) {
107 if(tabBehaviour
!= null) {
108 this.tabBehaviour
= tabBehaviour
;
113 public TabBehaviour
getTabBehaviour() {
118 public void setCharacter(TerminalPosition position
, TextCharacter screenCharacter
) {
119 setCharacter(position
.getColumn(), position
.getRow(), screenCharacter
);
123 public TextGraphics
newTextGraphics() {
124 return new ScreenTextGraphics(this) {
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());
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(' ');
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
);
146 //This is the normal case, no special character
147 backBuffer
.setCharacterAt(column
, row
, screenCharacter
);
150 //Pad CJK character with a trailing space
151 if(TerminalTextUtils
.isCharCJK(screenCharacter
.getCharacter())) {
152 backBuffer
.setCharacterAt(column
+ 1, row
, screenCharacter
.withCharacter(' '));
154 //If there's a CJK character immediately to our left, reset it
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(' '));
164 public synchronized TextCharacter
getFrontCharacter(TerminalPosition position
) {
165 return getFrontCharacter(position
.getColumn(), position
.getRow());
169 public TextCharacter
getFrontCharacter(int column
, int row
) {
170 return getCharacterFromBuffer(frontBuffer
, column
, row
);
174 public synchronized TextCharacter
getBackCharacter(TerminalPosition position
) {
175 return getBackCharacter(position
.getColumn(), position
.getRow());
179 public TextCharacter
getBackCharacter(int column
, int row
) {
180 return getCharacterFromBuffer(backBuffer
, column
, row
);
184 public void refresh() throws IOException
{
185 refresh(RefreshType
.AUTOMATIC
);
189 public synchronized void clear() {
190 backBuffer
.setAll(defaultCharacter
);
194 public synchronized TerminalSize
doResizeIfNecessary() {
195 TerminalSize pendingResize
= getAndClearPendingResize();
196 if(pendingResize
== null) {
200 backBuffer
= backBuffer
.resize(pendingResize
, defaultCharacter
);
201 frontBuffer
= frontBuffer
.resize(pendingResize
, defaultCharacter
);
202 return pendingResize
;
206 public TerminalSize
getTerminalSize() {
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
214 protected ScreenBuffer
getFrontBuffer() {
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
222 protected ScreenBuffer
getBackBuffer() {
226 private synchronized TerminalSize
getAndClearPendingResize() {
227 if(latestResizeRequest
!= null) {
228 terminalSize
= latestResizeRequest
;
229 latestResizeRequest
= null;
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
239 protected void addResizeRequest(TerminalSize newSize
) {
240 latestResizeRequest
= newSize
;
243 private TextCharacter
getCharacterFromBuffer(ScreenBuffer buffer
, int column
, int row
) {
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
251 else if(TerminalTextUtils
.isCharCJK(leftOfSpecifiedCharacter
.getCharacter())) {
252 return leftOfSpecifiedCharacter
;
255 return buffer
.getCharacterAt(column
, row
);
259 public String
toString() {
260 return getBackBuffer().toString();
264 * Performs the scrolling on its back-buffer.
267 public void scrollLines(int firstLine
, int lastLine
, int distance
) {
268 getBackBuffer().scrollLines(firstLine
, lastLine
, distance
);