From 217c61076c63ba87b64921bb072ccad72ecc5298 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Mon, 9 Mar 2015 22:32:35 -0400 Subject: [PATCH] ECMA48Screen compiles --- Makefile | 2 + src/jexer/io/ECMA48Screen.java | 277 +++++++++++++++++++++++++++++++ src/jexer/io/ECMA48Terminal.java | 9 + 3 files changed, 288 insertions(+) create mode 100644 src/jexer/io/ECMA48Screen.java diff --git a/Makefile b/Makefile index 2c97796..ddfabb5 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ JEXER_SRC = $(SRC_DIR)/jexer/TApplication.java \ $(SRC_DIR)/jexer/session/TSessionInfo.java \ $(SRC_DIR)/jexer/session/TTYSessionInfo.java \ $(SRC_DIR)/jexer/io/Screen.java \ + $(SRC_DIR)/jexer/io/ECMA48Screen.java \ $(SRC_DIR)/jexer/io/ECMA48Terminal.java \ $(SRC_DIR)/jexer/backend/Backend.java @@ -79,6 +80,7 @@ JEXER_BIN = $(TARGET_DIR)/jexer/TApplication.class \ $(TARGET_DIR)/jexer/session/TSessionInfo.class \ $(TARGET_DIR)/jexer/session/TTYSessionInfo.class \ $(TARGET_DIR)/jexer/io/Screen.class \ + $(TARGET_DIR)/jexer/io/ECMA48Screen.class \ $(TARGET_DIR)/jexer/io/ECMA48Terminal.class \ $(TARGET_DIR)/jexer/backend/Backend.class diff --git a/src/jexer/io/ECMA48Screen.java b/src/jexer/io/ECMA48Screen.java new file mode 100644 index 0000000..2e46e39 --- /dev/null +++ b/src/jexer/io/ECMA48Screen.java @@ -0,0 +1,277 @@ +/** + * Jexer - Java Text User Interface + * + * Version: $Id$ + * + * Author: Kevin Lamonte, kevin.lamonte@gmail.com + * + * License: LGPLv3 or later + * + * Copyright: This module is licensed under the GNU Lesser General + * Public License Version 3. Please see the file "COPYING" in this + * directory for more information about the GNU Lesser General Public + * License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program 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 + * 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 + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +package jexer.io; + +import jexer.bits.Cell; +import jexer.bits.CellAttributes; + +/** + * This Screen class draws to an xterm/ANSI X3.64/ECMA-48 type terminal. + */ +public class ECMA48Screen extends Screen { + + /** + * We call terminal.cursor() so need the instance + */ + private ECMA48Terminal terminal; + + /** + * Public constructor + * + * @param terminal ECMA48Terminal to use + */ + public ECMA48Screen(ECMA48Terminal terminal) { + this.terminal = terminal; + + // Query the screen size + setDimensions(terminal.session.getWindowWidth(), + terminal.session.getWindowHeight()); + } + + /** + * Perform a somewhat-optimal rendering of a line + * + * @param y row coordinate. 0 is the top-most row. + * @param sb StringBuilder to write escape sequences to + * @param lastAttr cell attributes from the last call to flushLine + */ + private void flushLine(int y, StringBuilder sb, CellAttributes lastAttr) { + + int lastX = -1; + int textEnd = 0; + for (int x = 0; x < width; x++) { + Cell lCell = logical[x][y]; + if (!lCell.isBlank()) { + textEnd = x; + } + } + // Push textEnd to first column beyond the text area + textEnd++; + + // DEBUG + // reallyCleared = true; + + for (int x = 0; x < width; x++) { + Cell lCell = logical[x][y]; + Cell pCell = physical[x][y]; + + if ((lCell != pCell) || (reallyCleared == true)) { + + if (debugToStderr) { + System.err.printf("\n--\n"); + System.err.printf(" Y: %d X: %d\n", y, x); + System.err.printf(" lCell: %s\n", lCell); + System.err.printf(" pCell: %s\n", pCell); + System.err.printf(" ==== \n"); + } + + if (lastAttr == null) { + lastAttr = new CellAttributes(); + sb.append(terminal.normal()); + } + + // Place the cell + if ((lastX != (x - 1)) || (lastX == -1)) { + // Advancing at least one cell, or the first gotoXY + sb.append(terminal.gotoXY(x, y)); + } + + assert(lastAttr != null); + + if ((x == textEnd) && (textEnd < width - 1)) { + assert(lCell.isBlank()); + + for (int i = x; i < width; i++) { + assert(logical[i][y].isBlank()); + // Physical is always updatesd + physical[i][y].reset(); + } + + // Clear remaining line + sb.append(terminal.clearRemainingLine()); + lastAttr.reset(); + return; + } + + // Now emit only the modified attributes + if ((lCell.foreColor != lastAttr.foreColor) && + (lCell.backColor != lastAttr.backColor) && + (lCell.bold == lastAttr.bold) && + (lCell.reverse == lastAttr.reverse) && + (lCell.underline == lastAttr.underline) && + (lCell.blink == lastAttr.blink)) { + + // Both colors changed, attributes the same + sb.append(terminal.color(lCell.foreColor, + lCell.backColor)); + + if (debugToStderr) { + System.err.printf("1 Change only fore/back colors\n"); + } + } else if ((lCell.foreColor != lastAttr.foreColor) && + (lCell.backColor != lastAttr.backColor) && + (lCell.bold != lastAttr.bold) && + (lCell.reverse != lastAttr.reverse) && + (lCell.underline != lastAttr.underline) && + (lCell.blink != lastAttr.blink)) { + + if (debugToStderr) { + System.err.printf("2 Set all attributes\n"); + } + + // Everything is different + sb.append(terminal.color(lCell.foreColor, + lCell.backColor, + lCell.bold, lCell.reverse, lCell.blink, + lCell.underline)); + + } else if ((lCell.foreColor != lastAttr.foreColor) && + (lCell.backColor == lastAttr.backColor) && + (lCell.bold == lastAttr.bold) && + (lCell.reverse == lastAttr.reverse) && + (lCell.underline == lastAttr.underline) && + (lCell.blink == lastAttr.blink)) { + + // Attributes same, foreColor different + sb.append(terminal.color(lCell.foreColor, true)); + + if (debugToStderr) { + System.err.printf("3 Change foreColor\n"); + } + + } else if ((lCell.foreColor == lastAttr.foreColor) && + (lCell.backColor != lastAttr.backColor) && + (lCell.bold == lastAttr.bold) && + (lCell.reverse == lastAttr.reverse) && + (lCell.underline == lastAttr.underline) && + (lCell.blink == lastAttr.blink)) { + + // Attributes same, backColor different + sb.append(terminal.color(lCell.backColor, false)); + + if (debugToStderr) { + System.err.printf("4 Change backColor\n"); + } + + } else if ((lCell.foreColor == lastAttr.foreColor) && + (lCell.backColor == lastAttr.backColor) && + (lCell.bold == lastAttr.bold) && + (lCell.reverse == lastAttr.reverse) && + (lCell.underline == lastAttr.underline) && + (lCell.blink == lastAttr.blink)) { + + // All attributes the same, just print the char + // NOP + + if (debugToStderr) { + System.err.printf("5 Only emit character\n"); + } + } else { + // Just reset everything again + sb.append(terminal.color(lCell.foreColor, lCell.backColor, + lCell.bold, lCell.reverse, lCell.blink, + lCell.underline)); + + if (debugToStderr) { + System.err.printf("6 Change all attributes\n"); + } + } + // Emit the character + sb.append(lCell.ch); + + // Save the last rendered cell + lastX = x; + lastAttr.setTo(lCell); + + // Physical is always updatesd + physical[x][y].setTo(lCell); + + } // if ((lCell != pCell) || (reallyCleared == true)) + + } // for (int x = 0; x < width; x++) + } + + /** + * Render the screen to a string that can be emitted to something that + * knows how to process ECMA-48/ANSI X3.64 escape sequences. + * + * @return escape sequences string that provides the updates to the + * physical screen + */ + public String flushString() { + if (dirty == false) { + assert(reallyCleared == false); + return ""; + } + + CellAttributes attr = null; + + StringBuilder sb = new StringBuilder(); + if (reallyCleared == true) { + attr = new CellAttributes(); + sb.append(terminal.clearAll()); + } + + for (int y = 0; y < height; y++) { + flushLine(y, sb, attr); + } + + dirty = false; + reallyCleared = false; + + String result = sb.toString(); + if (debugToStderr) { + System.err.printf("flushString(): %s\n", result); + } + return result; + } + + /** + * Push the logical screen to the physical device. + */ + @Override + public void flushPhysical() { + String result = flushString(); + if ((cursorVisible) && + (cursorY <= height - 1) && + (cursorX <= width - 1) + ) { + result += terminal.cursor(true); + result += terminal.gotoXY(cursorX, cursorY); + } else { + result += terminal.cursor(false); + } + terminal.getOutput().write(result); + terminal.flush(); + } +} diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index f6ae12f..8d3c0fe 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -159,6 +159,15 @@ public class ECMA48Terminal { */ private boolean brokenTerminalUTFMouse = false; + /** + * Get the output writer. + * + * @return the Writer + */ + public PrintWriter getOutput() { + return output; + } + /** * Call 'stty cooked' to set cooked mode. */ -- 2.27.0