/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library 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 2.1 of the License, or * (at your option) any later version. * * This library 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 library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.driver.console.textscreen; import java.io.PrintStream; import java.io.Reader; import java.io.Writer; import java.security.AccessController; import java.security.PrivilegedAction; import org.jnode.driver.console.ConsoleManager; import org.jnode.driver.console.InputCompleter; import org.jnode.driver.console.TextConsole; import org.jnode.driver.console.spi.AbstractConsole; import org.jnode.driver.console.spi.ConsoleWriter; import org.jnode.driver.textscreen.ScrollableTextScreen; import org.jnode.driver.textscreen.TextScreen; import org.jnode.system.event.FocusEvent; import org.jnode.system.event.FocusListener; import org.jnode.util.WriterOutputStream; import org.jnode.vm.VmSystem; import org.jnode.vm.isolate.VmIsolate; /** * @author Ewout Prangsma (epr@users.sourceforge.net) * @author Levente S\u00e1ntha (lsantha@users.sourceforge.net) */ public class TextScreenConsole extends AbstractConsole implements TextConsole { /** * The screen I'm writing on */ private TextScreen screen; /** * Width of the screen */ private final int scrWidth; /** * Height of the screen */ private final int scrHeight; /** * Current tab size */ private int tabSize = 4; /** * Current X position */ private int curX; /** * Current Y position */ private int curY; private Reader in; private final Writer out; private final Writer err; private PrintStream savedOut; private PrintStream savedErr; private boolean cursorVisible = true; private final boolean claimSystemOutErr; private VmIsolate myIsolate; /** * The options used to create this {@link TextScreenConsole} */ private final int options; /** * @param mgr * @param name * @param screen */ public TextScreenConsole(ConsoleManager mgr, String name, TextScreen screen, int options) { super(mgr, name); this.options = options; this.screen = screen; this.scrWidth = screen.getWidth(); this.scrHeight = screen.getHeight(); this.out = new ConsoleWriter(this, 0x07); this.err = new ConsoleWriter(this, 0x04); this.savedOut = new PrintStream(new WriterOutputStream(this.out, false), true); this.savedErr = new PrintStream(new WriterOutputStream(this.err, false), true); this.claimSystemOutErr = false; this.myIsolate = VmIsolate.currentIsolate(); } /** * Clear the console * * @see org.jnode.driver.console.TextConsole#clear() */ @Override public void clear() { final int size = screen.getWidth() * screen.getHeight(); screen.set(0, ' ', size, 0x07); syncScreen(0, size); } /** * Clear a given row */ @Override public void clearRow(int row) { final int size = screen.getWidth(); final int offset = screen.getOffset(0, row); screen.set(offset, ' ', size, 0x07); syncScreen(offset, size); } /** * Append characters to the current line. * * @param v * @param offset * @param length * @param color */ @Override public void putChar(char v[], int offset, int length, int color) { // This method tries to paint runs of characters on the current screen // line with one call to screen.set(int, char[], int, int, int). The // 'mark' is the offset of the start of the current run. The 'limit' // is the offset at which we must end (or have ended) the current run. int mark = 0; int limit = Math.min(length, scrWidth - curX) - 1; for (int i = 0; i < length; i++) { char c = v[i + offset]; if (c == '\n' || c == '\r' || c == '\b' || c == '\t' || i == limit) { // The current run ends now. First, output all but 'c' directly to the // current screen line, adjusting curX and curY when we're done. final int ln = i - mark; if (ln > 0) { screen.set(screen.getOffset(curX, curY), v, offset + mark, ln, color); curX += ln; if (curX >= scrWidth) { curY++; curX -= scrWidth; } } // Then output 'c' using doPutChar. This knows how to deal with // '\n', '\r', '\b' and '\t', and adjusts curX and curY accordingly. doPutChar(c, color); // Finally update 'mark' and 'limit' for the next run of characters. mark = i + 1; limit = Math.min(length - 1, i + scrWidth - curX); } } ensureVisible(screen, curY); } /** * Append a character to the current line. * * @param v * @param color */ @Override public void putChar(char v, int color) { doPutChar(v, color); ensureVisible(screen, curY); } private void doPutChar(char v, int color) { if (v == '\n') { // Goto next line // Clear till eol screen.set(screen.getOffset(curX, curY), ' ', scrWidth - curX, color); curX = 0; curY++; } else if (v == '\r') { // Goto beginning line curX = 0; } else if (v == '\b') { if (curX > 0) { curX--; } else if (curY > 0) { curX = scrWidth - 1; curY--; } setChar(curX, curY, ' ', color); } else if (v == '\t') { putChar(' ', color); while ((curX % tabSize) != 0) { putChar(' ', color); } } else { setChar(curX, curY, v, color); curX++; if (curX >= scrWidth) { curY++; curX = 0; } } while (curY >= scrHeight) { screen.copyContent(scrWidth, 0, (scrHeight - 1) * scrWidth); curY--; clearRow(curY); } } /** * @return Returns the tabSize. */ @Override public int getTabSize() { return tabSize; } @Override public void setTabSize(int tabSize) { this.tabSize = tabSize; } @Override public int getColor(int x, int y) { return screen.getColor(screen.getOffset(x, y)); } @Override public char getChar(int x, int y) { return screen.getChar(screen.getOffset(x, y)); } @Override public int getCursorX() { return curX; } @Override public int getCursorY() { return curY; } @Override public int getHeight() { return screen.getHeight(); } @Override public int getWidth() { return screen.getWidth(); } @Override public int getDeviceHeight() { return screen.getDeviceHeight(); } @Override public int getDeviceWidth() { return screen.getDeviceWidth(); } @Override public void setChar(int x, int y, char ch, int color) { int offset = screen.getOffset(x, y); screen.set(offset, ch, 1, color); syncScreen(offset, 1); } @Override public void setChar(int x, int y, char[] ch, int color) { int offset = screen.getOffset(x, y); screen.set(offset, ch, 0, ch.length, color); syncScreen(offset, ch.length); } @Override public void setChar(int x, int y, char[] ch, int cOfset, int cLength, int color) { int offset = screen.getOffset(x, y); screen.set(offset, ch, cOfset, cLength, color); syncScreen(offset, cLength); } @Override public void setCursor(int x, int y) { this.curX = x; this.curY = y; int offset = screen.setCursor(x, y); syncScreen(offset, 1); } private void syncScreen(int offset, int size) { if (isFocused()) { screen.sync(offset, size); } } @Override public InputCompleter getCompleter() { if (in instanceof KeyboardReader) { return ((KeyboardReader) in).getCompleter(); } else { return null; } } @Override public void setCompleter(InputCompleter completer) { if (in instanceof KeyboardReader) { ((KeyboardReader) in).setCompleter(completer); } } @Override public ConsoleKeyEventBindings getKeyEventBindings() { if (in instanceof KeyboardReader) { return ((KeyboardReader) in).getKeyEventBindings(); } else { throw new UnsupportedOperationException("key event bindings not available"); } } @Override public void setKeyEventBindings(ConsoleKeyEventBindings bindings) { if (in instanceof KeyboardReader) { ((KeyboardReader) in).setKeyEventBindings(bindings); } else { throw new UnsupportedOperationException("key event bindings cannt be set"); } } @Override public Reader getIn() { return in; } void setIn(Reader in) { this.in = in; } @Override public Writer getErr() { return err; } @Override public Writer getOut() { return out; } /** * Is the cursor visible. */ @Override public boolean isCursorVisible() { return cursorVisible; } /** * Make the cursor visible or not visible. * * @param visible */ @Override public void setCursorVisible(boolean visible) { this.cursorVisible = visible; int offset = screen.setCursorVisible(visible); syncScreen(offset, 1); } @Override public void focusGained(FocusEvent event) { super.focusGained(event); syncScreen(0, screen.getWidth() * screen.getHeight()); if (in instanceof FocusListener) { ((FocusListener) in).focusGained(event); } if (claimSystemOutErr && VmSystem.hasVmIOContext()) { myIsolate.invokeAndWait(new Runnable() { public void run() { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { System.setOut(savedOut); System.setErr(savedErr); return null; } }); } }); } } @Override public void focusLost(FocusEvent event) { if (in instanceof FocusListener) { ((FocusListener) in).focusLost(event); } if (claimSystemOutErr && VmSystem.hasVmIOContext()) { myIsolate.invokeAndWait(new Runnable() { public void run() { savedOut = System.out; savedErr = System.err; } }); } super.focusLost(event); } /** * @return Returns the screen. */ protected final TextScreen getScreen() { return this.screen; } /** * Get the options used to create this {@link TextScreenConsole} * * @return the options. */ final int getOptions() { return options; } @Override public void systemScreenChanged(TextScreen systemScreen) { // ensure that old and new screens are compatible if ((systemScreen.getWidth() != screen.getWidth()) || (systemScreen.getHeight() != screen.getHeight())) { throw new IllegalArgumentException("old and new screen have different sizes"); } TextScreen oldScreen = screen; screen = systemScreen; final int size = oldScreen.getWidth() * oldScreen.getHeight(); ensureVisible(oldScreen, 0); oldScreen.copyTo(screen, 0, size); syncScreen(0, size); } private final void ensureVisible(TextScreen scr, int row) { if (scr instanceof ScrollableTextScreen) { ((ScrollableTextScreen) scr).ensureVisible(row, isFocused()); } } }