/* * This file is part of lanterna (http://code.google.com/p/lanterna/). * * lanterna is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Copyright (C) 2010-2015 Martin */ package com.googlecode.lanterna.terminal.swing; import com.googlecode.lanterna.TerminalTextUtils; import com.googlecode.lanterna.TextCharacter; import com.googlecode.lanterna.screen.TabBehaviour; import com.googlecode.lanterna.TerminalPosition; import com.googlecode.lanterna.TerminalSize; import java.util.ArrayList; import java.util.List; /** * Contains the internal state of the Swing terminal * @author martin */ class VirtualTerminal { private final TextBuffer mainTextBuffer; private final TextBuffer privateModeTextBuffer; private final TerminalScrollController terminalScrollController; private TextBuffer currentBuffer; private TerminalSize size; private TerminalPosition cursorPosition; //To avoid adding more synchronization and locking, we'll store a copy of all visible lines in this list. This is //also the list we return (as an iterable) so it may not be reliable as each call to getLines will change it. This //isn't 100% safe but hopefully a good trade-off private final List> visibleLinesBuffer; VirtualTerminal( int backlog, TerminalSize initialSize, TerminalScrollController scrollController) { this.mainTextBuffer = new TextBuffer(backlog); this.privateModeTextBuffer = new TextBuffer(0); this.terminalScrollController = scrollController; this.currentBuffer = mainTextBuffer; this.size = initialSize; this.cursorPosition = TerminalPosition.TOP_LEFT_CORNER; this.visibleLinesBuffer = new ArrayList>(120); } void resize(TerminalSize newSize) { if(size.getRows() < newSize.getRows()) { cursorPosition = cursorPosition.withRelativeRow(newSize.getRows() - size.getRows()); } this.size = newSize; updateScrollingController(); correctCursor(); } private void updateScrollingController() { int totalSize = Math.max(currentBuffer.getNumberOfLines(), size.getRows()); int visibleSize = size.getRows(); this.terminalScrollController.updateModel(totalSize, visibleSize); } TerminalSize getSize() { return size; } synchronized void setCursorPosition(TerminalPosition cursorPosition) { //Make sure the cursor position is within the bounds cursorPosition = cursorPosition.withColumn( Math.min(Math.max(cursorPosition.getColumn(), 0), size.getColumns() - 1)); cursorPosition = cursorPosition.withRow( Math.min(Math.max(cursorPosition.getRow(), 0), size.getRows() - 1)); currentBuffer.ensurePosition(size, cursorPosition); this.cursorPosition = cursorPosition; correctCursor(); } TerminalPosition getTranslatedCursorPosition() { return cursorPosition.withRelativeRow(terminalScrollController.getScrollingOffset()); } private void correctCursor() { this.cursorPosition = new TerminalPosition( Math.min(cursorPosition.getColumn(), size.getColumns() - 1), Math.min(cursorPosition.getRow(), size.getRows() - 1)); this.cursorPosition = new TerminalPosition( Math.max(cursorPosition.getColumn(), 0), Math.max(cursorPosition.getRow(), 0)); } synchronized TextCharacter getCharacter(TerminalPosition position) { return currentBuffer.getCharacter(size, position); } synchronized void putCharacter(TextCharacter terminalCharacter) { if(terminalCharacter.getCharacter() == '\n') { moveCursorToNextLine(); } else if(terminalCharacter.getCharacter() == '\t') { int nrOfSpaces = TabBehaviour.ALIGN_TO_COLUMN_4.getTabReplacement(cursorPosition.getColumn()).length(); for(int i = 0; i < nrOfSpaces && cursorPosition.getColumn() < size.getColumns() - 1; i++) { putCharacter(terminalCharacter.withCharacter(' ')); } } else { currentBuffer.setCharacter(size, cursorPosition, terminalCharacter); //Advance cursor cursorPosition = cursorPosition.withRelativeColumn(TerminalTextUtils.isCharCJK(terminalCharacter.getCharacter()) ? 2 : 1); if(cursorPosition.getColumn() >= size.getColumns()) { moveCursorToNextLine(); } currentBuffer.ensurePosition(size, cursorPosition); } } /** * Method that updates the cursor position and puts a character atomically. This method is here for thread safety. * The cursor position after this call will be the following position after the one specified. * @param cursorPosition Position to put the character at * @param terminalCharacter Character to put */ synchronized void setCursorAndPutCharacter(TerminalPosition cursorPosition, TextCharacter terminalCharacter) { setCursorPosition(cursorPosition); putCharacter(terminalCharacter); } private void moveCursorToNextLine() { cursorPosition = cursorPosition.withColumn(0).withRelativeRow(1); if(cursorPosition.getRow() >= size.getRows()) { cursorPosition = cursorPosition.withRelativeRow(-1); if(currentBuffer == mainTextBuffer) { currentBuffer.newLine(); currentBuffer.trimBacklog(size.getRows()); updateScrollingController(); } } currentBuffer.ensurePosition(size, cursorPosition); } void switchToPrivateMode() { currentBuffer = privateModeTextBuffer; } void switchToNormalMode() { currentBuffer = mainTextBuffer; } void clear() { currentBuffer.clear(); setCursorPosition(TerminalPosition.TOP_LEFT_CORNER); } synchronized Iterable> getLines() { int scrollingOffset = terminalScrollController.getScrollingOffset(); int visibleRows = size.getRows(); //Make sure scrolling isn't too far off (can be sometimes when the terminal is being resized and the scrollbar //hasn't adjusted itself yet) if(currentBuffer.getNumberOfLines() > visibleRows && scrollingOffset + visibleRows > currentBuffer.getNumberOfLines()) { scrollingOffset = currentBuffer.getNumberOfLines() - visibleRows; } visibleLinesBuffer.clear(); for(List line: currentBuffer.getVisibleLines(visibleRows, scrollingOffset)) { visibleLinesBuffer.add(line); } return visibleLinesBuffer; } }