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
.swing
;
21 import com
.googlecode
.lanterna
.TerminalTextUtils
;
22 import com
.googlecode
.lanterna
.TextCharacter
;
23 import com
.googlecode
.lanterna
.screen
.TabBehaviour
;
24 import com
.googlecode
.lanterna
.TerminalPosition
;
25 import com
.googlecode
.lanterna
.TerminalSize
;
26 import java
.util
.ArrayList
;
27 import java
.util
.List
;
30 * Contains the internal state of the Swing terminal
33 class VirtualTerminal
{
35 private final TextBuffer mainTextBuffer
;
36 private final TextBuffer privateModeTextBuffer
;
37 private final TerminalScrollController terminalScrollController
;
39 private TextBuffer currentBuffer
;
40 private TerminalSize size
;
41 private TerminalPosition cursorPosition
;
43 //To avoid adding more synchronization and locking, we'll store a copy of all visible lines in this list. This is
44 //also the list we return (as an iterable) so it may not be reliable as each call to getLines will change it. This
45 //isn't 100% safe but hopefully a good trade-off
46 private final List
<List
<TextCharacter
>> visibleLinesBuffer
;
50 TerminalSize initialSize
,
51 TerminalScrollController scrollController
) {
53 this.mainTextBuffer
= new TextBuffer(backlog
);
54 this.privateModeTextBuffer
= new TextBuffer(0);
55 this.terminalScrollController
= scrollController
;
57 this.currentBuffer
= mainTextBuffer
;
58 this.size
= initialSize
;
59 this.cursorPosition
= TerminalPosition
.TOP_LEFT_CORNER
;
61 this.visibleLinesBuffer
= new ArrayList
<List
<TextCharacter
>>(120);
64 void resize(TerminalSize newSize
) {
65 if(size
.getRows() < newSize
.getRows()) {
66 cursorPosition
= cursorPosition
.withRelativeRow(newSize
.getRows() - size
.getRows());
69 updateScrollingController();
73 private void updateScrollingController() {
74 int totalSize
= Math
.max(currentBuffer
.getNumberOfLines(), size
.getRows());
75 int visibleSize
= size
.getRows();
76 this.terminalScrollController
.updateModel(totalSize
, visibleSize
);
79 TerminalSize
getSize() {
83 synchronized void setCursorPosition(TerminalPosition cursorPosition
) {
84 //Make sure the cursor position is within the bounds
85 cursorPosition
= cursorPosition
.withColumn(
86 Math
.min(Math
.max(cursorPosition
.getColumn(), 0), size
.getColumns() - 1));
87 cursorPosition
= cursorPosition
.withRow(
88 Math
.min(Math
.max(cursorPosition
.getRow(), 0), size
.getRows() - 1));
90 currentBuffer
.ensurePosition(size
, cursorPosition
);
91 this.cursorPosition
= cursorPosition
;
95 TerminalPosition
getTranslatedCursorPosition() {
96 return cursorPosition
.withRelativeRow(terminalScrollController
.getScrollingOffset());
99 private void correctCursor() {
100 this.cursorPosition
=
101 new TerminalPosition(
102 Math
.min(cursorPosition
.getColumn(), size
.getColumns() - 1),
103 Math
.min(cursorPosition
.getRow(), size
.getRows() - 1));
104 this.cursorPosition
=
105 new TerminalPosition(
106 Math
.max(cursorPosition
.getColumn(), 0),
107 Math
.max(cursorPosition
.getRow(), 0));
110 synchronized TextCharacter
getCharacter(TerminalPosition position
) {
111 return currentBuffer
.getCharacter(size
, position
);
114 synchronized void putCharacter(TextCharacter terminalCharacter
) {
115 if(terminalCharacter
.getCharacter() == '\n') {
116 moveCursorToNextLine();
118 else if(terminalCharacter
.getCharacter() == '\t') {
119 int nrOfSpaces
= TabBehaviour
.ALIGN_TO_COLUMN_4
.getTabReplacement(cursorPosition
.getColumn()).length();
120 for(int i
= 0; i
< nrOfSpaces
&& cursorPosition
.getColumn() < size
.getColumns() - 1; i
++) {
121 putCharacter(terminalCharacter
.withCharacter(' '));
125 currentBuffer
.setCharacter(size
, cursorPosition
, terminalCharacter
);
128 cursorPosition
= cursorPosition
.withRelativeColumn(TerminalTextUtils
.isCharCJK(terminalCharacter
.getCharacter()) ?
2 : 1);
129 if(cursorPosition
.getColumn() >= size
.getColumns()) {
130 moveCursorToNextLine();
132 currentBuffer
.ensurePosition(size
, cursorPosition
);
137 * Method that updates the cursor position and puts a character atomically. This method is here for thread safety.
138 * The cursor position after this call will be the following position after the one specified.
139 * @param cursorPosition Position to put the character at
140 * @param terminalCharacter Character to put
142 synchronized void setCursorAndPutCharacter(TerminalPosition cursorPosition
, TextCharacter terminalCharacter
) {
143 setCursorPosition(cursorPosition
);
144 putCharacter(terminalCharacter
);
147 private void moveCursorToNextLine() {
148 cursorPosition
= cursorPosition
.withColumn(0).withRelativeRow(1);
149 if(cursorPosition
.getRow() >= size
.getRows()) {
150 cursorPosition
= cursorPosition
.withRelativeRow(-1);
151 if(currentBuffer
== mainTextBuffer
) {
152 currentBuffer
.newLine();
153 currentBuffer
.trimBacklog(size
.getRows());
154 updateScrollingController();
157 currentBuffer
.ensurePosition(size
, cursorPosition
);
160 void switchToPrivateMode() {
161 currentBuffer
= privateModeTextBuffer
;
164 void switchToNormalMode() {
165 currentBuffer
= mainTextBuffer
;
169 currentBuffer
.clear();
170 setCursorPosition(TerminalPosition
.TOP_LEFT_CORNER
);
173 synchronized Iterable
<List
<TextCharacter
>> getLines() {
174 int scrollingOffset
= terminalScrollController
.getScrollingOffset();
175 int visibleRows
= size
.getRows();
176 //Make sure scrolling isn't too far off (can be sometimes when the terminal is being resized and the scrollbar
177 //hasn't adjusted itself yet)
178 if(currentBuffer
.getNumberOfLines() > visibleRows
&&
179 scrollingOffset
+ visibleRows
> currentBuffer
.getNumberOfLines()) {
180 scrollingOffset
= currentBuffer
.getNumberOfLines() - visibleRows
;
183 visibleLinesBuffer
.clear();
184 for(List
<TextCharacter
> line
: currentBuffer
.getVisibleLines(visibleRows
, scrollingOffset
)) {
185 visibleLinesBuffer
.add(line
);
187 return visibleLinesBuffer
;