#5 rgbColors fix
[nikiroo-utils.git] / src / jexer / io / ECMA48Screen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.io;
30
31 import jexer.bits.Cell;
32 import jexer.bits.CellAttributes;
33
34 /**
35 * This Screen implementation draws to an xterm/ANSI X3.64/ECMA-48 type
36 * terminal.
37 */
38 public final class ECMA48Screen extends Screen {
39
40 /**
41 * Emit debugging to stderr.
42 */
43 private boolean debugToStderr = false;
44
45 /**
46 * We call terminal.cursor() so need the instance.
47 */
48 private ECMA48Terminal terminal;
49
50 /**
51 * Public constructor.
52 *
53 * @param terminal ECMA48Terminal to use
54 */
55 public ECMA48Screen(final ECMA48Terminal terminal) {
56 this.terminal = terminal;
57
58 // Query the screen size
59 setDimensions(terminal.getSessionInfo().getWindowWidth(),
60 terminal.getSessionInfo().getWindowHeight());
61 }
62
63 /**
64 * Perform a somewhat-optimal rendering of a line.
65 *
66 * @param y row coordinate. 0 is the top-most row.
67 * @param sb StringBuilder to write escape sequences to
68 * @param lastAttr cell attributes from the last call to flushLine
69 */
70 private void flushLine(final int y, final StringBuilder sb,
71 CellAttributes lastAttr) {
72
73 int lastX = -1;
74 int textEnd = 0;
75 for (int x = 0; x < width; x++) {
76 Cell lCell = logical[x][y];
77 if (!lCell.isBlank()) {
78 textEnd = x;
79 }
80 }
81 // Push textEnd to first column beyond the text area
82 textEnd++;
83
84 // DEBUG
85 // reallyCleared = true;
86
87 for (int x = 0; x < width; x++) {
88 Cell lCell = logical[x][y];
89 Cell pCell = physical[x][y];
90
91 if (!lCell.equals(pCell) || reallyCleared) {
92
93 if (debugToStderr) {
94 System.err.printf("\n--\n");
95 System.err.printf(" Y: %d X: %d\n", y, x);
96 System.err.printf(" lCell: %s\n", lCell);
97 System.err.printf(" pCell: %s\n", pCell);
98 System.err.printf(" ==== \n");
99 }
100
101 if (lastAttr == null) {
102 lastAttr = new CellAttributes();
103 sb.append(terminal.normal());
104 }
105
106 // Place the cell
107 if ((lastX != (x - 1)) || (lastX == -1)) {
108 // Advancing at least one cell, or the first gotoXY
109 sb.append(terminal.gotoXY(x, y));
110 }
111
112 assert (lastAttr != null);
113
114 if ((x == textEnd) && (textEnd < width - 1)) {
115 assert (lCell.isBlank());
116
117 for (int i = x; i < width; i++) {
118 assert (logical[i][y].isBlank());
119 // Physical is always updated
120 physical[i][y].reset();
121 }
122
123 // Clear remaining line
124 sb.append(terminal.clearRemainingLine());
125 lastAttr.reset();
126 return;
127 }
128
129 // Now emit only the modified attributes
130 if ((lCell.getForeColor() != lastAttr.getForeColor())
131 && (lCell.getBackColor() != lastAttr.getBackColor())
132 && (lCell.isBold() == lastAttr.isBold())
133 && (lCell.isReverse() == lastAttr.isReverse())
134 && (lCell.isUnderline() == lastAttr.isUnderline())
135 && (lCell.isBlink() == lastAttr.isBlink())
136 ) {
137 // Both colors changed, attributes the same
138 sb.append(terminal.color(lCell.isBold(),
139 lCell.getForeColor(), lCell.getBackColor()));
140
141 if (debugToStderr) {
142 System.err.printf("1 Change only fore/back colors\n");
143 }
144 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
145 && (lCell.getBackColor() != lastAttr.getBackColor())
146 && (lCell.isBold() != lastAttr.isBold())
147 && (lCell.isReverse() != lastAttr.isReverse())
148 && (lCell.isUnderline() != lastAttr.isUnderline())
149 && (lCell.isBlink() != lastAttr.isBlink())
150 ) {
151 // Everything is different
152 sb.append(terminal.color(lCell.getForeColor(),
153 lCell.getBackColor(),
154 lCell.isBold(), lCell.isReverse(),
155 lCell.isBlink(),
156 lCell.isUnderline()));
157
158 if (debugToStderr) {
159 System.err.printf("2 Set all attributes\n");
160 }
161 } else if ((lCell.getForeColor() != lastAttr.getForeColor())
162 && (lCell.getBackColor() == lastAttr.getBackColor())
163 && (lCell.isBold() == lastAttr.isBold())
164 && (lCell.isReverse() == lastAttr.isReverse())
165 && (lCell.isUnderline() == lastAttr.isUnderline())
166 && (lCell.isBlink() == lastAttr.isBlink())
167 ) {
168
169 // Attributes same, foreColor different
170 sb.append(terminal.color(lCell.isBold(),
171 lCell.getForeColor(), true));
172
173 if (debugToStderr) {
174 System.err.printf("3 Change foreColor\n");
175 }
176 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
177 && (lCell.getBackColor() != lastAttr.getBackColor())
178 && (lCell.isBold() == lastAttr.isBold())
179 && (lCell.isReverse() == lastAttr.isReverse())
180 && (lCell.isUnderline() == lastAttr.isUnderline())
181 && (lCell.isBlink() == lastAttr.isBlink())
182 ) {
183 // Attributes same, backColor different
184 sb.append(terminal.color(lCell.isBold(),
185 lCell.getBackColor(), false));
186
187 if (debugToStderr) {
188 System.err.printf("4 Change backColor\n");
189 }
190 } else if ((lCell.getForeColor() == lastAttr.getForeColor())
191 && (lCell.getBackColor() == lastAttr.getBackColor())
192 && (lCell.isBold() == lastAttr.isBold())
193 && (lCell.isReverse() == lastAttr.isReverse())
194 && (lCell.isUnderline() == lastAttr.isUnderline())
195 && (lCell.isBlink() == lastAttr.isBlink())
196 ) {
197
198 // All attributes the same, just print the char
199 // NOP
200
201 if (debugToStderr) {
202 System.err.printf("5 Only emit character\n");
203 }
204 } else {
205 // Just reset everything again
206 sb.append(terminal.color(lCell.getForeColor(),
207 lCell.getBackColor(),
208 lCell.isBold(),
209 lCell.isReverse(),
210 lCell.isBlink(),
211 lCell.isUnderline()));
212
213 if (debugToStderr) {
214 System.err.printf("6 Change all attributes\n");
215 }
216 }
217 // Emit the character
218 sb.append(lCell.getChar());
219
220 // Save the last rendered cell
221 lastX = x;
222 lastAttr.setTo(lCell);
223
224 // Physical is always updated
225 physical[x][y].setTo(lCell);
226
227 } // if (!lCell.equals(pCell) || (reallyCleared == true))
228
229 } // for (int x = 0; x < width; x++)
230 }
231
232 /**
233 * Render the screen to a string that can be emitted to something that
234 * knows how to process ECMA-48/ANSI X3.64 escape sequences.
235 *
236 * @return escape sequences string that provides the updates to the
237 * physical screen
238 */
239 private String flushString() {
240 if (!dirty) {
241 assert (!reallyCleared);
242 return "";
243 }
244
245 CellAttributes attr = null;
246
247 StringBuilder sb = new StringBuilder();
248 if (reallyCleared) {
249 attr = new CellAttributes();
250 sb.append(terminal.clearAll());
251 }
252
253 for (int y = 0; y < height; y++) {
254 flushLine(y, sb, attr);
255 }
256
257 dirty = false;
258 reallyCleared = false;
259
260 String result = sb.toString();
261 if (debugToStderr) {
262 System.err.printf("flushString(): %s\n", result);
263 }
264 return result;
265 }
266
267 /**
268 * Push the logical screen to the physical device.
269 */
270 @Override
271 public void flushPhysical() {
272 String result = flushString();
273 if ((cursorVisible)
274 && (cursorY <= height - 1)
275 && (cursorX <= width - 1)
276 ) {
277 result += terminal.cursor(true);
278 result += terminal.gotoXY(cursorX, cursorY);
279 } else {
280 result += terminal.cursor(false);
281 }
282 terminal.getOutput().write(result);
283 terminal.flush();
284 }
285
286 /**
287 * Set the window title.
288 *
289 * @param title the new title
290 */
291 public void setTitle(final String title) {
292 terminal.getOutput().write(terminal.setTitle(title));
293 terminal.flush();
294 }
295
296 }