dbff8c05eb35d7231836d0c3a6c7c5b3c6cb1704
[nikiroo-utils.git] / src / jexer / io / ECMA48Screen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer.io;
32
33 import jexer.bits.Cell;
34 import jexer.bits.CellAttributes;
35
36 /**
37 * This Screen implementation draws to an xterm/ANSI X3.64/ECMA-48 type
38 * terminal.
39 */
40 public final class ECMA48Screen extends Screen {
41
42 /**
43 * Emit debugging to stderr.
44 */
45 private boolean debugToStderr;
46
47 /**
48 * We call terminal.cursor() so need the instance.
49 */
50 private ECMA48Terminal terminal;
51
52 /**
53 * Public constructor.
54 *
55 * @param terminal ECMA48Terminal to use
56 */
57 public ECMA48Screen(final ECMA48Terminal terminal) {
58 debugToStderr = false;
59
60 this.terminal = terminal;
61
62 // Query the screen size
63 setDimensions(terminal.getSessionInfo().getWindowWidth(),
64 terminal.getSessionInfo().getWindowHeight());
65 }
66
67 /**
68 * Perform a somewhat-optimal rendering of a line.
69 *
70 * @param y row coordinate. 0 is the top-most row.
71 * @param sb StringBuilder to write escape sequences to
72 * @param lastAttr cell attributes from the last call to flushLine
73 */
74 private void flushLine(final int y, final StringBuilder sb,
75 CellAttributes lastAttr) {
76
77 int lastX = -1;
78 int textEnd = 0;
79 for (int x = 0; x < width; x++) {
80 Cell lCell = logical[x][y];
81 if (!lCell.isBlank()) {
82 textEnd = x;
83 }
84 }
85 // Push textEnd to first column beyond the text area
86 textEnd++;
87
88 // DEBUG
89 // reallyCleared = true;
90
91 for (int x = 0; x < width; x++) {
92 Cell lCell = logical[x][y];
93 Cell pCell = physical[x][y];
94
95 if (!lCell.equals(pCell) || reallyCleared) {
96
97 if (debugToStderr) {
98 System.err.printf("\n--\n");
99 System.err.printf(" Y: %d X: %d\n", y, x);
100 System.err.printf(" lCell: %s\n", lCell);
101 System.err.printf(" pCell: %s\n", pCell);
102 System.err.printf(" ==== \n");
103 }
104
105 if (lastAttr == null) {
106 lastAttr = new CellAttributes();
107 sb.append(terminal.normal());
108 }
109
110 // Place the cell
111 if ((lastX != (x - 1)) || (lastX == -1)) {
112 // Advancing at least one cell, or the first gotoXY
113 sb.append(terminal.gotoXY(x, y));
114 }
115
116 assert (lastAttr != null);
117
118 if ((x == textEnd) && (textEnd < width - 1)) {
119 assert (lCell.isBlank());
120
121 for (int i = x; i < width; i++) {
122 assert (logical[i][y].isBlank());
123 // Physical is always updatesd
124 physical[i][y].reset();
125 }
126
127 // Clear remaining line
128 sb.append(terminal.clearRemainingLine());
129 lastAttr.reset();
130 return;
131 }
132
133 // Now emit only the modified attributes
134 if ((lCell.getForeColor() != lastAttr.getForeColor())
135 && (lCell.getBackColor() != lastAttr.getBackColor())
136 && (lCell.isBold() == lastAttr.isBold())
137 && (lCell.isReverse() == lastAttr.isReverse())
138 && (lCell.isUnderline() == lastAttr.isUnderline())
139 && (lCell.isBlink() == lastAttr.isBlink())
140 ) {
141 // Both colors changed, attributes the same
142 sb.append(terminal.color(lCell.getForeColor(),
143 lCell.getBackColor()));
144
145 if (debugToStderr) {
146 System.err.printf("1 Change only fore/back colors\n");
147 }
148 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
149 && (lCell.getBackColor() != lastAttr.getBackColor())
150 && (lCell.isBold() != lastAttr.isBold())
151 && (lCell.isReverse() != lastAttr.isReverse())
152 && (lCell.isUnderline() != lastAttr.isUnderline())
153 && (lCell.isBlink() != lastAttr.isBlink())
154 ) {
155 // Everything is different
156 sb.append(terminal.color(lCell.getForeColor(),
157 lCell.getBackColor(),
158 lCell.isBold(), lCell.isReverse(),
159 lCell.isBlink(),
160 lCell.isUnderline()));
161
162 if (debugToStderr) {
163 System.err.printf("2 Set all attributes\n");
164 }
165 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
166 && (lCell.getBackColor() == lastAttr.getBackColor())
167 && (lCell.isBold() == lastAttr.isBold())
168 && (lCell.isReverse() == lastAttr.isReverse())
169 && (lCell.isUnderline() == lastAttr.isUnderline())
170 && (lCell.isBlink() == lastAttr.isBlink())
171 ) {
172
173 // Attributes same, foreColor different
174 sb.append(terminal.color(lCell.getForeColor(), true));
175
176 if (debugToStderr) {
177 System.err.printf("3 Change foreColor\n");
178 }
179 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
180 && (lCell.getBackColor() != lastAttr.getBackColor())
181 && (lCell.isBold() == lastAttr.isBold())
182 && (lCell.isReverse() == lastAttr.isReverse())
183 && (lCell.isUnderline() == lastAttr.isUnderline())
184 && (lCell.isBlink() == lastAttr.isBlink())
185 ) {
186 // Attributes same, backColor different
187 sb.append(terminal.color(lCell.getBackColor(), false));
188
189 if (debugToStderr) {
190 System.err.printf("4 Change backColor\n");
191 }
192 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
193 && (lCell.getBackColor() == lastAttr.getBackColor())
194 && (lCell.isBold() == lastAttr.isBold())
195 && (lCell.isReverse() == lastAttr.isReverse())
196 && (lCell.isUnderline() == lastAttr.isUnderline())
197 && (lCell.isBlink() == lastAttr.isBlink())
198 ) {
199
200 // All attributes the same, just print the char
201 // NOP
202
203 if (debugToStderr) {
204 System.err.printf("5 Only emit character\n");
205 }
206 } else {
207 // Just reset everything again
208 sb.append(terminal.color(lCell.getForeColor(),
209 lCell.getBackColor(),
210 lCell.isBold(),
211 lCell.isReverse(),
212 lCell.isBlink(),
213 lCell.isUnderline()));
214
215 if (debugToStderr) {
216 System.err.printf("6 Change all attributes\n");
217 }
218 }
219 // Emit the character
220 sb.append(lCell.getChar());
221
222 // Save the last rendered cell
223 lastX = x;
224 lastAttr.setTo(lCell);
225
226 // Physical is always updated
227 physical[x][y].setTo(lCell);
228
229 } // if (!lCell.equals(pCell) || (reallyCleared == true))
230
231 } // for (int x = 0; x < width; x++)
232 }
233
234 /**
235 * Render the screen to a string that can be emitted to something that
236 * knows how to process ECMA-48/ANSI X3.64 escape sequences.
237 *
238 * @return escape sequences string that provides the updates to the
239 * physical screen
240 */
241 private String flushString() {
242 if (!dirty) {
243 assert (!reallyCleared);
244 return "";
245 }
246
247 CellAttributes attr = null;
248
249 StringBuilder sb = new StringBuilder();
250 if (reallyCleared) {
251 attr = new CellAttributes();
252 sb.append(terminal.clearAll());
253 }
254
255 for (int y = 0; y < height; y++) {
256 flushLine(y, sb, attr);
257 }
258
259 dirty = false;
260 reallyCleared = false;
261
262 String result = sb.toString();
263 if (debugToStderr) {
264 System.err.printf("flushString(): %s\n", result);
265 }
266 return result;
267 }
268
269 /**
270 * Push the logical screen to the physical device.
271 */
272 @Override
273 public void flushPhysical() {
274 String result = flushString();
275 if ((cursorVisible)
276 && (cursorY <= height - 1)
277 && (cursorX <= width - 1)
278 ) {
279 result += terminal.cursor(true);
280 result += terminal.gotoXY(cursorX, cursorY);
281 } else {
282 result += terminal.cursor(false);
283 }
284 terminal.getOutput().write(result);
285 terminal.flush();
286 }
287 }